A common mistake developers make when working with Java applications is leaving too many classes and packages exposed on the classpath. This makes sensitive internals accessible to attackers or malicious code. With the Java Platform Module System (JPMS) introduced in Java 9, developers now have a powerful way to limit the attack surface by controlling visibility and encapsulation.
In large enterprise systems and microservices, security breaches often occur because internal APIs were unintentionally accessible. By properly designing modules, you can restrict what is visible to consumers and prevent unauthorized reflection, creating more secure applications.
Think of JPMS modules like secure office buildings. Each department has locked doors (non-exported packages), while only the reception desk is public (exported APIs). Unauthorized personnel cannot wander into payroll or IT rooms without explicit permission.
Key Security Features of JPMS
1. Strong Encapsulation
By default, packages are not exported. Only explicitly exported ones are accessible:
module com.example.secureapp {
exports com.example.secureapp.api;
// internal logic hidden
}
2. Controlling Reflection with opens
Reflection is a major source of attack vectors. JPMS lets you control it:
module com.example.secureapp {
opens com.example.secureapp.model to com.fasterxml.jackson.databind;
}
opens
grants selective reflective access to trusted libraries.- Avoid using
open
(opens all packages) unless absolutely required.
3. Using requires transitive
Carefully
Only expose dependencies clients truly need:
module com.example.secureapp {
requires transitive java.sql; // only if clients use it
}
4. Service-Oriented Security
Use uses
and provides
for safe extensibility without leaking internals:
module com.example.secureapp {
exports com.example.secureapp.spi;
uses com.example.secureapp.spi.Plugin;
provides com.example.secureapp.spi.Plugin with com.example.secureapp.internal.SecurePlugin;
}
Pitfalls That Weaken Security
❌ Exporting all packages (exports ...
) → Exposes internals to attackers
❌ Using open
unnecessarily → All packages open to reflection
❌ Overusing requires transitive
→ Bloats client dependencies and increases exposure
❌ Relying on automatic modules → Can unintentionally expose packages
Best Practices for Securing Modules
✅ Export only stable, public APIs
✅ Use opens
sparingly and target only required packages
✅ Keep internal logic hidden by default
✅ Minimize transitive dependencies
✅ Bundle applications with jlink
to remove unused modules and reduce runtime surface
✅ Regularly audit dependencies with jdeps
Example: Secure API Design
module-info.java
module com.example.banking {
exports com.example.banking.api;
opens com.example.banking.dto to com.fasterxml.jackson.databind;
requires java.sql;
uses com.example.banking.spi.AuditLogger;
provides com.example.banking.spi.AuditLogger
with com.example.banking.impl.SecureAuditLogger;
}
Here:
- Only
api
is exported - Reflection is restricted to Jackson for DTOs
- Services allow extensibility without exposing internals
What's New in Java Versions?
- Java 5–8 → N/A (No JPMS)
- Java 9 → JPMS introduced, strong encapsulation,
exports
,opens
- Java 11 → Tooling improvements (
jdeps
, better error reporting) - Java 17 → Performance and stability refinements in JPMS
- Java 21 → No significant updates across Java versions for this feature
Real-World Analogy
Securing modules is like airport security. Passengers (classes) can only access the gates (public APIs) if properly ticketed (exported). Restricted areas (internal packages) remain locked down. Reflection rules (opens
) act like special access badges granted only to trusted staff.
Summary & Key Takeaways
- JPMS helps minimize the attack surface by restricting access to internal code
- Use
exports
andopens
selectively to balance usability and security - Avoid overexposing with
open
and automatic modules - Use
jlink
to remove unnecessary runtime modules - Regularly audit dependencies to detect overexposed packages
FAQ: Security in Java Modules
1. What is the difference between classpath and module path in terms of security?
Classpath exposes everything, while module path enforces strict boundaries.
2. Why do I get “package is not visible” errors?
Because the package is not exported in module-info.java
.
3. What is the purpose of requires transitive
?
It allows downstream modules to access dependencies automatically but may widen exposure.
4. How do open
and opens
differ?open
exposes the entire module to reflection; opens
restricts reflection to specific packages.
5. What are automatic modules, and should I use them?
They are plain JARs treated as modules. Use them temporarily, not in production.
6. How does JPMS improve security compared to the classpath?
By enforcing encapsulation and preventing unauthorized access.
7. When should I use jlink vs jmod?
Use jmod
for packaging, jlink
for building reduced, secure runtimes.
8. Can I secure legacy projects incrementally with modules?
Yes, modularize gradually, exporting only stable APIs.
9. How do I handle third-party libraries that aren’t modularized?
Use them on the classpath or convert them into automatic modules carefully.
10. Do frameworks like Spring or Hibernate work with JPMS security rules?
Yes, but some reflective frameworks may need opens
for certain packages.