NHacker Next on Rails
  • top
  • new
  • best
  • ask
  • show
  • jobs
login
GuidelinesFAQListsAPISecurityLegalApply to YCContact

Hacker News on Rails, powered by React on Rails Pro. Open the source on GitHub by ShakaCode.

Commit 06fc0df

Rendered at 12:03:31 GMT+0000 (Coordinated Universal Time) with Rails + React on Rails RSC.

Java sealed classes and exhaustive pattern matching (neilmadden.blog)

188 points by hnisjafx40 2d ago | 148 comments

smarks 2d ago
The article (which seems to be from 2026-04-24 based on its URL) refers to the JDK 26 version of the PEM Encodings API. [1]

[1]: https://openjdk.org/jeps/524

The article points out a potential pitfall with how this version of the API exposes a sealed class. If the application has an exhaustive switch expression that lists all permitted subclasses, and a later JDK release were to add a new permitted subclass, this would potentially expose the application to a MatchException at runtime.

In the JDK 27 version of the PEM Encodings API, JEP 538 [2], we’ve adjusted the permitted subclasses of the sealed class in question to include a non-public permitted subclass.

[2]: https://openjdk.org/jeps/538

This prevents applications from using an exhaustive switch expression by enumerating all permitted subclasses. This allows the JDK to extend set of permitted subclasses in the future without exposing applications to the risk of unexpected exceptions.

misja111 2d ago
It's a nice workaround but the necessity of such a non-obvious fix also shows that the design of sealed classes is really flawed ..
masklinn 2d ago
Not really.

Defaulting to exhaustive pattern matches is correct, it’s by far the most frequent case (which is why essentially every modern language does that); being able to prevent exhaustive matching is useful for libraries; and using a private subclass is a completely obvious way to implement that in Java.

See private extensible variants for the same capability in OCaml. Or the non_exhaustive attribute in Rust. Or the nonexhaustive attribute in Swift (or alternatively opting into library-evolution which reverses the defaults).

rf15 2d ago
I disagree - the problem lies with the intention of exhaustively listing all available values in a switch expression with no backup plan. Switch expressions should always have a default/fallback branch, even if it's theoretically impossible and only logs the attempt/picks a default for future proofing the code and its uses.
misja111 2d ago
> Switch expressions should always have a default/fallback branch,

This will nullify the advantage of being able to treat sealed classes as algebraic datatypes by using the exhaustiveness check.

