Tutoriel pour apprendre à migrer de Java 8 à Java 11

Loïc Mathieu

Si vous avez suivi un petit peu l'actualité de Java, vous avez dû remarquer qu'il s'est passé pas mal de choses cette dernière année : Java 9 est sorti il y a un an avec pas mal de nouveautés (dont la tant attendue modularité du JDK) et n'est déjà plus supporté depuis 6 mois, Java 10 est sorti il y a 6 mois et n'est plus supporté non plus, Java 11 vient de sortir et il est LTS (Long Term Support). Oracle fournit deux distributions de Java : Oracle et OpenJDK et propose du support étendu pour ceux qui le veulent. Java 8 (la version la plus utilisée actuellement) ne sera plus supporté à partir de janvier 2019 !

Ceux qui sont un peu perdus là-dedans, cet article est fait pour eux ! Je vais tenter d'éclaircir le nouveau mode de gouvernance de Java (cycle des versions, support, distribution) et de vous présenter les principales nouveautés des versions 9, 10 et 11 en faisant le point sur les changements à destination des développeurs.

Pour réagir au contenu de cet article, un espace de dialogue vous est proposé sur le forum Commentez .

- Titre : Tutoriel pour apprendre à migrer de Java 8 à Java 11

- Auteur : Loïc Mathieu

- Parution : 4 juin 2019

- Licence : Copyright ® 2019 Loïc Mathieu. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Le nouveau rythme des versions

- II. Les changements en termes de distribution

- III. Les nouveautés de Java 9

- III-A. La modularisation du JDK

- III-B. Un client HTTP/2 en stade expérimental

- III-C. Une refonte de l'API Process via ProcessHandle

- III-D. Méthode factory de création de collections

- III-E. Une implémentation des Reactive Stream

- III-F. jshell: The Java Shell (Read-Eval-Print Loop)

- III-G. Autres API

- IV. Les nouveautés de Java 10

- IV-A. Local-Variable Type Inference

- IV-B. Copy factory methods

- IV-C. Collectors to unmodifiable List

- V. Les nouveautés de Java 11

- V-A. HTTP Client (Standard)

- V-B. Launch Single-File Source-Code Programs

- V-C. Les nouveaux GCs

- V-D. Autres API

- VI. Conclusion

- VII. Remerciements

I - Le nouveau rythme des versions

Les architectes du langage Java ont fait un constat : le rythme des versions de Java jusqu'à la version 8 était d'une tous les 3 ans environ, ce faisant, on avait peu de versions avec à chaque fois beaucoup de fonctionnalités. C'était donc compliqué de livrer une nouvelle version de Java et compliqué pour les développeurs de faire la montée de version de Java pour leur application, au vu du nombre de changements inclus dans chaque nouvelle version. Comme une version avait un ensemble de fonctionnalités prévues, si une fonctionnalité prenait plus de temps à être développée, elle retardait la sortie de la nouvelle version.

De plus, ce rythme ne collait plus avec la réalité du monde de l'informatique où tout va de plus en plus vite ! Les architectes ont donc décidé de publier une version tous les 6 mois (donc un calendrier fixe), s'inspirant de ce que fait Ubuntu par exemple.

Dans chaque version, toutes les fonctionnalités prêtes sont incorporées, celles qui ne sont pas prêtes seront livrées dans la suivante (plus de version en retard).

