Hacker News new | past | comments | ask | show | jobs | submit login
JDK 21 Release Notes (java.net)
246 points by mfiguiere on Sept 19, 2023 | hide | past | favorite | 327 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.


Rust's tokio has a project called loom as well which can do permutation testing of async code: https://github.com/tokio-rs/loom


Isn't that a kind of half baked, guaranteed-incomplete kind of model checking?


Sure. That is not the sort of thing that is guaranteed to catch all bugs. Model checking approaches are better at providing proofs of non-trivial properties.

The problem is that model checking checks a model which often fails to properly match the actual implementation. Plenty of people have used model proven algorithms but had implementation bugs that mean that the highly desired properties proven for the model are not upheld.

Being able to automatically check the implementation against specific race conditions (etc.) helps to provide confidence in the implementation working correctly, which means your model proven results are more likely correct for the actual implementation.


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!).


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.


I worked at a place where we had machine learning systems with a big pile of dependencies that pip could not consistently resolve, I figured out what most of the technical problems where but I was still struggling with wetware problems and they eventually put me on a Scala/Typescript project instead.

One big problem is that pip just starts downloading and installing things optimistically, it does not get a global view of the dependencies and if it finds a conflict it can't reliably back out from where it is and find a good configuration. The answer is to do what maven does or what conda does and download the dependency graph of all the matching versions and get a solve before before you start downloading. Towards the end of my time on that project I had built something that assembled a "wheelhouse" of wheels necessary to run my system and would install them directly.

What I figured out was that you could download just the dependencies from a wheel with 2 or 3 range requests because a wheel is just a ZIP file and you can download the header and the directory from the end of the file and then know where the metadata is and download just that. Recently pypi got some sense and now they let you download just the metadata.

And that's the story of Python packaging. Things are really going in the right direction but progress has been slow because the community has mistaken "98% correct" (e.g. wrong) with "has 98% of the features somebody might want" It might have been a lot better if somebody with some vision and no tolerance for ambiguity had gotten in charge a long time ago.


A new dependency resolver was introduced [0] with pip 20.3 (in 2020) which sounds like it's meant to address the problem you're describing. Were you using an earlier version of pip or is this still a problem?

[0] https://pip.pypa.io/en/latest/user_guide/#changes-to-the-pip...


> In Java, 99% of all library dependencies are pure JARs

Yes, this is the difference...Python community has in practice chosen more native dependencies, Java has not. But Java JNI code (if you ever do have it) is just as painful.

> with Postgres being particularly painful

You want to a pure Python package, and those have gotten much better.

asyncpg is really, really good if you want async.

Otherwise, pg8000.


It got better, with containers.


Where I worked containers just gave the data scientists superpowers at finding corrupted Python runtimes. I don't know where they got one that had a Hungarian default charset, but they did.


And it’s LTS! When is the book and the certification exam coming out, does anyone know?


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.


Debugging issues with Netty from someone not intimately familiar with it's internals was an exercise in pain.


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.


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.


> couple hundred concurrent tasks, however many threads your particular linux kernel + hardware setup can context switch between before latency starts suffering.

and there any benchmarks saying it is couple hundred threads, and not 100k threads?..

couple hundred is about thread per core on modern CPUs..

Also, my belief is that JVM itself adds lots of overhead.


>Also, my belief is that JVM itself adds lots of overhead.

The JVM has ~nothing to do with the scheduling of platform threads

>and there any benchmarks saying it is couple hundred threads, and not 100k threads?..

It depends greatly on your hardware

>couple hundred is about thread per core on modern CPUs..

It depends on the hardware, of course

Overall, yes, you could probably run a lot of concurrent processes on a c7i.48xlarge in 2023. But why would you want to do that when, if you used virtual threads (or async), you could do the same work on an c7i.large? That's the whole point of this. There's no reason to waste CPU and memory on kernel context switching overhead.


> The JVM has ~nothing to do with the scheduling of platform threads

I didn't say it schedules platform threads, but JVM has many other performance issues with its GC/object identity/lock monitoring model, which makes it harder choice for ultra-high performance apps, so virtual thread scheduling may improve your app perf by 5%, where other 95% stuck in other bottlenecks

> But why would you want to do that when, if you used virtual threads

because virtual threads are not supported in ecosystem well, and it will take another 10 years for it to catch up, before that most API/libs will use old concepts and you take large risks of fragmentation by introducing virtual threads to your app.

Also, you can avoid all switching with 20 years old ExecutorService, where you have exactly the same m:n mapping between tasks and machine threads.


>I didn't say it schedules platform threads, but JVM has many other performance issues with its GC/object identity/lock monitoring model, which makes it harder choice for ultra-high performance apps, so virtual thread scheduling may improve your app perf by 5%, where other 95% stuck in other bottlenecks

Switching from async to virtual threads is typically not an improvement to performance in well-factored async code. The primary benefit is a clearer programming model that's significantly easier to get right and less code to implement while still the same performance.

>Also, you can avoid all switching with 20 years old ExecutorService, where you have exactly the same m:n mapping between tasks and machine threads.

You misunderstand the point of virtual threads. There is no need to pool them, as (most) executor service implementations do. And the whole point is that you no longer need to avoid blocking them, which pretty much every currently-existing solution will be doing. There's 0 benefit to switching without also updating your code to take advantage of the new paradigm.

>because virtual threads are not supported in ecosystem well, and it will take another 10 years for it to catch up, before that most API/libs will use old concepts and you take large risks of fragmentation by introducing virtual threads to your app.

What risks? The whole point of virtual threads vs async/await is that they don't color functions in the same way that async does.

Also, pretty much every notable framework already has support for virtual threads (Spring, jetty, helidon, and many others). The API has been the same for a couple years at this point.


sorry, I disagree with most of your points but not interested in continuing discussion.


You can just swap a single line in your ExecutorService then for all the benefits.


ExecutorService can be configured in many different ways, for example I often create one with boubned queue and CallerRunsPolicy, I somehow couldn't find equivalent virtual executor.


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.


It's worth mentioning that there are some aspects of the virtual thread implementation that is important to take into consideration before switching out the os-thread-per-task executor for the virtual-thread-per-task executor:

