Java 21 : quoi de neuf ?

Java 21 : quoi de neuf ?

Maintenant que Java 21 est features complete (Rampdown Phase Two au jour d’écriture de l’article), c’est le moment de faire le tour des fonctionnalités qu’apporte cette nouvelle version, à nous, les développeurs.

Cet article fait partie d’une suite d’article sur les nouveautés des dernières versions de Java, pour ceux qui voudraient les lire en voici les liens : Java 20, Java 19, Java 18, Java 17, Java 16, Java 15, Java 14, Java 13, Java 12, Java 11Java 10, et Java 9.

Java 21 est la nouvelle version avec support à long terme (Long Term Support – LTS), elle sera donc supportée pendant au moins 5 ans par Oracle. Au regard du nombre de JEP (Java Enhancement Proposals) qu’elle contient, pas moins de 15, on voit tout de suite que c’est une version riche en nouvelles fonctionnalités. Mais peut-être le plus important (pour ne pas dire excitant) est le passage en final des virtual threads ! Les virtual threads sont des threads légers, avec un coût de création et de scheduling faible, qui permettent de faciliter l’écriture d’application concurrente. Il faudra attendre quelque temps que l’écosystème les supporte, mais les virtual threads vont rendre Java pertinent pour des applications hautement concurrentes en environnement contraint en mémoire.

JEP 430 – String Templates (Preview)

Beaucoup de langages supportent l’interpolation de chaînes de caractères. L’interpolation de chaînes de caractères est un littéral de chaînes de caractères contenant des expressions ainsi que du texte littéral.

Par exemple, en Kotlin, "$x plus $y equals ${x + y}" est une chaîne de caractères contenant les expressions $x, $y, et ${x + y} qui vont être remplacées par leur valeur textuelle. Ces valeurs sont dites interpolées au sein de la chaîne de caractères. Cette interpolation se fait depuis les variables et permet des opérations entre les variables (ici, une addition).

Le problème de l’interpolation est qu’elle est dangereuse en tant que fonctionnalité globale, car elle ne permet pas de validation ou d’assainissement (sanitization) lors de la construction de la chaîne de caractère finale. Elle s’expose donc, par exemple, aux injections SQL ou JavaScript.

Les String Templates en Java proposent de l’interpolation de chaînes de caractères intégrant la validation et l’assainissement via un processeur de template.

Un processeur de template prend un template, puis va réaliser l’interpolation du template vers un objet d’un type précis ; on peut donc depuis un template interpoler une String, ou un PreparedStatement, ou un JSONObject, … Comme il est possible d’avoir plusieurs processeurs, chacun peut implémenter une étape de validation si nécessaire.

Voici un exemple d’utilisation du String Template STR, il ne comporte pas de validation spécifique et peut être utilisé pour remplacer de la concaténation de chaîne de caractère :

String firstName = "Loïc";
String lastName  = "Mathieu";
String helloWorld  = STR."Hello \{firstName} \{lastName}";

En Java, les expressions sont définies par \{expression}, et l’appel d’un processeur de template se fait via son nom, ici FMT qui est une constante de l’interface StringTemplate qui aura été précédemment importée via un import statique.

Il y a eu beaucoup de discussion autour du choix du format des expressions. À cause de l’existence de nombreuses librairies utilisant $, # ou {} comme délimiteur d’expression, le choix a été d’un format qui n’est pas valide en dehors des String Templates : String s = "Hello \{firstName} \{lastName}" ne compile pas. Cela permet de distinguer les String Templates des Strings simple.

La librairie standard inclue trois processeurs de template :

  • RAW : processeur qui n’interpole pas les chaînes de caractère, permet des manipulations bas niveau.
  • STR : processeur qui interpole une chaîne de caractère vers une autre chaîne de caractère via concaténation simple.
  • FMT : processeur qui interpole une chaîne de caractère vers une autre chaîne de caractère en permettant de formater les expressions via un Formatter, par exemple FMT."%05d\{x} + %05d\{y} = %05d\{x + y}";

Il est possible de créer ses propres processeurs en implémentant l’interface StringTemplate.Processor.

Plus d’information dans la JEP 430.

JEP 431 – Sequenced Collections

L’API de collection de Java a vu un ajout dans Java 21 d’une importance qui n’avait pas été vu depuis de très nombreuses releases !