Chaque version sera maintenue 6 mois par Oracle (libre à d'autres éditeurs de les supporter plus longtemps). Pour les entreprises qui ne peuvent ou ne veulent pas tenir le rythme, il y aura tous les trois ans une version dite LTS — Long Term Support — qui sera maintenue trois ans.

Le but de ceci : livrer de nouvelles fonctionnalités en continu (on parle alors de « release train »), forcer les utilisateurs à migrer et simplifier lesdites migrations, car le périmètre de chaque version est plus petit.

Mon avis sur la question : il faut vivre avec son temps. Tout le monde ou presque fait de l'intégration continue et/ou du déploiement continu, donc passer d'une version à l'autre de Java ne doit pas être un problème. Je conseille donc, à chaque nouveau projet, de passer sur la version de Java la plus récente. Ceux qui sont réfractaires au changement peuvent rester sur les LTS mais en tout cas, il faut dépasser Java 8 et embrasser le nouveau rythme des versions.

Posez-vous la question pour ceux qui font du JavaScript ou du TypeScript : quand migrez-vous ? Ubuntu et Angular font bien une version tous les 6 mois non ?

Donc, Oracle s'est juste aligné sur les bonnes pratiques du marché en termes de version.

Ce nouveau rythme a permis à Oracle de fournir dans le JDK, une implémentation de TLS 1.3 en quelques mois, ce qui aurait été impensable il y a quelques années.

II - Les changements en termes de distribution

Oracle fournit deux distributions de Java :

- OpenJDK : sous licence GPL v2 with ClassPath Exception, simple archive à décompresser : http://jdk.java.net/11/ c'est celle que je vous conseille ;

- Oracle : sous licence commerciale, avec support possible (qui commence à 25 $/mois/CPU core) et installeur pour les différentes plates-formes supportées : https://www.oracle.com/technetwork/java/javase/downloads/jdk11-downloads-5066655.html.

Ces deux distributions sont identiques en termes de fonctionnalités, Oracle ayant opensourcé les quelques fonctionnalités commerciales qui restaient dans Java (Java Flight Recording, Mission Control, CDS…).

D'autres vendeurs proposent leur propre distribution :

- RedHat via les repositories de leurs distributions : https://developers.redhat.com/products/openjdk/overview/ ;

- Azul qui a deux versions : Zulu (basé sur OpenJDK) qui est gratuite (avec support payant facultatif) et Zing qui est une JVM payante avec des composants spécifiques développés par Azul (G ) https://www.azul.com/ ;

- AdoptOpenJDK : https://adoptopenjdk.net/ ;

- …

Nous avons désormais le choix dans la distribution et dans le mode de support : communautaire ou payant (et fourni par plusieurs vendeurs différents !).

III - Les nouveautés de Java 9

III-A - La modularisation du JDK

Un système de module a été intégré à Java 9, le JDK en lui-même a été modularisé, ce qui permet de mettre des limites claires à chaque API qui déclare alors de quelles autres parties du JDK elle dépend et quels services elle expose. Le but de la modularisation est de permettre une évolution plus rapide du JDK, de pouvoir connaître plus facilement les dépendances entres les différentes parties du JDK et de pouvoir « cacher » les API internes du JDK. Cela ouvre aussi la porte à de nouvelles fonctionnalités telles que la possibilité de créer des bibliothèques Java natives, avec uniquement les parties du JDK utilisées par un programme, via l'outil jlink.

Bien que ce soit la principale nouvelle fonctionnalité de Java 9, ce n'est pas la seule et loin de là !

III-B - Un client HTTP/2 en stade expérimental

Ce nouveau client HTTP vient remplacer le très vieux HTTPUrlConnection intégré dans le JDK et quasiment jamais utilisé directement. Il implémente HTTP/2, propose une API facile d’accès et permet un mode asynchrone ou synchrone.

1. HttpClient.getDefault()

2. .request(URI.create("http://www.zenika.com"))

3. .GET()

4. .responseAsync() // CompletableFuture

5. .thenAccept(httpResponse ->

6. System.out.println(httpResponse.body(HttpResponse.asString()))

7. );

III-C - Une refonte de l'API Process via ProcessHandle

Gérer depuis java l'appel à un programme (process) externe a toujours été compliqué. Surtout si on veut faire des opérations qui semblent basiques, mais qui n'ont pas été prévues dans l'implémentation actuelle (récupérer le PID, killer un process, récupérer la ligne de commande…).

L'API Process a été grandement améliorée pour permettre toutes ces opérations, sur le process de la JVM (ProcessHandle.current()), ou un process fils créé via Runtime.getRuntime().exec("your_cmd"). De plus, comme toujours avec Java, c'est compatible avec les différents OS supportés par Java (et donc avec Windows ET Linux !).

En voici quelques exemples d'utilisations en Java 9 :

1. // Get PIDs of own processes

2. System.out.println("Your pid is " + ProcessHandle.current().getPid());

4. //start a new process and get PID

5. Process p = Runtime.getRuntime().exec("sleep 1h");

6. ProcessHandle h = ProcessHandle.of(p.getPid())

7. .orElseThrow(IllegalStateException::new);

9. // Do things on exiting process : CompletableFuture !

10. h.onExit().thenRun( () -> System.out.println("Sleeper exited") );

12. // Get info on process : return Optional!

13. System.out.printf("[%d] %s - %s\n", h.getPid(), h.info().user().orElse("unknown"),

14. h.info().commandLine().orElse("none"));

15. // Kill a process

16. h.destroy();

III-D - Méthode factory de création de collections

Ces nouvelles méthodes permettent de créer facilement des petites collections immuables.

1. List<Integer> listOfNumbers = List.of(1, 2, 3, 4, 5);

3. Set<Integer> setOfNumbers = Set.of(1, 2, 3, 4, 5);

5. Map<String, String> mapOfString =

6. Map.of("key1", "value1", "key2", "value2");

7. Map<String, String> moreMapOfString =

8. Map.ofEntries(

9. Map.entry("key1", "value1"),

10. Map.entry("key2", "value2"),

11. Map.entry("key1", "value3")

12. );

III-E - Une implémentation des Reactive Stream

La classe Flow propose trois interfaces pour implémenter vos stream réactives :

- Publisher : produit des messages que les subscriber vont consommer. La seule méthode est subscribe(Subscriber) ;

- Subscriber : souscrit à un publisher pour recevoir des messages (via la méthode onNext(T)), des messages d'erreur (onError(Throwable)), ou un signal indiquant qu’il n'y aura plus de messages (onComplete()). Avant toute chose, le publisher doit appeler onSubscription(Subscription) ;

- Subscription : la connexion entre un publisher et un subscriber. Le subscriber va l'utiliser pour demander des messages (request(long)), ou pour rompre la connexion (cancel()).

Un tutoriel est disponible ici : https://community.oracle.com/docs/DOC-1006738

III-F - jshell: The Java Shell (Read-Eval-Print Loop)

Dans le but de réduire le cérémonial nécessaire pour tester le langage Java, un shell (ou repl : read-eval-print-loop) a été introduit dans Java pour directement tester, depuis la ligne de commande, le langage Java.

Exemple ci-dessous.

III-G - Autres API

De nombreuses API existantes ont été améliorées par l'ajout de nouvelles méthodes, on peut citer :

- des ajouts à l'API de Stream : iterate(), ofNullable() et dans les Collectors : filtering(), flatMapping() ;

- des ajouts à Optional : or(Supplier), ifPresent(Consumer), ifPresentOrElse(Consumer, Runnable), stream() ;

- la création de Stream à partir d'une LocalDate : datesUntil().Performance.

Chaque version de Java se veut plus performante et moins gourmande en mémoire. Java 9 apporte aussi son lot de nouveauté sur ces sujets :

- Improve Contended Locking : optimisation des monitors Java (optimisation des lock) ;

- Compact Strings : revue de l'implémentation des chaînes de caractères (String) en Java pour en proposer une version plus compacte en cas de String ISO-8859-1 (ou Latin-1). Les String Java sont stockées en UTF-16 par défaut : chaque caractère est alors stocké sur deux octets. À la création de la String, si elle est compatible ISO-8859-1, elle sera stockée de manière compacte avec chaque caractère sur un octet et non deux ;

- Indify String Concatenation : optimisation de la concaténation des chaînes de caractères en interne de la JVM ;

- passage du garbage collector par défaut du Parallel GC au G1 GC.

IV - Les nouveautés de Java 10

IV-A - Local-Variable Type Inference

C'est la nouvelle fonctionnalité de Java 10 : l'introduction du mot-clef var, permettant de remplacer le type d'une variable locale, quand celle-ci peut être inférée par le compilateur.

Java reste un langage statiquement typé, mais quand on déclare une variable puis on l'instancie sur la même ligne, la déclaration de type est redondante ! Le mot-clef var va donc pouvoir remplacer le type.

À la compilation, var sera remplacé par le type de la variable par le compilateur : il n'y a donc pas de changement au runtime.

En Java 9 :

MyComplexType obj = new MyComplexType();
Map<String,List<MyComplexType>> map = new HashMap<String,List<MyComplexType>>();

En Java 10 avec le mot-clef var :

var obj = new MyComplexType();
var map = new HashMap<String,List<MyComplexType>>();

Stuart Marks a publié des modes d’emploi sur l'utilisation du mot-clef var :  http://openjdk.java.net/projects/amber/LVTIstyle.html

IV-B - Copy factory methods

L'API Collection a été enrichie de méthodes statiques permettant la copie de collections existantes : List.copyOf(), Set.copyOf() et Map.copyOf().

Dans les trois cas, la collection retournée sera une collection immuable. Si la collection existante était déjà une collection immuable, elle sera retournée directement (car il n'y a pas d'intérêt à créer une copie d'une collection immuable qui sera elle-même immuable…), sinon une nouvelle collection immuable sera créée.

IV-C - Collectors to unmodifiable List

Dans la même mouvance que le précédent changement, les collectors de l'API Stream ont été enrichis pour permettre la création de collections immuables :

- toUnmodifiableList()

- toUnmodifiableSet()

- toUnmodifiableMap(keyFunc, valueFunc)

- toUnmodifiableMap(keyFunc, valueFunc, mergeFunc)

V - Les nouveautés de Java 11

V-A - HTTP Client (Standard)

Le nouveau client HTTP, introduit en expérimental dans Java 9, a été inclus en standard dans Java 11, dans son propre package java.net.http.

Son implémentation a été totalement revue, elle est basée sur une API totalement asynchrone.

V-B - Launch Single-File Source-Code Programs

Dans le but de réduire le cérémonial nécessaire pour utiliser Java, on peut désormais lancer un programme basique Java (écrit dans un seul fichier .java) directement, sans passer par l'étape compilation, via la ligne de commande java.

Une compilation du programme en mémoire sera alors réalisée avant son exécution.

V-C - Les nouveaux GCs

Deux nouveaux Garbage Collector (GC) ont été intégrés à Java :

- Epsilon : un GC qui ne nettoie rien ! Il alloue les objets jusqu'à ce qu'il ne puisse plus, puis arrête la JVM. Dédié à une utilisation expérimentale dans le domaine du développement des GC ;

- ZGC (Zero GC) : sorti des laboratoires de recherche d'Oracle, Oracle Labs. Ce nouveau GC vise des pauses de moins de 10ms pour des applications pouvant utiliser jusqu'à des To de mémoire. Ce n'est pas un remplaçant de G1 mais un GC dédié aux très très grosses heap.

En plus de ces deux GC, un grand travail a été réalisé sur G1, le GC par défaut depuis Java 9, pour le rendre plus performant et moins gourmand en mémoire.

Selon l'auteur d'une partie de ces changements, en passant de Java 8 à Java 11, on aurait des pauses 60 % plus basses « gratuitement » pour une utilisation mémoire grandement réduite.

V-D - Autres API

De nombreuses API existantes ont été améliorées par l'ajout de nouvelles méthodes, on peut citer :

- méthodes pour comparer CharSequence, StringBuilder et StringBuffer : ajout de compareTo() ;

- isEmpty() : retourne true si la valeur n'est pas présente ;

- méthodes à nio.file.Files pour lire/écrire depuis une String ;

- not() : négation d'un prédicat ;

- convert(Duration) : conversion d'une Duration vers une TimeUnit ;

- String::repeat : créé une String qui est la répétition de la String sur laquelle la méthode est appelée ;

- String::strip, String::stripLeading, String::stripTrailing : créé une String en retirant les espaces (tels que définis par isWhitespace) de la String sur laquelle la méthode est appelée ;

- String::isBlank : retourne true si la String est vide ou ne contient que des espaces (tels que définis par isWhitespace) ;

- String::lines : retourne une Stream avec les lignes de la String. Plus performant que split car réalise le split de manière lazy.

VI - Conclusion

Le nouveau rythme des versions nous force à aller de l'avant et c'est tant mieux ! De plus en plus d'organisations contribuent au développement de Java dans ses fonctionnalités, via la diffusion de distributions et en offrant du support.

L'écosystème s'élargit !

Pour tirer parti des nouvelles fonctionnalités et des optimisations en termes de performance et de sécurité, il nous faut migrer au fur et à mesure sur ces nouvelles versions.

Encore heureux pour nous, nos pipelines modernes de développement et nos pratiques d'industrialisation nous permettent de le faire en toute sérénité via les outils à notre disposition : usine de développement (CI/CD), automatisation de la production (ansible, docker…), orchestration de la production (kubernetes), monitoring (Prometheus, Istio…) etc.

Donc, n'ayons pas peur et passons à Java 11 (version OpenJDK bien sûr) !

Et pour ceux qui auraient peur dans l'avenir de Java et dans la pérennité de son modèle Open Source, je les invite à lire la réponse d'experts reconnus dans la communauté sur le sujet : Java Is Still Free.

VII - Remerciements

Cet article a été publié avec l'aimable autorisation de Loïc Mathieu. L'article original (Java.Next : De Java 8 à Java 11 : Nouveautés et changements pour les développeurs) peut être consulté sur le blog/site de Zenika.

Nous tenons à remercier Escartefigue pour sa relecture orthographique attentive de cet article puis Winjerome et Mickael Baron pour la mise au gabarit.