The ‘No-Boilerplate’ Era Begins – amritpandey.io

6 min read Original article ↗

In this article, I’ve outlined the most important syntax and API updates introduced in JDK 25.

JDK 25 is a Long-Term Support (LTS) release, meaning it will be the standard for production environments for the next 4 years to come, succeeding JDK 21.

I’ve also written an article summarizing JDK 24 features which was released just before JDK 25 introducing massive performance improvements and new language features, check it out here.

⚠️ Some code examples provided here may still be preview features (like Stable Values). To run them, you might need to enable preview mode using the --enable-preview flag.

Here is a guide for symbols used in the articles:

🔗 — Link to JEP

⚠️ — Pay attention

❌ — Limitation

👉 — Reasoning

1. Module Import Declarations

Module Import declarations have moved from the preview in JDK 24 to being finalized in JDK 25.

❌ Until now, if you wanted to use List, Map, Stream, and Function, you had to clutter your file with multiple import statements or use wildcard imports that could lead to ambiguity.

Java

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.function.Function;

public class MyClass { ... }

👉 With JDK 25, you can simply import the java.base module, which automatically imports all the commonly used packages (java.util, java.io, java.math, etc.) on demand.

Java

import module java.base; // Module Import

void main() {
    List<String> list = List.of("Java", "25");
    println(list);
}

This dramatically reduces the “ceremony” at the top of your Java files.

🔗 Module Import Declarations are finalized in JEP 511.

2. Compact Source Files & Instance Main Methods

The removal of boilerplate initiative is finally complete now! 🥳

You can now write simple Java programs without the verbose class declarations and public static void main.

❌ Previously, even a simple script required understanding classes, static methods, and array arguments.

Java

public class HelloWorld { 
    public static void main(String[] args) { 
        System.out.println("Hello, World"); 
    } 
}

👉 With JDK 25, the syntax is streamlined. The implicit class feature is now standard, and you can use void main() as a valid entry point.

Java

void main() {
    println("Hello, World");
}

Methods like println are now available via the new java.lang.IO class, which is implicitly imported in compact source files.

🔗 Compact Source Files and Instance Main Methods are finalized in JEP 512.

3. Flexible Constructor Bodies

This JEP has also moved from preview to finalized in the JDK 25.

👉 Developers can finally execute logic in a constructor before calling super().

❌ In previous JDK versions, the call to super() had to be the very first statement in the constructor.

This forced developers to use awkward static helper methods if they needed to validate arguments or transform data before passing it to the parent constructor.

Now, the constructor body allows a Prologue (statements before super) and an Epilogue (statements after super).

Java

public class Employee extends Person {
    public Employee(String name) {
        // Prologue: Validate BEFORE super()
        if (name == null) throw new IllegalArgumentException("Name cannot be null");
        
        super(name); // Initialize Parent
        
        // Epilogue
        System.out.println("Employee created");
    }
}

⚠️ You still cannot access this (the instance being created) in the prologue, but you can perform calculations and validations freely.

🔗 Flexible Constructor Bodies are finalized in JEP 513.

4. Scoped Values

Scoped Values are now a permanent feature, officially replacing ThreadLocal as the modern standard for sharing data safely across threads.

👉 Scoped Values solve the critical issues of ThreadLocal:

  1. Immutability: Data is immutable, preventing accidental side-effects.
  2. Bounded Lifetime: The value is automatically cleaned up when the scope ends, preventing memory leaks.
  3. Efficiency: Designed to work seamlessly with Virtual Threads, allowing millions of threads to share data without memory bloat.

Java

static final ScopedValue<String> REQUEST_ID = ScopedValue.newInstance();

void main() {
    ScopedValue.where(REQUEST_ID, "REQ-12345").run(() -> {
        processRequest();
    });
}

void processRequest() {
    // Access the value anywhere in the call stack
    System.out.println("Processing: " + REQUEST_ID.get());
}

🔗 Scoped Values are finalized in JEP 506.

5. Compact Object Headers

This is a massive under-the-hood performance upgrade.

❌ Historically, every Java object carried a header of 96 to 128 bits (12-16 bytes) to store metadata like hash codes, GC state, and locking information.

This overhead added up significantly in memory-intensive applications.

👉 With JDK 25, the object header has been compressed to just 64 bits (8 bytes).

This change:

  • Reduces heap memory footprint by 10% to 20% automatically.
  • Improves CPU cache locality.

You don’t need to change a single line of code to benefit from this!

🔗 Compact Object Headers are finalized in JEP 519.

6. Stable Values

Talking about some preview features, comes the Stable Values API that bridges the gap between final fields and mutable variables.

❌ Currently, final fields must be initialized when the object is constructed.

If you want lazy initialization, you have to use mutable fields, which the JVM cannot optimize as effectively as constants.

👉 StableValue allows you to have a field that is set once (lazily) and then treated as a constant by the JVM for superior performance.

Java

import java.lang.StableValue;

public class Config {
    private static final StableValue<String> CONFIG_DATA = StableValue.of();

    public static String getData() {
        // Initializes lazily on first use
        return CONFIG_DATA.orElseSet(() -> loadHeavyData());
    }
}

🔗 Stable Values are introduced in JEP 502.

7. AOT Enhancements & Profiling

JDK 25 makes huge strides in Ahead-of-Time (AOT) compilation to fix Java’s slow startup reputation, which was already discussed in JDK 24.

JDK 25 introduces two key JEPs to improve the AOT cache:

  • JEP 514 (Ergonomics): Simplifies the command-line commands to create AOT caches.
  • JEP 515 (Method Profiling): Allows the AOT cache to store method profiles from a training run.

This means when your application restarts, the JIT compiler already knows which methods are hot and how to optimize them, leading to near-instant peak performance.

Other Features

There are other notable features like Generational Shenandoah GC (JEP 521) and the removal of the 32-bit x86 port (JEP 503).

Apart from these, the two more important features that are still in preview carried from JDK 24 are:

You can read about these two features in my JDK 24 article here.

Subscribe to my newsletter today!