Les collections en Java n’ont pas de type représentant une séquence ordonnée d’éléments, Java 21 viens combler ce manque en introduisant les interfaces SequencedCollection, SequencedSet et SequencedMap. Ces interfaces proposent des méthodes permettant d’ajouter, de modifier ou de supprimer des éléments au début ou a la fin de la collection, ainsi que d’itérer sur une collection en sens inverse.

Voici l’interface SequencedCollection :

interface SequencedCollection<E> extends Collection<E> {
    SequencedCollection<E> reversed();
    void addFirst(E);
    void addLast(E);
    E getFirst();
    E getLast();
    E removeFirst();
    E removeLast();
}

La méthode reversed() va retourner une vue de la collection dans un ordre inversé, une modification de la collection d’origine impactera la vue inversée.

SequencedSet est un set qui est aussi une SequencedCollection, voici son interface :

interface SequencedSet<E> extends Set<E>, SequencedCollection<E> {
    SequencedSet<E> reversed();
}

SequencedMap est une map dont les entrées sont ordonnées, voici son interface :

interface SequencedMap<K,V> extends Map<K,V> {
    SequencedMap<K,V> reversed();
    SequencedSet<K> sequencedKeySet();
    SequencedCollection<V> sequencedValues();
    SequencedSet<Entry<K,V>> sequencedEntrySet();
    V putFirst(K, V);
    V putLast(K, V);
    Entry<K,V> firstEntry();
    Entry<K,V> lastEntry();
    Entry<K,V> pollFirstEntry();
    Entry<K,V> pollLastEntry();
}

Ces nouvelles interfaces ont été intégrées dans la hiérarchie existante des classes de l’API Collection.

Java Collection API hierarchy

On peut noter que certaines des implémentations les plus utilisées de l’API Collection : ArrayList, LinkedList, LinkedHashMap et LinkedHashSet sont maintenant des collections séquentielles.

Plus d’information dans la JEP 431.

JEP 443 – Unnamed Patterns and Variables Preview)

Cette nouvelle fonctionnalité du langage Java permet d’utiliser _ comme pattern ou variable anonyme (littéralement sans nom ou non-nommé). Le but est d’utiliser _ pour dénoter un pattern ou une variable inutile, le compilateur s’assurera alors que la variable est réellement non utilisée car elle n’a pas de nom.

Au sein du pattern matching de Java, _ peut être utilisé comme pattern anonyme, par exemple dans un instanceof : instanceof Point(_, int y); ou comme variable de pattern anonyme, par exemple dans un instanceof : instanceof Point(int _, int y) ou dans un switch : case Box(GreenBall _).

_ peut aussi être utilisé pour dénoter une variable anonyme qui ne pourra ni être lue ni être écrite ; comme elle n’a pas de nom, on peut utiliser plusieurs variables anonymes dans un même scope.

On peut utiliser une variable anonyme :

  • Comme une variable locale a un statement,
  • Comme une ressource d’un try-with-resource,
  • Dans un entête d’une boucle for (basique ou enhanced),
  • Comme une exception en paramètre d’un bloc catch,
  • Comme un paramètre d’une expression lambda.

Voici quelques exemples tirés de la JEP 443 :

// enhanced for loop
int acc = 0;
for (Order _ : orders) {
    if (acc < LIMIT) { ... acc++ ... } 
} 

// block statement 
Queue<Integer> 
q = ... // x1, y1, z1, x2, y2, z2, ... 
while (q.size() >= 3) {
   var x = q.remove();
   var y = q.remove();
   var _ = q.remove();
   ... new Point(x, y) ...
}

// catch block
String s = ...
try { 
    int i = Integer.parseInt(s);
    ... i ...
} catch (NumberFormatException _) { 
    System.out.println("Bad number: " + s);
}

// try-with-resources
try (var _ = ScopedContext.acquire()) {
    ... no use of acquired resource ...
}

// lamdba parameter
stream.collect(Collectors.toMap(String::toUpperCase, _ -> "NODATA"))

Cette fonctionnalité, même si elle peut sembler petite, est en fait une fonctionnalité très attendue et qui peut fortement améliorer la lisibilité du code et éviter de possibles bugs en dénotant clairement qu’une variable ne doit pas être utilisée. On peut juste regretter que, pour l’instant, il ne soit pas possible d’utiliser _ comme un paramètre de méthode surchargée.

Plus d’information dans la JEP 443.

