Contents

Java 25 is comming

Summer hollidays, as it seems, for many Java developers would mean waiting for an official release (or at least for a fianl RC) of Java 25 which is planned to be generally available on 16th September this year. In preparation of this moment lets go through what going on

Note

You can go straight to javaalmanac.io Java 25 page for all the details, JEP links, API changes, release notes and vendors’ downloads.

This blog entry is just a summary and is intended for me just to clean up and gather thoughts. Feel free to move away from here and explore by your own!

Key changes

Out of all the JEP changes that go into Java 25 there are some of special importance to Java developers

Content Dev JEP
Remove the 32-bit x86 Port 😐 JEP 503
Scoped Values 🥰 JEP 506
Key Derivation Function API 😐 JEP 510
Module Import Declarations 🥰 JEP 511
Compact Source Files and Instance Main Methods 🥰 JEP 512
Flexible Constructor Bodies 🥰 JEP 513
Ahead-of-Time Command-Line Ergonomics 😀 JEP 514
Ahead-of-Time Method Profiling 😀 JEP 515
JFR Cooperative Sampling 🫣 JEP 518
Compact Object Headers 🥰 JEP 519
JFR Method Timing & Tracing 🫣 JEP 520
Generational Shenandoah 🥰 JEP 521

Legend

  • 🫣 - I don’t know much about JFR
  • 😀 -AOT is interesting
  • 🥰 - life will be easier
  • 😐 - I don’t think this will affect me

Schedule

Java 25 Schedule (click!)

Source: https://openjdk.org/projects/jdk/25/

Date Phase
2025-06-05 Rampdown Phase One (branch from main line)
2025-07-17 Rampdown Phase Two
2025-08-07 Initial Release Candidate
2025-08-21 Final Release Candidate
2025-09-16 General Availability

If anyone is interested in support from Oracle, they should pay close attention to the Roadmap published on https://www.oracle.com/java/technologies/java-se-support-roadmap.html page:

Oracle Java SE Support Roadmap

Installation (Ubuntu)

On Ubuntu you can apt install openjdk-25-jdk:

You can easily check where it was installed with dpkg -L openjdk-25-jdk:

To set java 25 as your default, you could

I checked my current java:

1
2
3
4
5
6
7
07:56:00 karma@nux ~  $ which java
/snap/openjdk/current/jdk/bin/java

07:58:05 karma@nux ~  $ java --version
openjdk 24.0.1 2025-04-15
OpenJDK Runtime Environment (build 24.0.1+9-snap)
OpenJDK 64-Bit Server VM (build 24.0.1+9-snap, mixed mode, sharing)

And decided to replace it completely with java 25 (when I removed the snap, newly installed openjdk-25 should become the default):

1
2
3
4
5
6
7
07:58:26 karma@nux ~  $ sudo snap remove openjdk
openjdk removed

07:58:39 karma@nux ~  $ java --version
openjdk 25-ea 2025-09-16
OpenJDK Runtime Environment (build 25-ea+16-Ubuntu-1)
OpenJDK 64-Bit Server VM (build 25-ea+16-Ubuntu-1, mixed mode, sharing)

And then I could run a Java25 application (with some features still in preview - see below) - Colors.java which is using implicitely declared classes:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
08:17:21 karma@nux ~/dev/java  $ java Colors.java
Colors.java:5: error: implicitly declared classes are a preview feature and are disabled by default.
void main(String[] args) {
^
  (use --enable-preview to enable implicitly declared classes)
1 error
error: compilation failed

08:17:24 karma@nux ~/dev/java  $ java --enable-preview Colors.java
Note: /home/karma/dev/java/Printer.java uses preview features of Java SE 25.
Note: Recompile with -Xlint:preview for details.
codecodecodecodecodecodecodecodecodecodecodecodecodecodecodecodecodecodecodecodecodecodecodecodecodecodecodecodecodecodecodecodecodecodecodecode
(...)

Preview features

I could see what exactly is causing the compliter requrie enabling preview versions by setting -Xlint:preview compiler (javac) argument. Using java runner, as above, only issues warnings from main script (Colors.java) and not from other classes (Printer.java).

IO and implicit classess are still in preview:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
08:13:30 karma@nux ~/dev/java  $ java  --enable-preview  -Xlint:preview Colors.java
Unrecognized option: -Xlint:preview
Error: Could not create the Java Virtual Machine.
Error: A fatal exception has occurred. Program will exit.

08:13:50 karma@nux ~/dev/java  $ javac --enable-preview -Xlint:preview Colors.java
error: --enable-preview must be used with either -source or --release

08:14:08 karma@nux ~/dev/java  $ javac --enable-preview --release 25 -Xlint:preview Colors.java
Colors.java:5: warning: [preview] implicitly declared classes are a preview feature and may be removed in a future release.
void main(String[] args) {
^
Colors.java:5: warning: [preview] IO is a preview API and may be removed in a future release.
void main(String[] args) {
^
(...)
./Printer.java:4: warning: [preview] print(Object) is a preview API and may be removed in a future release.
      print("\033[38;5;%sm%s\033[m".formatted(color, s));
      ^
5 warnings

08:14:22 karma@nux ~/dev/java  $

Completed features

Module import declarations

See: JEP 511 (and JEP 512 - compact source files) We can run small programs:

/en/posts/2025-06-29_java_25_is_comming/20250629171055.png

We can use JShell with automatically imported module java.base (today it is still under –enable-preview flag): /en/posts/2025-06-29_java_25_is_comming/20250629171854.png

When the programs get a bit bigger, we can:

  • import whole modules (and by transitive, open access, be able to use exposed classess/packages in them) - in particular, I can import module java.base which would significantly imp. And not only that - as this JEP is co-developed with JEP 512 I will be able to use them by default
  • use compact source file which does not require such imports.

So, instead of this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import java.util.Arrays;
import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
public class Compact {
 public static void main(String[] args) {

  var tillDate = LocalDate.parse(args[0]);
  var daysLeft = ChronoUnit.DAYS.between(LocalDate.now(), tillDate);
  System.out.printf("""
   Hello!
   Today is %s.
   Your args are: %s%n
   Days till %s: %s
   """, LocalDate.now(), Arrays.toString(args), tillDate, daysLeft);
 }
}

…I can now use this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import module java.base;

void main(String[] args) {
  var tillDate = LocalDate.parse(args[0]);
  var daysLeft = ChronoUnit.DAYS.between(LocalDate.now(), tillDate);
  IO.printf("""
   Hello!
   Today is %s.
   Your args are: %s%n
   Days till %s: %s
   """, LocalDate.now(), Arrays.toString(args), tillDate, daysLeft);
 }

Happy hacking!