Java 25 est la nouvelle version LTS après Java 21. Elle finalise plusieurs fonctionnalités restées en preview depuis Java 21-24 : Stream Gatherers, Structured Concurrency, Scoped Values, et les Primitive Types in Patterns. Elle introduit aussi des améliorations majeures pour le démarrage rapide des applications Cloud via l'AOT class loading.

Java 25 LTS sort en septembre 2025. C'est la version recommandée pour les nouvelles applications, avec le support commercial Oracle jusqu'en 2030+. Spring Boot 4.x devrait cibler Java 25 comme minimum.

1. Stream Gatherers (JEP 485 — stable)

Les Stream Gatherers sont l'extension la plus attendue de l'API Stream depuis Java 8. Ils permettent de créer des opérations intermédiaires personnalisées sur les streams, comblant les lacunes des opérations built-in.

Java — Gatherers built-in
import java.util.stream.Gatherers;

List<String> mots = List.of("a","b","c","d","e","f");

// windowFixed — fenêtres de taille fixe
mots.stream()
    .gather(Gatherers.windowFixed(2))
    .forEach(System.out::println);
// [a, b]  [c, d]  [e, f]

// windowSliding — fenêtre glissante
mots.stream()
    .gather(Gatherers.windowSliding(3))
    .forEach(System.out::println);
// [a, b, c]  [b, c, d]  [c, d, e]  [d, e, f]

// scan — accumulation avec état (comme reduce mais émet à chaque étape)
Stream.of(1, 2, 3, 4, 5)
    .gather(Gatherers.scan(() -> 0, Integer::sum))
    .toList();
// [0, 1, 3, 6, 10, 15]

// fold — réduction séquentielle avec état
Stream.of("a", "b", "c")
    .gather(Gatherers.fold(() -> "", (acc, s) -> acc + s))
    .findFirst();
// Optional["abc"]

// mapConcurrent — traitement parallèle ordonné
mots.stream()
    .gather(Gatherers.mapConcurrent(4, s -> appelExterne(s)))
    .toList();

Gatherers personnalisés

Java — Gatherer personnalisé
// Gatherer qui ne garde qu'un élément sur N (décimation)
public static <T> Gatherer<T, ?, T> everyNth(int n) {
    return Gatherer.ofSequential(
        () -> new int[]{0},     // initializer
        (state, element, downstream) -> {
            if (state[0]++ % n == 0) downstream.push(element);
            return true;
        }
    );
}

// Utilisation
IntStream.range(1, 20)
    .boxed()
    .gather(everyNth(3))
    .toList(); // [1, 4, 7, 10, 13, 16, 19]

2. Structured Concurrency (JEP 505 — stable)

La Concurrence Structurée sort de preview en Java 25. Elle garantit que les tâches filles ne survivent pas à leur tâche parente, évitant les fuites de threads.

Java
import java.util.concurrent.StructuredTaskScope;

// ShutdownOnFailure — annule tout si une tâche échoue
Response fetchData() throws Exception {
    try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
        var user    = scope.fork(() -> getUser());
        var orders  = scope.fork(() -> getOrders());
        var profile = scope.fork(() -> getProfile());

        scope.join().throwIfFailed();
        return new Response(user.get(), orders.get(), profile.get());
    }
}

// ShutdownOnSuccess — premier résultat gagné
String raceCondition() throws Exception {
    try (var scope = new StructuredTaskScope.ShutdownOnSuccess<String>()) {
        scope.fork(() -> apiPrimaire());
        scope.fork(() -> apiSecondaire());
        scope.fork(() -> cacheLocal());

        scope.join();
        return scope.result(); // le premier résultat disponible
    }
}

3. Scoped Values (JEP 506 — stable)

Les Scoped Values sont une alternative aux ThreadLocal pour partager des données dans un scope délimité, particulièrement adaptées aux Virtual Threads.

Java
import java.lang.ScopedValue;

public class RequestContext {
    // Déclaration — immutable, sans setter
    static final ScopedValue<User> CURRENT_USER =
        ScopedValue.newInstance();

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

    void handleRequest(HttpRequest req) {
        // Lier la valeur pour la durée du bloc
        ScopedValue.runWhere(
            CURRENT_USER, authenticate(req),
            () -> processRequest(req)
        );
    }