rf15 2d ago
Yes and no. You either accept that your code and the environment is final (in which case there's no problem and you can just blindly use the exhaustiveness check) or you accept that your code is a living thing that can change in part (including your edits, its dependencies, and its standard library), in which case you must be prepared for changes but can still make use of the exhaustiveness check by temporarily removing the default case.

I feel this is fundamentally a perspective issue. If this is a problem, also consider why people want sealed classes in the first place. It's always a tradeoff and you can also always fork or propose your own improved version of the concept and see if it has enough merit to gain traction. There's a million other footguns in the language that needs similar scrutiny, I'm just happy java isn't C/C++.

carlmr 2d ago
We could have both by introducing another keyword than "default" here.

In some cases you want "default" handling, in other cases it's a runtime detection of a code issue. E.g. you could add a "fallback" case which is the same as "default" yet if your code doesn't exhaustively handle all sealed class cases won't compile. This would still create a fallback, if your sealed class is widened later on, but still allow you to error out if it's already the case right now.

masklinn 2d ago
> You either accept

That is a false dichotomy. Some enumerations have reasons to likely change in the future, others (most) either have no such thing or their changing would be so impactful to the system it needs to be signaled loudly.

That’s really no different than any other type information.

> It's always a tradeoff and you can also always fork or propose your own improved version of the concept

You are the person asserting that the concept as defined is incorrect.

nmadden 2d ago
I’m glad you’ve fixed that issue. Hopefully you can fix the other design issues, including security weaknesses, I reported at the time [1]. I found the response rather dismissive and unhelpful, I have to say.

[1]: https://marc.info/?l=openjdk-security-dev&m=177435495515512&...

shepherdjerred 2d ago
I love how far Java has come. I still prefer TypeScript since I do a lot of frontend work and prefer to use the same language across the stack.

With the said, you can do exhaustive pattern matching in TypeScript! https://github.com/gvergnaud/ts-pattern

ngruhn 2d ago
Isn't that overkill? You can check exhaustiveness with a trivial little helper:

    function exhaustive(witness: never): never {
      return witness
    }
https://www.typescriptlang.org/play/?#code/FAFwngDgpgBAKgZQB...
ddosmax556 2d ago
you can just to

    default:
        matched satisfies never
        throw new Error(...)
You don't even need a function.
whilenot-dev 2d ago
Agree, python calls this little helper assert_never[0]. I opened an PR to add it to the TypeScript docs some time ago: https://github.com/microsoft/TypeScript-Website/pull/3599

[0]: https://docs.python.org/3/library/typing.html#typing.assert_...

n_e 2d ago
In the example you don't even need the default clause and the helper (the error message without it is worse though).
AdieuToLogic 2d ago
> I still prefer TypeScript since I do a lot of frontend work and prefer to use the same language across the stack.

If you perfer using the same language for both frontend and backend work, yet would like to have one more semantically rich than TypeScript, consider Scala.

For the frontend, ScalaJS[0].

For the backend, Scala[1].

0 - https://www.scala-js.org/

1 - https://scala-lang.org/

theanonymousone 2d ago
Or Java: https://github.com/konsoletyper/teavm
wk_end 2d ago
You don't need a library for this; the idiomatic approach is to lean on TypeScript's narrowing and native control flow (ifs and switches, or maybe IIFEs), along with (maybe) the never type to enforce exhaustiveness.

https://www.typescriptlang.org/play/?jsx=4#code/JYWwDg9gTgLg...

I guess the library is slightly nicer, arguably, but I don't think it's worth the external dependency and added complexity.

pjmlp 2d ago
I rather use something more beefy than V8 JIT with a dynamic language on the server side when given the option, after all they even had to rewrite the compiler.

On the other hand, at least I get an excuse to write native addons.

If it wasn't for the projects that require Next.js I would never bother with Node.

shepherdjerred 2d ago
I knew I should've written a disclaimer about doing this in vanilla TypeScript

Yes, you can do it via an assert never in a switch. That isn't pattern matching. That's an exhaustive switch enforced at compile time

Which is nice, but again, it's not pattern matching.

DimmieMan 2d ago
Might be grass is greener mentality, but I think I prefer this to whatever C# is doing.

The sealed keyword makes sense, isn't unique to this feature and you can pattern match exhaustively. I feel like I could explain this to a beginner without overloading them, it's still 'just Java' while giving you a way to occasionally model a one-of relations with out the awkward default handling while sticking to the predominately OOP driven nature of the language.

The stuff you can do with a functional language is amazing but I don't think bolting on new paradigm's until you have a diabetic Frankenstein of a language is the right answer, Scalar is right over there if that's your preferred style.

misja111 2d ago
> Although Java will produce a compile-time error for a non-exhaustive switch when you compile the consumer (main in this case), it cannot do so if the consumer is not recompiled when the sealed type changes

I think this is quite bad. I think such a change should break binary compatibility between the consumer and the producer api jar. Now any consumer might or might not run into runtime errors when switching to a new producer jar that is binary compatible.

Good4boothee 2d ago
That is nothing new or specific to this feature. Mismatched client and library code produced MethodNotFoundException for decades. You can't really fight against partially recompiled/updated code. The only Java attempt of checking some kind of integrity was `serialVersionUID" (for serialization) and getting "java.io.InvalidClassException" at runtime wasn't fun, even if somewhat useful.
looperhacks 2d ago
"binary compatibility" in the JVM sense shows up during runtime, anyway. The Throwable should maybe have been a subclass of LinkageError (which indicates a binary compatibility problem; though I'm sure there's a good reason why it isn't): Right now the MatchException can easily be caught by mistake, which wouldn't be an issue if it were a MatchError.

However, it doesn't change the behavior much: LinkageErrors can also happen any time at runtime, not necessarily during startup. Just like the MatchException.

tpxl 2d ago
This already happens though without sealed classes in existing code. If you have an interface A with implementing classes B and C, you'll eventually run into an error if the library starts emitting class D.
nmadden 2d ago
Not if you program to the interface. This kind of pattern matching is syntactic sugar for `instanceof` checks and down-casts. That’s fine within a module boundary (using “module” in a loose sense), but across module boundaries I think programming to an interface should still be the preferred solution.
armchairhacker 2d ago
Java also has recursive patterns and (maybe still in preview) literal patterns: `switch (x) { case A(B(true, var c)) -> f(c); }`
smarks 1d ago
In my earlier message [1] I described the evolution of the PEM Encodings API in a way that prevents applications from being exposed to the risk of runtime exceptions arising from future additions to the list of permitted subclasses.

[1]: https://news.ycombinator.com/item?id=50148756

The discussions around the PEM Encodings API also led my colleagues on the langtools team to write an article "Preparing for Change: Safe Switching over Sealed APIs" that discusses these issues in more detail. [2]

[2]: https://openjdk.org/projects/amber/guides/exhaustiveness-gui...

The article focuses on pattern matching and exhaustive checking, because this is an important feature for a large class of applications.

As a library designer, though, I'm primarily interested in the use of sealed classes to control subclassing. The old idiom for controlling subclassing of concrete and abstract classes was to ensure that all constructors had package access or less. This mostly prohibits subclasses outside the package. (Strictly speaking, it prohibits such subclasses from being compiled or instantiated.) For interfaces, it was formerly impossible to control subclassing at all. The addition of sealed classes to the language solves the problem completely.

Indeed, in the JDK we've converted all the uses of the older idioms to use sealed classes. [3] (This bug covers most of the changes; there are a handful of others that were converted individually.) Of the several dozen examples here, it doesn't look to me like ANY of them are candidates for pattern matching. This probably reflects the long-standing issue of subclassing control in object-oriented systems. That issue has existed in Java much longer than pattern matching. As time goes on, I expect the JDK class libraries to introduce more new APIs that make use of pattern matching and sealed classes.

[3]: https://bugs.openjdk.org/browse/JDK-8283414

timshel2 2d ago
Scala punching the air rn
hibikir 2d ago
80% of Java features in the last few years are Scala features with worse syntax, because the better syntax would not be backwards compatible. It's not good for Scala 3 adoption in industry , but I would argue Martin has no interest whatsoever helping that. He's happy with his research language that somehow has enough good features some companies put it in prod. Languages that care about industrial usage just organize themselves differently.
mlinksva 2d ago
No affiliation with either, but I don't think Java and Scala are punching at each other, and they're both doing great. Needless to say Java has continued to exceed expectations and given its huge role in the world, that's worth [handwave very huge number] in consumer welfare. Scala has evolved the last several years to again become perhaps the most interesting big-ish application language, eg see Odersky's recent https://scaladays.org/session/opening-session-keynote/ and https://dl.acm.org/doi/10.1145/3786335.3813127
cryptos 2d ago
I liked Scala a lot, but it also caused some real world problems like different, partly incomprehensible coding syles or compatibility issues. Although the concise syntax could help to save some token, I guess that Scala is not the best language for AI. Other languages like Java or Go are more explicit and clear to the reader - and the AI. The elegance and conciseness simply doesn't matter so much anymore.
AdrenalinMd 2d ago
I still write Scala. But not sure for how long. I swear, it's such a joy to write code in Scala.

It's like French poetry.. So much elegance. An no effect system BS. Just like Martin Odersky intended it to be!

theanonymousone 2d ago
>it's such a joy to write code in Scala.

Yes, but also a bit of a torture to read code in it..

owlstuffing 2d ago
Scala is its own worst enemy in this respect. It’s both too close to Java and too far.

Too far: there are several dialects one can use with it e.g., I can speak Haskell with Scala grammar. Unlike with Java there is no central Scala dialect, so reading/maintaining a Scala codebase can be a challenge.

Too close: its type system, as advanced as it is, is still stuck in Java’s inheritance and all its limitations. Traits, for example, are not runtime components, they dissolve into the hosting class’ single object, giving up the independent identity and flexibility of runtime configured composition. And the Scala 3 `exports` feature that departs Java’s inheritance is just forwarding, not true delegation; it is the inverse of traits: it trades self identity for independent identity (no late-bound self that comes free with impl inheritance).

rhdunn 2d ago
I like a lot of Scala but dislike implicits. I feel like it is the wrong abstraction in that it is similar to implicit type conversion in C/C++, making it harder to reason about what the code is doing and navigating to the types/methods being called.

I prefer extension methods for what Scala implicits do [1], [2]. The only part that is lacking with extension methods is to be able to extend a type to implement an interface, e.g. Comparable, but you can create extension methods that return anonymous objects [3].

[1] https://kotlinlang.org/docs/extensions.html#anonymous-extens...

[2] https://learn.microsoft.com/en-us/dotnet/csharp/programming-...

[3] https://discuss.kotlinlang.org/t/extensions-function-to-impl...

gf000 1d ago
Scala has refined implicits into givens and extension methods.
rr808 2d ago
Yes it is like French poetry - not many people like it. I've been on a Spark project the last 2 years and its a horrible code base. Everyone on the team hates it, will move to PySpark next year. Scala is dead.
therealdrag0 2d ago
Agree. Really enjoy it—without type effects libs.
tentacleuno 2d ago
Curious what you meant about the effect system BS?
jmalicki 2d ago
I'm assuming they mean Scala doesn't have the tortured Haskell IO monad.
pdpi 2d ago
Scala doesn't have an IO monad by default, but ZIO, CatsEffect, and probably others are fairly popular in at least some parts of the community. IMO it's kind of the worst of both worlds (all the complexity, none of the guarantees), but it's definitely there.
troupo 2d ago
Oh. It does. https://github.com/zio/zio

Thankfully, not in the core language :)

ackfoobar 2d ago
This is perhaps why the comment added "Just like Martin Odersky intended it to be!"

There are just too many ways to use Scala, and everyone thinks they're doing it "idiomatically".

dozerly 2d ago
Sigh, I so very miss writing Scala.
another_twist 2d ago
Took me a while to get used to sealed and I didn't quite see the use. But AI seems to love it for Java so I gave it a go and now I get it. Its like a really good access control mechanism esp if you use DI and want to deprecated some stuff. Apart from ofcourse the pattern matching - which comes in handy for event driven messages where payload shape changes.
panny 2d ago
I don't think you need those switch defaults with the sealed types. I didn't check with a compiler, but I think those are unnecessary.
thfuran 2d ago
The switches with defaults are both switching over a String with only literal cases. That can't be exhaustive without a default, and switch expressions must be exhaustive.
BatchJob 1d ago
Was never needed, will never be needed.
Larrikin 2d ago
Java continues to try and catch up to Kotlin https://kotlinlang.org/docs/sealed-classes.html

I didn't mind Java when the comparisons were to C++, Ruby, and Python. I can not find a reason to go back after Scala (which seemed to fail due to being indiscriminate) and especially Kotlin

pron 2d ago
A hoarder may have much more furniture than I do, but if I buy a sofa it doesn't mean I'm trying to catch up to the hoarder (who has five). Java isn't trying to catch up for the simple reason that we don't want it to have all the features Kotlin does; in fact, we're trying very hard to avoid that (not to mention that we have yet to adopt a single Kotlin feature; our features usually take a different form, even though Kotlin, Java, and many other languages often rely on the same inspiration, which is usually the ML language). That's because some languages aim to be feature-rich while others aim to be more minimal. Both are likely to accumulate features over time, but at very different rates (we'll hold off on features for years until we're satisfied that their value justifies the added complexity to the language, as keeping that relatively low is very important to us).

If you like a richer language, the Java platform offers you options, but many people prefer smaller languages (the number of Java Platform developers who use the Java language has remained pretty much at a constant ~90% for the past 15 or more years), but we're aware that a non-tiny minority of developers prefer richer languages, and so we accommodate them, too. Either way, 95% of the JDK is runtime (standard library and the JVM) and tooling, and you'll enjoy it regardless of which frontend language you prefer.

giamma 2d ago
I like the approach. As interesting as certain JVM languages can be (e.g. Scala, Kotlin, Clojure) they are not widely adopted in the industry.

I have been working in product development for 25+ years, and I have friends in different organizations all over Italy, where I live. They told me many horror stories of clients who had custom software built by consultants using Scala because it was cool, and few years later the consultants disappeared and the client remained with a sophisticate piece of software that is crucial for their business, which was written in a language that no other consultant wants to touch.

In short, while there are better languages out there, Java is a decent language with a large developer base and from a long term perspective and from a risk management point of view, Java is still the best choice for me. The latest revision of the Tiobe index shows Java at fourth place while Kotlin and Scala rank respectively 32nd and 40th place.

haspok 2d ago
> which was written in a language that no other consultant wants to touch.

...or maybe they are not willing to pay a high enough price for said consultants? I can guarantee you there would be many takers for the right price.

Some could say that the higher price is not justified, but that is a different discussion. Some others could also argue that if you pay peanuts, you get monkeys...

quietbritishjim 2d ago
The higher price is perhaps not justified in the sense that the software could surely have been built in a simpler language for which more (cheaper) consultants were available to maintain it.

i.e. today's consultant is justified in charging that price, but yesterday's developer was not justified in using Scala which caused such high future maintenance costs.

gf000 1d ago
Not disagreeing you, but can we please stop using tiobe? It's by far the worst metric that doesn't mean anything (even though it is correct here, but so is a broken clock twice a day)
bmacho 2d ago
> Both are likely to accumulate features over time, but at very different rates (we'll hold off on features for years until we're satisfied that their value justifies the added complexity to the language, as keeping that relatively low is very important to us).

This strategy will still navigate Java into a random local optimum like it happened with other languages (that value backward compatibility), just more slowly. The greedy algorithm does that. Even if you do it slowly and you are extra careful that each step indeed betters the language.

pron 2d ago
Everything comes to an end eventually, and there's no strategy to avoiding it. But not trying to adapt to real changes in the environment could certainly be a recipe to hasten that end. And yes, we do really try and make sure that any change we make is more than worth it.

But if in 2026 Java is the third or second most popular language in the world overall and the #1 choice for large applications that are performance-sensitive and/or need to be maintainable, but is it possible that in 20 years, despite our best efforts, Java will decline sharply, perhaps even to the point of being only as popular as, say, Go is today? Sure.

valzam 2d ago
Of course, invoking 'there are two kinds of languages: one that people complain about and one that nobody uses', there are two kinds of language designs: one that is perfectly consistent and one that has had contact with the real world.
oldsecondhand 2d ago
The issue is that it's hard to predict how certain features will be misused and even harder to remove a harmful feature from a language. That's why Java goes slow. (Although I think there's too much pussyfooting around string interpolation. But I also think too much type inference makes the code harder to read.)
throwaway7783 2d ago
Not always. See PostgreSQL as a counter example.
fallingbananna 2d ago
The analogy is very sensible, but I would like to add that Kotlin isn't really hoardning features in the same way Scala did.

Compared to Scala, C++, or even Typescript and C#, Kotlin feels a like a well thought out minimal modern language (as minimal as you can be, while still providing conviniences that we have grown to expect like nullability checks, or data classes).

(*Except coroutines, I feel like Java is achieving that goal waaay better with virtual threads and structured concurrency than Kotlin did with "forever experimental" API that is full of footguns and lacks any observability.)

gf000 2d ago
Kotlin is absolutely massive, and they often add n small features instead of a single more complicated one, so I don't think the comparision to Scala fits.

Funnily enough kotlin added some rushed, delivered syntax sugar in the form of `when` and java just came and added proper pattern matching with algebraic data types and exhaustiveness checking into the platform itself, making it better than the guest language at its own game. Meanwhile I never felt this "landing page growing" mindset with Scala.

pjmlp 2d ago
That is the curse of guest languages, adding features which eventually are incompatible with the design the platform ends up taking for the similar feature.

C vs C++ vs Objective-C on UNIX systems, JavaScript vs Typescript (hence why they now focus on it as a linter and nothing else), ...

derriz 2d ago
This is subjective of course but for me Kotlin is certainly not minimal - I'd put it closer to C# than Java in terms of features. Typescript and Java feel like far smaller languages. Of course, C++ is in a class of its own in terms of anti-minimalism.
speed_spread 2d ago
I find Kotlin to be full footguns in general, letting you do stuff because 'why the hell not'. It feels like the Kotlin designers just wanted to show off, without considering that many of the resulting code patterns would be terrible.
CrimsonRain 2d ago
You're driving java feature set with great expertise. It is hard to complain about it.

I know it is not a technically marvelous thing, it's not a high priority either...but the only thing I'd want differently is lot more syntactic sugar that reduces common boilerplates/actions/patterns to simple one liners.

However, with AI writing most of the code now, I now doubt the importance.

dtech 2d ago
> not to mention that we have yet to adopt a single Kotlin feature; our features usually take a different form, even though Kotlin, Java, and many other languages often rely on the same inspiration, which is usually the ML language

This seems disingenuous at best, or it's a symptom that I've noticed as a user more often that your JDK team seems to have blinders on and dismisses/ignores most other languages even if feature have proven themselves 10 times over.

There's several features Java added or is trying to add, usually 5-10+ years later than Kotlin, and that's ignoring Scala:

* Local type inference

* Switch expressions

* Records, although I found them barely usable because there is no copy() method or with-ers

* String templates unsuccessfully

* A very stripped down version of smart-casting with switch pattern matching

I'm sure there's tech-debt and esoteric compatibility concerns which make evolving Java hard, but from a developer's perspective it's just glacial to evolve and the result is weaksauce.

pron 2d ago
Local type inference, records, and switch expressions (alongside lambdas and generics) are all ML features, as every language designer will tell you (and Java did them quite differently from Kotlin). All of them, by the way, were well known to Java's original designer in the nineties, it's just that they thought it will be a while until mainstream programmers are ready for them, so their adoption in Java is best postponed.

BTW, even if you're not aware of ML's extraordinary influence on most programming languages, surely you're aware there are competitors to Java with analogous features that are far bigger than Kotlin.

As for string templates, our intent is not to have a feature simiar to what Kotlin offers. We obviously look at all languages regardless of popularity, but in that case, as we did in the case of virtual threads or records, we'll probably say, no, that's not what we want, to Kotlin's approach (even the Preview that we pulled was nothing like Kotlin's approach).

> I'm sure there's tech-debt and esoteric compatibility concerns which make evolving Java hard

Backward compatibility is rarely a hindrance at all. People just talk about it a lot because most organisations consistently say that backward compatibility is much more important to them than any language feature, but it's not all that hard to maintain.

> but from a developer's perspective it's just glacial to evolve and the result is weaksauce.

I find that statement hard to reconcile with the fact that the only languages as popular as Java or more are JS/TS and Python, while all the languages that are not "weaksauce" from "a developer's perspective" are doing far, far worse than Java. There has never, ever, been any sort of consensus among developers on the right set of features, and there have always been developers who prefer more features and those who prefer less. It's perfectly fine to be on one side or the other or even to say, "yes, I'm in the minority, but I still think I'm right", but I find it strange to claim to represent "the developer's perspective" when your preferences are mostly associated with languages that are doing worse rather than better.

tialaramex 1d ago
> even if you're not aware of ML's extraordinary influence on most programming languages

There are definitely many programmers who have a very parochial understanding of programming. They know one or two languages well, they've never been interested in what else might be possible - maybe they never took a language survey course or they barely paid attention in that class - and so they genuinely do not know what else is possible or what might be normal in other languages. This happens to spoken languages too, in English "singular they" was already right there ready to be used for cases where we do not want to specify gender, in some languages there was never an issue here, gender was only ever an optional adjunct so just don't specify if you don't want to, while in others a default gender is so engrained into the language that you're basically fighting the assumption just to say a simple sentence. But if you only know English there's no reason you'd realise that varies.

matsemann 2d ago
I get your point, but Kotlin is still what made many of these specific features widely known to java developers, and accelerated the push from devs to get similar things into java.

Mind you, I like that java has taken its time to get the features right. Of course, java has had the advantage of enhancing the jvm while adding the features, where Kotlin had to work around the jvm in some cases (like green threads vs coroutines).

So I prefer javas version of many of the of the more modern things (but do miss Kotlin's lambdas and vast library of extension functions when writing java). But I feel it's wrong to downplay Kotlin's role in modernizing things on the jvm. Heck, if it weren't for kotlin as a temporary stopgap, I'd probably would've left the jvm while the features were still lacking in java.

pron 2d ago
> I get your point, but Kotlin is still what made many of these specific features widely known to java developers, and accelerated the push from devs to get similar things into java.

I disagree for an obvious reason: only a small minority of Java developers know Kotlin. When we want to see if the median developer is ready for some ML feature, we certainly look at other languages, but Kotlin doesn't matter beyond its size (which is not inconsiderable, but is also nowhere near the largest Java competitor that's quick to adopt features).

> Of course, java has had the advantage of enhancing the jvm while adding the features, where Kotlin had to work around the jvm in some cases (like green threads vs coroutines).

And C# could have done the same as Java but instead did what Kotlin did (or, rather, Kotlin did what C# had done), and we looked at both and said: we don't want that. That's why I'm saying that the "catching up" narrative doesn't hold up to any scrutiny.

Obviously, Java has been a huge innovator in optimising compilers, garbage collection, and low-overhead telemetry — areas where it's ahead of virtually everyone else — and focusing innovation on the runtime rather than the language has been one of James Gosling's stated goals since the beginning. So catching up is also silly when talking about a technological leader.

> But I feel it's wrong to downplay Kotlin's role in modernizing things on the jvm.

I'm neither downplaying nor overplaying it. As one of the Java architects, I'm telling you what it is. And "modernising things" on the Java Platform ecosystem is quite a different thing from influencing Java language features.

When we discuss other languages, and we discuss all of them, the ones that are discussed more are either the bigger ones or the more novel ones. Kotlin has shown some novelty, especially around its design of generics and of nullability types, but these happen to be two areas where we have actually not (yet?) made changes.

> Heck, if it weren't for kotlin as a temporary stopgap, I'd probably would've left the jvm while the features were still lacking in java.

That may be true, but it's irrelevant to what we're discussing. If anything, if Kotlin had served as a significant enough stopgap for those developers who prefer richer languages, it would have helped us not to adopt certain features or to adopt them more slowly.

I'm not saying Kotlin hasn't had a positive influence on the platform and, indeed, it may have helped especially in those years when Java stagnated, but it didn't push Java toward adopting more ML features.

throwaway7783 2d ago
>I'm sure there's tech-debt and esoteric compatibility concerns which make evolving Java hard, but from a developer's perspective it's just glacial to evolve and the result is weaksauce.

This used to be the case a few years ago. But not anymore. Java is evolving very rapidly compared to those years.

troupo 2d ago
> A hoarder may have much more furniture than I do, but if I buy a sofa it doesn't mean I'm trying to catch up to the hoarder

A few years back the same argument was phrased as "we don't adopt features from lesser languages" or something to that effect. Since then Java has been busy adopting those features at an accelerated pace.

> If you like a richer language, the Java platform offers you options, but many people prefer smaller languages

And Java is no longer a smaller language. Because artificial purity for misguided reasons hurts the language. And thankfully most people in Java have finally realized it.

pron 2d ago
> A few years back the same argument was phrased as "we don't adopt features from lesser languages" or something to that effect. Since then Java has been busy adopting those features at an accelerated pace.

Except it wasn't, because adopting ML features, especially lambdas and generics, were discussed in the Java team even before Java was released.

Also, the "accelerated pace" is only compared to a dry spell where development stagnated not by choice but by Sun's problems. We had a pace we liked, then we were forced to slow down, and now we're back to the pace we like again.

Also, it has always been a stated strategy to focus on innovation more in the runtime than in the language (Gosling called it "a wolf in sheep's clothing"). Java has always aimed to innovate in compiler technology, garbage collection, and telemetry as fast as it could, and we innovate faster than most in these areas.

> And Java is no longer a smaller language.

No longer smaller than what? We aim to keep the language relatively small, i.e. smaller than mainstream languages that are more eager to add features. So Java is bigger than Go, but it's smaller than C# or Kotlin or Rust, and is roughly comparable to TS or Swift in its "size".

troupo 2d ago
> Except it wasn't, because

Except it was. It was you saying this. [1]

And yes, those ML features, and many other features, were adopted by other languages, and faster, than Java. Now you're suddenly saying "we're aware that a non-tiny minority of developers prefer richer languages, and so we accommodate them, too" and "oh yes, we've been thinking of these features since 1991".

> No longer smaller than what?

Than whatever your ever-changing definition is. Java, thankfully, is no longer a small/smaller language, and is rapidly adopting actual useful features, including those from lesser products.

Unfortunately it may be held back by people still thinking that a language should not be useful in any way, shape or form. Thankfully, you're being dragged into modern world kicking and screaming. Maybe in 10 more years Java will catch up with the rest of the world (as I said, it's been catching up quite rapidly).

