diff --git a/java/Makefile b/java/Makefile
new file mode 100644
index 000000000..0c952fa2f
--- /dev/null
+++ b/java/Makefile
@@ -0,0 +1,38 @@
+# Copyright 2018-2021 VMware, Inc.
+# SPDX-License-Identifier: Apache-2.0
+
+.suffixes:
+.PHONY: clean
+
+JAVA_HOME ?= $(shell readlink -e "$$(dirname "$$(readlink -e "$$(which javac)")")"/..)
+JAVAC = javac
+
+CFLAGS += -Iinclude -I"$(JAVA_HOME)/include" -I"$(JAVA_HOME)/include/linux" \
+ -fPIC
+LDFLAGS += -shared -lsplinterdb -DSPLINTERDB_PLATFORM_DIR=platform_linux
+
+OBJS = $(patsubst src/%.c,build/%.o,$(wildcard src/*.c))
+
+all: build SplinterDBJNI.h libsplinterdbjni.so splinterdb.jar run-test
+
+build:
+ mkdir -p include
+ mkdir -p build
+
+splinterdb.jar:
+ $(JAVAC) -d build src/SplinterDBJNI.java
+ jar cf build/splinterdbjni.jar -C build/ org/splinterdb/SplinterDBJNI.class
+
+SplinterDBJNI.h:
+ $(JAVAC) -h include/ src/SplinterDBJNI.java
+
+libsplinterdbjni.so:
+ gcc $(CFLAGS) -o build/libsplinterdbjni.so src/SplinterDBJNI.c $(LDFLAGS)
+
+run-test:
+ mvn test
+
+clean:
+ -rm -rf include
+ -rm -rf build
+ -mvn clean
diff --git a/java/pom.xml b/java/pom.xml
new file mode 100644
index 000000000..877084fb5
--- /dev/null
+++ b/java/pom.xml
@@ -0,0 +1,52 @@
+
+
+ 4.0.0
+ org.splinterdb
+ splinterdb
+ 1.0-SNAPSHOT
+
+
+ 1.8
+ 1.8
+
+
+
+
+ junit
+ junit
+ 4.11
+ test
+
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ 5.3.1
+ test
+
+
+ org.splinterdb
+ splinterdbjni
+ 1.0
+ system
+ ${project.basedir}/build/splinterdbjni.jar
+
+
+
+ test
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ 2.22.0
+
+
+ java.library.path
+ build
+
+ -Djava.library.path=build
+
+
+
+
+
diff --git a/java/src/SplinterDBJNI.c b/java/src/SplinterDBJNI.c
new file mode 100644
index 000000000..3f3ea68df
--- /dev/null
+++ b/java/src/SplinterDBJNI.c
@@ -0,0 +1,148 @@
+#include
+#include
+#include
+#include
+#include "org_splinterdb_SplinterDBJNI.h"
+#include "splinterdb/data.h"
+#include "splinterdb/default_data_config.h"
+#include "splinterdb/public_platform.h"
+#include "splinterdb/splinterdb.h"
+#include "splinterdb/public_util.h"
+
+#define Mega (1024UL * 1024UL)
+
+splinterdb *splinter;
+splinterdb_config cfg;
+data_config default_data_config;
+
+
+JNIEXPORT jint JNICALL
+Java_org_splinterdb_SplinterDBJNI_createOrOpen
+ (JNIEnv *env, jobject obj, jstring filename, jlong cache_size, jlong disk_size, jint max_key_size, jint max_value_size, jint open_existing) {
+
+
+ const char *dbname = (*env)->GetStringUTFChars(env, filename, NULL);
+
+
+ default_data_config_init(max_key_size, &default_data_config);
+
+
+ cfg =
+ (splinterdb_config) {
+ .filename = dbname,
+ .cache_size = cache_size * Mega,
+ .disk_size = disk_size * Mega,
+ .data_cfg = &default_data_config,
+ };
+
+ int rc;
+
+ if (open_existing) {
+ rc = splinterdb_open(&cfg, &splinter);
+ } else {
+ rc = splinterdb_create(&cfg, &splinter);
+ }
+
+ assert(splinter != NULL);
+
+ return rc;
+}
+
+JNIEXPORT jint JNICALL
+Java_org_splinterdb_SplinterDBJNI_insert
+ (JNIEnv *env, jobject obj, jint id, jbyteArray key, jbyteArray value) {
+
+
+ jboolean isCopy;
+
+ char * key_array = (char*)((*env)->GetByteArrayElements(env, key, &isCopy));
+ slice slice_key = slice_create((*env)->GetArrayLength( env, key ), key_array);
+
+
+ char * value_array = (char*)((*env)->GetByteArrayElements(env, value, &isCopy));
+ slice slice_value = slice_create((*env)->GetArrayLength( env, value ), value_array);
+
+ int rc = splinterdb_insert(splinter, slice_key, slice_value);
+
+ return rc;
+
+}
+
+JNIEXPORT jbyteArray JNICALL
+Java_org_splinterdb_SplinterDBJNI_lookup
+ (JNIEnv *env, jobject obj, jint id, jbyteArray key) {
+
+ jboolean isCopy;
+
+ char * key_array = (char*)((*env)->GetByteArrayElements(env, key, &isCopy));
+ slice slice_key = slice_create((*env)->GetArrayLength( env, key ), key_array);
+
+
+ splinterdb_lookup_result result;
+
+ splinterdb_lookup_result_init(splinter, &result, 0, NULL);
+
+ splinterdb_lookup(splinter, slice_key, &result);
+
+ slice value;
+
+ splinterdb_lookup_result_value(splinter, &result, &value);
+
+ char *value_chr = (char *)slice_data(value);
+
+
+ int length = sizeof(slice_data(value));
+
+
+ jbyteArray value_array = (*env)->NewByteArray(env, length);
+ (*env)->SetByteArrayRegion(env, value_array, 0, length, (char *)slice_data(value));
+
+ splinterdb_lookup_result_deinit(&result);
+
+ return value_array;
+}
+
+JNIEXPORT jint
+Java_org_splinterdb_SplinterDBJNI_delete
+ (JNIEnv *env, jobject obj, jint id, jbyteArray key) {
+
+ jboolean isCopy;
+
+ char * key_array = (char*)((*env)->GetByteArrayElements(env, key, &isCopy));
+ slice slice_key = slice_create((*env)->GetArrayLength( env, key ), key_array);
+
+
+ int rc = splinterdb_delete(splinter, slice_key);
+
+ return rc;
+
+}
+
+
+JNIEXPORT jint JNICALL
+Java_org_splinterdb_SplinterDBJNI_close
+ (JNIEnv *env, jobject obj, jint id) {
+
+ splinterdb_close(&splinter);
+
+ return 0;
+
+}
+
+JNIEXPORT jstring JNICALL
+Java_org_splinterdb_SplinterDBJNI_version
+ (JNIEnv *env, jobject object)
+{
+ const char *versionChar = splinterdb_get_version();
+
+ jstring version;
+ version = (*env)->NewStringUTF(env,versionChar);
+
+ return version;
+}
+
+
+int main (void **args)
+{
+ return 0;
+}
diff --git a/java/src/SplinterDBJNI.java b/java/src/SplinterDBJNI.java
new file mode 100644
index 000000000..aa1fe393e
--- /dev/null
+++ b/java/src/SplinterDBJNI.java
@@ -0,0 +1,62 @@
+package org.splinterdb;
+
+import java.util.concurrent.TimeUnit;
+
+public class SplinterDBJNI {
+
+
+ static {
+ System.loadLibrary("splinterdbjni");
+ }
+
+
+ public native synchronized int createOrOpen(String filename, long cacheSize, long diskSize,
+ int maxKeySize, int valueSize, int open_existing);
+
+ public native synchronized int insert(int dbid, byte[] key, byte[] value);
+
+ public native synchronized byte[] lookup(int dbid, byte[] key);
+
+ public native synchronized int delete(int dbid, byte[] key);
+
+ public native synchronized int close(int dbid);
+
+ public native synchronized String version();
+
+ public void myString() {
+ System.out.println("hello");
+ }
+
+ public static void main (String args[])
+ {
+ try {
+
+ SplinterDBJNI splinter = new SplinterDBJNI();
+
+ System.out.println(splinter.version());
+
+ int id = splinter.createOrOpen("mydb", 64, 1024, 100, 5, 0);
+
+ String keyStr = "key1";
+ byte[] keyBytes = keyStr.getBytes();
+
+ String valueStr = "value1";
+ byte[] valueBytes = valueStr.getBytes();
+
+ splinter.insert(id, keyBytes, valueBytes);
+
+ byte[] value = splinter.lookup(id, keyBytes);
+
+ String outputValueStr = new String(value);
+
+ System.out.println("My value is:" + outputValueStr);
+
+ splinter.delete(id, keyBytes);
+
+ splinter.close(id);
+
+ } catch (Exception ex) {
+ System.out.println(ex.toString());
+ }
+ }
+}
diff --git a/java/test/org/splinterdb/JavaJNITest.java b/java/test/org/splinterdb/JavaJNITest.java
new file mode 100644
index 000000000..049680e35
--- /dev/null
+++ b/java/test/org/splinterdb/JavaJNITest.java
@@ -0,0 +1,45 @@
+package org.splinterdb;
+
+import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import org.splinterdb.SplinterDBJNI;
+
+public class JavaJNITest {
+
+ @Test
+ public void testSplinterDB() {
+ try {
+
+ SplinterDBJNI splinter = new SplinterDBJNI();
+
+ System.out.println(splinter.version());
+
+ int id = splinter.createOrOpen("mydb", 64, 1024, 100, 5, 0);
+
+ String keyStr = "key1";
+ byte[] keyBytes = keyStr.getBytes();
+
+ String valueStr = "value1";
+ byte[] valueBytes = valueStr.getBytes();
+
+ splinter.insert(id, keyBytes, valueBytes);
+
+ byte[] value = splinter.lookup(id, keyBytes);
+
+ String outputValueStr = new String(value);
+
+ System.out.println("My value is:" + outputValueStr);
+
+ splinter.delete(id, keyBytes);
+
+ splinter.close(id);
+
+ } catch (Exception ex) {
+ System.out.println(ex.toString());
+
+ assertTrue(false);
+ }
+ assertTrue(true);
+
+ }
+}