Encapsulation — Specification Deep-Dive¶
Where the rules live: JLS §6.6 (access control), JLS §8.10 (records), JLS §8.1.1.2 (sealed classes), JLS §17.5 (final fields), JLS §7.7 (modules), JVMS §4.1/§4.5/§4.6 (class file access flags), JVMS §4.10.1.6 (verifier), JVMS §5.4.4 (linker access), JEP 181 (nestmates).
1. Where canonical text lives¶
| Concept | Source |
|---|---|
| Access modifiers | JLS §6.6 |
| Default access | JLS §6.6.1 |
protected rules | JLS §6.6.2 |
| Records | JLS §8.10 |
| Sealed classes | JLS §8.1.1.2 |
| Final fields, freeze action | JLS §17.5 |
| Module declarations | JLS §7.7 |
| Class file access flags | JVMS §4.1, §4.5, §4.6 |
| Verifier (access) | JVMS §4.10.1.6 |
| Linker (access) | JVMS §5.4.4 |
| Nestmates | JEP 181 (Java 11) |
| Module access enforcement | JEP 261 (Java 9) |
2. JLS §6.6 — accessibility¶
A reference type, member, or constructor of a reference type is accessible only if the type is accessible and the member or constructor is declared to permit access.
The four modifiers: - public — accessible everywhere the type is accessible - protected — accessible from same package, plus subclasses (with restrictions) - (default) — accessible only from the same package - private — accessible only from the same top-level class (and nestmates)
A class itself can be public or default (package-private). Inner classes can also be protected or private.
3. JLS §6.6.2 — protected access¶
The famous "protected access via subclass-typed reference" rule:
Let
Cbe the class in which aprotectedmember is declared. Access is permitted only within the body of a subclassSofC. In addition, ifIddenotes an instance field or instance method, then: - If the access is by a qualified nameQ.Id, whereQis anExpressionName, then the access is permitted if and only if the type of the expressionQisSor a subclass ofS.
In short: protected lets you access via this, but not via an arbitrary C-typed reference. Surprises beginners.
package alpha;
public class C { protected int x; }
package beta;
public class S extends C {
void m(C other) { other.x = 1; } // ERROR — other not S-typed
void n(S other) { other.x = 1; } // OK
}
4. JLS §8.10 — records¶
A record class declaration declares a class with a header that lists components. The class is implicitly final and extends java.lang.Record. The components are the canonical state of the record, and accessor methods are automatically generated.
Mandates: - Implicitly final (cannot be extended) - Implicitly extends Record - Accessors auto-generated (one per component) - Canonical constructor auto-generated (or compact form) - equals, hashCode, toString auto-generated based on components
Programmer can override any of the auto-generated members. Compact constructors validate but cannot reassign components after the implicit this.x = x assignments.
5. JLS §8.1.1.2 — sealed classes¶
A sealed class restricts which classes may directly extend it. The permits clause lists the directly extending classes/interfaces. Each permitted class must be
final,sealed, ornon-sealed.
Encapsulation aspect: the hierarchy is closed. External code cannot introduce variants, preserving invariants of the sealed type.
JVMS §4.7.31 — the PermittedSubclasses attribute makes this enforceable at the bytecode level.
6. JLS §17.5 — final field semantics¶
A thread that can only see a reference to an object after that object has been completely initialized is guaranteed to see the correctly initialized values for that object's final fields.
This gives final fields safe publication. A class with all final fields, no leaking this, is thread-safe for unsynchronized sharing.
This is the technical backbone of immutable encapsulation.
7. JLS §7.7 — modules¶
A module declaration:
module com.example {
requires com.other;
exports com.example.api;
opens com.example.entity to com.fasterxml.jackson;
}
exports— package is accessible from any module that requires this oneexports ... to— only specified modules see itopens— package is open for reflection (deep access) but not for code dependencyopens ... to— restricted to specified modules
exports and opens together gives full access. exports alone allows compilation but blocks reflection. opens alone allows reflection but blocks compile-time dependency.
8. Verifier access checks (JVMS §4.10.1.6)¶
The verifier ensures bytecode does not violate access rules at runtime:
getfield/putfieldinstructions check access against the declared field's class and modifiers.invokevirtual/invokespecial/invokeinterface/invokestaticcheck method access.newchecks the target class is accessible.
Failure → IllegalAccessError at class link time.
9. Linker access checks (JVMS §5.4.4)¶
When the JVM resolves a symbolic reference: - It locates the target class/method/field - It checks access modifiers - It throws IllegalAccessError if access is denied
Reflection (Field.get, Method.invoke) performs the same check at runtime, throwing IllegalAccessException (a checked exception). setAccessible(true) skips the check, but JPMS adds another layer (InaccessibleObjectException).
10. JEP 181 — nestmate access (Java 11)¶
A nest is a logical group of classes that share a single source file (top-level + nested classes). Members of the same nest can access each other's private members directly via the JVM's NestHost and NestMembers attributes.
Pre-Java 11: synthetic bridge methods. Post-Java 11: direct access.
This is purely a runtime simplification; doesn't change source-level semantics.
11. JEP 261 — module system (Java 9)¶
Defined modules as first-class entities. A module is a named collection of packages. The runtime enforces module boundaries during class resolution and reflection.
Provides: - Strong encapsulation of internal packages - Explicit declared dependencies - Improved security (no accidental access to JDK internals)
The JDK itself is split into ~80 modules, each strongly encapsulated.
12. JLS §15.13 / §15.27 — method references and lambdas¶
Lambdas can access private members of enclosing class:
class Outer {
private int x;
Runnable r = () -> System.out.println(x); // lambda accesses private x
}
The lambda's hidden class is generated as a nestmate of Outer, allowing direct access. The bytecode performs getfield Outer.x directly.
13. Reflection access semantics¶
Class.getDeclaredField(String) returns a Field regardless of access. Field.get(instance) checks access at call time:
Field f = User.class.getDeclaredField("password");
f.setAccessible(true); // ask for access (may throw with JPMS)
Object value = f.get(user);
setAccessible(true) raises InaccessibleObjectException if JPMS forbids it. The flag, once set, allows future access on this Field instance.
Frameworks routinely use this; --add-opens flags allow legacy code to keep working.
14. The "package" notion in JPMS¶
In JPMS, packages span at most one module. A package cannot be shared between modules. This is a strong rule: if com.example.X is in module A, it cannot also be in module B.
Result: package access (default modifier) effectively equals "same module + same package."
15. Reading order¶
- JLS §6.6 — access modifiers
- JLS §6.6.2 — protected rules
- JLS §8.10 — records
- JLS §8.1.1.2 — sealed classes
- JLS §17.5 — final field semantics
- JLS §7.7 — modules
- JVMS §4.5, §4.6 — class file (field/method flags)
- JVMS §4.7.31 — PermittedSubclasses
- JVMS §4.10 — verifier
- JVMS §5.4.4 — linker access checks
- JEP 181 — nestmates
- JEP 261 — modules
Memorize this: encapsulation is enforced at three levels: the compiler (javac rejects illegal access), the verifier (bytecode-level checks), and the linker/runtime (IllegalAccessError). JPMS adds module-level enforcement on top. Records, sealed types, and modules are the modern Java tools to express encapsulation declaratively. The spec is precise; read it when access surprises you.