A common mistake teams make when migrating to the cloud is assuming that containerization alone makes an application cloud-native. In reality, large monoliths deployed in containers often suffer from poor scalability, security risks, and inefficient resource usage. This is where the Java Platform Module System (JPMS) plays a crucial role.
By modularizing applications, you can reduce the runtime footprint, enforce encapsulation, and create smaller, optimized images tailored for containerized and serverless deployments. For enterprise-grade microservices, modular design ensures clear separation of concerns, faster startup, and enhanced security—critical in cloud environments where resources are elastic and costs scale with usage.
Think of JPMS as packing lightweight travel bags for a cloud journey. Instead of carrying an entire household (the full JDK), you pack only the essentials (required modules), enabling speed, agility, and efficiency in the cloud.
Benefits of JPMS in Cloud-Native Applications
1. Smaller Runtimes with jlink
Cloud services benefit from reduced startup times and memory usage. With jlink
, you can create custom runtimes:
jlink --module-path $JAVA_HOME/jmods:out --add-modules com.example.app --output cloud-runtime
2. Strong Encapsulation for Security
Encapsulation prevents unwanted reflective access, reducing the attack surface in multi-tenant environments.
3. Faster Startup and Scale-Out
Minimal runtimes and explicit dependencies mean quicker container cold starts and better scaling.
4. Improved Maintainability
Clear module boundaries align with microservice architectures, making services easier to evolve.
Example: Modular Cloud-Native Service
module-info.java
module com.example.orderservice {
requires spring.boot;
requires spring.context;
requires java.sql;
exports com.example.orderservice.api;
opens com.example.orderservice.model to spring.core, spring.beans, spring.context;
}
Key Points:
- Only
api
is exported model
opened only to Spring for reflection- Reduces exposure while enabling framework integration
Pitfalls in Cloud-Native Modularization
❌ Using open module ...
to fix reflection issues → destroys encapsulation
❌ Over-splitting modules → increases complexity without benefits
❌ Relying too much on automatic modules → weakens long-term stability
❌ Bundling full JDK into containers instead of jlink
runtimes
Best Practices
✅ Use jlink
to create lightweight runtimes for containers
✅ Keep modules cohesive and aligned with domain-driven design
✅ Export only APIs, not internal packages
✅ Use opens
sparingly and only for framework needs
✅ Validate module boundaries with jdeps
✅ Automate modular builds in CI/CD pipelines
Deployment Optimizations
- Docker + JPMS
FROM eclipse-temurin:17-jre
COPY cloud-runtime/ /opt/jre/
COPY target/app.jar /opt/app/app.jar
ENTRYPOINT ["/opt/jre/bin/java", "-m", "com.example.orderservice/com.example.orderservice.MainApp"]
-
Kubernetes Scaling
Modules reduce cold start latency, helping services scale faster in Kubernetes pods. -
Serverless Functions
Smaller runtimes lower cold start overhead in AWS Lambda or Azure Functions.
What's New in Java Versions?
- Java 5–8 → N/A (no JPMS)
- Java 9 → JPMS introduced with modular JDK, enabling
jlink
- Java 11 → First LTS with modularized Java, suitable for cloud runtimes
- Java 17 → Stability and performance improvements in JPMS
- Java 21 → No significant updates across Java versions for this feature
Real-World Analogy
Cloud-native modularization is like shipping goods with cargo containers. Instead of moving everything in bulk (full JDK), you ship only what’s needed, cutting costs and increasing agility.
Summary & Key Takeaways
- JPMS enables smaller, secure runtimes with
jlink
, ideal for cloud-native apps - Strong encapsulation reduces security risks in multi-tenant environments
- Modular services align with microservice architectures
- Avoid pitfalls like
open module
and over-splitting - Use JPMS with Docker/Kubernetes/Serverless for optimized deployments
FAQ: JPMS in Cloud-Native Apps
1. What is the difference between classpath and module path in cloud apps?
Classpath loads everything; module path enforces visibility and reduces runtime size.
2. Why do I get “package not visible” errors?
Because the package isn’t exported or opened to required frameworks.
3. What does requires transitive
mean in cloud services?
It exposes dependencies downstream, but should be used sparingly.
4. How do open
and opens
differ?open
exposes the entire module; opens
allows targeted reflection.
5. Are automatic modules good for cloud apps?
They can ease migration but aren’t stable for production.
6. How does JPMS improve security in cloud environments?
It limits reflective access and enforces encapsulation, reducing attack surface.
7. Should I use jlink or jmod for containers?
Use jlink
for custom runtimes, jmod
for packaging libraries.
8. Can legacy apps be migrated to JPMS for the cloud?
Yes, modularize gradually, starting with core APIs.
9. How do I handle third-party libraries that aren’t modularized?
Place them on the classpath or use automatic modules temporarily.
10. Do frameworks like Spring Boot and Hibernate work with JPMS in the cloud?
Yes, but require careful opens
configuration for reflection-heavy features.