OlegMikheev.com

Quick Java 25 Benchmarking: AOT Cache and Compact Headers

TL;DR

The Goal: Stress-testing a vibe-coded dummy Spring Boot app on Java 25 to see if two new performance features are worth the switch.
The Results: AOT Cache is the star, slashing boot times by ~30%. Compact Headers provided a modest ~3.4% heap saving.
The Catch: Marketing says “33% header reduction,” but real-world objects have payloads and padding that dilute those gains.

Java 25 (LTS) arrived in September 2025, helping Java become a bit more competitive in the serverless world. While Java 26 is already out (since March, making AOT caches available across different GCs), I’m sticking with Java 25 for this test because LTS is what’s (going to be) used in the enterprise world.

I wanted to see how two notable performance features – AOT Cache and Compact Object Headers -handle actual bloat. Thanks to GenAI advances it’s now pretty straight-forward to vibe-code a benchmark suite, which I did; it’s available at https://github.com/bornmw/java25-bench along with all the results and the prompt used to create the suite.

The Setup

I ran this on my laptop, generating 500,000 or 2,000,000 instances of a RetentionBean with 30 long fields.

Producing a good benchmarking suite is hard. You have to make sure that processes end up running on performance cores, that runs are not impacted by thermal throttling, that the cord is not unplugged between the runs. The prompt that was used to generate the suite hopefully covers the main aspects.

Mathematically, a standard 12-byte header plus 240 bytes of payload equals 252 bytes, which the JVM pads to a 256-byte object. Theoretically, Compact Headers (JEP 519) shrink that header to 8 bytes, making the object 248 bytes. That should save 8 bytes per object.

The Results

Again, full results are available at https://github.com/bornmw/java25-bench. The metrics on 2 million beans are:

  • Baseline: ~2.9s boot time | 520 MB Heap
  • AOT Cache: ~2.1s boot time | 519 MB Heap (Speedup: ~28%)
  • AOT + Compact Headers: ~2.1s boot time | 502 MB Heap (Savings: ~3.4%)

The AOT Cache is the real winner here. By mapping compiled classes and metadata, it bypasses the heavy initialization tax for a ~30% faster boot.

However, the Compact Headers only yielded a 3.4% heap reduction.

The “33% Savings” Myth

Why didn’t my heap shrink by 33%? Gemini suggests that:

  1. Field Dominance: The “33%” only applies to the header (12 bytes to 8 bytes). If your object is a 256-byte giant, saving 4 or 8 bytes is statistically tiny.
  2. The Padding Trap: The JVM insists on 8-byte memory alignment. If you shrink an object from 24 bytes to 20, the JVM just stuffs it with 4 bytes of empty padding anyway.

Compact Headers are a killer feature for apps that create millions of tiny, empty objects. But for heavy database DTOs? Don’t expect a miracle. The AOT Cache, however, makes Java a legitimate contender for the cold-start-heavy world of Serverless.

Full code and metrics: https://github.com/bornmw/java25-bench

P.S.

I’m not paying for AI subscriptions at home when I get the premium stuff for free at work. Why buy the cow when the office gives you the milk? Besides, it’s more fun to see what you can squeeze out of free tools. I used Gemini Pro to draft the architectural prompts and fed them into the “less smart” MiniMax M2.5 available in OpenCode. The combo is surprisingly productive.


Posted

in

by

Tags:

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *