JEP draft: Prepare to make final mean final
Immutability and “const-ness” across languages
- Several comments wish Java made
final(orval) the default and discouraged mutability. - People compare Java unfavorably to C++
constand especially Rust, where mutability is explicit (&mut) and visible at call sites, making reasoning about side effects easier. - Others warn that overusing const/final often masks poor design and can make code hard to evolve, leading to hacks like casting away const.
- D’s transitive
constis cited as powerful but “weird” and hard to get right; some prefer using const very sparingly.
C/C++ const vs Java final
- C/C++
constis criticized as mostly documentation plus extra compiler errors, undermined byconst_castand confusing rules about when UB occurs. - Some praise Java for being willing to tighten semantics, unlike C++ where
const-based optimization is limited by these escape hatches.
What the JEP changes about final and reflection
- The core change: reflective mutation of
finalinstance fields (viasetAccessible/Field::set) will be restricted or require explicit opt-in. - There are JVM flags such as
--illegal-final-final-mutation=denyto turn violations into hard errors. - Static finals, record fields, and hidden classes already behave as truly immutable; this JEP extends that direction.
- The aim is “integrity by default”: libraries shouldn’t be able to secretly rewrite other code’s invariants.
Impact on serialization, frameworks, and tests
- Many frameworks (GSON, JAXB, mocking libraries, class generators, Lombok, Spring, etc.) rely on reflective access; concern that this will become another “module system / --add-opens” situation.
- Counterpoint: most such libraries don’t need to set
finalfields; private access alone is enough. - Java serialization and similar libraries get special escape hatches via
sun.reflect.ReflectionFactoryandSerializable, though that doesn’t cover all JSON-style cases; some see that as insufficient. - Ideas raised: annotations to mark “final-mutatable” classes, or a dedicated “test mode” flag. Others push back: global easy flags encourage misuse; better to have tools/builds assemble precise options.
Optimization and Project Leyden motivations
- Supporters stress this is about correctness and enabling stronger optimizations (e.g., constant folding of truly-final fields), not just micro-speedups.
- Future Leyden-style caching of computations and JIT code across runs may rely on knowing fields never change across executions, not just within one run.
- Skeptics argue the JVM already does speculative optimization and deoptimization; proponents reply that deopt is expensive and pervasive reflective mutation prevents many optimizations entirely.
Java evolution, tooling, and ecosystem tangents
- Some see this as another painful but ultimately successful step, like JPMS: early breakage, but smoother upgrades later and fewer internals abuses.
- Others complain Java’s evolution (modules, integrity flags, complex build tools) feels heavy compared to ecosystems like Rust’s Cargo or Go’s tooling.
- Long tangent on Lombok: loved for boilerplate reduction, but criticized as a brittle hack on compiler internals and a frequent blocker for JDK upgrades; alternatives (records, annotation processors, other libs) are mentioned.
Security, integrity, and “developer agency”
- One side frames this as protecting applications from supply-chain issues and misbehaving libraries that use reflection/JNI to bypass invariants.
- The opposing view:
setAccessible(true)is explicitly a “I know what I’m doing” escape hatch; restricting it further undermines extensibility and controlled monkey-patching. - Pro-change commenters answer that nothing is outright impossible: you can still bypass
final, but it should be visible, deliberate, and come with clear configuration, not happen silently inside libraries.