JDK 21 Release Notes
Comments
One aspect of virtual threads that has gone somewhat unappreciated so far is that if you (ab)use some internal APIs you can probably implement the testing technique used by FoundationDB.
For those who don't know, FoundationDB is a horizontally scalable transactional (KV) database used by Apple for their cloud services. It's famous primarily for its testing approach in which they can run the entire set of microservices in a simulator that can both inject faults, but also completely control the scheduling of work. In this way they make the system entirely deterministic, and can then pseudo-randomize simulations to uncover race conditions and the like.
To do this they created a custom DSL on top of C++ called Flow which is compiled to coroutines.
https://www.youtube.com/watch?v=4fFDFbi3toc
Flow and what it enables is considered (by them at least) to be a key competitive advantage over other database engines.
With Loom the JVM now happens to support the very same thing, just not officially. That's because the JVM makes file and network IO fully "pluggable", virtual threads can be supplied with a custom scheduler, and classloaders can be used to run multiple programs within the same JVM (with some caveats). So if you wanted to make a deterministic simulation of your microservice based system then you could do that now.
The above would be a rather advanced project for intrepid developers only, but it'd be interesting to see someone explore that at some point.
Interesting idea. Are there any implementations of this sort of thing in the wild?
It was explored a bit in a blog article with some sample code. One big issue with this approach right now is that the scheduler is not officially user replaceable. It's possible using reflection to do so though, at least is was in 2022, the internal code may have changed since then. There's been some indication from the OpenJDK team that this might be an officially supported feature in the future.
Blog article: https://jbaker.io/2022/05/09/project-loom-for-distributed-sy... HN discussion: https://news.ycombinator.com/item?id=31314006
Cassandra has a deterministic testing framework in this vein that will support Loom before too long. Loom is more of an efficiency enhancement, however, permitting you to run a simulation with a single operating system thread, avoiding the overhead of managing which thread the OS is permitted to run. The framework already intercepts all of these operations and ensures they are executed deterministically.
As much as I want to clown a little on the language for taking so long to do string templates, congratulations on getting virtual threads shipped, I can't imagine the effort that must've took to implement into something as hairy and complex as the JVM!
The JVM originally had green threads, super super similar.
How is this new release better than Java 1.4.2? What can a server running Java 21 do that wasn't possible in Java 1.4.2? It's an opportunity to reflect on the material differences in this new release.
Comparing it to Java 1.5 is a better baseline since before that, Java didn't have a defined memory model so was an implementation-defined 'wild west'. Also, we can then skip the basic has/not-has generics.
> What can a server running Java 21 do that wasn't possible in Java 1.4.2?
Virtual threads
Is this a(n attempted) troll or an honest question? There's a mountain of new libraries and language features since then ...
At that point, why develop anything? The first Turing-complete processor is enough for everything anyone will ever want.
If me as someone experienced with the JavaScript ecosystem would like to get started learning latest Java, where should I start?
Interested in building web applications.
String templates seems like a basic needed and welcome feature nowadays
A bit late to the party, but yes. Although, in true Java form the designers sacrificed readability to satisfy lesser use-cases.
STR.”Total: \{count}”
Could not get uglier.
I haven't tried it yet but I'm guessing this will fold into a different visual representation in IntelliJ when the cursor isn't nearby. It's not pretty but if it's consistent I guess it's fine.
I’m using project manifold’s string templates[1] and loving it.
1. https://github.com/manifold-systems/manifold/tree/master/man...
> I haven't tried it yet but I'm guessing this will fold into a different visual representation in IntelliJ when the cursor isn't nearby.
sigh
Eww, you're not kidding, that's awful.
Java is my most ambivalent language. There's things I love, and things I hate, and when it's good it's really good, when it's bad ... Yuck.
That said, I do think Python went worse with f-strings. Would have loved to see Java look to Groovy for inspiration.
It looks so bad in the context they give themselves:
C# $"{x} plus {y} equals {x + y}"
Visual Basic $"{x} plus {y} equals {x + y}"
Python f"{x} plus {y} equals {x + y}"
Scala s"$x plus $y equals ${x + y}"
Groovy "$x plus $y equals ${x + y}"
Kotlin "$x plus $y equals ${x + y}"
JavaScript `${x} plus ${y} equals ${x + y}`
Ruby "#{x} plus #{y} equals #{x + y}"
Swift "\(x) plus \(y) equals \(x + y)"
And finally:
Java STR."\{x} plus \{y} equals \{x + y}"
It's explained if you look at the proposals: They wanted to avoid any and all risks of backwards compatibility with any existing templating tool in the Java ecosystem. Every sensible syntax was covered by one of those, so the core language is stuck with the least bad one available.
...at what cost tho
The JEP makes it kind of clear:
> trading off a small amount of convenience to gain a large amount of safety
*large
*small
You can create json and sql string template processors that suffer from no injection problems. I'd call that a large improvement over, for example, Javascript. But I'm not familiar enough with the string template feature in the other languages to comment on their relative security.
It's prefixed by "STR." so the syntax is different from any existing tools ?
It remember me the first proposed syntax for lambdas. Let's hope the next preview will fix that.
As a reminder, you still can make it $.”…” in your code, if you wish.
Is there any good docs website for Java? These release notes have such a bad design, and it seems that if you want to learn more of what are the features you need to dig into the mailing lists.
http://dev.java might be what you're looking for.
I kind of like the inside java website for these kind of things: https://inside.java/2023/09/19/the-arrival-of-java-21/
https://docs.oracle.com/en/java/javase/
If you're like me and more interested in language additions, each release entry has a "Language Updates" section (under "Language and Libraries") with example code/usage.
Virtual threads, generational GC, manual memory management, all things .NET has had for ages. Too bad it can’t shake the “MS tech is boring” reputation
Aren't you quite limited when targeting or developing on non-Windows platforms with .NET?
Not with the current versions, which are devrived from .NET core.
.NET officially supports Linux, macOS, iOS, and Android.
"Officially supported" is not the same as supported well.
Well, yeah, that's been Java's explicit philosophy for decades. Let other languages evaluate cool new features first, then learn from their mistakes and promise backward compatibility "forever".
Also, keeping the language conservative, but being state of the art on the runtime level — there is absolutely nothing even close to Java’s GCs.
I don't think .NET has virtual threads (async/await doesn't count), and it definitely doesn't have anything similar to ZGC/Shenandoah — two garbage collectors that provide sub-millisecond pauses with heaps up to the multiple terabyte range. You can also go completely pause-less on commercial JVMs from Azul.
The .NET GC philosophy, so to speak, is to provide such guarantees by allowing programs to avoid GC entirely with value types.
Eventually, Project Valhalla will provide this on JVM-land, but it doesn't seem like the day will soon come.
It's not generational GC, it's generational ZGC.
ZGC is a concurrent GC, both the marking phase and the evacuation phase are done concurrently with the program running. A generational ZGC is a generational concurrent GC.
MS does not have such kind of tech.
Would be cool if oracle worked on their presentation a bit. Whenever MS releases new C# features they've got a blog post showing the features off and how they can be used. Oracle just kind of dumps a bunch of JEP links
There is ton of Java related announcements, articles, tutorials and so on at https://dev.java/
Maybe this is a better link for you? https://jdk.java.net/21/release-notes
Each feature links to a deeper dive into it.
That's the link I'm talking about. A website which looks like it's from 2002, linking to each JEP. No syntax highlighting, and paragraphs of text to read before really showing a compare/contrast. It's the kind of thing that was written by programmers and not by writers
Compare to C# releases:
https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/cs...
Most links take you to a section of the same page, with syntax highlighting and optional light/dark mode on the site. For some features, it doesn't explain the rationale behind the change, it just shows the before/after, which is really all I care about:
https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/cs...
Personally, all of what you wrote is a benefit when it comes to release notes. I don't want flashy GIFs, words written by non-programmers who don't actually understand what they are writing and I definitely don't want the marketing/web design part of a company to be involved in it.
Simple, tell me what changes, where I can read more and that's it. It's the perfect release notes.
But, as always, people like different things, as is apparent here :)
I just want important information boiled down to code examples and lists of features for me. I don't need to read an essay on the history of string interpolation
Can the JVM represent a uint32 yet or a uint64?
If so, I overlooked it.
Long should be able to do the trick for you?
> Long l1 = Long.parseUnsignedLong("17916881237904312345");
Or, just use BigInteger
Java not having unsigned types is a feature IMO.
Are there modern learning resources available for Java?
leetcode
Best learning resource imo is https://www.onjava8.com/ (it covers up to java 17)
Bruce Eckel is fairly well known for his original Thinking in java/c++ books, and I find his style of writing great. He has a knack explaining topics in an easily digestible thorough way, while not being completely dry. Its a 1200 page book, and can double as a reference, but actually explains the when, how and why in thoughtful writing enough to double as a great book for learning and a reference when needed. I knew a lot about java and learned a lot of little things about topics I already knew, and it breaks down each piece of the language so you can easily gloss over things you already may know from other from other languages.
Virtual threads are going to make a lot of code much simpler.
Oracle managed to create one of the least ergonomic string interpolation syntaxes of any language I've ever seen. Using backslashes is truly a terrible move
In the JEP (https://openjdk.org/jeps/430) there is a bunch of prior art:
C# $"{x} plus {y} equals {x + y}"
Visual Basic $"{x} plus {y} equals {x + y}"
Python f"{x} plus {y} equals {x + y}"
Scala s"$x plus $y equals ${x + y}"
Groovy "$x plus $y equals ${x + y}"
Kotlin "$x plus $y equals ${x + y}"
JavaScript `${x} plus ${y} equals ${x + y}`
Ruby "#{x} plus #{y} equals #{x + y}"
Swift "\(x) plus \(y) equals \(x + y)"
Is any of those actually better? You have a non-existing better suggestion that works in the same cases they outline in the JEP?I think the following is the main part to keep in mind:
> For Java, we would like to have a string composition feature that achieves the clarity of interpolation but achieves a safer result out-of-the-box, perhaps trading off a small amount of convenience to gain a large amount of safety.
Yes, all of those except swift are better
Bikeshedding of the noblest kind. It is all subjective.
They are just following that other famous language for developing mobile apps.
While I don't like it, all other alternatives were kind of taken by libraries.
And who knows, since it is preview, it might still change.
May I introduce to Swift? They even decided on that without any backwards compatibility limitations. It really is not bad, does it really matter if it’s a $ or a \ ?
using backslashes for anything other than escaping a characters (e.g: \" or \n) makes it a nightmare to try to coerce the compiler into understanding what you're trying to do if you combine escape characters and syntax in the same string
You get a giant ass bracket after it. How is it not readable?
“\{a}+\{b}=\{ a + b }\n”
Do you also criticize this? “Price: $${price}”?
Try, for example, generating a regular expression which features backslashes in it. You will get an unreadable mess and probably have to play with it for a while before java will even compile it
I very hope you are not generating regexes based on user data..
Also, I’m fairly sure $ is a special character in regexes..
Not all variables are user data
They are maintaining backward compatibility.
"A \{variable}"
is currently invalid syntax, so it's fine to use. Something else like "A ${variable}"
is valid syntax, so using that would break existing code.
It's fine to break backwards compatibility when introducing an entirely new feature across a major version, actually. What's even the point of a major version bump?
There's plenty of languages that have your attitude. Java doesn't. Feel free to use one of those other languages.
Oracle has broken compatibility between versions before, at least in the runtime. URL Decoder's constructor was taken private after JDK 11:
https://docs.oracle.com/en/java/javase/11/docs/api/java.base...
https://docs.oracle.com/en/java/javase/17/docs/api/java.base...
There was a nice long deprecation period between those versions, where everyone using it got a warning. And then, when it was finally removed, everyone using it got an error, and they were forced to change their code. By contrast, a string literal could silently change meaning if it used syntax that was previously valid in strings. This is the exact same reason JS chose to require backticks to delimit interpolable strings - because otherwise existing string literals could silently change meaning.
Not everyone uses semver, although in any case Java "major" versions are actually a continuation of what was called the 1.x line -- they gave up on writing out the "1." a couple of decades ago. They're semver minor release, not semver major.
Plus, prepending the string with `STR.` changes the syntax entirely. Those strings are still invalid if you don't prepend `STR.` (which is another thing I hate, but not quite as much).
In C#, the string `"""Hello world"` is invalid, but `@"""Hello world"` is, because double quotes are valid with verbatim string literals.
I have a genuine question about Loom / virtual threads / etc.
In languages with async-await syntax, I like that it is explicit what is sync and what is async. I also like how the "bind" points within an async expression make it clear where the syncronization happens. It seems to me that Java 21 makes all of this implicit, which is a step backward in this respect.
Similar to how types enforce valid usage (a foundational idea in Java), I feel that an Async<T> should not be implicitly a T.
Are people concerned about this at all?
Have the Java designers addressed this?
Yes. The preemptive style (where scheduling points are determined dynamically) is more composable than the cooperative style (where scheduling points are determined statically). In the cooperative style, the correctness of any subroutine is implicitly dependent on there not being scheduling points where they're not explicit; adding a scheduling point to any subroutine, therefore, requires reconsidering the assumptions in all of its transitive callers. In the preemptive style, on the other hand, any subroutine can independently express its mutual-exclusion assumptions using various synchronisation constructs, such as locks (those independent decisions could result in deadlocks, but the cooperative style is also subject to deadlocks).
So if you start from scratch, the preemptive style is preferable, but for us the decision was even easier, because we weren't starting from scratch. Java already had threads, and so mutual exclusion was already explicit rather than implicit.
When is cooperative scheduling more appropriate? In a language like JavaScript that already had a lot of existing code with implicit mutual exclusion assumptions (because JS didn't have threads). To add concurrency to such a language with a large existing ecosystem, cooperative scheduling is a better choice.
Ideally knowing "what is synchronous and what is asynchronous" shouldn't be your concern, just like freeing memory isn't your concern in a language with garbage collection. Similarly, you don't have to know the "real" memory addresses when using virtual memory.
Ask yourself why do you want to know what is asynchronous. In a typical server-side app synchronous, blocking, thread-per-task code is easy to write and read. There is only one drawback: you can run out of kernel threads. Asynchronous code solves this problem, but virtual threads solve it in a better way, because you can have three orders of magnitude more virtual threads compared to kernel threads. Ultimately, it's all about the number of threads you are allowed to use.
Async vs green threads doesn't alter the number of running threads available to you, it's simply a difference in how the runtime schedules your virtual threads on the limited number of real threads. Async/await is cooperatively scheduled, Java's virtual threads are preemptively scheduled.
Both can have similar performance characteristics, the difference is primarily one of ergonomics, and since that's subjective there are people who will argue fiercely for either side.
This is an old discussion, Unyielding (https://glyph.twistedmatrix.com/2014/02/unyielding.html) discusses the trade-offs between function coloring and explicit transaction control from the point of view of "I need explicit transaction control" and of course What Color Is Your Function (https://journal.stuffwithstuff.com/2015/02/01/what-color-is-...) discusses it from the point of view of function coloring. Which one you want depends on your application, unfortunately.
I'm not personally concerned, no. You need to think about threads to efficiently exploit multi-core hardware anyway. Otherwise the best you can do is hope you're implementing isolated requests with multi-processing, and assume that you'll never benefit to exploit intra-operation parallelism.
One problem with async/await is that the term "blocking" is under-specified. If you try and nail down what is and isn't blocking, like with a formal way to classify operations as such, you rapidly end up with a lot of contradictions and gray areas. It's a distinction that doesn't really make sense except as a hack to work around expensive threads/callstacks, which is what Loom solves.
For example, is reading a file blocking? On modern computers this could do anything spanning from being purely a set of branches and pointer dereferences as a kernel block cache hit, thus being little different to any other code, or it could be an NVMe flash request (extremely fast, probably not worth switching the thread), or it could be an HDD seek (fairly slow, worth freeing up the thread to do something else), or it could be a network RPC (extremeley slow, definitely worth freeing up the thread). You may not even know ahead of time what it is. So, should the API be blocking? Or marked async?
The right place to figure this out isn't in hand-written code. That'd be a performance hack similar to the old C register keyword. The runtime has much more information on which to make the right call, but for that, the code has to be written in a uniform style.
More generally, type systems ideally aren't encoding performance-specific stuff into them. Type systems help with program organization and correctness, and as a happy side effect can also guide compilers to make better code, but the performance of an operation shouldn't itself be exposed in its prototype. Otherwise optimizations can break code, which wouldn't make sense, being able to optimize modules independently is a big part of why you want dynamic linking.
They also have enough gotchas that one of the main ASP.NET architects had to write a best practices document to address all common misuses.
[dead]
It's a different priority, not a down grade. Most people in Loom discussions really don't understand why cooperative async is set up the way it does and why explicit yielding is important if you need to manage a single UI or GPU bound thread. Most of the replies you get are just thinking about raw bandwidth of nameless threads.
But the structured concurrency stuff they added is sorta kinda familiar to explicit yields (as long as you set up everything properly). IMO its uglier than async/await but I also deal with main threads a lot.
I'm excited to see if this breeds any interesting UI frameworks or if its mostly just about server bandwidth.
The question of "single UI or GPU bound thread" could be solved in a future release with custom virtual thread schedulers. Java 21 concentrates on solving the problem of server-side applications.
Why do you like that? What problems has it helped you solve or avoid?
Not OP, but I agree with them. Here's my take:
If you design your software right, you can use async/await similarly to IO in Haskell—functions that are not async are fast and unlikely to fail, while functions that are async are potentially slow and likely to throw exceptions. This partition can help you reason about and structure your code as you're writing it for solid error handling and minimal duplicate I/O.
Yes but Java is not Haskell.
If you want a monadic IO on the JVM, Scala offers two ecosystems with extremely capable effect systems, providing highly performant runtimes and good (but not perfect) ergonomics.
I've always found async/await bolted on older, mostly imperative OO languages with exceptions to be pretty disappointing. Even modern designs on languages with proper error handling like Rust get their fair share of criticism.
I get that virtual threads will probably introduce surprising bugs and performance issues in some corner cases. But it also removes a whole lot of craziness in the Java ecosystem, namely reactive programming.
It feels like that's coupling some concerns together. Functions that do IO are more likely to fail, yes, but that doesn't have to do with their async-ness or their slowness.
It's not that async-ness causes things to fail more easily, it's that if you're going to make a function async it's because it interacts with the outside world in some fashion, and interaction with the outside world is slow and likely to fail.
We could use a different name for that concept to make it more clear why these ideas are related, like `IO`, but in practice async=IO in most code.
It seems like we're on the same page. My point is that your last comment...
> in practice async=IO in most code
does not need to be true. We will never avoid the complexity of IO, but we can avoid the complexity of async, and virtual threads provide us that opportunity.
I didn't mean that = to be transitive. All async is IO, but obviously some IO is not async.
> We will never avoid the complexity of IO, but we can avoid the complexity of async
Because we can never avoid the complexity of IO, having a language construct that makes it obvious when you're using IO is valuable. IO code is different in kind than non-IO code, and having it explicitly marked helps keep the mess that is IO from propagating its problems throughout the rest of your code.
Java has checked exceptions, so you don't need anything to signal that it is "likely", you can literally declare it in your code and have the compiler tell you if you're screwing up.
A common pattern is to use a single thread executor as one context. Any task scheduled to that context will not run concurrently. When you do it this way, every synchronous chunk (between yields/awaits) is a critical section. So you get critical sections easily, cheaply and with a pretty good syntax.
This pattern is used a lot when you have things like UI which use a single threaded design, or if you need to bind to do some IPC in a single native thread. It's actually very common.
That’s the idea behind suspend keyword in Kotlin:
https://elizarov.medium.com/how-do-you-color-your-functions-...
I agree with you—I prefer explicit synchronization. But this isn't an accidental flaw in Java's design, it's the entire purpose of their chosen model. A lot of people dislike the fact that async/await causes function coloring [0] and as a result may lead to duplication of sync and async version of functions. These people see Java as dodging a bullet and learning from past mistakes in other languages, so it's unlikely that Java will have anything comparable to async/await.
[0] https://journal.stuffwithstuff.com/2015/02/01/what-color-is-...
Java 21 is colorblind - but the colors still exist
Yep, I agree. I tend to be a type system maximalist: I'd rather have everything that is possible to encode statically be encoded statically, even if it leads to less flexibility later.
If you're starting a new Java codebase from scratch, you can create the structure where the language doesn't using classes, but it'd be nice to have syntactic support for it.
This property is not really part of the type system though.
Also, as mentioned by another commenter below - is a file exists operation async? Sync? Why, why not?
What if file exists operation MUST be sync?
Then you don’t start it in a virtual thread. Your calls automatically become blocking or not blocking depending on your callsites, which is the proper place to decide that.
So it still has colors?
In TypeScript at least, async-ness is encoded in the type system, because async/await is just syntactic sugar for Promise/then/catch.
Well, JS is paired with a single-threaded execution model, so you only have concurrency to worry about. C#, Java has proper multithreading as well, that gives another dimension to the problem.
I wonder if we'll see Lombok markup for @OnVirtualThread or whatever the pattern shakes out to be.
Lombok is unfortunately a separate programming language, albeit insufficiently distinct from Java.
(Why? Because it uses internal compiler APIs to change the behaviour of code you write in ways that are not valid: annotation processors are supposed to generate new classes, not mutate existing classes.)
Structured concurrency does give you a “structure” on possible fork and join points, for a more high-level management.
Within virt threads, does it really matter? Every function takes as much time as it takes - the reason you use virtual threads is to make that efficient, the semantics is clear - your code is blocking, and waits for every previous statement.
Also, it is not quite valid to model it as types - these are more like effects.
Why should that matter? If you want to run two tasks concurrently, you run them concurrently the same way, whether they are blocking or not.
In many languages the distinction is there because you have two versions of the IO routines, synchronous and asynchronous, and calling a synchronous one will block you asynchronous task. With virtual threads we took a different approach: if you make an IO call on a virtual thread it will do the IO asynchronously if at all possible and yield execution. This means you can take existing code and run it on virtual threads without modification and it will pretty much just work.
Not all IO operations can be done asynchronously on all platforms, and some new interfaces had to be introduced to allow for asynchronous name lookup, and some code needs refactoring because it had implicit assumptions that it would be used by tasks in a small thread pool and so used thread local values as a cache for expensive objects, but in general I think the design has achieved the goal of allowing existing code to be moved to virtual threads without the requirement to rewrite everything or scatter await / async all over the place.
Finally virtual threads I can use in production with generational ZGC! I've been using Kotlin coroutines for a while now, and want to see how that evolves with JVM now supporting virtual threads out of box.
I'm curious: will virtual threads make coroutines "better" (like, more efficient), or will they just enable different ways of doing concurrency?
Both - Kotlin's coroutines are a runtime for green threading. The implementation of that runtime could use Java's green threads for many things and where the developer UX or guarantees aren't there, fall back on other primitives. But users of Kotlin could also just use Java's new virtual threads if they don't need anything fancy from Kotlin's coroutine runtime.
You probably wouldn't want to _mix_ these threading approaches in a single codebase unless Kotlin's coroutines become a literal "dev UX facade" because otherwise things like ThreadLocals (and whatever Kotlin provides as an alternative) can get out of sync.
java async stuff is not colored :)
True. Although, I find that in practice Kotlin's coloring is quite pleasant to use due to the calling conventions and the tooling.
Color is a feature in Kotlin: https://elizarov.medium.com/how-do-you-color-your-functions-...
Duplicate of: https://news.ycombinator.com/item?id=37569573
Thanks! It's probably better to merge the comments hither since this URL is more informative.
I know java has fallen out of favor these days. I blame the ghastly complexity of j2ee and Spring, which can't seem to say no to any academic design pattern. Also Oracle
But java is a peach for complex systems that need to scale both in terms of performance and in terms of number of people working on the system.
I wish there was a good way to separate Java from the hell that is Spring. Technically I don’t have any issues, but the culture that goes with Spring destroys all software and my will to live.
Can you elaborate
Includes JEP 449 (deprecation of Windows 32-Bit builds) https://openjdk.org/jeps/449
I wonder how long those builds will remain available.
How do people deal with old clients? The Android app I (partially) work on can't even use JDK 8 (I learned this when trying to use Double.hash() to hash a double).
Coming from C++, where I can use C++20 and still deploy to ancient devices, I'm seriously baffled how people work with Java. Is there any way to use new JDK features in an Android app that also has to deploy to Android SDK 21 (i.e. Android 5.0 from 2014!)?
You can always use -target.
Most Android development is done in Kotlin which is where Java has been copying it's features from for the last 5 years. In that way, Android programming has been the future of Java programming today. But now Java has basically caught up, I blame this on Kotlin having lost focus on multiplatform and having stopped innovating new language features.
Android doesn’t use proper Java, it runs their own bastard child of it. You can sometimes get away with byte code desugaring of more modern features sometimes, and it has been getting better recently, but it is far from ideal.
If you develop for Android you likely use either Kotlin or Flutter; either way there's very little in this Java release that matters or should excite you. Kotlin has already had most of the features that Java has been adding over the last few releases in some form or another and most Android users use those a lot. So, from that point of view there are maybe a few things that Kotlin could do nicer but nothing world shocking.
The things I'm most interested in are JNI-related. Project Panama[1] will bring a ton of improvements. These aren't stable in JDK 21 but I'm anticipating their stability in the near(ish) future (JDK 22? 23?). I'd like to experiment with some of the preview APIs in JDK 21 but given I can't even use JDK 8 I'm wondering if I'm just SOL and if I should abandon all hope for me using things from Project Panama.
Some options for those who prefer to avoid the Oracle minefield:
https://aws.amazon.com/corretto
https://www.azul.com/downloads
https://bell-sw.com/pages/downloads
Sadly, no-one has managed to package it yet, but we should get something in the next couple of days. Since 21 is an "LTS" release, major Linux distributions will provide a runtime pretty soon. Ubuntu backports them to old releases too.
What is that minefield? How is avoided by repackagers? Do they take legal liability of Oracle lawsuits related to Java?
Oracle's stance on what contexts their JDK/JRE is free to use in is ... nuanced, and has changed in the past. While I don't wish to spread FUD, the thought of using it in a commercial environment in any fashion would give me pause. Playing around with Oracle on licensing is, as hobbies go, somewhere on my list of things to do down around making plutonium cocktails and alligator juggling.
The other vendors have different (typically more explicitly open) licensing on their shipped artifacts. As ever, if it really matters, it's time to consult a legal professional in your jurisdiction to be sure instead of taking a random internet nerd's opinion on it. ;)
All those vendors offer Oracle software that's licensed to you by Oracle. Amazon, for example, cannot change the license on the Oracle software they're distributing because it's not their software. The Oracle license is a standard open-source license, but if it makes you feel better to download Oracle software with an Oracle license from some other company's website -- go for it.
But Oracle can try to lure and deceive you into installing a variant that is affected by more licences than the stuff also available from others.
"But almost all of this would have been available elsewhere under a standard open source licence!" is worth exactly nothing if one day you discover that you failed to stay clear of all their friendly little traps.
Technically, the OpenJDK project is independent of Oracle, even though they do sponsor and presently lead it. That source is no more Oracle's than the linux kernel is Red Hat's (in both cases, a large contributor with their own copyrights, contributing to an open project with open license).
The other vendor's shipped artifacts are in turn the work product of those vendors, based on the OpenJDK core. They are not distributing Oracle software, they are distributing artifacts built from the OpenJDK project's software, and their licensing decisions are independent of constraint from Oracle.
The Oracle build of the JDK has not always been isomorphic to those produced based on the OpenJDK code, and has in the past had different license terms that can in no way be characterized as a standard open source license. See, for example, graalvm use in production environments with OracleJDK. Being transparent, this may have changed in the past few years; ever since I left Oracle I've tried to think about their existence as little as possible.
My overall point is that it is wise, based on Oracle's past history, to read and carefully evaluate the license terms for Oracle's software before you use any of it in a commercial context. The strictly-GPLv2-with-classpath-exception-sourced vendor distributions of OpenJDK, not so much as the license is in fact standard and widely used/understood already in both commercial and non-commercial situations.
> Technically, the OpenJDK project is independent of Oracle, even though they do sponsor and presently lead it. That source is no more Oracle's than the linux kernel is Red Hat's (in both cases, a large contributor with their own copyrights, contributing to an open project with open license).
That's not true. Oracle owns the OpenJDK project much like Google owns Chromium. To contribute to OpenJDK you need to sign a Contributor Agreement granting Oracle shared ownership over your contribution (i.e. you maintain copyright, but Oracle also has copyright); this is standard practice for such open-source projects (like Chromium). On the other hand, while Linux is developed by various for-profit companies, the code is owned by the Linux Foundation. As of now, there is no separate legal entity for OpenJDK. I'm not sure it makes a difference in practice, but if you wanted to be technically correct then that's the situation.
> The Oracle build of the JDK has not always been isomorphic to those produced based on the OpenJDK code,
No. We offer two builds of the OpenJDK JDK, under two different licenses. The open-source one is here: https://jdk.java.net It is an official Oracle build.
The other official Oracle build (called by various names, including Oracle JDK) has a different license, but only diverges from OpenJDK some time after a feature release due to different bugfix backports.
> The strictly-GPLv2-with-classpath-exception-sourced vendor distributions of OpenJDK
Just pointing out that Oracle also distributes the JDK under that very same license (https://jdk.java.net), but we also offer our software through other vendors. It's up to you which website you choose to download our software from.
From the other website, it’s Oracle software under the GPLv2. From Oracle, it’s under the Oracle Binary Code License for Java (https://www.oracle.com/ca-en/downloads/licenses/binary-code-...). These are NOT the same license. The GPL, for instance, does not allow after-the-fact fees.
Oracle OpenJDK: https://jdk.java.net (GPLv2 + Classpath Exception)
Oracle JDK: https://www.oracle.com/java/technologies/downloads/ (Oracle No-Fee Terms and Conditions)
The license you linked does not apply to current JDK versions at all.
Java’s reference implementation is OpenJDK, developed mostly by Oracle, and is completely open-source (GPL-2).
One can optionally buy support for it, that’s the whole deal. Not that hard, but apparently this is a minefield for some..
There’s that and there’s https://www.oracle.com/ca-en/downloads/licenses/binary-code-... , where using the wrong runtime flag makes you owe money.
There are two main branches of JDKs, OpenJDK and Oracle JDK (both maintained by Oracle).
Builds for the former are open-source but only updated for 6 months after each release, while the latter is under a commercial license with 3+ years commercial long-term support for some versions.
Projects like Eclipse Temurin (Adoptium) bring the LTS to OpenJDK. The underlying code is entirely open-source, so there is no licensing trouble.
Hence, if you want to download OpenJDK, it's important to do it from Adoptium, as you won't get updates on java.net after a while.
"Oracle(Java) is knocking at my company's door and they want money." - https://www.reddit.com/r/sysadmin/comments/165kzxg/oraclejav...
“I downloaded Photoshop and now adobe asks for its price”
The JDK is GPLv2, which means it can be legally redistributed without charge. Oracle puts their JDK release under a different license. Microsoft distributes the binaries under the GPL. It’s the same code going into the binary, but different license terms.
Oracle provides their builds for free, given you use their latest LTS release. But that’s apples to oranges comparison - that is an actual support release, meant for big enterprises that wants to point their fingers at someone on Christmas eve.
OpenJDK (maintained by Oracle) is GPL, but Oracle JDK is a different codebase with different features under a commercial license. Oracle's OpenJDK builds are distributed under the GPL, like Microsoft's.
Terrible take. Photoshop does not create and exploit confusion about how their products are free to use as much as Oracle does with Java. Photoshop also didn't sneakingly change terms from what used to be free without a proper notice while installing. Photoshop sales and marketing also don't do gotchas with crazy audits and threats of lawsuits. You don't need to buy Photoshop for all your employees if only a few use it.
> Photoshop also didn't sneaking change terms from what used to be free
Like making it even more free? You are literally free to use the “paid” OracleJDK on its latest LTS release up until the next LTS comes around+1 year.
Come on, just stop this FUD already.
Oracle is responsible for 95+% of every single commit going to OpenJDK, which has the exact same license and situation as the goddamn linux kernel, GPL2. Do you also sweat a lot about accidentally installing red hat linux? If not, you have zero reason to worry.
How is amazon compiling and repackaging the exact same thing somehow different?
People have been burned too many times by Oracle “true-ups”. And no, it’s not FUD. Their license explicitly states that JEE/SE (not JRE) is covered by their commercial license, not their open source license. This year, in 2023, Oracle introduced the enterprise-wide metric license model. So if you use Java for anything in your organization, they charge you based on your organization size. (All or nothing model).
That licensing would be the Oracle JDK. Not OpenJDK like parent was talking about, which is wholly open-source (supporting SE but not EE) and also maintained by Oracle but without LTS.
See my other comment about Adoptium vs. OpenJDK vs. Oracle JDK: https://news.ycombinator.com/item?id=37571143
Yes, Oracle JDK, the default one you are directed to at oracle.com. Java.net provides OpenJDK.
There is no such thing as a JRE anymore. And even the commercial OracleJDK is free to use (though not libre).
Some people would beg to differ. There are some organizations that have Java desktop apps, some that have kiosks and appliances that run JRE-like environments. There are some that are still on Java 8.
This is what jlink is for.
Oracle's OpenJDK builds are only updated for six months after each release, while Adoptium receives long-term support (like the commercial Oracle JDK).
Even the commercial OracleJDK is free to use, as long as you stay on the latest LTS.
No one offers free long-term support. The free "long-term support" releases merely backport some fixes from the mainline to the old release, so only the intersection between the old version and the newest one is maintained in free distributions. A real LTS service includes original bug fixes. If you want the entire JDK to be maintained you have to either use the current version (completely free) or pay someone for a proper LTS service. The LTS service that Oracle offers is how OpenJDK is funded.
Just to be clear, all of these sites offer builds of Oracle software (the OpenJDK JDK), developed by Oracle and licensed by Oracle. When you file an issue that's in the JDK, those companies forward it to us, Oracle engineers, to fix.
We offer our own builds under two different licenses on our own websites (https://jdk.java.net or https://www.oracle.com/java/technologies/downloads/) but you can also download our software on the other websites linked above.
Java downloaded from Oracle has the following license:
> Important Oracle Java License Information
> The Oracle Technology Network License Agreement for Oracle Java SE is substantially different from prior Oracle Java licenses. This license permits certain uses, such as personal use and development use, at no cost -- but other uses authorized under prior Oracle Java licenses may no longer be available. Please review the terms carefully before downloading and using this product.
[0] https://www.java.com/en/download/
Getting JDK from better sources won't risk you accidentally hitting the wrong download link and winding up with an enterprise licensing audit and a legal requirement to Java license for every employee.
I'd strongly recommend you don't download anything Java related from Oracle or have any direct relationship with Oracle at all. Java employees working for Oracle and their advice should be treated skeptically. Reporting issues to third parties and letting them contact Oracle's engineers is ideal (least of all because you need a support contract to report it directly to Oracle anyway).
Oracle is a bad actor and should be avoided for your own good.
Corretto seems to have 21 as of just now.
Where's Java primarily used these days?
Enterprise ETL/CRUD stuff and (some) native Android applications.
Android doesn’t follow the Oracle Java though, right. So all these new features are just not there, right
Not yet, I believe they currently support everything (or nearly everything) up to Java 11.
The compile-time features end up being supported. The runtime features (e.g., invokeDynamic) less so.
OTOH, Android would benefit from virtual threads, too, which is one of the reasons they've jumped feet-first on the Kotlin train (and Kotlin's coroutines are pretty well designed).
I think the question is more where is not...
The world "primarily" is important here. Sure, you _can_ use Java for embedded and even real-time stuff. Some games are written in Java, etc. But no one would say that Java is the primary language of choice in such domains.
Agree. Will leave the comment up for my eternal shame.
Java has been used for soft real-time systems, such as payment processors or real-time bidding. For a GC-ed language, its latency guarantees can be pretty OK due to having good garbage collectors and runtime optimizations.
But, yes, obviously it can't be used for systems where latency is a matter of life and death, like operating the breaks on a car.
Or a nuclear power plant
https://www.quora.com/The-license-agreement-of-Java-says-You...
There actually are special, hard real-time JVMs that are used in missile defense systems. Do note that hard real time is not about performance (and as computers are so fast, you really don’t need much time for millions of calculations), but the guarantee of executing it in X time. But it does require very special engineering.
Agreed. And there are big answers of course: data science, web front end, embedded, etc. But when you exclude those things, it does leave a huge swath of "generic back end" software where Java has a large if not dominating presence.
Elasticsearch and Minecraft :)
I've built my search engine in Java.
It's very good for backend/server stuff in general. Robust to a fault, reasonably fast, excellent tooling, very stable ecosystem.
everything web related?
Java is to Javascript as car is to carpet.
Being pretty fast for a relatively simple user experience is one thing people use it for:
https://www.techempower.com/benchmarks/#section=data-r21&tes...
Sure, Rust and C++ is probably faster when used naively, but I'm sure you'd have a harder time develop stuff in those languages.
Personally I'd probably reach for Rust before touching Java with a ten-foot pole, but people have different preferences for how easy a language should be to pick up, and if you're used to OOP and C-like languages, Java is pretty easy to pick up.
Pretty much everywhere! I bet every single company in the S&P 500 has Java code somewhere.
> Where's Java primarily used these days?
I've seen a lot of enterprise-y webdev projects use it for back end stuff (Dropwizard, Spring Boot, Vert.X, Quarkus) and in rare cases even front end (like Vaadin or JSF/PrimeFaces). The IDEs are pretty great, especially the ones by JetBrains, the tooling is pretty mature and boring, the performance is really good (memory usage aside) and the language itself is... okay.
Curiously, I wanted to run my own server for OIDC/OAuth2 authn/authz and to have common features like registration, password resets and social login available to me out of the box, for which I chose Keycloak: https://www.keycloak.org/
Surprise surprise, it's running Java under the hood. I wanted to integrate some of my services with their admin API, seems like the Java library is also updated pretty frequently: https://mvnrepository.com/artifact/org.keycloak/keycloak-adm... whereas ones I found for .NET feel like they're stagnating more: https://www.nuget.org/packages?q=keycloak (probably not a dealbreaker, though)
Then, I wanted to run an APM stack with Apache Skywalking (simpler to self-host than Sentry), which also turns out to be a Java app under the hood: https://skywalking.apache.org/
Also you occasionally see like bank auth libraries or e-signing libraries be offered in Java as well first and foremost, at least in my country (maybe PHP sometimes): https://www.eparaksts.lv/en/for_developers/Java_libraries and their app for getting certificates from the government issued eID cards also runs off of Java.
So while Java isn't exactly "hot" tech, it's used all over the place: even in some game engines, like jMonkeyEngine, or in infrastructure code where something like Go might actually be more comfortable to use. I actually think that universities in my country used to have courses in Pascal and then replaced it with Java as the "main" language to use for introduction to programming, before branching out into C/C++/JS/Python/Ruby/.NET etc. based on the course.
From my personal experience:
All banking lives on Java.
All Alibaba and its Asian offsprings (like Lazada) is Java.
Some big enterprises moved from Java to Go (I know of Shopee).
Also, Amazon cloud, Apple’s server infrastructure, a huge chunk of Google’s as well.
I think most corporations use Java, probably "primarily" Java too.
Even ten years ago -- and it's only gotten better since then -- you could use java to managed hundreds of gigabytes of memory with thousands of threads, and it would sit there and just do it with uptimes of half a year or more, while taking an absolute beating.
It's really well-built tech, with a very nice distribution story: no docker mess, no scribbling all over the OS, etc. Once you install the jvm, you can just ship one file -- jars are just renamed zip files, so you can add all dependencies to just one file.
I used it for servers. I wish writing it were as nice as ruby or python, but java is really well built. If you haven't tried it, you should give it a whirl.
I can't wait to see if/how tools like Reactor and Kotlin Coroutines adapt with Virtual Threads, and if they extend the tooling, or provide a migration path.
I've written a tonne of code on both Reactor and Coroutines, and would love something simpler, but dread the idea of migration.
Anyone know when homebrew installs will be released for JDK 21?
Excellent! With virtual threads, all the blocking code I wrote is efficient now :)
Less-joking: I'm so excited for this to start getting adoption. For a pre-release/preview feature, Loom already has SO much traction, with most major frameworks already having support for running app/user code on virtual threads and one notable project (Helidon Nima) replacing netty with their own solution based on blocking IO and virtual threads. Now I want to see the community run with it.
I've always thought async IO was just plain gross.
Python's implementation is so yucky that after using it for one project I decided that I'd rather DIY it with multiprocessing than use async again. (I don't have any more constructive feedback than that, my apologies, it was a while ago so I don't remember the specifics but what has lasted is the sour taste of the sum of all the problems we had with it - perhaps notably that only about 2 people on my dev team of 5 actually understood the async paradigm).
netty did it fine. I've built multiple projects on top of netty and it's fine. I like event-based-async more than async-await-based-async. But it's still a headache and notably I really rather missed the kinds of guarantees you can get (in blocking code) by wrapping the block in try-catch-finally (to e.g. guarantee resources get freed or that two counters, say a requests-in and a requests-out, are guaranteed to match up).
But dang am I excited to not do that anymore. I have one specific project that I'm planning to port from async to blocking+virtualthreads that I expect to greatly simplify the code. It has a lot of requests it makes back and forth (it has to manually resolve DNS queries among other things) so there's good chunks of 50-200 ms where I have to either yield (and has gross async code that yields and resumes all the heck over the place) or block the thread for human-noticeable chunks of time (also very gross of course!).
Dotnet has had Tasks for years, seems like the same thing.
You're supposed to either await a Task, or to block on it (thus blocking the underlying OS thread which probably eats a couple of megabytes of RAM). It's a completely different system more akin to what Go has been using.
Nah, cooperative vs pre-emptive models.
It's not the same thing.
The TLDR is that it needs “function coloring” which isn't necessarily bad, types themselves are “colors”, the problem being what you're trying to accomplish. In an FP language, it's good to have functions that are marked with an IO context because there the issue is the management of side effects. OTOH, the differences between blocking and non-blocking functions is: (1) irrelevant if you're going to `await` on those non-blocking functions or (2) error-prone if you use those non-blocking functions without `await`. Kotlin's syntax for coroutines, for example, doesn't require `await`, as all calls are (semantically) blocking by default. We should need extra effort to execute things asynchronously.
One issue with “function coloring” is that when a function changes its color, all downstream consumers have to change color too. This is actually useful when you're tracking side effects, but rather a burden when you're just tracking non-blocking code. To make matters worse, for side-effectful (void) functions, the compiler won't even warn you that the calls are now “fire and forget” instead of blocking, so refactorings are error-prone.
In other words, .NET does function coloring for the wrong reasons and the `await` syntax is bad.
Furthermore, .NET doesn't have a usable interruption model. Java's interruption model is error-prone, but it's more usable than that. This means that the “structured concurrency” paradigm can be implemented in Java, much like how it was implemented in Kotlin (currently in preview).
PS: the .NET devs actually did an experiment with virtual threads. Here are their conclusions (TLDR virtual threads are nice, but they won't add virtual threads due to async/await being too established):
Yes, but this is supposedly transparent. At least until you interface directly with native libraries, or with leaky abstractions that don’t account for that.
And it’s LTS! When is the book and the certification exam coming out, does anyone know?
Funny I think async in Python is a lot of fun for side projects but my experience is that if I hand my Python systems off to other people they usually have trouble deploying them and invariably can’t maintain them.
Whereas my Java projects live on long after I am gone from the project.
Personally I love aiohttp web servers, particularly when using web sockets and brokering events from message queues and stuff like that. Not to mention doing cool stuff with coroutines and hacking the event queue (like what do you do if your GUI framework also has an event queue?) If YOShInOn (my smart RSS reader + intelligent agent) were going to become open source though I might just need to switch to Flask which would be less fun.
Async in Python, for a long time, has been a horrible hack relying on monkey patching the socket module. The newer asyncio stuff is quite nice by comparison, but the problem is that Python, due to its popularity, has libraries that haven't been upgraded.
Python always had deployment issues, IMO. In Java, 99% of all library dependencies are pure JARs, and you rarely need to depend on native libraries. You can also assemble an executable fat JAR which will work everywhere, and the fact that the build tools are better (e.g., Maven, Gradle) helps.
Compared with Python, for which even accessing a RDBMS was an exercise in frustration, requiring installing the right blobs and library headers via the OS's package manager, with Postgres being particularly painful. NOTE: I haven't deployed anything serious built with Python in a while, maybe things are better now, but it couldn't get much better, IMO.
Can you explain why you're excited about with virtual threads? I get that they improve throughput in extremely high pressure apps, but the JVM's current threading isn't exactly a slouch. Java's used in HFT shops, and more generally in fintech where performance matters.
I can’t speak for the OP, but this makes it much easier to write code that uses threads that wait on IO, and just let the underlying system (VM + JDBC connectors, for example) handle the dirty work.
A few years ago, I wrote a load generation application using Kotlin’s coroutines - in this case, each coroutine would be a “device”. And I could add interesting modeling on each device; I easily ran 250k simulated devices within a single process, and it took me a couple of days. But coroutines are not totally simple; any method that might call IO needs to be made “coroutine aware”. So the abstraction kinda leaks all over the place.
Now, you can do the same thing in Java. Just simply model each device as its own Runnable and poof, you can spin up a million of them. And there isn’t much existing code that has to be rewritten. Pretty slick.
So this isn’t really a “high performance computing” feature, but a “blue collar coder” thing.
The main problem is that it's not a matter of "speed" but just of congestion.
If you write a program using blocking IO and Platform (OS) threads, you're essentially limited to a couple hundred concurrent tasks, or however many threads your particular linux kernel + hardware setup can context switch between before latency starts suffering. So it's slow not because Java is slow, but because kernel threads are heavyweight and you can't just make a trillion of them just for them to be blocking waiting on IO.
If you use async approaches, your programming model suffers, but now you're multiplexing millions of tasks over a small number of platform threads of execution still without even straining the kernel's switching. You've essentially moved from kernel scheduling to user-mode scheduling by writing async code.
Virtual threads is a response to this, saying "what if you can eat your cake and have it, too?" by "simply" providing a user-mode-scheduled thread implementation. They took the general strategy that async programming was employing and "hoisted" it up a couple levels of abstraction, to be "behind" the threading model. Now you have all the benefits of just blocking the thread without all the problems that come from trying to have a ton of platform threads that will choke the linux kernel out.
Pre-v21, Java's threads were 1:1 based on OS threads. HFT apps were normally single-threaded with exotic work-dispatching frameworks, native-memory buffers &| built-in network stacks so, while being undoubtedly fast, were not particularly representative.
V21 virtual threads are more like Go's goroutines. They map 1:m with OS threads, and the JVM is responsible for scheduling them, making them much less of a burden on the underlying OS, with fewer context switches, etc. And the best thing is, there has been minimal change in the Java standard library API, making them very accessible to existing devs and their codebases.
Once upon a time, Java had "green threads". They were kind of like virtual threads, but the way I understood it was that they all mapped to one OS thread. While these new virtual threads map m:n to OS threads.
I remember they were talking about Green Threads 20 years ago. Is it something new?
Yes, while green and red threads were an implementation detail not exposed to Java programmers, now both thread models are exposed (Threads and Virtual Threads).
Additionally since virtual threads are exposed across the whole runtime and standard library, not only they are built on top of native threads (red), the developers have control over their scheduling.
Yes - green threads at the time were basically a solution to a hardware limitation.
Virtual threads make blocking IO calls automagically non-blocking, allowing for better utilization of the CPU.
Fully agree on Helidon Nima and blocking IO. Zero hope that Spring framework crapola will not smother already massively simplified thing with convenient abstractions on top it.
Related:
Java 21 makes me like Java again - https://news.ycombinator.com/item?id=37538333 - Sept 2023 (728 comments)
Java 21: First Release Candidate - https://news.ycombinator.com/item?id=37126530 - Aug 2023 (107 comments)
Java 21: What’s New? - https://news.ycombinator.com/item?id=37067491 - Aug 2023 (256 comments)
Java 21: No more public static void main - https://news.ycombinator.com/item?id=36189923 - June 2023 (77 comments)
JEP 430: String Templates (Preview) Proposed to Target Java 21 - https://news.ycombinator.com/item?id=35012862 - March 2023 (237 comments)
surprised to see all the comments about virtual threads. the real best feature of this release is record pattern matching in switch. this is a huge syntax improvement