diff --git a/Cargo.toml b/Cargo.toml
index 1dfcb1eed..d51a3198a 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -102,4 +102,6 @@ members = [
     "scripts/import-yoga-tests",
     "benches",
     "taffy_stylo",
+    "bindings/java",
+    "scripts/genenums"
 ]
diff --git a/bindings/java/.gitignore b/bindings/java/.gitignore
new file mode 100644
index 000000000..8b2c3a1a1
--- /dev/null
+++ b/bindings/java/.gitignore
@@ -0,0 +1,6 @@
+.idea
+*.iml
+out
+*.class
+/target
+/java/target
\ No newline at end of file
diff --git a/bindings/java/Cargo.toml b/bindings/java/Cargo.toml
new file mode 100644
index 000000000..dd681feb8
--- /dev/null
+++ b/bindings/java/Cargo.toml
@@ -0,0 +1,19 @@
+[package]
+name = "jtaffy"
+version = "0.0.1"
+authors = [
+    "Arby Djabaev <contact@arby.be>"
+]
+edition = "2021"
+include = ["src/**/*", "Cargo.toml"]
+description = "Java bindings to Taffy (A flexible UI layout library)"
+repository = "https://github.com/DioxusLabs/taffy"
+license = "MIT"
+
+[lib]
+crate-type = ["cdylib"]
+name = "jtaffy"
+
+[dependencies]
+jni = "0.21.1"
+taffy = { path = "../.." }
diff --git a/bindings/java/java/examples/java/com/dioxuslabs/taffy/Basic.java b/bindings/java/java/examples/java/com/dioxuslabs/taffy/Basic.java
new file mode 100644
index 000000000..24cf0d65f
--- /dev/null
+++ b/bindings/java/java/examples/java/com/dioxuslabs/taffy/Basic.java
@@ -0,0 +1,40 @@
+package com.dioxuslabs.taffy;
+
+import com.dioxuslabs.taffy.enums.AlignContent;
+import com.dioxuslabs.taffy.geom.Size;
+import com.dioxuslabs.taffy.geom.measure.Dimension;
+import com.dioxuslabs.taffy.style.Style;
+
+import java.util.List;
+
+/**
+ * Equivalent of taffy/examples/basic.rs
+ */
+public class Basic {
+    public static void main(String[] args) {
+        TaffyTree taffy = new TaffyTree();
+
+        long child = taffy.newLeaf(
+                Style.builder().size(new Size<>(Dimension.percent(0.5f), Dimension.auto()))
+        );
+
+        long node = taffy.newWithChildren(
+                Style.builder().size(Size.length(Dimension.class, 100, 100))
+                        .justifyContent(AlignContent.CENTER),
+                List.of(child)
+        );
+
+        System.out.println("Compute layout with 100x100 viewport:");
+        taffy.computeLayout(
+                node,
+                Size.definiteAvailableSize(100, 100)
+        );
+        System.out.println("node: " + taffy.layout(node).toString());
+        System.out.println("child: " + taffy.layout(child).toString());
+
+        System.out.println("Compute layout with undefined (infinite) viewport:");
+        taffy.computeLayout(node, Size.maxContentAvailableSize());
+        System.out.println("node: " + taffy.layout(node).toString());
+        System.out.println("child: " + taffy.layout(child).toString());
+    }
+}
diff --git a/bindings/java/java/examples/java/com/dioxuslabs/taffy/FlexboxGap.java b/bindings/java/java/examples/java/com/dioxuslabs/taffy/FlexboxGap.java
new file mode 100644
index 000000000..afc146ca6
--- /dev/null
+++ b/bindings/java/java/examples/java/com/dioxuslabs/taffy/FlexboxGap.java
@@ -0,0 +1,32 @@
+package com.dioxuslabs.taffy;
+
+import com.dioxuslabs.taffy.geom.Size;
+import com.dioxuslabs.taffy.geom.measure.LengthPercentage;
+import com.dioxuslabs.taffy.style.Style;
+
+import java.util.List;
+
+/**
+ * Equivalent of taffy/examples/flexbox_gap.rs
+ */
+public class FlexboxGap {
+    public static void main(String[] args) {
+        TaffyTree taffy = new TaffyTree();
+
+        Style childStyle = Style.builder()
+                .size(Size.lengthDimension(20, 20));
+        long child0 = taffy.newLeaf(childStyle);
+        long child1 = taffy.newLeaf(childStyle);
+        long child2 = taffy.newLeaf(childStyle);
+
+        long root = taffy.newWithChildren(
+                Style.builder()
+                        .gap(new Size<>(LengthPercentage.length(10), LengthPercentage.zero())),
+                List.of(child0, child1, child2)
+        );
+
+        // Compute layout and print result
+        taffy.computeLayout(root, Size.maxContentAvailableSize());
+        taffy.printTree(root);
+    }
+}
diff --git a/bindings/java/java/examples/java/com/dioxuslabs/taffy/Measure.java b/bindings/java/java/examples/java/com/dioxuslabs/taffy/Measure.java
new file mode 100644
index 000000000..0f31b9eab
--- /dev/null
+++ b/bindings/java/java/examples/java/com/dioxuslabs/taffy/Measure.java
@@ -0,0 +1,75 @@
+package com.dioxuslabs.taffy;
+
+import com.dioxuslabs.taffy.common.Image.ImageContext;
+import com.dioxuslabs.taffy.common.Text;
+import com.dioxuslabs.taffy.common.Text.FontMetrics;
+import com.dioxuslabs.taffy.common.Text.TextContext;
+import com.dioxuslabs.taffy.enums.Display;
+import com.dioxuslabs.taffy.enums.FlexDirection;
+import com.dioxuslabs.taffy.geom.Size;
+import com.dioxuslabs.taffy.geom.measure.AvailableSpace;
+import com.dioxuslabs.taffy.geom.measure.Dimension;
+import com.dioxuslabs.taffy.style.Style;
+
+import java.util.List;
+
+import static com.dioxuslabs.taffy.common.Image.imageMeasureFunction;
+import static com.dioxuslabs.taffy.common.Text.LOREM_IPSUM;
+import static com.dioxuslabs.taffy.common.Text.textMeasureFunction;
+
+/**
+ * Equivalent of taffy/examples/measure.rs
+ */
+public class Measure {
+    public static Size<Float> measureFunction(
+            Size<Float> knownDimensions,
+            Size<AvailableSpace> availableSpace,
+            Object nodeContext,
+            FontMetrics fontMetrics
+    ) {
+        if (knownDimensions.bothAxisDefined()) {
+            return new Size<>(knownDimensions.width(), knownDimensions.height());
+        }
+
+        if (nodeContext instanceof TextContext nc) {
+            return textMeasureFunction(knownDimensions, availableSpace, nc, fontMetrics);
+        } else if (nodeContext instanceof ImageContext nc) {
+            return imageMeasureFunction(knownDimensions, nc);
+        } else {
+            return Size.zero(Float.class);
+        }
+    }
+
+    public static void main(String[] args) {
+        TaffyTree taffy = new TaffyTree();
+
+        FontMetrics fontMetrics = new FontMetrics(10f, 10f);
+
+        long textNode = taffy.newLeafWithContext(
+                Style.def(),
+                new TextContext(LOREM_IPSUM, Text.WritingMode.HORIZONTAL)
+        );
+
+        long imageNode = taffy.newLeafWithContext(
+                Style.def(),
+                new ImageContext(400f, 300f)
+        );
+
+        long root = taffy.newWithChildren(
+                Style.builder()
+                        .display(Display.FLEX)
+                        .flexDirection(FlexDirection.COLUMN)
+                        .size(new Size<>(Dimension.length(200), Dimension.auto())),
+                List.of(textNode, imageNode)
+        );
+
+        // Compute layout and print result
+        taffy.computeLayoutWithMeasure(
+                root,
+                Size.maxContentAvailableSize()
+                // todo measure func
+        );
+
+        taffy.printTree(root);
+    }
+}
diff --git a/bindings/java/java/examples/java/com/dioxuslabs/taffy/Nested.java b/bindings/java/java/examples/java/com/dioxuslabs/taffy/Nested.java
new file mode 100644
index 000000000..a0a04e2f3
--- /dev/null
+++ b/bindings/java/java/examples/java/com/dioxuslabs/taffy/Nested.java
@@ -0,0 +1,53 @@
+package com.dioxuslabs.taffy;
+
+import com.dioxuslabs.taffy.geom.Size;
+import com.dioxuslabs.taffy.geom.measure.Dimension;
+import com.dioxuslabs.taffy.style.Style;
+
+import java.util.List;
+
+/**
+ * Equivalent of taffy/examples/nested.rs
+ */
+public class Nested {
+    public static void main(String[] args) {
+        TaffyTree taffy = new TaffyTree();
+
+        // left
+        long childT1 = taffy.newLeaf(
+                Style.builder().size(Size.lengthDimension(5, 5))
+        );
+
+        long div1 = taffy.newWithChildren(
+                Style.builder().size(new Size<>(Dimension.percent(0.5f), Dimension.percent(1f))),
+                List.of(childT1)
+        );
+
+        // right
+        long childT2 = taffy.newLeaf(
+                Style.builder().size(Size.lengthDimension(5, 5))
+        );
+
+        long div2 = taffy.newWithChildren(
+                Style.builder().size(new Size<>(Dimension.percent(0.5f), Dimension.percent(1f))),
+                List.of(childT2)
+        );
+
+        long container = taffy.newWithChildren(
+                Style.builder().size(Size.percent(Dimension.class, 1, 1)),
+                List.of(div1, div2)
+        );
+
+        taffy.computeLayout(
+                container,
+                Size.definiteAvailableSize(100, 100)
+        );
+        System.out.println("node: " + taffy.layout(container).toString());
+
+        System.out.println("div1: " + taffy.layout(div1).toString());
+        System.out.println("div2: " + taffy.layout(div2).toString());
+
+        System.out.println("child1: " + taffy.layout(childT1).toString());
+        System.out.println("child: " + taffy.layout(childT2).toString());
+    }
+}
diff --git a/bindings/java/java/examples/java/com/dioxuslabs/taffy/common/Image.java b/bindings/java/java/examples/java/com/dioxuslabs/taffy/common/Image.java
new file mode 100644
index 000000000..a8e673c0c
--- /dev/null
+++ b/bindings/java/java/examples/java/com/dioxuslabs/taffy/common/Image.java
@@ -0,0 +1,26 @@
+package com.dioxuslabs.taffy.common;
+
+import com.dioxuslabs.taffy.geom.Size;
+
+public class Image {
+    public record ImageContext(
+            float width,
+            float height
+    ) {
+    }
+
+    public static Size<Float> imageMeasureFunction(Size<Float> knownDimensions, ImageContext imageContext) {
+        Float width = knownDimensions.width();
+        Float height = knownDimensions.height();
+
+        if (width != null && height != null) {
+            return new Size<>(width, height);
+        } else if (width != null) {
+            return new Size<>(width, (width / imageContext.width) * imageContext.height);
+        } else if (height != null) {
+            return new Size<>((height / imageContext.height) * imageContext.width, height);
+        } else {
+            return new Size<>(imageContext.width, imageContext.height);
+        }
+    }
+}
diff --git a/bindings/java/java/examples/java/com/dioxuslabs/taffy/common/Text.java b/bindings/java/java/examples/java/com/dioxuslabs/taffy/common/Text.java
new file mode 100644
index 000000000..2fe5b4695
--- /dev/null
+++ b/bindings/java/java/examples/java/com/dioxuslabs/taffy/common/Text.java
@@ -0,0 +1,98 @@
+package com.dioxuslabs.taffy.common;
+
+import com.dioxuslabs.taffy.enums.AbsoluteAxis;
+import com.dioxuslabs.taffy.geom.Size;
+import com.dioxuslabs.taffy.geom.measure.AvailableSpace;
+
+import java.util.Arrays;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class Text {
+    public static String LOREM_IPSUM = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.";
+
+    public record FontMetrics(
+            float charWidth,
+            float charHeight
+    ) {
+    }
+
+    public enum WritingMode {
+        HORIZONTAL,
+        VERTICAL
+    }
+
+    public record TextContext(
+            String textContent,
+            WritingMode writingMode
+    ) {
+    }
+
+    public static Size<Float> textMeasureFunction(
+            Size<Float> knownDimensions,
+            Size<AvailableSpace> availableSpace,
+            TextContext textContext,
+            FontMetrics fontMetrics
+    ) {
+        AbsoluteAxis inlineAxis = textContext.writingMode == WritingMode.HORIZONTAL ? AbsoluteAxis.HORIZONTAL : AbsoluteAxis.VERTICAL;
+        AbsoluteAxis blockAxis = inlineAxis == AbsoluteAxis.HORIZONTAL ? AbsoluteAxis.VERTICAL : AbsoluteAxis.HORIZONTAL;
+
+        String[] words = textContext.textContent.split(" ");
+
+        if (words.length == 0) {
+            return Size.zero(Float.class);
+        }
+
+        int minLineLength = Arrays.stream(words).map(String::length).max(Integer::compareTo).orElse(0);
+        AtomicInteger maxLineLength = new AtomicInteger();
+        Arrays.stream(words).forEach(line -> maxLineLength.addAndGet(line.length()));
+
+        Float abs = knownDimensions.getAbs(inlineAxis);
+        float inlineSize;
+        if (abs == null) {
+            AvailableSpace as = availableSpace.getAbs(inlineAxis);
+            if (as.isMinContent()) {
+                inlineSize = minLineLength * fontMetrics.charWidth;
+            } else if (as.isMaxContent()) {
+                inlineSize = maxLineLength.get() * fontMetrics.charWidth;
+            } else {
+                inlineSize = Math.max(Math.min(as.val(), maxLineLength.get() * fontMetrics.charWidth), minLineLength * fontMetrics.charWidth);
+            }
+        } else {
+            inlineSize = abs;
+        }
+
+        abs = knownDimensions.getAbs(blockAxis);
+        float blockSize;
+        if (abs == null) {
+            int inlineLineLength = (int) Math.floor(inlineSize / fontMetrics.charWidth);
+            int lineCount = 1;
+            int currentLineLength = 0;
+
+            for (String word : words) {
+                if (currentLineLength == 0) {
+                    // first word
+                    currentLineLength = word.length();
+                } else if (currentLineLength + word.length() + 1 > inlineLineLength) {
+                    // every word past the first needs to check for line length including the space between words
+                    // note: a real implementation of this should handle whitespace characters other than ' '
+                    // and do something more sophisticated for long words
+                    lineCount += 1;
+                    currentLineLength = word.length();
+                } else {
+                    // add the word and a space
+                    currentLineLength += word.length() + 1;
+                }
+            }
+
+            blockSize = lineCount * fontMetrics.charHeight;
+        } else {
+            blockSize = abs;
+        }
+
+        if (textContext.writingMode == WritingMode.HORIZONTAL) {
+            return new Size<>(inlineSize, blockSize);
+        } else {
+            return new Size<>(blockSize, inlineSize);
+        }
+    }
+}
diff --git a/bindings/java/java/pom.xml b/bindings/java/java/pom.xml
new file mode 100644
index 000000000..69da8cdef
--- /dev/null
+++ b/bindings/java/java/pom.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>com.dioxuslabs.taffy</groupId>
+    <artifactId>JTaffy</artifactId>
+    <version>0.0.1-SNAPSHOT</version>
+
+    <properties>
+        <maven.compiler.source>17</maven.compiler.source>
+        <maven.compiler.target>17</maven.compiler.target>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <rust.project.debug.dir>${pom.basedir}/../../../target/debug</rust.project.debug.dir>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter-api</artifactId>
+            <version>5.11.0</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <configuration>
+                    <argLine>-Djava.library.path="${rust.project.debug.dir}"</argLine>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
diff --git a/bindings/java/java/src/main/java/com/dioxuslabs/taffy/MeasureFunction.java b/bindings/java/java/src/main/java/com/dioxuslabs/taffy/MeasureFunction.java
new file mode 100644
index 000000000..d21e357b5
--- /dev/null
+++ b/bindings/java/java/src/main/java/com/dioxuslabs/taffy/MeasureFunction.java
@@ -0,0 +1,15 @@
+package com.dioxuslabs.taffy;
+
+import com.dioxuslabs.taffy.geom.Size;
+import com.dioxuslabs.taffy.geom.measure.AvailableSpace;
+import com.dioxuslabs.taffy.style.Style;
+
+public interface MeasureFunction {
+    Size<Float> measure(
+            Size<Float> knownDimensions,
+            Size<AvailableSpace> availableSpace,
+            long nodeId,
+            Object nodeContext,
+            Style style
+    );
+}
diff --git a/bindings/java/java/src/main/java/com/dioxuslabs/taffy/TaffyTree.java b/bindings/java/java/src/main/java/com/dioxuslabs/taffy/TaffyTree.java
new file mode 100644
index 000000000..8f215f1a7
--- /dev/null
+++ b/bindings/java/java/src/main/java/com/dioxuslabs/taffy/TaffyTree.java
@@ -0,0 +1,169 @@
+package com.dioxuslabs.taffy;
+
+import com.dioxuslabs.taffy.geom.Size;
+import com.dioxuslabs.taffy.geom.measure.AvailableSpace;
+import com.dioxuslabs.taffy.style.Style;
+import com.dioxuslabs.taffy.tree.Layout;
+
+import java.util.List;
+import java.util.Optional;
+
+public class TaffyTree {
+    public final long ptr;
+    public Object context;
+
+    public TaffyTree() {
+        this.ptr = nvNewTaffyTree();
+    }
+
+    public TaffyTree(int capacity) {
+        this.ptr = nvNewTaffyTreeWithCapacity(capacity);
+    }
+
+    public void enableRounding() {
+        nvEnableRounding(this.ptr);
+    }
+
+    public void disableRounding() {
+        nvDisableRounding(this.ptr);
+    }
+
+    public long newLeaf(Style style) {
+        return nvNewLeaf(this.ptr, style);
+    }
+
+    public long newLeafWithContext(Style style, Object context) {
+        this.context = context;
+        return nvNewLeaf(this.ptr, style);
+    }
+
+    public long newWithChildren(Style style, List<Long> children) {
+        return nvNewWithChildren(this.ptr, style, children);
+    }
+
+    public int childCount(long nodeId) {
+        return nvChildCount(this.ptr, nodeId);
+    }
+
+    public void clear() {
+        nvClear(this.ptr);
+    }
+
+    public long remove(long nodeId) {
+        return nvRemove(this.ptr, nodeId);
+    }
+
+    public void addChild(long parent, long child) {
+        nvAddChild(this.ptr, parent, child);
+    }
+
+    public void insertChildAtIndex(long parent, int childIndex, int child) {
+        nvInsertChildAtIndex(this.ptr, parent, childIndex, child);
+    }
+
+    public void setChildren(long parent, List<Long> children) {
+        nvSetChildren(this.ptr, parent, children);
+    }
+
+    public long removeChild(long parent, long child) {
+        return nvRemoveChild(this.ptr, parent, child);
+    }
+
+    public long removeChildAtIndex(long parent, int childIndex) {
+        return nvRemoveChildAtIndex(this.ptr, parent, childIndex);
+    }
+
+    public long replaceChildAtIndex(long parent, int childIndex, long newChild) {
+        return nvReplaceChildAtIndex(this.ptr, parent, childIndex, newChild);
+    }
+
+    public long childAtIndex(long parent, int childIndex) {
+        return nvChildAtIndex(this.ptr, parent, childIndex);
+    }
+
+    /**
+     * Returns the total number of nodes in the tree
+     */
+    public int totalNodeCount() {
+        return nvTotalNodeCount(this.ptr);
+    }
+
+    public Optional<Long> parent(long nodeId) {
+        throw new UnsupportedOperationException("Not implemented yet");
+    }
+
+    /**
+     * Returns a list of children that belong to the parent node
+     */
+    public List<Long> children(long nodeId) {
+        throw new UnsupportedOperationException("Not implemented yet");
+    }
+
+    public void setStyle(long node, Style style) {
+        nvSetStyle(this.ptr, node, style);
+    }
+
+    public Style style(long node) {
+        throw new UnsupportedOperationException("Not implemented yet");
+    }
+
+    public Layout layout(long node) {
+        return nvLayout(this.ptr, node);
+    }
+
+    public void computeLayout(long node, Size<AvailableSpace> availableSize) {
+        nvComputeLayout(this.ptr, node, availableSize);
+    }
+
+    public void printTree(long node) {
+        nvPrintTree(this.ptr, node);
+    }
+
+    private static native long nvNewTaffyTree();
+
+    private static native long nvNewTaffyTreeWithCapacity(int capacity);
+
+    private static native void nvEnableRounding(long pointer);
+
+    private static native void nvDisableRounding(long pointer);
+
+    private static native long nvNewLeaf(long pointer, Style style);
+
+    private static native long nvNewWithChildren(long pointer, Style style, List<Long> children);
+
+    private static native int nvChildCount(long pointer, long nodeId);
+
+    private static native void nvClear(long pointer);
+
+    private static native long nvRemove(long pointer, long node);
+
+    private static native void nvAddChild(long pointer, long parent, long child);
+
+    private static native void nvInsertChildAtIndex(long pointer, long parent, int childIndex, long child);
+
+    private static native void nvSetChildren(long pointer, long parent, List<Long> children);
+
+    private static native long nvRemoveChild(long pointer, long parent, long child);
+
+    private static native long nvRemoveChildAtIndex(long pointer, long parent, int childIndex);
+
+    private static native long nvReplaceChildAtIndex(long pointer, long parent, int childIndex, long newChild);
+
+    private static native long nvChildAtIndex(long pointer, long parent, int childIndex);
+
+    private static native int nvTotalNodeCount(long pointer);
+
+    private static native long nvParent(long pointer, long nodeId);
+
+    private static native List<Long> nvChildren(long pointer, long nodeId);
+
+    private static native void nvSetStyle(long pointer, long node, Style style);
+
+    private static native Style nvStyle(long pointer, long node);
+
+    private static native void nvComputeLayout(long pointer, long node, Size<AvailableSpace> availableSize);
+
+    private static native void nvPrintTree(long pointer, long node);
+
+    private static native Layout nvLayout(long pointer, long node);
+}
diff --git a/bindings/java/java/src/main/java/com/dioxuslabs/taffy/com_dioxuslabs_taffy_TaffyTree.h b/bindings/java/java/src/main/java/com/dioxuslabs/taffy/com_dioxuslabs_taffy_TaffyTree.h
new file mode 100644
index 000000000..6ae211ce6
--- /dev/null
+++ b/bindings/java/java/src/main/java/com/dioxuslabs/taffy/com_dioxuslabs_taffy_TaffyTree.h
@@ -0,0 +1,205 @@
+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include <jni.h>
+/* Header for class com_dioxuslabs_taffy_TaffyTree */
+
+#ifndef _Included_com_dioxuslabs_taffy_TaffyTree
+#define _Included_com_dioxuslabs_taffy_TaffyTree
+#ifdef __cplusplus
+extern "C" {
+#endif
+/*
+ * Class:     com_dioxuslabs_taffy_TaffyTree
+ * Method:    nvNewTaffyTree
+ * Signature: ()J
+ */
+JNIEXPORT jlong JNICALL Java_com_dioxuslabs_taffy_TaffyTree_nvNewTaffyTree
+  (JNIEnv *, jclass);
+
+/*
+ * Class:     com_dioxuslabs_taffy_TaffyTree
+ * Method:    nvNewTaffyTreeWithCapacity
+ * Signature: (I)J
+ */
+JNIEXPORT jlong JNICALL Java_com_dioxuslabs_taffy_TaffyTree_nvNewTaffyTreeWithCapacity
+  (JNIEnv *, jclass, jint);
+
+/*
+ * Class:     com_dioxuslabs_taffy_TaffyTree
+ * Method:    nvEnableRounding
+ * Signature: (J)V
+ */
+JNIEXPORT void JNICALL Java_com_dioxuslabs_taffy_TaffyTree_nvEnableRounding
+  (JNIEnv *, jclass, jlong);
+
+/*
+ * Class:     com_dioxuslabs_taffy_TaffyTree
+ * Method:    nvDisableRounding
+ * Signature: (J)V
+ */
+JNIEXPORT void JNICALL Java_com_dioxuslabs_taffy_TaffyTree_nvDisableRounding
+  (JNIEnv *, jclass, jlong);
+
+/*
+ * Class:     com_dioxuslabs_taffy_TaffyTree
+ * Method:    nvNewLeaf
+ * Signature: (JLcom/dioxuslabs/taffy/style/Style;)J
+ */
+JNIEXPORT jlong JNICALL Java_com_dioxuslabs_taffy_TaffyTree_nvNewLeaf
+  (JNIEnv *, jclass, jlong, jobject);
+
+/*
+ * Class:     com_dioxuslabs_taffy_TaffyTree
+ * Method:    nvNewWithChildren
+ * Signature: (JLcom/dioxuslabs/taffy/style/Style;Ljava/util/List;)J
+ */
+JNIEXPORT jlong JNICALL Java_com_dioxuslabs_taffy_TaffyTree_nvNewWithChildren
+  (JNIEnv *, jclass, jlong, jobject, jobject);
+
+/*
+ * Class:     com_dioxuslabs_taffy_TaffyTree
+ * Method:    nvChildCount
+ * Signature: (JJ)I
+ */
+JNIEXPORT jint JNICALL Java_com_dioxuslabs_taffy_TaffyTree_nvChildCount
+  (JNIEnv *, jclass, jlong, jlong);
+
+/*
+ * Class:     com_dioxuslabs_taffy_TaffyTree
+ * Method:    nvClear
+ * Signature: (J)V
+ */
+JNIEXPORT void JNICALL Java_com_dioxuslabs_taffy_TaffyTree_nvClear
+  (JNIEnv *, jclass, jlong);
+
+/*
+ * Class:     com_dioxuslabs_taffy_TaffyTree
+ * Method:    nvRemove
+ * Signature: (JJ)J
+ */
+JNIEXPORT jlong JNICALL Java_com_dioxuslabs_taffy_TaffyTree_nvRemove
+  (JNIEnv *, jclass, jlong, jlong);
+
+/*
+ * Class:     com_dioxuslabs_taffy_TaffyTree
+ * Method:    nvAddChild
+ * Signature: (JJJ)V
+ */
+JNIEXPORT void JNICALL Java_com_dioxuslabs_taffy_TaffyTree_nvAddChild
+  (JNIEnv *, jclass, jlong, jlong, jlong);
+
+/*
+ * Class:     com_dioxuslabs_taffy_TaffyTree
+ * Method:    nvInsertChildAtIndex
+ * Signature: (JJIJ)V
+ */
+JNIEXPORT void JNICALL Java_com_dioxuslabs_taffy_TaffyTree_nvInsertChildAtIndex
+  (JNIEnv *, jclass, jlong, jlong, jint, jlong);
+
+/*
+ * Class:     com_dioxuslabs_taffy_TaffyTree
+ * Method:    nvSetChildren
+ * Signature: (JJLjava/util/List;)V
+ */
+JNIEXPORT void JNICALL Java_com_dioxuslabs_taffy_TaffyTree_nvSetChildren
+  (JNIEnv *, jclass, jlong, jlong, jobject);
+
+/*
+ * Class:     com_dioxuslabs_taffy_TaffyTree
+ * Method:    nvRemoveChild
+ * Signature: (JJJ)J
+ */
+JNIEXPORT jlong JNICALL Java_com_dioxuslabs_taffy_TaffyTree_nvRemoveChild
+  (JNIEnv *, jclass, jlong, jlong, jlong);
+
+/*
+ * Class:     com_dioxuslabs_taffy_TaffyTree
+ * Method:    nvRemoveChildAtIndex
+ * Signature: (JJI)J
+ */
+JNIEXPORT jlong JNICALL Java_com_dioxuslabs_taffy_TaffyTree_nvRemoveChildAtIndex
+  (JNIEnv *, jclass, jlong, jlong, jint);
+
+/*
+ * Class:     com_dioxuslabs_taffy_TaffyTree
+ * Method:    nvReplaceChildAtIndex
+ * Signature: (JJIJ)J
+ */
+JNIEXPORT jlong JNICALL Java_com_dioxuslabs_taffy_TaffyTree_nvReplaceChildAtIndex
+  (JNIEnv *, jclass, jlong, jlong, jint, jlong);
+
+/*
+ * Class:     com_dioxuslabs_taffy_TaffyTree
+ * Method:    nvChildAtIndex
+ * Signature: (JJI)J
+ */
+JNIEXPORT jlong JNICALL Java_com_dioxuslabs_taffy_TaffyTree_nvChildAtIndex
+  (JNIEnv *, jclass, jlong, jlong, jint);
+
+/*
+ * Class:     com_dioxuslabs_taffy_TaffyTree
+ * Method:    nvTotalNodeCount
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_com_dioxuslabs_taffy_TaffyTree_nvTotalNodeCount
+  (JNIEnv *, jclass, jlong);
+
+/*
+ * Class:     com_dioxuslabs_taffy_TaffyTree
+ * Method:    nvParent
+ * Signature: (JJ)J
+ */
+JNIEXPORT jlong JNICALL Java_com_dioxuslabs_taffy_TaffyTree_nvParent
+  (JNIEnv *, jclass, jlong, jlong);
+
+/*
+ * Class:     com_dioxuslabs_taffy_TaffyTree
+ * Method:    nvChildren
+ * Signature: (JJ)Ljava/util/List;
+ */
+JNIEXPORT jobject JNICALL Java_com_dioxuslabs_taffy_TaffyTree_nvChildren
+  (JNIEnv *, jclass, jlong, jlong);
+
+/*
+ * Class:     com_dioxuslabs_taffy_TaffyTree
+ * Method:    nvSetStyle
+ * Signature: (JJLcom/dioxuslabs/taffy/style/Style;)V
+ */
+JNIEXPORT void JNICALL Java_com_dioxuslabs_taffy_TaffyTree_nvSetStyle
+  (JNIEnv *, jclass, jlong, jlong, jobject);
+
+/*
+ * Class:     com_dioxuslabs_taffy_TaffyTree
+ * Method:    nvStyle
+ * Signature: (JJ)Lcom/dioxuslabs/taffy/style/Style;
+ */
+JNIEXPORT jobject JNICALL Java_com_dioxuslabs_taffy_TaffyTree_nvStyle
+  (JNIEnv *, jclass, jlong, jlong);
+
+/*
+ * Class:     com_dioxuslabs_taffy_TaffyTree
+ * Method:    nvComputeLayout
+ * Signature: (JJLcom/dioxuslabs/taffy/geom/Size;)V
+ */
+JNIEXPORT void JNICALL Java_com_dioxuslabs_taffy_TaffyTree_nvComputeLayout
+  (JNIEnv *, jclass, jlong, jlong, jobject);
+
+/*
+ * Class:     com_dioxuslabs_taffy_TaffyTree
+ * Method:    nvPrintTree
+ * Signature: (JJ)V
+ */
+JNIEXPORT void JNICALL Java_com_dioxuslabs_taffy_TaffyTree_nvPrintTree
+  (JNIEnv *, jclass, jlong, jlong);
+
+/*
+ * Class:     com_dioxuslabs_taffy_TaffyTree
+ * Method:    nvLayout
+ * Signature: (JJ)Lcom/dioxuslabs/taffy/tree/Layout;
+ */
+JNIEXPORT jobject JNICALL Java_com_dioxuslabs_taffy_TaffyTree_nvLayout
+  (JNIEnv *, jclass, jlong, jlong);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/bindings/java/java/src/main/java/com/dioxuslabs/taffy/enums/AbsoluteAxis.java b/bindings/java/java/src/main/java/com/dioxuslabs/taffy/enums/AbsoluteAxis.java
new file mode 100644
index 000000000..1da764d52
--- /dev/null
+++ b/bindings/java/java/src/main/java/com/dioxuslabs/taffy/enums/AbsoluteAxis.java
@@ -0,0 +1,19 @@
+/*
+**********************************************************
+**            AUTOGENERATED CLASSES FOR TAFFY           **
+**  This code was automatically generated. Do not edit. **
+**********************************************************
+*/
+package com.dioxuslabs.taffy.enums;
+
+public enum AbsoluteAxis {
+    HORIZONTAL,
+    VERTICAL
+    ;
+
+    private final int ordinal;
+
+    AbsoluteAxis() {
+        this.ordinal = ordinal();
+    }
+}
diff --git a/bindings/java/java/src/main/java/com/dioxuslabs/taffy/enums/AlignContent.java b/bindings/java/java/src/main/java/com/dioxuslabs/taffy/enums/AlignContent.java
new file mode 100644
index 000000000..25e61c8af
--- /dev/null
+++ b/bindings/java/java/src/main/java/com/dioxuslabs/taffy/enums/AlignContent.java
@@ -0,0 +1,26 @@
+/*
+**********************************************************
+**            AUTOGENERATED CLASSES FOR TAFFY           **
+**  This code was automatically generated. Do not edit. **
+**********************************************************
+*/
+package com.dioxuslabs.taffy.enums;
+
+public enum AlignContent {
+    START,
+    END,
+    FLEX_START,
+    FLEX_END,
+    CENTER,
+    STRETCH,
+    SPACE_BETWEEN,
+    SPACE_EVENLY,
+    SPACE_AROUND
+    ;
+
+    private final int ordinal;
+
+    AlignContent() {
+        this.ordinal = ordinal();
+    }
+}
diff --git a/bindings/java/java/src/main/java/com/dioxuslabs/taffy/enums/AlignItems.java b/bindings/java/java/src/main/java/com/dioxuslabs/taffy/enums/AlignItems.java
new file mode 100644
index 000000000..1e5f0478e
--- /dev/null
+++ b/bindings/java/java/src/main/java/com/dioxuslabs/taffy/enums/AlignItems.java
@@ -0,0 +1,24 @@
+/*
+**********************************************************
+**            AUTOGENERATED CLASSES FOR TAFFY           **
+**  This code was automatically generated. Do not edit. **
+**********************************************************
+*/
+package com.dioxuslabs.taffy.enums;
+
+public enum AlignItems {
+    START,
+    END,
+    FLEX_START,
+    FLEX_END,
+    CENTER,
+    BASELINE,
+    STRETCH
+    ;
+
+    private final int ordinal;
+
+    AlignItems() {
+        this.ordinal = ordinal();
+    }
+}
diff --git a/bindings/java/java/src/main/java/com/dioxuslabs/taffy/enums/BoxGenerationMode.java b/bindings/java/java/src/main/java/com/dioxuslabs/taffy/enums/BoxGenerationMode.java
new file mode 100644
index 000000000..bbc5f06db
--- /dev/null
+++ b/bindings/java/java/src/main/java/com/dioxuslabs/taffy/enums/BoxGenerationMode.java
@@ -0,0 +1,19 @@
+/*
+**********************************************************
+**            AUTOGENERATED CLASSES FOR TAFFY           **
+**  This code was automatically generated. Do not edit. **
+**********************************************************
+*/
+package com.dioxuslabs.taffy.enums;
+
+public enum BoxGenerationMode {
+    NORMAL,
+    NONE
+    ;
+
+    private final int ordinal;
+
+    BoxGenerationMode() {
+        this.ordinal = ordinal();
+    }
+}
diff --git a/bindings/java/java/src/main/java/com/dioxuslabs/taffy/enums/BoxSizing.java b/bindings/java/java/src/main/java/com/dioxuslabs/taffy/enums/BoxSizing.java
new file mode 100644
index 000000000..752aa09c8
--- /dev/null
+++ b/bindings/java/java/src/main/java/com/dioxuslabs/taffy/enums/BoxSizing.java
@@ -0,0 +1,19 @@
+/*
+**********************************************************
+**            AUTOGENERATED CLASSES FOR TAFFY           **
+**  This code was automatically generated. Do not edit. **
+**********************************************************
+*/
+package com.dioxuslabs.taffy.enums;
+
+public enum BoxSizing {
+    BORDER_BOX,
+    CONTENT_BOX
+    ;
+
+    private final int ordinal;
+
+    BoxSizing() {
+        this.ordinal = ordinal();
+    }
+}
diff --git a/bindings/java/java/src/main/java/com/dioxuslabs/taffy/enums/Display.java b/bindings/java/java/src/main/java/com/dioxuslabs/taffy/enums/Display.java
new file mode 100644
index 000000000..5215a7d7b
--- /dev/null
+++ b/bindings/java/java/src/main/java/com/dioxuslabs/taffy/enums/Display.java
@@ -0,0 +1,21 @@
+/*
+**********************************************************
+**            AUTOGENERATED CLASSES FOR TAFFY           **
+**  This code was automatically generated. Do not edit. **
+**********************************************************
+*/
+package com.dioxuslabs.taffy.enums;
+
+public enum Display {
+    BLOCK,
+    FLEX,
+    GRID,
+    NONE
+    ;
+
+    private final int ordinal;
+
+    Display() {
+        this.ordinal = ordinal();
+    }
+}
diff --git a/bindings/java/java/src/main/java/com/dioxuslabs/taffy/enums/FlexDirection.java b/bindings/java/java/src/main/java/com/dioxuslabs/taffy/enums/FlexDirection.java
new file mode 100644
index 000000000..f16357277
--- /dev/null
+++ b/bindings/java/java/src/main/java/com/dioxuslabs/taffy/enums/FlexDirection.java
@@ -0,0 +1,21 @@
+/*
+**********************************************************
+**            AUTOGENERATED CLASSES FOR TAFFY           **
+**  This code was automatically generated. Do not edit. **
+**********************************************************
+*/
+package com.dioxuslabs.taffy.enums;
+
+public enum FlexDirection {
+    ROW,
+    COLUMN,
+    ROW_REVERSE,
+    COLUMN_REVERSE
+    ;
+
+    private final int ordinal;
+
+    FlexDirection() {
+        this.ordinal = ordinal();
+    }
+}
diff --git a/bindings/java/java/src/main/java/com/dioxuslabs/taffy/enums/FlexWrap.java b/bindings/java/java/src/main/java/com/dioxuslabs/taffy/enums/FlexWrap.java
new file mode 100644
index 000000000..c5718b122
--- /dev/null
+++ b/bindings/java/java/src/main/java/com/dioxuslabs/taffy/enums/FlexWrap.java
@@ -0,0 +1,20 @@
+/*
+**********************************************************
+**            AUTOGENERATED CLASSES FOR TAFFY           **
+**  This code was automatically generated. Do not edit. **
+**********************************************************
+*/
+package com.dioxuslabs.taffy.enums;
+
+public enum FlexWrap {
+    NO_WRAP,
+    WRAP,
+    WRAP_REVERSE
+    ;
+
+    private final int ordinal;
+
+    FlexWrap() {
+        this.ordinal = ordinal();
+    }
+}
diff --git a/bindings/java/java/src/main/java/com/dioxuslabs/taffy/enums/GridAutoFlow.java b/bindings/java/java/src/main/java/com/dioxuslabs/taffy/enums/GridAutoFlow.java
new file mode 100644
index 000000000..0bf56bb3d
--- /dev/null
+++ b/bindings/java/java/src/main/java/com/dioxuslabs/taffy/enums/GridAutoFlow.java
@@ -0,0 +1,21 @@
+/*
+**********************************************************
+**            AUTOGENERATED CLASSES FOR TAFFY           **
+**  This code was automatically generated. Do not edit. **
+**********************************************************
+*/
+package com.dioxuslabs.taffy.enums;
+
+public enum GridAutoFlow {
+    ROW,
+    COLUMN,
+    ROW_DENSE,
+    COLUMN_DENSE
+    ;
+
+    private final int ordinal;
+
+    GridAutoFlow() {
+        this.ordinal = ordinal();
+    }
+}
diff --git a/bindings/java/java/src/main/java/com/dioxuslabs/taffy/enums/Overflow.java b/bindings/java/java/src/main/java/com/dioxuslabs/taffy/enums/Overflow.java
new file mode 100644
index 000000000..67ffe141a
--- /dev/null
+++ b/bindings/java/java/src/main/java/com/dioxuslabs/taffy/enums/Overflow.java
@@ -0,0 +1,21 @@
+/*
+**********************************************************
+**            AUTOGENERATED CLASSES FOR TAFFY           **
+**  This code was automatically generated. Do not edit. **
+**********************************************************
+*/
+package com.dioxuslabs.taffy.enums;
+
+public enum Overflow {
+    VISIBLE,
+    CLIP,
+    HIDDEN,
+    SCROLL
+    ;
+
+    private final int ordinal;
+
+    Overflow() {
+        this.ordinal = ordinal();
+    }
+}
diff --git a/bindings/java/java/src/main/java/com/dioxuslabs/taffy/enums/Position.java b/bindings/java/java/src/main/java/com/dioxuslabs/taffy/enums/Position.java
new file mode 100644
index 000000000..369c1a973
--- /dev/null
+++ b/bindings/java/java/src/main/java/com/dioxuslabs/taffy/enums/Position.java
@@ -0,0 +1,19 @@
+/*
+**********************************************************
+**            AUTOGENERATED CLASSES FOR TAFFY           **
+**  This code was automatically generated. Do not edit. **
+**********************************************************
+*/
+package com.dioxuslabs.taffy.enums;
+
+public enum Position {
+    RELATIVE,
+    ABSOLUTE
+    ;
+
+    private final int ordinal;
+
+    Position() {
+        this.ordinal = ordinal();
+    }
+}
diff --git a/bindings/java/java/src/main/java/com/dioxuslabs/taffy/enums/TextAlign.java b/bindings/java/java/src/main/java/com/dioxuslabs/taffy/enums/TextAlign.java
new file mode 100644
index 000000000..9215004a5
--- /dev/null
+++ b/bindings/java/java/src/main/java/com/dioxuslabs/taffy/enums/TextAlign.java
@@ -0,0 +1,21 @@
+/*
+**********************************************************
+**            AUTOGENERATED CLASSES FOR TAFFY           **
+**  This code was automatically generated. Do not edit. **
+**********************************************************
+*/
+package com.dioxuslabs.taffy.enums;
+
+public enum TextAlign {
+    AUTO,
+    LEGACY_LEFT,
+    LEGACY_RIGHT,
+    LEGACY_CENTER
+    ;
+
+    private final int ordinal;
+
+    TextAlign() {
+        this.ordinal = ordinal();
+    }
+}
diff --git a/bindings/java/java/src/main/java/com/dioxuslabs/taffy/geom/Line.java b/bindings/java/java/src/main/java/com/dioxuslabs/taffy/geom/Line.java
new file mode 100644
index 000000000..366228403
--- /dev/null
+++ b/bindings/java/java/src/main/java/com/dioxuslabs/taffy/geom/Line.java
@@ -0,0 +1,12 @@
+package com.dioxuslabs.taffy.geom;
+
+/**
+ * An abstract "line". Represents any type that has a start and an end
+ * @param start The start position of a line
+ * @param end The end position of a line
+ */
+public record Line<T>(
+        T start,
+        T end
+) {
+}
diff --git a/bindings/java/java/src/main/java/com/dioxuslabs/taffy/geom/Point.java b/bindings/java/java/src/main/java/com/dioxuslabs/taffy/geom/Point.java
new file mode 100644
index 000000000..38ed05b5f
--- /dev/null
+++ b/bindings/java/java/src/main/java/com/dioxuslabs/taffy/geom/Point.java
@@ -0,0 +1,48 @@
+package com.dioxuslabs.taffy.geom;
+
+/**
+ * A 2-dimensional coordinate.
+ * <p>
+ * When used in association with a {@link Rect}, represents the top-left corner.
+ */
+public class Point<T> {
+    /**
+     * The x-coordinate
+     */
+    private T x;
+    /**
+     * The y-coordinate
+     */
+    private T y;
+
+    public Point(T x, T y) {
+        this.x = x;
+        this.y = y;
+    }
+
+    public T x() {
+        return x;
+    }
+
+    public T y() {
+        return y;
+    }
+
+    public void x(T x) {
+        this.x = x;
+    }
+
+    public void y(T y) {
+        this.y = y;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("""
+                        Point {
+                            x: %s,
+                            y: %s,
+                        }""",
+                x, y);
+    }
+}
diff --git a/bindings/java/java/src/main/java/com/dioxuslabs/taffy/geom/Rect.java b/bindings/java/java/src/main/java/com/dioxuslabs/taffy/geom/Rect.java
new file mode 100644
index 000000000..3106c1c0c
--- /dev/null
+++ b/bindings/java/java/src/main/java/com/dioxuslabs/taffy/geom/Rect.java
@@ -0,0 +1,81 @@
+package com.dioxuslabs.taffy.geom;
+
+/**
+ * An axis-aligned UI rectangle
+ */
+public class Rect<T> {
+    /**
+     * This can represent either the x-coordinate of the starting edge,
+     * or the amount of padding on the starting side.
+     * <p>
+     * The starting edge is the left edge when working with LTR text,
+     * and the right edge when working with RTL text.
+     */
+    private T left;
+    /**
+     * This can represent either the x-coordinate of the ending edge,
+     * or the amount of padding on the ending side.
+     */
+    private T right;
+    /**
+     * This can represent either the y-coordinate of the top edge,
+     * or the amount of padding on the top side.
+     */
+    private T top;
+    /**
+     * This can represent either the y-coordinate of the bottom edge,
+     * or the amount of padding on the bottom side.
+     */
+    private T bottom;
+
+    public Rect(T left, T right, T top, T bottom) {
+        this.left = left;
+        this.right = right;
+        this.top = top;
+        this.bottom = bottom;
+    }
+
+    public T left() {
+        return left;
+    }
+
+    public T right() {
+        return right;
+    }
+
+    public T top() {
+        return top;
+    }
+
+    public T bottom() {
+        return bottom;
+    }
+
+    public void left(T left) {
+        this.left = left;
+    }
+
+    public void right(T right) {
+        this.right = right;
+    }
+
+    public void top(T top) {
+        this.top = top;
+    }
+
+    public void bottom(T bottom) {
+        this.bottom = bottom;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("""
+                        Rect {
+                            left: %s,
+                            right: %s,
+                            top: %s,
+                            bottom: %s,
+                        }""",
+                left, right, top, bottom);
+    }
+}
diff --git a/bindings/java/java/src/main/java/com/dioxuslabs/taffy/geom/Size.java b/bindings/java/java/src/main/java/com/dioxuslabs/taffy/geom/Size.java
new file mode 100644
index 000000000..01582ab0d
--- /dev/null
+++ b/bindings/java/java/src/main/java/com/dioxuslabs/taffy/geom/Size.java
@@ -0,0 +1,223 @@
+package com.dioxuslabs.taffy.geom;
+
+import com.dioxuslabs.taffy.enums.AbsoluteAxis;
+import com.dioxuslabs.taffy.geom.measure.AvailableSpace;
+import com.dioxuslabs.taffy.geom.measure.Dimension;
+import com.dioxuslabs.taffy.geom.measure.LengthPercentage;
+import com.dioxuslabs.taffy.geom.measure.LengthPercentageAuto;
+
+/**
+ * The width and height of a {@link Rect}
+ *
+ * @param width  The x extent of the rectangle
+ * @param height The y extent of the rectangle
+ */
+public record Size<T>(
+        T width,
+        T height
+) {
+    /**
+     * Dynamic function to create a {@link Size} with a fixed width and height
+     *
+     * @param clazz  The type of the {@link Size}
+     * @param width  The width
+     * @param height The height
+     * @param <T>    The type between {@link Dimension}, {@link AvailableSpace}, {@link LengthPercentage} and {@link LengthPercentageAuto}
+     * @return A new {@link Size} with the given width and height
+     */
+    @SuppressWarnings("unchecked")
+    public static <T> Size<T> length(Class<T> clazz, float width, float height) {
+        if (clazz == Dimension.class) {
+            return (Size<T>) lengthDimension(width, height);
+        } else if (clazz == AvailableSpace.class) {
+            return (Size<T>) definiteAvailableSize(width, height);
+        } else if (clazz == LengthPercentage.class) {
+            return (Size<T>) lengthLengthPercentage(width, height);
+        } else if (clazz == LengthPercentageAuto.class) {
+            return (Size<T>) lengthLengthPercentageAuto(width, height);
+        } else if (clazz == Float.class) {
+            return (Size<T>) new Size<>(width, height);
+        }
+        return null;
+    }
+
+    /**
+     * Dynamic function to create a {@link Size} with fixed zero width and height
+     *
+     * @param clazz  The type of the {@link Size}
+     * @param <T>    The type between {@link Dimension}, {@link AvailableSpace}, {@link LengthPercentage} and {@link LengthPercentageAuto}
+     * @return A new {@link Size} with the given width and height
+     */
+    public static <T> Size<T> zero(Class<T> clazz) {
+        return length(clazz, 0, 0);
+    }
+
+    /**
+     * Dynamic function to create a {@link Size} with a percentage width and height
+     *
+     * @param clazz  The type of the {@link Size}
+     * @param width  The width
+     * @param height The height
+     * @param <T>    The type between {@link Dimension}, {@link LengthPercentage} and {@link LengthPercentageAuto}
+     * @return A new {@link Size} with the given width and height
+     */
+    @SuppressWarnings("unchecked")
+    public static <T> Size<T> percent(Class<T> clazz, float width, float height) {
+        if (clazz == Dimension.class) {
+            return (Size<T>) percentDimension(width, height);
+        } else if (clazz == LengthPercentage.class) {
+            return (Size<T>) percentLengthPercentage(width, height);
+        } else if (clazz == LengthPercentageAuto.class) {
+            return (Size<T>) percentLengthPercentageAuto(width, height);
+        }
+        return null;
+    }
+
+    /**
+     * Dynamic function to create a {@link Size} with auto as values
+     *
+     * @param clazz The type of the {@link Size}
+     * @param <T>   The type between {@link Dimension} and {@link LengthPercentageAuto}
+     * @return A new {@link Size} with auto as width and height
+     */
+    @SuppressWarnings("unchecked")
+    public static <T> Size<T> auto(Class<T> clazz) {
+        if (clazz == Dimension.class) {
+            return (Size<T>) autoDimension();
+        } else if (clazz == LengthPercentageAuto.class) {
+            return (Size<T>) autoLengthPercentageAuto();
+        }
+        return null;
+    }
+
+    /**
+     * Creates a {@link LengthPercentageAuto} {@link Size} with a fixed width and height
+     *
+     * @param width  The width
+     * @param height The height
+     * @return A new {@link Size} with the given width and height
+     */
+    public static Size<LengthPercentageAuto> lengthLengthPercentageAuto(float width, float height) {
+        return new Size<>(LengthPercentageAuto.length(width), LengthPercentageAuto.length(height));
+    }
+
+    /**
+     * Creates a {@link LengthPercentageAuto} {@link Size} with a percentage width and height
+     *
+     * @param width  The width
+     * @param height The height
+     * @return A new {@link Size} with the given width and height
+     */
+    public static Size<LengthPercentageAuto> percentLengthPercentageAuto(float width, float height) {
+        return new Size<>(LengthPercentageAuto.percent(width), LengthPercentageAuto.percent(height));
+    }
+
+    /**
+     * Creates a {@link LengthPercentageAuto} {@link Size} with auto as width and height
+     *
+     * @return A new {@link Size} with auto as width and height
+     */
+    public static Size<LengthPercentageAuto> autoLengthPercentageAuto() {
+        return new Size<>(LengthPercentageAuto.auto(), LengthPercentageAuto.auto());
+    }
+
+    /**
+     * Creates a {@link LengthPercentage} {@link Size} with a fixed width and height
+     *
+     * @param width  The width
+     * @param height The height
+     * @return A new {@link Size} with the given width and height
+     */
+    public static Size<LengthPercentage> lengthLengthPercentage(float width, float height) {
+        return new Size<>(LengthPercentage.length(width), LengthPercentage.length(height));
+    }
+
+    /**
+     * Creates a {@link LengthPercentage} {@link Size} with a percentage width and height
+     *
+     * @param width  The width
+     * @param height The height
+     * @return A new {@link Size} with the given width and height
+     */
+    public static Size<LengthPercentage> percentLengthPercentage(float width, float height) {
+        return new Size<>(LengthPercentage.percent(width), LengthPercentage.percent(height));
+    }
+
+    /**
+     * Creates a {@link Dimension} {@link Size} with a fixed width and height
+     *
+     * @param width  The width
+     * @param height The height
+     * @return A new {@link Size} with the given width and height
+     */
+    public static Size<Dimension> lengthDimension(float width, float height) {
+        return new Size<>(Dimension.length(width), Dimension.length(height));
+    }
+
+    /**
+     * Creates a {@link Dimension} {@link Size} with a fixed width and height
+     *
+     * @param width  The width
+     * @param height The height
+     * @return A new {@link Size} with the given width and height
+     */
+    public static Size<Dimension> percentDimension(float width, float height) {
+        return new Size<>(Dimension.percent(width), Dimension.percent(height));
+    }
+
+    /**
+     * Creates a {@link Dimension} {@link Size} with auto as width and height
+     *
+     * @return A new {@link Size} with auto as width and height
+     */
+    public static Size<Dimension> autoDimension() {
+        return new Size<>(Dimension.auto(), Dimension.auto());
+    }
+
+    /**
+     * Creates a {@link AvailableSpace} {@link Size} with a fixed width and height
+     *
+     * @param width  The width
+     * @param height The height
+     * @return A new {@link Size} with the given width and height
+     */
+    public static Size<AvailableSpace> definiteAvailableSize(float width, float height) {
+        return new Size<>(AvailableSpace.definite(width), AvailableSpace.definite(height));
+    }
+
+    /**
+     * Creates a {@link Dimension} {@link Size} with MinContent as width and height
+     *
+     * @return A new {@link Size} with MinContent as width and height
+     */
+    public static Size<AvailableSpace> minContentAvailableSize() {
+        return new Size<>(AvailableSpace.minContent(), AvailableSpace.minContent());
+    }
+
+    /**
+     * Creates a {@link Dimension} {@link Size} with MaxContent as width and height
+     *
+     * @return A new {@link Size} with MaxContent as width and height
+     */
+    public static Size<AvailableSpace> maxContentAvailableSize() {
+        return new Size<>(AvailableSpace.maxContent(), AvailableSpace.maxContent());
+    }
+
+    @Override
+    public String toString() {
+        return String.format("""
+                        Size {
+                            width: %s,
+                            height: %s,
+                        }""",
+                width, height);
+    }
+
+    public T getAbs(AbsoluteAxis axis) {
+        return axis == AbsoluteAxis.HORIZONTAL ? width : height;
+    }
+
+    public boolean bothAxisDefined() {
+        return width != null && height != null;
+    }
+}
diff --git a/bindings/java/java/src/main/java/com/dioxuslabs/taffy/geom/grid/GenericGridPlacement.java b/bindings/java/java/src/main/java/com/dioxuslabs/taffy/geom/grid/GenericGridPlacement.java
new file mode 100644
index 000000000..3e4cb9787
--- /dev/null
+++ b/bindings/java/java/src/main/java/com/dioxuslabs/taffy/geom/grid/GenericGridPlacement.java
@@ -0,0 +1,51 @@
+package com.dioxuslabs.taffy.geom.grid;
+
+import com.dioxuslabs.taffy.enums.GridAutoFlow;
+
+/**
+ * A grid line placement specification which is generic over the coordinate system that it uses to define
+ * grid line positions.
+ * <p>
+ * GenericGridPlacement<GridLine> is aliased as GridPlacement and is exposed to users of Taffy to define styles.
+ * GenericGridPlacement<OriginZeroLine> is aliased as OriginZeroGridPlacement and is used internally for placement computations.
+ * <p>
+ * See [`crate::compute::grid::type::coordinates`] for documentation on the different coordinate systems.
+ */
+public class GenericGridPlacement {
+    private final byte type;
+    private final short value;
+
+    private GenericGridPlacement(byte type, short value) {
+        this.type = type;
+        this.value = value;
+    }
+
+    /**
+     * Place item according to the auto-placement algorithm, and the parent's {@link GridAutoFlow} property
+     */
+    public static GenericGridPlacement auto() {
+        return new GenericGridPlacement((byte) 0, (short) 0);
+    }
+
+    /**
+     * Place item at specified line (column or row) index
+     */
+    public static GenericGridPlacement line(short value) {
+        return new GenericGridPlacement((byte) 1, value);
+    }
+
+    /**
+     * Item should span specified number of tracks (columns or rows)
+     */
+    public static GenericGridPlacement span(short value) {
+        return new GenericGridPlacement((byte) 2, value);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (!(obj instanceof GenericGridPlacement gpg)) {
+            return false;
+        }
+        return type == gpg.type && value == gpg.value;
+    }
+}
diff --git a/bindings/java/java/src/main/java/com/dioxuslabs/taffy/geom/grid/GridTrackRepetition.java b/bindings/java/java/src/main/java/com/dioxuslabs/taffy/geom/grid/GridTrackRepetition.java
new file mode 100644
index 000000000..27178350f
--- /dev/null
+++ b/bindings/java/java/src/main/java/com/dioxuslabs/taffy/geom/grid/GridTrackRepetition.java
@@ -0,0 +1,48 @@
+package com.dioxuslabs.taffy.geom.grid;
+
+/**
+ * The first argument to a repeated track definition. This type represents the type of automatic repetition to perform.
+ * <p>
+ * See <a href="https://www.w3.org/TR/css-grid-1/#auto-repeat">...</a> for an explanation of how auto-repeated
+ * track definitions work and the difference between AutoFit and AutoFill.
+ */
+public class GridTrackRepetition {
+    private final byte type;
+    private final short value;
+
+    private GridTrackRepetition(byte type, short value) {
+        this.type = type;
+        this.value = value;
+    }
+
+    /**
+     * Auto-repeating tracks should be generated to fit the container
+     * See: <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/repeat#auto-fill">...</a>
+     */
+    public static GridTrackRepetition autoFill() {
+        return new GridTrackRepetition((byte) 0, (short) 0);
+    }
+
+    /**
+     * Auto-repeating tracks should be generated to fit the container
+     * See: <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/repeat#auto-fit">...</a>
+     */
+    public static GridTrackRepetition autoFit() {
+        return new GridTrackRepetition((byte) 1, (short) 0);
+    }
+
+    /**
+     * The specified tracks should be repeated exacts N times
+     */
+    public static GridTrackRepetition count(short count) {
+        return new GridTrackRepetition((byte) 2, count);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (!(obj instanceof GridTrackRepetition lpo)) {
+            return false;
+        }
+        return type == lpo.type && value == lpo.value;
+    }
+}
diff --git a/bindings/java/java/src/main/java/com/dioxuslabs/taffy/geom/grid/MaxTrackSizingFunction.java b/bindings/java/java/src/main/java/com/dioxuslabs/taffy/geom/grid/MaxTrackSizingFunction.java
new file mode 100644
index 000000000..d52f1953a
--- /dev/null
+++ b/bindings/java/java/src/main/java/com/dioxuslabs/taffy/geom/grid/MaxTrackSizingFunction.java
@@ -0,0 +1,69 @@
+package com.dioxuslabs.taffy.geom.grid;
+
+import com.dioxuslabs.taffy.geom.measure.LengthPercentage;
+
+/**
+ * Maximum track sizing function
+ * <p>
+ * Specifies the maximum size of a grid track. A grid track will automatically size between it's minimum and maximum size based
+ * on the size of it's contents, the amount of available space, and the sizing constraint the grid is being size under.
+ * See <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/grid-template-columns">...</a>
+ */
+public class MaxTrackSizingFunction {
+    private final byte type;
+    private final Object value;
+
+    private MaxTrackSizingFunction(byte type, LengthPercentage value) {
+        this.type = type;
+        this.value = value;
+    }
+
+    private MaxTrackSizingFunction(byte type, float value) {
+        this.type = type;
+        this.value = value;
+    }
+
+    /**
+     * Track maximum size should be a fixed length or percentage value
+     */
+    public static MaxTrackSizingFunction fixed(LengthPercentage value) {
+        return new MaxTrackSizingFunction((byte) 0, value);
+    }
+
+    /**
+     * Track maximum size should be content sized under a min-content constraint
+     */
+    public static MaxTrackSizingFunction minContent() {
+        return new MaxTrackSizingFunction((byte) 1, null);
+    }
+
+    /**
+     * Track maximum size should be content sized under a max-content constraint
+     */
+    public static MaxTrackSizingFunction maxContent() {
+        return new MaxTrackSizingFunction((byte) 2, null);
+    }
+
+    /**
+     * Track maximum size should be sized according to the fit-content formula
+     */
+    public static MaxTrackSizingFunction fitContent(LengthPercentage value) {
+        return new MaxTrackSizingFunction((byte) 3, value);
+    }
+
+    /**
+     * Track maximum size should be automatically sized
+     */
+    public static MaxTrackSizingFunction auto() {
+        return new MaxTrackSizingFunction((byte) 4, null);
+    }
+
+    /**
+     * The dimension as a fraction of the total available grid space (`fr` units in CSS)
+     * Specified value is the numerator of the fraction. Denominator is the sum of all fraction specified in that grid dimension
+     * Spec: <a href="https://www.w3.org/TR/css3-grid-layout/#fr-unit">Spec</a>
+     */
+    public static MaxTrackSizingFunction fraction(float value) {
+        return new MaxTrackSizingFunction((byte) 5, value);
+    }
+}
diff --git a/bindings/java/java/src/main/java/com/dioxuslabs/taffy/geom/grid/MinTrackSizingFunction.java b/bindings/java/java/src/main/java/com/dioxuslabs/taffy/geom/grid/MinTrackSizingFunction.java
new file mode 100644
index 000000000..8ae955813
--- /dev/null
+++ b/bindings/java/java/src/main/java/com/dioxuslabs/taffy/geom/grid/MinTrackSizingFunction.java
@@ -0,0 +1,48 @@
+package com.dioxuslabs.taffy.geom.grid;
+
+import com.dioxuslabs.taffy.geom.measure.LengthPercentage;
+
+/**
+ * Minimum track sizing function
+ * <p>
+ * Specifies the minimum size of a grid track. A grid track will automatically size between it's minimum and maximum size based
+ * on the size of it's contents, the amount of available space, and the sizing constraint the grid is being size under.
+ * See <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/grid-template-columns">...</a>
+ */
+public class MinTrackSizingFunction {
+    private final byte type;
+    private final LengthPercentage value;
+
+    private MinTrackSizingFunction(byte type, LengthPercentage value) {
+        this.type = type;
+        this.value = value;
+    }
+
+    /**
+     * Track minimum size should be a fixed length or percentage value
+     */
+    public static MinTrackSizingFunction fixed(LengthPercentage value) {
+        return new MinTrackSizingFunction((byte) 0, value);
+    }
+
+    /**
+     * Track minimum size should be content sized under a min-content constraint
+     */
+    public static MinTrackSizingFunction minContent() {
+        return new MinTrackSizingFunction((byte) 1, null);
+    }
+
+    /**
+     * Track minimum size should be content sized under a max-content constraint
+     */
+    public static MinTrackSizingFunction maxContent() {
+        return new MinTrackSizingFunction((byte) 2, null);
+    }
+
+    /**
+     * Track minimum size should be automatically sized
+     */
+    public static MinTrackSizingFunction auto() {
+        return new MinTrackSizingFunction((byte) 3, null);
+    }
+}
diff --git a/bindings/java/java/src/main/java/com/dioxuslabs/taffy/geom/grid/NonRepeatedTrackSizingFunction.java b/bindings/java/java/src/main/java/com/dioxuslabs/taffy/geom/grid/NonRepeatedTrackSizingFunction.java
new file mode 100644
index 000000000..ed5e9c62e
--- /dev/null
+++ b/bindings/java/java/src/main/java/com/dioxuslabs/taffy/geom/grid/NonRepeatedTrackSizingFunction.java
@@ -0,0 +1,15 @@
+package com.dioxuslabs.taffy.geom.grid;
+
+/**
+ * The sizing function for a grid track (row/column) (either auto-track or template track)
+ * May either be a MinMax variant which specifies separate values for the min-/max- track sizing functions
+ * or a scalar value which applies to both track sizing functions.
+ *
+ * @param min The value representing the minimum
+ * @param max The value representing the maximum
+ */
+public record NonRepeatedTrackSizingFunction(
+        MinTrackSizingFunction min,
+        MaxTrackSizingFunction max
+) {
+}
diff --git a/bindings/java/java/src/main/java/com/dioxuslabs/taffy/geom/grid/TrackSizingFunction.java b/bindings/java/java/src/main/java/com/dioxuslabs/taffy/geom/grid/TrackSizingFunction.java
new file mode 100644
index 000000000..eaeccb14f
--- /dev/null
+++ b/bindings/java/java/src/main/java/com/dioxuslabs/taffy/geom/grid/TrackSizingFunction.java
@@ -0,0 +1,51 @@
+package com.dioxuslabs.taffy.geom.grid;
+
+import java.util.List;
+
+/**
+ * The sizing function for a grid track (row/column)
+ * See <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/grid-template-columns">...</a>
+ */
+public abstract class TrackSizingFunction {
+    private byte type;
+
+    private TrackSizingFunction(byte type) {
+        this.type = type;
+    }
+
+    /**
+     * A single non-repeated track
+     */
+    public static TrackSizingFunction single(NonRepeatedTrackSizingFunction func) {
+        return new TrackSingleSizingFunction(func);
+    }
+
+    /**
+     * Automatically generate grid tracks to fit the available space using the specified definite track lengths
+     * Only valid if every track in template (not just the repetition) has a fixed size.
+     */
+    public static TrackSizingFunction repeat(GridTrackRepetition reps, List<NonRepeatedTrackSizingFunction> list) {
+        return new TrackRepeatSizingFunction(reps, list);
+    }
+
+    private static class TrackSingleSizingFunction extends TrackSizingFunction {
+        private final NonRepeatedTrackSizingFunction func;
+
+        private TrackSingleSizingFunction(NonRepeatedTrackSizingFunction func) {
+            super((byte) 0);
+            this.func = func;
+        }
+    }
+
+    private static class TrackRepeatSizingFunction extends TrackSizingFunction {
+        private final GridTrackRepetition repetitions;
+        private final List<NonRepeatedTrackSizingFunction> functions;
+
+        private TrackRepeatSizingFunction(GridTrackRepetition repetitions,
+                                          List<NonRepeatedTrackSizingFunction> functions) {
+            super((byte) 1);
+            this.repetitions = repetitions;
+            this.functions = functions;
+        }
+    }
+}
diff --git a/bindings/java/java/src/main/java/com/dioxuslabs/taffy/geom/measure/AvailableSpace.java b/bindings/java/java/src/main/java/com/dioxuslabs/taffy/geom/measure/AvailableSpace.java
new file mode 100644
index 000000000..d99ee61ee
--- /dev/null
+++ b/bindings/java/java/src/main/java/com/dioxuslabs/taffy/geom/measure/AvailableSpace.java
@@ -0,0 +1,47 @@
+package com.dioxuslabs.taffy.geom.measure;
+
+public class AvailableSpace {
+    private final byte type;
+    private final float value;
+
+    private AvailableSpace(byte type, float value) {
+        this.type = type;
+        this.value = value;
+    }
+
+    public static AvailableSpace definite(float value) {
+        return new AvailableSpace((byte) 0, value);
+    }
+
+    public static AvailableSpace minContent() {
+        return new AvailableSpace((byte) 1, 0);
+    }
+
+    public static AvailableSpace maxContent() {
+        return new AvailableSpace((byte) 2, 0);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (!(obj instanceof AvailableSpace as)) {
+            return false;
+        }
+        return type == as.type && value == as.value;
+    }
+
+    public boolean isDefinite() {
+        return type == 0;
+    }
+
+    public boolean isMinContent() {
+        return type == 1;
+    }
+
+    public boolean isMaxContent() {
+        return type == 2;
+    }
+
+    public float val() {
+        return value;
+    }
+}
diff --git a/bindings/java/java/src/main/java/com/dioxuslabs/taffy/geom/measure/Dimension.java b/bindings/java/java/src/main/java/com/dioxuslabs/taffy/geom/measure/Dimension.java
new file mode 100644
index 000000000..0ca0f755d
--- /dev/null
+++ b/bindings/java/java/src/main/java/com/dioxuslabs/taffy/geom/measure/Dimension.java
@@ -0,0 +1,31 @@
+package com.dioxuslabs.taffy.geom.measure;
+
+public class Dimension {
+    private final byte type;
+    private final float value;
+
+    private Dimension(byte type, float value) {
+        this.type = type;
+        this.value = value;
+    }
+
+    public static Dimension length(float value) {
+        return new Dimension((byte) 0, value);
+    }
+
+    public static Dimension percent(float value) {
+        return new Dimension((byte) 1, value);
+    }
+
+    public static Dimension auto() {
+        return new Dimension((byte) 2, 0);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (!(obj instanceof Dimension lpo)) {
+            return false;
+        }
+        return type == lpo.type && value == lpo.value;
+    }
+}
diff --git a/bindings/java/java/src/main/java/com/dioxuslabs/taffy/geom/measure/LengthPercentage.java b/bindings/java/java/src/main/java/com/dioxuslabs/taffy/geom/measure/LengthPercentage.java
new file mode 100644
index 000000000..b56469dac
--- /dev/null
+++ b/bindings/java/java/src/main/java/com/dioxuslabs/taffy/geom/measure/LengthPercentage.java
@@ -0,0 +1,31 @@
+package com.dioxuslabs.taffy.geom.measure;
+
+public class LengthPercentage {
+    private final byte type;
+    private final float value;
+
+    private LengthPercentage(byte type, float value) {
+        this.type = type;
+        this.value = value;
+    }
+
+    public static LengthPercentage length(float value) {
+        return new LengthPercentage((byte) 0, value);
+    }
+
+    public static LengthPercentage percent(float value) {
+        return new LengthPercentage((byte) 1, value);
+    }
+
+    public static LengthPercentage zero() {
+        return length(0);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (!(obj instanceof LengthPercentage lpo)) {
+            return false;
+        }
+        return type == lpo.type && value == lpo.value;
+    }
+}
diff --git a/bindings/java/java/src/main/java/com/dioxuslabs/taffy/geom/measure/LengthPercentageAuto.java b/bindings/java/java/src/main/java/com/dioxuslabs/taffy/geom/measure/LengthPercentageAuto.java
new file mode 100644
index 000000000..0a1950015
--- /dev/null
+++ b/bindings/java/java/src/main/java/com/dioxuslabs/taffy/geom/measure/LengthPercentageAuto.java
@@ -0,0 +1,31 @@
+package com.dioxuslabs.taffy.geom.measure;
+
+public class LengthPercentageAuto {
+    private final byte type;
+    private final float value;
+
+    private LengthPercentageAuto(byte type, float value) {
+        this.type = type;
+        this.value = value;
+    }
+
+    public static LengthPercentageAuto length(float value) {
+        return new LengthPercentageAuto((byte) 0, value);
+    }
+
+    public static LengthPercentageAuto percent(float value) {
+        return new LengthPercentageAuto((byte) 1, value);
+    }
+
+    public static LengthPercentageAuto auto() {
+        return new LengthPercentageAuto((byte) 2, 0);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (!(obj instanceof LengthPercentageAuto lpo)) {
+            return false;
+        }
+        return type == lpo.type && value == lpo.value;
+    }
+}
diff --git a/bindings/java/java/src/main/java/com/dioxuslabs/taffy/style/Style.java b/bindings/java/java/src/main/java/com/dioxuslabs/taffy/style/Style.java
new file mode 100644
index 000000000..f93b4d963
--- /dev/null
+++ b/bindings/java/java/src/main/java/com/dioxuslabs/taffy/style/Style.java
@@ -0,0 +1,241 @@
+package com.dioxuslabs.taffy.style;
+
+import com.dioxuslabs.taffy.enums.*;
+import com.dioxuslabs.taffy.geom.Line;
+import com.dioxuslabs.taffy.geom.Point;
+import com.dioxuslabs.taffy.geom.Rect;
+import com.dioxuslabs.taffy.geom.Size;
+import com.dioxuslabs.taffy.geom.grid.GenericGridPlacement;
+import com.dioxuslabs.taffy.geom.grid.NonRepeatedTrackSizingFunction;
+import com.dioxuslabs.taffy.geom.grid.TrackSizingFunction;
+import com.dioxuslabs.taffy.geom.measure.Dimension;
+import com.dioxuslabs.taffy.geom.measure.LengthPercentage;
+import com.dioxuslabs.taffy.geom.measure.LengthPercentageAuto;
+
+import java.util.List;
+
+@SuppressWarnings("all")
+public class Style {
+    private Display display;
+    private boolean itemIsTable;
+    private BoxSizing boxSizing;
+
+    private Point<Overflow> overflow;
+    private float scrollbarWidth;
+
+    private Position position;
+    private Rect<LengthPercentageAuto> inset;
+
+    private Size<Dimension> size;
+    private Size<Dimension> minSize;
+    private Size<Dimension> maxSize;
+    private Float aspectRatio;
+
+    private Rect<LengthPercentageAuto> margin;
+    private Rect<LengthPercentage> padding;
+    private Rect<LengthPercentage> border;
+
+    private AlignItems alignItems;
+    private AlignItems alignSelf;
+    private AlignItems justifyItems;
+    private AlignItems justifySelf;
+    private AlignContent alignContent;
+    private AlignContent justifyContent;
+    private Size<LengthPercentage> gap;
+
+    private TextAlign textAlign;
+
+    private FlexDirection flexDirection;
+    private FlexWrap flexWrap;
+
+    private Dimension flexBasis;
+    private float flexGrow;
+    private float flexShrink;
+
+    private List<TrackSizingFunction> gridTemplateRows;
+    private List<TrackSizingFunction> gridTemplateColumns;
+    private List<NonRepeatedTrackSizingFunction> gridAutoRows;
+    private List<NonRepeatedTrackSizingFunction> gridAutoColumns;
+    private GridAutoFlow gridAutoFlow;
+
+    private Line<GenericGridPlacement> gridRow;
+    private Line<GenericGridPlacement> gridColumn;
+
+    public static Style builder() {
+        return new Style();
+    }
+
+    public static Style def() {
+        return new Style();
+    }
+
+    public Style display(Display display) {
+        this.display = display;
+        return this;
+    }
+
+    public Style itemIsTable(boolean itemIsTable) {
+        this.itemIsTable = itemIsTable;
+        return this;
+    }
+
+    public Style boxSizing(BoxSizing boxSizing) {
+        this.boxSizing = boxSizing;
+        return this;
+    }
+
+    public Style overflow(Point<Overflow> overflow) {
+        this.overflow = overflow;
+        return this;
+    }
+
+    public Style scrollbarWidth(float scrollbarWidth) {
+        this.scrollbarWidth = scrollbarWidth;
+        return this;
+    }
+
+    public Style position(Position position) {
+        this.position = position;
+        return this;
+    }
+
+    public Style inset(Rect<LengthPercentageAuto> inset) {
+        this.inset = inset;
+        return this;
+    }
+
+    public Style size(Size<Dimension> size) {
+        this.size = size;
+        return this;
+    }
+
+    public Style minSize(Size<Dimension> minSize) {
+        this.minSize = minSize;
+        return this;
+    }
+
+    public Style maxSize(Size<Dimension> maxSize) {
+        this.maxSize = maxSize;
+        return this;
+    }
+
+    public Style aspectRatio(Float aspectRatio) {
+        this.aspectRatio = aspectRatio;
+        return this;
+    }
+
+    public Style margin(Rect<LengthPercentageAuto> margin) {
+        this.margin = margin;
+        return this;
+    }
+
+    public Style padding(Rect<LengthPercentage> padding) {
+        this.padding = padding;
+        return this;
+    }
+
+    public Style border(Rect<LengthPercentage> border) {
+        this.border = border;
+        return this;
+    }
+
+    public Style alignItems(AlignItems alignItems) {
+        this.alignItems = alignItems;
+        return this;
+    }
+
+    public Style alignSelf(AlignItems alignSelf) {
+        this.alignSelf = alignSelf;
+        return this;
+    }
+
+    public Style justifyItems(AlignItems justifyItems) {
+        this.justifyItems = justifyItems;
+        return this;
+    }
+
+    public Style justifySelf(AlignItems justifySelf) {
+        this.justifySelf = justifySelf;
+        return this;
+    }
+
+    public Style alignContent(AlignContent alignContent) {
+        this.alignContent = alignContent;
+        return this;
+    }
+
+    public Style justifyContent(AlignContent justifyContent) {
+        this.justifyContent = justifyContent;
+        return this;
+    }
+
+    public Style gap(Size<LengthPercentage> gap) {
+        this.gap = gap;
+        return this;
+    }
+
+    public Style textAlign(TextAlign textAlign) {
+        this.textAlign = textAlign;
+        return this;
+    }
+
+    public Style flexDirection(FlexDirection flexDirection) {
+        this.flexDirection = flexDirection;
+        return this;
+    }
+
+    public Style flexWrap(FlexWrap flexWrap) {
+        this.flexWrap = flexWrap;
+        return this;
+    }
+
+    public Style flexBasis(Dimension flexBasis) {
+        this.flexBasis = flexBasis;
+        return this;
+    }
+
+    public Style flexGrow(float flexGrow) {
+        this.flexGrow = flexGrow;
+        return this;
+    }
+
+    public Style flexShrink(float flexShrink) {
+        this.flexShrink = flexShrink;
+        return this;
+    }
+
+    public Style gridTemplateRows(List<TrackSizingFunction> gridTemplateRows) {
+        this.gridTemplateRows = gridTemplateRows;
+        return this;
+    }
+
+    public Style gridTemplateColumns(List<TrackSizingFunction> gridTemplateColumns) {
+        this.gridTemplateColumns = gridTemplateColumns;
+        return this;
+    }
+
+    public Style gridAutoRows(List<NonRepeatedTrackSizingFunction> gridAutoRows) {
+        this.gridAutoRows = gridAutoRows;
+        return this;
+    }
+
+    public Style gridAutoColumns(List<NonRepeatedTrackSizingFunction> gridAutoColumns) {
+        this.gridAutoColumns = gridAutoColumns;
+        return this;
+    }
+
+    public Style gridAutoFlow(GridAutoFlow gridAutoFlow) {
+        this.gridAutoFlow = gridAutoFlow;
+        return this;
+    }
+
+    public Style gridRow(Line<GenericGridPlacement> gridRow) {
+        this.gridRow = gridRow;
+        return this;
+    }
+
+    public Style gridColumn(Line<GenericGridPlacement> gridColumn) {
+        this.gridColumn = gridColumn;
+        return this;
+    }
+}
diff --git a/bindings/java/java/src/main/java/com/dioxuslabs/taffy/tree/Layout.java b/bindings/java/java/src/main/java/com/dioxuslabs/taffy/tree/Layout.java
new file mode 100644
index 000000000..9977ce2ab
--- /dev/null
+++ b/bindings/java/java/src/main/java/com/dioxuslabs/taffy/tree/Layout.java
@@ -0,0 +1,50 @@
+package com.dioxuslabs.taffy.tree;
+
+import com.dioxuslabs.taffy.geom.Point;
+import com.dioxuslabs.taffy.geom.Rect;
+import com.dioxuslabs.taffy.geom.Size;
+
+/**
+ * The final result of a layout algorithm for a single node.
+ *
+ * @param order         The relative ordering of the node
+ *                      <p>
+ *                      Nodes with a higher order should be rendered on top of those with a lower order. This is
+ *                      effectively a topological sort of each tree.
+ * @param location      The top-left corner of the node
+ * @param size          The width and height of the node
+ * @param contentSize   The width and height of the content inside the node. This may be larger than the size
+ *                      of the node in the case of overflowing content and is useful for computing a "scroll
+ *                      width/height" for scrollable nodes
+ * @param scrollbarSize The size of the scrollbars in each dimension. If there is no scrollbar then the
+ *                      size will be zero.
+ * @param border        The size of the borders of the node
+ * @param padding       The size of the padding of the node
+ * @param margin        The size of the margin of the node
+ */
+public record Layout(
+        int order,
+        Point<Float> location,
+        Size<Float> size,
+        Size<Float> contentSize,
+        Size<Float> scrollbarSize,
+        Rect<Float> border,
+        Rect<Float> padding,
+        Rect<Float> margin
+) {
+    @Override
+    public String toString() {
+        return String.format("""
+                        Layout {
+                            order: %s,
+                            location: %s,
+                            size: %s;
+                            content_size: %s,
+                            scrollbar_size: %s,
+                            border: %s,
+                            padding: %s,
+                            margin: %s,
+                        }""",
+                order, location, size, contentSize, scrollbarSize, border, padding, margin);
+    }
+}
diff --git a/bindings/java/java/src/test/java/com/dioxuslabs/taffy/BorderAndPaddingTests.java b/bindings/java/java/src/test/java/com/dioxuslabs/taffy/BorderAndPaddingTests.java
new file mode 100644
index 000000000..5c394b00e
--- /dev/null
+++ b/bindings/java/java/src/test/java/com/dioxuslabs/taffy/BorderAndPaddingTests.java
@@ -0,0 +1,109 @@
+package com.dioxuslabs.taffy;
+
+import com.dioxuslabs.taffy.geom.Rect;
+import com.dioxuslabs.taffy.geom.Size;
+import com.dioxuslabs.taffy.geom.measure.LengthPercentage;
+import com.dioxuslabs.taffy.style.Style;
+import com.dioxuslabs.taffy.tree.Layout;
+import org.junit.jupiter.api.Test;
+
+import java.util.Arrays;
+
+import static com.dioxuslabs.taffy.TaffyTests.assertEquals;
+
+public class BorderAndPaddingTests {
+    static {
+        System.loadLibrary("jtaffy");
+    }
+
+    public <T> Rect<T> arrToRect(T[] items) {
+        return new Rect<>(items[0], items[1], items[2], items[3]);
+    }
+
+    @Test
+    public void borderOnASingleAxisDoesntIncreaseSize() {
+        for (int i = 0; i < 4; i++) {
+            TaffyTree taffy = new TaffyTree();
+
+            LengthPercentage[] lengths = new LengthPercentage[4];
+            Arrays.fill(lengths, LengthPercentage.length(0f));
+            lengths[i] = LengthPercentage.length(10f);
+            Rect<LengthPercentage> rect = arrToRect(lengths);
+
+            long node = taffy.newLeaf(Style.builder()
+                    .border(rect)
+            );
+
+            taffy.computeLayout(node, Size.definiteAvailableSize(100, 100));
+
+            Layout layout = taffy.layout(node);
+
+            assertEquals(layout.size().width() * layout.size().height(), 0.0f);
+        }
+    }
+
+    @Test
+    public void paddingOnASingleAxisDoesntIncreaseSize() {
+        for (int i = 0; i < 4; i++) {
+            TaffyTree taffy = new TaffyTree();
+
+            LengthPercentage[] lengths = new LengthPercentage[4];
+            Arrays.fill(lengths, LengthPercentage.length(0f));
+            lengths[i] = LengthPercentage.length(10f);
+            Rect<LengthPercentage> rect = arrToRect(lengths);
+
+            long node = taffy.newLeaf(Style.builder()
+                    .padding(rect)
+            );
+
+            taffy.computeLayout(node, Size.definiteAvailableSize(100, 100));
+
+            Layout layout = taffy.layout(node);
+
+            assertEquals(layout.size().width() * layout.size().height(), 0.0f);
+        }
+    }
+
+    @Test
+    public void borderAndPaddingOnASingleAxisDoesntIncreaseSize() {
+        for (int i = 0; i < 4; i++) {
+            TaffyTree taffy = new TaffyTree();
+
+            LengthPercentage[] lengths = new LengthPercentage[4];
+            Arrays.fill(lengths, LengthPercentage.length(0f));
+            lengths[i] = LengthPercentage.length(10f);
+            Rect<LengthPercentage> rect = arrToRect(lengths);
+
+            long node = taffy.newLeaf(Style.builder()
+                    .border(rect)
+                    .padding(rect)
+            );
+
+            taffy.computeLayout(node, Size.definiteAvailableSize(100, 100));
+
+            Layout layout = taffy.layout(node);
+
+            assertEquals(layout.size().width() * layout.size().height(), 0.0f);
+        }
+    }
+
+    @Test
+    public void verticalBorderAndPaddingPercentageValuesUseAvailableSpaceCorrectly() {
+        TaffyTree taffy = new TaffyTree();
+        long node = taffy.newLeaf(Style.builder()
+                .padding(new Rect<>(
+                        LengthPercentage.percent(1f),
+                        LengthPercentage.length(0f),
+                        LengthPercentage.percent(1f),
+                        LengthPercentage.length(0f)
+                ))
+        );
+
+        taffy.computeLayout(node, Size.definiteAvailableSize(200, 100));
+
+        Layout layout = taffy.layout(node);
+
+        assertEquals(layout.size().width(), 200.0f);
+        assertEquals(layout.size().height(), 200.0f);
+    }
+}
diff --git a/bindings/java/java/src/test/java/com/dioxuslabs/taffy/RoundingTests.java b/bindings/java/java/src/test/java/com/dioxuslabs/taffy/RoundingTests.java
new file mode 100644
index 000000000..a880a7a99
--- /dev/null
+++ b/bindings/java/java/src/test/java/com/dioxuslabs/taffy/RoundingTests.java
@@ -0,0 +1,44 @@
+package com.dioxuslabs.taffy;
+
+import com.dioxuslabs.taffy.enums.AlignContent;
+import com.dioxuslabs.taffy.geom.Size;
+import com.dioxuslabs.taffy.geom.measure.Dimension;
+import com.dioxuslabs.taffy.style.Style;
+import com.dioxuslabs.taffy.tree.Layout;
+import org.junit.jupiter.api.Test;
+
+import java.util.Arrays;
+
+import static com.dioxuslabs.taffy.TaffyTests.assertEquals;
+
+public class RoundingTests {
+    static {
+        System.loadLibrary("jtaffy");
+    }
+
+    @Test
+    public void roundingDoesntLeaveGaps() {
+        // First create an instance of TaffyTree
+        TaffyTree taffy = new TaffyTree();
+
+        Size<Dimension> wSquare = Size.lengthDimension(100.3f, 100.3f);
+
+        long childA = taffy.newLeaf(Style.builder().size(wSquare));
+        long childB = taffy.newLeaf(Style.builder().size(wSquare));
+
+        long rootNode = taffy.newWithChildren(
+                Style.builder()
+                        .size(Size.length(Dimension.class, 963.3333f, 1000f))
+                        .justifyContent(AlignContent.CENTER),
+                Arrays.asList(childA, childB)
+        );
+
+        taffy.computeLayout(rootNode, Size.maxContentAvailableSize());
+        taffy.printTree(rootNode);
+
+        Layout layoutA = taffy.layout(childA);
+        Layout layoutB = taffy.layout(childB);
+
+        assertEquals(layoutA.location().x() + layoutA.size().width(), layoutB.location().x());
+    }
+}
diff --git a/bindings/java/java/src/test/java/com/dioxuslabs/taffy/TaffyTests.java b/bindings/java/java/src/test/java/com/dioxuslabs/taffy/TaffyTests.java
new file mode 100644
index 000000000..a7a42fcb0
--- /dev/null
+++ b/bindings/java/java/src/test/java/com/dioxuslabs/taffy/TaffyTests.java
@@ -0,0 +1,9 @@
+package com.dioxuslabs.taffy;
+
+import org.junit.jupiter.api.Assertions;
+
+public class TaffyTests {
+    public static void assertEquals(Object actual, Object expected) {
+        Assertions.assertEquals(expected, actual);
+    }
+}
diff --git a/bindings/java/src/collections.rs b/bindings/java/src/collections.rs
new file mode 100644
index 000000000..6e4f8cbb1
--- /dev/null
+++ b/bindings/java/src/collections.rs
@@ -0,0 +1,54 @@
+use crate::conversions::f_get_value;
+use jni::objects::{JObject, JValue, JValueOwned};
+use jni::JNIEnv;
+use std::convert::TryInto;
+
+/// Unwraps a Java List into a Rust Vec
+pub unsafe fn get_list<'local, T, F>(env: &mut JNIEnv<'local>, value: JValueOwned<'local>, f: F) -> Vec<T>
+where
+    F: Fn(&mut JNIEnv<'local>, JValueOwned<'local>) -> Option<T>,
+{
+    let jlist_object = value.l().unwrap();
+
+    if jlist_object.is_null() {
+        return Vec::with_capacity(0);
+    }
+
+    let list = env.get_list(&jlist_object).unwrap();
+
+    let jlist_size = env.call_method(&list, "size", "()I", &[]).expect("Couldn't call size on List").i().unwrap();
+
+    let mut vec: Vec<T> = Vec::new();
+    for i in 0..jlist_size {
+        let element = env.call_method(&list, "get", "(I)Ljava/lang/Object;", &[JValue::Int(i)]).unwrap();
+
+        let value = f(env, element);
+        if let Some(value) = value {
+            vec.push(value);
+        }
+    }
+
+    vec
+}
+
+/// Unwraps a Java List into a Rust array, it basically uses `unwrap_jlist` and then tries to convert the Vec into an array
+#[allow(dead_code)]
+pub unsafe fn get_array<'local, T, F, const N: usize>(
+    env: &mut JNIEnv<'local>,
+    value: JValueOwned<'local>,
+    f: F,
+) -> [T; N]
+where
+    F: Fn(&mut JNIEnv<'local>, JValueOwned<'local>) -> Option<T>,
+{
+    let vec = get_list(env, value, f);
+    vec.try_into().unwrap_or_else(|v: Vec<T>| panic!("Expected a Vec of length {} but it was {}", N, v.len()))
+}
+
+pub unsafe fn f_get_list<'local, T, F>(env: &mut JNIEnv<'local>, base: &JObject<'local>, field: &str, f: F) -> Vec<T>
+where
+    F: Fn(&mut JNIEnv<'local>, JValueOwned<'local>) -> Option<T>,
+{
+    let value = f_get_value(env, base, field, "Ljava/util/List;");
+    get_list(env, value, f)
+}
diff --git a/bindings/java/src/conversions.rs b/bindings/java/src/conversions.rs
new file mode 100644
index 000000000..f0c4f5aad
--- /dev/null
+++ b/bindings/java/src/conversions.rs
@@ -0,0 +1,123 @@
+use crate::collections::f_get_list;
+use crate::enums::{
+    f_get_align_content, f_get_align_items, f_get_box_sizing, f_get_display, f_get_flex_direction, f_get_flex_wrap,
+    f_get_grid_auto_flow, f_get_position, f_get_text_align, get_overflow,
+};
+use crate::geom::{f_get_line, f_get_point, f_get_rect, f_get_size, get_opt_non_repeated_track_sizing_function};
+use crate::measure::{
+    f_get_dimension_or, get_dimension, get_grid_placement, get_length_percentage, get_length_percentage_auto,
+    get_opt_track_sizing_function,
+};
+use crate::primitives::{f_bool_from_primitive, f_f32_from_primitive, f_opt_f32_from_object};
+use jni::objects::{JObject, JValueOwned};
+use jni::JNIEnv;
+use taffy::{Dimension, GridPlacement, Line, Overflow, Point, Rect, Size, Style};
+
+pub unsafe fn get_style<'local>(env: &mut JNIEnv<'local>, style: &JObject<'local>) -> Style {
+    let display = f_get_display(env, style, "display");
+    let item_is_table = f_bool_from_primitive(env, style, "itemIsTable", || false);
+    let box_sizing = f_get_box_sizing(env, style, "boxSizing");
+
+    let overflow =
+        f_get_point(env, style, "overflow", get_overflow, || Point { x: Overflow::Visible, y: Overflow::Visible });
+    let scrollbar_width = f_f32_from_primitive(env, style, "scrollbarWidth", || 0.0);
+
+    let position = f_get_position(env, style, "position");
+    let inset = f_get_rect(env, style, "inset", get_length_percentage_auto, Rect::auto);
+
+    let size = f_get_size(env, style, "size", get_dimension, Size::auto);
+    let min_size = f_get_size(env, style, "minSize", get_dimension, Size::auto);
+    let max_size = f_get_size(env, style, "maxSize", get_dimension, Size::auto);
+    let aspect_ratio = f_opt_f32_from_object(env, style, "aspectRatio", || None);
+
+    let margin = f_get_rect(env, style, "margin", get_length_percentage_auto, Rect::zero);
+    let padding = f_get_rect(env, style, "padding", get_length_percentage, Rect::zero);
+    let border = f_get_rect(env, style, "border", get_length_percentage, Rect::zero);
+
+    let align_items = f_get_align_items(env, style, "alignItems");
+    let align_self = f_get_align_items(env, style, "alignSelf");
+    let justify_items = f_get_align_items(env, style, "justifyItems");
+    let justify_self = f_get_align_items(env, style, "justifySelf");
+    let align_content = f_get_align_content(env, style, "alignContent");
+    let justify_content = f_get_align_content(env, style, "justifyContent");
+    let gap = f_get_size(env, style, "gap", get_length_percentage, Size::zero);
+
+    let text_align = f_get_text_align(env, style, "textAlign");
+    let flex_direction = f_get_flex_direction(env, style, "flexDirection");
+    let flex_wrap = f_get_flex_wrap(env, style, "flexWrap");
+
+    let flex_basis = f_get_dimension_or(env, style, "flexBasis", || Dimension::Auto);
+    let flex_grow = f_f32_from_primitive(env, style, "flexGrow", || 0.0);
+    let flex_shrink = f_f32_from_primitive(env, style, "flexShrink", || 1.0);
+
+    let grid_template_rows = f_get_list(env, style, "gridTemplateRows", get_opt_track_sizing_function);
+    let grid_template_columns = f_get_list(env, style, "gridTemplateColumns", get_opt_track_sizing_function);
+    let grid_auto_rows = f_get_list(env, style, "gridAutoRows", get_opt_non_repeated_track_sizing_function);
+    let grid_auto_columns = f_get_list(env, style, "gridAutoColumns", get_opt_non_repeated_track_sizing_function);
+    let grid_auto_flow = f_get_grid_auto_flow(env, style, "gridAutoFlow");
+
+    let grid_row = f_get_line(env, style, "gridRow", get_grid_placement, || Line {
+        start: GridPlacement::Auto,
+        end: GridPlacement::Auto,
+    });
+
+    let grid_column = f_get_line(env, style, "gridColumn", get_grid_placement, || Line {
+        start: GridPlacement::Auto,
+        end: GridPlacement::Auto,
+    });
+
+    Style {
+        display,
+        item_is_table,
+        box_sizing,
+
+        overflow,
+        scrollbar_width,
+
+        position,
+        inset,
+
+        size,
+        min_size,
+        max_size,
+        aspect_ratio,
+
+        margin,
+        padding,
+        border,
+
+        align_items,
+        align_self,
+        justify_items,
+        justify_self,
+        align_content,
+        justify_content,
+        gap,
+
+        text_align,
+        flex_direction,
+        flex_wrap,
+
+        flex_basis,
+        flex_grow,
+        flex_shrink,
+
+        grid_template_rows,
+        grid_template_columns,
+        grid_auto_rows,
+        grid_auto_columns,
+        grid_auto_flow,
+
+        grid_row,
+        grid_column,
+    }
+}
+
+pub fn f_get_value<'local>(
+    env: &mut JNIEnv<'local>,
+    object: &JObject<'local>,
+    field: &str,
+    jtype: &str,
+) -> JValueOwned<'local> {
+    env.get_field(object, field, jtype).unwrap_or_else(|_| panic!("Couldn't get field {}, {}", field, jtype))
+}
diff --git a/bindings/java/src/enums.rs b/bindings/java/src/enums.rs
new file mode 100644
index 000000000..43c5e69fb
--- /dev/null
+++ b/bindings/java/src/enums.rs
@@ -0,0 +1,181 @@
+use crate::traits::FromJavaEnum;
+use taffy::AbsoluteAxis;
+use taffy::AlignContent;
+use taffy::AlignItems;
+use taffy::BoxGenerationMode;
+use taffy::BoxSizing;
+use taffy::Display;
+use taffy::FlexDirection;
+use taffy::FlexWrap;
+use taffy::GridAutoFlow;
+use taffy::Overflow;
+use taffy::Position;
+use taffy::TextAlign;
+/// FromJavaEnum implementations for Enums that need it
+
+impl FromJavaEnum<AbsoluteAxis> for AbsoluteAxis {
+    const JAVA_CLASS: &'static str = "Lcom/dioxuslabs/taffy/enums/AbsoluteAxis;";
+
+    fn from_ordinal(internal: i32) -> Option<AbsoluteAxis> {
+        Some(match internal {
+            0 => AbsoluteAxis::Horizontal,
+            1 => AbsoluteAxis::Vertical,
+            _ => panic!("Invalid value: {internal}"),
+        })
+    }
+}
+
+impl FromJavaEnum<AlignContent> for AlignContent {
+    const JAVA_CLASS: &'static str = "Lcom/dioxuslabs/taffy/enums/AlignContent;";
+
+    fn from_ordinal(internal: i32) -> Option<AlignContent> {
+        Some(match internal {
+            0 => AlignContent::Start,
+            1 => AlignContent::End,
+            2 => AlignContent::FlexStart,
+            3 => AlignContent::FlexEnd,
+            4 => AlignContent::Center,
+            5 => AlignContent::Stretch,
+            6 => AlignContent::SpaceBetween,
+            7 => AlignContent::SpaceEvenly,
+            8 => AlignContent::SpaceAround,
+            _ => panic!("Invalid value: {internal}"),
+        })
+    }
+}
+
+impl FromJavaEnum<AlignItems> for AlignItems {
+    const JAVA_CLASS: &'static str = "Lcom/dioxuslabs/taffy/enums/AlignItems;";
+
+    fn from_ordinal(internal: i32) -> Option<AlignItems> {
+        Some(match internal {
+            0 => AlignItems::Start,
+            1 => AlignItems::End,
+            2 => AlignItems::FlexStart,
+            3 => AlignItems::FlexEnd,
+            4 => AlignItems::Center,
+            5 => AlignItems::Baseline,
+            6 => AlignItems::Stretch,
+            _ => panic!("Invalid value: {internal}"),
+        })
+    }
+}
+
+impl FromJavaEnum<BoxGenerationMode> for BoxGenerationMode {
+    const JAVA_CLASS: &'static str = "Lcom/dioxuslabs/taffy/enums/BoxGenerationMode;";
+
+    fn from_ordinal(internal: i32) -> Option<BoxGenerationMode> {
+        Some(match internal {
+            0 => BoxGenerationMode::Normal,
+            1 => BoxGenerationMode::None,
+            _ => BoxGenerationMode::default(),
+        })
+    }
+}
+
+impl FromJavaEnum<BoxSizing> for BoxSizing {
+    const JAVA_CLASS: &'static str = "Lcom/dioxuslabs/taffy/enums/BoxSizing;";
+
+    fn from_ordinal(internal: i32) -> Option<BoxSizing> {
+        Some(match internal {
+            0 => BoxSizing::BorderBox,
+            1 => BoxSizing::ContentBox,
+            _ => BoxSizing::default(),
+        })
+    }
+}
+
+impl FromJavaEnum<Display> for Display {
+    const JAVA_CLASS: &'static str = "Lcom/dioxuslabs/taffy/enums/Display;";
+
+    fn from_ordinal(internal: i32) -> Option<Display> {
+        Some(match internal {
+            0 => Display::Block,
+            1 => Display::Flex,
+            2 => Display::Grid,
+            3 => Display::None,
+            _ => Display::default(),
+        })
+    }
+}
+
+impl FromJavaEnum<FlexDirection> for FlexDirection {
+    const JAVA_CLASS: &'static str = "Lcom/dioxuslabs/taffy/enums/FlexDirection;";
+
+    fn from_ordinal(internal: i32) -> Option<FlexDirection> {
+        Some(match internal {
+            0 => FlexDirection::Row,
+            1 => FlexDirection::Column,
+            2 => FlexDirection::RowReverse,
+            3 => FlexDirection::ColumnReverse,
+            _ => FlexDirection::default(),
+        })
+    }
+}
+
+impl FromJavaEnum<FlexWrap> for FlexWrap {
+    const JAVA_CLASS: &'static str = "Lcom/dioxuslabs/taffy/enums/FlexWrap;";
+
+    fn from_ordinal(internal: i32) -> Option<FlexWrap> {
+        Some(match internal {
+            0 => FlexWrap::NoWrap,
+            1 => FlexWrap::Wrap,
+            2 => FlexWrap::WrapReverse,
+            _ => FlexWrap::default(),
+        })
+    }
+}
+
+impl FromJavaEnum<GridAutoFlow> for GridAutoFlow {
+    const JAVA_CLASS: &'static str = "Lcom/dioxuslabs/taffy/enums/GridAutoFlow;";
+
+    fn from_ordinal(internal: i32) -> Option<GridAutoFlow> {
+        Some(match internal {
+            0 => GridAutoFlow::Row,
+            1 => GridAutoFlow::Column,
+            2 => GridAutoFlow::RowDense,
+            3 => GridAutoFlow::ColumnDense,
+            _ => GridAutoFlow::default(),
+        })
+    }
+}
+
+impl FromJavaEnum<Overflow> for Overflow {
+    const JAVA_CLASS: &'static str = "Lcom/dioxuslabs/taffy/enums/Overflow;";
+
+    fn from_ordinal(internal: i32) -> Option<Overflow> {
+        Some(match internal {
+            0 => Overflow::Visible,
+            1 => Overflow::Clip,
+            2 => Overflow::Hidden,
+            3 => Overflow::Scroll,
+            _ => Overflow::default(),
+        })
+    }
+}
+
+impl FromJavaEnum<Position> for Position {
+    const JAVA_CLASS: &'static str = "Lcom/dioxuslabs/taffy/enums/Position;";
+
+    fn from_ordinal(internal: i32) -> Option<Position> {
+        Some(match internal {
+            0 => Position::Relative,
+            1 => Position::Absolute,
+            _ => Position::default(),
+        })
+    }
+}
+
+impl FromJavaEnum<TextAlign> for TextAlign {
+    const JAVA_CLASS: &'static str = "Lcom/dioxuslabs/taffy/enums/TextAlign;";
+
+    fn from_ordinal(internal: i32) -> Option<TextAlign> {
+        Some(match internal {
+            0 => TextAlign::Auto,
+            1 => TextAlign::LegacyLeft,
+            2 => TextAlign::LegacyRight,
+            3 => TextAlign::LegacyCenter,
+            _ => TextAlign::default(),
+        })
+    }
+}
diff --git a/bindings/java/src/geom.rs b/bindings/java/src/geom.rs
new file mode 100644
index 000000000..8cbf4f9e4
--- /dev/null
+++ b/bindings/java/src/geom.rs
@@ -0,0 +1,204 @@
+use crate::conversions::f_get_value;
+use crate::measure::{get_max_track_sizing_function, get_min_track_sizing_function};
+use jni::objects::{JObject, JValueOwned};
+use jni::JNIEnv;
+use taffy::{Line, MaxTrackSizingFunction, MinTrackSizingFunction, NonRepeatedTrackSizingFunction, Point, Rect, Size};
+
+/// Get a Point from its Java counterpart, using a JValueOwned
+pub fn get_point<'local, T, F>(
+    env: &mut JNIEnv<'local>,
+    value: JValueOwned<'local>,
+    f: F,
+    def: fn() -> Point<T>,
+) -> Point<T>
+where
+    F: Fn(&mut JNIEnv<'local>, JValueOwned<'local>) -> T,
+{
+    let point_object = &value.l().unwrap();
+    if point_object.is_null() {
+        return def();
+    }
+
+    let x_field = f_get_value(env, point_object, "x", "Ljava/lang/Object;");
+    let y_field = f_get_value(env, point_object, "y", "Ljava/lang/Object;");
+
+    Point { x: f(env, x_field), y: f(env, y_field) }
+}
+
+/// Get a Rect from its Java counterpart, using a JValueOwned
+pub fn get_rect<'local, T, F>(
+    env: &mut JNIEnv<'local>,
+    value: JValueOwned<'local>,
+    f: F,
+    def: fn() -> Rect<T>,
+) -> Rect<T>
+where
+    F: Fn(&mut JNIEnv<'local>, JValueOwned<'local>) -> T,
+{
+    let rect_object = &value.l().unwrap();
+    if rect_object.is_null() {
+        return def();
+    }
+
+    let l_field = f_get_value(env, rect_object, "left", "Ljava/lang/Object;");
+    let r_field = f_get_value(env, rect_object, "right", "Ljava/lang/Object;");
+    let t_field = f_get_value(env, rect_object, "top", "Ljava/lang/Object;");
+    let b_field = f_get_value(env, rect_object, "bottom", "Ljava/lang/Object;");
+
+    Rect { left: f(env, l_field), right: f(env, r_field), top: f(env, t_field), bottom: f(env, b_field) }
+}
+
+/// Get a Size from its Java counterpart, using a JValueOwned
+pub fn get_size<'local, T, F>(
+    env: &mut JNIEnv<'local>,
+    value: JValueOwned<'local>,
+    f: F,
+    def: fn() -> Size<T>,
+) -> Size<T>
+where
+    F: Fn(&mut JNIEnv<'local>, JValueOwned<'local>) -> T,
+{
+    let size_object = &value.l().unwrap();
+    if size_object.is_null() {
+        return def();
+    }
+
+    let w_field = f_get_value(env, size_object, "width", "Ljava/lang/Object;");
+    let h_field = f_get_value(env, size_object, "height", "Ljava/lang/Object;");
+
+    Size { width: f(env, w_field), height: f(env, h_field) }
+}
+
+/// Get a Line from its Java counterpart, using a JValueOwned
+pub fn get_line<'local, T, F>(
+    env: &mut JNIEnv<'local>,
+    value: JValueOwned<'local>,
+    f: F,
+    def: fn() -> Line<T>,
+) -> Line<T>
+where
+    F: Fn(&mut JNIEnv<'local>, JValueOwned<'local>) -> T,
+{
+    let line_object = &value.l().unwrap();
+    if line_object.is_null() {
+        return def();
+    }
+
+    let s_field = f_get_value(env, line_object, "start", "Ljava/lang/Object;");
+    let e_field = f_get_value(env, line_object, "end", "Ljava/lang/Object;");
+
+    Line { start: f(env, s_field), end: f(env, e_field) }
+}
+
+/// Get a NonRepeatedTrackSizingFunction from its Java counterpart, using a JValueOwned
+pub fn get_non_repeated_track_sizing_function<'local>(
+    env: &mut JNIEnv<'local>,
+    value: JValueOwned<'local>,
+) -> NonRepeatedTrackSizingFunction {
+    let nrtsf_object = &value.l().unwrap();
+    if nrtsf_object.is_null() {
+        panic!("NonRepeatedTrackSizingFunction cannot be null");
+    }
+
+    let n_val = f_get_value(env, nrtsf_object, "min", "Lcom/dioxuslabs/taffy/geom/grid/MinTrackSizingFunction;");
+    let x_val = f_get_value(env, nrtsf_object, "max", "Lcom/dioxuslabs/taffy/geom/grid/MaxTrackSizingFunction;");
+
+    let min = get_min_track_sizing_function(env, n_val, || MinTrackSizingFunction::Auto);
+    let max = get_max_track_sizing_function(env, x_val, || MaxTrackSizingFunction::Auto);
+
+    NonRepeatedTrackSizingFunction { min, max }
+}
+
+/// Get an Option<NonRepeatedTrackSizingFunction> from its Java counterpart, using a JValueOwned
+pub fn get_opt_non_repeated_track_sizing_function<'local>(
+    env: &mut JNIEnv<'local>,
+    value: JValueOwned<'local>,
+) -> Option<NonRepeatedTrackSizingFunction> {
+    let nrtsf_object = &value.l().unwrap();
+    if nrtsf_object.is_null() {
+        return None;
+    }
+
+    let n_val = f_get_value(env, nrtsf_object, "min", "Lcom/dioxuslabs/taffy/geom/grid/MinTrackSizingFunction;");
+    let x_val = f_get_value(env, nrtsf_object, "max", "Lcom/dioxuslabs/taffy/geom/grid/MaxTrackSizingFunction;");
+
+    let min = get_min_track_sizing_function(env, n_val, || MinTrackSizingFunction::Auto);
+    let max = get_max_track_sizing_function(env, x_val, || MaxTrackSizingFunction::Auto);
+
+    Some(NonRepeatedTrackSizingFunction { min, max })
+}
+
+/// Get a Point from its Java counterpart, using a field name
+pub fn f_get_point<'local, T, F>(
+    env: &mut JNIEnv<'local>,
+    base: &JObject<'local>,
+    field: &str,
+    f: F,
+    def: fn() -> Point<T>,
+) -> Point<T>
+where
+    F: Fn(&mut JNIEnv<'local>, JValueOwned<'local>) -> T,
+{
+    let point_field = f_get_value(env, base, field, "Lcom/dioxuslabs/taffy/geom/Point;");
+
+    get_point(env, point_field, f, def)
+}
+
+/// Get a Rect from its Java counterpart, using a field name
+pub fn f_get_rect<'local, T, F>(
+    env: &mut JNIEnv<'local>,
+    base: &JObject<'local>,
+    field: &str,
+    f: F,
+    def: fn() -> Rect<T>,
+) -> Rect<T>
+where
+    F: Fn(&mut JNIEnv<'local>, JValueOwned<'local>) -> T,
+{
+    let point_field = f_get_value(env, base, field, "Lcom/dioxuslabs/taffy/geom/Rect;");
+
+    get_rect(env, point_field, f, def)
+}
+
+/// Get a Size from its Java counterpart, using a field name
+pub fn f_get_size<'local, T, F>(
+    env: &mut JNIEnv<'local>,
+    base: &JObject<'local>,
+    field: &str,
+    f: F,
+    def: fn() -> Size<T>,
+) -> Size<T>
+where
+    F: Fn(&mut JNIEnv<'local>, JValueOwned<'local>) -> T,
+{
+    let point_field = f_get_value(env, base, field, "Lcom/dioxuslabs/taffy/geom/Size;");
+
+    get_size(env, point_field, f, def)
+}
+
+/// Get a Line from its Java counterpart, using a field name
+pub fn f_get_line<'local, T, F>(
+    env: &mut JNIEnv<'local>,
+    base: &JObject<'local>,
+    field: &str,
+    f: F,
+    def: fn() -> Line<T>,
+) -> Line<T>
+where
+    F: Fn(&mut JNIEnv<'local>, JValueOwned<'local>) -> T,
+{
+    let point_field = f_get_value(env, base, field, "Lcom/dioxuslabs/taffy/geom/Line;");
+
+    get_line(env, point_field, f, def)
+}
+
+/// Get a NonRepeatedTrackSizingFunction from its Java counterpart, using a field name
+pub fn f_get_non_repeated_track_sizing_function<'local>(
+    env: &mut JNIEnv<'local>,
+    base: &JObject<'local>,
+    field: &str,
+) -> NonRepeatedTrackSizingFunction {
+    let nrtsft_field = f_get_value(env, base, field, "Lcom/dioxuslabs/taffy/geom/grid/NonRepeatedTrackSizingFunction;");
+
+    get_non_repeated_track_sizing_function(env, nrtsft_field)
+}
diff --git a/bindings/java/src/java.rs b/bindings/java/src/java.rs
new file mode 100644
index 000000000..376869ee0
--- /dev/null
+++ b/bindings/java/src/java.rs
@@ -0,0 +1,67 @@
+use jni::objects::{JObject, JValue};
+use jni::sys::jobject;
+use jni::JNIEnv;
+use taffy::{Layout, Point, Rect, Size};
+
+pub fn to_java_layout(env: &mut JNIEnv, layout: &Layout) -> jobject {
+    let layout_class = &env.find_class("com/dioxuslabs/taffy/tree/Layout").unwrap();
+
+    let location = &f32_point_to_java(env, layout.location);
+    let size = &f32_size_to_java(env, layout.size);
+    let content_size = &f32_size_to_java(env, layout.content_size);
+    let scrollbar_size = &f32_size_to_java(env, layout.scrollbar_size);
+    let border = &f32_rect_to_java(env, layout.border);
+    let padding = &f32_rect_to_java(env, layout.padding);
+    let margin = &f32_rect_to_java(env, layout.margin);
+
+    *env.new_object(layout_class, "(ILcom/dioxuslabs/taffy/geom/Point;Lcom/dioxuslabs/taffy/geom/Size;Lcom/dioxuslabs/taffy/geom/Size;Lcom/dioxuslabs/taffy/geom/Size;Lcom/dioxuslabs/taffy/geom/Rect;Lcom/dioxuslabs/taffy/geom/Rect;Lcom/dioxuslabs/taffy/geom/Rect;)V", &[
+        JValue::Int(layout.order as i32),
+        JValue::Object(location),
+        JValue::Object(size),
+        JValue::Object(content_size),
+        JValue::Object(scrollbar_size),
+        JValue::Object(border),
+        JValue::Object(padding),
+        JValue::Object(margin),
+    ]).unwrap()
+}
+
+pub fn f32_point_to_java<'local>(env: &mut JNIEnv<'local>, point: Point<f32>) -> JObject<'local> {
+    let class = &env.find_class("com/dioxuslabs/taffy/geom/Point").unwrap();
+
+    let a = &f32_to_java(env, point.x);
+    let b = &f32_to_java(env, point.y);
+
+    env.new_object(class, "(Ljava/lang/Object;Ljava/lang/Object;)V", &[JValue::Object(a), JValue::Object(b)]).unwrap()
+}
+
+pub fn f32_size_to_java<'local>(env: &mut JNIEnv<'local>, size: Size<f32>) -> JObject<'local> {
+    let class = &env.find_class("com/dioxuslabs/taffy/geom/Size").unwrap();
+
+    let a = &f32_to_java(env, size.width);
+    let b = &f32_to_java(env, size.height);
+
+    env.new_object(class, "(Ljava/lang/Object;Ljava/lang/Object;)V", &[JValue::Object(a), JValue::Object(b)]).unwrap()
+}
+
+pub fn f32_rect_to_java<'local>(env: &mut JNIEnv<'local>, rect: Rect<f32>) -> JObject<'local> {
+    let class = &env.find_class("com/dioxuslabs/taffy/geom/Rect").unwrap();
+
+    let a = &f32_to_java(env, rect.left);
+    let b = &f32_to_java(env, rect.right);
+    let c = &f32_to_java(env, rect.top);
+    let d = &f32_to_java(env, rect.bottom);
+
+    env.new_object(
+        class,
+        "(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)V",
+        &[JValue::Object(a), JValue::Object(b), JValue::Object(c), JValue::Object(d)],
+    )
+    .unwrap()
+}
+
+pub fn f32_to_java<'local>(env: &mut JNIEnv<'local>, value: f32) -> JObject<'local> {
+    let class = &env.find_class("java/lang/Float").unwrap();
+
+    env.new_object(class, "(F)V", &[JValue::Float(value)]).unwrap()
+}
diff --git a/bindings/java/src/lib.rs b/bindings/java/src/lib.rs
new file mode 100644
index 000000000..b8d1c2c0c
--- /dev/null
+++ b/bindings/java/src/lib.rs
@@ -0,0 +1,410 @@
+mod collections;
+mod conversions;
+mod enums;
+mod geom;
+mod java;
+mod measure;
+mod primitives;
+mod traits;
+
+use crate::collections::get_list;
+use crate::conversions::get_style;
+use crate::geom::get_size;
+use crate::java::to_java_layout;
+use crate::measure::get_available_space;
+use crate::primitives::{node_id_from_primitive, opt_node_id_from_object};
+use jni::objects::{JClass, JObject, JValueOwned};
+use jni::sys::{jint, jlong, jobject};
+use jni::JNIEnv;
+use std::panic;
+use taffy::{NodeId, TaffyTree, TraversePartialTree};
+
+#[no_mangle]
+pub extern "system" fn Java_com_dioxuslabs_taffy_TaffyTree_nvNewTaffyTree<'local>(
+    _env: JNIEnv<'local>,
+    _class: JClass<'local>,
+) -> jlong {
+    let tree: Box<TaffyTree<()>> = Box::new(TaffyTree::new());
+    Box::into_raw(tree) as jlong
+}
+
+#[no_mangle]
+pub extern "system" fn Java_com_dioxuslabs_taffy_TaffyTree_nvNewTaffyTreeWithCapacity<'local>(
+    _env: JNIEnv<'local>,
+    _class: JClass<'local>,
+    capacity: jint,
+) -> jlong {
+    let tree: Box<TaffyTree<()>> = Box::new(TaffyTree::with_capacity(capacity as usize));
+    Box::into_raw(tree) as jlong
+}
+
+#[no_mangle]
+pub extern "system" fn Java_com_dioxuslabs_taffy_TaffyTree_nvEnableRounding<'local>(
+    _env: JNIEnv<'local>,
+    _class: JClass<'local>,
+    pointer: jlong,
+) {
+    unsafe {
+        let raw_ptr: *mut TaffyTree<()> = pointer as *mut TaffyTree<()>;
+        if raw_ptr.is_null() {
+            return;
+        }
+        let tree: &mut TaffyTree<()> = &mut *raw_ptr;
+        tree.enable_rounding()
+    }
+}
+
+#[no_mangle]
+pub extern "system" fn Java_com_dioxuslabs_taffy_TaffyTree_nvDisableRounding<'local>(
+    _env: JNIEnv<'local>,
+    _class: JClass<'local>,
+    pointer: jlong,
+) {
+    unsafe {
+        let raw_ptr: *mut TaffyTree<()> = pointer as *mut TaffyTree<()>;
+        if raw_ptr.is_null() {
+            return;
+        }
+        let tree: &mut TaffyTree<()> = &mut *raw_ptr;
+        tree.disable_rounding()
+    }
+}
+
+#[no_mangle]
+#[allow(clippy::not_unsafe_ptr_arg_deref)]
+pub extern "system" fn Java_com_dioxuslabs_taffy_TaffyTree_nvNewLeaf<'local>(
+    mut env: JNIEnv<'local>,
+    _class: JClass<'local>,
+    pointer: jlong,
+    style: jobject,
+) -> jlong {
+    unsafe {
+        let style = get_style(&mut env, &JObject::from_raw(style));
+
+        let raw_ptr: *mut TaffyTree<()> = pointer as *mut TaffyTree<()>;
+        let tree: &mut TaffyTree<()> = &mut *raw_ptr;
+        let res = tree.new_leaf(style);
+        res.map_or(-1, |v| <NodeId as Into<u64>>::into(v) as jlong) as jlong
+    }
+}
+
+#[no_mangle]
+#[allow(clippy::not_unsafe_ptr_arg_deref)]
+pub extern "system" fn Java_com_dioxuslabs_taffy_TaffyTree_nvNewWithChildren<'local>(
+    mut env: JNIEnv<'local>,
+    _class: JClass<'local>,
+    pointer: jlong,
+    style: jobject,
+    children: jobject,
+) -> jlong {
+    unsafe {
+        let style = get_style(&mut env, &JObject::from_raw(style));
+        let list = JValueOwned::from(JObject::from_raw(children));
+        let children = &get_list(&mut env, list, opt_node_id_from_object);
+
+        let raw_ptr: *mut TaffyTree<()> = pointer as *mut TaffyTree<()>;
+        let tree: &mut TaffyTree<()> = &mut *raw_ptr;
+        let res = tree.new_with_children(style, children);
+        res.map_or(-1, |v| <NodeId as Into<u64>>::into(v) as jlong) as jlong
+    }
+}
+
+#[no_mangle]
+pub extern "system" fn Java_com_dioxuslabs_taffy_TaffyTree_nvChildCount<'local>(
+    mut env: JNIEnv<'local>,
+    _class: JClass<'local>,
+    pointer: jlong,
+    node: jlong,
+) -> jint {
+    let node = node_id_from_primitive(&mut env, JValueOwned::from(node));
+    let raw_ptr: *mut TaffyTree<()> = pointer as *mut TaffyTree<()>;
+    if raw_ptr.is_null() {
+        return -2;
+    }
+    let tree: &mut TaffyTree<()> = unsafe { &mut *raw_ptr };
+    tree.child_count(node) as jint
+}
+
+#[no_mangle]
+pub extern "system" fn Java_com_dioxuslabs_taffy_TaffyTree_nvClear<'local>(
+    _env: JNIEnv<'local>,
+    _class: JClass<'local>,
+    pointer: jlong,
+) {
+    unsafe {
+        let raw_ptr: *mut TaffyTree<()> = pointer as *mut TaffyTree<()>;
+        if raw_ptr.is_null() {
+            return;
+        }
+        let tree: &mut TaffyTree<()> = &mut *raw_ptr;
+        tree.clear()
+    }
+}
+
+#[no_mangle]
+pub extern "system" fn Java_com_dioxuslabs_taffy_TaffyTree_nvRemove<'local>(
+    mut env: JNIEnv<'local>,
+    _class: JClass<'local>,
+    pointer: jlong,
+    node: jlong,
+) -> jlong {
+    unsafe {
+        let raw_ptr: *mut TaffyTree<()> = pointer as *mut TaffyTree<()>;
+        let tree: &mut TaffyTree<()> = &mut *raw_ptr;
+
+        let node = node_id_from_primitive(&mut env, JValueOwned::from(node));
+        let res = tree.remove(node);
+
+        res.map_or(-1, |v| <NodeId as Into<u64>>::into(v) as jlong) as jlong
+    }
+}
+
+#[no_mangle]
+pub extern "system" fn Java_com_dioxuslabs_taffy_TaffyTree_nvAddChild<'local>(
+    mut env: JNIEnv<'local>,
+    _class: JClass<'local>,
+    pointer: jlong,
+    parent: jlong,
+    child: jlong,
+) {
+    unsafe {
+        let raw_ptr: *mut TaffyTree<()> = pointer as *mut TaffyTree<()>;
+        let tree: &mut TaffyTree<()> = &mut *raw_ptr;
+
+        let parent_node = node_id_from_primitive(&mut env, JValueOwned::from(parent));
+        let child_node = node_id_from_primitive(&mut env, JValueOwned::from(child));
+        tree.add_child(parent_node, child_node).expect("Failed to add child");
+    }
+}
+
+#[no_mangle]
+pub extern "system" fn Java_com_dioxuslabs_taffy_TaffyTree_nvInsertChildAtIndex<'local>(
+    mut env: JNIEnv<'local>,
+    _class: JClass<'local>,
+    pointer: jlong,
+    parent: jlong,
+    child_index: jint,
+    child: jlong,
+) {
+    unsafe {
+        let raw_ptr: *mut TaffyTree<()> = pointer as *mut TaffyTree<()>;
+        let tree: &mut TaffyTree<()> = &mut *raw_ptr;
+
+        let parent_node = node_id_from_primitive(&mut env, JValueOwned::from(parent));
+        let child_node = node_id_from_primitive(&mut env, JValueOwned::from(child));
+        tree.insert_child_at_index(parent_node, child_index as usize, child_node).expect("Failed to insert child");
+    }
+}
+
+#[no_mangle]
+#[allow(clippy::not_unsafe_ptr_arg_deref)]
+pub extern "system" fn Java_com_dioxuslabs_taffy_TaffyTree_nvSetChildren<'local>(
+    mut env: JNIEnv<'local>,
+    _class: JClass<'local>,
+    pointer: jlong,
+    parent: jlong,
+    children: jobject,
+) {
+    unsafe {
+        let raw_ptr: *mut TaffyTree<()> = pointer as *mut TaffyTree<()>;
+        let tree: &mut TaffyTree<()> = &mut *raw_ptr;
+
+        let parent_node = node_id_from_primitive(&mut env, JValueOwned::from(parent));
+
+        let list = JValueOwned::from(JObject::from_raw(children));
+        let children = &get_list(&mut env, list, opt_node_id_from_object);
+
+        tree.set_children(parent_node, children).expect("Failed to set children");
+    }
+}
+
+#[no_mangle]
+pub extern "system" fn Java_com_dioxuslabs_taffy_TaffyTree_nvRemoveChild<'local>(
+    mut env: JNIEnv<'local>,
+    _class: JClass<'local>,
+    pointer: jlong,
+    parent: jlong,
+    child: jlong,
+) -> jlong {
+    unsafe {
+        let raw_ptr: *mut TaffyTree<()> = pointer as *mut TaffyTree<()>;
+        let tree: &mut TaffyTree<()> = &mut *raw_ptr;
+
+        let parent_node = node_id_from_primitive(&mut env, JValueOwned::from(parent));
+        let child_node = node_id_from_primitive(&mut env, JValueOwned::from(child));
+
+        let res = tree.remove_child(parent_node, child_node);
+        res.map_or(-1, |v| <NodeId as Into<u64>>::into(v) as jlong) as jlong
+    }
+}
+
+#[no_mangle]
+pub extern "system" fn Java_com_dioxuslabs_taffy_TaffyTree_nvRemoveChildAtIndex<'local>(
+    mut env: JNIEnv<'local>,
+    _class: JClass<'local>,
+    pointer: jlong,
+    parent: jlong,
+    child_index: jint,
+) -> jlong {
+    unsafe {
+        let raw_ptr: *mut TaffyTree<()> = pointer as *mut TaffyTree<()>;
+        let tree: &mut TaffyTree<()> = &mut *raw_ptr;
+
+        let parent_node = node_id_from_primitive(&mut env, JValueOwned::from(parent));
+
+        let res = tree.remove_child_at_index(parent_node, child_index as usize);
+        res.map_or(-1, |v| <NodeId as Into<u64>>::into(v) as jlong) as jlong
+    }
+}
+
+#[no_mangle]
+pub extern "system" fn Java_com_dioxuslabs_taffy_TaffyTree_nvReplaceChildAtIndex<'local>(
+    mut env: JNIEnv<'local>,
+    _class: JClass<'local>,
+    pointer: jlong,
+    parent: jlong,
+    child_index: jint,
+    new_child: jlong,
+) -> jlong {
+    unsafe {
+        let raw_ptr: *mut TaffyTree<()> = pointer as *mut TaffyTree<()>;
+        let tree: &mut TaffyTree<()> = &mut *raw_ptr;
+
+        let parent_node = node_id_from_primitive(&mut env, JValueOwned::from(parent));
+        let new_child_node = node_id_from_primitive(&mut env, JValueOwned::from(new_child));
+
+        let res = tree.replace_child_at_index(parent_node, child_index as usize, new_child_node);
+        res.map_or(-1, |v| <NodeId as Into<u64>>::into(v) as jlong) as jlong
+    }
+}
+
+#[no_mangle]
+pub extern "system" fn Java_com_dioxuslabs_taffy_TaffyTree_nvChildAtIndex<'local>(
+    mut env: JNIEnv<'local>,
+    _class: JClass<'local>,
+    pointer: jlong,
+    parent: jlong,
+    child_index: jint,
+) -> jlong {
+    unsafe {
+        let raw_ptr: *mut TaffyTree<()> = pointer as *mut TaffyTree<()>;
+        let tree: &mut TaffyTree<()> = &mut *raw_ptr;
+
+        let parent_node = node_id_from_primitive(&mut env, JValueOwned::from(parent));
+
+        let res = tree.child_at_index(parent_node, child_index as usize);
+        res.map_or(-1, |v| <NodeId as Into<u64>>::into(v) as jlong) as jlong
+    }
+}
+
+#[no_mangle]
+pub extern "system" fn Java_com_dioxuslabs_taffy_TaffyTree_nvTotalNodeCount<'local>(
+    _env: JNIEnv<'local>,
+    _class: JClass<'local>,
+    pointer: jlong,
+) -> jint {
+    unsafe {
+        let raw_ptr: *mut TaffyTree<()> = pointer as *mut TaffyTree<()>;
+        let tree: &mut TaffyTree<()> = &mut *raw_ptr;
+
+        tree.total_node_count() as jint
+    }
+}
+
+#[no_mangle]
+pub extern "system" fn Java_com_dioxuslabs_taffy_TaffyTree_nvParent<'local>(
+    mut env: JNIEnv<'local>,
+    _class: JClass<'local>,
+    pointer: jlong,
+    child_id: jlong,
+) -> jlong {
+    unsafe {
+        let raw_ptr: *mut TaffyTree<()> = pointer as *mut TaffyTree<()>;
+        let tree: &mut TaffyTree<()> = &mut *raw_ptr;
+
+        let child_node = node_id_from_primitive(&mut env, JValueOwned::from(child_id));
+
+        let res = tree.parent(child_node);
+        res.map_or(-1, |v| <NodeId as Into<u64>>::into(v) as jlong) as jlong
+    }
+}
+
+#[no_mangle]
+#[allow(clippy::not_unsafe_ptr_arg_deref)]
+pub extern "system" fn Java_com_dioxuslabs_taffy_TaffyTree_nvSetStyle<'local>(
+    mut env: JNIEnv<'local>,
+    _class: JClass<'local>,
+    pointer: jlong,
+    node: jlong,
+    style: jobject,
+) {
+    unsafe {
+        let raw_ptr: *mut TaffyTree<()> = pointer as *mut TaffyTree<()>;
+        let tree: &mut TaffyTree<()> = &mut *raw_ptr;
+
+        let node = node_id_from_primitive(&mut env, JValueOwned::from(node));
+
+        let style = get_style(&mut env, &JObject::from_raw(style));
+
+        tree.set_style(node, style).expect("Failed to set children");
+    }
+}
+
+#[no_mangle]
+#[allow(clippy::not_unsafe_ptr_arg_deref)]
+pub extern "system" fn Java_com_dioxuslabs_taffy_TaffyTree_nvComputeLayout<'local>(
+    mut env: JNIEnv<'local>,
+    _class: JClass<'local>,
+    pointer: jlong,
+    node: jlong,
+    available_size: jobject,
+) {
+    unsafe {
+        let node = node_id_from_primitive(&mut env, JValueOwned::from(node));
+        let as_object = JValueOwned::from(JObject::from_raw(available_size));
+        let available_size =
+            get_size(&mut env, as_object, get_available_space, || panic!("AvailableSize cannot be null"));
+
+        let raw_ptr: *mut TaffyTree<()> = pointer as *mut TaffyTree<()>;
+        if raw_ptr.is_null() {
+            return;
+        }
+        let tree: &mut TaffyTree<()> = &mut *raw_ptr;
+        tree.compute_layout(node, available_size).expect("Failed to compute layout");
+    }
+}
+
+#[no_mangle]
+pub extern "system" fn Java_com_dioxuslabs_taffy_TaffyTree_nvPrintTree<'local>(
+    mut env: JNIEnv<'local>,
+    _class: JClass<'local>,
+    pointer: jlong,
+    node: jlong,
+) {
+    unsafe {
+        let raw_ptr: *mut TaffyTree<()> = pointer as *mut TaffyTree<()>;
+        if raw_ptr.is_null() {
+            return;
+        }
+        let tree: &mut TaffyTree<()> = &mut *raw_ptr;
+        let node = node_id_from_primitive(&mut env, JValueOwned::from(node));
+        tree.print_tree(node)
+    }
+}
+
+#[no_mangle]
+pub extern "system" fn Java_com_dioxuslabs_taffy_TaffyTree_nvLayout<'local>(
+    mut env: JNIEnv<'local>,
+    _class: JClass<'local>,
+    pointer: jlong,
+    node: jlong,
+) -> jobject {
+    let node = node_id_from_primitive(&mut env, JValueOwned::from(node));
+    let raw_ptr: *mut TaffyTree<()> = pointer as *mut TaffyTree<()>;
+    if raw_ptr.is_null() {
+        panic!("Pointer cannot be null");
+    }
+    let tree: &mut TaffyTree<()> = unsafe { &mut *raw_ptr };
+    let res = tree.layout(node);
+    to_java_layout(&mut env, res.unwrap())
+}
diff --git a/bindings/java/src/measure.rs b/bindings/java/src/measure.rs
new file mode 100644
index 000000000..5fb4217d0
--- /dev/null
+++ b/bindings/java/src/measure.rs
@@ -0,0 +1,231 @@
+use crate::collections::f_get_list;
+use crate::conversions::f_get_value;
+use crate::geom::{f_get_non_repeated_track_sizing_function, get_opt_non_repeated_track_sizing_function};
+use crate::primitives::{f_f32_from_primitive, f_i16_from_primitive, f_i8_from_primitive};
+use jni::objects::{JObject, JValueOwned};
+use jni::JNIEnv;
+use taffy::style_helpers::{TaffyGridLine, TaffyGridSpan};
+use taffy::{
+    AvailableSpace, Dimension, GridPlacement, GridTrackRepetition, LengthPercentage, LengthPercentageAuto,
+    MaxTrackSizingFunction, MinTrackSizingFunction, TrackSizingFunction,
+};
+
+pub fn get_length_percentage<'local>(env: &mut JNIEnv<'local>, value: JValueOwned<'local>) -> LengthPercentage {
+    let obj = &value.l().unwrap();
+
+    if obj.is_null() {
+        panic!("LengthPercentage cannot be null");
+    }
+
+    let internal = f_i8_from_primitive(env, obj, "type", || 0);
+
+    match internal {
+        0 => LengthPercentage::Length(f_f32_from_primitive(env, obj, "value", || 0.0)),
+        _ => LengthPercentage::Percent(f_f32_from_primitive(env, obj, "value", || 0.0)),
+    }
+}
+
+pub fn get_length_percentage_auto<'local>(
+    env: &mut JNIEnv<'local>,
+    value: JValueOwned<'local>,
+) -> LengthPercentageAuto {
+    let obj = &value.l().unwrap();
+
+    if obj.is_null() {
+        panic!("LengthPercentageAuto cannot be null");
+    }
+
+    let internal = f_i8_from_primitive(env, obj, "type", || 0);
+
+    match internal {
+        0 => LengthPercentageAuto::Length(f_f32_from_primitive(env, obj, "value", || 0.0)),
+        1 => LengthPercentageAuto::Percent(f_f32_from_primitive(env, obj, "value", || 0.0)),
+        _ => LengthPercentageAuto::Auto,
+    }
+}
+
+pub fn get_dimension<'local>(env: &mut JNIEnv<'local>, value: JValueOwned<'local>) -> Dimension {
+    let obj = &value.l().unwrap();
+
+    if obj.is_null() {
+        panic!("Dimension cannot be null");
+    }
+
+    let internal = f_i8_from_primitive(env, obj, "type", || 0);
+
+    match internal {
+        0 => Dimension::Length(f_f32_from_primitive(env, obj, "value", || 0.0)),
+        1 => Dimension::Percent(f_f32_from_primitive(env, obj, "value", || 0.0)),
+        _ => Dimension::Auto,
+    }
+}
+
+pub fn get_available_space<'local>(env: &mut JNIEnv<'local>, value: JValueOwned<'local>) -> AvailableSpace {
+    let obj = &value.l().unwrap();
+
+    if obj.is_null() {
+        panic!("AvailableSpace cannot be null");
+    }
+
+    let internal = f_i8_from_primitive(env, obj, "type", || 0);
+
+    match internal {
+        0 => AvailableSpace::Definite(f_f32_from_primitive(env, obj, "value", || 0.0)),
+        1 => AvailableSpace::MinContent,
+        _ => AvailableSpace::MaxContent,
+    }
+}
+
+pub fn get_dimension_or<'local>(
+    env: &mut JNIEnv<'local>,
+    value: JValueOwned<'local>,
+    f: fn() -> Dimension,
+) -> Dimension {
+    let obj = &value.l().unwrap();
+
+    if obj.is_null() {
+        return f();
+    }
+
+    let internal = f_i8_from_primitive(env, obj, "type", || 0);
+
+    match internal {
+        0 => Dimension::Length(f_f32_from_primitive(env, obj, "value", || 0.0)),
+        1 => Dimension::Percent(f_f32_from_primitive(env, obj, "value", || 0.0)),
+        _ => Dimension::Auto,
+    }
+}
+
+pub fn get_grid_track_repetition<'local>(env: &mut JNIEnv<'local>, value: JValueOwned<'local>) -> GridTrackRepetition {
+    let obj = &value.l().unwrap();
+
+    if obj.is_null() {
+        panic!("GridTrackRepetition cannot be null");
+    }
+
+    let internal = f_i8_from_primitive(env, obj, "type", || 0);
+
+    match internal {
+        0 => GridTrackRepetition::AutoFill,
+        1 => GridTrackRepetition::AutoFit,
+        _ => GridTrackRepetition::Count(f_i16_from_primitive(env, obj, "value", || 0) as u16),
+    }
+}
+
+pub fn get_grid_placement<'local>(env: &mut JNIEnv<'local>, value: JValueOwned<'local>) -> GridPlacement {
+    let obj = &value.l().unwrap();
+
+    if obj.is_null() {
+        panic!("GridPlacement cannot be null");
+    }
+
+    let internal = f_i8_from_primitive(env, obj, "type", || 0);
+
+    match internal {
+        0 => GridPlacement::Auto,
+        1 => GridPlacement::from_line_index(f_i16_from_primitive(env, obj, "value", || 0)),
+        _ => GridPlacement::from_span(f_i16_from_primitive(env, obj, "value", || 0) as u16),
+    }
+}
+
+pub fn get_min_track_sizing_function<'local>(
+    env: &mut JNIEnv<'local>,
+    value: JValueOwned<'local>,
+    def: fn() -> MinTrackSizingFunction,
+) -> MinTrackSizingFunction {
+    let obj = &value.l().unwrap();
+
+    if obj.is_null() {
+        return def();
+    }
+
+    let internal = f_i8_from_primitive(env, obj, "type", || 0);
+
+    match internal {
+        0 => MinTrackSizingFunction::Fixed(f_get_length_percentage(env, obj, "value")),
+        1 => MinTrackSizingFunction::MinContent,
+        2 => MinTrackSizingFunction::MaxContent,
+        _ => MinTrackSizingFunction::Auto,
+    }
+}
+
+pub fn get_max_track_sizing_function<'local>(
+    env: &mut JNIEnv<'local>,
+    value: JValueOwned<'local>,
+    def: fn() -> MaxTrackSizingFunction,
+) -> MaxTrackSizingFunction {
+    let obj = &value.l().unwrap();
+
+    if obj.is_null() {
+        return def();
+    }
+
+    let internal = f_i8_from_primitive(env, obj, "type", || 0);
+
+    match internal {
+        0 => MaxTrackSizingFunction::Fixed(f_get_length_percentage(env, obj, "value")),
+        1 => MaxTrackSizingFunction::MinContent,
+        2 => MaxTrackSizingFunction::MaxContent,
+        3 => MaxTrackSizingFunction::FitContent(f_get_length_percentage(env, obj, "value")),
+        4 => MaxTrackSizingFunction::Auto,
+        _ => MaxTrackSizingFunction::Fraction(f_f32_from_primitive(env, obj, "value", || 0.0)),
+    }
+}
+
+pub fn get_opt_track_sizing_function<'local>(
+    env: &mut JNIEnv<'local>,
+    value: JValueOwned<'local>,
+) -> Option<TrackSizingFunction> {
+    let obj = &value.l().unwrap();
+
+    if obj.is_null() {
+        return None;
+    }
+
+    let internal = f_i8_from_primitive(env, obj, "type", || 0);
+
+    Some(match internal {
+        0 => TrackSizingFunction::Single(f_get_non_repeated_track_sizing_function(env, obj, "func")),
+        _ => TrackSizingFunction::Repeat(f_get_grid_track_repetition(env, obj, "repetitions"), unsafe {
+            f_get_list(env, obj, "functions", get_opt_non_repeated_track_sizing_function)
+        }),
+    })
+}
+
+pub fn f_get_length_percentage<'local>(
+    env: &mut JNIEnv<'local>,
+    base: &JObject<'local>,
+    field: &str,
+) -> LengthPercentage {
+    let value = f_get_value(env, base, field, "Lcom/dioxuslabs/taffy/geom/measure/LengthPercentage;");
+
+    get_length_percentage(env, value)
+}
+
+#[allow(dead_code)]
+pub fn f_get_dimension<'local>(env: &mut JNIEnv<'local>, base: &JObject<'local>, field: &str) -> Dimension {
+    let value = f_get_value(env, base, field, "Lcom/dioxuslabs/taffy/geom/measure/Dimension;");
+
+    get_dimension(env, value)
+}
+
+pub fn f_get_dimension_or<'local>(
+    env: &mut JNIEnv<'local>,
+    base: &JObject<'local>,
+    field: &str,
+    f: fn() -> Dimension,
+) -> Dimension {
+    let value = f_get_value(env, base, field, "Lcom/dioxuslabs/taffy/geom/measure/Dimension;");
+
+    get_dimension_or(env, value, f)
+}
+
+pub fn f_get_grid_track_repetition<'local>(
+    env: &mut JNIEnv<'local>,
+    base: &JObject<'local>,
+    field: &str,
+) -> GridTrackRepetition {
+    let value = f_get_value(env, base, field, "Lcom/dioxuslabs/taffy/geom/grid/GridTrackRepetition;");
+
+    get_grid_track_repetition(env, value)
+}
diff --git a/bindings/java/src/primitives.rs b/bindings/java/src/primitives.rs
new file mode 100644
index 000000000..926a325bd
--- /dev/null
+++ b/bindings/java/src/primitives.rs
@@ -0,0 +1,286 @@
+use crate::conversions::f_get_value;
+use crate::traits::FromJavaEnum;
+use jni::objects::{JObject, JValueOwned};
+use jni::JNIEnv;
+use taffy::NodeId;
+
+/// Convert Java float to f32
+pub fn f32_from_primitive<'local>(_env: &mut JNIEnv<'local>, value: JValueOwned<'local>, def: fn() -> f32) -> f32 {
+    value.f().unwrap_or(def())
+}
+
+/// Convert Java Float object to f32
+#[allow(dead_code)]
+pub fn f32_from_object<'local>(env: &mut JNIEnv<'local>, value: JValueOwned<'local>, def: fn() -> f32) -> f32 {
+    let object = value.l().unwrap();
+
+    if object.is_null() {
+        return def();
+    }
+
+    env.call_method(object, "floatValue", "()F", &[]).unwrap().f().unwrap()
+}
+
+/// Convert Java Float object to Option<f32>
+pub fn opt_f32_from_object<'local>(
+    env: &mut JNIEnv<'local>,
+    value: JValueOwned<'local>,
+    def: fn() -> Option<f32>,
+) -> Option<f32> {
+    let object = value.l().unwrap();
+
+    if object.is_null() {
+        return def();
+    }
+
+    Some(env.call_method(object, "floatValue", "()F", &[]).unwrap().f().unwrap())
+}
+
+/// Convert Java byte to i8
+pub fn i8_from_primitive<'local>(_env: &mut JNIEnv<'local>, value: JValueOwned<'local>, def: fn() -> i8) -> i8 {
+    value.b().unwrap_or(def())
+}
+
+/// Convert Java Byte object to i8
+#[allow(dead_code)]
+pub fn i8_from_object<'local>(env: &mut JNIEnv<'local>, value: JValueOwned<'local>, def: fn() -> i8) -> i8 {
+    let object = value.l().unwrap();
+
+    if object.is_null() {
+        return def();
+    }
+
+    env.call_method(object, "byteValue", "()B", &[]).unwrap().b().unwrap()
+}
+
+/// Convert Java short to i16
+pub fn i16_from_primitive<'local>(_env: &mut JNIEnv<'local>, value: JValueOwned<'local>, def: fn() -> i16) -> i16 {
+    value.s().unwrap_or(def())
+}
+
+/// Convert Java Short object to i16
+#[allow(dead_code)]
+pub fn i16_from_object<'local>(env: &mut JNIEnv<'local>, value: JValueOwned<'local>, def: fn() -> i16) -> i16 {
+    let object = value.l().unwrap();
+
+    if object.is_null() {
+        return def();
+    }
+
+    env.call_method(object, "shortValue", "()S", &[]).unwrap().s().unwrap()
+}
+
+/// Convert Java int to i32
+pub fn i32_from_primitive<'local>(_env: &mut JNIEnv<'local>, value: JValueOwned<'local>, def: fn() -> i32) -> i32 {
+    value.i().unwrap_or(def())
+}
+
+/// Convert Java Integer object to i32
+#[allow(dead_code)]
+pub fn i32_from_object<'local>(env: &mut JNIEnv<'local>, value: JValueOwned<'local>, def: fn() -> i32) -> i32 {
+    let object = value.l().unwrap();
+
+    if object.is_null() {
+        return def();
+    }
+
+    env.call_method(object, "intValue", "()I", &[]).unwrap().i().unwrap()
+}
+
+/// Convert Java long to i64
+#[allow(dead_code)]
+pub fn i64_from_primitive<'local>(_env: &mut JNIEnv<'local>, value: JValueOwned<'local>, def: fn() -> i64) -> i64 {
+    value.j().unwrap_or(def())
+}
+
+/// Convert Java Long object to i64
+#[allow(dead_code)]
+pub fn i64_from_object<'local>(env: &mut JNIEnv<'local>, value: JValueOwned<'local>, def: fn() -> i64) -> i64 {
+    let object = value.l().unwrap();
+
+    if object.is_null() {
+        return def();
+    }
+
+    env.call_method(object, "longValue", "()J", &[]).unwrap().j().unwrap()
+}
+
+/// Convert Java Long object to Option<i64>
+pub fn opt_i64_from_object<'local>(env: &mut JNIEnv<'local>, value: JValueOwned<'local>) -> Option<i64> {
+    let object = value.l().unwrap();
+
+    if object.is_null() {
+        return None;
+    }
+
+    Some(env.call_method(object, "longValue", "()J", &[]).unwrap().j().unwrap())
+}
+
+/// Convert Java boolean to bool
+pub fn bool_from_primitive<'local>(_env: &mut JNIEnv<'local>, value: JValueOwned<'local>, def: fn() -> bool) -> bool {
+    value.z().unwrap_or(def())
+}
+
+/// Convert Java Boolean object to bool
+#[allow(dead_code)]
+pub fn bool_from_object<'local>(env: &mut JNIEnv<'local>, value: JValueOwned<'local>, def: fn() -> bool) -> bool {
+    let object = value.l().unwrap();
+
+    if object.is_null() {
+        return def();
+    }
+
+    env.call_method(object, "booleanValue", "()Z", &[]).unwrap().z().unwrap()
+}
+
+/// Convert Java Boolean object to Option<bool>
+#[allow(dead_code)]
+pub fn opt_bool_from_object<'local>(
+    env: &mut JNIEnv<'local>,
+    value: JValueOwned<'local>,
+    def: fn() -> Option<bool>,
+) -> Option<bool> {
+    let object = value.l().unwrap();
+
+    if object.is_null() {
+        return def();
+    }
+
+    Some(env.call_method(object, "booleanValue", "()Z", &[]).unwrap().z().unwrap())
+}
+
+/// Convert Java long to NodeId
+#[allow(dead_code)]
+pub fn node_id_from_primitive<'local>(env: &mut JNIEnv<'local>, value: JValueOwned<'local>) -> NodeId {
+    NodeId::from(i64_from_primitive(env, value, || 0) as u64)
+}
+
+/// Convert Java Long to NodeId
+#[allow(dead_code)]
+pub fn node_id_from_object<'local>(env: &mut JNIEnv<'local>, value: JValueOwned<'local>) -> NodeId {
+    NodeId::from(i64_from_object(env, value, || 0) as u64)
+}
+
+/// Convert Java Long to Option<NodeId>
+pub fn opt_node_id_from_object<'local>(env: &mut JNIEnv<'local>, value: JValueOwned<'local>) -> Option<NodeId> {
+    let o = opt_i64_from_object(env, value);
+    if o.is_none() {
+        None
+    } else {
+        Some(NodeId::from(o? as u64))
+    }
+}
+
+/// Convert Java float to f32, using a field name
+pub fn f_f32_from_primitive<'local>(
+    env: &mut JNIEnv<'local>,
+    base: &JObject<'local>,
+    field: &str,
+    def: fn() -> f32,
+) -> f32 {
+    let value = f_get_value(env, base, field, "F");
+
+    f32_from_primitive(env, value, def)
+}
+
+/// Convert Java float to f32, using a field name
+pub fn f_opt_f32_from_object<'local>(
+    env: &mut JNIEnv<'local>,
+    base: &JObject<'local>,
+    field: &str,
+    def: fn() -> Option<f32>,
+) -> Option<f32> {
+    let value = f_get_value(env, base, field, "Ljava/lang/Float;");
+
+    opt_f32_from_object(env, value, def)
+}
+
+/// Convert Java byte to i8, using a field name
+pub fn f_i8_from_primitive<'local>(
+    env: &mut JNIEnv<'local>,
+    base: &JObject<'local>,
+    field: &str,
+    def: fn() -> i8,
+) -> i8 {
+    let value = f_get_value(env, base, field, "B");
+
+    i8_from_primitive(env, value, def)
+}
+
+/// Convert Java short to i16, using a field name
+pub fn f_i16_from_primitive<'local>(
+    env: &mut JNIEnv<'local>,
+    base: &JObject<'local>,
+    field: &str,
+    def: fn() -> i16,
+) -> i16 {
+    let value = f_get_value(env, base, field, "B");
+
+    i16_from_primitive(env, value, def)
+}
+
+/// Convert Java int to i32, using a field name
+pub fn f_i32_from_primitive<'local>(
+    env: &mut JNIEnv<'local>,
+    base: &JObject<'local>,
+    field: &str,
+    def: fn() -> i32,
+) -> i32 {
+    let value = f_get_value(env, base, field, "I");
+
+    i32_from_primitive(env, value, def)
+}
+
+/// Convert Java long to i64, using a field name
+#[allow(dead_code)]
+pub fn f_i64_from_primitive<'local>(
+    env: &mut JNIEnv<'local>,
+    base: &JObject<'local>,
+    field: &str,
+    def: fn() -> i64,
+) -> i64 {
+    let value = f_get_value(env, base, field, "J");
+
+    i64_from_primitive(env, value, def)
+}
+
+/// Convert Java boolean to bool, using a field name
+pub fn f_bool_from_primitive<'local>(
+    env: &mut JNIEnv<'local>,
+    base: &JObject<'local>,
+    field: &str,
+    def: fn() -> bool,
+) -> bool {
+    let value = f_get_value(env, base, field, "Z");
+
+    bool_from_primitive(env, value, def)
+}
+
+/// Enums are here as these are basically represented by integers which are primitives
+
+/// Get enum value T from a JValueOwned (as well as a JNIEnv which is required by JNI)
+pub fn get_enum<T: FromJavaEnum + Default>(env: &mut JNIEnv, value: JValueOwned) -> T {
+    let obj = &value.l().unwrap();
+    if obj.is_null() {
+        return T::default();
+    }
+
+    let internal = get_enum_value(env, obj);
+    T::from_ordinal(internal)
+}
+
+/// Get enum value T from a JObject and a field name (as well as a JNIEnv which is required by JNI)
+pub fn f_get_enum<'local, T: FromJavaEnum + Default>(
+    env: &mut JNIEnv<'local>,
+    base: &JObject<'local>,
+    field: &str,
+) -> T {
+    let obj = f_get_value(env, base, field, T::JAVA_CLASS);
+
+    get_enum(env, obj)
+}
+
+/// Internal method that gets the ordinal int
+fn get_enum_value<'local>(env: &mut JNIEnv<'local>, object: &JObject<'local>) -> i32 {
+    f_i32_from_primitive(env, object, "ordinal", || 0)
+}
diff --git a/bindings/java/src/traits.rs b/bindings/java/src/traits.rs
new file mode 100644
index 000000000..e94ca6568
--- /dev/null
+++ b/bindings/java/src/traits.rs
@@ -0,0 +1,4 @@
+pub trait FromJavaEnum<RustType> {
+    const JAVA_CLASS: &'static str;
+    fn from_ordinal(ordinal: i32) -> Option<RustType>;
+}
diff --git a/scripts/genenums/Cargo.toml b/scripts/genenums/Cargo.toml
new file mode 100644
index 000000000..85f2e4fe8
--- /dev/null
+++ b/scripts/genenums/Cargo.toml
@@ -0,0 +1,13 @@
+[package]
+name = "genenums"
+version = "0.0.1"
+authors = [
+    "Arby Djabaev <contact@arby.be>"
+]
+edition = "2021"
+description = "Scripts to generate bindings related stuff for Taffy (A flexible UI layout library)"
+repository = "https://github.com/DioxusLabs/taffy"
+license = "MIT"
+
+[dependencies]
+convert_case = "0.6.0"
diff --git a/scripts/genenums/src/generators/java.rs b/scripts/genenums/src/generators/java.rs
new file mode 100644
index 000000000..bae302048
--- /dev/null
+++ b/scripts/genenums/src/generators/java.rs
@@ -0,0 +1,60 @@
+use std::fs;
+use std::fs::File;
+use std::io::Write;
+
+pub(crate) fn create_java_enum(name: &str, values: &[&str]) {
+    use convert_case::{Case, Casing};
+
+    let package = "com.dioxuslabs.taffy.enums";
+    let enum_name = name.to_case(Case::Pascal);
+
+    let auto_gen_comment = "/*
+**********************************************************
+**            AUTOGENERATED CLASSES FOR TAFFY           **
+**  This code was automatically generated. Do not edit. **
+**********************************************************
+*/
+";
+
+    let mut result = format!(
+        r"{}package {};
+
+public enum {} {{
+",
+        auto_gen_comment, package, enum_name
+    );
+
+    // Todo: Consider using https://doc.rust-lang.org/stable/std/iter/trait.Iterator.html#method.intersperse (ONCE STABLE) to insert the commas to avoid needing this special case.
+    for value in values.iter() {
+        result.push_str("    ");
+        result.push_str(&value.to_case(Case::UpperSnake));
+        result.push_str(",\n");
+    }
+
+    // eliminate the last comma
+    if !values.is_empty() {
+        result.pop();
+        result.pop();
+        result.push('\n');
+    }
+
+    result.push_str("    ;\n");
+    result.push('\n');
+
+    result.push_str("    private final int ordinal;\n");
+
+    result.push('\n');
+    result.push_str("    ");
+    result.push_str(enum_name.as_str());
+    result.push_str("() {\n");
+    result.push_str("        this.ordinal = ordinal();\n");
+    result.push_str("    }\n");
+
+    result.push_str("}\n");
+
+    fs::create_dir_all("./bindings/java/java/src/main/java/com/dioxuslabs/taffy/enums/")
+        .expect("Couldn't create directories");
+    let file =
+        File::create(format!("./bindings/java/java/src/main/java/com/dioxuslabs/taffy/enums/{}.java", enum_name));
+    file.expect("Error: File not found").write_all(result.as_ref()).expect("Error: Couldn't write to file");
+}
diff --git a/scripts/genenums/src/generators/mod.rs b/scripts/genenums/src/generators/mod.rs
new file mode 100644
index 000000000..8a6ae718a
--- /dev/null
+++ b/scripts/genenums/src/generators/mod.rs
@@ -0,0 +1 @@
+pub(crate) mod java;
diff --git a/scripts/genenums/src/main.rs b/scripts/genenums/src/main.rs
new file mode 100644
index 000000000..f01febf0e
--- /dev/null
+++ b/scripts/genenums/src/main.rs
@@ -0,0 +1,73 @@
+use crate::generators::java::create_java_enum;
+use crate::transformers::java::create_java_tranformer;
+use std::fs;
+
+mod generators;
+mod transformers;
+
+struct RustEnum<'local> {
+    name: &'local str,
+    values: Vec<&'local str>,
+    default: bool,
+}
+
+fn main() {
+    fs::remove_file("./bindings/java/src/enums.rs").expect("Error: Unable to remove java/src/enums.rs file");
+
+    let mut enums: Vec<RustEnum> = vec![
+        RustEnum { name: "Display", values: vec!["Block", "Flex", "Grid", "None"], default: true },
+        RustEnum { name: "BoxGenerationMode", values: vec!["Normal", "None"], default: true },
+        RustEnum { name: "Position", values: vec!["Relative", "Absolute"], default: true },
+        RustEnum { name: "BoxSizing", values: vec!["BorderBox", "ContentBox"], default: true },
+        RustEnum { name: "Overflow", values: vec!["Visible", "Clip", "Hidden", "Scroll"], default: true },
+        RustEnum {
+            name: "TextAlign",
+            values: vec!["Auto", "LegacyLeft", "LegacyRight", "LegacyCenter"],
+            default: true,
+        },
+        RustEnum { name: "GridAutoFlow", values: vec!["Row", "Column", "RowDense", "ColumnDense"], default: true },
+        RustEnum { name: "FlexWrap", values: vec!["NoWrap", "Wrap", "WrapReverse"], default: true },
+        RustEnum { name: "FlexDirection", values: vec!["Row", "Column", "RowReverse", "ColumnReverse"], default: true },
+        RustEnum {
+            name: "AlignContent",
+            values: vec![
+                "Start",
+                "End",
+                "FlexStart",
+                "FlexEnd",
+                "Center",
+                "Stretch",
+                "SpaceBetween",
+                "SpaceEvenly",
+                "SpaceAround",
+            ],
+            default: false,
+        },
+        RustEnum {
+            name: "AlignItems",
+            values: vec!["Start", "End", "FlexStart", "FlexEnd", "Center", "Baseline", "Stretch"],
+            default: false,
+        },
+        RustEnum { name: "AbsoluteAxis", values: vec!["Horizontal", "Vertical"], default: false },
+    ];
+
+    // Sort to ensure consistency
+    enums.sort_by(|a, b| a.name.cmp(b.name));
+
+    for value in enums.into_iter() {
+        create_enum(value.name, &value.values);
+        create_transformer(value.name, &value.values, value.default);
+    }
+}
+
+/// Enum generators
+
+fn create_enum(name: &str, values: &[&str]) {
+    create_java_enum(name, values);
+}
+
+/// Transformer generators
+
+fn create_transformer(name: &str, values: &[&str], default: bool) {
+    create_java_tranformer(name, values, default);
+}
diff --git a/scripts/genenums/src/transformers/java.rs b/scripts/genenums/src/transformers/java.rs
new file mode 100644
index 000000000..b975fb37a
--- /dev/null
+++ b/scripts/genenums/src/transformers/java.rs
@@ -0,0 +1,48 @@
+use std::fs;
+use std::fs::File;
+use std::io::Write;
+use std::path::Path;
+
+pub(crate) fn create_java_tranformer(name: &str, values: &[&str], default: bool) {
+    let mut file_content: String =
+        "use crate::traits::FromJavaEnum;\n/// FromJavaEnum implementations for Enums that need it\n".to_string();
+
+    if Path::new("./bindings/java/src/enums.rs").exists() {
+        file_content = fs::read_to_string(Path::new("./bindings/java/src/enums.rs")).unwrap()
+    }
+
+    let mut enum_values: String = "".to_string();
+    for (index, value) in values.iter().enumerate() {
+        enum_values.push_str(format!("\n            {index} => {name}::{value},").as_str());
+    }
+    if default {
+        enum_values.push_str(format!("\n            _ => {name}::default(),").as_str());
+    } else {
+        enum_values.push_str("\n            _ => panic!(\"Invalid value: {internal}\"),");
+    }
+
+    file_content = file_content.replace(
+        "\n/// FromJavaEnum implementations for Enums that need it",
+        format!(
+            "\nuse taffy::{name};
+/// FromJavaEnum implementations for Enums that need it"
+        )
+        .as_str(),
+    );
+
+    file_content = format!(
+        "{file_content}
+impl FromJavaEnum<{name}> for {name} {{
+    const JAVA_CLASS: &'static str = \"Lcom/dioxuslabs/taffy/enums/{name};\";
+
+    fn from_ordinal(internal: i32) -> Option<{name}> {{
+        Some(match internal {{{enum_values}
+        }})
+    }}
+}}
+"
+    );
+
+    let file = File::create("./bindings/java/src/enums.rs");
+    file.expect("Error: File not found").write_all(file_content.as_ref()).expect("Error: Couldn't write to file");
+}
diff --git a/scripts/genenums/src/transformers/mod.rs b/scripts/genenums/src/transformers/mod.rs
new file mode 100644
index 000000000..8a6ae718a
--- /dev/null
+++ b/scripts/genenums/src/transformers/mod.rs
@@ -0,0 +1 @@
+pub(crate) mod java;