Introduction aux modules Java (JPMS)
Ce que sont les modules Java, les problèmes que JPMS résout et sa relation avec le classpath.
Le Java Platform Module System (JPMS), introduit dans Java 9, ajoute une couche au-dessus des packages. Un module est un groupe de packages nommé et auto-descriptif qui déclare explicitement deux choses : ce dont il a besoin des autres modules, et ce qu'il leur offre. Cette déclaration réside dans un seul fichier, module-info.java, à la racine du module. Cette partie du livre l'explore en détail ; ce chapitre explique pourquoi il existe.
Le problème que JPMS résout : le classpath
Avant Java 9, chaque JAR était placé sur un classpath plat et unique. Cette organisation présentait des problèmes chroniques :
- Aucune encapsulation. Chaque classe
publicde chaque JAR était accessible par tous. Une classe destinée uniquement à un usage interne (sun.misc.Unsafe,com.example.internal.*) pouvait être utilisée par n'importe qui et ne pouvait donc jamais être modifiée en toute sécurité. - Aucune dépendance déclarée. Un JAR ne précisait jamais quels autres JARs il nécessitait. On découvrait une dépendance manquante seulement lorsqu'une
NoClassDefFoundErrorse produisait à l'exécution — potentiellement en production. - L'enfer des JARs. Deux JARs fournissant le même package, ou deux versions de la même bibliothèque, étaient silencieusement fusionnés selon l'ordre du classpath. La classe chargée en premier gagnait.
Les modules attaquent ces trois problèmes : un module cache tout package qu'il n'export pas explicitement, déclare chaque module qu'il requires, et la JVM vérifie l'ensemble du graphe au démarrage — les modules manquants ou dupliqués échouent rapidement.
Modules, packages et JARs
Ces trois concepts sont faciles à confondre :
| Concept | Ce qu'il regroupe | Règle de visibilité |
|---|---|---|
| Package | classes | public/protected/package-private au sein du JAR |
| JAR | packages + ressources | tout ce qui est public est visible sur le classpath |
| Module | packages | seuls les packages exported sont visibles pour les autres modules |
Un module est généralement empaqueté sous forme de JAR (un « JAR modulaire » — un JAR ordinaire avec un module-info.class à sa racine). La différence tient au descripteur : placez le JAR sur le classpath et les règles sont ignorées ; placez-le sur le module path et JPMS les applique.
L'encapsulation forte, en une phrase
La règle principale : un package est invisible pour les autres modules sauf si son module l'exports — même si ses classes sont public. public signifie désormais « accessible au code qui peut lire ce package », et lire un package nécessite un exports ainsi qu'un requires. C'est pourquoi le JDK lui-même a pu finalement cacher ses éléments internes : java.base exporte java.util mais pas jdk.internal.misc.
Le JDK est lui aussi modulaire
Depuis Java 9, le JDK est divisé en ~70 modules (java.base, java.sql, java.xml, java.net.http, …). java.base est spécial : il est implicitement requis par chaque module et contient les éléments essentiels du langage (java.lang, java.util, java.io). Chaque classe que vous avez jamais utilisée vit dans l'un de ces modules — ce que l'exemple pratique ci-dessous rend visible.
Exemple pratique : inspecter les modules à l'exécution
Il n'est pas nécessaire d'écrire un module pour voir les modules : l'API Module du runtime indique le module de n'importe quelle classe. Ce programme interroge plusieurs classes sur leur module d'appartenance, vérifie son propre module, et consulte la couche de démarrage que la JVM a initialisée.
Ce que retenir de l'exécution :
String,ArrayListetHttpClientont rapporté respectivementjava.base,java.baseetjava.net.http. Chaque classe appartient exactement à un module, etgetModule()vous l'indique — les types du langage vivent tous dansjava.base, tandis queHttpClientse trouve dans son propre module, que vous devriez déclarerrequires java.net.httppour utiliser.- La propre classe du programme a rapporté
isNamed() == falseet ungetName()valantnull. Le code exécuté depuis le classpath atterrit dans le module sans nom, un compartiment de compatibilité qui ne requiert rien explicitement et lit chaque autre module. C'est pourquoi les programmes utilisant le classpath continuent à compiler et s'exécuter sans changement sur Java 9+. ModuleLayer.boot()a exposé le graphe des modules que la JVM a résolus au démarrage — compter les modulesjava.*montre que le JDK est vraiment divisé en de nombreux modules, pas en un seul monolithe.java.basen'est pas ouvert (isOpen() == false) mais exporte de nombreux packages ; il exposejava.langetjava.utilà tous en gardantjdk.internal.*caché. Exporter un package et ouvrir un module sont deux interrupteurs distincts — un chapitre ultérieur reviendra suropens.- Rien ici ne nécessite un
module-info.java. L'API Module est une métadonnée réflexive disponible pour tout programme ; écrire votre propre module (chapitre suivant) est ce qui vous permet de déclarer ces règles plutôt que de simplement observer celles du JDK.
Ce que couvre le reste de cette partie
module-info.java— les directives :requires,exports,opens,uses,provides.- Types de modules — modules nommés, automatiques et sans nom, et comment ils se mélangent.
- Services — découpler une interface de son implémentation avec
uses/providesetServiceLoader. - Migration — déplacer une application classpath existante vers le module path sans réécriture complète.
Les modules sont facultatifs : une application Java peut fonctionner indéfiniment sur le classpath. Mais les comprendre explique le JDK moderne, débloque les runtimes personnalisés jlink, et donne aux bibliothèques une encapsulation réelle. Le chapitre suivant, la déclaration module-info.java, écrit le descripteur qui fait d'un module un module.