Jacobin: a JVM written in Go that runs Java 21 classes
A more than minimal JVM written in Go and capable of running Java 21 classes.
At a glance
- What is it?
- An alternative JVM implementation, written in Go, that loads and executes Java 21 bytecode without JNI, without a JIT and without a security manager, and leaves garbage collection to the Go runtime. It is pre-alpha software for people who want to read a JVM rather than ship on one.
- Who is it for?
- Adopt Jacobin if you are studying JVM internals, need to run Java 21 bytecode in an environment where a standard JVM is awkward, or want to build native Go functions callable from Java, since the runtime does that natively. Do not adopt it for production workloads, because it is tagged pre-alpha, does not execute invokedynamic, has no JIT, and the project itself says it is not looking for testers.
- Can I use it commercially?
- Yes, with conditions. MPL-2.0 is a weak copyleft licence: you can use it inside commercial and closed-source software, but if you distribute changes to its own files, you must publish those changes under the same licence.
- Is it still maintained?
- Yes. The repository last received commits 5 days ago.
- What is it written in?
- Mainly Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A JVM that drops JNI, the JIT and the security manager on purpose
Jacobin describes itself as a more-than-minimal JVM written in Go, and the repository summary puts it as capable of running Java 21 classes. The interesting design decision is the list of things it deliberately leaves out.
There is no JNI, because Oracle intends to replace it, as the project notes with a reference to JEP 389. There is no security manager, because Oracle intends to remove it, with a reference to JEP 411. There is no JIT. Bytecode verification is described as somewhat less stringent, and sealed classes are not enforced.
Each of those omissions traces back to the Java platform's own direction rather than to a shortcut. JNI exists mainly so code outside the JVM can call in, and the replacement path is the foreign function and memory API. The security manager is on its way out of the JDK. A JIT is a large subsystem, and a Go implementation that skips it trades throughput for a much smaller and easier to read codebase.
So the trade is explicit. You get a JVM you can read, with Go as the host language, at the cost of the performance work and the platform features that are on their way out anyway. That trade suits an implementer, a teacher, or someone embedding Java bytecode in a Go program, rather than someone looking for a faster way to serve a web application.
Class loading from JDK binaries, JAR files and the local JAVA_HOME
The class loading section is where the design becomes concrete. Jacobin correctly reads and parses most classes, extracts the bytecode and the parameters needed for execution, and pre-loads core Java classes such as Object automatically.
The java.*, javax.*, jdk.* and sun.* classes are loaded from the JAVA_HOME directory, that is, from the JDK binaries. Jacobin does not reimplement the standard library. It expects a JDK to be installed and borrows the class files from it, which is why the class loader can cover the whole standard library surface without a rewrite.
JAR files are handled, as are interfaces. The unfinished list is specific and useful for judging scope: more complex classes, such as those called via method handles, and inner classes.
Beyond class loading, the runtime performs a format check of the class file, following the Java Virtual Machine Specification section 4.8, and performs preparation of a loaded class file, following section 5.4.2. Linking and initialization happen minimally, and only as needed at execution time. Verification itself is still on the to do list, along with robust linking and initialization.
That combination, minimal linking done lazily plus a format check that is not backed by full verification, is the architectural centre of the project. It is how a JVM stays small enough to write in Go, and it is also where the security exposure lives.
Execution covers most bytecodes, and invokedynamic is the hole
On execution, the claim is that all bytecodes are executed except INVOKEDYNAMIC, including one and multi-dimensional arrays. Static initialization blocks run, exceptions are thrown and caught, calls to superclasses work, and threading is implemented with synchronized methods, monitorenter and monitorexit, tested up to 64 threads.
The missing list is the part to plan around. Method handles are not implemented. Inner and nested classes are not implemented. The invokedynamic bytecode is not implemented. Annotations are not implemented.
invokedynamic is the one that matters most in practice. It is the mechanism behind lambdas, and it is also what string concatenation compiles to on modern javac. A class that uses a lambda expression or, on newer compilers, a string concatenation is very likely to contain an invokedynamic instruction. That makes this less a niche gap than a boundary on the kind of Java code the runtime can currently execute.
Native functions are the opposite story, and they are the most interesting capability. The project runs native functions written in Go, so a Go function can be exposed to Java code as a native method. That is a real embedding path that a conventional JVM would need JNI for, and JNI is exactly what Jacobin drops.
Tracing options for watching bytecodes execute
Instrumentation is a first class feature rather than a debugging afterthought, and it comes with three levels of detail.
Class-loading instrumentation is switched on with -trace=cloadi. Instruction-level tracing uses -trace=inst and produces a listing of class, method, instructions and size of op stack for all instructions as they execute. Detailed instruction tracing uses trace=verbose, which shows everything in -trace=inst plus the contents of the op stack for each instruction.
The project's own suggestion for a first look is to run a program with the -trace:inst option and watch the executing Java bytecodes whiz by with the changing contents of the operand stack. The README uses -trace:inst in that sentence while the feature list uses -trace=inst, so the two spellings in the documentation are not consistent with each other, and it is worth checking which form the current build accepts.
Two items on the instrumentation to do list show where this is heading. The plan is to emit instrumented data to a port for reading and display by a separate program, and to write a UI component that watches the bytecodes executing and the changes in the various stacks. In other words, the tracing is currently a text log, and the intent is to turn it into a live view of the interpreter.
The garbage collection story is the opposite: Jacobin does not implement one. GC is handled by the Go runtime, which has its own GC.
What the Go module and the version trail say about the build
The module declaration is plain, module jacobin, and the go directive records the target toolchain with the issue number that requested it:
module jacobingo 1.27.1 // as of 2026-09-30 (v. 0.9.1) per JACOBIN-979The comment history in the file shows the toolchain being tracked deliberately, from go 1.18 up through 1.19, 1.20, 1.21, 1.21.4, 1.24.0, 1.25 and 1.26.1, each annotated with the date and the version that introduced it. That is a project that upgrades its Go baseline as new releases land, and the current line requires a recent Go toolchain.
The dependencies are few for a runtime of this size. The direct requires are xxhash, circl, smartgo, the x/crypto and x/term modules, with x/sys pulled in indirectly, and a tool directive for the deadcode command from the x/tools module. There is no dependency on a JVM, a parser library or a bytecode library, which is consistent with the claim that the agent is written with zero external dependencies in the sense that matters here.
The tree is small too. Top level entries are AUTHORS.txt, CONTRIBUTING.md, the LICENSE, the go module files, a notes directory, a src directory and testdata, plus the GitHub workflow configuration. There is also a wiki described as a detailed roadmap to the code base, and the project home page at jacobin.org carries a note thanking the vendors and programmers who made the project possible.
Pre-alpha releases, and an explicit request that you not test it yet
The version trail is honest about where the project is. The most recent tagged release is v.0.9.1-pre-alpha, published on 2026-09-07. Before it came v.0.9.0-pre-alpha on 2026-03-11 and v.0.8.0-pre-alpha on 2025-09-02. Every published tag carries the pre-alpha suffix, and the numbering has not moved to a 1.0 in the project's lifetime.
The project is not archived and the last push was on 2026-10-18, so the code is being worked on steadily even though the release cadence is a few per year.
More telling is the request aimed at would-be testers. The project says it is not looking for testers at present because it knows the missing features and is working on them, and that testing at this point will likely result in frustration. People who still want to try it are pointed to the directions and cautions on the release page. The project also says it expects or hopes or trusts that by the end of the year it will be ready to ask interested users to test on real programs and share feedback.
The licence is MPL 2.0, the file based copyleft licence, which means changes to the covered files must be published under the same terms when distributed, while larger works that merely link to it are not forced into that. For an embedder that matters: shipping a Go binary that embeds Jacobin carries obligations you would not have with a permissive licence.
Read that request seriously if you are evaluating this for anything but learning. The list of unfinished items is long enough that ordinary Java code will hit it.
Where a standard JVM is the better answer
The honest comparison here is with running the same Java 21 classes on a normal JVM, since Jacobin is explicitly modelled on that world.
The difference in approach is what each one optimises for. A standard JVM ships a JIT, so it compiles hot methods to native code while the program runs, and it ships the security manager and JNI that Jacobin leaves out. Jacobin instead interprets bytecode directly, keeps the whole implementation small enough to read in Go, borrows the standard library classes from the JDK installation, and delegates garbage collection to the Go runtime.
The consequences follow from that. On a standard JVM you get performance, the full bytecode set including invokedynamic, and the ability to use JNI to bind native code. On Jacobin you get a runtime you can read end to end, native Go functions callable from Java without JNI, and no JIT overhead in the interpreter loop, at the cost of throughput, method handles, inner classes, annotations and invokedynamic.
There is a third option worth naming for some workloads, and it is not a competitor project but a different deployment model: leaving the JVM out of the picture entirely and reimplementing the logic in Go. If the Java code is being ported rather than executed, that removes the compatibility question altogether. Jacobin is the option for when you must run the existing classes, and the readymade option for when you can rewrite them.
Editorial conclusion
Adopt Jacobin if you are studying JVM internals, need to run Java 21 bytecode in an environment where a standard JVM is awkward, or want to build native Go functions callable from Java, since the runtime does that natively. Do not adopt it for production workloads, because it is tagged pre-alpha, does not execute invokedynamic, has no JIT, and the project itself says it is not looking for testers. Verify first that the classes you need avoid invokedynamic, method handles and inner classes, all of which are listed as unfinished.
Frequently asked questions
What is Jacobin and what can it run?
Jacobin is a more-than-minimal JVM written in Go. It runs Java 21 classes, and executes all bytecodes except invokedynamic, including static initialization blocks, exception handling, superclass calls, arrays and threading.
Does Jacobin support JNI, a JIT or a security manager?
No on all three. The project states there is no JNI, no security manager and no JIT, and points to JEP 389 and JEP 411 as the reason, since Oracle intends to replace JNI and remove the security manager.
Where does Jacobin get the java.* standard library classes from?
The java.*, javax.*, jdk.* and sun.* classes are loaded from the JAVA_HOME directory, which means from the JDK binaries. Jacobin expects a JDK to be installed and does not reimplement the standard library.
How do I see what Jacobin is executing?
Class-loading instrumentation uses -trace=cloadi, instruction-level tracing uses -trace=inst, and detailed tracing with op stack contents uses trace=verbose. The project suggests running a program with the trace option and watching the bytecodes execute.
Can Jacobin call Go code from Java?
Yes. The project runs native functions written in Go, so Go functions can be called from Java as native methods. Garbage collection is handled by the Go runtime rather than by Jacobin itself.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/platypusguy-jacobin)Was this useful?