JEP 445: Unnamed Classes and Instance Main Methods (Preview)

Le but de cette nouvelle fonctionnalité est de faciliter l’apprentissage de Java et simplifier son utilisation pour des cas simple, entre autres, pour l’écriture d’une simple méthode main.

Prenons comme exemple un « Hello World » en Java :

public class HelloWorld { 
    public static void main(String[] args) { 
        System.out.println("Hello, World!");
    }
}

Pour simplement écrire « Hello World » dans la consoles il faut connaitre les principes de classe, de méthode, de visibilité, du modificateur static, ainsi que la signature très particulière de la méthode main en Java : la méthode qui sera exécutée comme point d’entrée du programme.

Premier changement : permettre une méthode main non statique (méthode d’instance), non publique, et sans paramètre :

class HelloWorld { 
    void main() { 
        System.out.println("Hello, World!");
    }
}

Second changement : l’introduction des classes anonymes (non-nommées) :

void main() {
    System.out.println("Hello, World!");
}

Une classe anonyme est une classe dans un fichier .class qui n’a pas de déclaration de classe, elle ne peut pas être appelée par une autre classe, mais peut contenir des méthodes et des champs. Elle se trouve dans un package anonyme.

Ces deux nouveautés, bien que ciblant les développeurs apprenant Java, peuvent grandement faciliter l’écriture de petit programme en Java en réduisant la cérémonie autour de l’écriture du point d’entrée d’un programme Java.

Lors de discussion autour de cette fonctionnalité, il a aussi été abordé le cas de System.out.println() (ainsi que la lecture depuis la console) et de possible simplification. Il se peut que dans une prochaine version de Java des simplifications à ce sujet soit intégrées.

Plus d’information dans la JEP 445.

Les fonctionnalités qui sortent de preview

Les fonctionnalités suivantes sortent de preview (ou du module incubator) et passent en standard :

  • JEP 440 – Record Patterns : enrichit le pattern matching de Java avec les record patterns qui permettent de déconstruire un record en ses attributs.
  • JEP 441 – Pattern Matching for switch : permet de faire un switch sur le type d’une variable (y compris enum, record et tableau), et en extraire une variable locale au case qui sera du type correspondant.
  • JEP 444 – Virtual Threads : parfois aussi nommés green threads ou lightweight threads; ce sont des threads légers, avec un coût de création et de scheduling faible, qui permettent de faciliter l’écriture d’application concurrente.

Pour les détails sur celles-ci, vous pouvez vous référer à mes articles précédents.

Les fonctionnalités qui restent en preview

Les fonctionnalités suivantes restent en preview (ou en incubator module).

  • JEP 442 – Foreign Function & Memory API (Third Preview) : amélioration suite aux retours de la preview. La gestion des segments mémoire natifs est désormais faite via une nouvelle API Arena.
  • JEP-448 – Vector API : sixième incubation de la fonctionnalité. Cette nouvelle version inclut des bugfixes et des améliorations de performance.
  • JEP 446 – Scoped Values (Preview) : précédemment en incubation, permettent le partage de données immuables au sein et entre les threads.
  • JEP 453 – Structured Concurrency (Preview) : précédemment en incubation, nouvelle API permettant de simplifier l’écriture de code multi-threadé en permettant de traiter plusieurs tâches concurrentes comme une unité de traitement unique.

Pour les détails sur celles-ci, vous pouvez vous référer à mes articles précédents.

Divers

Divers ajouts au JDK :

  • Character.isEmoji(), Character.isEmojiPresentation(), Character.isEmojiModifier(), Character.isEmojiModifierBase(), Character.isEmojiComponent(), Character.isExtendedPictographic().
  • Math.clamp() et StrictMath.clamp() : restreint une valeur entre deux bornes min et max.
  • StringBuilder.repeat() : joint un caractère ou une chaîne de caractère un certain nombre de fois.
  • HttpClient implémente maintenant AucoCloseable et peut donc être utilisé plus facilement dans un bloc try-with-resources.
  • Locale.availableLocales() : retourne la liste des locales installées.
  • Collections.shuffle(List, RandomGenerator) : effectue des permutations aléatoires des éléments de la liste depuis un RandomGenerator.
  • String.splitWithDelimiters() et Pattern.splitWithDelimiters() : scinde une chaîne de caractères en incluant les délimiteurs.

