JEP 395: Records: https://openjdk.org/jeps/395
JEP 440: Record Patterns: https://openjdk.org/jeps/440
JEP 394: Pattern Matching for Instanceof: https://openjdk.org/jeps/394
JEP 441: Pattern Matching for Switch: https://openjdk.org/jeps/441
JEP 409: Sealed Classes: https://openjdk.org/jeps/409
JEP 361: Switch Expressions: https://openjdk.org/jeps/361
JEP 456: Unnamed Variables & Patterns: https://openjdk.org/jeps/456
JEP 507: Primitive Types in Patterns, instanceof and switch (Third Preview): https://openjdk.org/jeps/507
JEP 512: Compact Source Files and Instance Main Methods: https://openjdk.org/jeps/512
JEP 458: Launch Multi-File Source-Code Programs: https://openjdk.org/jeps/458
JEP 511: Module Import Declarations: https://openjdk.org/jeps/511
JEP 502: Stable Values (Preview): https://openjdk.org/jeps/502
JEP 513: Flexible Constructor Bodies: https://openjdk.org/jeps/513
> try (var ignored = CloseableThreadContext.put(…)) {
to
> try (var _ = CloseableThreadContext.put(…)) {
There were times I hated it, but turns out I really just hated messy, over-engineered legacy code and working in a gray cubicle at aging MegaCorps.
The language itself is quite beautiful when used properly and with modern features.
It just really needs a makeover and better tools.
* no type level concept of a const object (ie, you can have a const reference to a List, but never a reference to a const list). this makes const-ness an implementation detail of the class itself! so frustrating that List:add() can throw depending on the underlying class.
* lack of tuples (and no, record doesn't count). this is just a syntactic sugar thing, but I really miss it from c++ and python.
* var is far less powerful than c++ auto.
in most cases, I actually prefer the syntax of c++, which is really saying something.
But does it though?
I've just taken a new job writing primarily Java whereas I was previously writing mainly python and typescript.
One of the first things I've noticed is how dead Java's ecosystem (Maven central) seems in comparison to other ecosystems like PyPI, NPM or Cargo.
(Also side note: I've published packages on each of these registries and the publishing process for Maven central is comically terrible! This has to be discouraging people from contributing to the ecosystem.)
I respect your opinion but I wouldn't call Java beautiful (of course it depends on your definition of beautiful). It takes so much ceremony to do things you would do without any thought in other languages .
Initiating a mutable set
Python
`a = {1,2}`
What most Java programmers do
``` var a = new HashSet<>(); a.add(1); a.add(2); ```
Shorter one but requires more ceremony, and hence knowledge of more language semantics
``` var a = new HashSet<>(new Arraylist<>(List.of(1,2)) ```
I don't know if the above works but the Idea is to initiate a list and pass it into a HashSet constructor.
Similarly Java 21 allows you to model union types but doing so requires you to define N+1 classes where N is the number of union cases whereas in other languages it's as simple as `type A = C |D`
> It just really needs a makeover and better tools.
I love java, wouldn't call it beautiful though. But I don't need a language to be beautiful, I need it to be pragmatic, blisteringly fast, have an extensive ecosystem and top quality tooling. Java delivers #1 on all of those so I love it.
For beauty, I like ruby and lisp, but those both fail on all the other criteria so they are mostly for hobby use for me. (Python, the darling of everyone these days, is pretty much dead last on every criteria except popularity.)
> better tools
I'd say Java & JVM has pretty much the best tooling on all fronts.
I can't think of anything that has better tooling around the language and runtime.
Second, the more I worked with C# and visual studio, the more I hated it. It was pretty much the opposite with Javascript, typescript and even Java.
And yet, my latest talk at Lambda Days basically boiled down to “Java 21 and later don’t actually suck anymore”, and I genuinely do mean that.
Java 21 is actually fun to write, even for a grumpy FP advocate like me. Virtual threads make concurrency a lot simpler, and now that there’s proper records and ADTs (in the form of sealed interfaces), along with pattern matching, the language is actually pleasant to use. I haven’t dived into 25 yet, but I suspect I will like it as much or more than 21.
The biggest issue, though, is that Java programmers won’t use the new features. It was like pulling teeth at my last job to get people to use stuff from Java 8 (e.g. the `var` keyword), and none of my coworkers even knew how to use NIO or BlockingQueues which I think predate agriculture. I mean, hell, I had explain what “fairness” was to engineers when using a ReentrantLock because someone “corrected” my code with `synchronized`.
I don’t think Java makes people into bad programmers, but I do think it selection-biases for intellectually unambitious engineers. They learn exactly enough Java in college to pass their courses, and then get a job at a BigCo that doesn’t strictly require ever learning anything more than what they were taught in their “intro to data structures” course.
I have met some extremely intelligent Java engineers who do have intellectual curiosity, so I am not saying it affects everyone, but I do think that they are the minority. Java 25 might add every feature to make my wildest dream come true but it won’t matter if I am not allowed to use it.
I think that's a fair comment, but also there's this perspective: I first touched Java 1.1 in 1997 in college, and only for a semester. Then for the next 22 years never looked at a line of Java, working mostly in C++ and Python plus dabbling in FORTRAN for high performance stuff that needed to be written there. I generally consider my self not intellectually unambitious.
Then I moved to a Java shop who specifically needed high performance math (well at least as high performance as you can get in Java, which is actually pretty good now). But we stick to Java 8 compatibility because we have some BIG customers who require that because of LTS on Java 8. There are some nice constructs that would help make the code more readable and modern, but when you need to support people and actually make money you do what you need to.
A lot of Java jobs aren’t that though, especially internal applications. A lot of places are running Java 17 or Java 21 on all their servers, literally have no plans to ever support anything lower, but the engineers are still writing Java like it’s 2003. That is what’s maddening to me.
There is no value in solving a challenge in a way that only you understand or make others lose time trying to understand the logic.
Java 8 was the peak of development age for the JDK. Everything that came after isn't really memorable nor helpful, especially lambdas. You mention "var", why would we ever want in Java to hold a variable that you can't read immediately what is the type? That is a source of bugs and time waste to track down what object is being used.
I don't mind you are happy with all these changes, just remember that we absolutely don't care about them nor will make much of an effort because in the end of the day we don't want to follow the same route of other programming languages unable to handle gigantic and complex platform systems.
This isn't a competition to showoff who can apply new tricks, we absolutely don't care about functional programming. Java code must be simple and easy for anyone to read, that's it.
List<Account> accounts = List.of(new Account(1), new Account(2));
var accounts = List.of(new Account(1), new Account(2));
It just reduces visual noise and boilerplate that you already know.Java 8 is also a slow and old runtime. It performs terribly in 2025. Here’s a quote from 2020 and the gap has only gotten wider [0]:
> JDK 8 is an antiquated runtime. The default Parallel collector enters huge Full GC pauses and the G1, although having less frequent Full GCs, is stuck in an old version that uses just one thread to perform it, resulting in even longer pauses. Even on a moderate heap of 12 GB, the pauses were exceeding 20 seconds for Parallel and a full minute for G1. The ConcurrentMarkSweep collector is strictly worse than G1 in all scenarios, and its failure mode are multi-minute Full GC pauses.
You’re doing a disservice to everyone by continuing to use and glorify it.
[0] https://hazelcast.com/blog/performance-of-modern-java-on-dat...
var items = new HashMap();
Instead of
HashMap items = new HashMap();
That's the point of var. It reduces noise a lot in some situations.
I can use my IDE to see the type if necessary.
> Everything that came after isn't really memorable nor helpful,
There are several improvements that are very helpful
One example is how multi line strings help me to read more clearly without the unnecessary string concatenations:
var sql = """
SELECT foo
FROM bar
WHERE last_updated > :lastUpdated
""";
Another example is how switch statements have improved code readability, at least from my personal subjective viewpoint. String dayName = switch (day) {
case 1 -> "Monday";
case 2 -> "Tuesday";
case 3, 4, 5 -> "Other day";
default -> "Weekend";
};I've been a full time java developer for the past 7 years. Let me start by agreeing that I have very little interests in the functional "innovations" they added. They're fine, but most of my colleagues agree that code using streams or lambdas very quickly becomes harder to debug then if you just wrote the code with loops and.
That's far from true for every java feature though. Switch statements have been super cool, Green threads are a promising road out of the CompletionStage hell the children are dreaming of these days. "var" is a very nice way to reduce double typing ("Thing x = new Thing()" -> "var x = new Thing()") and also a nice way to avoid changes to unrelated files ("Thing x = getFoo(); f(x);" -> "var x = getFoo(); f(x)" means changing the name of class Thing doesn't require any change to the code) that's been helpful in a lot of cases.
> Everything that came after isn't really memorable nor helpful, especially lambdas.
Lambdas came out in Java 8, along with the streams API, and the fact that you don’t think they’re useful more demonstrates to me that you don’t actually understand it, since nearly every language before and after Java has lambdas and nearly everyone agrees that they’re useful.
Reifying a bunch of temporary interfaces is not “more readable” than a lambda. A bunch of terrible nested for-loop logic is not “more readable” than the streams API.
> You mention "var", why would we ever want in Java to hold a variable that you can't read immediately what is the type?
Your IDE can show the type, but even disregarding that there are lots of cases where you have to write the type twice in Java and it just makes noise. It doesn’t make the code more readable.
> we don't want to follow the same route of other programming languages unable to handle gigantic and complex platform systems.
Burying your head in the sand and Ignoring improvements in the language doesn’t make you more able to handle complex problems. It actually does the opposite and that’s so plainly obvious that I don’t think you actually thought through the sentence before you wrote it.
Take something like virtual threads. Most Java programmers don’t use them and instead keep using an executor service incorrectly because they also never learned the difference between blocking and non blocking IO. For them, virtual threads would be strictly better because it properly parks blocking IO.
Ultimately, I guess I disrespectfully disagree that Java “peaked” in 2012.
* Performant and safe standard library. * batteries included * a good way to actually care about managing dependencies, during build and runtime.
Okay, you got your stuff, please everyone now let's care about the standard library and that it really good.
Dude, java 8's eol was 8 6 years ago, now. I have nothing gainst waiting a bit for "newer JDKs", but way too often the pattern is that teams use the oldest possible JDK and only migrate several months/years after the last possible vendor has sunset their support.
> Java 8 was the peak of development age for the JDK.
To me it looks it was merely the point where your stopped caring.
I think people here are really underestimating how intellectually lazy most people are at their jobs. HN selection-biased for a geekier crowd so a lot of my criticisms don’t apply to readers of this forum.
My old CTO boss swore he wouldn't ever use annotations because they were too much magic for him.
"No! Writing out gobs of XML to configure Spring DI is the only way!"
If you’re an engineer you should be able to easily read the code to see what’s going on. Most of the time the “magic” can be understood in less than 30 minutes and then it’s not magic anymore.
A few of the older developers also complained about the use of map, filter, zip, lambdas, etc being harder to read as well. Then a month or two later when they realized they weren't going away, it was an important part of the language, and just learned how to use them the complaints just one day stopped.
Except for when we had to touch Java code and it didn't make sense to convert it fully to Kotlin.
They’ll give half-hearted justifications that are usually reductive or just flat out lies [1], but ultimately it seems to boil down to new=bad.
The amount of terrible code I have had to debug because Java programmers haven’t figured out you can use queues is upsetting, because all they learned in university is how to use `synchronized` wrong.
[1] I have learned that nearly every time someone says their disgusting code is “faster”, even when they claimed they tested it, it is almost universally untrue when I write a microbenchmark to check it.
- how’s the environment? Build tools, dependency management, etc. it used to be a PitA back then. - how has the typing system and generics evolved to support this? Have they introduced any type of variance?
IntelliJ IDEA is genuinely great. It helps that they were the ones who developed Kotlin, and a fair bit of the actual language changes were gacked from Kotlin. (Or you could say "prototyped and shown valuable in Kotlin".)
They are still hampered by lousy nullable support.
You didn't ask, but Spring still sucks. It's not part of the language but it's a ubiquitous framework.
It turns out, though, it’s still good enough for sealed interfaces and the like; I don’t have too many issues with it, though that might just be Stockholm syndrome at this point.
Maven is terrible as always but Gradle is generally fine. I use IntelliJ community edition solely for Java and it works well enough for what I need.
It’s not like Java is going to replace F# for me or anything, but I do genuinely think it’s more fun to write now than it used to be.
Every single, logical step that led to this hidden performance problem makes complete sense, because every improvement had to be its own tiny, contained improvement, but the end result is that `case Foo(int _, String _, User _, int age)` will still call three getters _just in case_ you're abusing the language to add side effects to accessors.
Perhaps even worse is the explanation that follows: if you don't mess up your accessors, the JVM _may_ decide to not call those accessors at runtime. So now the language itself has this weird performance impact to maintain backwards compatibility, but at runtime that backwards compatibility _may_ not exist and provide you with a performance improvement instead, negating the whole reason why backwards compatibility was added in the first place.
I like the improvements to Java, don't get me wrong. It's no longer the JDK 1.7 language poor enterprise programmers are stuck with. But if the Java people had come together and worked this out as one single feature, rather than five different ones, we wouldn't have needed to remember edge cases/a code analysis tool to remind us that using this intuitive language feature _may_ actually has a 3x performance impact depending on the mood the JVM is in today.
The reason I ask is that I recently had to join a Java project at my company, and having a background in Node/Rust/Perl/Lua and some C++, I found the Java tooling to be extremely unsuitable for my taste.
A simple example: there is no standard LSP server, and the amount of jumps required to have a working setup with FOSS tools and make it IDE-independent is just horrendous. In every other ecosystem I've worked with so far, it was pretty easy in the last 5 years: if you don't like IDEs, you can keep using your vim/emacs/helix or whatever and just embed a plugin or two, with LSP integrated -- and you're ready to go.
Java world felt complete the opposite, like you had to use/buy some commercial tools to start doing something.
Beyond the IDE you also have to consider the build tools, package management, debuggers, profilers, static analysis tools, etc.
It’s honestly too much for an HN comment. But as an example, if I do open one of these awful projects at work and it uses gradle for example, intellij will understand that, import the project, get all dependencies, let me run any target with debugging or profiling, give me code coverage, etc.
Not sure they are worse than other languages?
What I actually haven’t used up to this point are VTs. I got a service that implements a job queue and it currently works flawlessly with scheduled executor pool. I’m reluctant to go with VTs before evaluating what implications that may have.
many people will tell you that the standard library is not as performant as it could be and does not have as many batteries as python and try managing your dependencies...
that would be far more important than the next super duper feature IMHO.
such as?
Java 22: JEP 456: Unnamed Variables & Patterns
Java 24: JEP 485: Stream Gatherers
Java 25: JEP 511: Module Import Declarations
Java 25: JEP 513: Flexible Constructor Bodies