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.

Java — Créer des Virtual Threads
// 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.

Java — Pattern classique devenu efficace
// 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.

Java
// 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.

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

Java
// 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.

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

Java 21 — Hello World simplifié
// 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.