diff --git a/.gitignore b/.gitignore
index 73884b91fac2a2ba2270efee9c420d94098cebe6..de25edbef1b1df341abc149d95f47b973a90087d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
 build/
 .idea/
 .gradle/
-gradle.properties
\ No newline at end of file
+gradle.properties
+target/
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..157b8780a51886db32115f65d4cf31b9c693e19c
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,34 @@
+image: maven:3-eclipse-temurin-17
+
+variables:
+  MAVEN_OPTS: "-Dhttps.protocols=TLSv1.2 -Dmaven.repo.local=$CI_PROJECT_DIR/.m2/repository -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=WARN -Dorg.slf4j.simpleLogger.showDateTime=true"
+  MAVEN_CLI_OPTS: "--batch-mode --errors --fail-at-end --show-version"
+
+cache:
+  key: "$CI_JOB_NAME"
+  paths:
+    - .m2/repository
+
+stages:
+  - build
+  - deploy
+  
+build:
+  stage: build
+  script:
+    - mkdir -p src/main/resources/sh/blake/niouring/util/Linux/amd64/
+# disabled because compiling the drivers requires a c compiler toolchain
+#    - mvn -P jni compile native:compile native:link
+#    - cp target/libnio_uring.so src/main/resources/sh/blake/niouring/util/Linux/amd64/
+    - mvn install -s ci_settings.xml -DskipTests
+  artifacts:
+    paths:
+      - target/
+
+# build the docker image for the tool, see the Dockerfile for details
+deploy:
+  stage: deploy
+  rules:
+    - if: $CI_COMMIT_TAG
+  script:
+    - mvn deploy -s ci_settings.xml -DskipTests
diff --git a/build.sh b/build.sh
new file mode 100644
index 0000000000000000000000000000000000000000..6fbce0c96d529716d86c05818e9dcb7e50a5291d
--- /dev/null
+++ b/build.sh
@@ -0,0 +1,4 @@
+mvn -P jni compile native:compile native:link
+mkdir -p src/main/resources/sh/blake/niouring/util/Linux/amd64/
+cp target/libnio_uring.so src/main/resources/sh/blake/niouring/util/Linux/amd64/
+mvn package
diff --git a/ci_settings.xml b/ci_settings.xml
new file mode 100644
index 0000000000000000000000000000000000000000..59c8c271cb4549ebcad535c1c9e1907839ba440f
--- /dev/null
+++ b/ci_settings.xml
@@ -0,0 +1,16 @@
+<settings xmlns="http://maven.apache.org/SETTINGS/1.1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.1.0 http://maven.apache.org/xsd/settings-1.1.0.xsd">
+  <servers>
+    <server>
+      <id>gitlab-maven</id>
+      <configuration>
+        <httpHeaders>
+          <property>
+            <name>Job-Token</name>
+            <value>${env.CI_JOB_TOKEN}</value>
+          </property>
+        </httpHeaders>
+      </configuration>
+    </server>
+  </servers>
+</settings>
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000000000000000000000000000000000000..16446a96fdd8c398c86a220487bbcea8ae6b034c
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,138 @@
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>sh.blake.niouring</groupId>
+    <artifactId>nio_uring-jpms</artifactId>
+    <version>0.1.4</version>
+    <packaging>jar</packaging>
+    <properties>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <jdk.version>17</jdk.version>
+        <junit.version>5.10.1</junit.version>
+        <junitplatform.version>1.10.1</junitplatform.version>
+    </properties>
+    <repositories>
+        <repository>
+            <id>central</id>
+            <name>Central Repository</name>
+            <url>https://repo.maven.apache.org/maven2</url>
+        </repository>
+    </repositories>
+    <dependencies>
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter-api</artifactId>
+            <version>${junit.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter-engine</artifactId>
+            <version>${junit.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter-params</artifactId>
+            <version>${junit.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.junit.platform</groupId>
+            <artifactId>junit-platform-commons</artifactId>
+            <version>${junitplatform.version}</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+    <profiles>
+        <profile>
+            <id>default</id>
+            <activation>
+                <activeByDefault>true</activeByDefault>
+            </activation>
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-compiler-plugin</artifactId>
+                        <version>3.12.1</version>
+                        <configuration>
+                            <release>${jdk.version}</release>
+                            <source>${jdk.version}</source>
+                            <target>${jdk.version}</target>
+                        </configuration>
+                    </plugin>
+                    <plugin>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-jar-plugin</artifactId>
+                        <version>3.3.0</version>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+        <profile>
+            <id>jni</id>
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-compiler-plugin</artifactId>
+                        <version>3.10.1</version>
+                        <executions>
+                            <execution>
+                                <phase>generate-sources</phase>
+                            </execution>
+                        </executions>
+                        <configuration>
+                            <compilerArgs>
+                                <arg>-h</arg>
+                                <arg>${project.build.directory}/include</arg>
+                            </compilerArgs>
+                            <compileSourceRoots>src/main/native</compileSourceRoots>
+                            <outputDirectory>${project.build.directory}/include</outputDirectory>
+                            <release>${jdk.version}</release>
+                            <source>${jdk.version}</source>
+                            <target>${jdk.version}</target>
+                        </configuration>
+                    </plugin>
+                    <plugin>
+                        <groupId>org.codehaus.mojo</groupId>
+                        <artifactId>native-maven-plugin</artifactId>
+                        <version>1.0-M1</version>
+                        <extensions>true</extensions>
+                        <configuration>
+                            <sources>
+                                <source>
+                                    <directory>src/main/c</directory>
+                                    <includes>
+                                        <include>**/*.c</include>
+                                    </includes>
+                                </source>
+                            </sources>
+                            <linkerFinalName>libnio_uring</linkerFinalName>
+                            <linkerFinalNameExt>so</linkerFinalNameExt>
+                            <compilerStartOptions>
+                                <compilerStartOption>-Os -c -fPIC -I${JAVA_HOME}/include -I${JAVA_HOME}/include/linux -I${project.build.directory}/include</compilerStartOption>
+                            </compilerStartOptions>
+                            <linkerStartOptions>
+                                <linkerStartOption>-shared</linkerStartOption>
+                            </linkerStartOptions>
+                            <linkerEndOptions>
+                                <linkerEndOption>-luring</linkerEndOption>
+                            </linkerEndOptions>
+                        </configuration>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+    </profiles>
+    <distributionManagement>
+        <repository>
+            <id>gitlab-maven</id>
+            <url>${env.CI_API_V4_URL}/projects/${env.CI_PROJECT_ID}/packages/maven</url>
+        </repository>
+        <snapshotRepository>
+            <id>gitlab-maven</id>
+            <url>${env.CI_API_V4_URL}/projects/${env.CI_PROJECT_ID}/packages/maven</url>
+        </snapshotRepository>
+    </distributionManagement>
+</project>
diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java
new file mode 100644
index 0000000000000000000000000000000000000000..740220449c6d6d06d8bf31567cbd1a0a4e56ec1e
--- /dev/null
+++ b/src/main/java/module-info.java
@@ -0,0 +1,5 @@
+module sh.blake.niouring {
+    opens sh.blake.niouring;
+    exports sh.blake.niouring;
+    exports sh.blake.niouring.util;
+}
diff --git a/src/main/java/sh/blake/niouring/AbstractIoUringChannel.java b/src/main/java/sh/blake/niouring/AbstractIoUringChannel.java
index 1fcc3278ca0f6bff855bfbf9fe8459b60dd87676..01b4f71347c96e68c47431dda2a919a2bb74621c 100644
--- a/src/main/java/sh/blake/niouring/AbstractIoUringChannel.java
+++ b/src/main/java/sh/blake/niouring/AbstractIoUringChannel.java
@@ -1,10 +1,11 @@
 package sh.blake.niouring;
 
-import org.eclipse.collections.impl.map.mutable.primitive.LongObjectHashMap;
 import sh.blake.niouring.util.NativeLibraryLoader;
 import sh.blake.niouring.util.ReferenceCounter;
 
 import java.nio.ByteBuffer;
+import java.util.HashMap;
+import java.util.Map;
 import java.util.function.Consumer;
 
 /**
@@ -12,8 +13,8 @@ import java.util.function.Consumer;
  */
 public abstract class AbstractIoUringChannel {
     private final int fd;
-    private final LongObjectHashMap<ReferenceCounter<ByteBuffer>> readBufferMap = new LongObjectHashMap<>();
-    private final LongObjectHashMap<ReferenceCounter<ByteBuffer>> writeBufferMap = new LongObjectHashMap<>();
+    private final Map<Long,ReferenceCounter<ByteBuffer>> readBufferMap = new HashMap<>();
+    private final Map<Long,ReferenceCounter<ByteBuffer>> writeBufferMap = new HashMap<>();
     private boolean closed = false;
     private Consumer<ByteBuffer> readHandler;
     private Consumer<ByteBuffer> writeHandler;
@@ -146,7 +147,7 @@ public abstract class AbstractIoUringChannel {
      *
      * @return the read buffer map
      */
-    LongObjectHashMap<ReferenceCounter<ByteBuffer>> readBufferMap() {
+    Map<Long,ReferenceCounter<ByteBuffer>> readBufferMap() {
         return readBufferMap;
     }
 
@@ -155,7 +156,7 @@ public abstract class AbstractIoUringChannel {
      *
      * @return the write buffer map
      */
-    LongObjectHashMap<ReferenceCounter<ByteBuffer>> writeBufferMap() {
+    Map<Long,ReferenceCounter<ByteBuffer>> writeBufferMap() {
         return writeBufferMap;
     }
 
diff --git a/src/main/java/sh/blake/niouring/IoUring.java b/src/main/java/sh/blake/niouring/IoUring.java
index e69cda392d57f1c7e6a4d4089cae2087aed3b0c8..8d12432e330f0b1ca4aec81e7f2e9af184b0d809 100644
--- a/src/main/java/sh/blake/niouring/IoUring.java
+++ b/src/main/java/sh/blake/niouring/IoUring.java
@@ -1,10 +1,11 @@
 package sh.blake.niouring;
 
-import org.eclipse.collections.impl.map.mutable.primitive.IntObjectHashMap;
 import sh.blake.niouring.util.ReferenceCounter;
 import sh.blake.niouring.util.NativeLibraryLoader;
 
 import java.nio.ByteBuffer;
+import java.util.HashMap;
+import java.util.Map;
 import java.util.function.Consumer;
 
 /**
@@ -20,7 +21,7 @@ public class IoUring {
 
     private final long ring;
     private final int ringSize;
-    private final IntObjectHashMap<AbstractIoUringChannel> fdToSocket = new IntObjectHashMap<>();
+    private final Map<Integer,AbstractIoUringChannel> fdToSocket = new HashMap<>();
     private Consumer<Exception> exceptionHandler;
     private boolean closed = false;
     private final long cqes;
diff --git a/src/main/java/sh/blake/niouring/util/ByteBufferUtil.java b/src/main/java/sh/blake/niouring/util/ByteBufferUtil.java
index bbad33ab5f7bfc19987f437d6be4a4ce30185598..fe5b6e8c0654cefa957db314e4dac8d10c268c1c 100644
--- a/src/main/java/sh/blake/niouring/util/ByteBufferUtil.java
+++ b/src/main/java/sh/blake/niouring/util/ByteBufferUtil.java
@@ -15,7 +15,7 @@ public class ByteBufferUtil {
      * @return the byte buffer
      */
     public static ByteBuffer wrapDirect(byte[] data) {
-        return (ByteBuffer) ByteBuffer.allocateDirect(data.length).put(data).flip();
+        return ByteBuffer.allocateDirect(data.length).put(data).flip();
     }
 
     /**
@@ -26,7 +26,7 @@ public class ByteBufferUtil {
      */
     public static ByteBuffer wrapDirect(String utf8) {
         byte[] data = utf8.getBytes(StandardCharsets.UTF_8);
-        return (ByteBuffer) ByteBuffer.allocateDirect(data.length).put(data).flip();
+        return ByteBuffer.allocateDirect(data.length).put(data).flip();
     }
 
     /**
@@ -41,6 +41,6 @@ public class ByteBufferUtil {
             return buffer;
         }
         buffer.flip();
-        return (ByteBuffer) ByteBuffer.allocateDirect(buffer.remaining()).put(buffer).flip();
+        return ByteBuffer.allocateDirect(buffer.remaining()).put(buffer).flip();
     }
 }
diff --git a/src/main/java/sh/blake/niouring/util/NativeLibraryLoader.java b/src/main/java/sh/blake/niouring/util/NativeLibraryLoader.java
index dbf161728229f1932945fa4a9c6806f9d5431567..6f8d84977052606dd7e86cbe64d251c4da0367e0 100644
--- a/src/main/java/sh/blake/niouring/util/NativeLibraryLoader.java
+++ b/src/main/java/sh/blake/niouring/util/NativeLibraryLoader.java
@@ -1,9 +1,9 @@
 package sh.blake.niouring.util;
 
 import java.io.File;
-import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.nio.file.Files;
 
 public class NativeLibraryLoader {
     private static boolean loadAttempted = false;
@@ -16,20 +16,16 @@ public class NativeLibraryLoader {
             }
             loadAttempted = true;
 
-            try (InputStream inputStream = NativeLibraryLoader.class.getResourceAsStream("/libnio_uring.so")) {
-                if (inputStream == null) {
-                    throw new IOException("Native library not found");
-                }
-                File tempFile = File.createTempFile("libnio_uring", ".tmp");
+            var arch = System.getProperty("os.arch");
+            var os = System.getProperty("os.name");
 
-                byte[] buffer = new byte[8192];
-                try (FileOutputStream fileOutputStream = new FileOutputStream(tempFile)) {
-                    while (inputStream.available() > 0) {
-                        int bytesRead = inputStream.read(buffer);
-                        fileOutputStream.write(buffer, 0, bytesRead);
-                    }
-                }
+            try (InputStream inputStream =
+                         NativeLibraryLoader.class.getResourceAsStream(os + "/" + arch + "/libnio_uring.so")) {
+                if (inputStream == null)
+                    throw new IOException("Native library not found");
 
+                File tempFile = File.createTempFile("libnio_uring", ".tmp");
+                Files.write(tempFile.toPath(), inputStream.readAllBytes());
                 System.load(tempFile.getAbsolutePath());
             }
         } catch (IOException ex) {
diff --git a/src/main/resources/sh/blake/niouring/util/Linux/amd64/libnio_uring.so b/src/main/resources/sh/blake/niouring/util/Linux/amd64/libnio_uring.so
new file mode 100755
index 0000000000000000000000000000000000000000..46b8eb9ca5613d86d0af0156d6e80d10f9cc3bdf
Binary files /dev/null and b/src/main/resources/sh/blake/niouring/util/Linux/amd64/libnio_uring.so differ
diff --git a/src/test/java/sh/blake/niouring/IoUringFileTest.java b/src/test/java/sh/blake/niouring/IoUringFileTest.java
index 7dcc2d1eadc3a5ea507847ecf0d5eb9091343763..ce35f2b8604b44966c759b0d129eed8b2e8f0c74 100644
--- a/src/test/java/sh/blake/niouring/IoUringFileTest.java
+++ b/src/test/java/sh/blake/niouring/IoUringFileTest.java
@@ -1,7 +1,7 @@
 package sh.blake.niouring;
 
-import org.junit.Assert;
-import org.junit.Test;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
 
 import java.nio.ByteBuffer;
 import java.nio.charset.StandardCharsets;
@@ -31,7 +31,7 @@ public class IoUringFileTest extends TestBase {
 
         attemptUntil(ioUring::execute, readSuccessfully::get);
 
-        Assert.assertTrue("File read successfully", readSuccessfully.get());
+        Assertions.assertTrue(readSuccessfully.get(), "File read successfully");
     }
 
     @Test
@@ -62,6 +62,6 @@ public class IoUringFileTest extends TestBase {
         ioUring.queueRead(file, buffer);
         attemptUntil(ioUring::execute, seekSuccessfully::get);
 
-        Assert.assertTrue("File seek successfully", seekSuccessfully.get());
+        Assertions.assertTrue(seekSuccessfully.get(), "File seek successfully");
     }
 }
diff --git a/src/test/java/sh/blake/niouring/IoUringSocketTest.java b/src/test/java/sh/blake/niouring/IoUringSocketTest.java
index 18a5802e24a8d1dad4a5dae5f9eac29933a45e4d..f361b3a28a00df27c1608755344645c3c342ccf0 100644
--- a/src/test/java/sh/blake/niouring/IoUringSocketTest.java
+++ b/src/test/java/sh/blake/niouring/IoUringSocketTest.java
@@ -1,7 +1,7 @@
 package sh.blake.niouring;
 
-import org.junit.Assert;
-import org.junit.Test;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
 import sh.blake.niouring.util.ByteBufferUtil;
 
 import java.nio.ByteBuffer;
@@ -31,16 +31,16 @@ public class IoUringSocketTest extends TestBase {
         });
 
         IoUring ioUring = new IoUring(TEST_RING_SIZE)
-            .onException(Exception::printStackTrace)
-            .queueAccept(serverSocket)
-            .queueConnect(socket);
+                .onException(Exception::printStackTrace)
+                .queueAccept(serverSocket)
+                .queueConnect(socket);
 
         attemptUntil(ioUring::execute, () -> accepted.get() && connected.get());
 
         ioUring.close();
 
-        Assert.assertTrue("Server accepted connection", accepted.get());
-        Assert.assertTrue("Client connected", connected.get());
+        Assertions.assertTrue(accepted.get(), "Server accepted connection");
+        Assertions.assertTrue(connected.get(), "Client connected");
     }
 
     @Test
@@ -58,9 +58,9 @@ public class IoUringSocketTest extends TestBase {
         socket.onException(ex -> exceptionProduced.set(true));
 
         IoUring ioUring = new IoUring(TEST_RING_SIZE)
-            .onException(Exception::printStackTrace)
-            .queueAccept(serverSocket)
-            .queueConnect(socket);
+                .onException(Exception::printStackTrace)
+                .queueAccept(serverSocket)
+                .queueConnect(socket);
 
         attemptUntil(ioUring::execute, exceptionProduced::get);
 
@@ -68,8 +68,8 @@ public class IoUringSocketTest extends TestBase {
         serverSocket.close();
         socket.close();
 
-        Assert.assertFalse("Server accepted connection", accepted.get());
-        Assert.assertTrue("Client to connect (wrong port)", exceptionProduced.get());
+        Assertions.assertFalse(accepted.get(), "Server accepted connection");
+        Assertions.assertTrue(exceptionProduced.get(), "Client to connect (wrong port)");
     }
 
     @Test
@@ -108,18 +108,18 @@ public class IoUringSocketTest extends TestBase {
         });
 
         IoUring ioUring = new IoUring(TEST_RING_SIZE)
-            .onException(Exception::printStackTrace)
-            .queueAccept(serverSocket)
-            .queueConnect(socket);
+                .onException(Exception::printStackTrace)
+                .queueAccept(serverSocket)
+                .queueConnect(socket);
 
         attemptUntil(ioUring::execute, () ->
-            serverAccepted.get() && clientConnected.get() && serverSent.get() && clientReceived.get());
+                serverAccepted.get() && clientConnected.get() && serverSent.get() && clientReceived.get());
 
         ioUring.close();
 
-        Assert.assertTrue("Server accepted connection", serverAccepted.get());
-        Assert.assertTrue("Server sent data", serverSent.get());
-        Assert.assertTrue("Client connected", clientConnected.get());
-        Assert.assertTrue("Client received data", clientReceived.get());
+        Assertions.assertTrue(serverAccepted.get(), "Server accepted connection");
+        Assertions.assertTrue(serverSent.get(), "Server sent data");
+        Assertions.assertTrue(clientConnected.get(), "Client connected");
+        Assertions.assertTrue(clientReceived.get(), "Client received data");
     }
 }