Skip to main content
Java 25 (LTS)
  1. Posts/

Java 25 (LTS)

·871 words·5 mins·
Roman
Author
Roman
Photographer with MSci in Computer Science and a Home Lab obsession
Table of Contents

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
#

JEP 512

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.java

Only 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
#

JEP 511

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
#

JEP 513

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
#