At Devoxx Belgium (I wasn’t there, a month later it still hurts, so I’m catching up) Ron Pressler gave a keynote called “Java - The Year of the Runtime!”. The argument is simple: now that a model writes the for loop more and more often, nobody argues about the for loop anymore - a bit like tabs and spaces.
The clip above comes from the documentary series Silicon Valley. I’m waiting for another season, although reality has probably outrun the art in absurdity by now.
Back to the runtime - if syntax doesn’t matter, what remains is how fast a program starts, how much memory it eats and what it can talk to efficiently.
I took him at his word and checked what happened below the language level in the last few weeks. Quite a lot. So this will be a low-level edition, but I promise it reads easier than it sounds: every section is about somebody choosing which engine to put under the hood.
And here is Pressler’s keynote itself, if you have a spare fifteen minutes:
1. Detroit fits factory engines into the JVM: V8 and CPython
Since the whole issue is about engines, the first stop has to be the car city.
In the car industry hardly anybody designs their own engine. Smaller brands have bought ready-made units from bigger ones for decades, because a good engine means years of work and a factory full of people who do nothing else. For a long time Java took the road of the ambitious garage: JavaScript had its own implementation there, written from scratch, called Nashorn. How that ended, in a moment.
Project Detroit is an OpenJDK project that does what a sensible car maker does. It lets a Java program run code written in JavaScript and Python, and the bodywork stays the same: the javax.script API, the one Nashorn used to sit behind. What changes is what’s under the hood. Detroit doesn’t build its own JavaScript or Python engine. It fits factory engines into the JVM process, meaning the real V8 and the real CPython, loaded as native libraries.
The project came back to life in March, and on November 4 Inside Java published the first long presentation: Maurizio Cimadamore‘s Devoxx talk, “Project Detroit: From Foreign Libraries to Foreign Language Runtimes”.
About the name: V8 is a car engine, and you go to Detroit for engines. Mikael Vidstedt admitted on the Inside Java podcast that John Rose had explained it to him only two months earlier, so if you didn’t get it right away, consider yourselves absolved.
Detroit grew out of an honest post-mortem of Nashorn. Nashorn shipped in Java 8 with ECMAScript 5.1. Then ES2015 arrived and the team chased it for about a year. Then the standard started changing every year, and npm exploded. An attempt to run Node.js on Nashorn showed that real compatibility means compatibility with V8 bug for bug. Cimadamore’s conclusion: maintaining somebody else’s language takes a “permanent industrial scale commitment”. Nashorn was removed in JDK 15.
So Detroit takes engines that somebody already maintains. The Python half stands on the FFM API, the successor to JNI that arrived in Java 22. The jextract tool reads CPython’s C headers and generates Java bindings from them, so there is no hand-written native code at all. In the demo a Gradio app with PyTorch runs through ScriptEngineManager.getEngineByName("python"), and packages are installed with plain pip. The JavaScript half still uses JNI, and an FFM prototype is on the way.
The benchmarks show both sides of this choice. On Octane, Detroit does roughly five times better than GraalJS, although the team itself notes that with a longer warm-up the gap would shrink a lot. It goes the other way when code keeps jumping between languages. When you sort an array in JavaScript with a comparator written in Java, every comparison crosses the border between runtimes, and here GraalJS is many times faster, even than the FFM prototype. Cimadamore doesn’t pretend it will ever be different: “It will never be great because this is like the worst possible case for Detroit.”
That one benchmark contains the whole trade-off. GraalJS implements JavaScript on the JVM, so both languages share one heap and one JIT, and calls between them are cheap. Detroit runs two separate runtimes. Each is as fast as it is at home, and you pay for every crossing.
Two runtimes also mean two garbage collectors. Store a Java callback in a Python object that Java itself holds a reference to, and you get a cycle across two heaps. Neither collector sees all of it, so neither will free it. Detroit’s answer is explicit lifetime management, borrowed from FFM.
One caveat before you start planning. According to Vidstedt, Detroit will most likely not be part of the JDK itself. It is meant to be a separate library that you add to a project like any other dependency. There are no builds to download yet, and the project describes its own status as “very experimental”.
Detroit has already had a cameo in this spring’s biggest runtime argument. When OpenJDK was removing JVMCI, the interface through which the Graal compiler plugged into HotSpot, Paul Hohensee of Amazon asked for a delay. He wrote that Detroit might provide the same thing one day, but that it isn’t certain and would take “possibly years”. JVMCI disappeared in JDK 27 anyway. We’ll come back to this in section 3.
2. Valhalla is now real enough to cancel other features
Something dropped out of JDK 27. For a change, something is coming into JDK 28, and it is the thing Java has been waiting for for more than ten years: value objects from Project Valhalla. JEP 401, Value Objects, is on the JDK 28 list as a preview. The release itself enters rampdown on December 3 and ships on March 9.
A reminder: an ordinary Java object has identity. Two Integers with the value 5 are two objects, each with a header and an address. A value class gives identity up, so the JVM can treat the object like an int: copy it, pass it in registers, pack it into an array without pointers.
Two things from the last two weeks are worth knowing.
The first is a Quality Outreach heads-up from Nicolai Parlog. With preview enabled, Integer, Double, the Optional family and most of java.time, among others, become value classes. The line to show your team: LocalDate.of(2026,7,31) == LocalDate.of(2026,7,31) returns true. If your code compares such objects with == and counts on false, or synchronizes on them, this is the release in which you want to find out.
The second one stopped me mid-scroll. On November 9 Angelos Bimpoudis wrote on amber-spec-experts: “We have decided to cease development of Primitive Types in Patterns, instanceof, and switch.” The feature had five previews behind it and will disappear from JDK 28 entirely.
The reason is Valhalla. Patterns on primitive types rest on a definition of “exact numeric conversion”, that is, of when turning a long into an int loses nothing. Valhalla is now exploring user-defined numeric types, and those would need the same definition stretched over types that don’t exist yet. Part of the feature will be replaced by the smaller Constant Patterns (for example case Point(0, 0)).
Withdrawing a preview is rare, but this is exactly what previews are for. I would rather lose a feature now than live for twenty years with two incompatible ideas of what a number is.
3. GraalVM is in every section of this issue, so it deserves its own
The sixth GraalVM Community Summit took place in October at Oracle’s office in Zurich. I wasn’t there, and I found no public write-up apart from one paragraph in Alina Yurenko‘s 25.5 release post, which lists attendees from Amazon and Apple to Netflix and Red Hat. So I rely on what GraalVM announced publicly this autumn, and that adds up to quite a bit.
GraalVM now releases every month, independently of the JDK calendar, and 25.5 is the fifth such release. Along the way more features move from the paid Oracle GraalVM to the free Community Edition: most recently loop vectorization and isolates, with G1 for Native Image being integrated right now.
Working meetings do leave traces in issue trackers, though. Volker Simonis of Amazon is porting the Shenandoah collector to Native Image, and in a review comment he mentions that G1 and Shenandoah will land on the same branch, “as discussed at the GraalVM Summit”. In practice Native Image gets serious garbage collectors by compiling HotSpot’s collectors, written in C++, into itself. His measurements show what all this is for: on SPECjbb, throughput with Shenandoah was almost twenty times higher than with the Serial collector.
The most important announcement is a different one. Just before Zurich, Fabio Niephaus showed a benchmark with two commands. java HelloWorld.java takes about 220 ms. java -svm HelloWorld.java takes about 55 ms and uses half the memory.
The second command has no native-image step, because the java launcher itself is a native image that runs ordinary bytecode. The project is called GraalVM Next and it rests on two pieces I described in vol. 185: Crema and Ristretto.
To see what they are for, you have to remember how classic Native Image works. The compiler analyzes the whole program before it runs and turns into machine code only the classes it can reach. That gives fast startup, and also the main limitation: the finished binary can’t execute bytecode it didn’t see at build time. So libraries that generate classes on the fly are out, and so are plugins loaded after startup and javac annotation processors.
Crema removes this limitation by adding a bytecode interpreter to the native image. The binary can then load a new class at run time, link it with the classes compiled earlier, and execute it. An interpreter is slow, though, so code loaded this way runs clearly worse than the rest of the program. That is the job of Ristretto, a JIT compiler for classes loaded by Crema: hot methods go to the Graal compiler and from then on run as machine code. Together they give the arrangement you know from HotSpot (an interpreter with a JIT above it), except that it starts from a program that is mostly compiled already. The coffee names are no accident: the interpreter descends from Espresso, the implementation of Java on Truffle.
In September Crema was already running Maven and JShell, and in 25.5 it handles JDK tools such as jstack and keeps compiled code for the next run.
For a decade Native Image meant a closed world: everything known at build time, nothing loaded later. GraalVM Next keeps the fast startup and gives the open world back. In my opinion this is the most important thing happening in this project, and it deserves more noise than a single Bluesky post.
Now the wider picture, because GraalVM plays both roles in this issue at once. It borrows: it takes its garbage collectors from HotSpot and assembles the whole new launcher from a regular JDK. And it is borrowed from: Elide and Bali in section 5 stand on Native Image, Crema and Ristretto, and GraalWasm and Web Image will return in a moment in the WebAssembly section. It is also the second answer to the question Detroit asks. Detroit puts somebody else’s engine into the JVM. GraalJS and the other Truffle languages write that engine again on the JVM, so that everything shares one heap and one JIT. Both roads exist side by side today, and the benchmark in section 1 showed what each of them costs.
Monthly releases have a cost that the team talks about openly, and this is where the end of section 1 comes back. GraalVM needs a JDK with the JVMCI interface, the same one Amazon asked to keep a little longer, because the Detroit-shaped replacement is a wait of possibly years. OpenJDK no longer has that interface, so GraalVM patches the JDK itself, and Mandrel, Red Hat’s build used by Quarkus, stays on the 25.0 line. The plan stays the same: skip JDK 26, 27 and 28 and return to a shared calendar with Java at JDK 29.
4. The browser’s garbage collector now collects Java objects
On November 2 Leaning Technologies released CheerpJ 5.0. CheerpJ is a JVM for the browser: a virtual machine written in C++ and compiled to WebAssembly that runs unmodified JARs. Version 5.0 adds Java 21 and ships a full JDK for the first time, so javac works on the client side.
Alessandro Pignotti writes in the announcement, however, that modern Java is not the biggest achievement of this release. That would be the second engine, built on WasmGC.
There is a simple idea behind the acronym. Classic WebAssembly gives a program one flat block of memory, as in C. A language with a garbage collector had to carry its own collector inside that block. WasmGC lets a program create objects that are managed by the browser’s own collector. In CheerpJ’s new engine a Java object is a WasmGC object. The JVM handed its heap over to the browser. The engine is experimental and the team asks people not to use it in production before 6.0.
Their test for “does it really work” is Minecraft. A historical version already runs, with the original client.jar downloaded from Mojang’s servers. On Hacker News somebody noticed that the game recommends installing 64-bit Java. An options screen from 2011 is not impressed by the WebAssembly era.
Endive, formerly Chicory, goes in the opposite direction: it runs WebAssembly on the JVM, in pure Java. In May the project moved under the Bytecode Alliance, as announced by Andrea Peruffo. In Endive a WasmGC object is an ordinary object on the Java heap and the Java GC cleans it up. GraalVM has both directions: GraalWasm runs Wasm on the JVM, and Web Image compiles Java to a WasmGC module.
These building blocks produced my favorite artifact of the season. Peruffo took javac, compiled it to WebAssembly with Web Image and ran that module on Endive, which runs on the JVM. The Java compiler as Wasm on Java produced correct .class files. It has no practical use whatsoever and I’m very glad it exists.
From the same neighborhood: Scala.js declared its Wasm backend stable, Kotlin/Wasm is still in beta, and TeaVM deleted its WASI backend and is betting everything on WasmGC in the browser.
5. Elide wants to be for the JVM what Bun is for JavaScript
I’m closing the tour with the project that borrows more consistently than any other, and that I consider one of the most interesting things being built around the JVM today. Elide, founded by Sam Gammon, describes itself as a runtime, compiler and toolchain in one binary, for JavaScript, TypeScript, Python, Java and Kotlin.
What the team wants to achieve
The team explains its goal on the project site with a history of runtimes: every decade a new abstraction arrives that makes software faster to write and slower to run, and Elide is meant to break that pattern. This translates into two concrete aims. The first is one install instead of separate sets of tools for compiling, running and building. The second concerns the JVM itself: give it a modern toolchain without moving anybody off the platform, with the same artifacts and the same Maven dependencies, and a much shorter feedback loop.
The easiest way to think about it is Bun. Bun took the JavaScript tools (runtime, package manager, test runner) and put them into one fast binary. Elide does the same with javac, kotlinc, the jar tool, the Maven resolver and a test runner. Kotlin is treated as an equal of Java here: compiler plugins, the KotlinX libraries and JUnit work with no extra configuration.
How they plan to do it
The team writes as little as possible from scratch and lists on its own site what the product is assembled from. All the tools are compiled ahead of time with GraalVM Native Image, so none of them has to start and warm up a JVM first. The dynamic languages run on Truffle, which lets JavaScript, TypeScript and Python call each other in a single process. The I/O and networking layer is written in Rust. Project configuration is written in Pkl, Apple’s typed configuration language, and JDK 25 and the Kotlin compiler come in the box.
The project’s own chart shows what skipping the warm-up buys: a JavaScript hello world on Elide starts ahead of Bun, Deno and Node.js.
The second design decision is compatibility with what you already have. elide mvn runs existing Maven builds, elide adopt moves Maven and Gradle projects to Elide’s own format, and Bazel can use Elide’s compilers as its workers.
Why does it matter right now? Agents like Rust partly because cargo check answers quickly. On the JVM every “does this compile?” costs seconds, and an agent asks that question hundreds of times a day. A toolchain that starts in milliseconds changes that loop for agents and for people. It is Pressler’s keynote seen from the build side. Do measure the multipliers from the project site on your own code, though, because the gain from starting without warm-up is biggest for small, frequently run compilations.
What stage the project is at
Elide is at version 1.6 and publishes nightly builds for Linux, macOS and Windows. Python is an optional component, and the SDK for embedding the engine in your own application is experimental. The project recently moved to a source-available license, and the older code, now called Elide Classic, stayed open on a separate branch. The community is still small, so for now it is something to try on a side module or in CI.
Madura and Bali: the next islands
Next to the main product the team runs two experiments that show where this philosophy leads. Madura is, according to its README, a hermetic, minimalist Java compiler made only from JDK internals. The madura check command skips code generation and only checks correctness, so it does for Java what cargo check does for Rust. The README itself names agents as the main reason: code is generated often and has to be checked often.
The README comes with its own hyperfine run, in which madura check finishes about twenty times faster than javac:
The packaging is the more interesting part. Madura is one file, and the same file runs on macOS, Linux and Windows, on x86-64 and ARM64. The format is called Actually Portable Executable and comes from Justine Tunney‘s Cosmopolitan project. A pull request that adds this capability to Native Image is waiting for review in the GraalVM repository.
“Write once, run anywhere” was a promise about bytecode and a virtual machine. Thirty years later somebody delivered it as a native binary.
The third island is called Bali. (Java, Madura, Bali. There are plenty of islands left in Indonesia.) Bali is a JVM built from a regular JDK, without a fork, on the same Crema and Ristretto projects that java -svm from section 3 stands on. The repository is not public, but the project already leaves traces. The team’s public test suite runs OpenJDK’s own jtreg tests on it, next to a stock JDK as the reference, and in the published report Bali passed more than three quarters of them. The same document notes that the result does not establish Java SE conformance.
HotSpot remains the obvious choice for long-running servers. Elide bets on short, tool-shaped, agent-driven work, the way Bun bet on a different shape of work in JavaScript. I saw what Bun did to its neighborhood, and I wouldn’t bet against it.
6. The second keynote: Odersky asks whether this is the end of programming
We started with Pressler’s keynote, so let’s finish with a second one that answers it, though probably not on purpose. In the same October, Martin Odersky opened Scala Days in Berlin with a talk called “The End of Programming?”. Pressler says that since agents write the code, the runtime is what counts. Odersky asks what is left of the language in such a world.
The starting point is a scenario we all know from LinkedIn: a human writes a prompt, an agent writes the program, and another agent does the review. Nobody reads the program, so the language stops mattering. Odersky doesn’t want that world, because whoever stops understanding programs hands the keys to agents, their own or somebody else’s. He compares an agent to a compiler, only a flaky one: it gives something different every time, makes mistakes and guarantees nothing. So correctness can’t be begged for in a prompt and has to be enforced from the outside.
TACIT: the agent writes Scala and the compiler stands guard
This has gone furthest in tool-calling agents, where generated code executes immediately and nobody reads it. The known safeguards are too coarse. A sandbox can’t tell a secret from ordinary data, so an agent can read a secret in step one and send it out in step five, and each step looks innocent on its own.
So Odersky’s team secures the medium and doesn’t have to trust the model. The agent expresses its intent in Scala 3 with capability tracking turned on, and only code checked by the compiler reaches the world. A capability is a key that can’t be forged, and the new part is that the keys are visible in the types. A function with the -> arrow is pure and holds no key, and a function with Scala’s familiar => arrow can do anything. Private data is wrapped in a Classified type. The agent can transform it only with a pure function, so it can’t print it, send it or remember it, and it never gets the key to unwrap it.
The whole thing is called TACIT (Tracked Agent Capabilities In Types) and it is an MCP server: the agent sends a Scala snippet, the compiler checks it, the REPL executes it. The paper won the best paper award at the CAIS conference in May. Odersky is honest about what the system doesn’t catch: timing attacks, where an agent smuggles information out through the length of a computation.
The first agent built on TACIT already exists. Capybara Claw is an experiment from the same lab: an always-running agent that gets selected files, by default .ssh and .env among others, only as Classified. Its README warns that the project is highly experimental and not ready for production yet.
This is where the talk touches the rest of this issue. Tools for the agent can be written in Java, JavaScript or as a Wasm component, and the team is exploring a variant in which types describe the policy and a sandbox enforces it at run time, whatever the implementation language.
Three ideas for languages after agents
The second half is about large systems that live for decades, and here Odersky warns that he is showing early ideas. The first is a prompt that stays in the program: a natural-language specification sits next to the function signature and the agent keeps the code in line with it. The second is required interfaces, declared by the user of a library: I need a JSON parser that uses nothing but the console. If, after a supply-chain attack, a new version wants the network and an API key, the compiler rejects it. The third is types with a predicate, such as “a vector that is sorted”, for which the model constructs the proof.
All three lead to the same place: supervising an agent can mean reviewing short signatures that hold what matters, instead of reading every line. Will languages change to allow this? “We don’t know yet”, says Odersky, but it would be a miracle if everything changed except them.
What remains when syntax stops being the topic
I started with Pressler’s line that syntax moves to the background and the runtime remains. After six sections I would add one refinement to it: what remains is the border.
Look at what the people in this issue were busy with. Nobody wrote a runtime from scratch. Detroit took V8 and CPython, because Nashorn showed what it costs to write somebody else’s language again. CheerpJ handed its heap to the browser, Endive handed Wasm objects to Java’s collector, and Native Image borrowed its collectors from HotSpot. GraalVM Next and Bali assemble a JVM from a ready-made JDK. All the hard work happened at the seam: who owns an object, who frees it, what it costs to cross to the other side.
Odersky does the same one floor up. His seam runs between the code an agent wrote and the world in which that code will do something. Types, a compiler and a sandbox guard it.
In July I wrote about Metropolis, the dream of one JVM written in one language. That train has left. What we have in its place is a garage full of other people’s engines and better and better ways to bolt them together.
So if you are going to learn one thing outside your own language this year, pick something from this list: the FFM API, WasmGC, capabilities in Scala 3. Today’s crossings stand on them, and the crossings decide whether a borrowed engine runs at all.
PS: If you maintain a library, run your tests on JDK 28 early-access with --enable-preview this month. Integer, Optional and most of java.time change the meaning of ==, and rampdown starts on December 3.
PS2: The current GraalVM release is called 25.5.5.0.3. Fabio Niephaus wrote after the launch himself: “I hope we can simplify this soon.” Me too, Fabio.
PS3: Yesterday was Independence Day in Poland, so, as tradition has it, it’s time for Banach and Tarski. In 1924, a few years after Poland regained independence, two Polish mathematicians proved that a ball can be cut into a finite number of pieces and reassembled into two balls identical to the first. It sounds like the plan of the teams in this issue who assemble a second JVM from one JDK, except that the mathematicians only needed the axiom of choice. Michael from Vsauce explains it better than I ever will:
PS4: This time the cover is a nod to “Tetsuo: The Iron Man” (1989), Shinya Tsukamoto’s film about a man who gradually turns into scrap metal. Duke got off lightly: one V8 and a few exhaust pipes.
PS5: One more announcement, this time about me. On February 22-24 I’m speaking at the AI Software Factory Summit in Miami, organized by Moderne and OpenRewrite. My talk is called “Build a Software Factory: An Agentic SDLC on Kubernetes, From Scratch”, and it does what the title says: we put together an agentic delivery pipeline on Kubernetes piece by piece. If you’d like to come, the code KUBERNETES26 takes half off the ticket. See you on the beach!
Thanks for reading JVM Weekly!









