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.
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
// 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.
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.
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.
// 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.
# É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.
// 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).
// 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 8 | LTS | Lambdas, Stream API, Optional | 2014 |
| Java 11 | LTS | HTTP Client, nouvelles String API | 2018 |
| Java 17 | LTS | Records, Sealed Classes, Pattern instanceof | 2021 |
| Java 21 | LTS | Virtual Threads, Pattern switch | 2023 |
| Java 25 | LTS | Stream Gatherers, AOT, Scoped Values | 2025 |