Sunday, September 21, 2025

JDK-25: The Next Big Thing

Not exactly sure why but JDK-25 is a long awaited release. Probably because it is the next LTS (or whatever it means these days), or probably because it establishes a new baseline where there is no place for SecurityManager anymore. In any case, let us talk about everything that JDK-25 bundles in, starting with the stable features first.

  • JEP-506: Scoped Values: introduces scoped values, which enable a method to share immutable data both with its callees within a thread, and with child threads. Scoped values are easier to reason about than thread-local variables. They also have lower space and time costs, especially when used together with virtual threads and structured concurrency.

    Since the API is final now, let us take a look at it closely.

       private static final ScopedValue<Object> CONTEXT = ScopedValue.newInstance();
    
       executor.submit(() -> ScopedValue.where(CONTEXT, new Object()).run(() -> {
           final Object context = CONTEXT.get();
           // Use 'context'
        }));
      

    In simple terms, you can think of scoped values as an immutable ThreadLocals, however their true power kicks in with structured concurrency, which sadly is still in preview in JDK-25.

  • JEP-511: Module Import Declarations: enhances the Java programming language with the ability to succinctly import all of the packages exported by a module. This simplifies the reuse of modular libraries, but does not require the importing code to be in a module itself.

    This is pretty useful and simple enhancement that helps with imports explosion, for example:

      import module jdk.jfr;
     
  • JEP-512: Compact Source Files and Instance Main Methods: evolves the Java programming language so that beginners can write their first programs without needing to understand language features designed for large programs. Far from using a separate dialect of the language, beginners can write streamlined declarations for single-class programs and then seamlessly expand their programs to use more advanced features as their skills grow. Experienced developers can likewise enjoy writing small programs succinctly, without the need for constructs intended for programming in the large.

    It is now possible to omit some boilerplate when using the language:

      void main() {
          IO.println("Hello, World!");
      }
      
  • JEP-510: Key Derivation Function API: introduces an API for Key Derivation Functions (KDFs), which are cryptographic algorithms for deriving additional keys from a secret key and other data.

  • JEP-503: Remove the 32-bit x86 Port: removes the source code and build support for the 32-bit x86 port. This port was deprecated for removal in JDK 24.

  • JEP-514: Ahead-of-Time Command-Line Ergonomics: makes it easier to create ahead-of-time caches, which accelerate the startup of Java applications, by simplifying the commands required for common use cases.

    This is really nice improvement over two-step workflow in JDK 24 (please notice a new -XX:AOTCacheOutput command line flag), only one step is now required:

    $ java -XX:AOTCacheOutput=app.aot -cp app.jar com.example.App ...

    As a convenience, when operating in this way the JVM creates a temporary file for the AOT configuration and deletes the file when finished. The command line to run the application stays the same:

    $ java -XX:AOTCache=app.aot -cp app.jar com.example.App ...

    A new environment variable, JDK_AOT_VM_OPTIONS, can be used to pass command-line options that apply specifically to cache creation (AOTMode=create), without affecting the training run (AOTMode=record). The syntax is the same as for the existing JAVA_TOOL_OPTIONS environment variable. This enables the one-step workflow to apply even in use cases where it might seem that two steps are necessary due to differences in the command-line options.

  • JEP-513: Flexible Constructor Bodies: in the body of a constructor, allows statements to appear before an explicit constructor invocation, i.e., super(...) or this(...). Such statements cannot reference the object under construction, but they can initialize its fields and perform other safe computations. This change allows many constructors to be expressed more naturally. It also allows fields to be initialized before they become visible to other code in the class, such as methods called from a superclass constructor, thereby improving safety.

    In my opinion, this feature would greatly improve the readability of the class initialization, let us take a look at the example:

         class ByteArrayInputStreamInputStream extends ByteArrayInputStream {
            public ByteArrayInputStreamInputStream(int size) {
                if (size <= 0) {
                    throw new IllegalArgumentException("The size has to be greater than 0");
                }
                super(new byte[size]);
            }
        }
        

    In pre-JDK-25, super(...) had to be the first statement in the constructor body and we would have no choice but to implement a function to validate the size and return new byte array (or throw an IllegalArgumentException exception).

  • JEP-519: Compact Object Headers: changes compact object headers from an experimental feature (introduced in JDK 24) to a product feature. To enable this feature pass command line option:

    $ java -XX:+UseCompactObjectHeaders
  • JEP-521: Generational Shenandoah: changes the generational mode of the Shenandoah garbage collector from an experimental feature (introduced in JDK 24) to a product feature. The generational mode could be enabled through command line flags:

    $ java -XX:+UseShenandoahGC -XX:ShenandoahGCMode=generational
  • JEP-515: Ahead-of-Time Method Profiling: improves warmup time by making method-execution profiles from a previous run of an application instantly available, when the HotSpot Java Virtual Machine starts. This will enable the JIT compiler to generate native code immediately upon application startup, rather than having to wait for profiles to be collected.

  • JEP-518: JFR Cooperative Sampling: improves the stability of the JDK Flight Recorder (JFR) when it asynchronously samples Java thread stacks. Achieves this by walking call stacks only at safepoints, while minimizing safepoint bias.

    There is a new event introduced, jdk.SafepointLatency, which records the time it takes for a thread to reach a safepoint, for example:

        $ java -XX:StartFlightRecording:jdk.SafepointLatency#enabled=true,filename=recording.jfr
        $ jfr print --events jdk.SafepointLatency recording.jfr
        
  • JEP-520: JFR Method Timing & Tracing: extends the JDK Flight Recorder (JFR) with facilities for method timing and tracing via bytecode instrumentation.

    There are two new JFR events introduced, jdk.MethodTiming and jdk.MethodTrace, they both accept a filter to select the methods to time and trace, couple of the examples below:

        $ java '-XX:StartFlightRecording:jdk.MethodTrace#filter=java.util.HashMap::resize,filename=recording.jfr' ...
        $ jfr print --events jdk.MethodTrace --stack-depth 20 recording.jfr
        
        $ java '-XX:StartFlightRecording:filename=fd.jfr,method-trace=java.io.FileDescriptor::<init>java.io.FileDescriptor::close' ..
        $ jfr view --cell-height 5 MethodTrace fd.jfr
        
        $ java '-XX:StartFlightRecording:method-timing=::<clinit>,filename=clinit.jfr' ...
        $ jfr view method-timing clinit.jfr
       

    A filter can also name an annotation. This causes all methods bearing the annotation, and all methods in all classes bearing the annotation, to be timed or traced.

        $ jcmd <pid> JFR.start method-timing=@jakarta.ws.rs.GET
        

    It is also possible to use JMX and the JFRs RemoteRecordingStream class to configure timing and tracing over the network.

From all perspectives, the list of the finalized features that made it into JDK-25 is rock solid. But the release also bundles a number of a new experimental and preview APIs, in addition to carried over ones.

Besides JEPs, JDK-25 has plenty of enhancements across the board, including bugfixes and changes in the behavior that may affect the existing applications.

Moving on to the standard library, JDK-25 delivers rather moderate changes, but quite handy nonetheless. Let us take a look at those.

With respect to security, there are a few changes worth mentioning:

Last but not least, few regressions slipped into JDK-25 at the last moment, please be aware of those:

My personal retrospective on the JDK-25 release, among many other things, highlights a significant progress of the Project Leyden and substantial investments into JDK Flight Recorder (JFR) tooling and instrumentation. Let us see what comes next, the lineup for JDK-26 already looks exciting.

I πŸ‡ΊπŸ‡¦ stand πŸ‡ΊπŸ‡¦ with πŸ‡ΊπŸ‡¦ Ukraine.

No comments: