What is 'strong encapsulation' in JPMS, and how does it change the meaning of 'public'?
answer
- Hidden by default; exports opens the door
- public AND exported AND read by consumer
- Non-exported public class = internal
- opens = reflection access, exports = API access
- Enforced by compiler/runtime, not convention
basics
~20 sStrong encapsulation means only the packages a module explicitly exports are usable by other modules. A public class in a non-exported package is hidden, so 'public' no longer means 'visible to everyone' — it means visible only within the module unless the package is exported.
solid answer
~40 sStrong encapsulation is JPMS's second core goal: a module's internals are hidden by default, and only packages listed with `exports` are accessible to other modules. This redefines `public`. Before modules, `public` effectively meant 'reachable by all code on the classpath'. Now reachability has two requirements: the type must be `public` AND its package must be exported by its module (and the consuming module must read yours). A `public` class in a non-exported package is internal — other modules cannot compile against it or reflect into it. This lets library authors keep implementation packages truly private while exposing a deliberate API surface. For reflection-heavy frameworks, `opens` grants deep reflective access without granting compile-time access, separating the two concerns. The result is enforced, not just conventional, encapsulation.
code
java · 12 linesmodule com.example.lib {
// Public API: usable by other modules
exports com.example.lib.api;
// Only this module may compile against the spi package
exports com.example.lib.spi to com.example.plugin;
// Let a DI framework reflect over entities, but not compile against them
opens com.example.lib.model to spring.core;
// com.example.lib.internal is neither exported nor opened -> fully hidden
}go deeper
Knows that only exported packages are visible to other modules and that public alone is no longer enough.
States the three conditions for cross-module access, distinguishes exports vs opens, and explains qualified exports/opens.
Explains how strong encapsulation gives library authors a real enforced API surface, how it changes reflection behaviour, and migration impact on frameworks.
Designs module API surfaces deliberately (what to export vs keep internal), reasons about qualified exports for SPI/plugin boundaries, and weighs encapsulation enforcement against framework/reflection needs across an organization.
## What 'encapsulation' meant before modules **Encapsulation** is hiding internal details behind a controlled interface. Java's access modifiers (`private`, package-private, `protected`, `public`) handle this *within and between classes*. But at the JAR level there was a gaping hole: anything declared **`public`** was reachable by **all** code on the classpath. If you wanted a class usable by other classes in your own library, it had to be `public` — and then it was equally usable by everyone else, including consumers who should never touch your internals. Library authors documented 'do not use' and put classes in `internal` packages, but nothing **enforced** it; reflection could reach anything regardless. ## What strong encapsulation adds **Strong encapsulation** is JPMS's guarantee that a module's packages are **hidden by default** and become accessible to other modules **only when the module explicitly exports them**. The directive is `exports`: ```java module com.example.lib { exports com.example.lib.api; // visible to other modules // com.example.lib.internal is NOT exported -> hidden } ``` Here `com.example.lib.internal` stays private to the module even if its classes are declared `public`. ## The new meaning of `public` The key conceptual shift: **`public` is necessary but no longer sufficient** for another module to use a type. Cross-module accessibility now requires **all** of: 1. the member/type is `public` (the language-level modifier), AND 2. the package containing it is **exported** by its module, AND 3. the consuming module **reads** the providing module (has a `requires` edge to it). So within a module, `public` works as it always did. Across modules, a `public` type in a non-exported package is invisible — you cannot import it, compile against it, or (by default) reflect into it from another module. People summarize this as 'public no longer means public to everyone.' ## Reflection and the `opens` directive Reflection (inspecting/invoking code at runtime via the `java.lang.reflect` API) used to bypass all access checks — it could read private fields of any class anywhere. Strong encapsulation closes that door too: by default you cannot reflect into a non-open package of another module. But frameworks (dependency injection, serialization, ORMs) rely on deep reflection. JPMS provides two graduated tools: - **`exports pkg;`** → grants *compile-time and normal* access to a package's public API. - **`opens pkg;`** → grants *deep reflective* access (including to non-public members) at runtime, but **not** compile-time API access. This separation lets a module say 'frameworks may reflect over my entities, but no one may compile against this package as API.' You can also target a specific module: `exports pkg to other.module;` (a **qualified export**) or `opens pkg to framework.module;`. ## Why 'strong' It is called *strong* encapsulation because, unlike the old convention-and-documentation approach, it is **enforced by the runtime and compiler**. An attempt to access a non-exported package fails to compile, and a reflective attempt throws `IllegalAccessException` unless the package is open. Boundaries that used to be honor-system are now hard walls. ## Relationship to reliable configuration The two goals are complementary. **Reliable configuration** decides *which modules and dependencies exist* (the graph). **Strong encapsulation** decides *what inside each module is reachable* across that graph. A consumer needs both: a `requires` edge (configuration) and an `exports` on the target package (encapsulation) to use another module's API. ## Practical payoff Library authors gain a real, enforced API surface: implementation packages can finally be private, free to change without breaking consumers; consumers get a smaller, intentional API and can no longer accidentally couple to internals. That is the durable architectural value of strong encapsulation.
- If a class is public but its package is not exported, can another module use it?No. Cross-module access requires both public AND an exported (or for reflection, opened) package, plus a requires edge from the consumer. A public class in a non-exported package is effectively internal.
- What is the difference between exports and opens?exports grants normal compile-time and runtime access to a package's public API. opens grants deep reflective access at runtime (even to non-public members) but not compile-time access — it is for frameworks that reflect over your code.
A house where every room (package) is sealed by default. exports installs a front door to a specific room so guests (other modules) can enter; opens is a service hatch that lets inspectors (reflection) reach inside without giving them the front-door key (compile-time API).
saying these in an interview costs you the question
- Saying public still means visible everywhere — across modules it requires an export
- Using opens when you mean exports (or vice versa) — opens is reflection-only, exports is API access
- Thinking reflection bypasses encapsulation as before — by default it does not; the package must be open
- Forgetting the consumer also needs a requires edge, not just the producer's export