---

[1] https://news.ycombinator.com/item?id=28985688

"our goal isn't to adopt the strategy of less successful products, but to forge our own"

pron 2d ago
> Except it was. It was you saying this.

I said "strategy" not "feature". That adopting a feature that a less successful language had invented or adopted is not the same as adopting the strategy of a less successful language was precisely my point about buying a sofa not being the same as "catching up" to a hoarder.

I said "we shouldn't be a hoarder", not "we shouldn't get a sofa".

> Now you're suddenly saying "we're aware that a non-tiny minority of developers prefer richer languages, and so we accommodate them, too" and "oh yes, we've been thinking of these features since 1991".

But Java started adopting ML features over twenty years ago. Where do you think we got generics from? The discussion about lambdas and generics between Gosling and Joy, even predating Java's release are Java lore (https://archive.computerhistory.org/resources/access/text/20...).

> Than whatever your ever-changing definition is. Java, thankfully, is no longer a small/smaller language, and is rapidly adopting actual useful features, including those from lesser products.

My definition isn't ever-changing, it's just relative. Being a relatively small language means, at the same time, not adopting the strategy of richer languages and adding select features but at a slower pace. This was Gosling's "a wolf in sheep's clothing" strategy since the very start: a very innovative, fast-moving runtime (where most of the benefits to the software's end user are hidden) wrapped in a non-threatening, conservative language. Conservative doesn't mean frozen and relatively small doesn't mean never-growing, but they do mean not adopting relatively rich fast-changing language strategy either. Our strategy has worked well for us, and yes, I still think it's silly to suggest we should change it because languages that aren't doing as well have a different strategy.

