OpenLDK: Bringing the Java World into Common Lisp
OpenLDK runs Java libraries inside SBCL Common Lisp by translating JVM bytecode to Lisp, from a former gcj developer.
Extract
Many years ago I worked on an alternate Java compiler and runtime called gcj, the GNU Compiler for the Java Programming Language. These days, my preferred programming language for most tasks is Common Lisp, but I’m still interested in Java runtimes.Back in late 2024, I wanted to use a Java library from SBCL, but couldn’t find an integration approach that suited the application. This led me to explore the idea of building a simple JVM bytecode processor in Lisp so I could execute the code directly in process.I started before coding agents became useful for this kind of work, and wrote the first working version by hand.OpenLDK is a JIT compiler and runtime for Java, written in Common Lisp. It reads Java bytecode, translates it into Lisp, and lets SBCL compile that Lisp into native machine code. Java objects live in the same process and heap as the Lisp application.My goal is to make Java libraries feel at home in Common Lisp. For general Java execution, HotSpot will be much faster.In the last few days, however, OpenLDK has gotten much better. I’ve experimented with JVM-hosted languages and Spring Boot application execution.OpenLDK leads to some wonderfully strange possibilities. Java classes become CLOS classes. Lisp can add methods around methods written in Kotlin. A Clojure function can become something you pass to funcall. Java’s own compiler runs inside SBCL. And an initialized Spring Boot application can be saved in a Lisp image and launched again in about 84 milliseconds.You can even connect Emacs and SLY to a Spring Boot application running on OpenLDK, inspect its objects, and monkey-patch its behavior while it keeps running.Here is a tour of how it works, what it can do, and what that startup number actually means.The compilation pipeline begins with ordinary class files. OpenLDK targets JDK 25 and uses OpenJDK’s runtime libraries, reading them from the JDK’s JMOD files or its lib/modules container.When a class loads, OpenLDK creates its CLOS representation and installs stubs for its methods. Calling a method for the first time triggers compilation: bytecode becomes an intermediate representation, then Lisp, then native code through SBCL. The compiled definition replaces the stub.That laziness matters. Loading a library does not mean compiling every method in it. Much of a large library may never be used by a particular application.Consider a Java method:public static int add(int x, int y) { return x + y; } Stripped of the generated control-flow …