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: use var only 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/Calendar APIs are universally derided; java.time is 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.