Even Java's GC and JIT came from Lisp and Smalltalk, but not the strategy (which was Gosling's point that these helpful features were part of thretening products).

DarkNova6 2d ago
What I like about Java is that it is a deliberate late-mover language. It doesn't go for the latest shiny thing but looks at what features have proven themselves in other languages.

I can't help but to be a little sobered by the promises of Kotlin and C#. They look great on the surface, but once you have more complicated code to work on they tend to show warts quite fast.

Java is remarkably consistent and really doesn't have any sharp edges. Even with generics and type erasure, I found myself fighting the language semantics in C# way more often than Java.

cogman10 2d ago
I personally enjoy Kotlin. There are potential warts with it, but those exist with other languages.

That said, Java taking it's time and observing the playing field is a huge benefit. And now that they've been slowly delivering new language features, it's really made it pretty nice flow of powerful well thought out extensions each release. There hasn't been one of the new features that I think missed the mark.

jayd16 2d ago
> I found myself fighting the language semantics in C# way more often than Java

What's an example? I find C# quite well designed despite the Chinese menu of features.

> Java is remarkably consistent and really doesn't have any sharp edges. Even with generics and type erasure [...]

These back to back with a straight face...What is a sharp edge in your mind?

arein3 2d ago
A sharp edge is writing meta typesystem code infolving covariance, invariance etc

