La semaine dernière est sortie la version 18 de Java. Peu de nouveauté mais ça vaut toujours le coup de regarder ce qui bouge ! 🤓

Simple Web Server

Ça ne va pas révolution notre quotidien, mais c'est un ajout qui sera toujours pratique : un serveur http pour des fichiers statiques.

Le but assumé n'est pas d'avoir un serveur http haute performance ou ultra configurable, mais juste d'avoir la possibilité de servir des fichiers statiques très facilement sans avoir besoin de passer par un outil externe. C'est toujours pratique d'avoir ça sous le coude pour quelques fichiers ou pour tester des choses en local.

On a le choix entre deux approches : la CLI ou du code Java.

Côté CLI c'est très simple :

jwebserver -b 0.0.0.0 -p 9000 -d ./statics

Chacun des paramètres étant optionnel avec des valeurs par défaut très bien détaillés.

Côté code Java, c'est toujours aussi simple :

var server = SimpleFileServer.createFileServer(
    new InetSocketAddress(9000),
    Path.of("./statics"),
    OutputLevel.VERBOSE
);
server.start();

Il y a pas mal de variantes sur la signature de SimpleFileServer.createFileServer pour ajouter plus ou moins de paramètres. On a aussi la possibilité d'avoir encore plus la main sur la configuration en passant par HttpServer.create ou HttpsServer.create, avec entre autres la possibilité de filtrer les requêtes via une fonction, d'avoir plusieurs handler de requête pour avoir plusieurs stratégies, etc.

Je ne peux que vous recommander de prendre un peu de temps pour tester les possibilités offertes par cette API !

On pousse dehors finalize() !

Si vous n'avez jamais utilisé la méthode finalize(), ce n'est pas le moment de commencer, car cette méthode est maintenant dépréciée pour suppression (donc devrait disparaître en Java 19).

L'idée derrière cette mécanique était d'offrir la possibilité d'exécuter quelques instructions juste avant la destruction d'un objet pour faire un nettoyage pointu que la JVM (et le Garbage Collector) n'arriverait pas à gérer. Ça fait très longtemps qu'on utilise plus cette méthode car peu pratique, pas très probante et dans la vaste majorité des cas la JVM fait déjà très bien le travail !

Si vous avez quelques cas qui traînent, je vous conseille (comme l'équipe derrière l'OpenJDK) de vous pencher très vite vers les alternatives try-with-resources (introduite en Java 7) ou Cleaner API (introduite en Java 9).

Seconde preview pour le Pattern Matching des switch

Bien que toujours en preview, le pattern matching des switch continue d'avoir des améliorations. On a deux gros changements ici : l'interdiction de la dominance des étiquettes de pattern et l'obligation d'exhaustivité.

L'interdiction de la dominance des étiquettes de pattern ("Dominance of pattern labels" dans la JEPS) correspond à l'ajout d'une vérification à la compilation évitant ce genre de cas :

switch (Object o) {
    case CharSequence cs -> ...
    case String s -> ...
    default -> ...
}

Ici on a un problème tout simple : toutes les instances de String sont des instances de CharSequence (car String étend CharSequence). À partir de Java 18 on aura une erreur de compilation pour nous obliger à permuter le pattern CharSequence et String de sorte à s'assurer qu'on a pas de cas inatteignable. Je prends l'exemple de deux patterns d'instance, mais on aura aussi des erreurs dans ce genre de cas, si on ne met pas les patterns dans cet ordre précis :

switch(o) {
    case -1, 1 -> ...               // Special cases 
    case Integer i && i > 0 -> ...  // Positive integer cases
    case Integer i -> ...           // All the remaining integers
    default -> 
}

L'obligation d'exhaustivité c'est très simple, on ne pourra pas écrire ce genre de switch sans erreur de compilation, car on ne gère pas tous les cas :

static int coverage(Object o) {
    return switch (o) {
        case String s -> s.length();
    };
}

Réimplémentation du cœur de l'API reflection

Comme beaucoup le savent, Java a toujours été un langage proposant pas mal de reflection via une API dédiée. Malheureusement, cette API s'appuie très fortement sur la classe sun.misc.Unsafe.

À partir de Java 18 ce ne sera plus le cas, en effet les classes java.lang.reflect.Method,java.lang.reflect.Constructor et java.lang.reflect.Field ont été réimplémenté en utilisant les classes du package java.lang.invoke. En plus de limiter encore un peu plus l'usage de sun.misc.Unsafe, ça devrait permettre à l'équipe de développement de l'OpenJDK de mieux gérer la maintenance de l'API reflection.

Même si l'API exposé ne change pas, les benchmarks montrent par contre une perte de performance plus ou moins grande entre l'ancienne et la nouvelle implémentation. Je vous redirige vers la JEPS qui donne le détail des changements de performance mais c'est potentiellement un point à prendre en compte en fonction des applications !

À noter qu'une option a été ajoutée pour permettre de maintenir l'ancien fonctionnement au besoin : -Djdk.reflect.useDirectMethodHandle=false. Par contre c'est bien uniquement un palliatif à court terme, car le but est bien de retirer totalement l'ancienne implémentation dans une prochaine version de Java, mais ça peut laisser du temps de corriger des problèmes d'ici là.

Quelques changements bienvenus

À partir de Java 18, Java va utiliser le charset UTF-8 par défaut pour toutes les entrées/sorties (à l'exception de la console). Jusque-là, le charset par défaut était déterminé au lancement de la JVM, ce qui obligeait à toujours bien spécifier le charset qu'on voulait sous peine de ne pas avoir un code portable.

À partir de Java 18, on aura la possibilité d'inclure des snippets de code dans la Javadoc. Contrairement au tag @code, le tag @snippet devrait permettre de vraiment identifier des portions de code qui sont censés être exécutables. Comme spécifié dans la JEPS, le but n'est pas que l'outil javadoc fasse de la validation, mais que ce soit possible d'ajouter des outils qui le fassent. Je vois très bien à l'avenir la possibilité d'intégrer dans la Javadoc des snippets de code qu'on pourrait exécuter dans le navigateur pour les tester ou bien un outil qui viendrait exécuter le code des snippets un peu comme des tests pour s'assurer que les snippets soient toujours à jour. À suivre !

Cette version ajoute la possibilité d'avoir une résolution de nom de domaine interne à la JVM, sans pour autant changer le comportement actuel par défaut : utiliser la résolution de nom fourni par l'OS hôte. Le but est uniquement de préparer le terrain pour d'autres évolutions, ainsi qu'offrir une piste pour certains besoins précis où on voudrait mieux contrôler la gestion de résolution de nom (par exemple pour les tests).

Conclusion

On est pas là face à une version énorme de Java, dans la majorité des cas, cette version ne changera pas notre manière d'utiliser Java.

C'est par contre une version qui encore une fois prépare le terrain pour des changements à venir, même si vous ne passez pas en Java 18 sur vos applications, je vous conseille quand même d'au moins tester que tout fonctionne si vous deviez monter de version, rien que pour la partie baisse de performance de la réimplémentation de l'API reflection qui pourrait ralentir fortement le démarrage de certains projets Spring à vue de nez ! 🧐

Sources :

Crédit photo : https://pixabay.com/photos/wall-industrial-18-building-616594/