logo le blog invivoo blanc

De JAVA 9 à JAVA 15 : Évolutions et nouveautés

30 septembre 2021 | Design & Code | 0 comments

Introduction : de Java 9 à Java 15

Utilisée par neuf millions de développeurs à plein temps, selon le dernier rapport d’IDC (International Data Corporation), Java reste un des langages le plus populaire et le plus utilisé dans le monde d’entreprise.

En effet, aujourd’hui, selon ORACLE, on compte 9 millions de développeurs Java (dans le monde et près de 51 milliards de JVM actives. 

Pour conserver cette popularité et pour élargir d’avantage la communauté Java, ORACLE a accéléré le rythme des releases Java depuis la sortie de Java 9 en septembre 2017.

Au lieu de fournir des dizaines de milliers de correctifs et de nouvelles fonctionnalités dans une seule release tous les trois ans, des améliorations sont maintenant livrées dans des releases chaque six mois et une release LTS (long time support) est disponible tous les trois ans.

L’objectif est d’assurer une meilleure productivité aux développeurs et de leur permettre de s’adapter aux nouvelles pratiques du marché, tout en offrant une prévisibilité et une stabilité continue.

Cela facilitera également la migration vers des nouvelles versions et élargira la communauté Java et cet enjeu est déjà en œuvre avec Java 15 où plus de 20% des correctifs sont apportés par des développeurs non ORACLE.

Ce changement de rythme après Java 8 a été assez soudain et beaucoup d’entreprise n’ont pas encore emboîté le pas de ce nouveau rythme, par crainte ou par méconnaissance des bénéfices apportées par chacune des nouvelles versions. Cet article a pour but de donner une vue d’ensemble sur les différentes évolutions qui ont été amenées par les versions de Java 9 à Java 15 (sorti en septembre dernier) afin qu’elles se préparent au mieux à la migration de leur SDK.

Dans une première partie, nous allons nous intéresser aux évolutions techniques du langage qui ont simplifié la vie du développeur et amélioré son quotidien. Ensuite, nous allons étudier les différentes améliorations apportées à la JVM, notamment les « Garbage Collectors » récemment disponibles. Enfin, nous allons finir avec une troisième partie dédiée aux modules et aux API supprimés ou à supprimer du JDK. Nous conclurons avec quelques recommandations pour que vous puissiez aborder votre migration en toute sérénité. Si vous souhaitez être accompagné sur vos problématiques Java vous pouvez aussi vous rapprocher de notre expertise Java.

Partie 1 : Améliorations techniques 

1.Les blocs de texte (MODE Standard depuis Java 15)

Les chaînes littérales dans la programmation Java ne sont pas limitées à des chaînes courtes mais peuvent aussi correspondre à des descriptions en XML, des requêtes SQL, des pages web en HTML, etc.

Elles peuvent contenir alors plusieurs séquences d’échappements, des retours en lignes, des caractères spéciaux…

Prenons l’exemple d’une page web en HTML : 

String html = "<html>\n" +
              "    <body>\n" +
              "        <p> Java, technical Enhancement </p>\n" +
              "    </body>\n" +
              "</html>\n";

Ces écritures sont lourdes et peuvent également être une source d’erreur pour le développeur.

Pour résoudre ce problème, Java 13 a apporté la nouvelle notion de « blocs de texte » bidimensionnels en mode préview et cette notion a été standardisée avec Java 15. Voici à quoi ressemblera l’exemple précédent :

String html = """  //Retournez à la ligne 
              <html>
                  <body>
                      <p> Java, technical Enhancement </p>
                  </body>
              </html>
              """;

Dans le même contexte, de nouvelles méthodes sont introduites par JAVA 13 pour la classe String :

  • String::translateEscape : retire les séquences d’échappement dans une chaîne de caractères
  • String::stripIndent : supprime l’indentation accidentelle au début de chaque ligne.
  • String::formatted : permet de formater un string selon les paramètres passés.

On trouve aussi d’autres méthodes pratiques introduites précédemment par Java 11 :

  • String::isBlank, String::repeat,
  • String::lines : retourne une stream à partir des lignes

String::strip, String::stripLeading , String::stripTrailing : permettent une meilleur gestion des espaces.

2. Les switchs (MODE Standard depuis Java 14)

Les blocs de textes ne sont pas les seuls à être assez verbeux en Java, le « switch » est aussi une instruction qui s’écrit historiquement sur beaucoup de lignes.

Voilà à quoi ressemble un « switch » avant Java 12 :

    switch (day) {
        case MONDAY:
        case TUESDAY:
        case THURSDAY:
            planning = "Training";
            break;    
        case SUNDAY:
        case SATURDAY:
            planning = "Work";
            break;
        case WEDNESDAY:
        case FRIDAY:
            planning = "Weekend";
            break;
        default:
            throw new IllegalStateException(day+ "does not exist");
    }

Après Java 12, on a quelque chose de beaucoup plus concis :

  int planning = switch (day) {
    case MONDAY, TUESDAY, THURSDAY -> "Training"; // “case” accepte plusieurs valeurs séparées par des virgules 
    case WEDNESDAY, FRIDAY                     -> "Work"; // il est possible d’utiliser l’operateur “arrow”
    case SATURDAY, SUNDAY                       -> "Weekend"; // pas besoin de mettre “default” pour les enums si on couvre tous les cas  
    };

Il est possible également de mettre un bloc de code dans la close case. Java 13 a apporté à son tour un nouveau mot clé « yield » qui vient remplacer break et qui permet de sortir du switch. Ces nouveautés sont passées en mode standard avec Java 14.

3. VAR pour les variables locales (MODE Standard depuis java 10)

Prenons l’exemple suivant :

BufferedReader reader = Files.newBufferedReader(...);
List<String> programmingLanguage = List.of("java", "python", "c++",”javaScript”);
Map<String, List<String>> mapping = retrieveWritersBooksMap();  

Après Java 10, il pourra s’écrire comme ceci :

var reader = Files.newBufferedReader(...);
var stringList = List.of("java", "python", "c++",”javaScript”);
Map<String, List<String>> mapping = retrieveWritersBooksMap();  

L’inférence des types des variables locales est la plus grande nouveauté de Java 10. Elle permet d’éviter la redondance vue dans les exemples précédents et simplifie l’affichage pour les types compliqués.

Attention, l’utilisation excessive de var peut créer une confusion au niveau du compilateur. Pour cela Java a imposé quelques restrictions pour son utilisation. Voilà quelques exemples de code non autorisé :

var value ;  //il faut obligatoirement initialiser les variables
var object = null ; //il faut obligatoirement initialiser les variables
var a=1,b=2 ; // impossible de déclarer plusieurs variable sur la même ligne
var words =   {“word1”, ”word2”}; // l’initialisation d’un tableau nécessite un type explicite
var addition =  {a, b} -> a+b; // les lamdas expressions nécessitent un type explicite mais il est possible de caster 
var compareString = String ::compareTo ; // les méthodes référence nécessitent un type explicite mais il est possible de caster (avec Comparator<String> dans l’exemple)
var value = 10 ; value = ’’hello ‘’ ; // impossible de transformer une var déjà typée

Avec Java 11, Il est possible d’utiliser var à l’intérieur des fonctions lambdas. Cette utilisation présente un avantage majeur car ça permet d’annoter les paramètres.

(@Nonnull var x, @Nullable var y) -> x.process(y)

4. Les streams

La nouvelle API stream introduite par Java 8 a modifié fondamentalement la façon de traiter les collections en proposant une alternative plus simple et plus performante pour le pattern « Iterator », relativement lourd à mettre en place.

Java 9 a fourni à son tour de nouvelles méthodes à cette API, voici quelques exemples :

  • Stream::takeWhile : permet de parcourir une collection en utilisant un « prédicat ». On parcourt la collection tant que la condition est validée.
  • Stream::dropWhile : suit le fonctionnement inverse de takeWhile. On ne commence à parcourir la collection que si la condition est validée.
  • Stream::Iterate : permet d’itérer sur une collection en précisant la valeur de départ, un prédicat et une fonction d‘incrémentation.
  • Stream::ofNullable : permet d’éviter les NPE en renvoyant une interface “Optional” quand la valeur est nulle.

Java 11 a introduit les Null/InputStream, OutputStream, Reader, Writer qui permettent d’initier un stream avec Null sans générer de NPE. Il est donc possible de traiter d‘une manière transparente des input/output même s’ils représentent des flux d’entrée/ sortie nulles.

Pour finir avec les « streams », Java 12 a introduit la méthode Stream::teeing sur l’interface java.util.stream.Collectors. Elle prend en entrée deux collections indépendantes et permet de les combiner en utilisant une bi-fonction :

     Double average = numbers.stream()
                                  .collect(teeing(
                                  SummingDouble(i -> i),
                                  counting(),
                                 (sum, n) -> sum / n));

5. Les classes « Sealed » (MODE PREVIEW)

Le mot clé sealed est l’une des plus importantes nouveautés de java 15. Il a pour objectif de restreindre l’implémentation ou l’héritage d’une classe/interface à une liste de classes définie par le mot clé « permits » :

public abstract sealed class Shape
    	permits Circle, Rectangle, Square {...}

Si les classes sont déclarées dans le même fichier source, la close « permits » peut être retirée.

Il faut garder à l’esprit que cette nouveauté reste toujours en mode « preview ». Elle sera éventuellement standardisée avec les versions suivantes.

6. Les classes records (MODE PREVIEW)

Les records sont des POJO moins verbeux. En effet, ils permettent de réduire la quantité de code requise pour créer une classe (constructeur, accesseurs getter/ setter, les méthodes equals(), hashcode()) et le temps nécessaire pour la maintenir à chaque fois qu’un nouvel attribut est créé.

Voici un exemple :

public record Person (String firstName, String lastName) {}

Ils étaient initialement introduits par Java 14, mais restent toujours en mode « preview » en s’adaptant aux améliorations apportées par Java 15. En effet, un record pourra implémenter une sealed interface, et peut être déclaré localement à l’intérieur d’une méthode.

7. Null Pointer Exception (MODE STANDARD depuis Java 15)

String emailAddress = employee.getPersonalDetails().getEmailAddress().toLowerCase();

Comme dans cet exemple, un développeur peut souvent écrire un code qui enchaîne plusieurs méthodes. Mais lorsqu’une NPE est générée, il peut devenir difficile de savoir d’où provient l’erreur.

Exception in thread "main" java.lang.NullPointerException
at com.enhancement.DemoNullPointerException.main(HelpfulNullPointerException.java:10)

Avec Java 14, la JVM fournit un message plus explicite indiquant exactement d’où vient l’erreur.

Exception in thread "main" java.lang.NullPointerException: 
Cannot invoke "String.toLowerCase()" because the return value of 
"com.enhancement.DemoNullPointerException$PersonalDetails.getEmailAddress()" is null
at com.enhancement.DemoNullPointerException.main(HelpfulNullPointerException.java:10)

Cette amélioration passe en mode standard avec Java 15.

8. JShell REPL « Read Evaluate Print Loop » (MODE STANDARD depuis Java 9)

« JShell », introduit par Java 9, est un outil de commande en ligne qui permet d’évaluer le code Java.

En effet, plus besoin de créer tout un programme (importer des bibliothèques, définir une classe avec une méthode main(), etc.) pour tester une simple expression. Cela parait utile surtout pour les développeurs qui débutent avec le langage Java.

Voici un exemple de code exécuté avec Jshell :

.. >jshell
|  Welcome to JShell -- Version 9
|  For an introduction type: /help intro

jshell>public long multiply(long n , long m ){
   ...>    return m * n ;
   ...> }
|  created method multiply(long ,long)
jshell>long result = multiply(20 , 10 )
result ==> 200
|  created variable result : long

« Jshell » fournit une liste de commandes parmi lesquelles, on trouve :

/set feedback verbose : permet d’obtenir plus d’informations sur les commandes exécutées.
/list -start : liste toutes les commandes Jshell.
/drop [nom_variable] : permet de supprimer la variable créée.
/vars : permet d’afficher la liste de toutes les variables actives dans la session en cours.
/vars [Nom_variable] : permet d’afficher la variable [Nom_variable] et sa valeur.
/vars – all : permet d’afficher la liste de toutes les variables actives, inactives et chargées au démarrage.

/types : permet de lister l’ensemble des types (Class, Interface, Enum) actifs créés dans JShell.
/types [Nom_Type] : permet d’afficher le type correspondant à [Nom_Type].
/types -all : permet de lister l’ensemble des types de la session en cours (actifs, inactifs et chargés au démarrage de JShell).

/edit : permet de modifier les constructeurs dans la session en cours.
/edit 1 :  permet de modifier le premier constructeur dans la session en cours.
/edit [Nom-constructeur] : permet de modifier un constructeur déterminé dans la session en cours.
/exit: permet de quitter JShell.

Pour finir avec cette partie, voici brièvement quelques autres améliorations qui peuvent vous intéresser :

  • Les méthodes « private » dans les interfaces : Java 9

Cela Permet de faciliter l’encapsulation et éviter de dupliquer certaines parties du code et d’exposer uniquement les méthodes souhaitées.

  • Des fabriques pour des collections immutables : Java 9

 List.of(),Set.of(),Map.of().

List<String> availableGC= List.of("GC1", "ZGC", "EPSILON");
  • Les variables finales dans Final variables in « try-with-resources » Java 9 : Il est possible de déclarer les ressources « Closeable » à l’extérieur du bloc « try » si elles sont finales. Il est également possible pour faciliter la lisibilité de créer des méthodes utilitaires pour l’instanciation des ressources.
  • Predicate ::not Java11 : fournit un moyen facile d’inverser la valeur d’un « Predicate » exprimé sous la forme de lambdas ou de références de méthodes, ce qui réduit la complexité du code.

Partie 2 : Amélioration de la JVM

Quand on rencontre des problèmes de performance (une application qui met beaucoup de temps pour démarrer, des erreurs de type Java OutOfMemoryError ou autre), on pense souvent au code (améliorer sa qualité, optimiser les algorithmes, choisir les bonnes collections, etc.) mais on oublie souvent le choix du « garbage collector ».

Dans cette partie, on va se concentrer sur les améliorations de performances apportées par les dernières versions de Java, notamment les nouveaux «garbage collectors» et l’archivage des classes en Java.

1. Garbage Collector

Au cours de son cycle de vie, une application crée un certain nombre d’objets, dont la durée de vie varie selon son rôle au sein du programme.

Cette durée de vie est définie par un “compteur de référence”. Un objet dont le compteur de référence est à zéro est un objet non utilisé.

Un « garbage collector » permet d’identifier puis de supprimer ces objets non utilisés (ou déchets). Historiquement, il divise la mémoire en deux zones : la « YoungGen », qui stocke les objets récents, et l’«OldGen », qui stocke ceux à durée de vie longue. La libération de ces zones (collecte ou GC pour Garbage Collection) se fait ensuite suivant des algorithmes comme le Comptage de références, algorithme « Mark and Sweep », algorithme « Stop and Copy », etc.

Dans cette partie de l’article, on va parler des nouveaux GC (Garbage collectors) implémentés depuis Java 9.

EPSILON No-Op Garbage Collector

Introduit par Java 11, Epsilon est un GC no-op (passif). Il gère seulement les allocations mémoire mais ne permet pas le nettoyage des objets non utilisés. Quand le tas (« heap ») alloué par l’application est épuisé, la JVM s’arrête.

Epsilon est utilisé dans le cas des applications à courte durée, sans déchet ou quand on sait que la mémoire allouée (taille de heap) est largement suffisante pour l’application en cours.

Il peut être également utile pour réaliser des tests de performance (test des nouveaux algorithmes de GC, test de pression de la mémoire, etc.).

G1 Garbage First :

Le garbage collector « Garbage-first », utilisé par défaut à partir de Java 9, fonctionne principalement avec les threads d’application (comme le CMS) mais il permet d’offrir des temps de pause plus courts et plus prévisibles.

En effet, au lieu de diviser le tas en 2 grandes régions, il le divise en petit lots de taille égale. Les données utilisées dans chaque lot sont tracées. Lorsqu’une collecte est déclenchée, le G1GC effacera en “premier” les lots qui contiennent le plus de « déchets » – d’où son nom « first ».

Mais le G1 n’est pas optimal dans toutes les situations. En effet, s’il n’arrive pas à récupérer rapidement la mémoire non utilisée, il arrête les threads d’application pour faire un full GC.

Avec Java 10, au lieu d’utiliser un seul thread lors du full GC, il est devenu possible de lancer plusieurs threads en parallèle (Parallel full GC)

On peut alors personnaliser le nombre de threads utilisés avec l’option « -XX : ParallelGCThreads »

l existe également 2 améliorations importantes apportées par Java 12 à ce GC :

  • G1 commence par définir le temps nécessaire pour faire une collection (“collection set“). Une fois démarrée, G1 doit collecter tous les objets utilisés sans s’arrêter. Mais cela peut prendre beaucoup de temps si la “collection set” est trop grande. Pour résoudre ce problème, avec Java 12, G1 divise la collection set en deux parties : une partie obligatoire et une partie optionnelle qui ne sera effectuée que si le GC ne dépasse pas le temps de pause prévue.
  • Java 12 a fourni également une deuxième amélioration. Le G1, retourne automatiquement la mémoire non utilisée aux systèmes d’exploitation (non seulement lors du full GC comme avec les anciennes versions de Java).

Avec Java 14, G1 est devenu « NUMA-Aware » (NUMA : Non Uniform Memory Access) en utilisant l’option « +UseNUMA »

Cette fonctionnalité cible principalement les machines ayant plusieurs sockets ou un grand nombre de cœurs.

ZGC (Concurrent Garbage Collector)

ZGC est un GC évolutif et à faible latence. En effet, ses temps de pause n’excèdent pas les 10 millisecondes et son débit de réduction d’application est inférieur à 15% par rapport au G1.

Lors de son lancement avec Java 11, ZGC ne renvoyait pas la mémoire au système d’exploitation même si elle n’a pas été utilisée depuis longtemps. Java 13 a fourni cette nouvelle fonctionnalité. Cela est utile dans le cas des applications où l’empreinte mémoire pose problème, ou dans le cadre d’un système avec plusieurs applications actives.

ZGC se base sur des pointeurs de couleurs pour stocker des informations relatives au marquage et à la relocation de mémoire. Ça permet de garder tout type d’information et donc d’agir selon ces données (cela n’est possible qu’avec un processeur 64 bit).

Il faut noter aussi qu’à partir de Java 14, il est disponible avec « macOS » et « Windows ».

ZGC est aujourd’hui stable, performant, à faible latence et prêt à être utilisé en production à partir de Java 15.

Il pourrait rivaliser avec « Shenandoah », également destiné aux applications à grand segment de mémoire.

Shenandoah (Concurrent Garbage Collector)

Java 12 a introduit « Shenandoah » (il peut être configuré avec Java 8).

« Shenandoah » permet de diminuer le temps de pause du collecteur.

En effet, ses tâches (marquage, libération de mémoire, compactage) sont exécutées dans des threads concurrents aux threads applicatifs utilisés par le programme en cours.

Il est surtout utile dans le cas des applications nécessitant un temps de réponse rapide et prédictible. Il faut noter aussi que la taille des « heaps » n’affecte pas le temps de pause.

Ce GC est maintenant fourni en mode standard avec Java 15.

CMS Concurrent Mark and Sweep

Après avoir été déprécié avec Java 9, CMS est finalement supprimé avec Java 14. Cette décision a pour but de réduire la charge de maintenance de la base du code GC et accélérer le développement des nouveaux algorithmes.

Comparaison des nouveaux GC disponibles

Pour choisir le meilleur GC pour une application Java, trois aspects importants doivent être pris en compte :

  • Taille de la « heap » : taille de mémoire nécessaire pour faire exécuter une application
  • Le temps de pauses de l’application : le temps nécessaire pour le GC pour exécuter ses tâches (principalement le full GC).
  • « Throughput » ou débit de l’application : la vitesse à laquelle une application Java s’exécute (Considérer le temps nécessaire pour effectuer les tâches du GC et le temps consacré pour exécuter le code).

2. Système d’archivage

En Java, pour exécuter le « bytecode » d’une classe donnée, la JVM effectue quelques étapes préparatoires. En effet, en se basant sur le nom de la classe, la JVM la cherche sur le disque, la charge, vérifie son « bytecode » et puis la met dans une structure de données interne.

 Cela peut prendre beaucoup de temps lors du lancement de l’application (chargement d’une centaine de classes, voir plus) ou l’exécution d’une nouvelle fonctionnalité pour une première fois.

Ces opérations sont inutiles tant que les jars de l’application ne changent pas. En effet, la JVM exécute les mêmes étapes pour finir avec les mêmes résultats à chaque fois que l’application est lancée.

La notion de CDS archive «class data-sharing », introduite par Java 8, vient résoudre ce problème. 

Default CDS archives

L’idée des CDS consiste à créer des jars une seule fois, les enregistrer dans une archive, et les réutiliser ultérieurement lors des prochains lancements de l’application.

Cette archive peut être partagée par les instances de JVM exécutées simultanément. Cela permet d’économiser la mémoire autrement gaspillée dans la réplication des données pour chaque instance de JVM.

Pour bénéficier des default CDS, il faut suivre ces trois étapes :

  • ⁃                     Créer une liste de classe pour les mettre dans l’archive : -XX:DumpLoadedClassList
  • Créer une archive : -Xshare:dump et -XX:SharedArchiveFile
  • L’utiliser avec l’option : -Xshare:on et -XX:SharedArchiveFile

JDK 10 vient avec une liste de classes à archiver. La première étape peut donc être ignorée à partir de Java 10. L’archivage des classes JDK (defaults CDS) est appliqué automatiquement à partir de Java 12.

AppCDS : Application class data sharing

« AppCDS » concerne l’archivage des classes de l’application et non seulement celles du JDK.

Avant Java 13, la création et l’utilisation d’une archive pour les classes de l’application devait suivre la même logique que les classes JDK.

Cependant, avec cette version, il est devenu possible de combiner les deux premières étapes et faire un archivage dynamique des classes après l’exécution de l’application (« Dynamic CDS »).

En ajoutant l’option -XX: ArchiveClassesAtExit, la JVM enregistrera toutes les classes chargées de l’application et les placera dans une archive dédiée.

Les classes archivées incluront donc les classes d’application et les classes de bibliothèque qui ne sont pas présentes dans l’archive CDS par défaut.

L’« AppCDS » fournit des avantages supplémentaires en matière de temps de démarrage et de mémoire par rapport à l’archive CDS par défaut.

Les métadonnées des expressions « lambdas » sont rajoutées à l’archive « AppCDS » à partir de Java 15

3. Compact String

Avant Java 9, les strings étaient stockés dans un tableau de char où chaque caractère prenait deux octets (1 char = 2 octets).

Avec Java 9, il y’a eu un changement d’implémentation des strings. En effets, elles sont maintenant stockées dans un tableau d’octets et chaque caractère a besoin seulement d’un seul octet.

Mais cela n’est possible que si elles compatibles ISO-8859-1, ce qui est souvent le cas.

Sinon, les caractères seront quand mêmes stockés dans un tableau d’octets, mais il faudra deux octets pour chaque caractère. Un flag est ajouté à la classe String pour connaitre le codage de celle-ci.

La compacité des strings a permis une diminution de presque 50% de la taille nécessaire pour stocker les strings. Les strings sont la principale source d’utilisation de la mémoire (25% des objets sont des String). Cela a permis donc de réduire la taille de la « heap » utilisée (Gain de 5% à 15%) et donc d’améliorer la performance de la JVM.

Ce changement d’implémentation disponible avec Java 9 était fait sans modification des interfaces publiques existantes ni la création d’une nouvelle API ou autre.

4. Java Flight Recorder et Java Mission Control

Java Flight Recorder (JFR) collecte les données de diagnostic et de profilage d’une application Java en cours d’exécution. JFR a un impact minime sur une application Java en cours d’exécution.

Java Mission Control (JMC) permet d’analyser les données collectées par JFR. Il les affiche sous forme graphique et permet donc de les explorer en détail.

JFR et JMC étaient des fonctionnalités commerciales dans Java 8, et passent en mode open source avec Java 11. Avec ces outils, on peut diagnostiquer les problèmes de « runtime » comme la surcharge de GC, la fuite mémoire, etc. 

Modules et classes supprimés

Avec les dernières versions de Java, plusieurs modules ou API étaient dépréciées et même supprimées.

En effet, La dépréciation encourage les applications à s’éloigner de ces APIs et donc d’éviter d’y rajouter toute dépendance. L’outil « jdeprscan », fourni par Java 11, permet aux développeurs d’effectuer une analyse statique de leurs fichiers jar (ou autre agrégation de fichiers de classe) pour identifier les utilisations des APIs obsolètes, leur permettant ainsi de se préparer à l’avance pour une éventuelle migration vers une nouvelle version de Java.

Java 11 a introduit également un nouvel outil « Jdep» qui permet d’analyser les dépendances de l’application y compris les dépendances vers des APIs internes. Il fournit également une suggestion sur ce qu’il faut utiliser en remplacement.

Voilà quelques modules supprimés par les dernières versions de Java :

Java 11

Les modules contenant Java EE et Corba sont dépréciés avec Java 9 et supprimés avec Java 11 :

  • java.xml.ws (JAX-WS, Java API pour XML-Based Web Services)
  • java.xml.bind (JAXB, Java Architecture pour XML Binding)
  • java.activation (JAF, JavaBeans Activation Framework)
  • java.xml.ws.annotation 
  • java.corba (CORBA)
  • java.transaction (JTA)
  • java.se.ee (module agrégateur pour les six précédents modules)
  • jdk.xml.ws (outil pour JAX-WS)
  • jdk.xml.bind (outil pour JAXB)
Java 14
  • La combinaison des deux algorithmes « Parallel Scavenge » et « Serial Old garbage collection » est dépréciée (très peu utilisée mais nécessitant un effort de maintenance important)
  • CMS est supprimé et remplacé par G1
  • « pack200 », « unpack200 » et « Pack200 API » (dans java.util.jar package) utilisés par les développeurs pour compresser et décompresser les fichiers jar
Java 15
  • Le mécanisme d’activation RMI (Remote Method Invocation) est déprécié et il sera supprimé dans une version future
  • Le moteur de script et les API JavaScript « Nashorn », ainsi que l’outil jjs sont supprimés
  • Le code source et la prise en charge de build pour les ports « Solaris / SPARC », « Solaris / x64 » et « Linux / SPARC » sont supprimés. Ces ports ont été marqués comme obsolète dans JDK 14

Conclusion de Java 9 à Java 15 :

Cet article a exploré les nouvelles fonctionnalités fournies par Java pendant les trois dernières années, ainsi que les nouveaux outils et les améliorations de performance, en particulier les « Garbage Collectors » et l’archivage des classes.

Afin de bénéficier de ces nouveautés techniques et de ces optimisations de performance et de sécurité, la migration vers une version récente de Java paraît nécessaire. Pour ce faire, plusieurs aspects doivent être étudiés. Premièrement, comme expliqué dans la dernière partie de l’article, plusieurs modules ont été supprimés du JDK (en effet, la dépréciation est prise beaucoup plus au sérieux dans les dernières versions de Java). Les dépendances vers ces modules dépréciés ou vers des bibliothèques tierces doivent alors être étudiées avant tout changement. De plus, le «garbage collector» utilisé par défaut dans les versions Java n’est pas toujours le même. Il faut bien penser à faire une étude de performance de l’application avant toute migration. Par ailleurs, Java a mis à la disposition des développeurs une multitude d’outils permettant de faciliter et d’alléger ce changement comme «jdeprscan», et «jdep» qui permettent d’analyser les dépendances de l’application,

«jLink» qui permet de packager et de déployer les éléments nécessaire du JDK dont on a vraiment besoin, «JEnv» qui facilite la gestion de plusieurs installations de version de JDK etc.

Mais comment choisir sa version de Java ? Pour pouvoir suivre cette cadence de releases et être à jour avec les dernières nouveautés, les entreprises doivent trouver un compromis entre la gratuité, la stabilité et la sécurité. Les versions LTS sont gratuites et plus stables, mais elles sont fournies chaque trois ans et les mises à jour de sécurités sont payantes. Les versions non-LTS (sorties tous les six mois) fournissent plus de nouveautés techniques, ainsi que des améliorations de performance et de sécurité. Cependant, elles sont jeunes, moins stables et leurs fonctionnalités, notamment celles en « Preview », peuvent être modifiées voire supprimées.

Il faudra alors définir clairement vos priorités en termes de « gratuité-stabilité-sécurité » et mettre en place les garde-fous nécessaires (par exemple ne pas utiliser de fonctionnalité en mode Preview) et un comité en charge du suivi et de la communication sur ces versions pour vous lancer la mise à jour de vos SDK. En effet, ce sera un incontournable si vous ne voulez pas refaire votre parc applicatif. À vous de jouer 🙂

Vous avez apprécié cet article de Java 9 à Java 15 et vous désirez apprendre l’art du “Clean Code” en environnement Java ? Rendez-vous sur notre article dédié !