Saw that in a typescript repository

It's ok when it's part of a very specific library, it's not ok using it as part of usual project code

jayd16 2d ago
What is the significant difference to Java? In C# you would write something like Foo<out Bar> instead of Foo<? extends Bar> or some such thing.
ysleepy 2d ago
In c# you can't be generic over generic types. In java you can. Also enums can't implement interfaces.
afdbcreid 2d ago
> A sharp edge is writing meta typesystem code infolving covariance, invariance etc

Like in Java? C# has its own sharp edges (like most or all languages), but it's almost disingenuous to bring a feature that is much more complex and weird in Java as an example.

biglyburrito 2d ago
Java lacks a ton of quality-of-life features that are table stakes in other languages, including C# and Kotlin. Auto-properties, async, extension methods, nullable reference types, primitive types, string interpolation/templates -- Java is inferior to C# in every one of these areas.
za3faran 2d ago
Java's approach to async is virtual threads. Extension methods are being explored through typeclasses. Solutions for nullability are also proposed, and in the mean time, you can use JSpecify. If by primitive types you mean value classes, they are going into preview in the upcoming JDK.
peterashford 2d ago
See, this is entirely the problem. Your "table stakes" includes stuff I 100% do not want - like async
jmarlow47 2d ago
Kotlin feels like it's having an identity crisis. The gap between the two languages continues to decrease. Kotlin is trying to be server-side, Android, but also every other platform (KMP). Feels like everything is brand new and also deprecated at the same time, always moving on to something new.

