Java reaches for the GPU, Records fall off the Cliff, and Gatherers prove that Data Structures still matter - JVM Weekly vol. 162
Babylon finally gets its JEP, Brian Goetz opens a new chapter of data-oriented programming, and two gems from the ecosystem.
Recent weeks are exceptionally juicy when it comes to “fundamentals.” We have two really big moves on the OpenJDK mailing lists - one I’ve been waiting for a long time, and another that could change the way we think about classes in Java. Plus two items from the ecosystem that are begging for attention. Let’s go.
1. Code Reflection Gets Its JEP - and Babylon Finally Speaks to GPUs in Java
Some of you probably remember that I’ve been repeating like a mantra for a while now that Project Babylon is one of the most exciting endeavors in the OpenJDK ecosystem. And at last we have something concrete to talk about - JEP draft: Code Reflection (Incubator), owned by Paul Sandoz, with reviewers including Adam Sotona , Juan Fumero , and Maurizio Cimadamore . The very composition of such a trio should give you pause - we’re connecting the worlds of core Java, hardware accelerators, and the Foreign Function API.
Let’s start with what Code Reflection actually is, because I know not everyone follows Babylon as obsessively as I do. Traditional reflection in Java let us inspect classes, fields, and methods — but not the code inside those methods. Let me repeat: from Java 1.1 to today, reflection lets you find out that the method add takes two ints and returns an int, but doesn’t let you look inside and see what that method does. Code Reflection changes this situation fundamentally.
The mechanism works like this: we annotate a method or lambda expression with @Reflect (formerly @CodeReflection - the name refactoring is fairly recent). The javac compiler translates the internal code model into a standard code model - an immutable tree of code elements - and saves it in a separate class file associated with the original class. At runtime, we can retrieve this model via the API: Op.ofMethod(method) for methods, Op.ofLambda(instance) for lambdas. Sounds simple? Conceptually it is - but the consequences are enormous.
Because what can you do with a code model? You can traverse it, build one from scratch, transform it - and translate it into something entirely different. The JEP explicitly talks about “foreign programming models,” and GPU is the flagship example here. Instead of embedding CUDA C as strings in Java code (yes, people did this), you can write a normal Java lambda, annotate it with @Reflect, and let a library translate it into GPU code:
dispatchKernel((@Reflect IntConsumer) i -> {
byte r = rgbImage[i * 3 + 0];
byte g = rgbImage[i * 3 + 1];
byte b = rgbImage[i * 3 + 2];
grayImage[i] = gray(r, g, b);
});This isn’t pseudocode but a real vision of how a GPU library could work, extracting the code model from a lambda and generating the appropriate kernel from it. Crucially, the JEP explicitly emphasizes that it is not a goal for the JVM itself to run code on GPUs. This is a primitive that gives libraries the tools to do so. Just as the FFM API knows nothing about GPUs but lets you talk to them. The philosophy is the same: the platform provides building blocks, the ecosystem builds solutions from them.
And that leads us to the second link in this section, which is a natural complement to the JEP. Juan Fumero (from the JEP’s review group - coincidence? I think not) published an extensive article on optimizing matrix multiplication on GPUs from Java using Babylon and HAT (Heterogeneous Accelerator Toolkit). The article is technical, but it’s worth pushing through - because it shows what the practical use of Code Reflection looks like on the example of one of the most important algorithms of the AI era.
HAT is a framework built on Babylon that offers several key abstractions: an ND-Range API for defining GPU thread configurations (similar to OpenCL or CUDA), a Kernel-Context Layer for expressing per-thread code, a Compute-Context Layer for orchestrating multiple kernels, and an Interface Mapper for efficient memory mapping between CPU and GPU (based on Panama FFM). The programmer writes a Java method annotated with @Reflect, HAT extracts the code model from it, performs transformations, and generates the appropriate CUDA or OpenCL program.
And now for the numbers, because they speak louder than a thousand paragraphs. Fumero performs 1024×1024 matrix multiplication on an NVIDIA A10 GPU, step by step optimizing the kernel: from naive 1D, through 2D, coalesced memory access, loop tiling with shared memory, register tiling, vectorization, all the way to FP16. The result? A jump from ~7 GFLOP/s on CPU (Java parallel streams) to ~14 TFLOP/s on GPU - close to native cuBLAS. Yes, you read that right: code written in Java, translated to CUDA by Babylon+HAT, achieves performance comparable to NVIDIA’s native library.
Not bad... not bad at all.
This changes the perspective. Project Sumatra in the 2010s tried to hide the GPU from the programmer - and failed, because GPUs are a fundamentally different execution model (”Single instruction, multiple threads”, separate memory, no polymorphism). HAT approaches this completely differently: it doesn’t pretend GPUs don’t exist but gives the Java developer tools for consciously programming for that architecture. And thanks to Code Reflection, it does so without leaving Java.
It’s worth adding that GPUs are just one of the use cases. The JEP explicitly mentions automatic differentiation, translating Java to SQL, and passing translated code to native ML runtimes. In an era where every framework wants to have an “AI story,” Java is getting the foundation on which that story can be told without needing to escape to Python at every tu
PS: The topic of GPUs in Java is particularly close to my heart, because just a week ago I had the chance to talk about it live - at Jfokus in Stockholm I presented JVM in the Age of AI, where a significant part of the talk was dedicated to how Java is reaching for the GPU and why Babylon changes the rules of the game. The repo I linked has an set of different technologies already set-up, so you can check them by yourself.
2. Carrier Classes - Brian Goetz Beyond Records, or the End of the “Cliff”
Brian Goetz published on amber-spec-experts one of those posts where it’s worth sitting down with a coffee, because it changes the map of where data-oriented programming in Java is heading. Title: Data Oriented Programming, Beyond Records - and yes, it’s exactly as big as it sounds.
The problem is well known to anyone who has ever started with a record and then realized they needed even one mutable field, or an internal representation different from the API, or inheritance. The result? A “cliff” - you fall off the full set of record benefits (automatic constructor, accessors, equals/hashCode/toString, destructuring in pattern matching) straight down to a bare class where you have to write all of it manually.
Visualization of your miserable experience.
Goetz explicitly calls this a problem and finally proposes a solution - carrier classes.
A carrier class is a regular class declared with a state description, exactly like a record — but without record restrictions. It doesn’t have to be final. Fields don’t have to be final. It can inherit from something other than Record. The only commitment: the state description is a complete, canonical, nominal description of the object’s state — meaning if you extract those components and reconstruct the object, you get an “equivalent” instance. In return, the language gives you almost everything it gives records: deconstruction in pattern matching, sensible default Object methods, and (crucially!) support for the upcoming reconstruction with with expressions (JEP 468).
What excites me most about this proposal is the approach to bridging between the API and internal representation. Goetz introduces the concept of component fields — fields marked with the component modifier that tell the language: “this field is a direct representation of a component from my state description.” This lets the compiler generate an accessor — exactly like in records.
And when there isn’t a direct mapping? Goetz illustrates this with an elegant example of a class with state description (int x, int y, Optional<String> s) that internally stores s as a nullable String. In a record, this is impossible — the state description is the representation. In a carrier class, you write only the few lines that handle the difference: conversion in the constructor and in the accessor. The rest is derived.
This is actually the key motif of the entire document — the amount of help from the language should be proportional to the deviation from the record ideal. The closer to a record, the less code you write. The further away — the more, but only as much as the actual differences require. No cliff. Goetz literally writes:
It would be nice if the author of this class only had to write the code that was different from what we could derive for a record.
Compact constructors also get generalized. In records, the compact constructor frees you from assigning parameters to fields. In carrier classes, it frees you from initializing component fields, leaving you responsible only for fields that don’t map directly to components. In the extreme case, when all components have their component fields and you don’t need any additional logic — you can skip the compact constructor entirely. The end result looks like this:
class Point(int x, int y) {
private /* mutable */ component int x;
private /* mutable */ component int y;
// derived: compact constructor, accessors, equals, hashCode, toString
// supports: pattern matching, with-expressions
}For someone who has been watching for years as Amber step by step builds a coherent vision of data-oriented programming, this document is a breakthrough moment. Records, sealed classes, and pattern matching were the first story arc. Carrier classes, with their generalized state description, compact constructors, and component fields, open the second arc - and finally provide the answer to the question that has been hanging in the air since JDK 14: “What if my class is almost a record?”
But man does not live by the JDK alone - let’s move on to the ecosystem.
3. Gatherers in Practice - or Why Data Structures Still Matter
Gatherers are one of those things that “finally” made it into the standard library (with finalization in JDK 24), but their practical applications are only now crystallizing in developers’ heads. Grzegorz Piwowarek, current maintainer of Vavr, decided to change that, presenting a case study of implementing a Gatherer that returns the last N elements of a stream - like limit() from the other end.
And here’s the catch, because his article Implementing Efficient Last Stream Elements Gatherer in Java isn’t a dry tutorial on “how to implement a Gatherer.” It’s a story about optimization, profiling, and choosing the right data structure - guided step by step, with JMH benchmarks and flamegraphs from async-profiler. Grzegorz starts with a naive implementation on ArrayList, moves through buffering, ArrayDeque, circular buffer, all the way to power-of-two bit masking - and at each stage shows why the previous version was slow.
The results speak for themselves: from ~1.3 ops/s in the first version (where ArrayList.removeFirst() was copying the entire array on every element!) to over 100 ops/s in the final version with a circular buffer sized to a power of two. Nearly a hundredfold jump. And the climactic moment is dropping down to the assembly level, where Piwowarek shows that the modulo operation (sdiv + msub on ARM) costs 7 to 21 cycles, while a bitwise AND after aligning the size to a power of two - 1–2 cycles. That kind of detour through Integer.highestOneBit() and a bit mask is something that wouldn’t occur to you if you were coding “by eye” - but it makes a huge difference.
So yes - the article is ostensibly about Gatherers, but really about the fact that even in 2026, when we have Virtual Threads, Structured Concurrency, and AI-generated code, the ability to choose the right data structure and profile at the assembly level is still what separates “it works” from “it works well.” If you haven’t played with the Gatherers API yet - this is a great entry point. And if you have but haven’t done benchmarks - time to catch up.
4. True Gem: Java UI in 2026 - The Complete Guide
To finish, something that is a little gem in its own right - Robin Tegg, a developer from CreateFuture, compiled a complete overview of UI frameworks available in the Java ecosystem in 2026, divided into four platforms: desktop, web, mobile, and terminal.
Why is this a gem? Because nobody normally does compilations like this - and if they do, they’re either superficial or immediately outdated. Tegg covered 25+ frameworks (yes, really!), from obvious choices like JavaFX, Vaadin, and Compose Desktop, through Gluon Mobile and Codename One on mobile, all the way to terminals with JLine. Each framework gets a summary, a code example, and links to documentation. There’s also a “quick reference guide” organized by scenarios - looking for full-stack Java without JavaScript? Vaadin or Wicket. Cross-platform mobile? Codename One. And so on.
The article’s conclusion is one that’s hard to disagree with: Java UI development is alive, modern, and production-ready on every platform. React and the JavaScript ecosystem dominate in mindshare, but Java frameworks power critical applications in the largest enterprises, banks, and government institutions, serving hundreds of millions of users. Add to that the fact that most of the described frameworks are open source — so if you’re looking for something to contribute to or simply want to know what your options are — this is your starting point.
PS: I know this issue is a bit heavier than usual - but both the JEP on Code Reflection and Goetz’s post on carrier classes are documents worth reading in full. If you only have time for one, go with Goetz - carrier classes are about the everyday code we all write. The Babylon JEP is wonderful, but if you’re not programming GPUs or writing frameworks, it’s more of a medium-term perspective (though a fascinating one).
And let’s be honest - who among us hasn’t had an “almost-record” in their life? 😉








Hi - thank you for the article. Quick question re: #4 - do you have the link to the article with the Java UI platforms?
Found it: https://robintegg.com/2026/02/08/java-ui-in-2026-the-complete-guide.html