    void processRequest(HttpRequest req) {
        // Accessible partout dans le scope — même dans les sous-méthodes
        User user = CURRENT_USER.get();
        checkPermission(user, req.path());
    }
}

4. Primitive Types in Patterns (JEP 488 — stable)

Java 25 permet d'utiliser les types primitifs (int, long, double…) dans les patterns, fermant le fossé entre types primitifs et types référence.

Java
// Primitifs dans instanceof
Object obj = 42;

if (obj instanceof int i) {  // autoboxing automatique
    System.out.println("Entier : " + i);
}

// Primitifs dans switch
String describe(Object o) {
    return switch (o) {
        case int    i -> "int : "    + i;
        case long   l -> "long : "   + l;
        case double d -> "double : " + d;
        case String s -> "String : " + s;
        default       -> "autre";
    };
}

// Conversion sécurisée avec narrowing
long valeur = 1000L;
if (valeur instanceof byte b) {
    // seulement si la valeur tient dans un byte (−128 à 127)
    System.out.println(b);
}
// 1000 ne tient pas → pas de match

5. AOT Class Loading (JEP 483)

L'Ahead-of-Time Class Loading permet de pré-charger et optimiser les classes au build plutôt qu'au runtime, réduisant drastiquement le temps de démarrage.

Shell — Workflow AOT
# Étape 1 : entraînement — enregistrer les classes chargées
java -XX:AOTMode=record \
     -XX:AOTConfiguration=app.aotconf \
     -jar monapp.jar

# Étape 2 : compilation AOT — créer le cache
java -XX:AOTMode=create \
     -XX:AOTConfiguration=app.aotconf \
     -XX:AOTCache=app.aot \
     -jar monapp.jar

# Étape 3 : démarrage ultra-rapide avec le cache
java -XX:AOTCache=app.aot -jar monapp.jar
# Démarrage Spring Boot : 2s → 200ms !

6. Module Imports (JEP 476)

Les Module Imports permettent d'importer tout un module Java en une ligne, simplifiant les imports dans les scripts et petits programmes.

Java
// Avant — imports répétitifs
import java.util.List;
import java.util.Map;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.stream.Collectors;
import java.util.stream.Stream;

// Java 25 — un seul import de module
import module java.base;

// Tout java.util, java.io, java.lang, java.nio... disponibles
void main() {
    var liste = new ArrayList<String>();
    var map   = new HashMap<String, Integer>();
    liste.stream().collect(Collectors.toList());
}

7. Flexible Constructor Bodies (JEP 492)

Java 25 assouplit la règle qui imposait que super() soit le premier statement d'un constructeur. Le code peut désormais s'exécuter avant l'appel au constructeur parent, à condition de ne pas accéder à l'instance (this).

Java
// Avant Java 25 — validation impossible avant super()
class Compte extends EntiteBase {
    public Compte(String iban) {
        // super() DOIT être ici — impossible de valider avant
        super(iban);
    }
}

// Java 25 — validation autorisée avant super()
class Compte extends EntiteBase {
    public Compte(String iban) {
        // Code avant super() — ok si pas de this.*
        if (iban == null || !iban.matches("[A-Z]{2}\\d{2}[A-Z0-9]{4,30}"))
            throw new IllegalArgumentException("IBAN invalide : " + iban);
        var normalized = iban.toUpperCase().replaceAll("\\s", "");
        super(normalized); // appel avec valeur traitée
    }
}

8. Récapitulatif Java 25

🌊

Stream Gatherers

Opérations intermédiaires custom : windowFixed, windowSliding, scan, fold…

Structured Concurrency

Cycle de vie des tâches parallèles garanti — stable en Java 25.

📌

Scoped Values

Remplacement de ThreadLocal adapté aux Virtual Threads.

🔢

Primitive Patterns

int, long, double dans instanceof et switch — unification des types.

🚀

AOT Class Loading

Démarrage Spring Boot en ~200ms grâce au cache AOT.

📦

Module Imports

import module java.base — fini les dizaines d'imports répétitifs.

Version Type Feature phare Année
Java 8LTSLambdas, Stream API, Optional2014
Java 11LTSHTTP Client, nouvelles String API2018
Java 17LTSRecords, Sealed Classes, Pattern instanceof2021
Java 21LTSVirtual Threads, Pattern switch2023
Java 25LTSStream Gatherers, AOT, Scoped Values2025