La totalité des nouvelles API du JDK 21 peuvent être trouvées dans The Java Version Almanac – New APIs in Java 21.

Des changements internes, de la performance, et de la sécurité

Le Garbage Collector ZGC devient générationnel, il peut donc séparer la heap en plusieurs zones en fonction de l’âge des objets. Pour utiliser cette fonctionnalité il faut utiliser l’option ligne de commande -XX:+ZGenerational. Ce Garbage Collector a été créé pour supporter des heaps de tailles très importantes (plusieurs terraoctets) avec des pauses très faibles (de l’ordre de la milliseconde). L’ajout d’une heap générationnelle lui permet de supporter des workloads différents en consommant moins de ressources. Plus d’information dans la JEP 439.

G1 GC a, lui aussi, profité de quelques nouvelles optimisations : les full GCs ont été optimisés et le Hot Card Cache qui s’avérait n’apporter aucun avantage a été supprimé, libérant de la mémoire native (0,2% de la taille de la heap). D’autres changements ont été faits côté Garbage Collector, vous pouvez les retrouver dans cet article de Thomas Schatzl : JDK 21 G1/Parallel/Serial GC changes.

Côté sécurité, Java supporte maintenant les mécanismes d’encapsulation de clé (Key Encapsulation Mechanism – KEM), une technique de chiffrement pour sécuriser les clés symétriques à l’aide de la cryptographie à clé publique. Plus d’information à ce sujet dans la JEP 452.
Le support de vérification de signature de type Leighton-Micali Signature (LMS) ainsi que les algorithme de cryptographie de type PBES2 ont été ajouté, vous pouvez vous référer à l’article de Sean Mullan pour une liste exhaustive des changements de sécurité inclus cette version : JDK 21 Security Enhancements.

Le port pour Windows 32-bit pour CPU x86 a été déprécié pour suppression dans une prochaine release. Windows 10, la dernière version de Windows supportant les architectures 32-bit, sera en fin de vie en octobre 2025. Déprécier puis supprimer le port Windows 32-bit permettra de simplifier le build l’Open JDK et de réduire son coût de maintenance. Plus d’information dans la JEP 449.

Le chargement dynamique d’agent Java est maintenant déprécié pour suppression. Si utilisé, il affichera un WARNING dans les logs de la JVM. Le chargement d’un agent Java au démarrage de l’application reste supporté, c’est uniquement le chargement dynamique après le démarrage d’une application qui est déprécié. Le but est d’améliorer l’intégrité de la JVM, un agent pouvant modifier le code d’une application, le charger après démarrage de la JVM est un risque de sécurité. Plus d’information dans la JEP 451.

Côté performance, Per Minborg a réalisé des améliorations dans la conversion entre les primitifs (long vers int par exemple) via l’utilisation de VarHandle en remplacement des calculs binaires existants. Ces opérations de conversions étant très utilisé au sein de la sérialisation Java, celle-ci en tire parti et voit une amélioration de performance de près de 5%. D’autres API du JDK ainsi que de nombreuses librairies utilisent aussi ces conversions et verront leur performance s’améliorer. Plus d’information dans l’article de Per Minborg : Java 21: Performance Improvements Revealed.

Conclusion

Au vu du nombre très important de fonctionnalités inclues dans Java 21, on peut s’attendre à ce que Java 22 soit une release de stabilisation. J’espère que certaines fonctionnalités sortiront de preview en Java 22 (ce qui devrait être le cas pour la Foreign Function & Memory API), tout particulièrement les Scoped Values et la Structured Concurrency qui complémentent les Virtual Threads en facilitant l’écriture d’application concurrente.

Pour retrouver tous les changements de Java 21, se référer aux release notes.

2 réflexions sur « Java 21 : quoi de neuf ? »

  1. As was pointed out on lobste.rs; Java LTS is longer than 2 years. The details vary on which JDK you’re looking at, but Oracle is going to support Java 21 until September of 2028 or 2031, depending on whether or not you’re a paying Oracle customer or not.

    Amazon announced that they’re supporting the previous LTS release, 17, for eight years until September 2029.

    A new LTS version is designated every 2 years, which is almost certainly the source of the confusion. But they’re supported for much longer than two years.

    1. Thanks for the detailed information.
      Yes, support period depends on the JDK distribution provider, so the correct sentence should be « it will be supported at least 5 years by Oracle » to keep it short.

Laisser un commentaire

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.