1. Use of synchronized keyword can pin the virtual thread to the carrier thread, resulting in the carrier thread being blocked and unable to drive any other virtual threads. Recommendation is to refactor to use ReentrantLock. This may be solved in the future though.

2. Use of ThreadLocals should be reduced/avoided since they're local to the virtual thread and not the carrier threads, which could result in balooning memory usage with extensive usage from many virtual threads.


Regarding the second point: there are now alternatives to ThreadLocals available that are intended to be used by virtual threads: Scoped Values. Unfortunately, they are preview-only in JDK 21.


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.


An interesting part of HFT is you normally do everything on a single thread. Your unit of parallelism would be a different JVM and you want to avoid context switching at all costs, going as far as to pin specific OS threads, making sure your thread for execution is never used by GC.


To understand the benefit of virtual threads, I think it's helpful to think of it as a "best of both worlds" situation between blocking IO and async IO. In summary, virtual threads give you the scalability (not simply "performance") benefits of async IO code while keeping the simplified developer experience of normal threads and blocking IO.

First, it's best to understand the benefit of virtual threads from a webserver. Usually, a webserver maps 1 request to 1 thread. However, most of the time the webserver actually doesn't run much code itself: it calls out to make DB requests, pulls files from disk, makes remote API requests, etc. With blocking IO, when a thread makes one of these remote calls, it just sits there and waits for the remote call to return. In the meantime, it holds on to a bunch of resources (e.g. memory) while it's sitting doing nothing. For something like HFT, that's normally not much of a problem because the goal isn't to server tons of independent incoming requests (sometimes, obviously the usage pattern can differ), but for a webserver, it can have a huge limiting effect on the number of concurrent requests that can be processed, hurting scalability.

Compare that to how NodeJS processes incoming web requests. With Node (and JS in general), there is just a single thread that processes incoming requests. However, with async IO in Node (which is really just syntactic sugar around promises and generators), when a request calls out to something like a DB, it doesn't block. Instead, the thread is then free to handle another incoming web request. When the original DB request returns, the underlying engine in Node essentially starts up that request from where it left off (if you want more info just search for "Node event loop"). Folks found that in real world scenarios that Node can actually scale extremely well to the number of incoming request, because lots of webserver code is essentially waiting around for remote IO requests to complete.

However, there are a couple of downsides to the async IO approach:

1. In Node, the main event loop is single threaded. So if you want to do some work that is heavily CPU intensive, until you make an IO call, the Node server isn't free to handle another incoming request. You can test this out with a busy wait loop in a Node request handler. If you have that loop run for, say, 10 seconds, then no other incoming requests can be dispatched for 10 seconds. In other words, Node doesn't allow for preemptive interruption.

2. While I generally like the async IO style of programming and I find it easy to reason about, some folks don't like it. In particular, it creates a "function coloring" problem: https://journal.stuffwithstuff.com/2015/02/01/what-color-is-... . Async functions can basically only be called from other async functions if you want to do something with the return value.

Virtual threads then basically can provide the best features from both of these approaches:

1. From a programming perspective, it "feels" pretty much like you're just writing normal, blocking IO code. However, under the covers, when you make a remote call, the Java schedule will reuse that thread to do other useful work while the remote call is executing. Thus, you get greatly increased scalability for this type of code.

2. You don't have to worry about the function coloring problem. A "synchronous" function can call out to a remote function, and it doesn't need to change anything about its own function signature.

3. Virtual threads can be preemptively interrupted by the underlying scheduler, preventing a misbehaving piece of code from starving resources (I'm actually less sure of the details on this piece for Java).

Hope that helps!


1. In Node, the main event loop is single threaded. So if you want to do some work that is heavily CPU intensive, until you make an IO call, the Node server isn't free to handle another incoming request. You can test this out with a busy wait loop in a Node request handler. If you have that loop run for, say, 10 seconds, then no other incoming requests can be dispatched for 10 seconds. In other words, Node doesn't allow for preemptive interruption.

Nice. I will add that JS runtimes now have worker threads, while they are still terribly inefficient even when compared to OS threads they can alleviate this problem if you don't need to spawn more than number of cores worker threads. If you are using nodejs and that is not enough, welcome to the microservices world.


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.


Dotnet has had Tasks for years, seems like the same thing.


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):

https://github.com/dotnet/runtimelab/issues/2398


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.


This is not necessarily correct. Tasks can be run in a "fire-and-forget" way. Also, only synchronous prelude of the task is executed inline in .NET.

The continuation will then be ran on threadpool worker thread (unless you override task scheduler and continuation context).

Also, you can create multiple tasks in a method and then await their results later down the execution path when you actually need it, easily achieving concurrency.

Green threads is a more limited solution focused first and foremost on solving blocking.


But there is no point to it in this comparison if it can block a whole thread to itself - that can be done with java as well since forever.


Blocking the thread with unawaited task.Result is an explicit choice which will be flagged by a warning by an IDE and when building, that this may not be what you intended.


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.


Nah, cooperative vs pre-emptive models.


Some options for those who prefer to avoid the Oracle minefield:

https://adoptium.net

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.


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. The only ones suggesting getting it from Oracle directly are also Oracle employees.


Oracle offers the JDK under two licenses, one of them is the same license under which all other vendors offer our software: https://jdk.java.net

The fact is that if you're using Java these days (or for the past 13 years), you're using Oracle software under a license issued to you by Oracle, regardless of which website you download it from. If you want the support or advice of the people who, you know, actually develop the software then you'll need to talk to Oracle people, and those who want their software to be supported by the people who write it are those who fund the development of OpenJDK. You're welcome, by the way.


Oracle offers two licenses: A "foot gun" license that would cost you tens of thousands of dollars, and a clean license.

> The fact is that if you're using Java these days (or for the past 13 years), you're using Oracle software under a license issued to you by Oracle, regardless of which website you download it from.

That is inaccurate. If third parties build the JDK themselves, then you're only subject to the JDK's license. If you get the binaries from Oracle, you're subject to the JDK license and binary distribution license.

