Boost Your Coding Skills: The Ultimate Guide to Java Modules


Java has been powering enterprise systems for decades, but until Java 9, it lacked a true modular system. Everything lived in the classpath, and every class could see every other class — even when it shouldn’t. This lack of encapsulation made systems harder to maintain, scale, and secure.
That changed with the Java Platform Module System (JPMS).
In this article, we’ll explore Java modules using a real-world e-commerce example, show how they improve architecture, and share tips to adopt them effectively in your codebase.
Why Java Modules?
Modules give you:
Strong encapsulation: Only explicitly exported packages are visible.
Reliable dependency management: No more "classpath hell."
Smaller, faster applications: Thanks to tools like
jlink
andjdeps
.Better security: Internal APIs are no longer accidentally accessible.
Think of modules as first-class citizens in your architecture — not just folders, but building blocks with contracts.
Real-World Example: E-Commerce Checkout System
To understand Java modules in a real context, let’s break down and build a modular E-Commerce Checkout System. This system follows clear separation of concerns by dividing responsibilities into distinct modules. This makes the application easier to maintain, test, and extend over time.
ecommerce-checkout/
├── checkout.service/ → Handles the checkout process and business flow
│ ├── module-info.java
│ └── com.store.checkout/
│ └── CheckoutManager.java
├── inventory.service/ → Manages product catalog and stock levels
│ ├── module-info.java
│ └── com.store.inventory/
│ ├── InventoryService.java
│ └── Product.java
├── common.utils/ → Provides shared utilities like logging and validation
│ ├── module-info.java
│ └── com.store.common/
│ ├── Logger.java
│ └── Validator.java
Module Responsibilities
Module | Responsibility | Exports | Requires |
common.utils | Shared helper utilities (e.g., logging, input validation) | com.store .common | — |
inventory.service | Inventory management (e.g., checking and updating stock) | com.store .inventory | common.utils |
checkout.service | Checkout flow (validation, stock check, payment logic) | com.store .checkout | inventory.service , common.utils |
Architecture Diagram :
Module 1: common.utils
module-info.java
javaCopierModifiermodule common.utils {
exports com.store.common;
}
Logger.java
javaCopierModifierpackage com.store.common;
public class Logger {
public static void log(String message) {
System.out.println("[LOG] " + message);
}
}
Validator.java
javaCopierModifierpackage com.store.common;
public class Validator {
public static boolean isValidQuantity(int qty) {
return qty > 0;
}
}
Module 2: inventory.service
module-info.java
javaCopierModifiermodule inventory.service {
requires common.utils;
exports com.store.inventory;
}
Product.java
javaCopierModifierpackage com.store.inventory;
public class Product {
private final String name;
private final int stock;
public Product(String name, int stock) {
this.name = name;
this.stock = stock;
}
public boolean isInStock(int quantity) {
return stock >= quantity;
}
public String getName() {
return name;
}
}
InventoryService.java
javaCopierModifierpackage com.store.inventory;
import com.store.common.Logger;
public class InventoryService {
public boolean checkAvailability(Product product, int qty) {
Logger.log("Checking inventory for " + product.getName());
return product.isInStock(qty);
}
}
Module 3: checkout.service
module-info.java
javaCopierModifiermodule checkout.service {
requires inventory.service;
requires common.utils;
exports com.store.checkout;
}
CheckoutManager.java
javaCopierModifierpackage com.store.checkout;
import com.store.inventory.InventoryService;
import com.store.inventory.Product;
import com.store.common.Validator;
import com.store.common.Logger;
public class CheckoutManager {
private final InventoryService inventoryService = new InventoryService();
public void checkout(Product product, int quantity) {
if (!Validator.isValidQuantity(quantity)) {
throw new IllegalArgumentException("Quantity must be greater than zero");
}
if (inventoryService.checkAvailability(product, quantity)) {
Logger.log("Checkout successful for: " + product.getName());
} else {
Logger.log("Checkout failed: Insufficient stock for " + product.getName());
}
}
}
Benefits of This Design
Encapsulation
Each module exposes only what’s necessary. No other module can access unexported internals.
Reusability
common.utils
can be reused in other parts of the system like payments or shipping.
Maintainability
Teams can work on modules independently. Changes in inventory.service
won't break checkout.service
unless APIs change.
Performance
Using tools like jlink
, you can package only the modules you need into a lightweight runtime image.
Best Practices for Working with Java Modules
Don’t export everything: Use
exports
selectively.Avoid circular dependencies: Keep your module graph clean.
Use
opens
only when required (e.g., for reflection in frameworks).Use
provides/uses
for dynamic service loading.Modularize incrementally: You can migrate existing codebase module-by-module.
Tooling That Helps
jdeps
: Analyze class dependencies.jlink
: Create custom JDK runtimes with only required modules.javac --module-path
: Compile modular projects.Maven/Gradle: Both support modular builds natively with a few tweaks.
When Should You Use Java Modules?
Use JPMS if you:
Are building libraries with a public API.
Want better separation in a large monolith or modular microservices.
Need secure or minimal JDK runtimes (e.g., IoT, Docker, or CLI tools).
Care about long-term maintainability, structure, and team autonomy.
You might skip JPMS if:
⚠️ You heavily rely on frameworks that use deep reflection (Spring Boot, Hibernate), unless you configure opens
properly.
⚠️ You’re building small, quick POCs that don’t need modular structure.
Wrapping Up
Java Modules are more than a language feature — they’re an architectural tool. They help you structure large systems, enforce contracts, and build safer, more maintainable applications.
If you're writing Java in 2025 and beyond, learning JPMS is worth it. Even if you're not ready to modularize everything today, understanding modules can improve how you design and organize code.
Subscribe to my newsletter
Read articles from Zied MELLITI directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Zied MELLITI
Zied MELLITI
As a Senior Software Engineering Consultant, I believe in mentoring and coaching others to help them reach their full potential and achieve their professional goals. I am dedicated to inspiring a culture of excellence, continuous learning, and collaboration, ensuring the delivery of high-quality software solutions. Throughout my career, I have collaborated with diverse teams to deliver successful software projects utilizing Agile methodologies. I have also led innovation initiatives to improve processes, tools, and technologies, driving efficiency and productivity. My experience and expertise have allowed me to develop a strong foundation in software engineering, project management, and innovation management.