Java’s Plans for 2026 and new curiosities from JDK Mailing Lists - JVM Weekly vol. 159
Let's have a sneak peak into what will happen 2026!
New year, new plans, new promises we’ll look back on in twelve months with a mixture of nostalgia and disappointment.
But rather than dwelling too much on those New Year’s resolutions - there’ll be plenty of opportunity for that - let’s take a look at what OpenJDK teams themselves are saying about their plans for 2026.
OpenJDK Plans for 2026 - Valhalla Getting Closer, Amber Not Slowing Down, Loom Nearly Complete
I have to admit: Nicolai Parlog from Oracle gave me a pleasant surprise. His New Year’s episode of Inside Java Newscast extracted concrete details from people working on individual projects. An “anonymous source” (whose identity anyone who’s ever heard Brian Goetz will immediately guess) dropped some bombs worth discussing.
Valhalla is targeting JDK 28. This is probably the most important piece of news. Our Anonymous Source revealed that value types won’t make it into JDK 27 - but not because they aren’t ready.
“We’re bringing an elephant onto a train and want to make sure we get into an empty car.”
JDK 27 forks in June, so mainline will switch to 28 then - with room for JEP 401. After value types, nullness markers, array improvements, and primitive-wrapper unification are queued up - but that’s a perspective for later releases.
Vector API is waiting for Valhalla. JDK 26 will see its eleventh (!) incubation - and it’ll stay that way until value types land in mainline. When that happens, the implementation will be rewritten and the API moved from jdk.incubator.vector to the proper java package.
Leyden - AOT code compilation. The AOT cache will contain not just loaded classes and method profiles, but compiled machine code as well. The runtime will be able to pull optimized code straight from the cache, dramatically reducing warmup time.
Structured Concurrency nearing finalization. After the revamp in JDK 25, the API will go through preview with minimal changes - Nikolai rates the chances of finalization this year as good. This is the last piece of the big Project Loom picture (though some would like to see a bit of “scope creep” here - in the positive way - which we’ll get to shortly).
Amber - constant patterns and pattern assignment. The team is “knee-deep in the second phase of pattern matching.” Two features are mature enough that JEPs may appear this year. We’ll examine all of this ourselves in a moment since the details from the mailing lists are very interesting, but apparently ideas for generalizing records and pattern matching to classes and interfaces are popping up on amber-spec-experts. So “things are happening.”
Babylon preparing code reflection incubation. The technology allowing frameworks to reflect over code in methods and lambdas is developing well—we should hear more this year. I’m personally eager for any announcements related to GPU support.
That’s the official plans. But as usual, the most interesting things happen at the margins—in experimental branches and mailing list discussions. And that’s exactly where Valhalla is showing that value types are just the beginning of a much more ambitious story.
Type Classes - Valhalla experiments with the next step
Maurizio Cimadamore announced on the valhalla-dev list the publication of an experimental type classes prototype - a mechanism Brian Goetz presented at JVMLS 2025 in his talk Growing the Java Language. The code landed in a new type-classes branch in the Valhalla repository.
What problem do type classes solve? Today in Java, you can’t write generic mathematical code that works on int, BigDecimal, and a hypothetical Float16 alike. Interfaces require types to explicitly implement them - you can’t say Integer implements Addable without modifying the Integer class. Type classes (known from Haskell, appearing as traits in Rust) invert this relationship: instead of requiring implementation in the class, you provide an external “witness” that says “here’s how to add values of type X.” Witnesses can be defined for any type, even someone else’s.
record MyInt(int x) { }
interface Sum<X> {
X zero();
X add(X a, X b);
}
__witness Sum<MyInt> SUM_MYINT = new Sum<>() {
MyInt zero() { return new MyInt(0); }
MyInt add(MyInt a, MyInt b) { return new MyInt(a.x + b.x); }
};
// Usage:
Sum<MyInt> sum = Sum<MyInt>.__witness;
MyInt zero = sum.zero();
MyInt one = new MyInt(1);
assert sum.add(zero, one).equals(one);
In this prototype, you can define a type class and a witness for a specific type. For example, here’s how an addition type class (Sum) and its witness for a value class MyInt are defined.
Sum<X>is a generic interface representing a type class for addition.__witnessdefines a witness for howMyIntimplements that type class externally.The witness can then be looked up and used at runtime.
This prototype enables external definitions of operations for types without modifying those types themselves, addressing a long-standing limitation in Java: today it’s impossible to write truly generic mathematical code that works uniformly across primitives like int, boxed types such as BigDecimal, and custom numeric or value types, because interfaces require the types to explicitly implement them. Type classes invert this relationship by allowing behavior to be attached externally via so-called “witnesses,” making it possible to state “here’s how type X does addition” without editing X at all.
Goetz explained the broader vision at JVMLS: type classes are meant to enable operator overloading for value types, collection literals, or new numeric types with full support for +, -, * - but without the operator hell known from C++, since they’d be limited to value classes only.
Maurizio is very clear on one point, though: this is purely a space for experimentation, not a proposal for inclusion in the Java platform, and any JEP is still a long way off.
With Valhalla, as ever, patience is part of the lesson. However, they have even more under they sleeve now!
Null Checks get concrete - The “Bang World” Prototype
Daniel Smith announced on valhalla-spec-experts another prototype branch worth watching: bworld (short for “bang world”). This one tackles the long-awaited nullness markers - specifically, the runtime enforcement side of making ! actually mean something.
The idea is straightforward: mark types with ! to indicate a non-null barrier, and have the JVM enforce it. You’ll be able to use ! on field types, local variables, method parameters, return types, casts, instanceof, and array element types. And crucially - this isn’t limited to value classes. Any reference type can be marked.
What happens at runtime? The compiler generates calls to a new java.lang.runtime.Checks API (deliberately not Objects.requireNonNull - they want the JVM to have freedom to treat these checks specially). What those it mean in practice?
A cast to
String!will throw if you pass nullA field declared as
String! namemust be initialized before thesuper()call.Arrays created with
new Foo![]{a, b, c}will reject null writes dynamically.
Daniel notes that current implementation only fully supports runtime checks for value-class-typed fields and arrays - other reference types will get the metadata in the class file, but enforcement is coming later.
The prototype also includes optional lint warnings for suspicious patterns - like assigning null literals to ! targets or removing ! markers when overriding methods. But the most interesting bit is “use-site checks”: the compiler can insert null checks when calling methods from untrusted binaries! This neatly exposes the real problem: not greenfield code, but millions of libraries that were written when null markers weren’t even imaginable.
That’s the real challenge: how to introduce null-safety into a 30-year-old ecosystem gradually, without breaking the world? But as Daniel puts it:
This is not the final version of the feature... it’s a snapshot. But we’ve been wanting something concrete that we could play with.
The Kotlin crowd will feel right at home, except for one crucial difference: Kotlin’s null-safety is purely a compiler-land, erased at runtime. Java is building actual JVM enforcement. Slower to arrive, but when a String! field says “never null,” the runtime will back that promise up.
Ephemeral Threads - Clojure knocks on Project Loom’s Door “With a Request”
Mama, take this badge off of me
I can’t use it anymore
It’s gettin’ dark, too dark for me to see
I feel like I’m knockin’ on heaven’s door
Since Structured Concurrency is approaching finalization, can Project Loom be considered “finished”? The community has a different opinion. A heated discussion (30+ emails in a week) erupted on the loom-dev list, initiated by Alex Miller from the Clojure team.
The topic? So-called “ephemeral threads”- threads that can be garbage collected before they finish their work. Sounds like heresy in the Java world, where for 30 years an iron rule has applied: threads are GC roots and live until they complete their work. But for the Clojure community, this has been daily bread for over a decade.
What’s the deal? The core.async library lets you create lightweight “go blocks” that wait for data from channels. If all channels become unreachable, the block can also be collected by GC - since it would never wake up anyway. Elegant and practical when building pub/sub or pipelines.
The problem: after migrating to virtual threads, the pattern stopped working. Alan Bateman, Tech Lead of Loom, is however skeptical about the ephemeral thread concept, pointing to deeper complications: interactions with finalizers and cleaners can lead to scenarios from mildly creepy to truly terrifying.:
The possibility of GC’ing a started thread before it terminates is a scary topic. It interacts with many areas and gets really scary once you bring phantom refs, cleaners, and finalizers into the discussion.
For these so-called “forgotten sender” and “abandoned receiver” cases then it might be more interesting to see how they could be work with structured concurrency. Right now, the first API is focused on fan-out scenarios but in time we would like to have it work with channel like constructs too. I suspect this will be closer to what you are interested in.
Since JDK 21, VTs are tracked by default for diagnostic tools, so “abandoned” threads hang in memory indefinitely. The flag - Djdk.trackAllThreads=false helps, but Miller rightly asks about its future, and hears from Alan Bateman:
It's clearly an attractive nuisance right now and setting it to
false is specific to the root "thread grouping". There is some
performance work required in that area but otherwise I think it needs to
be removed.
The argument for ephemeral threads is simple: virtual threads open the door to Erlang-style architectures where lightweight processes can be abandoned when they become redundant. Miller writes directly:
Most of these constructs work as infinite loops without persistent references. You simply can’t build such libraries with the traditional approach to thread termination.
Similar voices came from other users experimenting with their own schedulers - they want to use VTs as an invisible implementation detail where the end programmer doesn’t think about threads at all.
Oracle has serious concerns, however. The main one: debugging. Viktor Klang illustrates with an example - code acquires a file descriptor, parks the thread, then releases it. If the thread gets collected while parked, the descriptor leaks without a trace.
This easily leads to problems where resources leak without any trace of who lost the - which can be nightmarish in production,
Andrew Haley from Red Hat offered an interesting counterargument: if a thread is waiting on an unreachable semaphore, it will never release resources anyway - whether it takes up memory or not, the problem is the same.
There is a light at the end of the tunnel, however. Bateman suggests that cases of “abandoned” threads might play better with structured concurrency, which over time is meant to handle channel constructs as well. For Clojure, though, this means waiting - an official API for ephemeral threads probably won’t materialize, and the unofficial flag will likely disappear.
The discussion shows a broader trend: virtual threads opened Pandora’s box of new patterns that the JVM ecosystem is only beginning to explore.
Project Amber in 2026 - Pattern Assignment and Constant Patterns on the Horizon
And finally, Gavin Bierman from the Amber team shared details of plans for 2026 on the amber-spec-experts list. Beyond continuing work on Primitive Patterns (currently in preview), two new features are in the pipeline - draft JEPs should appear soon.
Pattern Assignment solves an irritating problem: sometimes we use pattern matching not because something might match, but because we want to conveniently decompose a value into parts. Today we have to write:
void process(ColorPoint cp) {
if (cp instanceof ColorPoint(var x, var y, var c)) {
// actual code, unnecessarily nested
}
}
The compiler and programmer both know the pattern will always work—but the syntax forces us to pretend it’s a conditional operation. The new proposal will let you simply write:
void process(ColorPoint cp) {
ColorPoint(var x, var y, var c) = cp; // Pattern Assignment!
// actual code, no nesting
}Constant Patterns is the second proposal, simplifying a common case—matching against a specific value. Instead of:
case Point(var x, var y) when x == 0 && y == 0 -> { /* origin */ }You’ll be able to write:
case Point(0, 0) -> { /* origin */ }Constants (including null) will be able to appear directly as nested patterns - which will somewhat unify the awkward division between “case constants” and “case patterns.”
Finally, a small observation from my functional heart (working at the company behind Scala obliges 😉).
Looking at all these discussions, it’s hard not to notice a common denominator: functional languages and their concepts remain a key reference point for JVM evolution. Type classes are a mechanism straight from Haskell. Pattern assignment is essentially the equivalent of let with deconstruction known from ML-family languages. And ephemeral threads? It’s a request for semantics that Erlang and its descendants have treated as obvious for years.
But here’s where it gets interesting: Java isn’t so much “adopting” these concepts as conducting a sort of dialogue with them - and often says “no” or “yes, but.” Type classes will be limited to value classes to avoid “operator overloading hell.” Ephemeral threads? Bateman politely suggests that maybe structured concurrency will someday handle these cases - which in practice means “we’ll do it our way or not at all.” Pattern matching is evolving so cautiously that Scala had time to have it, stop having it (in the sense of: stop being fashionable), and have it again before Java got to constant patterns.
And this is precisely the paradox that fascinates me: paradoxically, Java has probably become the most important testing ground for functional ideas in the mainstream—not despite its conservatism, but because of it. Every feature goes through such brutal mills of backward compatibility, edge case analysis, and years of preview that what comes out the other side is... surprisingly solid. Haskell’s type classes are elegant but also notoriously difficult for the average programmer to understand. Java will probably produce something less elegant, more “corporate”- and paradoxically more useful for most developers.
There’s a certain irony in all of this: Clojure, the language that since 2007 has been proving that functional programming on the JVM is possible and practical, is now asking for functionality it implemented itself for a decade—but can’t port to virtual threads without platform support. Alex Miller knocks on the door with a proposal inspired by Erlang, and Java responds in the style of: “interesting, but have you thought about interaction with finalizers? Because we have, and we have nightmares.”
Maybe that’s exactly why this relationship works. Functional languages explore, Java stabilizes. Erlang shows that ephemeral processes are possible, Clojure proves they work in practice on the JVM, and ten years from now Java will introduce something called “Scoped Ephemeral Task Contexts” that will work with every version back to JDK 8. And mass-market enterprise will finally get a feature that functional programmers were talking about at conferences in 2015. That’s the deal—and honestly, I’m not sure there’s a better model for evolving a programming language that has to support billions of lines of production code.
PS: If you want to go deeper - see you in person 👋
If this edition resonated and you’d like to continue the conversation offline, I’ll be talking about JVM in the Age of AI at a few upcoming events:
🇸🇪 JVM in the Age of AI: 2026 Edition @ JFokus 2026
I’ll be running a 90-minute Deep Dive session focused on what really needs to happen inside the JVM for it to remain a serious platform for AI and ML - hardware, Valhalla, Babylon, GPU offloading, TornadoVM, Llama3.java, and the 2026 perspective.
I’ll also be doing two polish JUGs during one week, talking about “Agentic Systems beyond localhost” with more room for questions (and for me to fill 😁)
21.01 - Wrocław Java User Group (With One-and-Only Jarek Pałka)
22.01 - Kielce Java User Group
Love the meetup, as they give a bit more freedom to speaker - and I always like to adapt to what you want to dig into.
If you’re around: come say hi, argue, disagree, or just nerd out about the JVM.
Before we close this edition, one sad piece of news.
Scott Adams has passed away.
I know - he was a controversial figure, especially in his later years, and not everyone agreed with his views. But there’s no denying one thing: his humor was singular, sharp in a way that only engineers and office survivors truly appreciate.
For years, Dilbert perfectly captured the absurdities of corporate life, technical organizations, and management theater — and it did so with a precision that made it a recurring guest in this newsletter. Many of us laughed because it was funny; many of us winced because it was accurate.
Whatever one thinks about Scott Adams the person, Scott Adams the cartoonist shaped a generation of engineers and gave us a shared language to talk about dysfunction, nonsense, and the quiet heroism of surviving another meeting that should have been an email.
Rest in peace, Scott.