Maybe Roman really was the soul and it lost its touch without him?

I guess in the age of AI it doesn't matter since a few years from now, if you change your mind, you can just ask Jeeves to one-shot rewrite it back to Java.

jeroenhd 2d ago
Until Java fixes its type system to add nullable types (and not in that ridiculous tri-state nullability method that one JEP proposes), Kotlin will be several steps ahead of Java.

I can speak from experience that Fable 5.1 still writes code that compiles and usually runs fine but throws null pointer exceptions in reasonable edge cases.

jmarlow47 2d ago
That's valid. Kotlins nullable types do help tremendously in that regard with AI-generated code. If Java nailed a nullable type system that's actually a win for Kotlin as well since Kotlin has to support "platform types" where it can't tell if Java code is producing null or non-null types.

That's probably the largest gap in usability.

arein3 2d ago
Waiting so long for type safety and hoping it won't be a weird implementation.

Kotlin's implementation is very good, hoping with all my heart java will copy it

jeroenhd 2d ago
The JEP is what I would call a weird implementation: https://openjdk.org/jeps/8303099

Types would be nullable (default), explicitly nullable (new), and explicitly non-nullable. Concretely, something is nullable, nullable, or non-nullable.

"Nullness is ignored when determining whether two methods have the same signature" is also rather annoying, this takes away the ability to provide fast overloads for non-null types for instance. Feels like a missed opportunity to me, though I'm sure there are reasons for that.

