Framework Integration: Spring and Java Modules with JPMS

Illustration for Framework Integration: Spring and Java Modules with JPMS
By Last updated:

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 of open 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

  1. Start Small
    Modularize core business logic modules before Spring Boot entry points.

  2. Handle Reflection Carefully
    Use targeted opens for controllers, services, and entities.

  3. Custom Runtime with jlink
    Build optimized Spring Boot runtimes by including only required JDK modules.

  4. 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 in module-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.