A frequent misconception when adopting the Java Platform Module System (JPMS) is that frameworks like Spring fully support modularization out-of-the-box. Developers often run into errors such as “Unable to access class via reflection” or “package is not visible”. This is because Spring relies heavily on reflection, which JPMS restricts by default.
Understanding how to properly integrate Spring with JPMS is essential in real-world applications. Large enterprise systems and microservices often combine Spring Boot with JPMS to enforce encapsulation while still allowing reflective access for dependency injection, proxies, and runtime enhancements.
Think of Spring in a modular world as an auditor who inspects office files. By default, JPMS keeps most files locked. For Spring to function, you need to provide controlled access keys (opens
) so it can continue inspecting and wiring beans.
Key Considerations for Spring and JPMS
1. Reflection in Spring
Spring uses reflection for:
- Dependency injection
- Bean instantiation
- Proxy generation
- AOP (Aspect-Oriented Programming)
Without opens
, Spring cannot reflectively access internal classes.
2. Using opens
in module-info.java
Instead of open
(which opens the entire module), prefer targeted opens
.
module com.example.app {
requires spring.context;
requires spring.beans;
opens com.example.app.service to spring.core, spring.beans, spring.context;
}
Example: Spring Boot Application with JPMS
module-info.java
module com.example.springbootapp {
requires spring.boot;
requires spring.boot.autoconfigure;
requires spring.context;
requires spring.web;
opens com.example.springbootapp.controller to spring.core, spring.beans, spring.web;
opens com.example.springbootapp.service to spring.core, spring.beans, spring.context;
}
Key points:
- Only necessary packages are opened
opens
allows Spring to use reflection safely- Core APIs remain encapsulated
Best Practices & Pitfalls
✅ Best Practices
- Use
opens
per package instead ofopen
module-wide - Keep
module-info.java
minimal, declaring only necessary dependencies - Combine JPMS with
jlink
for smaller Spring Boot runtimes - Use dedicated test modules with
opens
for JUnit/TestNG
❌ Pitfalls
- Using
open module ...
(unnecessary exposure) - Exporting internal packages for Spring instead of
opens
- Forgetting reflective access for controllers or beans → runtime errors
- Relying solely on automatic modules for Spring dependencies
Real-World Integration Steps
-
Start Small
Modularize core business logic modules before Spring Boot entry points. -
Handle Reflection Carefully
Use targetedopens
for controllers, services, and entities. -
Custom Runtime with jlink
Build optimized Spring Boot runtimes by including only required JDK modules. -
Testing
Add--add-opens
in Maven/Gradle test configs for reflective test frameworks.
What's New in Java Versions?
- Java 5–8 → N/A (no JPMS)
- Java 9 → JPMS introduced, Spring initially struggled with reflection restrictions
- Java 11 → Spring Boot and Spring Framework improved JPMS compatibility
- Java 17 → Stable support with clearer integration patterns
- Java 21 → No significant updates across Java versions for this feature
Real-World Analogy
Integrating Spring with JPMS is like granting a trusted auditor temporary access to inspect certain rooms in a secure office building. You don’t hand them the master key (open
), but instead give them access only to relevant departments (opens
).
Summary & Key Takeaways
- Spring relies on reflection, which JPMS restricts by default
- Use targeted
opens
inmodule-info.java
for Spring packages - Avoid exposing entire modules unnecessarily
- Combine JPMS with Spring Boot for secure, maintainable applications
- Use tooling (Maven/Gradle +
--add-opens
) to configure tests properly
FAQ: Spring and Java Modules
1. What is the difference between classpath and module path for Spring apps?
Classpath exposes everything; module path enforces boundaries, requiring explicit opens
.
2. Why do I get “package is not visible” errors in Spring Boot apps?
Because the required package isn’t opens
to Spring.
3. What’s the purpose of requires transitive
in modular Spring apps?
It allows downstream modules to inherit Spring dependencies automatically.
4. How do open
and opens
differ for Spring integration?open
exposes the entire module; opens
grants reflection only on specified packages.
5. Do automatic modules work for Spring Boot dependencies?
Yes, but they’re a migration aid, not recommended long-term.
6. How does JPMS improve security in Spring applications?
By enforcing encapsulation and minimizing reflection to trusted packages.
7. When should I use jlink vs jmod with Spring?
Use jmod
for packaging dependencies, jlink
for optimized runtime images.
8. Can I modularize a Spring Boot app incrementally?
Yes, start with business logic modules and gradually modularize entry points.
9. How do I handle third-party libraries not modularized?
Place them on the classpath or use automatic modules temporarily.
10. Is Spring fully JPMS-compliant today?
Yes, modern Spring versions work with JPMS, though configuration is required for reflection-heavy features.