Rating 26 years of Java changes
Boxing, primitives, and performance
- Early Java collections required manual boxing of primitives; autoboxing largely fixed ergonomics but introduces subtle bugs (e.g., cached boxed values,
==vs.equals, null auto‑unboxing NPEs). - Several commenters note boxed primitives and streams hurt memory locality and vectorization; performance‑sensitive code avoids them or uses primitive collections libraries.
- There’s interest in Project Valhalla / value classes (values that “code like a class, work like an int”) as a long‑term fix.
- Some point out other languages (Rust, C++, Julia, Fortran) avoid boxing in collections entirely; others note most mainstream high‑level languages rely on boxing under the hood.
Java’s design philosophy and feature borrowing
- Many features are seen as copied from C#, Scala, Kotlin, etc. Others counter that Java intentionally lets other languages experiment and then adopts proven ideas cautiously for backward compatibility.
- This conservatism is praised for keeping old code running, but blamed for “Frankenstein” designs (streams, modules) and for not fully leveraging hindsight from JVM peers.
- Checked exceptions spark a major dispute:
- Critics: ergonomically bad, widely avoided in practice (libraries use unchecked), don’t correlate well with likelihood of failure, interact poorly with lambdas/streams.
- Defenders: make error paths explicit, similar in spirit to Rust/Swift/Kotlin error types; the problem is Java’s syntax and hierarchy, not the concept.
- Modules (JPMS) are widely disliked: painful Java 8→9 migration, little payoff for application developers, hard to adopt incrementally. Supporters stress their value for JDK encapsulation and future tooling, but admit ecosystem uptake is minimal.
Annotations, Spring, and “magic”
- Many argue annotations are massively impactful (especially with Spring/DI), removing boilerplate and enabling “configuration as code”: scheduled jobs, REST endpoints, auto‑wiring, etc.
- Others find annotation‑driven wiring opaque and hard to debug, preferring explicit, linear code and external configuration (e.g., old Spring XML).
- There’s a meta‑debate: are annotation‑heavy frameworks elegant DSLs or “garbage code” only understandable at runtime? Opinions are sharply split.
Streams and lambdas
- Several commenters think the article’s low scores for lambdas/streams are “bogus”; for many, they were paradigm‑shifting and now feel essential in any modern language.
- Criticisms:
- Streams API is over‑complex due to built‑in parallelism; execution order and error handling become obscure.
- Checked exceptions inside streams are especially awkward.
- Some developers avoid lambdas/streams entirely for debuggability and readability.
- Others report heavy productive use of parallel streams for CPU‑bound workloads, rating them highly.
var and type inference
- Pro‑
var: reduces repetitive type noise (especially with long generic types), improves visual clarity, and aligns Java with modern inference‑heavy languages. - Anti‑
var: hides types when reading code, makes PR review and text‑only browsing harder, and increases reliance on IDE hovers. Many adopt a compromise: usevaronly when the type is obvious from the right‑hand side.
Other features and ecosystem notes
- Assertions are underused in Java compared to C, but some value them as a canonical, togglable invariant mechanism.
- Collections and generics are praised as the point when Java became truly usable, especially compared to the pre‑collections era.
- The old
Date/CalendarAPIs are universally derided;java.timeis seen as a huge improvement. - Text blocks, try‑with‑resources, NIO, and markdown in Javadoc are generally viewed as quality‑of‑life wins, though the article’s ratings are seen as overly harsh.
- Several comments emphasize that much of Java’s real story is the ecosystem (HotSpot, JITs, concurrency utilities, build tools, Spring) more than individual language features.