Java 21 est la version LTS la plus révolutionnaire depuis Java 8. Au programme : les Virtual Threads (Project Loom) qui transforment la concurrence Java, le Pattern Matching pour switch stabilisé, et les Sequenced Collections qui corrigent un manque historique de l'API Collections.
1. Virtual Threads — Project Loom (JEP 444)
Les Virtual Threads sont des threads ultra-légers gérés par la JVM (et non l'OS). On peut en créer des millions sans impact mémoire significatif — ce qui était impossible avec les Platform Threads classiques.
// Thread virtuel simple
Thread vt = Thread.ofVirtual().start(() -> {
System.out.println("Je suis un virtual thread !");
});
// Via un Executor (recommandé pour les serveurs)
try (var exec = Executors.newVirtualThreadPerTaskExecutor()) {
// Chaque tâche = 1 virtual thread
IntStream.range(0, 1_000_000).forEach(i ->
exec.submit(() -> traiterRequete(i))
);
} // try-with-resources attend la fin de toutes les tâches
// Mesure : Platform Threads vs Virtual Threads
// Platform : ~1 MB par thread → 10 000 threads = 10 GB RAM
// Virtual : ~few KB par VT → 1 000 000 VT = quelques centaines de MB
Spring Boot 3.2+ active automatiquement les Virtual Threads via spring.threads.virtual.enabled=true. Chaque requête HTTP devient un Virtual Thread, remplaçant le pool de threads Tomcat classique.
Blocking I/O avec Virtual Threads
Le vrai intérêt des Virtual Threads : quand un VT se bloque sur une I/O (lecture BDD, appel HTTP…), la JVM le "démonte" du Platform Thread porteur et en monte un autre — sans bloquer l'OS thread.
// Ce code BLOQUANT devient efficace avec les Virtual Threads
// pas besoin de CompletableFuture ou de reactive programming
try (var exec = Executors.newVirtualThreadPerTaskExecutor()) {
var future1 = exec.submit(() -> appelApiExterne()); // bloque OK
var future2 = exec.submit(() -> lireBDD()); // bloque OK
var future3 = exec.submit(() -> lireFichier()); // bloque OK
var r1 = future1.get();
var r2 = future2.get();
var r3 = future3.get();
}
2. Pattern Matching pour switch (JEP 441 — stable)
Le Pattern Matching pour switch est maintenant stable. Combiné aux Sealed Classes et Records, il permet un dispatch polymorphique exhaustif et sûr.
// Pattern Matching + Sealed + Records = puissant
sealed interface Forme permits Cercle, Rectangle, Triangle {}
record Cercle(double r) implements Forme {}
record Rectangle(double w, double h) implements Forme {}
record Triangle(double b, double h) implements Forme {}
double surface(Forme f) {
return switch (f) {
case Cercle c -> Math.PI * c.r() * c.r();
case Rectangle r -> r.w() * r.h();
case Triangle t -> 0.5 * t.b() * t.h();
}; // pas de default — sealed = exhaustif !
}
// Avec guarded patterns (when)
String classifier(Number n) {
return switch (n) {
case Integer i when i < 0 -> "Entier négatif";
case Integer i when i == 0 -> "Zéro";
case Integer i -> "Entier positif : " + i;
case Double d -> "Décimal : " + d;
default -> "Nombre inconnu";
};
}
3. Record Patterns (JEP 440 — stable)
Les Record Patterns permettent la déconstruction directe d'un Record dans un pattern. On accède aux composants sans appeler les accesseurs manuellement.
record Point(int x, int y) {}
record Segment(Point debut, Point fin) {}
// Déconstruction dans instanceof
if (obj instanceof Point(int x, int y)) {
System.out.println("x=" + x + " y=" + y);
}
// Déconstruction imbriquée dans switch
String describe(Object obj) {
return switch (obj) {
case Point(int x, int y) when x == y -> "Diagonale (" + x + ")";
case Point(int x, int y) -> "Point (" + x + "," + y + ")";
case Segment(Point d, Point f) -> "Segment de (" + d.x() + ") à (" + f.x() + ")";
default -> "Inconnu";
};
}
4. Sequenced Collections (JEP 431)
Java 21 introduit trois nouvelles interfaces pour les collections avec un ordre défini : SequencedCollection, SequencedSet et SequencedMap.
// Nouvelles méthodes uniformes sur toutes les collections séquencées
List<String> liste = new ArrayList<>(List.of("A", "B", "C"));
// Premier et dernier élément — enfin une API standard !
liste.getFirst(); // "A" (avant : liste.get(0))
liste.getLast(); // "C" (avant : liste.get(liste.size()-1))
liste.addFirst("Z"); // ["Z","A","B","C"]
liste.addLast("M"); // ["Z","A","B","C","M"]
liste.removeFirst(); // retire "Z"
liste.removeLast(); // retire "M"
// Vue inversée
List<String> inverse = liste.reversed();
// Fonctionne aussi sur LinkedList, Deque, NavigableSet, LinkedHashMap...
LinkedHashMap<String, Integer> map = new LinkedHashMap<>();
map.put("a", 1); map.put("b", 2);
map.firstEntry(); // a=1
map.lastEntry(); // b=2
5. Structured Concurrency (JEP 453 — preview)
La Structured Concurrency traite un groupe de tâches concurrentes comme une unité : si l'une échoue, les autres sont annulées automatiquement.
import java.util.concurrent.StructuredTaskScope;
Response handle() throws Exception {
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
// Fork = lancer en parallèle
Subtask<User> user = scope.fork(() -> getUser());
Subtask<Order> order = scope.fork(() -> getOrder());
scope.join(); // attendre les deux
scope.throwIfFailed(); // si l'une a échoué, annuler tout
return new Response(user.get(), order.get());
}
}
6. Unnamed Classes & Instance Main (JEP 445 — preview)
Pour simplifier l'apprentissage, Java 21 permet d'écrire un programme sans classe ni static.
// Avant Java 21
public class Hello {
public static void main(String[] args) {
System.out.println("Hello");
}
}
// Java 21 preview — 1 seule ligne !
void main() { System.out.println("Hello"); }
7. Récapitulatif Java 21
Virtual Threads
Millions de threads légers gérés par la JVM. Fin du reactive programming forcé.
Pattern Matching switch
Dispatch exhaustif et sûr sur les types, avec guards when.
Record Patterns
Déconstruction directe des Records dans les patterns.
Sequenced Collections
getFirst(), getLast(), reversed() — API uniforme enfin.
Structured Concurrency
Gestion structurée du cycle de vie des tâches parallèles.
Unnamed Classes
Hello World sans boilerplate — Java accessible aux débutants.