diff --git a/Makefile b/Makefile
index 97c9151c2f75e126d5a2f10797cb3605a44e701a..bca70425691042288dc58e33054fcbdf3e607b28 100644
--- a/Makefile
+++ b/Makefile
@@ -32,6 +32,10 @@ build/lib/%.o:	%.c Makefile | build/lib
 $(ADAPTORS) $(PLUGINS): 
 	$(MAKE) -C $@
 
+.PHONY: $(PLUGINS) $(ADAPTORS)
+$(ADAPTORS) $(PLUGINS): 
+	$(MAKE) -C $@
+
 .PHONY: TAR
 TAR:
 	git archive \
@@ -60,6 +64,7 @@ clean:
 	rm -f moberg-*.spec
 	rm -f moberg-*.tar.gz
 	make -C test clean
+	for d in $(ADAPTORS) ; do make -C $$d clean ; done
 
 build/libmoberg.so: build/lib/moberg.o
 build/libmoberg.so: build/lib/moberg_config.o
diff --git a/adaptors/java/Makefile b/adaptors/java/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..1687218a0c4e8e721d514409ded5c6953116424f
--- /dev/null
+++ b/adaptors/java/Makefile
@@ -0,0 +1,5 @@
+all:
+	make -f src/Makefile
+
+clean:
+	make -f src/Makefile clean
diff --git a/adaptors/java/README b/adaptors/java/README
new file mode 100644
index 0000000000000000000000000000000000000000..8dbdf8d69ca1e3611b9eec4b0ca49494e99145cf
--- /dev/null
+++ b/adaptors/java/README
@@ -0,0 +1 @@
+Moberg support for java
\ No newline at end of file
diff --git a/adaptors/java/src/Makefile b/adaptors/java/src/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..d82b2637be1a974ffecde8b1304299fd25d5dfeb
--- /dev/null
+++ b/adaptors/java/src/Makefile
@@ -0,0 +1,66 @@
+VERSION=1.0
+JARNAME=Moberg
+TARFILE=java_se.lth.control.realtime.Moberg-$(VERSION).tar.gz
+JNINAME=se_lth_control_realtime_moberg_Moberg
+
+JAVAH_PATH=$(shell javah -Xbootclasspath/p:build -version >/dev/null 2>&1 && \
+	echo -Xbootclasspath/p:build || echo -bootclasspath build)
+JNI_INCLUDE=/usr/lib/jvm/java/include/
+CC=gcc
+CC_JNI_FLAGS=-Wall -Werror -shared -fPIC \
+	     -I$(JNI_INCLUDE) -I$(JNI_INCLUDE)/linux -Ibuild \
+	     -lmoberg
+JAVADIR=/tmp
+INSTALL_DIR=$(JAVADIR)/$(JARNAME)
+JAR_DIR=$(INSTALL_DIR)/jre/lib/ext/
+SO_DIR=$(INSTALL_DIR)/jre/lib/$(shell /bin/sh ./src/getProperty_os_arch)
+SRCFILES=README \
+	 src/Makefile \
+	 src/getProperty_os_arch \
+	 $(JNINAME:%=src/%.c) \
+	 $(JAVAFILES:%=src/%)
+JAVAFILES=$(shell find src -name '*.java' | sed -e 's:^src/::g')
+
+all: JAVAC build/$(JARNAME).jar $(JNINAME:%=build/lib%.so)
+
+install: install_src install_jar install_so
+
+install_src:
+	for f in $(SRCFILES) ; do install -D $$f $(INSTALL_DIR)/$$f ; done
+
+install_jar: build/$(JARNAME).jar
+	install -d $(JAR_DIR)
+	install build/$(JARNAME).jar $(JAR_DIR)
+
+install_so: $(JNINAME:%=install_%.so)
+
+install_%.so: build/lib%.so
+	install -d $(SO_DIR)
+	install $< $(SO_DIR)
+
+JAVAC:	$(JAVAFILES:%.java=build/%.class)
+
+build/%.class:	src/%.java
+# Recompile all javafiles at once (do JIT compilation once)
+	mkdir -p build	
+	cd src ; javac -d ../build -target 1.5 -source 1.5 $(JAVAFILES)	
+
+build/$(JARNAME).jar: JAVAC
+	jar -cf $@ \
+	    -C build se \
+	     $(SRCFILES)
+
+build/%.h: $(shell echo $(JNINAME:%=build/%.class) | sed -e 's:_:/:g')
+# Too many dependencies, base the ones we need on $* (matches % above)	
+	javah -o $@ -force $(JAVAH_PATH) $(shell echo $* | sed -e s/_/./g) 
+
+build/lib%.so: $(JNINAME:%=build/%.h) $(JNINAME:%=src/%.c)
+# Too many dependencies, base the ones we need on $* (matches % above)	
+	$(CC) -o $@ $(CC_JNI_FLAGS) -I. src/$*.c
+
+clean:
+	rm -rf build *~
+
+TAR:
+	mkdir -p build
+	tar -C .. -cvzf build/$(TARFILE) $(SRCFILES:%=$(shell basename $$(pwd))/%)
diff --git a/adaptors/java/src/getProperty_os_arch b/adaptors/java/src/getProperty_os_arch
new file mode 100755
index 0000000000000000000000000000000000000000..a178e0fd0ed0680f062098222ee31e6ef49c2c4a
--- /dev/null
+++ b/adaptors/java/src/getProperty_os_arch
@@ -0,0 +1,14 @@
+#!/usr/bin/sh
+
+BASENAME=`basename $0`
+cat > ${BASENAME}.java << EOF
+public class ${BASENAME} {
+  
+  public static void main(String[] args) {
+     System.out.println(System.getProperty("os.arch"));
+  }
+}
+EOF
+javac ${BASENAME}.java
+java ${BASENAME}
+rm ${BASENAME}.java ${BASENAME}.class
diff --git a/adaptors/java/src/se/lth/control/realtime/AlreadyAllocatedException.java b/adaptors/java/src/se/lth/control/realtime/AlreadyAllocatedException.java
new file mode 100644
index 0000000000000000000000000000000000000000..91e8253fd899ff499153429657e68cffcfbf68a7
--- /dev/null
+++ b/adaptors/java/src/se/lth/control/realtime/AlreadyAllocatedException.java
@@ -0,0 +1,30 @@
+/**
+ * se.lth.control.realtime.moberg.AlreadyAllocatedException.java
+ *
+ * Copyright (C) 2005-2019  Anders Blomdell <anders.blomdell@control.lth.se>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package se.lth.control.realtime;
+
+public class AlreadyAllocatedException extends IOChannelException {
+
+  private static final long serialVersionUID = 1L;
+
+  public AlreadyAllocatedException(String kind, int index) {
+    super(kind, index, "is already allocated");
+  }
+
+}
diff --git a/adaptors/java/src/se/lth/control/realtime/AnalogIn.java b/adaptors/java/src/se/lth/control/realtime/AnalogIn.java
new file mode 100644
index 0000000000000000000000000000000000000000..3fc715f9fefa6e947ee35bb66753a7c9b9050951
--- /dev/null
+++ b/adaptors/java/src/se/lth/control/realtime/AnalogIn.java
@@ -0,0 +1,42 @@
+/**
+ * se.lth.control.realtime.AnalogIn.java
+ *
+ * Copyright (C) 2005-2019  Anders Blomdell <anders.blomdell@control.lth.se>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package se.lth.control.realtime;
+
+import se.lth.control.realtime.moberg.Moberg;
+
+public class AnalogIn extends IOChannel {
+
+  public AnalogIn(final int index) throws IOChannelException {
+    super(index);
+  }
+
+  protected void open() throws IOChannelException {
+    Moberg.analogInOpen(index);
+  }
+
+  protected void close() throws IOChannelException {
+    Moberg.analogInClose(index);
+  }
+
+  public double get() throws IOChannelException {
+    return Moberg.analogIn(index);
+  }
+
+}
diff --git a/adaptors/java/src/se/lth/control/realtime/AnalogOut.java b/adaptors/java/src/se/lth/control/realtime/AnalogOut.java
new file mode 100644
index 0000000000000000000000000000000000000000..4310079bf5560794574a3979aef27abdea591645
--- /dev/null
+++ b/adaptors/java/src/se/lth/control/realtime/AnalogOut.java
@@ -0,0 +1,46 @@
+/**
+ * se.lth.control.realtime.AnalogOut.java
+ *
+ * Copyright (C) 2005-2019  Anders Blomdell <anders.blomdell@control.lth.se>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package se.lth.control.realtime;
+
+
+import java.util.BitSet;
+
+import se.lth.control.realtime.moberg.Moberg;
+
+
+public class AnalogOut extends IOChannel {
+
+  public AnalogOut(int index) throws IOChannelException {
+    super(index);
+  }
+
+  public void open() throws IOChannelException {
+    Moberg.analogOutOpen(index);
+  }
+
+  public void close() throws IOChannelException {
+    Moberg.analogOutClose(index);
+  }
+
+  public void set(double value) throws IOChannelException {
+    Moberg.analogOut(index, value);
+  }
+
+}
diff --git a/adaptors/java/src/se/lth/control/realtime/DigitalIn.java b/adaptors/java/src/se/lth/control/realtime/DigitalIn.java
new file mode 100644
index 0000000000000000000000000000000000000000..e34e28bce878997f80d4465d178a7e40bc21adfd
--- /dev/null
+++ b/adaptors/java/src/se/lth/control/realtime/DigitalIn.java
@@ -0,0 +1,44 @@
+/**
+ * se.lth.control.realtime.DigitalIn.java
+ *
+ * Copyright (C) 2005-2019  Anders Blomdell <anders.blomdell@control.lth.se>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package se.lth.control.realtime;
+
+import java.util.BitSet;
+
+import se.lth.control.realtime.moberg.Moberg;
+
+public class DigitalIn extends IOChannel {
+
+  public DigitalIn(int index) throws IOChannelException {
+    super(index);
+  }
+
+  public void open() throws IOChannelException {
+    Moberg.digitalInOpen(index);
+  }
+
+  public void close() throws IOChannelException {
+    Moberg.digitalInClose(index);
+  }
+
+  public boolean get() throws IOChannelException {
+    return Moberg.digitalIn(index);
+  }
+
+}
diff --git a/adaptors/java/src/se/lth/control/realtime/DigitalOut.java b/adaptors/java/src/se/lth/control/realtime/DigitalOut.java
new file mode 100644
index 0000000000000000000000000000000000000000..a14f189f62d3c0b40eb729bdc7100dcd3bc67f3d
--- /dev/null
+++ b/adaptors/java/src/se/lth/control/realtime/DigitalOut.java
@@ -0,0 +1,43 @@
+/**
+ * se.lth.control.realtime.DigitalOut.java
+ *
+ * Copyright (C) 2005-2019  Anders Blomdell <anders.blomdell@control.lth.se>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package se.lth.control.realtime;
+
+import java.util.BitSet;
+
+import se.lth.control.realtime.moberg.Moberg;
+
+public class DigitalOut extends IOChannel {
+
+  public DigitalOut(int index) throws IOChannelException {
+    super(index);
+  }
+   
+  public void open() throws IOChannelException {
+    Moberg.digitalOutOpen(index);
+  }
+  public void close() throws IOChannelException {
+    Moberg.digitalOutClose(index);
+  }
+
+  public void set(boolean value) throws IOChannelException {
+    Moberg.digitalOut(index, value);
+  }
+
+}
diff --git a/adaptors/java/src/se/lth/control/realtime/EncoderIn.java b/adaptors/java/src/se/lth/control/realtime/EncoderIn.java
new file mode 100644
index 0000000000000000000000000000000000000000..d1f919d70b805a61845e512614c83b067899b52c
--- /dev/null
+++ b/adaptors/java/src/se/lth/control/realtime/EncoderIn.java
@@ -0,0 +1,44 @@
+/**
+ * se.lth.control.realtime.EncoderIn.java
+ *
+ * Copyright (C) 2005-2019  Anders Blomdell <anders.blomdell@control.lth.se>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package se.lth.control.realtime;
+
+import java.util.BitSet;
+
+import se.lth.control.realtime.moberg.Moberg;
+
+public class EncoderIn extends IOChannel {
+  
+  public EncoderIn(int index) throws IOChannelException {
+    super(index);
+  }
+
+  protected void open() throws IOChannelException {
+     Moberg.encoderInOpen(index);
+  }
+
+  protected void close() throws IOChannelException {
+    Moberg.encoderInClose(index);
+  }
+  
+  public long get() throws IOChannelException {
+    return Moberg.encoderIn(index);
+  }
+    
+}
diff --git a/adaptors/java/src/se/lth/control/realtime/IOChannel.java b/adaptors/java/src/se/lth/control/realtime/IOChannel.java
new file mode 100644
index 0000000000000000000000000000000000000000..6f83f0ceaccd811c62e326d1db393eabbe0c8179
--- /dev/null
+++ b/adaptors/java/src/se/lth/control/realtime/IOChannel.java
@@ -0,0 +1,88 @@
+/**
+ * se.lth.control.realtime.moberg.IOChannel.java
+ *
+ * Copyright (C) 2005-2019  Anders Blomdell <anders.blomdell@control.lth.se>
+ * Copyright (C) 2014 Alfred Theorin <alfred.theorin@control.lth.se>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package se.lth.control.realtime;
+
+import java.util.HashMap;
+import java.util.BitSet;
+
+public abstract class IOChannel {
+
+  private static HashMap allocations = new HashMap();
+
+  public final int index;
+  private BitSet allocation;
+  private boolean isAllocated = false;
+
+  public IOChannel(int index) throws IOChannelException {
+    this.index = index;
+    open();
+    allocate();
+  }
+
+  private BitSet getAllocation() {
+    BitSet result = allocation;
+    if (result == null) {
+      result = (BitSet)allocations.get(this.getClass().getName());
+      if (result == null) {
+	result = new BitSet();
+	allocations.put(this.getClass().getName(), result);
+      }
+    }
+    return result;
+  }
+
+  private synchronized void allocate() throws IOChannelException {
+    if (getAllocation().get(index)) {
+      // Try to free unreferenced channels 
+      System.gc();
+      System.runFinalization();
+    }
+    if (getAllocation().get(index)) {
+      try {
+	close();
+      } catch (IOChannelException e) {
+      }
+      throw new AlreadyAllocatedException(this.getClass().getName(), index);
+    }
+    getAllocation().set(index);
+    isAllocated = true;
+  }
+
+  private synchronized void deallocate() {
+    if (isAllocated) {
+      getAllocation().clear(index);
+      try {
+        close();
+      } catch (IOChannelException e) {
+      }
+      isAllocated = false;
+    }
+  }
+
+
+  protected abstract void open() throws IOChannelException;
+  protected abstract void close() throws IOChannelException;
+
+  protected void finalize() throws IOChannelException {
+    deallocate();
+  }
+
+}
diff --git a/adaptors/java/src/se/lth/control/realtime/IOChannelException.java b/adaptors/java/src/se/lth/control/realtime/IOChannelException.java
new file mode 100644
index 0000000000000000000000000000000000000000..69ff09630fd1288b978aff56737ccbdf7ddd3d31
--- /dev/null
+++ b/adaptors/java/src/se/lth/control/realtime/IOChannelException.java
@@ -0,0 +1,32 @@
+/**
+ * se.lth.control.realtime.IOChannelException.java
+ *
+ * Copyright (C) 2005-2019  Anders Blomdell <anders.blomdell@control.lth.se>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package se.lth.control.realtime;
+
+import java.io.IOException;
+
+public class IOChannelException extends IOException {
+
+  private static final long serialVersionUID = 1L;
+
+  public IOChannelException(String kind, int index, String s) {
+    super(kind + " channel #" + index + " " + s);
+  }
+
+}
diff --git a/adaptors/java/src/se/lth/control/realtime/moberg/Moberg.java b/adaptors/java/src/se/lth/control/realtime/moberg/Moberg.java
new file mode 100644
index 0000000000000000000000000000000000000000..2a79c03ccf8cade5e6be27dcec2c9008621195c4
--- /dev/null
+++ b/adaptors/java/src/se/lth/control/realtime/moberg/Moberg.java
@@ -0,0 +1,51 @@
+/**
+ * se.lth.control.realtime.moberg.Moberg.java
+ *
+ * Copyright (C) 2005-2019  Anders Blomdell <anders.blomdell@control.lth.se>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package se.lth.control.realtime.moberg;
+
+import java.io.File;
+
+public class Moberg {
+  public static native void analogInOpen(int index) throws MobergException;
+  public static native void analogInClose(int index) throws MobergException;
+  public static native double analogIn(int index) throws MobergException;
+
+  public static native void analogOutOpen(int index) throws MobergException;
+  public static native void analogOutClose(int index) throws MobergException;
+  public static native void analogOut(int index, double value) throws MobergException;
+
+  public static native void digitalInOpen(int index) throws MobergException;
+  public static native void digitalInClose(int index) throws MobergException;
+  public static native boolean digitalIn(int index) throws MobergException;
+
+  public static native void digitalOutOpen(int index) throws MobergException;
+  public static native void digitalOutClose(int index) throws MobergException;
+  public static native void digitalOut(int index, boolean value) throws MobergException;
+
+  public static native void encoderInOpen(int index) throws MobergException;
+  public static native void encoderInClose(int index) throws MobergException;
+  public static native long encoderIn(int index) throws MobergException;
+
+  private static native void Init();
+
+  static  {
+    System.loadLibrary("se_lth_control_realtime_moberg_Moberg");
+    Init();
+  }
+}
diff --git a/adaptors/java/src/se/lth/control/realtime/moberg/MobergConfigurationFileNotFoundError.java b/adaptors/java/src/se/lth/control/realtime/moberg/MobergConfigurationFileNotFoundError.java
new file mode 100644
index 0000000000000000000000000000000000000000..7daa0fed48dba5c7c7978d11a43a5aa060bbc7e0
--- /dev/null
+++ b/adaptors/java/src/se/lth/control/realtime/moberg/MobergConfigurationFileNotFoundError.java
@@ -0,0 +1,31 @@
+/**
+ * se.lth.control.realtime.moberg.MobergConfigurationFileNotFoundError.java
+ *
+ * Copyright (C) 2005-2019  Anders Blomdell <anders.blomdell@control.lth.se>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package se.lth.control.realtime.moberg;
+
+
+public class MobergConfigurationFileNotFoundError extends Error {
+  
+  private static final long serialVersionUID = 1L;
+  
+  public MobergConfigurationFileNotFoundError(int index) {
+    super("Moberg configuration file '/etc/moberg.conf' was not found");
+  }
+  
+}
diff --git a/adaptors/java/src/se/lth/control/realtime/moberg/MobergDeviceDoesNotExistException.java b/adaptors/java/src/se/lth/control/realtime/moberg/MobergDeviceDoesNotExistException.java
new file mode 100644
index 0000000000000000000000000000000000000000..ffc876f1e0834f0eef05f974327e6d3e11ee7b02
--- /dev/null
+++ b/adaptors/java/src/se/lth/control/realtime/moberg/MobergDeviceDoesNotExistException.java
@@ -0,0 +1,31 @@
+/**
+ * se.lth.control.realtime.moberg.MobergDeviceDoesNotExistException.java
+ *
+ * Copyright (C) 2005-2019  Anders Blomdell <anders.blomdell@control.lth.se>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package se.lth.control.realtime.moberg;
+
+
+public class MobergDeviceDoesNotExistException extends MobergException {
+  
+  private static final long serialVersionUID = 1L;
+  
+  public MobergDeviceDoesNotExistException(int index) {
+    super(index, "does not exist");
+  }
+  
+}
diff --git a/adaptors/java/src/se/lth/control/realtime/moberg/MobergDeviceReadException.java b/adaptors/java/src/se/lth/control/realtime/moberg/MobergDeviceReadException.java
new file mode 100644
index 0000000000000000000000000000000000000000..2b9b056b13df7816d71b44daca60f431fad2edff
--- /dev/null
+++ b/adaptors/java/src/se/lth/control/realtime/moberg/MobergDeviceReadException.java
@@ -0,0 +1,31 @@
+/**
+ * se.lth.control.realtime.moberg.MobergDeviceReadException.java
+ *
+ * Copyright (C) 2005-2019  Anders Blomdell <anders.blomdell@control.lth.se>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package se.lth.control.realtime.moberg;
+
+
+public class MobergDeviceReadException extends MobergException {
+  
+  private static final long serialVersionUID = 1L;
+  
+  public MobergDeviceReadException(int index) {
+    super(index, "read failed");
+  }
+  
+}
diff --git a/adaptors/java/src/se/lth/control/realtime/moberg/MobergDeviceWriteException.java b/adaptors/java/src/se/lth/control/realtime/moberg/MobergDeviceWriteException.java
new file mode 100644
index 0000000000000000000000000000000000000000..fa24bd15f85c07623fbc7ecb95a606b644397736
--- /dev/null
+++ b/adaptors/java/src/se/lth/control/realtime/moberg/MobergDeviceWriteException.java
@@ -0,0 +1,31 @@
+/**
+ * se.lth.control.realtime.moberg.MobergDeviceWriteException.java
+ *
+ * Copyright (C) 2005-2019  Anders Blomdell <anders.blomdell@control.lth.se>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package se.lth.control.realtime.moberg;
+
+
+public class MobergDeviceWriteException extends MobergException {
+  
+  private static final long serialVersionUID = 1L;
+  
+  public MobergDeviceWriteException(int index) {
+    super(index, "write failed");
+  }
+  
+}
diff --git a/adaptors/java/src/se/lth/control/realtime/moberg/MobergException.java b/adaptors/java/src/se/lth/control/realtime/moberg/MobergException.java
new file mode 100644
index 0000000000000000000000000000000000000000..015acd002c10f33bab4ba6efac97b573c2d7e3f3
--- /dev/null
+++ b/adaptors/java/src/se/lth/control/realtime/moberg/MobergException.java
@@ -0,0 +1,32 @@
+/**
+ * se.lth.control.realtime.moberg.MobergException.java
+ *
+ * Copyright (C) 2005-2019  Anders Blomdell <anders.blomdell@control.lth.se>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package se.lth.control.realtime.moberg;
+
+import se.lth.control.realtime.IOChannelException;
+
+public class MobergException extends IOChannelException {
+
+  private static final long serialVersionUID = 1L;
+
+  public MobergException(int index, String reason) {
+    super("Moberg", index, reason);
+  }
+
+}
diff --git a/adaptors/java/src/se/lth/control/realtime/moberg/MobergNotOpenException.java b/adaptors/java/src/se/lth/control/realtime/moberg/MobergNotOpenException.java
new file mode 100644
index 0000000000000000000000000000000000000000..2e23889d1aecc3c1518f61893b951b4c829775d9
--- /dev/null
+++ b/adaptors/java/src/se/lth/control/realtime/moberg/MobergNotOpenException.java
@@ -0,0 +1,12 @@
+package se.lth.control.realtime.moberg;
+
+
+public class MobergNotOpenException extends MobergException {
+  
+  private static final long serialVersionUID = 1L;
+  
+  public MobergNotOpenException(int index) {
+    super(index, "is not open");
+  }
+  
+}
diff --git a/adaptors/java/src/se/lth/control/realtime/moberg/MobergRangeNotFoundException.java b/adaptors/java/src/se/lth/control/realtime/moberg/MobergRangeNotFoundException.java
new file mode 100644
index 0000000000000000000000000000000000000000..fb4d8d0ac2b3cd1bd3f23a145d51d9dac0b77c2c
--- /dev/null
+++ b/adaptors/java/src/se/lth/control/realtime/moberg/MobergRangeNotFoundException.java
@@ -0,0 +1,12 @@
+package se.lth.control.realtime.moberg;
+
+
+public class MobergRangeNotFoundException extends MobergException {
+  
+  private static final long serialVersionUID = 1L;
+  
+  public MobergRangeNotFoundException(int index) {
+    super(index, "has no range information");
+  }
+  
+}
diff --git a/adaptors/java/src/se_lth_control_realtime_moberg_Moberg.c b/adaptors/java/src/se_lth_control_realtime_moberg_Moberg.c
new file mode 100644
index 0000000000000000000000000000000000000000..9363df3b46e52f98835d00e215429764613e5f73
--- /dev/null
+++ b/adaptors/java/src/se_lth_control_realtime_moberg_Moberg.c
@@ -0,0 +1,413 @@
+/**
+ * se_lth_control_realtime_moberg_Moberg.c
+ *
+ * Copyright (C) 2005-2019  Anders Blomdell <anders.blomdell@control.lth.se>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <jni.h>
+#include "se_lth_control_realtime_moberg_Moberg.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <string.h>
+#include <moberg.h>
+
+static void throwMoberg(JNIEnv *env, int chan, char *exceptionName)
+{
+  jclass exceptionClass = 0;
+  jmethodID exceptionClassInit = 0;
+  jobject exception = 0;
+  jint result = -1;
+
+  exceptionClass = 
+    (*env)->FindClass(env, exceptionName);
+  if (exceptionClass) {
+     exceptionClassInit = (*env)->GetMethodID(env, exceptionClass,
+					      "<init>", "(I)V");
+  }
+  if (exceptionClassInit) {
+    exception = (*env)->NewObject(env, exceptionClass, exceptionClassInit, 
+				  chan);
+  }
+  if (exception) {
+    result = (*env)->Throw(env, exception);
+  }
+  if (result != 0) {
+    fprintf(stderr, "%s: %d\n", exceptionName, chan);
+  }
+}
+
+static void throwMobergNotOpenException(JNIEnv *env, int chan)
+{
+  throwMoberg(env, chan, "se/lth/control/realtime/moberg/MobergNotOpenException");		      
+}
+
+static void throwMobergDeviceDoesNotExistException(JNIEnv *env, int chan)
+{
+  throwMoberg(env, chan, "se/lth/control/realtime/moberg/MobergDeviceDoesNotExistException");		      
+}
+
+#if 0
+static void throwMobergDeviceReadException(JNIEnv *env, int chan)
+{
+  throwMoberg(env, chan, "se/lth/control/realtime/moberg/MobergDeviceReadException");		      
+}
+
+static void throwMobergDeviceWriteException(JNIEnv *env, int chan)
+{
+  throwMoberg(env, chan, "se/lth/control/realtime/moberg/MobergDeviceWriteException");		      
+}
+
+static void throwMobergRangeNotFoundException(JNIEnv *env, int chan)
+{
+  throwMoberg(env, chan, "se/lth/control/realtime/moberg/MobergRangeNotFoundException");		      
+}
+
+static void throwMobergConfigurationFileNotFoundError(JNIEnv *env)
+{
+  throwMoberg(env, 0, "se/lth/control/realtime/moberg/MobergConfigurationFileNotFoundError");		      
+}
+#endif
+
+static struct list {
+  int capacity;
+  struct channel {
+    int count;
+    union {
+      struct moberg_analog_in analog_in;
+      struct moberg_analog_out analog_out;
+      struct moberg_digital_in digital_in;
+      struct moberg_digital_out digital_out;
+      struct moberg_encoder_in encoder_in;
+    };
+  } *channel;
+} analog_in = { .capacity=0, .channel=NULL },
+  analog_out = { .capacity=0, .channel=NULL },
+  digital_in = { .capacity=0, .channel=NULL },
+  digital_out = { .capacity=0, .channel=NULL },
+  encoder_in = { .capacity=0, .channel=NULL };
+
+struct {
+  int count;
+  struct moberg *moberg;
+} g_moberg = { 0, NULL };
+
+static int up()
+{
+  if (g_moberg.count <= 0) {
+    g_moberg.moberg = moberg_new(NULL);
+  }
+  g_moberg.count++;
+  return 0;
+}
+
+static int down()
+{
+  g_moberg.count--;
+  if (g_moberg.count <= 0) {
+    moberg_free(g_moberg.moberg);
+    g_moberg.moberg = NULL;
+  }
+  return 0;
+}
+
+static struct channel *channel_get(struct list *list, int index)
+{
+  if (index < list->capacity && list->channel[index].count > 0) {
+    return &list->channel[index];
+  }
+  return NULL;
+}
+
+static int channel_set(struct list *list, int index, struct channel channel)
+{
+  if (list->capacity < index) {
+    int capacity = index + 1;
+    void *new = realloc(list->channel, capacity * sizeof(*list->channel));
+    if (new) {
+      void *p = new + list->capacity * sizeof(*list->channel);
+      memset(p, 0, (capacity - list->capacity) * sizeof(*list->channel));
+      list->channel = new;
+      list->capacity = capacity;
+    }
+  }
+  if (0 <= index && index < list->capacity) {
+    list->channel[index] = channel;
+    return 1;
+  } else {
+    return 0;
+  }
+}
+
+static int channel_up(struct list *list, int index)
+{
+  struct channel *channel = channel_get(list, index);
+  if (channel) {
+    up();
+    channel->count++;
+    return 1;
+  }
+  return 0;
+}
+
+static struct channel *channel_down(struct list *list, int index)
+{
+  struct channel *channel = channel_get(list, index);
+  if (channel) {
+    down();
+    channel->count--;
+    return channel;
+  }
+  return NULL;
+}
+
+JNIEXPORT void JNICALL 
+Java_se_lth_control_realtime_moberg_Moberg_analogInOpen(
+  JNIEnv *env, jclass obj, jint index
+)
+{
+  if (! channel_up(&analog_in, index)) {
+    struct channel channel;
+    up();
+    if (! moberg_analog_in_open(g_moberg.moberg, index, &channel.analog_in)) {
+      down();
+      throwMobergDeviceDoesNotExistException(env, index);      
+    } else {
+      channel.count = 1;
+      channel_set(&analog_in, index, channel);
+    }
+  }
+}
+
+JNIEXPORT void JNICALL 
+Java_se_lth_control_realtime_moberg_Moberg_analogInClose(
+  JNIEnv *env, jclass obj, jint index
+)
+{
+  struct channel *channel = channel_down(&analog_in, index);
+  if (! channel) {
+    throwMobergDeviceDoesNotExistException(env, index);
+  } else if (channel->count == 0) {
+    moberg_analog_in_close(g_moberg.moberg, index, channel->analog_in);
+  }
+}
+
+
+JNIEXPORT jdouble JNICALL 
+Java_se_lth_control_realtime_moberg_Moberg_analogIn(
+  JNIEnv *env, jobject obj, jint index
+) 
+{
+  double result = 0.0;
+
+  struct channel *channel = channel_get(&analog_in, index);
+  if (! channel) {
+    throwMobergNotOpenException(env, index);
+  } else {
+    channel->analog_in.read(channel->analog_in.context, &result);
+  }
+  return result;
+}
+
+JNIEXPORT void JNICALL 
+Java_se_lth_control_realtime_moberg_Moberg_analogOutOpen(
+  JNIEnv *env, jclass obj, jint index
+)
+{
+  if (! channel_up(&analog_out, index)) {
+    struct channel channel;
+    up();
+    if (! moberg_analog_out_open(g_moberg.moberg, index, &channel.analog_out)) {
+      down();
+      throwMobergDeviceDoesNotExistException(env, index);      
+    } else {
+      channel.count = 1;
+      channel_set(&analog_out, index, channel);
+    }
+  }
+}
+
+JNIEXPORT void JNICALL 
+Java_se_lth_control_realtime_moberg_Moberg_analogOutClose(
+  JNIEnv *env, jclass obj, jint index
+)
+{
+  struct channel *channel = channel_down(&analog_out, index);
+  if (! channel) {
+    throwMobergDeviceDoesNotExistException(env, index);
+  } else if (channel->count == 0) {
+    moberg_analog_out_close(g_moberg.moberg, index, channel->analog_out);
+  }
+}
+
+
+JNIEXPORT void JNICALL 
+Java_se_lth_control_realtime_moberg_Moberg_analogOut(
+  JNIEnv *env, jobject obj, jint index, jdouble value
+) 
+{
+  struct channel *channel = channel_get(&analog_out, index);
+  if (! channel) {
+    throwMobergNotOpenException(env, index);
+  } else {
+    channel->analog_out.write(channel->analog_out.context, value);
+  }
+}
+
+JNIEXPORT void JNICALL 
+Java_se_lth_control_realtime_moberg_Moberg_digitalInOpen(
+  JNIEnv *env, jclass obj, jint index
+)
+{
+  if (! channel_up(&digital_in, index)) {
+    struct channel channel;
+    up();
+    if (! moberg_digital_in_open(g_moberg.moberg, index, &channel.digital_in)) {
+      down();
+      throwMobergDeviceDoesNotExistException(env, index);      
+    } else {
+      channel.count = 1;
+      channel_set(&digital_in, index, channel);
+    }
+  }
+}
+
+JNIEXPORT void JNICALL 
+Java_se_lth_control_realtime_moberg_Moberg_digitalInClose(
+  JNIEnv *env, jclass obj, jint index
+)
+{
+  struct channel *channel = channel_down(&digital_in, index);
+  if (! channel) {
+    throwMobergDeviceDoesNotExistException(env, index);
+  } else if (channel->count == 0) {
+    moberg_digital_in_close(g_moberg.moberg, index, channel->digital_in);
+  }
+}
+
+JNIEXPORT jboolean JNICALL 
+Java_se_lth_control_realtime_moberg_Moberg_digitalIn(
+  JNIEnv *env, jobject obj, jint index
+) 
+{
+  int result = 0;
+
+  struct channel *channel = channel_get(&digital_in, index);
+  if (! channel) {
+    throwMobergNotOpenException(env, index);
+  } else {
+    channel->digital_in.read(channel->digital_in.context, &result);
+  }
+  return result;
+}
+
+JNIEXPORT void JNICALL 
+Java_se_lth_control_realtime_moberg_Moberg_digitalOutOpen(
+  JNIEnv *env, jclass obj, jint index
+)
+{
+  if (! channel_up(&digital_out, index)) {
+    struct channel channel;
+    up();
+    if (! moberg_digital_out_open(g_moberg.moberg, index, &channel.digital_out)) {
+      down();
+      throwMobergDeviceDoesNotExistException(env, index);      
+    } else {
+      channel.count = 1;
+      channel_set(&digital_out, index, channel);
+    }
+  }
+}
+
+JNIEXPORT void JNICALL 
+Java_se_lth_control_realtime_moberg_Moberg_digitalOutClose(
+  JNIEnv *env, jclass obj, jint index
+)
+{
+  struct channel *channel = channel_down(&digital_out, index);
+  if (! channel) {
+    throwMobergDeviceDoesNotExistException(env, index);
+  } else if (channel->count == 0) {
+    moberg_digital_out_close(g_moberg.moberg, index, channel->digital_out);
+  }
+}
+
+JNIEXPORT void JNICALL 
+Java_se_lth_control_realtime_moberg_Moberg_digitalOut(
+  JNIEnv *env, jobject obj, jint index, jboolean value
+) 
+{
+  struct channel *channel = channel_get(&digital_out, index);
+  if (! channel) {
+    throwMobergNotOpenException(env, index);
+  } else {
+    channel->digital_out.write(channel->digital_out.context, value);
+  }
+}
+
+JNIEXPORT void JNICALL 
+Java_se_lth_control_realtime_moberg_Moberg_encoderInOpen(
+  JNIEnv *env, jclass obj, jint index
+)
+{
+  if (! channel_up(&encoder_in, index)) {
+    struct channel channel;
+    up();
+    if (! moberg_encoder_in_open(g_moberg.moberg, index, &channel.encoder_in)) {
+      down();
+      throwMobergDeviceDoesNotExistException(env, index);      
+    } else {
+      channel.count = 1;
+      channel_set(&encoder_in, index, channel);
+    }
+  }
+}
+
+JNIEXPORT void JNICALL 
+Java_se_lth_control_realtime_moberg_Moberg_encoderInClose(
+  JNIEnv *env, jclass obj, jint index
+)
+{
+  struct channel *channel = channel_down(&encoder_in, index);
+  if (! channel) {
+    throwMobergDeviceDoesNotExistException(env, index);
+  } else if (channel->count == 0) {
+    moberg_encoder_in_close(g_moberg.moberg, index, channel->encoder_in);
+  }
+}
+
+JNIEXPORT jlong JNICALL 
+Java_se_lth_control_realtime_moberg_Moberg_encoderIn(
+  JNIEnv *env, jobject obj, jint index
+) 
+{
+  long result = 0;
+
+  struct channel *channel = channel_get(&encoder_in, index);
+  if (! channel) {
+    throwMobergNotOpenException(env, index);
+  } else {
+    channel->encoder_in.read(channel->encoder_in.context, &result);
+  }
+  return result;
+}
+
+JNIEXPORT void JNICALL
+Java_se_lth_control_realtime_moberg_Moberg_Init(
+  JNIEnv *env, jobject obj
+)
+{
+}
diff --git a/adaptors/matlab/Makefile b/adaptors/matlab/Makefile
index 561bfd6da50cf58ee9fecd5e3cb29de9a65785bf..d59df2125d6e652de64bd868eb56e0087dcab901 100644
--- a/adaptors/matlab/Makefile
+++ b/adaptors/matlab/Makefile
@@ -3,6 +3,8 @@ CCFLAGS+=-Wall -Werror -O3 -I. -I../.. -g
 
 all:	$(LIBRARIES:%=../../build/%)
 
+clean:
+
 ../../build/libmoberg4simulink.so: moberg4simulink.c Makefile
 	$(CC) -o $@ $(CCFLAGS) -L../../build -shared -fPIC -lmoberg $<
 
diff --git a/moberg.spec.template b/moberg.spec.template
index 36e54381770aed0586f9e451dfbac82797eef6d7..504f1ae33f9ad2a46f15f320ef8b8c88fcc840d7 100644
--- a/moberg.spec.template
+++ b/moberg.spec.template
@@ -9,6 +9,7 @@ BuildRequires:  gcc
 BuildRequires:  comedilib-devel
 BuildRequires:  valgrind
 BuildRequires:  libxdg-basedir-devel
+BuildRequires:  java-devel
 
 %description
 Shared library for abstracting physical process I/O (analog, digital
@@ -30,12 +31,19 @@ Requires: %{name} = %{version}-%{release}
 Development files for %{name}
 
 %package matlab
-Summary: Matlab development files for %{name}
+Summary: Matlab support files for %{name}
 Requires: %{name} = %{version}-%{release}
 Requires: %{name}-devel = %{version}-%{release}
 
 %description matlab
-Matlab development files for %{name}
+Matlab support files for %{name}
+
+%package java
+Summary: Java support files for %{name}
+Requires: %{name} = %{version}-%{release}
+
+%description java
+Java support files for %{name}
 
 
 %prep