Plus if Oracle's distributions and third parties are the same, but the risks are much lower with third party, then why NOT get it from a third party? You haven't named a downside, and I've explained the upsides.

You cannot, in good faith, argue that downloading from Oracle "supports" Java development, while also arguing that downloading it from Oracle is always free. The only reason it supports Java development is when people screw up and get billed.


> If you get the binaries from Oracle, you're subject to the JDK license and binary distribution license.

No, if you download binaries from Oracle you're subject to the one license that accompanies your chosen binary. The main terms of the non-open-source licensed distribution are the very first thing you see on the download page of that distribution:

JDK 21 binaries are free to use in production and free to redistribute, at no cost, under the Oracle No-Fee Terms and Conditions (NFTC). JDK 21 will receive updates under the NFTC, until September 2026, a year after the release of the next LTS. Subsequent JDK 21 updates will be licensed under the Java SE OTN License (OTN) and production use beyond the limited free grants of the OTN license will require a fee.

There is nothing underhanded here. You can use the open-source license, or you can use this one, but updates for this distribution will be offered to paid support subscribers only after September 2026.

But in any event, if it makes you feel better to download our software from another website, by all means do that!

> You cannot, in good faith, argue that downloading from Oracle "supports" Java development, while also arguing that downloading it from Oracle is always free. The only reason it supports Java development is when people screw up and get billed.

I didn't say that downloading from Oracle supports anything (it doesn't because it's free under both licenses we offer). You pay Oracle for a support subscription and that's how OpenJDK (and the Java SE specification) is funded. Luckily, many of those who choose to buy support prefer buying it from the developers of the software, and that's how we've been able to increase the investment in OpenJDK over the past few years.


I feel like the attitude in your post was completely unnecessary. You’re an ambassador of OpenJDK.


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.


There is cognitive overhead in even having to consider how Oracle could be licensing anything you download from them. Their licensing has a long history of being complex, expensive, and enforced. Choosing to get software from a less complicated (licensing wise) provider is a valid choice.

The licensing risk of using open source software, that is mostly being written by Oracle, from Amazon is less.

Amazon's open source reputation is that they'll screw over maintainers with their rebranded forks, but not go after users of open source.


Oracle's OpenJDK builds are only updated for six months after each release, while Adoptium receives long-term support (like the commercial Oracle JDK).


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.


Can't upvote this response enough! Thank you Ron. You corrected my understanding on this a few years ago in a Reddit thread on /r/java. I've tried to convey this response ever since. You are NOT getting an LTS release from anyone outside of Oracle. Others will provide backports and that might be "good enough". If you're not paying for an LTS, you may as well update to each new release (as long as nothing else breaks).


To be fair, a few other companies also have excellent OpenJDK developers (although not as many as Oracle) and they also offer LTS services that cover the entire JDK, but no one offers one for free.


Yes! the "free" thing being the main difference. Thanks!


My understanding is:

Oracle publishes updates for latest release until the next release.

Then they drop maintenance of that branch, and RedHat picks it up (at least they did for 8, 11 and 17) and keep supporting/backporting fixes, with the help of other vendors and the community.

So when you are using 8/11/17 from RedHat/Adoptium/Corretto/you name it, you are actually using what RedHat provides to its customers as LTS, unless they have some private build, which I seriously doubt.

I wasn't able to find out what exactly Oracle does for their private LTS releases, do they use the public maintenance branch as a base of their private branch? Or do they continue their own private branch independently from what the community maintains? That would mean that the older the branch is (say jdk11u) the more differences there are between Oracle JDK and what you get from RedHat/Adoptium/...

On top of that Amazon provides support for Corretto on AWS, if you are using e.g. EC2 you get it as part of the fees, not exactly free, but cheap, considering you already pay for the VMs.

I appreciate any corrections/clarifications.


> Oracle publishes updates for latest release until the next release.

