Designing a public API with Java Modules requires careful planning: deciding what to expose, what to hide, and how to evolve the API without breaking clients. In real-world enterprise systems, API stability is critical—microservices, frameworks, and libraries rely on contracts that must remain clear and predictable.
Think of modules like departments in an organization. The HR department shares job postings (APIs) with the public, but not internal payroll records (implementation details). Public APIs must be carefully curated to balance usability with security.
Core Principles of Public API Design in Modules
1. Use exports
Wisely
Expose only the packages intended for clients:
module com.example.library {
exports com.example.library.api;
// implementation not exported
}
2. Keep Internal Code Hidden
By default, packages not listed in exports
remain inaccessible. This enforces encapsulation.
// Not exported → clients cannot use
package com.example.library.internal;
3. Use requires
for Dependencies
Declare dependencies explicitly:
module com.example.library {
requires java.sql;
exports com.example.library.api;
}
4. Consider requires transitive
If clients need a dependency transitively:
module com.example.library {
requires transitive com.example.utils;
exports com.example.library.api;
}
Best Practices for Public API Design
✅ Best Practices
- Clearly separate API packages and implementation packages
- Use
exports
only for stable, documented APIs - Use
requires transitive
to reduce boilerplate for clients - Provide versioning policies for modular libraries
- Add service interfaces using
uses
andprovides
for extensibility
❌ Pitfalls
- Exporting all packages (destroys encapsulation)
- Exposing unstable or internal APIs prematurely
- Using
open
unnecessarily (security risk) - Ignoring dependency version conflicts
Example: Library API Design
module-info.java
module com.example.mathlib {
exports com.example.mathlib.api;
requires transitive com.example.utils;
uses com.example.mathlib.spi.MathExtension;
provides com.example.mathlib.spi.MathExtension
with com.example.mathlib.impl.AdvancedMathExtension;
}
This design:
- Exports only
api
- Requires
utils
transitively - Uses service providers for extensibility
What's New in Java Versions?
- Java 5–8 → N/A (Modules introduced in Java 9)
- Java 9 → Introduction of JPMS,
exports
,requires
,provides
- Java 11 → Better tooling for modular libraries
- Java 17 → Stability improvements in JPMS usage for large projects
- Java 21 → No significant updates across Java versions for this feature
Real-World Analogy
Designing public APIs in modules is like publishing an official company handbook. Employees (modules) follow it, customers (clients) depend on it, and internal drafts remain private. Once published, the handbook must remain reliable, with updates carefully managed.
Summary & Key Takeaways
- Public API design in JPMS requires deliberate exports
- Internal code should remain hidden for encapsulation
- Use
requires transitive
to simplify dependencies for clients - Service interfaces (
uses
/provides
) enable extensibility - Avoid overexposing internals to keep APIs stable and secure
FAQ: Designing Public APIs with Modules
1. What is the difference between the classpath and module path?
Classpath loads everything blindly, module path enforces module boundaries.
2. Why do I get “package not visible” errors?
Because the package is not exported in module-info.java
.
3. What does requires transitive
do?
It exposes dependencies to downstream modules automatically.
4. How do open
and opens
differ?open
exposes the whole module for reflection, opens
targets specific packages.
5. What are automatic modules?
Non-modular JARs treated as modules. Useful for migration, not for stable APIs.
6. How does JPMS improve API security compared to classpath?
It enforces strict encapsulation, preventing unwanted access to internals.
7. When should I use jlink vs jmod?jmod
packages modules, jlink
builds runtime images with selected modules.
8. Can I evolve a public API without breaking clients?
Yes, by versioning carefully and avoiding removal of exported APIs.
9. How do I handle third-party libraries that aren’t modularized?
Use automatic modules temporarily or keep them on the classpath.
10. Do frameworks like Spring support modular API design?
Yes, though reflection-heavy frameworks may require opens
.