This covers most of the major language changes from Java 21 (the last LTS) through Java 25.
Unnamed Variables & Patterns #
JEP 456 - Java 22
Use _ for variables and patterns that are intentionally unused. Clarifies intent and suppresses IDE warnings about unused bindings.
// Unused loop variable
for (Order _ : orders)
total++;
// Unused lambda parameter
map.forEach((_, value) -> process(value));
// Unused exception binding
try {
return Integer.parseInt(s);
} catch (NumberFormatException _) {
return 0;
}// Pattern matching - unnamed pattern variable
if (obj instanceof Point(int x, _)) {
System.out.println("x = " + x);
}
// Multiple patterns in one case when no variables are bound
switch (shape) {
case Circle _, Square _ -> System.out.println("basic shape");
}Compact Source Files & Instance Main Methods #
Removes boilerplate from small programs. The main method no longer needs public static void or String[] args. A class declaration can be omitted entirely, fields and methods become part of an implicitly declared class.
// Minimal "Hello, World!" - no class, no static, no args
void main() {
System.out.println("Hello, World!");
}// With a class declaration (still valid)
class Hello {
void main() {
System.out.println("Hello, World!");
}
// don't need this now
public static void main(String[] args) {
//If you choice to write out the full main,
//it would take priority of the simple main
}
}Launch Multi-File Source-Code Programs #
JEP 458 - Java 22
Run multi-file Java programs directly without compiling first. Extends the single-file launcher to automatically discover and compile referenced source files.
# Project layout
# Prog.java - contains main()
# Helper.java - referenced by Prog.java
# pkg/Util.java - in package pkg
# Run without compiling
java Prog.javaOnly the entry-point file needs to be named on the command line. The launcher resolves the rest from the source directory. Useful for scripts and quick prototypes that span more than one class.
Module Import Declarations #
Import all packages exported by a module in a single declaration, useful when prototyping.
Markdown Documentation Comments #
JEP 467 - Java 23
Write Javadoc using /// line comments with standard Markdown syntax instead of HTML block comments.
/// Returns the absolute value of the given number.
///
/// Equivalent to calling `Math.abs(n)`.
///
/// @param n the input value
/// @return the absolute value
public int abs(int n) { ... }Flexible Constructor Bodies #
Statements can now appear before super() or this() in a constructor. Useful for validating arguments or initializing fields before the superclass constructor runs.
// Before Java 25 - super()/this() had to be the first statement.
// If omitted, the compiler inserted super() automatically at the top.
// Either way, no code could run before the superclass was constructed.
class Employee extends Person {
Employee(int age) {
super(age); // must be first - runs with bad age, too late to stop it
if (age < 18 || age > 67)
throw new IllegalArgumentException("bad age: " + age);
}
}
// Java 25 - validate before calling super()
class Employee extends Person {
Employee(int age) {
if (age < 18 || age > 67)
throw new IllegalArgumentException("bad age: " + age);
super(age);
}
}Scoped Values #
JEP 506 - java.lang.ScopedValue
Share immutable data from a caller down to its callees (including child threads), for a bounded duration. A safer, more efficient alternative to ThreadLocal.
https://www.baeldung.com/java-20-scoped-values
Stream Gatherers #
JEP 485 - java.util.stream.Gatherers - Java 24
A new Stream::gather(Gatherer) intermediate operation for custom stream transformations. Fills the gap left by the fixed set of built-in operations.
Built-in gatherers in java.util.stream.Gatherers:
| Method | Description |
|---|---|
windowFixed(int windowSize) |
Groups elements into non-overlapping fixed-size lists. The last window may be smaller. |
windowSliding(int windowSize) |
Groups elements into overlapping windows, each shifting by one element. |
fold(Supplier initial, BiFunction folder) |
Accumulates all elements into a single output value. Like reduce but as an intermediate operation, so further operations can be chained after it. Only supports sequential, order-dependent reductions (no combiner). |
scan(Supplier initial, BiFunction scanner) |
Like fold but emits the running accumulated value after each element. |
mapConcurrent(int maxConcurrency, Function mapper) |
Maps each element concurrently using virtual threads, up to a max concurrency limit. Preserves order. |
// Fixed-size non-overlapping windows
Stream.of(1, 2, 3, 4, 5, 6, 7)
.gather(Gatherers.windowFixed(3))
.toList();
// [[1, 2, 3], [4, 5, 6], [7]]
// Sliding windows
Stream.of(1, 2, 3, 4)
.gather(Gatherers.windowSliding(2))
.toList();
// [[1, 2], [2, 3], [3, 4]]
// Running total with scan
Stream.of(1, 2, 3, 4)
.gather(Gatherers.scan(() -> 0, Integer::sum))
.toList();
// [1, 3, 6, 10]
// Fold to a single value (like reduce, but as an intermediate step)
Stream.of("a", "b", "c")
.gather(Gatherers.fold(() -> "", (acc, s) -> acc + s))
.toList();
// ["abc"]Gatherers compose with andThen:
stream.gather(gathererA.andThen(gathererB)).toList();Sources #
- openjdk.org: JDK 25
- JEP 512: Compact Source Files and Instance Main Methods
- JEP 513: Flexible Constructor Bodies
- JEP 506: Scoped Values
- JEP 511: Module Import Declarations
- JEP 456: Unnamed Variables & Patterns
- JEP 458: Launch Multi-File Source-Code Programs
- JEP 467: Markdown Documentation Comments
- JEP 484: Class-File API
- JEP 485: Stream Gatherers
- JEP 507: Primitive Types in Patterns (Third Preview)
- JEP 521: Generational Shenandoah
- JEP 519: Compact Object Headers (Experimental)
- docs.oracle.com: Java 25 API
- All New Java Language Features Since Java 21 | Youtube