JDK 21 Release Notes
jdk.java.net
jdk.java.net
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.
Blog article: https://jbaker.io/2022/05/09/project-loom-for-distributed-sy... HN discussion: https://news.ycombinator.com/item?id=31314006
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.
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!).
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.
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.
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.
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.
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.
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.
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.
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.
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!
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.
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.
Virtual threads make blocking IO calls automagically non-blocking, allowing for better utilization of the CPU.
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.
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):
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.
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.
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.
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.
[0] https://pip.pypa.io/en/latest/user_guide/#changes-to-the-pip...
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.
The only remaining differences between OracleJDK and OpenJDK are logo, and trivial things - Oracle has opened up everything else.
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.
So that’s why you shouldn’t download from Oracle.
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. ;)
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.
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.
Oooh that makes sense, thanks for clarifying this!
"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.
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.
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.
> 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.
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#...
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.
One can optionally buy support for it, that’s the whole deal. Not that hard, but apparently this is a minefield for some..
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.
> 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.
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.
> 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.
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.
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?
See my other comment about Adoptium vs. OpenJDK vs. Oracle JDK: https://news.ycombinator.com/item?id=37571143
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.
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.
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).
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.
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.
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.
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 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?
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)
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?
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.
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.
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.
> 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.
> 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.
Language designers don't add async to make IO look special, they add it as a concurrent programming model.
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.
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.
[0] https://journal.stuffwithstuff.com/2015/02/01/what-color-is-...
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.
Also, as mentioned by another commenter below - is a file exists operation async? Sync? Why, why not?
(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.)
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.
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.
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.
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.
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.
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.
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.
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.
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.
This is precisely why await was introduced in the first place. Promise and callback code was impossible to reason about.
String res = someRestCall();
is.
var s = await someCall();
is way easier to reason about than: someCall().then(…)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.
https://elizarov.medium.com/how-do-you-color-your-functions-...
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.
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.
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.
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.
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...
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.
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...
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. 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.
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).
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.
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).
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".
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).
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 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.
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!
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.
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.
“\{a}+\{b}=\{ a + b }\n”
Do you also criticize this? “Price: $${price}”?
Also, I’m fairly sure $ is a special character in regexes..
"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.In C#, the string `"""Hello world"` is invalid, but `@"""Hello world"` is, because double quotes are valid with verbatim string literals.
https://docs.oracle.com/en/java/javase/11/docs/api/java.base...
https://docs.oracle.com/en/java/javase/17/docs/api/java.base...
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.
Each feature links to a deeper dive into it.
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...
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 :)
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.
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.
I wonder how long those builds will remain available.
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.
STR.”Total: \{count}”
Could not get uglier.
1. https://github.com/manifold-systems/manifold/tree/master/man...
sigh
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.
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}"
> trading off a small amount of convenience to gain a large amount of safety
*small
FormattableString foo = $"select * from {table} where {column} = 42";
https://learn.microsoft.com/en-us/ef/core/querying/sql-queri...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...It remember me the first proposed syntax for lambdas. Let's hope the next preview will fix that.
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!)?
I'm not sure what the current matrix of support for this is.
I've written a tonne of code on both Reactor and Coroutines, and would love something simpler, but dread the idea of migration.
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.
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.
Interested in building web applications.
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.
Newer alternatives are: https://micronaut.io/ and https://quarkus.io/
If you want to have something really simple look at Javalin: https://javalin.io/
If so, I overlooked it.
> Long l1 = Long.parseUnsignedLong("17916881237904312345");
Or, just use BigInteger
(I ask from someone who has been away from Java since 2001 for the most part, in Swift and Rust and of course C.)
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.
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).
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.
https://www.quora.com/The-license-agreement-of-Java-says-You...
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).
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).
They have their own db in that
It's very good for backend/server stuff in general. Robust to a fault, reasonably fast, excellent tooling, very stable ecosystem.
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.
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 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.
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.
Eventually, Project Valhalla will provide this on JVM-land, but it doesn't seem like the day will soon come.
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).
It's obviously "MS tech is boring", e.g. TypeScript and VSCode.
Virtual threads