vips7L 1d ago
I love Kotlin and prefer to write it over Java nowadays but Kotlin has the same tristate nullability. It has nullable, non-nullable, and platform types when interacting with types from JS or Java.
biglyburrito 2d ago
Java still inexplicably doesn't have first-class support for string interpolation/templates. They were slated to add it in Java 23, but they removed it from the preview and it hasn't been seen since.

https://www.happycoders.eu/java/string-templates/

pjmlp 2d ago
Because the feedback on how they worked apparently wasn't that positive.

Also who cares having to use String.format() in the agentic coding age?

kbenson 2d ago
Honestly, I still write a lot of perl, and while interpolation is nice and good to have for simple things, I find myself using sprintf more and more in production code, especially in the cases the examples always show off, such as needing to alter the variable, add two together, etc. I assume those are just shown to illustrate you you can do that, and not imply you should, because commonly using arbitrary code execution within interpolation is something I'd feel compelled to beat out of my coworkers if I encountered it regularly.
ndriscoll 2d ago
The point of (sane) string interpolation is that it runs at compile time, which helps prevent people from inventing crazy template systems at runtime. And you automatically get things like safe SQL queries with parameter binding instead of injection vulnerabilities.
kbenson 2d ago
> The point of (sane) string interpolation is that it runs at compile time, which helps prevent people from inventing crazy template systems at runtime.

It's the trivial cases that are compile time. The interesting templating is done with input that isn't defined within the source, and those can't be run entirely at compile time.

> And you automatically get things like safe SQL queries with parameter binding instead of injection vulnerabilities.