This is changing every now and again. For example, the Oracle JDK builds for JDK 21 will be offering free updates for three years (https://www.oracle.com/java/technologies/downloads/)

> and keep supporting/backporting fixes, with the help of other vendors and the community. So when you are using 8/11/17 from RedHat/Adoptium/Corretto/you name it, you are actually using what RedHat provides to its customers as LTS, unless they have some private build, which I seriously doubt.

This is where things get complicated, and given that there are all done by companies that fund their Java operations by selling support -- just as Oracle does -- clearly they don't offer their paid service for free. What you get for free is some backports from the mainline, i.e. only the intersection of the old and current JDK is maintained. Now, I believe that Red Hat specifically offers the same builds as they do to customers, that is not what you want if you really care about a fully maintained JDK. And that is for the following reason: If you're stuck on an old version, that probably means you're using some aspect of the JDK that's been removed or else you'd be using a new version. But the only things we remove are things that aren't widely used. This means that the fix you need is probably not the same as one some other customer who is stuck on the old version needs. If you're not a customer, your issue will not be fixed because no one offers original work on old releases for free.

Of course, if your software is heavily maintained, the safest, most secure, and cheapest option is to always use the current version rather than an old one. Once you get used to doing that, it's also the easiest.


Due to the GPL, eventually all commits will find their way into the OpenJDK repos, it is sort of similar to how vulnerabilities are handled — with paid support, you will be the first to get updates. Also, since Oracle does most of the development on the main branch as is, they have deep understanding of those patches - while during backporting it may not be as expertly implemented.

But Oracle also backports commits to older JDKs, e.g. Java 8 is supported for 2030, though that is paid.

The main takeaway is that it is still a singular project where bugfixes definitely, but even most custom features find their way eventually back to mainline (e.g. it was the case that Microsoft made some specific feature for their usecase, that was later merged).


Even the commercial OracleJDK is free to use, as long as you stay on the latest LTS.


According to

https://inside.java/2023/09/19/the-arrival-of-java-21/

Oracle provides roughly 3/4 of the commits and fixed issues. It's a majority, but there are other significant players (always surprised to see SAP higher then Google and Amazon).


I get what you mean, but given answers here (and other places in this thread) it seems like the licensing is not clear at all.

At least it's not clear to me, I'm still on "Oracle is evil, go as far as you possibly can from them". I would genuinely be happy to be able to confidently say that "this particular project of Oracle is safe for me to use", though.


It’s not clear for people that put their hands over their ears and go LA-LA-LA-LA. Hell, in many places bullshit like that is not even enforceable.

There was a good comment on one similar threads: oracle is thought as this litigious monster because they provide DRM-free, no phoning home solutions to companies. This is very important for these customers, as you just simply can’t shut down a country’s hospital system because Google or whatever managed to falsely flag it as some malware and now you try to grab onto any form of human contact from their support, without success.

But every sufficiently big company is only after money, so to make some form of profit they do have to actually enforce their contracts somehow, here comes the inspection - which sure suck, but there is no other way around it given the previous limitation. And sure, some higher management can easily have fked up and used some feature that was not part of their plan, and they desperately want to cover up their mishandling, and Oracle “kindly” offers them the option to forget about the fee for that plus option, if you subscribe to this other service of ours for X years, we are good; and higher management couldn’t be happier.


That kind of clarification is very useful (at least to me). I honestly don't really know why Oracle "are the bad guys". I just heard it so much that I accepted it.


Corretto seems to have 21 as of just now.


Yeah the lack of anyone packaging this up was kinda weird to me. I understand these are done on someone else's dime, and no one owes me a free lunch though.

I don't really pay a lot of attention to Java releases but I was actually excited about this one. No one ran a build process last night in anticipation?


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, and you're welcome.


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.


> On the other hand, while Linux is developed by various for-profit companies, the code is owned by the Linux Foundation.

This is incorrect. Linux does not require copyright assignment.

The documentation [0] says: "Copyright assignments are not required (or requested) for code contributed to the kernel. All code merged into the mainline kernel retains its original ownership; as a result, the kernel now has thousands of owners."

[0] https://www.kernel.org/doc/html/latest/process/1.Intro.html#...


Sorry, I stand corrected on the Linux Foundation.


It is true that https://jdk.java.net/21/ starts with:

> This page provides production-ready open-source builds of the Java Development Kit, version 21, an implementation of the Java SE 21 Platform under the GNU General Public License, version 2, with the Classpath Exception.


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.


Hmm I think you are not right. If you go to https://jdk.java.net/21/, the first sentence says that it is GPLv2 (+ Classpath exception, whatever that is).


> Classpath exception, whatever that is

GPL is a copyleft license, so if you would take linux kernel (same license), added some patches to it and you would distribute the resulting software, you would have to make sure your sources are available.

This would make Java as a runtime a very bad target for any company - they don’t want to share their application code - hence the classpath exception, giving you basically a boundary over which the copyleft doesn’t propagate over.

But the JVM itself still benefits from any updates made by any company.


> hence the classpath exception, giving you basically a boundary over which the copyleft doesn’t propagate over.

Oooh that makes sense, thanks for clarifying this!


Forgot to add: I am no lawyer


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.


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.


It’s not the Caribbean sea with singing sirens.


"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”


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.


The reference is to a previous licensing change, which made it less free, with Oracle shaking down groups using the Oracle JDK. They've changed their terms before and could do so again with future releases.



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.


You would be right, if you would have made this comment a bit more than a decade ago.

The only remaining differences between OracleJDK and OpenJDK are logo, and trivial things - Oracle has opened up everything else.


As far as I'm aware, Oracle JDK gets fixes that OpenJDK doesn't, and is under a commercial license that's not GPL (even if it's free-as-in-beer under certain circumstances)? Is that wrong?


Right. So that’s the minefield, no? If you download from Oracle, it’s like Photoshop from Adobe. If you download from Amazon, it’s like gcc. But it’s the same software: near invisible difference.

So that’s why you shouldn’t download from Oracle.


Are you a huge enterprise system that has to be able to call someone on some national holiday at 2AM? If no, then just do whatever you want - I don’t understand running to Amazon, when you can just do `apt install openjdk`, but whatever. There is way too much fuss around this non-issue. It’s the exact same software plus-minus a few bytes.


Ubuntu LTS does not have modern JDK. Amazon has Coretto distribution. Much better.


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.


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.


Don't use them commercial features if you don't be want to pay for them. And don't download the Enterprise Oracle JRE from the Oracle website if you don't want to use them commercial features. We all should read terms&conditions more carefully.


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)


and, of course, the unholy number of pseudo-dupes: https://hn.algolia.com/?dateRange=last24h&page=0&prefix=true... https://hn.algolia.com/?dateRange=last24h&page=0&prefix=true... as everyone picks their favorite perspective on the launch


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.


And we had electric cars before internal combustion engines.. they are not at all the same, and not even my analogy gives enough credit to loom.


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?


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.


Major nitpick: also virtual threads are cooperatively scheduled on carrier threads (the underlying platform threads). You "cooperate" implicitly by calling APIs that block the thread. There is no preemption if you hog the carrier thread with, say, number crunching or array sorting.

Straight from JEP 444:

> The scheduler does not currently implement time sharing for virtual threads. Time sharing is the forceful preemption of a thread that has consumed an allotted quantity of CPU time. While time sharing can be effective at reducing the latency of some tasks when there are a relatively small number of platform threads and CPU utilization is at 100%, it is not clear that time sharing would be as effective with a million virtual threads.


I’m not really sure that it is all that subjective — plain old serial, blocking code is objectively easier to reason about than “jumping around” code.

It’s a different question that async-await may have a few very specialist use-cases, that is not readily available in the preemptive model.


> blocking code is objectively easier to reason about than “jumping around” code.

This is precisely why await was introduced in the first place. Promise and callback code was impossible to reason about.


A Promise is not ordinary blocking code.

String res = someRestCall();

is.


I was agreeing with you. They introduced await because they know that

    var s = await someCall();
is way easier to reason about than:

    someCall().then(…)


Oh, I see! Sorry for the misunderstanding!


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?


Very simple - in Java, almost any IO operation in blocking. Exceptions are the stuff in the NIO package, thread interruption and signalling, and maybe process termination.


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.


Kotlin has multithreading, and it also puts `suspend` in the type system.


Some more examples: Rust, Python (with most/all? type checkers), C# all bake this into their type systems.


This is more due to the fact that Kotlin doesn’t control its backend and is designed to work with a language that already includes this model, js.


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?


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.)


