diff --git a/src/bindings/java/native/src/test/java/HammerTest.java b/src/bindings/java/native/src/test/java/HammerTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..d9b3ac07a40ec3aa138ba6d14627381b188cb32d
--- /dev/null
+++ b/src/bindings/java/native/src/test/java/HammerTest.java
@@ -0,0 +1,349 @@
+package com.upstandinghackers.hammer; 
+
+import java.math.BigInteger;
+import java.util.Arrays;
+import org.testng.annotations.*;
+import org.testng.Assert;
+
+public class HammerTest {
+
+    static {
+        System.loadLibrary("hammer-java");
+    }
+
+    private boolean handle(ParsedToken p, Object known) {
+	System.out.println("Known: " + known);
+        switch (p.getTokenType()) {
+        case BYTES:
+	    System.out.println("Parsed: " + Arrays.toString(p.getBytesValue()));
+            return Arrays.toString(p.getBytesValue()).equals((String)known);
+        case SINT:
+	    System.out.println("Parsed: " + p.getSIntValue());
+            return ((Long)p.getSIntValue()).equals(known);
+        case UINT:
+	    System.out.println("Parsed: " + p.getUIntValue());
+            return ((Long)p.getUIntValue()).equals(known);
+        case SEQUENCE:
+            int i=0;
+            for (ParsedToken tok : p.getSeqValue()) {
+                if (!handle(tok, ((Object[])known)[i]))
+                    return false;
+                ++i;
+            }
+            return true;
+        default:
+            return false;
+        }
+    }
+
+    @Test
+    public void TestToken() {
+        Parser parser;
+        parser = Hammer.token("95\u00a2");
+        Assert.assertTrue(handle(parser.parse("95\u00a2").getAst(), "95\u00a2"));
+        Assert.assertNull(parser.parse("95\u00a2"));
+    }
+    @Test
+    public void TestCh() {
+        Parser parser;
+        parser = Hammer.ch(0xa2);
+        Assert.assertTrue(handle(parser.parse("\u00a2").getAst(), "\u00a2".getBytes()[0]));
+        Assert.assertNull(parser.parse("\u00a3"));
+    }
+    @Test
+    public void TestChRange() {
+        Parser parser;
+        parser = Hammer.chRange(0x61, 0x63);
+        Assert.assertTrue(handle(parser.parse("b").getAst(), "b".getBytes()[0]));
+        Assert.assertNull(parser.parse("d"));
+    }
+    @Test
+    public void TestInt64() {
+        Parser parser;
+        parser = Hammer.int64();
+        Assert.assertTrue(handle(parser.parse("\u00ff\u00ff\u00ff\u00fe\u0000\u0000\u0000\u0000").getAst(), new BigInteger("-200000000", 16)));
+        Assert.assertNull(parser.parse("\u00ff\u00ff\u00ff\u00fe\u0000\u0000\u0000"));
+    }
+    @Test
+    public void TestInt32() {
+        Parser parser;
+        parser = Hammer.int32();
+        Assert.assertTrue(handle(parser.parse("\u00ff\u00fe\u0000\u0000").getAst(), new BigInteger("-20000", 16)));
+        Assert.assertNull(parser.parse("\u00ff\u00fe\u0000"));
+        Assert.assertTrue(handle(parser.parse("\u0000\u0002\u0000\u0000").getAst(), new BigInteger("20000", 16)));
+        Assert.assertNull(parser.parse("\u0000\u0002\u0000"));
+    }
+    @Test
+    public void TestInt16() {
+        Parser parser;
+        parser = Hammer.int16();
+        Assert.assertTrue(handle(parser.parse("\u00fe\u0000").getAst(), new BigInteger("-200", 16)));
+        Assert.assertNull(parser.parse("\u00fe"));
+        Assert.assertTrue(handle(parser.parse("\u0002\u0000").getAst(), new BigInteger("200", 16)));
+        Assert.assertNull(parser.parse("\u0002"));
+    }
+    @Test
+    public void TestInt8() {
+        Parser parser;
+        parser = Hammer.int8();
+        Assert.assertTrue(handle(parser.parse("\u0088").getAst(), new BigInteger("-78", 16)));
+        Assert.assertNull(parser.parse(""));
+    }
+    @Test
+    public void TestUint64() {
+        Parser parser;
+        parser = Hammer.uint64();
+        Assert.assertTrue(handle(parser.parse("\u0000\u0000\u0000\u0002\u0000\u0000\u0000\u0000").getAst(), new BigInteger("200000000", 16)));
+        Assert.assertNull(parser.parse("\u0000\u0000\u0000\u0002\u0000\u0000\u0000"));
+    }
+    @Test
+    public void TestUint32() {
+        Parser parser;
+        parser = Hammer.uint32();
+        Assert.assertTrue(handle(parser.parse("\u0000\u0002\u0000\u0000").getAst(), new BigInteger("20000", 16)));
+        Assert.assertNull(parser.parse("\u0000\u0002\u0000"));
+    }
+    @Test
+    public void TestUint16() {
+        Parser parser;
+        parser = Hammer.uint16();
+        Assert.assertTrue(handle(parser.parse("\u0002\u0000").getAst(), new BigInteger("200", 16)));
+        Assert.assertNull(parser.parse("\u0002"));
+    }
+    @Test
+    public void TestUint8() {
+        Parser parser;
+        parser = Hammer.uint8();
+        Assert.assertTrue(handle(parser.parse("x").getAst(), new BigInteger("78", 16)));
+        Assert.assertNull(parser.parse(""));
+    }
+    @Test
+    public void TestIntRange() {
+        Parser parser;
+        parser = Hammer.intRange(Hammer.uint8(), 0x3, 0x10);
+        Assert.assertTrue(handle(parser.parse("\u0005").getAst(), new BigInteger("5", 16)));
+        Assert.assertNull(parser.parse("\u000b"));
+    }
+    @Test
+    public void TestWhitespace() {
+        Parser parser;
+        parser = Hammer.whitespace(Hammer.ch(0x61));
+        Assert.assertTrue(handle(parser.parse("a").getAst(), "a".getBytes()[0]));
+        Assert.assertTrue(handle(parser.parse(" a").getAst(), "a".getBytes()[0]));
+        Assert.assertTrue(handle(parser.parse("  a").getAst(), "a".getBytes()[0]));
+        Assert.assertTrue(handle(parser.parse("\u0009a").getAst(), "a".getBytes()[0]));
+        Assert.assertNull(parser.parse("_a"));
+        parser = Hammer.whitespace(Hammer.endP());
+        Assert.assertTrue(handle(parser.parse("").getAst(), null));
+        Assert.assertTrue(handle(parser.parse("  ").getAst(), null));
+        Assert.assertNull(parser.parse("  x"));
+    }
+    @Test
+    public void TestLeft() {
+        Parser parser;
+        parser = Hammer.left(Hammer.ch(0x61), Hammer.ch(0x20));
+        Assert.assertTrue(handle(parser.parse("a ").getAst(), "a".getBytes()[0]));
+        Assert.assertNull(parser.parse("a"));
+        Assert.assertNull(parser.parse(" "));
+        Assert.assertNull(parser.parse("ba"));
+    }
+    @Test
+    public void TestMiddle() {
+        Parser parser;
+        parser = Hammer.middle(Hammer.ch(" "), Hammer.ch("a"), Hammer.ch(" "));
+        Assert.assertTrue(handle(parser.parse(" a ").getAst(), "a".getBytes()[0]));
+        Assert.assertNull(parser.parse("a"));
+        Assert.assertNull(parser.parse(" a"));
+        Assert.assertNull(parser.parse("a "));
+        Assert.assertNull(parser.parse(" b "));
+        Assert.assertNull(parser.parse("ba "));
+        Assert.assertNull(parser.parse(" ab"));
+    }
+    @Test
+    public void TestIn() {
+        Parser parser;
+        parser = Hammer.in("abc");
+        Assert.assertTrue(handle(parser.parse("b").getAst(), "b".getBytes()[0]));
+        Assert.assertNull(parser.parse("d"));
+    }
+    @Test
+    public void TestNotIn() {
+        Parser parser;
+        parser = Hammer.notIn("abc");
+        Assert.assertTrue(handle(parser.parse("d").getAst(), "d".getBytes()[0]));
+        Assert.assertNull(parser.parse("a"));
+    }
+    @Test
+    public void TestEndP() {
+        Parser parser;
+        parser = Hammer.sequence(Hammer.ch("a"), Hammer.endP());
+        Assert.assertTrue(handle(parser.parse("a").getAst(), new Object[]{ "a".getBytes()[0]}));
+        Assert.assertNull(parser.parse("aa"));
+    }
+    @Test
+    public void TestNothingP() {
+        Parser parser;
+        parser = Hammer.nothingP();
+        Assert.assertNull(parser.parse("a"));
+    }
+    @Test
+    public void TestSequence() {
+        Parser parser;
+        parser = Hammer.sequence(Hammer.ch("a"), Hammer.ch("b"));
+        Assert.assertTrue(handle(parser.parse("ab").getAst(), new Object[]{ "a".getBytes()[0], "b".getBytes()[0]}));
+        Assert.assertNull(parser.parse("a"));
+        Assert.assertNull(parser.parse("b"));
+        parser = Hammer.sequence(Hammer.ch("a"), Hammer.whitespace(Hammer.ch("b")));
+        Assert.assertTrue(handle(parser.parse("ab").getAst(), new Object[]{ "a".getBytes()[0], "b".getBytes()[0]}));
+        Assert.assertTrue(handle(parser.parse("a b").getAst(), new Object[]{ "a".getBytes()[0], "b".getBytes()[0]}));
+        Assert.assertTrue(handle(parser.parse("a  b").getAst(), new Object[]{ "a".getBytes()[0], "b".getBytes()[0]}));
+    }
+    @Test
+    public void TestChoice() {
+        Parser parser;
+        parser = Hammer.choice(Hammer.ch("a"), Hammer.ch("b"));
+        Assert.assertTrue(handle(parser.parse("a").getAst(), "a".getBytes()[0]));
+        Assert.assertTrue(handle(parser.parse("b").getAst(), "b".getBytes()[0]));
+        Assert.assertTrue(handle(parser.parse("ab").getAst(), "a".getBytes()[0]));
+        Assert.assertNull(parser.parse("c"));
+    }
+    @Test
+    public void TestButnot() {
+        Parser parser;
+        parser = Hammer.butnot(Hammer.ch("a"), Hammer.token("ab"));
+        Assert.assertTrue(handle(parser.parse("a").getAst(), "a".getBytes()[0]));
+        Assert.assertNull(parser.parse("ab"));
+        Assert.assertTrue(handle(parser.parse("aa").getAst(), "a".getBytes()[0]));
+        parser = Hammer.butnot(Hammer.chRange("0", "9"), Hammer.ch("6"));
+        Assert.assertTrue(handle(parser.parse("5").getAst(), "5".getBytes()[0]));
+        Assert.assertNull(parser.parse("6"));
+    }
+    @Test
+    public void TestDifference() {
+        Parser parser;
+        parser = Hammer.difference(Hammer.token("ab"), Hammer.ch("a"));
+        Assert.assertTrue(handle(parser.parse("ab").getAst(), "ab"));
+        Assert.assertNull(parser.parse("a"));
+    }
+    @Test
+    public void TestXor() {
+        Parser parser;
+        parser = Hammer.xor(Hammer.chRange("0", "6"), Hammer.chRange("5", "9"));
+        Assert.assertTrue(handle(parser.parse("0").getAst(), "0".getBytes()[0]));
+        Assert.assertTrue(handle(parser.parse("9").getAst(), "9".getBytes()[0]));
+        Assert.assertNull(parser.parse("5"));
+        Assert.assertNull(parser.parse("a"));
+    }
+    @Test
+    public void TestMany() {
+        Parser parser;
+        parser = Hammer.many(Hammer.choice(Hammer.ch("a"), Hammer.ch("b")));
+        Assert.assertTrue(handle(parser.parse("").getAst(), new Object[]{ }));
+        Assert.assertTrue(handle(parser.parse("a").getAst(), new Object[]{ "a".getBytes()[0]}));
+        Assert.assertTrue(handle(parser.parse("b").getAst(), new Object[]{ "b".getBytes()[0]}));
+        Assert.assertTrue(handle(parser.parse("aabbaba").getAst(), new Object[]{ "a".getBytes()[0], "a".getBytes()[0], "b".getBytes()[0], "b".getBytes()[0], "a".getBytes()[0], "b".getBytes()[0], "a".getBytes()[0]}));
+    }
+    @Test
+    public void TestMany1() {
+        Parser parser;
+        parser = Hammer.many1(Hammer.choice(Hammer.ch("a"), Hammer.ch("b")));
+        Assert.assertNull(parser.parse(""));
+        Assert.assertTrue(handle(parser.parse("a").getAst(), new Object[]{ "a".getBytes()[0]}));
+        Assert.assertTrue(handle(parser.parse("b").getAst(), new Object[]{ "b".getBytes()[0]}));
+        Assert.assertTrue(handle(parser.parse("aabbaba").getAst(), new Object[]{ "a".getBytes()[0], "a".getBytes()[0], "b".getBytes()[0], "b".getBytes()[0], "a".getBytes()[0], "b".getBytes()[0], "a".getBytes()[0]}));
+        Assert.assertNull(parser.parse("daabbabadef"));
+    }
+    @Test
+    public void TestRepeatN() {
+        Parser parser;
+        parser = Hammer.repeatN(Hammer.choice(Hammer.ch("a"), Hammer.ch("b")), 0x2);
+        Assert.assertNull(parser.parse("adef"));
+        Assert.assertTrue(handle(parser.parse("abdef").getAst(), new Object[]{ "a".getBytes()[0], "b".getBytes()[0]}));
+        Assert.assertNull(parser.parse("dabdef"));
+    }
+    @Test
+    public void TestOptional() {
+        Parser parser;
+        parser = Hammer.sequence(Hammer.ch("a"), Hammer.optional(Hammer.choice(Hammer.ch("b"), Hammer.ch("c"))), Hammer.ch("d"));
+        Assert.assertTrue(handle(parser.parse("abd").getAst(), new Object[]{ "a".getBytes()[0], "b".getBytes()[0], "d".getBytes()[0]}));
+        Assert.assertTrue(handle(parser.parse("acd").getAst(), new Object[]{ "a".getBytes()[0], "c".getBytes()[0], "d".getBytes()[0]}));
+        Assert.assertTrue(handle(parser.parse("ad").getAst(), new Object[]{ "a".getBytes()[0], null, "d".getBytes()[0]}));
+        Assert.assertNull(parser.parse("aed"));
+        Assert.assertNull(parser.parse("ab"));
+        Assert.assertNull(parser.parse("ac"));
+    }
+    @Test
+    public void TestIgnore() {
+        Parser parser;
+        parser = Hammer.sequence(Hammer.ch("a"), Hammer.ignore(Hammer.ch("b")), Hammer.ch("c"));
+        Assert.assertTrue(handle(parser.parse("abc").getAst(), new Object[]{ "a".getBytes()[0], "c".getBytes()[0]}));
+        Assert.assertNull(parser.parse("ac"));
+    }
+    @Test
+    public void TestSepBy() {
+        Parser parser;
+        parser = Hammer.sepBy(Hammer.choice(Hammer.ch("1"), Hammer.ch("2"), Hammer.ch("3")), Hammer.ch(","));
+        Assert.assertTrue(handle(parser.parse("1,2,3").getAst(), new Object[]{ "1".getBytes()[0], "2".getBytes()[0], "3".getBytes()[0]}));
+        Assert.assertTrue(handle(parser.parse("1,3,2").getAst(), new Object[]{ "1".getBytes()[0], "3".getBytes()[0], "2".getBytes()[0]}));
+        Assert.assertTrue(handle(parser.parse("1,3").getAst(), new Object[]{ "1".getBytes()[0], "3".getBytes()[0]}));
+        Assert.assertTrue(handle(parser.parse("3").getAst(), new Object[]{ "3".getBytes()[0]}));
+        Assert.assertTrue(handle(parser.parse("").getAst(), new Object[]{ }));
+    }
+    @Test
+    public void TestSepBy1() {
+        Parser parser;
+        parser = Hammer.sepBy1(Hammer.choice(Hammer.ch("1"), Hammer.ch("2"), Hammer.ch("3")), Hammer.ch(","));
+        Assert.assertTrue(handle(parser.parse("1,2,3").getAst(), new Object[]{ "1".getBytes()[0], "2".getBytes()[0], "3".getBytes()[0]}));
+        Assert.assertTrue(handle(parser.parse("1,3,2").getAst(), new Object[]{ "1".getBytes()[0], "3".getBytes()[0], "2".getBytes()[0]}));
+        Assert.assertTrue(handle(parser.parse("1,3").getAst(), new Object[]{ "1".getBytes()[0], "3".getBytes()[0]}));
+        Assert.assertTrue(handle(parser.parse("3").getAst(), new Object[]{ "3".getBytes()[0]}));
+        Assert.assertNull(parser.parse(""));
+    }
+    @Test
+    public void TestAnd() {
+        Parser parser;
+        parser = Hammer.sequence(Hammer.and(Hammer.ch("0")), Hammer.ch("0"));
+        Assert.assertTrue(handle(parser.parse("0").getAst(), new Object[]{ "0".getBytes()[0]}));
+        Assert.assertNull(parser.parse("1"));
+        parser = Hammer.sequence(Hammer.and(Hammer.ch("0")), Hammer.ch("1"));
+        Assert.assertNull(parser.parse("0"));
+        Assert.assertNull(parser.parse("1"));
+        parser = Hammer.sequence(Hammer.ch("1"), Hammer.and(Hammer.ch("2")));
+        Assert.assertTrue(handle(parser.parse("12").getAst(), new Object[]{ "1".getBytes()[0]}));
+        Assert.assertNull(parser.parse("13"));
+    }
+    @Test
+    public void TestNot() {
+        Parser parser;
+        parser = Hammer.sequence(Hammer.ch("a"), Hammer.choice(Hammer.token("+"), Hammer.token("++")), Hammer.ch("b"));
+        Assert.assertTrue(handle(parser.parse("a+b").getAst(), new Object[]{ "a".getBytes()[0], "+", "b".getBytes()[0]}));
+        Assert.assertNull(parser.parse("a++b"));
+        parser = Hammer.sequence(Hammer.ch("a"), Hammer.choice(Hammer.sequence(Hammer.token("+"), Hammer.not(Hammer.ch("+"))), Hammer.token("++")), Hammer.ch("b"));
+        Assert.assertTrue(handle(parser.parse("a+b").getAst(), new Object[]{ "a".getBytes()[0], new Object[]{ "+"}, "b".getBytes()[0]}));
+        Assert.assertTrue(handle(parser.parse("a++b").getAst(), new Object[]{ "a".getBytes()[0], "++", "b".getBytes()[0]}));
+    }
+    @Test
+    public void TestRightrec() {
+        Parser parser;
+        Parser sp_rr = Hammer.indirect();
+        sp_rr.bindIndirect(Hammer.choice(Hammer.sequence(Hammer.ch("a"), sp_rr), Hammer.epsilonP()));
+        parser = sp_rr;
+        Assert.assertTrue(handle(parser.parse("a").getAst(), new Object[]{ "a".getBytes()[0]}));
+        Assert.assertTrue(handle(parser.parse("aa").getAst(), new Object[]{ "a".getBytes()[0], new Object[]{ "a".getBytes()[0]}}));
+        Assert.assertTrue(handle(parser.parse("aaa").getAst(), new Object[]{ "a".getBytes()[0], new Object[]{ "a".getBytes()[0], new Object[]{ "a".getBytes()[0]}}}));
+    }
+    @Test
+    public void TestAmbiguous() {
+        Parser parser;
+        Parser sp_d = Hammer.indirect();
+        Parser sp_p = Hammer.indirect();
+        Parser sp_e = Hammer.indirect();
+        sp_d.bindIndirect(Hammer.ch("d"));
+        sp_p.bindIndirect(Hammer.ch("+"));
+        sp_e.bindIndirect(Hammer.choice(Hammer.sequence(sp_e, sp_p, sp_e), sp_d));
+        parser = sp_e;
+        Assert.assertTrue(handle(parser.parse("d").getAst(), "d".getBytes()[0]));
+        Assert.assertTrue(handle(parser.parse("d+d").getAst(), new Object[]{ "d".getBytes()[0], "+".getBytes()[0], "d".getBytes()[0]}));
+        Assert.assertTrue(handle(parser.parse("d+d+d").getAst(), new Object[]{ new Object[]{ "d".getBytes()[0], "+".getBytes()[0], "d".getBytes()[0]}, "+".getBytes()[0], "d".getBytes()[0]}));
+    }
+}