That's been solved for decades with parameterized queries (binding variables), and doesn't require interpolation in any way (at least not in the context we're using it here, but probably not at all in most DB drivers and engines). In fact, I would go so far as to say if someone is trying to use string interpolation instead of parameterized queries to avoid SQL injection, they've fundamentally misunderstood the problem and are trying to solve it at the wrong layer.

Also, if the string interpolation for the query is done at compile time, what type of content known at compile time is something that we'd be worried about SQL injection being in?

whartung 2d ago
At a glance, the string templating system they were trying out looked pretty good to me.

And clearly a lot of work and time went into it with the previews.

The fact that they pulled it back suggests they looked down deep into an abyss that looked back at them, and what they saw scared them.

I don't know if there has been a published deep dive post mortem on the whole string templating thing, but I am quite comfortable with the team yanking it out if they felt it wasn't right.

pjmlp 2d ago
To me they felt overengineered, with all the customisation points that were to be available, and the way to use them.

Maybe there is something on the mailing lists.

mystifyingpoi 2d ago
Interesting point - syntax sugar was nice when writing code by hand. Now we'd be fine if no such sugar was added anymore ever again.
ndriscoll 2d ago
Syntax sugar still matters if you need to review the code. It was always about reading, not writing. Actually macros are even more powerful with LLMs. Metaprogramming has always been difficult for many (most?) programmers, but LLMs have no issue with it, and it's mostly doing the kinds of manipulations where as long as it compiles, it's correct, so you don't really need to review it in the same way.
pjmlp 2d ago
The LLM itself is the metaprogramming, eventually we will be getting executables directly, the dream of previous AI Summers might finally materialise, with all the implications for the victims of software factories automation.
hibikir 2d ago
Getting it all to work nicely with easy syntax without breaking backwards compatibility at all is not easy. It's the same reason Option types don't take off in Java, as the effort to make them work in an environment with no features for null safety is going to break things.

It's a very conservative language that wants to minimize breakage, and the tradeoff is not without costs. I would argue than in LLM-world, the right tradeoffs change, but I don't think a language like Java is going to assume everyone has an LLM to do migrations for free any time soon.

sthuck 2d ago
I would definitely trim like 20% off scala, but I think slow compile times were the main reason. To a much lesser degree but sbt being one of the first things you deal with when learning the language didn't help.

If you put a gun to my head and tell me you can't write in kotlin, you must choose either Java or Scala - for a small project I think I choose modern Java. If you tell me I need to choose for a large cross team and mission critical project - I think I take the bullet.

Still, I have a soft spot in my heart for scala, learned a lot from it.

hibikir 2d ago
SBT cleared a lot of footguns in the last decade or so, but at the time where Scala adoption was popular, it still had a lot of weird syntax, including the strange, meaningful whitespace, and enough DSL syntax to scare people.
blandflakes 2d ago
sbt breaking our builds every six months or so has become quite tiresome. It's still the worst part of Scala IMO
misja111 2d ago
I used to agree with your sentiment, but the LLM revolution has changed my mind. I noticed that the verboseness of Java leads to a higher token use when compared with Scala.
cryptos 2d ago
But you could save some token with Kotlin, too. The language is a bit less "magic" and compiles faster. So, Kotlin might be a sweetspot for AI code generation. Go is probably another good candidate.
sideeffffect 2d ago
> Java continues to try and catch up to Kotlin

Does it, really? Java has had pattern matching since 21 (2023). Kotlin still, to this day, the year of our Lord 2026, doesn't...

tancop 2d ago
Kotlin had it since 1.0, it's just called `when` instead of `match`.

https://kotlinlang.org/docs/control-flow.html#expressions

za3faran 2d ago
Kotlin does not have inline destructuring in pattern matching.
ackfoobar 2d ago
Pattern matching with only positional destructuring is a bad feature. I've written enough Scala to say this with confidence.
wiseowise 2d ago
That’s just fancy switch statement.
dtech 2d ago
Kotlin has always had smart-casting in if and when (sort of switch) expressions, which are strictly more powerful than the current version of Java "pattern matching". Both don't do de-structuring which I would consider a basic requirement for pattern matching, but at least Kotlin didn't call it pattern matching.
14u2c 2d ago
This comment has major 2018 vibes. Most enterprises I know have have migrated back to mainline Java. We'll see if this changes again LLM era, now that prioritizing the talent pool is less important and the friction of migrating stacks is much reduced.
Sharlin 2d ago
To be fair, sealed classes were introduced in Java 15 back in 2020. But granted pattern matching is what makes them really useful.
pjmlp 2d ago
It is the Java Virtual Machine for a reason.

Without Google's godfather, Kotlin would be gone for now.

Kotlin is so great, where is the Kotlin Virtual Machine, showing how Kotlin can stand on its own?

jayd16 2d ago
Uhm, Kotlin does compile to native without the JVM doesn't it? It runs in the JVM for Java interop, no?

https://kotlinlang.org/docs/native-overview.html

dathanb82 2d ago
Pretty sure it’s the other way around: Kotlin was implemented on the JVM and that’s its primary target, and got a native implementation later for folks that don’t want to run a JVM.
jayd16 2d ago
Is that relevant? Kotlin fully exists without the JVM, as requested.
dathanb82 2d ago
Sort of? The kotlin standard library yes, but it’s pretty rare to write an app with only the standard library. It feels like you’re dismissing the Java interop as a secondary benefit, but for most uses of Kotlin it’s probably more important to project success than the language itself. So I think it’s accurate that without the JVM Kotlin would be essentially dead.
pjmlp 2d ago
A subset of Kotlin has a native backend, not good enough to power JetBrains products, which still require Java Virtual Machine.

It is so good, that JetBrains themselves rather use Rust or C++ for native code libraries on their IDEs, and not officially supported on the Android NDK.

vips7L 1d ago
No it doesn’t. You can’t even read a file without Java.
pjmlp 2d ago
Nope, the JVM is the main platform, and also used for Android development, which by the way, still mostly uses Android Java on the platform code.

Kotlin Native came later, its memory model was incompatible with Java, thus breaking portability for libraries between JVM and Kotlin Native, thus Jet Brains had to reboot Kotlin Native memory model.

https://blog.jetbrains.com/kotlin/2021/05/kotlin-native-memo...

Currently it is mostly used to port Android code into iOS apps, hardly something that even JetBrains could show off running their IDEs on top off, nah, they still need the JVM.

troupo 2d ago
Of all languages the only one it's trying to catch up to (and in places outran) is C#

With many others it doesn't compete because it would need a from-scratch rewrite of the runtime (Erlang/Elixir), or a from-scratch redesign of the type system (Rust, perhaps Swift).

pixelstack 2d ago
Scala*