A common mistake developers encounter when migrating to Java Modules is the split package problem. On the classpath, having the same package spread across multiple JARs usually worked fine—though it often introduced hidden risks. But on the module path, the Java Platform Module System (JPMS) strictly prohibits split packages, leading to compilation or runtime errors like:
Error: Packages com.example.utils in module A and module B conflict
This matters because in large enterprise systems or legacy migrations, it’s common for utilities or shared packages to be scattered across different libraries. Without resolving these conflicts, modularization fails, breaking builds and deployment pipelines.
In this tutorial, we’ll explore what split packages are, why they are disallowed, and how to resolve them effectively.
What Are Split Packages?
A split package occurs when the same package name exists across multiple modules.
Example (Classpath World – Works)
app.jar → com.example.utils.StringUtils
lib.jar → com.example.utils.DateUtils
On the classpath, both StringUtils
and DateUtils
are visible to all code.
Example (Module Path – Fails)
moduleA → com.example.utils.StringUtils
moduleB → com.example.utils.DateUtils
JPMS error:
Packages com.example.utils in moduleA and moduleB conflict
Why JPMS Disallows Split Packages
- Strong Encapsulation → Prevents ambiguity about which class is being loaded.
- Predictability → Eliminates runtime surprises from duplicate packages.
- Security → Reduces the risk of malicious JARs injecting classes into trusted packages.
Real-World Example
Imagine a banking system:
user-module
containscom.example.common.UserValidator
.transaction-module
also containscom.example.common.TransactionLogger
.
Both declare com.example.common
, creating a split package conflict.
Resolution Options
- Refactor Packages
// user module
module com.example.user {
exports com.example.user.validator;
}
// transaction module
module com.example.transaction {
exports com.example.transaction.logger;
}
Move UserValidator
into com.example.user.validator
and TransactionLogger
into com.example.transaction.logger
.
- Merge Modules
If the code is tightly coupled, merge into a single common
module.
module com.example.common {
exports com.example.common.validator;
exports com.example.common.logger;
}
- Use Services
Define service interfaces in one module and implement them in others using provides
and uses
.
Pitfalls & Misuse Cases
- Leaving Split Packages Unresolved → Blocks compilation on module path.
- Over-Merging Modules → Leads to large “god modules” that defeat modularization.
- Package Refactoring Without Care → Breaks backward compatibility for APIs.
- Mixing Classpath and Module Path → Can reintroduce split package conflicts.
Best Practices
- Keep package names unique per module.
- Create a dedicated common module for shared code.
- Use service loaders instead of duplicating utilities.
- Plan refactoring in advance to maintain API stability.
- Regularly scan dependencies for duplicate packages.
📌 What's New in Java Versions?
- Java 5 → N/A (modules introduced in Java 9)
- Java 9 → JPMS introduced with strict prohibition of split packages
- Java 11 → Tooling improvements to detect split package issues earlier
- Java 17 → Minor refinements and better diagnostic messages
- Java 21 → No significant updates across Java versions for this feature
Analogy
Think of modules as office departments:
- If both HR and Finance maintain files in the same shared cabinet (package), confusion arises over who owns which documents.
- JPMS solves this by ensuring only one department controls a cabinet. If they both need access, either merge into one shared department or create distinct cabinets.
Summary + Key Takeaways
- Split packages occur when multiple modules declare the same package.
- JPMS prohibits split packages to enforce encapsulation and prevent ambiguity.
- Solutions include refactoring packages, merging modules, or using services.
- Avoid over-merging or careless refactoring that breaks APIs.
- Clear boundaries and unique package names keep modular systems maintainable.
FAQs
Q1. What is the difference between the classpath and module path?
Classpath allows split packages and global visibility; module path enforces strict encapsulation.
Q2. Why do I get split package errors when migrating to modules?
Because JPMS does not allow the same package across multiple modules.
Q3. What is the purpose of requires transitive
?
It propagates dependencies so downstream modules inherit them without declaring explicitly.
Q4. How do open
and opens
differ in reflection?
open module
opens all packages.opens
opens specific packages for reflection.
Q5. What are automatic modules, and should I use them?
They’re non-modular JARs treated as modules. Useful during migration but fragile in production.
Q6. How does JPMS improve security compared to classpath?
By disallowing split packages and blocking hidden reflective access.
Q7. When should I use jlink
vs jmod
?
jlink
creates custom runtime images.jmod
packages modules for distribution.
Q8. Can I migrate a legacy project to modules incrementally?
Yes, but refactor split packages early to avoid conflicts.
Q9. How do I handle third-party libraries that use split packages?
Upgrade to modularized versions or repackage them into a single module.
Q10. Do frameworks like Spring or Hibernate fully support modules?
Partial support—many rely on opens
for reflection and may bypass some JPMS restrictions.