It’s been a month since the release of the new Long-Term Support (LTS) version of Java. This release brings a host of new language and compiler features, but in this post, I’ll focus on several that stand out as truly transformative for developers and production systems alike.
1. Memory Efficiency: Compact Object Headers
One of the most exciting improvements in JDK 25 is the introduction of Compact Object Headers, now a finalized feature via JEP 519. This enhancement builds upon JEP 450, where Compact Object Headers were first introduced as an experimental feature.
In short, this feature compresses the Java object header from 96 bits down to 64 bits, dramatically reducing heap memory usage for existing applications.
How It Works?
Previously, the Mark Word contained several unused bits — 27 in total. Out of 96 bits, only 69 were actually needed. Java 25 reorganizes and optimizes the structure to fit into a compact 64-bit format.
The new header structure now directly stores:
- A compressed class pointer (reduced from 32 bits to 22 bits)
- A 31-bit identity hash code (unchanged)
- 4 bits reserved for Project Valhalla
- 4 bits for object age (unchanged)
- 1 bit for the “Self Forwarded Tag”
- 2 tag bits (unchanged)
Performance results
Early testing shows substantial performance and memory gains:
- Up to 22% less heap usage
- 8% faster execution times
- Amazon reports up to 30% CPU reduction across hundreds of production services
- 15% fewer garbage collection cycles for both G1 and Parallel collectors
- JSON parsing benchmarks show 10% faster execution
- Throughput overhead stays below 5% even in worst-case scenarios
These improvements make Compact Object Headers a massive win for both cloud and on-premise deployments.
2. Faster Startup: Project Leyden Integration
One of Java’s longest-standing challenges has been slow startup and warmup times. JDK 25 makes a significant leap forward in this area through enhancements connected to Project Leyden.
Project Leyden’s main goal is to improve application startup and warmup performance by introducing profile-guided optimizations and ahead-of-time (AOT) capabilities at the JVM level.
How It Works?
During a typical application run, the JVM collects profiling data — information about which methods are invoked most frequently or consume the most CPU. Traditionally, this profiling happens at runtime during the first execution phase.
With Java 25 and Leyden’s enhancements, these profiles can now be saved during a “training run” and reused later. In subsequent executions, the JVM can skip the early profiling stage and begin optimized compilation immediately, resulting in much faster startup.
Benchmark Example
Recent benchmark results running over the Quarkus framework show measurable improvements in startup times and warmup efficiency.
Source: OpenJDK Project Leyden README
These enhancements align well with modern deployment patterns — especially for containerized or serverless environments — where rapid startup is critical.
3. Stable Values: Safe, On-Demand Initialization
JDK 25 introduces a powerful new construct called Stable Values — a mechanism for defining values that are initialized exactly once and then remain immutable throughout the lifetime of the application.
This feature standardizes lazy initialization patterns that developers often implement manually and allows the JVM to optimize these values in the same way as final fields through constant folding and other compiler optimizations.
A StableValue behaves like a container that holds an object — the so-called “content.”
- It starts uninitialized.
- It is initialized once, when first accessed.
- After initialization, it becomes immutable and thread-safe.
Here’s a simple example:
public class ConfigService {
// Create a StableValue container for caching configuration
private final StableValue<AppConfig> config = StableValue.of();
public AppConfig getConfig() {
// Initialize lazily on first access
return config.orElseSet(this::loadConfig);
}
private AppConfig loadConfig() {
System.out.println("Loading configuration from file...");
// Simulate reading configuration data
return new AppConfig("prod", 20, true);
}
public void printConfig() {
AppConfig cfg = getConfig();
System.out.printf("Mode: %s | Workers: %d | Cache: %s%n",
cfg.mode(), cfg.workerCount(), cfg.cacheEnabled());
}
// Sample record representing configuration structure
private record AppConfig(String mode, int workerCount, boolean cacheEnabled) {}
}
What happens here:
- The first call to getConfig() triggers loadConfig() and stores the result in the stable value.
- All subsequent accesses return the same cached instance without reloading.
- Even in multithreaded contexts, orElseSet() ensures initialization happens exactly once.
This pattern eliminates the need for manually synchronized lazy initialization blocks. Once populated, the stable value behaves just like a final constant, and the JVM can optimize it accordingly.
JDK 25 also includes related classes such as:
-
StableList – for lists initialized once
-
StableMap – for single-initialization maps
-
StableFunction – for functions that compute a result once, then cache it immutably
Conclusion
Java 25 marks a pivotal moment in the evolution of the Java platform.
With Compact Object Headers, Project Leyden startup optimizations, and Stable Values, the focus is clear: greater performance, efficiency, and modernized application behavior for cloud-native environments.
If you’re maintaining long-lived services or performance-critical applications, upgrading to Java 25 should definitely be on your roadmap.