I went looking on their because for some reason I thought that an `@Async` annotation would already exist. But no such luck.

If you want to use something like this today, then you could take a look at Spring 's Async support. It also lets you supply the Executor.


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.


> So if you start from scratch, the preemptive style is preferable

I don't believe that you proved that in the preceding paragraph. All you said is that cooperative scheduling requires making the scheduling points explicit while preemptive doesn't, but "explicit is better than implicit" is a principle that a substantial portion of the programming community believes, and OP specifically expressed a preference for explicit scheduling points.

For myself, I also want to see scheduling points propagate up the call tree the same way that I want checked exceptions. The lack of composability is a feature, not a bug. I prefer to know which parts of my code are most likely to be problematic and then keep those parts isolated from the rest.

I recognize that this is a subjective opinion and not everyone believes what I do, and for some use cases preemptive is better, but I don't see why you need to pretend that you made the most optimal decision instead of just acknowledging that it's complicated and you made the one that you feel is best for Java.


> All you said is that cooperative scheduling requires making the scheduling points explicit while preemptive doesn't, but "explicit is better than implicit" is a principle that a substantial portion of the programming community believes, and OP specifically expressed a preference for explicit scheduling points.

The problem is that the cooperative style makes mutual exclusion implicit and that's the important correctness assumption that is explicit in the preemptive style. The possible presence of scheduling point is the lack of an assumption, and therefore it shouldn't be explicit.

You're right that the issue is subjective, but it's subjectively determined in the overall design of the language. In Haskell, for example, there is an ever-present default assumption of lack of side effects and that assumption is at the core of the language, but if your language doesn't do that -- and Java doesn't and neither do C# or Python -- then the choice of cooperative scheduling isn't a good fit for the rest of the language. So perhaps I should have said that unless you design your language like Haskell (and unless you don't have the constraints the JS does), preemptive is the better fit for the rest of the language.


There's nothing to stop you from setting up synchronization constructs like mutexes in a cooperatively-scheduled application. Kotlin bundles them with kotlinx.coroutines [0]. If anything, having possibly-concurrent methods explicitly marked makes mutual exclusion easier to reason about, because you know exactly which functions may have concurrency problems.

Are you saying that there's a mechanism in Loom that somehow forces you to mark mutual exclusion which is absent from cooperatively-scheduled languages?

[0] https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-corout...


> There's nothing to stop you from setting up synchronization constructs like mutexes in a cooperatively-scheduled application.

Right, but assumptions of mutual exclusion are still implicit, and so you must carefully analyse them any time you add a scheduling point. I.e. code may implicitly rely on mutual exclusion in cooperative scheduling but not in preemptive scheduling.

> Are you saying that there's a mechanism in Loom that somehow forces you to mark mutual exclusion which is absent from cooperatively-scheduled languages?

It's not a mechanism in virtual threads but in Java threads in general. Yes, to mark mutual exclusion you must employ some synchronisation construct explicitly, and that is not the case for cooperative scheduling where mutual exclusion is implicit, and that's what makes cooperative scheduling less composable.


I think we're talking past each other.

Can we make this concrete? In what way is mutual exclusion implicit in Kotlin coroutines that is not also true in Java Loom?

Here's Kotlin's explanation of how to do mutual exclusion [0], and here's a blog post explaining how mutual exclusion works in Java [1]. The main difference I see is that Java's `synchronized` is a full syntax construct while Kotlin uses its trailing-lambda syntax, but that's not semantically significant.

What do you see as more explicit in the Java version?

[0] https://kotlinlang.org/docs/shared-mutable-state-and-concurr...

[1] https://www.baeldung.com/java-mutex


