4 min read14 hours ago
–
Press enter or click to view image in full size
I like messing with new Java features. This time I tried combining value classes (JEP 401) with Protocol Buffers, not because I needed to, but because protobuf messages are a perfect playground for a lot of small, repeated objects and I wanted to see what happens if those objects could be value classes. JEP 401 is still preview/experimental, and the Valhalla early-access builds let you try it out.
This post is a short write-up of an experiment I did for fun: I wrote a **protoc** plugin that generates value-class-based protobuf messages. The repo/README with quick details is here.
*TL;DR: *value classes can improve memory layout…
4 min read14 hours ago
–
Press enter or click to view image in full size
I like messing with new Java features. This time I tried combining value classes (JEP 401) with Protocol Buffers, not because I needed to, but because protobuf messages are a perfect playground for a lot of small, repeated objects and I wanted to see what happens if those objects could be value classes. JEP 401 is still preview/experimental, and the Valhalla early-access builds let you try it out.
This post is a short write-up of an experiment I did for fun: I wrote a **protoc** plugin that generates value-class-based protobuf messages. The repo/README with quick details is here.
TL;DR: value classes can improve memory layout, parsing and tail latencies in some parsing-heavy workloads, but they add tradeoffs (sorting/copying cost).*** This is a toy/experiment: ***don’t drop it in production yet.
What is a value class? (super short )
I’ll keep this short: you’ll probably drop in the JEP or the Valhalla EA docs for the deep dive.
A value class is a class whose instances don’t have identity.
Think: int or LocalDate treated as a first-class language value, no object header, no == identity, no synchronization, just the fields that define the value.
The feature is currently preview/experimental and available in Valhalla early-access builds. (I’ll add two pictures from the JEP page here showing LocalDate[] standard vs Valhalla layout.)
Press enter or click to view image in full size
Standard array of java objects, the content of the array is a referece and data is store somewhere else.
Why this matters practically: without object headers and with flattened layouts, arrays and containers can be denser and more cache-friendly. That can change allocation behavior and tail latency in interesting ways.
JEP 401 Optimisation: inside the array all value classes can be stored continuously
Why frameworks like protobuf could benefit
**Frameworks that create lots of short-lived or many small immutable objects. **Think message parsing in GRPC servers, streaming pipelines, or any deserialization-heavy code can benefit from:
- Less per-object overhead: flattened values remove headers/refs in some cases.
- Better locality: repeated fields (arrays/lists) can be stored more compactly.
- Lower allocation pressure: scalarization and flattening can reduce GC churn in hot paths.
These are exactly the areas protobuf and GRPC deal with parsing many messages, streaming large numbers of small objects, or holding arrays of domain objects. So it’s a natural experiment to see how value classes affect protobuf generation and runtime behavior.
What I tried (the hack)
Short story: protobuf-java normally generates message classes that extend com.google.protobuf.GeneratedMessage. Those generated classes are regular identity-bearing objects, so they can’t be value classes. To see what a value-class-based protobuf might look like, I wrote a protoc plugin that:
- Generates an immutable value class for each message type (declared with the
valuemodifier). - Generates a small mutable builder class for ease of construction.
- Implements direct wire-format parsing/serialization methods on the value class (
parseFrom,toByteArray) rather than relying on the standard generated-message runtime. - Keeps the generated API minimal and intentionally not a drop-in replacement full protobuf runtime compatibility is out of scope.
// generated value class (concept)public value class Person { private final String name; private final int age; .... public static Person parseFrom(byte[] data) { ... } // direct wire parsing public byte[] toByteArray() { ... } // direct wire serialization public Person withAge(int newAge) { ... } // immutable update}
Results — the short, honest summary
I ran microbenchmarks (parsing, field access, array operations, sorting) using an early-access JDK build and compared generated value-class code vs standard protobuf generated code. The short summary of what I saw:
- Parsing: slightly faster in many cases (3–8% typical improvements), especially for large arrays. Tail latencies tended to be tighter.
- Field access / repeated fields: noticeably faster on large arrays due to reduced indirection and better locality (≈15% in some large-array cases).
- Sorting / reordering: significantly worse. Because value classes are identity-free and immutable, some operations involve copying the whole value; sorting large arrays of messages became 2×–3× slower in my tests.
- Net effect depends on workload: parsing-heavy, mostly-read, or streaming workloads gained the most; workloads that do lots of in-place mutation, sorting or frequent copying could regress.
More details can be found here.
Limitations & scope
Some important caveats, this is a research toy:
- Not production-ready: missing many protobuf features, not compatible with the standard protobuf runtime API, and requires
--enable-previewand EA JDKs to run. - Value-class semantics aren’t magic: flattening and scalarization are optimizations the JVM may or may not apply depending on JIT heuristics and platform; results can change with JDK build, CPU architecture, and workload.
- Sorting and mutation patterns: workloads that require a lot of copying/reordering can be slower with this approach.
- Interoperability: generated classes are not drop-in with protobuf runtime; full compatibility is out of scope for this experiment.
So… should you use this in prod?
Nope. This is a fun experiment. It illustrates where value classes could help frameworks that create many small immutable values, but it’s not a production-ready library. Performance numbers are machine and workload-dependent, they will vary by JDK build, CPU, and whether the JVM actually flattens or scalarizes the values in your workload. Try it in a controlled experiment if you’re curious, but don’t ship it to users yet.
Where to look / try it yourself
- Plugin repo (quick start, benchmarks, limitations): GitHub
- JEP 401 and Project Valhalla: background on value classes and EA builds.