1. Problèmes de l’ancienne approche
« Classpath party » : quand tout se retrouve dans la même cuisine
Avant l’apparition des modules dans Java (en version 9), tout le code de l’application, les bibliothèques et les dépendances étaient empilés dans un grand tas — le classpath. Il n’y avait pratiquement pas de frontières entre les bibliothèques : toute classe présente dans le classpath pouvait être trouvée et utilisée par n’importe qui.
Des conflits de noms apparaissaient : deux bibliothèques contiennent une classe portant le même nom complet, par exemple com.example.Util — « on en choisissait une au hasard », et vous ne découvriez le problème qu’au travers d’un comportement étrange au runtime.
Le grand classique — dependency hell : une bibliothèque exige un logger en version 1.2, une autre — 1.3, et les deux se retrouvent dans le classpath. La JVM ne sait pas laquelle prendre, et vous récoltez un mystérieux NoSuchMethodError.
Et encore : même si une classe est déclarée public mais destinée aux besoins internes d’une bibliothèque, un autre code peut quand même l’utiliser — et se casser quelque chose au passage. Les grands projets devenaient des champs de mines, et la maintenance — une aventure.
2. Qu’est-ce qu’un module en Java ?
Le module : comme une boîte percée de trous
Avec Java 9, un système modulaire est apparu (JPMS). Un module, c’est une boîte de classes et de packages dans laquelle vous faites vous‑même des « trous » : vous indiquez explicitement ce que vous exposez vers l’extérieur (exports) et ce que vous cachez à l’intérieur. Et oui : même une classe public n’est pas visible pour les autres modules si son package n’est pas exporté.
Définition formelle
Un module est une unité logique de découpage du code, réunissant des packages et classes liés. Chaque module déclare explicitement :
- ce qu’il exporte — rend accessible aux autres modules (exports) ;
- et ce qu’il importe — de quels autres modules il dépend (requires).
Propriétés clés d’un module :
- Frontières explicites. Il est clairement défini ce qui est accessible de l’extérieur et ce qui ne l’est pas.
- Dépendances explicites. Un module ne « voit » pas un autre module sans un requires explicite.
- Isolation. Les détails internes peuvent être entièrement masqués du monde extérieur.
3. Avantages de la modularité
Encapsulation améliorée
Un nouveau niveau de visibilité est apparu — au niveau du module. Même si une classe est public, mais que son package n’est pas exporté, elle n’est visible qu’à l’intérieur du module. Les entrailles de la bibliothèque ne « s’échappent » plus vers l’extérieur.
Description explicite des dépendances
Les dépendances nécessaires sont listées dans module-info.java. Le compilateur et l’IDE vous avertiront à l’avance si vous avez oublié d’indiquer un module dont vous dépendez.
Sécurité et fiabilité améliorées
La limitation de l’accès aux API internes complique toute intervention accidentelle ou intentionnelle. C’est crucial pour les grandes bibliothèques et les modules de plateforme.
Possibilité de créer des JRE « allégées »
Avec jlink, vous pouvez assembler un runtime minimal à partir des seuls modules nécessaires. Dans le cloud et les scénarios embarqués, cela économise de l’espace disque et de la mémoire.
Bonus : démarrage plus rapide et taille réduite
Seuls les modules réellement utilisés sont chargés — les applications démarrent plus vite et consomment moins de ressources.
4. Où les modules sont-ils utilisés
Dans la bibliothèque standard de Java
Depuis Java 9, la plateforme elle‑même est modulaire. Exemples :
- java.base — module de base (obligatoire pour tout programme).
- java.sql — accès aux bases de données.
- java.xml — traitement d’XML.
Si vous n’utilisez pas XML, le module java.xml n’entrera même pas dans votre runtime.
Dans les grandes applications et bibliothèques
Indispensable pour les systèmes d’entreprise, où des dizaines d’équipes développent des parties d’un monorepo. La modularité réduit les conflits et simplifie la maintenance.
Dans vos propres projets
Même dans un projet personnel, les modules aident à entraîner la pensée architecturale et à garder le code en ordre, en évitant les « spaghetti ».
5. Aperçu rapide de la syntaxe : module-info.java
L’essentiel — le fichier module-info.java
Le fichier module-info.java se trouve à la racine des sources du module et en déclare les frontières et dépendances.
module my.awesome.module {
exports com.example.api; // Package exporté
requires java.sql; // Dépendance à un module standard
}
Mots-clés principaux :
- module <nom> — déclare un module.
- exports <paquet> — rend un package accessible aux autres modules.
- requires <module> — déclare une dépendance à un autre module.
Exemple minimal :
module com.myproject.core {
exports com.myproject.core.api;
}
Exemple avec dépendance :
module com.myproject.app {
requires com.myproject.core;
requires java.sql;
}
Fonctionnalités supplémentaires (bref) : pour la réflexion, il y a opens, pour les services — uses et provides ... with ... (en détail — dans les cours avancés).
6. Nuances utiles
Les modules, c’est comme des « passeports » pour les classes. Autrefois, toute classe avec le visa public pouvait « voyager » dans l’application. Désormais, il faut aussi un « passeport » modulaire — l’export du package (exports). Sans cela, la classe reste « à la maison ».
Dependency hell est un véritable terme. Si vous avez vu ClassNotFoundException: com.google.common.base.Strings, vous y êtes déjà passé. Les modules sont faits pour chasser ces « démons » avec des frontières et des dépendances strictes.
Toute la plateforme Java est désormais modulaire. Même si vous n’écrivez pas vos propres modules, la plateforme est déjà découpée en dizaines d’éléments. Essayez la commande :
java --list-modules
Impact sur le développement
Vous gérez explicitement la visibilité du code, et les packages internes ne deviennent plus accessibles à tous « par accident ». L’IDE et le compilateur attrapent plus tôt les erreurs : si vous oubliez de déclarer une dépendance, le projet ne se construira pas. La maintenance de grands systèmes s’en trouve simplifiée : il est plus facile de comprendre qui dépend de qui et ce qui cassera en cas de changements.
7. Erreurs typiques lors du passage aux modules
Erreur n° 1 : vous avez oublié d’exporter le package, mais la classe est public. Vous avez déclaré une classe public, mais vous n’avez pas exporté son package — les autres modules ne pourront pas l’utiliser. Le compilateur indiquera : « Le package n’est pas exporté par le module ».
Erreur n° 2 : vous n’avez pas déclaré la dépendance via requires. Vous utilisez un type d’un autre module, mais vous avez oublié d’ajouter requires dans module-info.java. Résultat : erreur de compilation « le module ne peut pas trouver la classe requise ».
Erreur n° 3 : noms de modules en double. Dans un grand projet, deux modules ont accidentellement reçu le même nom. La JVM ne pardonne pas cela — renommez et respectez une convention de nommage cohérente.
Erreur n° 4 : oubli des modules standard. Par exemple, vous utilisez JDBC, mais vous n’avez pas ajouté requires java.sql;. En Java 8, « c’était visible quand même », mais en 9+ — non.
Erreur n° 5 : tentative d’utiliser une classe interne d’un autre module. Si le package n’est pas exporté, même une classe public reste invisible de l’extérieur. Exposez le package ou placez l’API dans une couche « publique ».
GO TO FULL VERSION