Kotlin is not really a good example because it offers the worst of both worlds in this area: You must account for both preemptive and cooperative scheduling because both occur at the same time (they had no choice because they don't control the platform, only the frontend compiler). But let's look at a language like JS, that offers "clean" cooperative scheduling, and compare that to Java.

An async JS function can contain code like this:

    x = 3;
    foo();
    print(x);
Now suppose that you want to perform some I/O operation in foo, which would require changing the function to:

    x = 3;
    await foo();
    print(x);
But is that allowed or does our function depend on x (which is shared among tasks in some way) not being changed between the first and third lines? Does the original function rely on the implicit mutual exclusion for its correct operation or not?

In Java, on the other hand, if x is shared among some tasks then, regardless of what foo does, if the method depends on x not changing then the code must be written as:

    try (xGuard.lock()) {
        x = 3;
        foo();
        print(x);
    } finally {
        xGuard.unlock();
    }
So adding a scheduling point to foo doesn't matter because if our method has any mutual exclusion assumptions it must express them explicitly (in practice, if x is guarded, then the guarding will often be encapsulated somehow, but the point remains). In JS that's not the case, so the addition of a scheduling point requires a careful analysis of all callers.


Does Java actually prevent this code from compiling if `x` is shared?

    x = 3;
    foo();
    print(x);
If not, then I fail to see what the difference is with your JS example. It's still on the developer to keep track of whether x may change (and therefore whether a mutex is required), with the added disadvantage that there's no `async` keyword to clue you in that there might be a problem.

You seem to be describing a style guide rule that Java projects should adopt, not a language feature, and there's no particular reason the same style guide couldn't be adopted another language if required.


> Does Java actually prevent this code from compiling if `x` is shared?

Why would it prevent it from compiling? Whether it's right or wrong depends on what the programmer has in mind. If they rely on a mutual exclusion constraint, they must specify it.

> It's still on the developer to keep track of whether x may change (and therefore whether a mutex is required), with the added disadvantage that there's no `async` keyword to clue you in that there might be a problem.

No, because in Java, the author of this method has to decide what they want. In JS, when the author of foo decides to change it they need to analyse what the authors of the callers had in mind.

The point is that the presence of a scheduling point or not is not just informative but has an impact on the correctness of the code.

> You seem to be describing a style guide rule that Java projects should adopt, not a language feature

No, it's a language feature. In Java the default is "scheduling point allowed." If you want to exclude it, you have to be explicit. This approach makes composing code easier than the opposite default unless your entire language is built around an even stronger constraint (like Haskell).


> In JS, when the author of foo decides to change it they need to analyse what the authors of the callers had in mind.

No, because they already committed to not having any concurrent code in that method as part of the function contract. Making a function async is a breaking change (the return type changes to Promise<T>). If the author can't avoid the breaking change, then it would indeed force the caller to update their call site, cluing them in that the code may now run in parallel and mutual exclusion should be considered. There's no guesswork at what the callers are doing because you had an explicit contract with them that there would be no concurrency.

If a Java method is passed an object by reference and subsequently decides to modify it in a thread instead of inline, that information is not propagated up to the callers, so they would have either had to guess that the library might change to a concurrent model and make the object thread-safe ahead of time, or they would have to read the release notes and then go find the call sites that need fixing.

EDIT: And again, I'm not critiquing your decision to use this model for Java. On the balance I believe it was the right move, because as you note it's the model that was already extensively used in Java. I'm just arguing that preemptive is not objectively better, it depends on what existing code is already doing and on what you're trying to accomplish with the feature.


> Making a function async is a breaking change (the return type changes to Promise<T>).

Sure, that's another way of showing the same thing, and that is the essence of non-composability or poor abstraction. It's what you want in Haskell because it's designed precisely for that, but very much not what you want in an imperative language. And the reason it's easy to see it's not what you want is that those languages, even JS, don't generally colour blocking functions. In imperative languages, blocking is an implementation detail. Deviating from that principle goes against the grain of those languages (though not of Haskell's).

> If a Java method is passed an object by reference and subsequently decides to modify it in a thread instead of inline, that information is not propagated up to the callers, so they would have either had to guess that the library might change to a concurrent model and make the object thread-safe ahead of time, or they would have to read the release notes and then go find the call sites that need fixing.

In Java (and in any imperative language that offers threading, including Rust and Kotlin and C#) any interaction with data that may be shared across threads absolutely requires explicit attention to memory visibility. In particular, in your example you don't need to communicate anything to the caller, just ensure that the method that passes the object to be processed by a thread ensures visibility. This is what the Java Memory Model is about, and thread pools, futures etc. do it automatically.

It's true that JS doesn't require attention to memory visibility, but that has absolutely nothing with cooperative or preemptive scheduling. Rather it has to do with multiprocessing. A language that offers preemptive threads over a single processor will not need any more attention to memory visibility than JS. Conversely, cooperative scheduling in Kotlin, whose scheduler supports multi-processing also requires the same kind of attention.

> I'm just arguing that preemptive is not objectively better, it depends on what existing code is already doing and on what you're trying to accomplish with the feature.

Well, I'm arguing that preemptive is objectively better in imperative languages unless there are external concerns, and that's why Erlang and Go have gone down that route. I know of one exception to that, although the paradigm isn't quite classical imperative, and that is synchronous languages like Esterel [1], but note that the model there isn't quite like async/await and isn't quite cooperative, either. I am not aware of (ordinary) imperative languages that chose cooperative scheduling primarily because they truly thought the programming model is better; it's lack of composability with imperative paradigms is fairly obvious (and I know for a fact that C#, Kotlin, C++, Rust, and JS didn't choose that style because of that, but rather because of other constraints).

[1]: https://en.wikipedia.org/wiki/Esterel


> In the cooperative style, the correctness of any subroutine is implicitly dependent on there not being scheduling points where they're not explicit

Removing double negations, you're saying "cooperative style is bad because the runtime can't interrupt user code implicitly"? You are looking at this from the perspective of the runtime implementer (meaning yourself, thank you by the way), not the programmer. You absolutely should look at this from the perspective of the programmer too.

Preemptive multitasking gives the runtime more capabilities because you can just assume the user has to deal with anything that your implicit rescheduling causes. But users don't actually deal with that, even if they should! The havoc that concurrency bugs are causing is evidence for that!

JavaScript's concurrency model is so nice because despite lacking real parallelism, you only have to think about concurrency (race conditions, data races, deadlocks, ...) at scheduling points. Rust has an interesting model with both parallelism and cooperative async-await. Its type system requires strong guarantees to move data between (preemptively scheduled) threads, but much weaker guarantees to use it in a cooperatively scheduled async function. Of course, it comes at a cost, namely function coloring.

After decades of legacy, none of that is feasible in Java of course, but let's not pretend that preemptive scheduling is objectively "preferable".


> That's a weird way to phrase "in the cooperative style, scheduling points are explicit, and in the preemptive style, the burden is on the developer to get it right".

Because it's the opposite. In the preemptive style, the mutual exclusion assumption -- i.e. the relevant correctness assumption -- is explicit, while in the cooperative style the correctness assumption is implicit and does not compose well. I.e. any addition of a scheduling point requires re-examining the implicit assumptions in all transitive callers.

> Preemptive multitasking gives the runtime more capabilities and makes it easier to implement because you can just assume the user has to deal with anything that your implicit rescheduling causes.

Preemptive multitasking is far, far harder to implement (that's why virtual threads took so long) and it provides superior composability. Cooperative scheduling is very easy to implement (often all it requires is just a change to the frontend compiler, which is why languages that have no support of the backend have to choose it) but it places the burden of examining implicit assumptions on the user.

> After decades of legacy, none of that is feasible in Java of course, but let's not pretend that preemptive scheduling is objectively "preferable".

Java is no different in this case from C# or Rust. They all support threading. But after significant research we've decided to spend years on offering the better approach. It looks like C# has now realised that, while hard, it may be possible for them, too, so they're giving it a go.

If we look at true clean-slate choices, the two languages that are most identified with concurrency as their core -- Erlang and Go -- have also chosen preemptive scheduling. Languages that didn't have either done so due to legacy constraints (JS), lack of control over the backend (Kotlin), complex/expensive allocation in low-level languages (C++ and Rust), or possibly because it's simply much easier (C# I guess).


You don't have to transitively examine callers because you have colored functions. Every awaited call is a scheduling point, and once a function is colored it can't be "doubly colored". You can then still use the mutexes provided by those languages.

From a programmer standpoint, preemptive scheduling is like cooperative scheduling, except that every function and operation is colored as async by default. Sometimes that simplifies things! But sometimes you'd like to have some guarantees that an async function cannot give you.

The "easier to implement" part was a remnant of an older rewording, and I edited it out immediately after posting. You must've seen the comment right after I posted it, sorry about that, that wasn't supposed to be my point.

Either way, we could argue here for days, but the difference in opinions here on this thread AND among programming language maintainers are enough evidence to show that the decision isn't as clear as you make it out to be. It's a little dismissive to pretend that other language designers, eg. those of Python, Kotlin, haven't done their years of research, and made inferior choices due to lack of expertise, when a LOT of developers (eg. myself, or many commenters here on this thread) like those languages particularly because of those decisions.

Opinions differ. And if you do research on Java developers you'll get different results than if you do research on JS developers. For good reasons. I fully trust you that you made the right decision for Java.


> You don't have to transitively examine callers because you have colored functions.

You do, because the problem is not the "await" calls but everything else. The language tells you which are the callers that need to be changed when you a scheduling point, but it doesn't tell you whether that change preserves their correctness or not. You have to analyse every caller individually to know whether there was a reliance on mutual exclusion or not.

> It's a little dismissive to pretend that other language designers, eg. those of Python, Kotlin, haven't done their years of research, and made inferior choices due to lack of expertise,

But I said the opposite: that they chose the best option that was available to them. Kotlin couldn't implement user-mode threads, or at least not efficiently, because they have no control over the platforms they run on. JS also had little choice because adding threads would have broken a lot of existing code. In their place I would have made the same choice. But the point is that in imperative languages the choice of cooperative scheduling is not done because the programming model it offers is attractive but because of other constraints. All else being equal, cooperative scheduling offers worse composition in imperative languages; it's just that in many cases all else is not equal and there are other constraints.

But yes, the important thing is that even though cooperative scheduling would have been so much easier to provide in Java, we decided to take our time and do what offers the best experience to Java developers.


> It's a little dismissive to pretend that other language designers, eg. those of Python, Kotlin, haven't done their years of research

Different features have different levels of depth in languages - as mentioned, where it is only a frontend compiler level change, it definitely didn’t require as much background knowledge as Loom.

Hell, Ron has been working on this exact problem for close to a decade, if I’m not mistaken! Kotlin is barely a decade old!


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.


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.


I think it would require new paradigms, though. The existing popular patterns assume cooperative concurrency. We've had preemptive threading for a long time and UI programming has not made inroads with it. I don't see how using virtual threads makes this an easier lift.

My fear is that we'll end up living with a bolted on async/await style for this stuff but with worse syntax. Still, I'm excited to see how it shakes out.


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.


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.


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.


That’s the idea behind suspend keyword in Kotlin:

https://elizarov.medium.com/how-do-you-color-your-functions-...


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.


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.


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.


So this might actually make checked exceptions popular again eh? ;)


I know they cause their own problems, but I generally prefer checked exceptions over "read the non-existent documentation" exceptions.


> functions that are not async are fast and unlikely to fail, while functions that are async are potentially slow and likely to throw exceptions.

That would seem like using the wrong tool for the job. If you want to colour functions according to reliability and speed you should use a different mechanism to colour them than a mechanism designed to colour functions based on how they behave if IO blocks.


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.


I don't entirely disagree that treating IO as something special at the linguistic level is a good idea, but I do think that async is a wildly indirect and confusing way to achieve that.

Language designers don't add async to make IO look special, they add it as a concurrent programming model.


When IO fails, it doesn't matter a single bit whether the call occured on a virtual or on a platform thread. The likelyhood of failure deserves coloring, but Java already offers a mechanism for that.


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.


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.


Some of the recommendations in the list date back to .NET Framework.

Most of the time, it is not something one needs to worry about because actually problematic cases are highlighted by default Roslyn analyzers and will give you a warning on build as well.

Async/await is a superior model that is more flexible and solves more tasks (heh) at hand than green threads which have more limited scope of application, higher base overhead and cannot reach parity at efficiency due to stack state having to be always preserved.


Stating something doesn’t make it so.


That is a conscious design choice by the language designers. They feel that having to "color" your code as either async or sync is tedious leads to code duplication. After having worked a lot with Kotlin coroutines, I tend to agree.


Virtual threads don't give you async IO, they give you (the illusion of) more threads. If one VT blocks, the underlying OS thread switches to another VT. All IO is blocking.


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.


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


I agree that $ is more common and causes less visual friction, but what I like about \{} is that on my keyboard they are three keys adjacent to each other.


Bikeshedding of the noblest kind. It is all subjective.


I’d say valuing shorter syntax is better which is subjective, but then measuring it is objective. Requiring 3 chars per variable is silly when many languages only require 1


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?


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.


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 if string literals are prepended with `STR.`. Old strings won't be affected


I hadn't caught that this was required, but then it'd lock them out of removing that requirement. Plus it'd complicate the parser, to have there be two different kinds of string literals syntactically.


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


A template processor implementing regexes should be used for that situation.


The backslash is actually perfectly appropriate because they are doing more than interpolating strings (even though that's what `STR` is doing). They are also supposed to safely escape the contents of the values.


It's only a matter of time before IntelliJ (or some plugin) translates it on-the-fly. Reviews on GitHub will be annoying though.


Just use a custom template processor


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


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


An example can summarize hundreds of words with less explanation cruft and wording confusion.

Of all the math classes I've taken, the best teachers are the ones that just walk through examples. Not the ones explaining in abstract how to solve problems generally.


There were no flashy GIFs in the above links.


Maybe MS needs to advertise their new features.

Java is developed in the open, we have had previews of the new features in the previous releases and the Java community has already written gazillion of articles about them. Today we celebrate they are finally production ready.


There is ton of Java related announcements, articles, tutorials and so on at https://dev.java/


Includes JEP 449 (deprecation of Windows 32-Bit builds) https://openjdk.org/jeps/449

I wonder how long those builds will remain available.


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.


Only in the hipster blogosphere. In the real world, Java is quite strong.


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.


Spring and Android are 2 things which keep Java relevant. Sure there are projects using neither, but majority of Java code written nowadays is.


Can you elaborate


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


Oh no. Without record pattern matching you have to have few extra variables declared in scope. Easy with modern IDE. Async code with either a spaghetti of CompletableFutures or reactive style is a nightmare. But I can achieve the same throughput with a “virtual thread per request” style as with reactive, it’s a game changer.



Thanks! It's probably better to merge the comments hither since this URL is more informative.


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.


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.


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.


...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.


> 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.

Bad example. JavaScript literally has that (ever since ES6). [1]

  function sql(strings, ...args) {
    // ...
  }

  sql`SELECT * FROM user WHERE email = $1`
[1] https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...


This is a solved problem in .NET, and C# only has one kind of string templating:

  FormattableString foo = $"select * from {table} where {column} = 42";
https://learn.microsoft.com/en-us/ef/core/querying/sql-queri...


Not sure if C# can do that, but java’s implementation can return an arbitrary object based on the templates/parameters.


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


It makes perfect sense if you consider that String Templates don't just support interpolation, but also /escaping/ of values. Therefore, Java's existing syntax for escaping stuff is the obvious choice for consistency reasons. If interpolation is all you want, use `STR`. In the future, we will see libraries with template processors for regex patterns, SQL, XML, etc.


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.


As a reminder, you still can make it $.”…” in your code, if you wish.


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!)?


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.

[1]: https://openjdk.org/projects/panama/


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.


Cross-compilation is possible. (See javac -target.)

I'm not sure what the current matrix of support for this is.


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.


If you have working code that uses reactive frameworks, think twice about refactoring. Primarily , because it /works/. The reactive calls might also have some non-obvious semantics that could get lost if you don't carefully translate them to virtual threads.


Are there modern learning resources available for Java?


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.


leetcode


Really great release, Virtual Threads have been eagerly anticipated.

Now it's just the waiting game for Valhalla and Panama to land which will address the last of the things that make me sometimes pull my hair out.


Virtual threads are going to make a lot of code much simpler.


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.


I think these days Clojure is a nicer way to easy into the Java ecosystem, because you can explore existing Java libraries interactively.

Here is a nice demonstration of what can you expect, if you want to build a web app:

https://youtu.be/LcpbBth7FaQ?si=PCZijF-JRmp0xxbz

If you add https://htmx.org/ into the mix, then you can build highly interactive applications, with a lot less complexity compared to building a purely data API consumed by some JavaScript frontend framework (React/Svelte/SolidJS and even ClojureScript)

Also, if you learn Clojure, you get a lot more benefits. Shorter programs, which are easier to reason about and hence easier to maintain, etc.


One of the most common web frameworks used is Spring Boot - here is their quickstart: https://spring.io/quickstart

Newer alternatives are: https://micronaut.io/ and https://quarkus.io/

If you want to have something really simple look at Javalin: https://javalin.io/


Can the JVM represent a uint32 yet or a uint64?

If so, I overlooked it.


Java not having unsigned types is a feature IMO.


Why has this been helpful for your uses?

(I ask from someone who has been away from Java since 2001 for the most part, in Swift and Rust and of course C.)


Most uses of unsigned are a code smell. People think unsigned means that something can't go negative. This isn't really true though. It CAN go negative, and then it silently wraps around and you then lose the ability to detect that it went negative.


Long should be able to do the trick for you?

> Long l1 = Long.parseUnsignedLong("17916881237904312345");

Or, just use BigInteger


Can anyone recommend a good book queueing theory math that goes beyond Little’s? Preferably with exercises.


Anyone know when homebrew installs will be released for JDK 21?


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.


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.


I kind of like the inside java website for these kind of things: https://inside.java/2023/09/19/the-arrival-of-java-21/


http://dev.java might be what you're looking for.


Where's Java primarily used these days?


Pretty much everywhere! I bet every single company in the S&P 500 has Java code somewhere.


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'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.


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.


Isn’t apple server on C/C++?

They have their own db in that

https://github.com/apple/foundationdb


I think no FAANG company has a singular stack, but Apple definitely has quite a bit of Java on the server side.


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.


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


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).


It uses their compiler javac. Then the class files get compiled into dex files for androids runtime.


Not yet, I believe they currently support everything (or nearly everything) up to Java 11.


I think the question is more where is not...


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.


Even data-science is limited to just the science part. All the heavy lifting is still Java. Between Hadoop stack/Spark/Beam/Presto/Kafka/friends pretty much all data at any reasonable sized company is getting shifted around with Java.

Even more so if you are using the "hosted"/SaaS stuff as the cloud providers heavily use Java for their data intensive services (though Google has more C++ than the others).


True, I almost said "data science exploration" as a way to avoid this inevitable comment :). Java can't nearly compete with python (right now) for a certain type of data science programming, but you're right that a ton of data science infrastructure is built with Java.


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.


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.



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.


Agree. Will leave the comment up for my eternal shame.


> 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.


Elasticsearch and Minecraft :)


I think most corporations use Java, probably "primarily" Java too.


everything web related?


Java is to Javascript as car is to carpet.


Unless your carpet implements car.


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


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.


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.


Linux support is bad on MS tech. Poor ecosystem and VM isn’t that great. JVM with ZGC is fast on Linux. Biggest lack is Value Types.

And I’m not deploying to Windows. That’s right out.

But lots of people find it useful, so more power to them. For me, it must deploy on Linux. That’s a dealbreaker. It’s not enough if some bare functionality is on Linux. I don’t want to be experiencing the equivalent trouble of trying to have a musl-only build.

JNI story is great. Can bind easily with rust-jni on Linux.

But need value types and some longer primitives (i128 and u128 would be nice).


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.


Okay sure let's just ignore Linux, Apple Silicon, BSD support by .NET and Visual Studio.

It's obviously "MS tech is boring", e.g. TypeScript and VSCode.


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.


At that point, why develop anything? The first Turing-complete processor is enough for everything anyone will ever want.


As long as it has 640KB of memory.


Is this a(n attempted) troll or an honest question? There's a mountain of new libraries and language features since then ...


> What can a server running Java 21 do that wasn't possible in Java 1.4.2?

Virtual threads


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.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: