Virtual Threads Arrive in JDK 21, Ushering a New Era of Concurrency
infoq.com
infoq.com
Maybe the confusion is just the term "framework" as opposed to API? I don't mean a third party library is needed. The built in JDK alone is sufficient but you're still juggling promises/futures and the like.
One could argue that automatic promise wrapping and an await keyword ends up giving you cleaner, more simple code (despite the coloring).
I really like the approach Java has chosen: keep the language itself as dumb as possible, but add all the niceties to the standard library and runtime.
But in general it's best to write async code from the get-go because that forces the programmer to compress application state rather than inefficiently smear parts of it on the stack. Better state compression (because smaller stacks) -> smaller memory footprint per-client (or whatever) -> faster (because of higher cache hit ratios because of less cache thrashing because fewer memory accesses).
Because of this server based on new threads does not need to be more performant, Jetty server guys tested this and they were not that happy: https://webtide.com/do-looms-claims-stack-up-part-1/
Before getting too excited I advise to watch Tomasz Nurkiewicz lecture on the subject - https://www.youtube.com/watch?v=n_XRUljffu0, it explains what are the trade-offs here.
No silver bullet, again.
This was in 2021. Hopefully things have and will improve in the final version. We have to start somewhere.
> No silver bullet, again.
More like progressive improvement. If you always need things to be a 100% better we'll never have new toys :)
A typical request easily creates 100's of objects that needs to be garbage collected. Adding a single thread object on top of that means absolutely nothing. And how much garbage to you think reactive frameworks create? I can you tell you it is a lot more.
> Before getting too excited I advise to watch Tomasz Nurkiewicz lecture on the subjec
Don't waste your time. He is pretty clueless. I remember him complaining about lack of backpressure and composability. But this comes pretty much out of the box with loom.
"The posts read like they’re leading to a negative conclusion, but at the end of part 2 it turns out that the current Loom prototype does perform as well as async in their experiments ..."
https://www.reddit.com/r/java/comments/kmn6m3/do_looms_claim...
2. The Jetty experiment measured the wrong thing as they misunderstood the origin of the "million thread" scenario. What happens in a real application is that you have some number of threads with deep stacks servicing incoming requests -- say 50K concurrent sessions -- and then each of those fans out to, say, 19 micro services in parallel, each of those outgoing requests is done on a virtual thread with a very small stack, and that's how you get to 1M threads. I.e. when you have a high number of threads, only a small minority of them (5% in this example) have a deep stack.
3. I don't think anyone would claim anything is a silver bullet. All virtual threads do is let a server service the same throughput as asynchronous code does, but the code is much simpler and it is observable, i.e. easily debuggable and profitable, something that async code can't do.
I could imagine that by having fewer physical threads running, the stop-the-world part of garbage collection could suspend the runtime more quickly. That could reduce the effect of GC-pauses.
> I could imagine that by having fewer physical threads running, the stop-the-world part of garbage collection could suspend the runtime more quickly. That could reduce the effect of GC-pauses.
Precisely. Although it's worth mentioning that while that's true for G1, ZGC does not stop-the-world when scanning roots, including platform thread stacks (https://openjdk.org/jeps/376).
When a call returns, locals and parameters back up the stack will be expected to be live. Since there's no way in general to create a reference to a stack using JVM instructions (unlike .NET), the stack of every live thread must be a GC root.
If you want some more detail, when a virtual thread is in the runnable state, it is reachable from the scheduler (which itself is a Java object, and not a GC root); when it is blocked on a lock or IO, then the lock object or the IO mechanism must retain a reference to it, or there would be no way to unblock it. The thread object has a reference to the stack, which is a heap object (actually, it could be made up of several heap objects).
A thread that is not strongly reachable can provably no longer make progress -- it must be blocked but there's no way to unblock it -- and will be collected even if it has not terminated. It may live forever in our hearts, but not in the heap.
Objects are obviously rooted for blocked virtual threads that may resume - a formal understanding of them being GC roots - but the implementation appears to be by taking a reference to the heap object containing the stack at the moment of being blocked, presumably by a JVM native method or similar.
If by "rooted" you mean reachable in the object graph when starting the traversal from the roots, then yes. If a blocked thread isn't reachable, there is no way to call its unpark method that resumes it.
> the heap object containing the stack at the moment of being blocked, presumably by a JVM native method or similar.
Yes, we implemented virtual threads on top of continuations that, in turn, are implemented inside the VM. Their stacks are reified as heap objects.
Async code is the wrong fit for Java, and it complicates everything tremendously. There also is no reason to believe async code should consume any less (or more, for that matter) memory than Loom-style concurrency.
If anything, I would assume that, after a few rounds of optimization, Loom will be significantly better at managing memory than a Java async framework.
Yes, async is a bad fit for Java. That's a problem with Java, not a problem with my statement.
> There also is no reason to believe async code should consume any less (or more, for that matter) memory than Loom-style concurrency.
I'm not familiar with Loom. I was referring to async vs. threaded. Async code does make the programmer make state explicit in objects rather than partially implicit as local variable values on the stack, and this is more compact than smearing part of the state on the stack.
-- edit myself: no, it can't be. JVM had green threads since eons ago, according to wikipedia.
-- edit again: this SO thread --pun intended!-- explains it
https://stackoverflow.com/questions/74639116/what-is-the-dif...
> In fact, in very early Java versions, the JVM threads were multiplexed onto OS threads (also known as platform threads), in what were referred to as green threads because those earliest JVM implementations actually used only a single platform thread.
> However, this single platform thread practice died away around the Java 1.2 and Java 1.3 era (and slightly earlier on Sun’s Solaris OS). Modern Java versions running on mainstream OSs instead implement the rule that one Java thread equals exactly one OS thread.
https://blogs.oracle.com/javamagazine/post/going-inside-java...
It was originally designed to run on embedded-ish platforms without any real OS. And in such an environment it makes perfect sense to do threading at a VM level (also implementing it that way is not that hard for bytecode interpreter, as additional bonus you don't have to think about issues like concurrent accesses to internal structures of the VM and stopping the world for GC).
The time when Java was designed more or less overlaps the time when first "mainstream" operating systems with OS-level threads (ie. Windows NT and Solaris) were also designed, so it could not exactly assume that underlying target supports OS-level threads. For client platforms you had Classic MacOS and 16bit windows both with multitasking models where the concept of thread does not really make sense and Windows 95 with NT-derived Win32 API with real threads. In server space you had various Unix flavors that migh or might not have OS-level threads but these that had threads had mutually incompatible APIs and then you have "Network Operating Systems" (eg. Netware) that were marketed in a way that presented absence of real multitasking as an "performance benefit".
In this 90's environment typical large application that was intended to be portable included somewhat extensive platform abstraction library that more often than not included implementation of something similar to green threads (with POSIX standartizing ucontext_t and friends as an portable-ish API to built such a thing on). You can probably find remnants of such an layer in Firefox code to this day (and probably in other large originally proprietary software packages that were then opensourced).
Virtual threads to the rescue!
Got any links where I can read about it?
2004 -> 2011, 7 years!
Now they hopefully can work around the kernel for file descriptors (network and disk) saving 30% CPU globally on all Java servers.
Many nuclear power plants wasted on the kernel.
iTunes could have reached greater heights had they allowed nuclear plants.
The old iTunes "Burn Disc" icon with its yellow and black colors and sharp angles always reminded me of the "Radiation Warning" symbol:
https://cdn.osxdaily.com/wp-content/uploads/2009/09/burn-iso...
https://en.wikipedia.org/wiki/File:Radiation_warning_symbol....
When you were burning your own custom mix CD, things got hot . . .
Can you expand on this a little? Are you referring to the necessity to copy between kernel memory and user-space memory, or something else?
How does this work around the kernel? This lets you write java code that uses async IO but make it look nice, like golang code. But this isn't DPDK.
Bypassing the kernel. Java is already sandboxed we don't need the kernel unless the net/ssd drivers crash completely.
It's a huge task/risk, but 30% is alot.
Really what you are proposing is that server Operating Systems and hardware should have a "general NIC" for mundane shared tasks, and a dedicated NIC for handing over to a process and saying GLHF.
they should have just called them "Tasks" leaving the already overloaded term "virtual" out of the conversation.
[1] https://docs.oracle.com/javase/9/docs/api/index.html?javafx/...
Calls are virtual, though under the hood the compiler may inline them.
Memory is managed, though through analysis some allocations will go on the stack.
Threads are now (no full grandfathering, but they tried) virtual, and the runtime may now optimize them differently on different OSes and hardware.
Naming things well is harder than making said things sometimes.
Java has an obviously better type system, while Java originated the billion dollar mistake, Java also at this point has much better practices around handling nulls than Go, Java's jars are more portable than go's binaries, Java's GC performs better, Java has a more mature ecosystem and more libraries... Java has better IDE support and comparable compile times.
I guess at this point the only major difference is that you can teach a 3 year old to write Go more easily, so ChatGPT produces correct Go more easily than Java, and the dependency management story differs a little (though I don't think you can really call a winner or loser on that one, it's just different)
try { return true; }
finally { return false; }Java itself is great these days.
I can spend that time building things instead. If I have an idea I can implement it. Downstream dependencies are rock solid, and the language changes at a manageable pace.
Like I did some stuff in python the other week, and every other line I wrote had to stop several minutes and figure out basic syntax stuff. Just a pain in the ass. Like I probably could take the time and freshen up on python and be up to speed in a few weeks, but that's a few weeks I'm not moving toward my goals.
There's always legacy and people that live with it.
It's a feature that there's no history now?
And soon we're going to cry about Kotlin developers that don't use the latest idioms... oh give it a few years...
On Reddit or HN? Definitely in not the real world. Plenty of projects and even recently created 1s don't always use these new ways of working. Even on Reddit when I see a new project getting posted it doesn't have this trait you mention.
And lots of companies are still on Node 14/16 or even older :( only forced to move by e.g. AWS lambda runtime requirements...
And Node developers are still using ExpressJs and promote it all the time. Guess when Express 4 was released? 5 has been in beta forever with no updates. Yet, most promote it and ignore the other options. Lack of legacy? This thing is 10+ years old. It's barely maintained... what's the difference?
I'm a C# developer and I also have a kind of resistance of java - having to install JRE and now the minefield Oracle made with the JRE, I'm hoping not only that I don't have to code in it, but that I don't have to use ANY Java program just to avoid the runtime or have to choose between different versions of it.
And then the .jar files and how you execute them in command line is different (java -jar), maybe it is simple, but it is different than a plain executable file.
Also, Oracle being a minefield is just bullshit - they are the ones that open-sourced the platform completely to the point that their paid version is only marginally different, but OpenJDK is the reference implemented. They are surprisingly good stewards of the language.
> The quantity of licenses is determined by the total number of employees, not the number of employees using the programs https://www.infoworld.com/article/3686611/oracle-per-employe...
> Employee for Java SE Universal Subscription: is defined as (i) all of Your full-time, part-time, temporary employees, and (ii) all of the full-time employees, part-time employees and temporary employees of Your agents, contractors, outsourcers, and consultants that support Your internal business operations. The quantity of the licenses required is determined by the number of Employees and not just the actual number of employees that use the Programs. https://www.oracle.com/us/corporate/pricing/price-lists/java...
Excuse me, but isn't that a minefield?
Another point I don't want to use java: Now I have to understand what is Java SE and if the runtime falls under it or the development tools or how my users use the software, whether we now how to license every user that won't even use that program and even any people that interacts with our business. Pure maddness.
Will you uninstal linux because Red Hat has a paid support version as well?
> The Java Platform, Standard Edition (Java SE) and Java SE Universal Subscription from Oracle include the Java Development Kit (JDK), and Java Runtime Environment (JRE) https://www.oracle.com/java/technologies/faqs-jsp.html#:~:te....
Do I not need the Java SE subscription to use JRE? Do I not need the Java SE subscription to use JDK?
I have so much questions and because there is even place for questions, I better avoid this thing.
No, use OpenJDK like the rest of the world. It’s free and open-source.
> To run your Java 8 application, a user needs the Java SE 8 Runtime Environment, which is available from Oracle under the Oracle Technology Network License Agreement for Oracle Java SE, which is free for personal use, development, testing, prototyping and some other important use cases covered in this FAQ
https://www.oracle.com/java/technologies/javase/jre8-readme....
Straight from oracle.com.
How is this free? Of course I don't need to pay for Linux kernel. Of course some products feature paid support. But how can I justify the quoted text that Oracle JRE is free?
I was going to say to the guy that decided there must be no Oracle Runtime at our company (some software doesn't work without it, I have no status if workaround has been found) - Hey, maybe Java SE is just the support/patches stuff and maybe we can use runtime? Until I stumble on that text - free for personal use, etc...
And there is one piece that wont run without big red O... :(
That seems like looking for a problem where one does not exist to be honest.
The peer comment regarding RedHat is spot on. Yes you can purchase a Linux distro from RedHat and pay lots of money.
That doesn't mean anyone will argue with a straight face that you can't run Linux for free!
It's the exact same scenario with Java. You could pay Oracle for a Oracle JDK if for some reason you reall want to, but approximately nobody does that.
> How is this free?
Yes, don't go to oracle.com.
This is what you want (it's the same thing, but free open source): https://openjdk.org/
While I agree with the bullshit claim, Sun was the company to open source Java. Which itself goes back to IBM blocking the community process in order to force an Apache licensed Java implementation, Sun releasing the OpenJDK made most people happy without killing its embedded cash cow.
Of course their continued work on the big JVM features and GraalVM are huge for the community too.
And as you mostly use Java on the backend, you're probably running Linux where a free and open jre is packaged, so just target that and not worry about it ever.
running in a container.
If you use 200x images based on, say, `FROM docker.io/library/eclipse-temurin:17-jdk`, you only use 230MB + all other JAR/overlay layer sizes.
You can use also jpackage to create an executable file you can just double-click (.exe on Windows, whatever on mac and linux).
https://www.graalvm.org/latest/reference-manual/native-image...
"Use GraalVM Dashboard to Optimize the Size of a Native Executable"
https://www.graalvm.org/latest/reference-manual/native-image...
Tutorial that shows reducing binary size from 17MB to 862KB!
https://docs.oracle.com/en/graalvm/enterprise/22/docs/refere...
This is available for some of the more common libraries (there is definitely more work to do here), and you can also use an agent, run your code on a regular JVM and it will collect the runtime accesses and create that config file for you.
On the other hand portability and debugging experience with jars are vastly better. Just consider a developer on Mac with Apple silicon cannot use the same executable as on Amd/Intel server, while jars are cpu-independent.
That legalistic hydra is enough to make you second guess using it.
Canonical doesn't own the Linux trademark, nor have the litigation history.
This myth needs to be dispelled.
OpenJDK is Java, and is open source under GPL.
It's true Java still smells like "corporate" and "slow". And when I say "slow", I don't mean the runtime speed, but the company speed ;)
Until not a long time ago I've been maintaining a bunch of Sun|Oracle|J9 Java 6 + JBoss 4.2.3 + AIX|Linux + PPC|x86_64 ... That was not fun, and the task of moving it to more modern platforms was insurmountable to us. No wonder Azul has support for Java 6 until 2027.
But it's true the platform has changed greatly in every way: The VM, the ecosystem... are so different now. And although as a language, it still feels strange to me (I just use Python), now that I manage a fleet of modern Java and Scala microservices, I don't get scared when I hear the name "Java" :)
These blog posts mentions a company that has to write their own database library, auth services or other basic functionality. Sometimes they fix so many issues with the core language that they become close partners with the core developers of the programming language. I can't help wonder if the competition also reads the post and just smiles before they go back to actually solving a business problem. To me it's a symptom of choosing the wrong stack, even though working on non-business related problems may be more rewarding to the individual.
> Think of the history of data access strategies to come out of Microsoft. ODBC, RDO, DAO, ADO, OLEDB, now ADO.NET—All New! Are these technological imperatives? The result of an incompetent design group that needs to reinvent data access every goddamn year? (That’s probably it, actually.) But the end result is just cover fire. The competition has no choice but to spend all their time porting and keeping up, time that they can’t spend writing new features.
Case and point I built a new service recently in Kotlin on JVM. It's a SQL translation gateway that maps an internal representation of simplified relational query model onto other databases so you can connect your own MySQL, PostgreSQL, Oracle, SQL Server, etc. Because Java has such a deep ecosystem in this area I was able to leverage JDBC and jOOQ to deliver support for all the require database types in only a few days of work with very clean code and full test coverage that tests compatibility with all the target databases.
Imagine trying to do that in Typescript. You don't have a standardised interface to database drivers so you would need to first choose that abstraction and implement it yourself + adapters for each driver you need to support (i.e re-inventing JDBC). Then you need to handle the different SQL dialects as they all have different quoting rules, different LIMIT/OFFSET syntax, different supported LIKE/ILIKE/etc, there is no library for handling this in Typescript, especially not with support for commercial DBs like Oracle, SQL Server and -definitely- not for more esoteric stuff like HANA and Informix, etc. Just writing a dialect agnostic query builder abstraction is weeks of work, actually handling all the dialects and emulating anything that isn't native for your targets is much longer.
So you might say, "but that is just libraries"... however that -is- the entire point. When you buy JVM you are buying the ecosystem of libraries primarily. The very nice runtime/GC/languages is really just icing. Same way that when you buy Python you are really buying numpy/pandas/PyTorch/etc, the language itself isn't the important part.
Or you could say this is just a problem uniquely suited for JVM and you wouldn't be wrong but isn't that also the point? Use the right tool for the job. JVM is to business logic and solving business problems what Python is to data science and machine learning.
The big epiphany I had recently was that when I build on JVM I don't write much code. I make a design, assemble some libraries in the correct way, iterate on it by refactoring until the abstraction is clean and then turning it on. There is very little mechanical coding involved because everything fits together very cleanly with only business logic being surfaced in actual "code". I feel more like an architect, less like a code monkey and I can ship results way faster because I don't have to work on code that doesn't directly solve the problem. i.e there are much less yaks to shave.
Now, with the advent of virtual threads this should no longer be a problem! JDBC can block all day long, and your app will still scale without a problem.
I've done quite a few startups by now and I can say that in all of them the initial quick-n-dirty MVP codebase lasted well into the era where things like "security, performance, scaling, testing" became a priority.
Unless you are absolutely committed to throwing away the MVP (and I don't see how you could because as soon as sales team sees it they start selling it and you won't have time to throw it away), your best early move is to use technologies that will take you to the long haul because you will be stuck with that initial code for a very long time.
(This doesn't mean architecting the whole system for scale you won't hit in years, mind you. Just to use technologies that will make the transition to a mature engineering team easier. Such as Java, given the topic of this conversation.)
People that avoid Java don't do it because of some stigma, but because they have been burned before. Remember that a typical Java project is not the latest version and uses a mix of dependencies that make using newer features impossible. Then, there is no point in updating, you might as well rewrite it. That is the reality.
Fear of Oracle is rational though, unlike fear of Java.
In this case, it's an "irrational fear" of Java to have preferences beyond Java.
This so much. It's exhausting to experience startups shoooting themselves in the foot by not using Java where it's best simply because it's not hip.
Golden rule for startups, spend your innovation capital on your product not on trendy (unproven, immatue) implementation technologies. Use boring (aka mature, high performance, best tooling) technology.
In my experience, Java's problem is the same as C++'s when Java began to take over: you usually encounter it in projects stuck at ancient runtime versions, with ancient libraries, full of code nobody dares to touch in case something collapses. The same will happen to Go, and it'll happen to whatever popular language will displace Go as the trendy language to learn; it's just a consequence of keeping around legacy code and not spending the time and money on migrating as soon as possible.
I mean, it’s also less verbose, easier to start a new project, faster to startup, has far fewer configuration knobs, has native dependency management, is far easier to build CI/CD for, compiles more quickly, has very few NPEs gotchas, has value types, and avoids idiomatic boilerplate.
Go is far from perfect, but it has clear advantages over Java and I would not go back.
But the thing I love the most is the lack of a JRE. If I build for a target, it runs there.
Are we talking about the same language? Or maybe you consider writing "if err != nil" every few seconds as good exercise for your fingers...
I think we shouldn't point fingers at Java when it comes to Go's verbosity.
I can simply do dict.contains("foo") in Java vs having to go through a verbose hoop:
if val, ok := dict["foo"]; ok {
//do something here
}
Common operations like filtering/transforming collections are very concise in Java - I'm certain doing so in Go will be a bunch of more boilerpate: collection.stream().filter(...).map(...).collect(...)
Don't get me started on having to go through iota hoops to declare enums.But I can say honestly that I prefer Go’s error handling, which I find tends to result in errors which are actionable. I think it takes a lot more effort up front to get Java exceptions to work rationally.
If I could change Java then the thing I’d do is I’d make it necessary to declare all exceptions in a method, but get rid of caught exceptions. So you can see what’s coming, even if you don’t have to deal with it.
In terms of stream operations, well, I write a lot of typescript lately and just like in Java, I find that I end up having to fall back to regular loops quite often. I’m not convinced that stream operations are useful in as many use cases as people would like. For example, the moment something can throw an exception, things are going to get gnarly.
That's pretty much what javadocs contain already
This kind of nonsense was endemic to the Java engineering culture while I was working in it.
Perhaps the worst example I saw was during the fluent API craze where someone in my team replaced a constructor call for a Button with five lines of fluent Builder pattern gibberish.
The point of mentioning FactoryFactory was to poke fun at the culture surrounding Java, which ended up being one of the reasons I stopped using it, because idiomatic java stopped making sense to me.
Hopefully it’s changed now.
So I guess I also like Go’s batteries-included standard library.
And, IMHO, duck typed systems are better than the Java style OOP.
I've used java for over 20 years and managed to almost completely avoid it.
As with npm - I think automatic dependency management at the library level pulls in far too much of the world - most of which your code doesn't actually depend on because the one function you need in lib A, doesn't actually require lib B, and therefore lib B dependencies C&D etc etc etc.
Madness.
If you do dependency management at the source level ( using the compiler.... ) then life is generally much simpler ( reflection being the only aspect you need to manage ).
The wheels aren't the car. Sure, try to drive around without wheels.
You gradually pull in more and more c*p.
So if you don't do it that way, just you need to manage the dependencies yourself, but they are much much simplier!
In a bit more detail - at the top of your source code is:
import someorg.somepackge.class;
If you have the source code for someorg.somepackage in your sourcepath/classpath ( and the source of any transitive dependencies ) then the compiler will magically find all the transitive dependencies for class at the class level at compile time. [1]
This results in the minimal number of classes you have to ship and as a result the minimal number of transitive dependencies.
Now your build tool/script ( whatever tool you use ) will need to bring in those dependencies from a versions repo somewhere - but that can be your own source code repo ( vendoring I think it's called ).
Yes - you need need to keep that build config yourself - but frankly that's time well spent as it results in you have proper control over your dependencies, and they don't balloon out of control.
Occasions like some random third party has added dependency D to C which is brought in via an A->B->C chain and then you have some library version clash are much much less as a result.
That's not to say I don't occasionally use jar files in the classpath - but that's typically only if that library is self contained, single focus and small - and again you can put that in your build script.
In my view, Maven magic is part of the problem, not a solution - and to sum up why - it hides the cost of the dependencies - if people had to manually add the chain of true dependencies when they decided to use a apache utility class, they might think twice about whether that chain of dependencies is well designed or not.
[1] With the reflection proviso I mentioned above.
A couple of minutes ? Even timed this with another HN Java disbeliever who said its too much effort.
How to produce a single binary:
go build
cargo build
zig build
<project>
...
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.4.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<artifactSet>
<excludes>
<exclude>classworlds:classworlds</exclude>
<exclude>junit:junit</exclude>
<exclude>jmock:*</exclude>
<exclude>*:xml-apis</exclude>
<exclude>org.apache.maven:lib:tests</exclude>
<exclude>log4j:log4j:jar:</exclude>
</excludes>
</artifactSet>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
...
</project>
Can you spot the difference?Also, how so wonderfully and utterly balanced of you to give an old-school Maven pom.xml file with the maven-shade-plugin and exclusion lists, but omit the Cargo.toml and go.mod files.
Also, its as simple in Java as:
Gradlefile:
apply plugin: 'java'
command: gradle build
Can you spot the difference ?EDIT: Ok you were asking about creating single all-in-one fatjar ? Add the below to your Gradlefile:
Gradlefile:
plugins {
id 'com.github.johnrengelman.shadow' version '8.1.1'
}
apply plugin: 'com.github.johnrengelman.shadow'
command: gradle shadowJarI guess I just need to know which random Github project is the new hotness today that does something that the other languages do out of the box. Thanks for supporting my point of view.
Again, to produce a single binary you need to know/care much less with Zig, Go and Rust than with Java.
Second, Java's infamous for having VeryLongClassNameFactoryBeans on the one hand, and deep class hierarchies on the other, with new classes being declared for everything.
Third, it depends. You can set up a HTTP server in two or three lines in Go without 3rd party dependencies. This [1] is the shortest Java versions I've found, and it contains a lot more magic.
I'm biased in favor of Go, but verbosity is not the issue there.
[1] https://stackoverflow.com/questions/3732109/simple-http-serv...
Composition over inheritance has been a mantra in Java circles for a very long time now. The important difference is for example that Go can’t have proper error handling no matter how good the developer is, due to the language not having a proper abstraction over that.
But that doesn’t work, and you can’t handle every error at the place of its origin.
What part do you find magical?
HttpServer server = HttpServer.create(new InetSocketAddress(8000), 0);
server.createContext("/test", http -> http.sendResponseHeaders(200, -1));
server.start();
Contains too much magic?I have always wondered why Java is hammered for this while Apple is celebrated for this.
CMMetadataFormatDescriptionCreateWithMetadataFormatDescriptionAndMetadataSpecifications(allocator:sourceDescription:metadataSpecifications:formatDescriptionOut:)
https://developer.apple.com/documentation/coremedia/1489454-...
"Creates a metadata format description by extending an existing description with the values you specify."
The description doesn't sound so bad...
Indeed.
And it's good to remember that there is nothing in the Java language forcing one to name things verbosely, that's on the developer's personal choice. Just like on every other language.
But what would I know? I only worked in Java for 20+ years…
Project Valhalla, and the foreign function and memory APIs in particular.
I don't expect a lot of code to actually use these features, but the code that does will be the low level stuff that lets the higher level stuff really perform.
Also, this is a huge advantage of the system, you don’t shallowly depend on a ton of C libraries, you can be confident that your whole application to the last bit is properly abstracted (plus can be debugged and observed with the same great tooling).
In my experience, most (especially business) applications are absolutely not going through millions of data entries doing some local calculations over them, that part is delegated to a database. They either do some IO over them, or have much smaller element sizes.
Java has escape hatches even for handling some of the “millions-of-elements in a hot loop”, but for an AAA game engine it might not be the best choice, but for anything else? It is more than fair game.
If you do have a million objects and it is indeed a performance bottleneck (as shown by the profiler) then it might be worthwhile to pursue these solutions, or even implementing them in another language. Noone claims that Java is for everything, but it is for most things.
This is use case for writing GC benchmarks developers not for typical enterprise CRUD developers.
while Java originated the billion dollar mistake
By the "billion dollar mistake", are you referring to null references [1]? But null references were introduced in 1965 in Algol, by Tony Hoare. They long predate Java.[1]: https://www.infoq.com/presentations/Null-References-The-Bill...
No. Go does containerized microservices better because of its lower memory footprint. If you can use GraalVM then that might not matter.
Java stacks like Quarkus [1] have memory as low as 12MB and JVMs like OpenJ9 natively support CRIU for instant startup.
[2] https://blog.openj9.org/2022/10/14/openj9-criu-support-a-loo...
So no, it's not as simple and perfect in my opinion.
I regrettably don't remember the outcome though, and don't use either of them anymore to even test them readily, especially not under the same load.
And those Go binaries aren't runnable across a wide range of platforms like JVM apps are.
Likewise for "worse" ecosystem. It really depends on your context.
Edit: A closing thought: One thing I find enjoyable about working with Go is that it is tuned for my particular context. I also enjoy writing Java (and more specifically Kotlin) when that context changes (e.g. a desktop application).
Java can be taught to 2-year olds so Go seems to have a slight disadvantage there. ;)
At the same time, the ecosystem can feel messy and overwhelming. There are multiple @Nonnull annotation libraries and it's not clear what the difference is. There are many different testing libraries (some TDD, some BDD) but it seems everybody picks a different one. There are multiple advanced concurrency libraries and concepts, but again not much consensus and everybody uses a different one. Lots of deprecations all over the place.
It's even worse in the Jenkins-specific ecosystem where nearly every plugin uses some deprecated aspect of either Java or Jenkins, and hunting for what the "right" way is supposed to be is a bit of a daunting task.
There are like 20 different JDKs, not sure why.
On the other hand, startup time and memory usage still seems to be a big issue. Starting Jenkins takes forever even with JVM in client mode and with bytecode verification disabled. And when started it uses a huge amount of memory. To date I've never seen one non-hello-world Java app that isn't like that.
- Nonnull annotations: you can pick any. Tooling doesn't care and tends to just accept any annotation with a name like @Nullable or @Nonnull regardless of namespace. There was an attempt to standardize this years ago which failed for reasons that are hard to understand from the outside, something to do with lack of agreement on the exact semantics and use cases. No, me neither. Or just write in Kotlin where it's all integrated with the type system and the compiler understands / hides from you the Java annotation mess.
- Testing libraries: it's been quite a long time since I encountered anything except JUnit 4 or 5, but they interoperate so that's no big deal. JUnit 5 is great and very standard so you can't go wrong by picking it.
- Concurrency libraries/concepts: yes this is a problem but it's one found in other ecosystems, and it's one the new virtual threads are designed to solve. The hope is (and I guess we'll see) that everyone can forget about coroutines and reactive programming now, at least where performance matters, and go back to writing old fashioned ordinary threaded code. I guess they'll stick around in Jetpack Compose GUI programming.
- Lots of different JDKs. That is indeed new and unfortunate. They don't actually vary much, mostly in how long major versions remain supported. Amazon is a sort of default choice unless you're doing GUI work, in which case JetBrains Runtime has lots of desktop specific patches.
- Startup time/memory usage. There are simple things that can be done to improve this (see AppCDS). Some of it is cultural however, the people who write CI servers probably don't care about startup time. IntelliJ for example starts pretty fast in the latest versions, because they care to optimize it. For memory usage, be aware that the JVM will default to using most of your free system RAM even if it doesn't need to. It figures hey, the RAM is free, so why waste CPU time and energy on garbage collecting if I don't have to. If you planned to use the RAM for something else, or conclude that this is the "natural" level of RAM usage, it can be annoying however. You can give it a limit or in recent versions set a flag that'll cause it to regularly run GC when the app is idle, to give back memory to the OS.
You can generally use any.
Azul, PTC, Aicas, microEJ, OpenJ9 are their own thing. And then there is ART.
Also special stuff like Ricoh, Xerox, Gemalto, Cisco among many others using Java subsets to customize their hardware.
But the “niche” JVM category is really (perhaps surprisingly) huge.
There is a HotSpot feature called AppCDS. It improves startup time by about 30% in my experiments. However you have to turn it on. Just running `java -jar` won't do it.
Then there is GraalVM Native Image. It can compile JARs to native code ahead of time. They start as fast or faster than the equivalent C program, so it's the big hammer for CLI tools. However, it can't cross compile so you have to compile for the target system on the target system. There can also be compatibility issues with some libraries, though that's getting better rapidly.
So those are the options. Still, even without those extra features, startup time is usually good enough.
Doesn’t it generate every additional code at compile time? I didn’t notice it slowing down my startup times, it’s mostly every other lib (which you usually have, as what’s the point of a hello world cli app)
From the documentation:
> The picocli-codegen module includes an annotation processor that can build a model from the picocli annotations at compile time rather than at runtime.
> Enabling this annotation processor in your project is optional, but strongly recommended. Use this if you’re interested in
I think you didn’t use it.
See here:
https://github.com/remkop/picocli/issues/539
Although looking over that issue, it looks like maybe reflection was never proven to be the source of the slowdowns.
Edit: Now it’s down to 80ms - I guess at this scale it’s hard to pin down.
Right, but large Java programs use huge numbers of classes. They may load thousands of classes just during start-up.
> the byte code format is very compact and can be parsed in a single pass, so I don’t think it would be significantly slower than ordinary machine code loading
That's what I was wondering about. There used to be a flag in OpenJDK to disable bytecode verification to improve start performance, but it was deprecated in JDK 13 [0] and I think it's since been removed entirely. Its removal is understandable but I still wonder about the performance impact.
[0] https://www.oracle.com/java/technologies/javase/13-relnote-i...
I have a tool I created that creates a git branch based off a Jira ticket number. If I don't supply a ticket number then it errors out straight away, so I think most of that 148ms is the JVM starting up.
It's not free, but VS isn't either for any serious commercial usage, and Rider is certainly worth the money.
What does the Python core community offer? IDLE is ugly, is barely an IDE, and didn’t have line numbers until a few years ago. Everyone is using PyCharm or VSCode.
Sun made Java, it offered Netbeans.
As for the Python community, no one expects great tooling from FOSS languages, as you mention everyone is using tooling from two big corporations.
Also, Go doesn’t have inheritance, but does have interface forwarding and structural (as opposed to nominative) type-system - those both very significant factors that influence final program design.
In C# you must write explicit async/await keywords whereas in Go it is implicit, but the code reads almost the same.
I would argue against using implementation inheritance generally and interfaces sparingly in C# anyways.
As an aside pipelines are terriblely unergonomic. The public APIs are not fully developed and something simple, like IDK creating an actual processing pipeline, is funky as all hell. Creating a pipe wrapper feels dirty.
The buffer management is cool though and sequence seems like it should have just been made a first class slice type..
See https://learn.microsoft.com/en-us/dotnet/standard/io/pipelin...
The main problem it seems to solve is processing a text or byte range from a stream (be it an endless SSE/WebSocket stream of messages, or just from a huge (multigigabyte+) file on disk - in a non-blocking/async manner.
...but the .NET runtime has a higher memory floor. It's a lot easier to write very small apps in Go. New versions are working on stripping in AOT builds but it's still very much a work in progress
The JVM is very impressive and a great thing to build upon. The Java standard library is vast and the developers actually care about it (unlike the Python folks, who gave up on having a sane HTTP client library built-in and instead defer to the third-party requests library).
The Java language is not as great, lacking some quality-of-life features (properties and operator overloading, for example). It often suffers from design-by-committee (e.g. https://openjdk.org/jeps/430 ).
And then you get to the web frameworks. The most popular one is Spring, and it's a pain, with abuse of reflection and magic, bad docs, and other issues.
How is it any different from something like RoR or Django, both of which are well-liked?
Both of them do not have bad docs, maybe some sub project might have, but compared to RoR (sorry never used Django), Spring's projects docs are magnificent. Their problem could be navigation for someone new to it, or in the case of Spring Framework, just too much concepts.
So, Spring Framework is basically "make everything configurable extensible etc", its code if full of old java patterns for generic stuff (the infamous "AbstractSingletonProxyBeanFactory" , ye, not great), to not only have a very feature full dependency injection, but to allow stuff like AOP and setting up via xml and many other things. This is the base of spring so you will have to kinda deal with its concepts if you hit an issue.
Ruby on Rails was a reaction to things like Spring, it's philosophy is "convention over configuration" in contrast to Spring's unparalleled configuration options. And so Spring Boot is born as a reaction to Rails.
Spring Boot is at its core 2 things
* an annotation engine for Spring configuration, instead of using all those hellish factories or XML
* Sane Defaults for Spring Framework There were also many other projects under its umbrella to streamline with those 2 things other components of the Spring ecosystems, from Http Apis to Repository patterns
Ok so reflection and magic:
* Both are full of reflection. That's how it keeps being very generic at its core.
* Magic is an issue of Boot, you just put some annotation and it would do who knows what. Still way less magic than Rails, as at least you see the annotations, and search for it in the docs. Framework isn't really "magic", as you would have to explicitly configure everything in either XML or through its classes. ( Spring Data does have weird magic, worse that Rails, generating full SQL queries from method names? C'mon)
Nowadays, modern java (server side) either uses Spring Boot (due to easily supporting most kind of infra you might use from persistence to messaging), something specific for their use case (quarkus makes it easy for small image and quick startup) or not use a DI framework at all, as most servers nowadays are fine, and language has plenty of features to not need some management of Injected. I actually see no value now to "minimal DI frameworks", because manually wiring is not actually hard except when you have a circular dependency, which you should not have anyway. (The codebases I had better time working with were either on Boot or had no managed DI)
Spring Boot and Framework new releases actually require Java17, with the objective of being able to start to clean up their codebase from old patterns that were kinda required in the old times.
Reflection is not a feature, but a kludge - to conceal how non-expressive the core language is. Add annotations to that, and you might start asking yourself, what value does exactly Java's static type system add here, compared to a dynamic language (say Javascript)? Most of your errors will remain undiscovered until runtime, so why bother?
Things are changing for the better with Java, of course, with the speed of a glacier, but I don't think we'll ever be able to get rid of Spring proxies, or annotations or reflection.
Spring already bumped base to java 17, and the plan is to improve the internals, but changing public APIs is a different story.
Still, other options appear everyday so you don't have to use Spring. But yeah, glaciar speed, but with that comes low churning rate and old stuff needing very low maintenance.
I think you'll need to substantiate that one with some solid evidence. I don't see anything wrong with Go's error handling that cannot be explained by developer choice.
Exceptions (especially checked ones) allow for as fine or crude-grade error handling as needed. Don’t get me wrong, Java’s checked exceptions are not without problems, but compared to Go almost everything is better in this regard.
I have seen the below occur again and again in Go. (no compiler help, so this can only be caught in reviews and not if the reviewer is tired).
#50: Checking an error type inaccurately
#51: Checking an error value inaccurately
#54: Not handling defer errors
I've used both Java and Go and I can tell you, there is no right or wrong but they differ a lot in their ecosystem, tooling and in design patterns. I don't see myself going back to Java because of the virtual threads.
It really is the best of both languages... unfortunately, the main supporter of Pony seems to have stopped using it in favour of Rust though :D.
But if that's really what you want, Pony is your language. It definitely deserves more love.
How does Java have a better type system? Java's generics are unsound, while Go's generics are sound. Java's generics do type erasure, while Go does not. Java's type system is not unified, it does not have a top type (an int is not an Object, int vs. Integer etc.).
My main issue with non-erased generics is that they actually break reasoning wrt. parametric polymorphism as soon as you allow for pattern matching or even just .isInstanceOf on type parameters. Suddenly a function which takes a List<A> can do entirely different things depending on what A is... and that takes away a powerful reasoning tool.
Collection<Foo> values = ...
Foo[] array = values.toArray(Foo[]::new);
Not the worst, but not the best either.There are many situations where the code I implemented could have been far cleaner with reified generics.
One example I can think of is Java's annotations vs Go's closest alternative, struct tags [0], which are just strings added to struct fields that specific libraries can act on; at best these are only checked at runtime, the compiler or type system will not help you with those.
[0] https://go.dev/ref/spec#:~:text=A%20field%20declaration%20ma...
> Java's type system is not unified, it does not have a top type
Neither does Go for that matter.
Go's lack of visibility modifiers and package scope namespace cause very common gotchas mentioned in https://itnext.io/we-need-to-talk-about-the-bad-sides-of-go-....
There is a Manning book about Go: https://www.manning.com/books/100-go-mistakes-and-how-to-avo... . And these are not rare mistakes. Everyone makes them and some of those mistakes are repeated again and again in every Go project. (Esp the for-loop ones). I have found programming in Go needing the kind of alert, defensive mindset I adopt for C++ which is quite exhausting. Not so much for Java.
However, to be honest, Rust is probably the only the language where you can relax your "defect-analysis" mind thread while coding - with the exception of async Rust.
As of writing this comment, golang does not allow you to have generics on methods as far as I'm aware.
Also, what about channels, and select semantics etc?
I think before those are in place, with good library support, it won't be very close to competing with Go.
Not to mention compilation to fully self-contained binaries.
Yes, you can set the number of OS threads they can use.
Channels and alia have been implemented in Java’s standard library for many many years, it doesn’t need additional language features.
This change improves Java/JVM capabilities and that's great. Java is a great language and it's nice that it is shaking off the stagnation/perception of stagnation it has acquired over the years. Golang also has really great capabilities and use cases. Having written tons of code in dozens of languages it seems to be folly to expect one language to rule them all. The features that you don't have are almost as important as the features you do have.
If Java is looking for additional golang cherrypicks I would love to see their start times improve (shortlived java processes are painful, Graal doesn't cover all uses). FFI even in Panama is much more boilerplate then CGO. Go's deployment story is so much cleaner and straightforward then Java. The amount of engineering hours saved through language/toolchain standardized formatting is just monumental. Those are just 3 things at random, their are a bunch of other great ideas to steal.
I also wouldn't throw any shade at Golang's vibrant library ecosystem. There are just so many great active projects written in pure go that make spinning up new services a joy.
You can deploy Go app with a single binary, no runtime required. No runtime configuration.
People want to learn/write Go. It's hard to find anyone that wants to learn/write Java. People probably prefer Kotlin over Java today. It's a much better language and you can keep using JVM libraries.
Go is very simple language that is just as powerful as Java, if not more powerful due to not having to rely on the JVM. That's why it's so popular as a replacement for Java.
How are Jars more portable than Go binaries when you need Java installed to run Jars?
Java obviously has a more mature ecosystem with more libraries due to age, but Go you don't need many libraries to begin with. The Go std lib will give you almost everything you need in most cases.
It's even harder to find Rust developers and yet it's well liked - so? So?
The funny part is most of the anti-Java logic is flawed. Maybe using Java will fix that for them :p
Uh, you don't...?
https://docs.oracle.com/en/java/javase/14/docs/specs/man/jpa...
> you don't need many libraries to begin with.
This really depends on what you're doing. Having many libraries creates a lot of weird potential. You can absolutely subsist on less, but you can do more with more.
A while back I found myself wanting to render MathML into an image. So I dug up jeuclid which is absolutely antique and as far as I can tell not actively maintained. My project flat out would have ended there if I had to build my own math rendering engine to proceed.
So now you're back to platform-specific packages, except you don't have Go's support for cross-compilation. Hardly much of an advantage.
But to be realistic, most Java code either runs in application servers (as WARs) or in containers, and in the latter case, even though your java jars are architecture independent, docker just isn't.
Plus the target OS needs to exist on the toolchain anyway.
https://conveyor.hydraulic.dev/
It's not JVM specific but it does understand how to bundle the JVM, use jlink to shrink it, read your classpath from Gradle etc.
I don’t think so, Go is very low on expressivity. Generics help, but I don’t see anything like JOOQ for go, just as an example. Also, no real alternative to Java’s stream api, which can at times make code much more readable than the 4 nested for loops with 4 different exits.
Your java knowledge is half a decade out of date.
I would actually argue the inverse. Between the two languages, Go would be my second choice precisely because it does NOT have the JVM. Even though I have used GraalVM for AOT, with Docker / containerd, I would take the JVM any day. It's just night & day when operating something in production.
That being said, Go still has a lighter resource footprint but I found Go to be a better Python alternative than Java.
Here are some IMHO acceptable gripes with Java:
- Java represents strings using UTF-16 (although there are optimizations introduced in Java9+ already to use LATIN1 / ascii encoding if you don't need to use Unicode)
- Java makes it difficult to have steady state memory consumption (by design)
- Java's escape analysis is primitive
- Java is missing value types (coming soon!)
I think Go is great, but IMHO modern Java is just better at building backend systems.
I find your other points a bit dubious. Java's type system suffers from extreme verbosity and little soundness, and exceptions have not been a successful error-handling story. I don't think you can call a jar that needs a system installation of some specific jre portable - at all. The ecosystem may be huge, but also nightmare in terms of interoperability and support of newer features. IDE click-driven-development is a crutch for the extreme verbosity and complexity of patterns in Java. Compile times are much worse in my limited experience.
Based on your writing, you sound like either a junior engineer, or someone who has had very little responsibility for making resource allocation and strategic decisions.
Java is, like its large corporate users, stuck in the past.
No improvements, what a joke.
If you compare Java 6 (2006) and Java 20 (2023), and then look at C# 3.0 (2007) versus C# 11 (2022), C# has gained many more features and improvements than Java did over the years.
Anyone that misses C# on the JVM can use Kotlin or Scala.
And best of all, due to Microsoft's lack of investment on VS4Mac and VSCode versus VS proper, the best .NET IDE outside Windows runs on Java/Kotlin.
> best .NET IDE outside Windows runs on Java/Kotlin.
I would argue “in the world”. And it does not matter in the slightest to a user. The best Python IDE is PyCharm, also written in Java/Kotlin. I don’t know which PHP IDE is the best, but I’m pretty sure none of them are written in PHP, and the same probably applies to Ruby.
As for mattering to the user, it surely does, it shows how lacking their ecosystem is for specific workloads.
Java may be the other side of it, but I think it is a safer bet.
Is this supposed to be sarcasm? Because the literal opposite is true.
Jars are more portable at the cost of requiring a JVM installed on the target, whereas Go's statically linked binaries and great cross-compilation make portability mostly moot for server applications.
Also, with Java 21, the JVM brings runtime support for virtual threads, at the VM and standard library level, but there is no real language support, whereas Go has actual support with syntactic sugar around goroutines, channels, and select statements.
Tooling and the sheer number of libraries are definitely Java's biggest draws, but it's not enough to justify choosing Java over Go across the board.
Fully accept that Go has the superior cross-compilation story, though you can do this via Github actions if you really want this for Java. https://github.com/marketplace/actions/github-action-for-gra...
As for GC - Java is superior in nearly all respects. But you are right that common programming libs+paradigms in Java produce too much garbage. This has changed with lean and memory-sensitive frameworks in Java nowadays - like Quarkus https://quarkus.io/
Since when?
Of course whether that matters to you is another thing. Most people package either of those up using docker and run them on generic linux hosts.
And I'm probably not the kind of person you imagine. I'm in my 50s. I used Java for ~20 years. I spent a lot of time developing efficient programming practices in Java and teaching those to both junior developers and people with decades of experience.
I'm also not someone who takes a language switch lightly. It takes much, much longer to become reasonably competent in a programming language and I think that if you are going to choose a "workhorse language", you kind of need to see it as a decade long investment. You don't learn a language in a year.
I switched to Go mostly because it is a nicer language for what I do (servers). But I can fully understand why someone would flee Java. Java has a lot of baggage in the shape of legacy code. When people talk about languages they tend to talk about them as if we all live in a fantasy world where we can write new programs from scratch all the time. But that isn't reality for most programmers. Most programmers aren't starting projects from scratch - they work on stuff that has already existed for a while.
Java has been around for a long time. Which means that every version of the language, every fad, every architectural trend, everything that has happened in almost 3 decades of Java exists at the same time in codebases that are alive and kicking. When developers get a job, this is what they are faced with.
The same is true for C++. Yes, the most recent language spec is a nicer language than what you had 10 or 20 years ago. But that probably isn't the language you get to use when you join a company and start to work on their product.
The reason people choose different languages when they do start from scratch (do a startup) probably isn't entirely rational. I suspect a lot of people avoid languages where they have encountered large legacy codebases full of frustrating code that is done in a out of fashion way.
I chose to switch to Go, despite having 20 years invested in Java, because Go has the features I need in a language and isn't cluttered with much of what I don't need. It has nicer tooling that just works better for everyday things. It also has a healthier approach to how you design stuff: it isn't dominated by large frameworks. I don't have to teach Go programmers minimalism and have "experienced" programmers throw tantrums because they feel threatened when having to re-learn how to program Java.
To be honest, I shudder a bit when thinking about going back to Java. It has so much stuff to deal with when reading other people's code. It doesn't feel like it is worth my time. I can't go back to that. My time is too valuable to me.
(I probably should point out that I did decide that I had better find an alternative to Java when Oracle showed itself to be untrustworthy and litigious. But it took years to find both the opportunity to leave Java and the language to leave Java for)
Go of course has simplicity as its main advantage. That doesn't go away of course. But it's a double edged sword and it can be a bit overly verbose / limited for some stuff.
Maybe more interesting is the trend towards native compilation in the Java world. Graal is a pretty big deal. And with languages like Kotlin, there is also kotlin native and wasm as a relatively new option. That's the other advantage Go has had that is slowly going becoming less relevant: fast start up times and a simpler run-time (i.e. a statically compiled binary that you start).
ChatGPT is fluent in many languages. I've been generating some usable Kotlin code with it, for example. I'm sure it does Java just fine as well.
- compiling to a single binary (I guess jpackage fixes this)
- saner / less elaborate / more ergonomic interfaces to implement and use e.g. compare `io.Reader` (https://go.dev/tour/methods/21) to the Java equivalent
https://docs.oracle.com/en/java/javase/20/docs/api/java.base...
Single method interfaces are what lambdas are for.
public abstract class Reader
Did Java add multiple inheritance or am I missing something?However, there's lots of code which just takes a Reader/Writer and then you're SOL.
(In general, the I/O interface/class hierarchy in Java is a bit of a mess because a lot of it was retrofitted to maintain binary compatibility. The equivalent stuff in Guava seems a bit saner, but of course not directly usable with all the 3rd party libraries you might want -- it has shims for the most part, though.)
regarding the "com.lol.myapp", i actually think this is a good think for package management, it avoids naming issues with packages for different vendors and forks. On the code itself you should only see these on the import lines on top of the file, which isn't really a big deal.
I think most people issues with java come from old legacy codebases that had over the top patterns like that due to limitations in the language and culture. Nowadays, with a proper conventions guide, java can be quite clean. Sure it won't ever be as clean as something new as Kotlin, as it tries hard to maintain backwards compatibility, so old ugly stuff will remain in the language (even if you don't use it, you might see old code that does), and new stuff designs are restricted by what already exists, but it still has its advantages over new shiny things like kotlin for example: new pattern matching, compile time and compatibility (kotlin "100% compatibility" doesn't actually cover everything, and compatibility is important for Big Co with loads of teams and loads of internal and external dependencies and tools)
Define rapidly. It's now been two decades since I was first told "yes, that's a culture thing, but everyone knows it's crazy, and it's on the way out".
E.g., this masterpiece from Benji Smith [0], originally on the old "Joel On Software" forum, is from 2005.
[0] https://gwern.net/doc/cs/2005-09-30-smith-whyihateframeworks...
Every java project I encounter tends to be overengineered, has tons of useless boilerplate code and ends up throwing mile-long stack traces as a result. Also, somehow maven manages to be even more unreliable than npm as a package manager. I still frequently encounter situations which seem to only get resolved by throwing away my .m2 folder.
Furthermore, since the language evolved so much in recent years, it's hard for newcomers to know what the best practices are. There's at least 6 different ways to iterate over map values. Which one should I pick?
I stopped working with Java not because the language or the ecosystem. I stopped working with Java because Java developers and their culture of over-engineering everything defending it as "clean code" and "good practice".
But this seems a taboo topic in a culture infested with "good practice gurus".
I think most Java developers will wince at clean code. Clean Code made sense in contrast to the pervading gang-of-four norms that preceded it, but I don't think many people would recommend the style today.
Those types of Java devs can use a lot of interesting sounding terminology but they overengineer everything and commit ludicrous amounts of code that doesn't tackle the problems at hand.
Unfortunately, Gradle is the best build tool I've used for complex systems. And it still fucking sucks.
I have my own issues with Maven, but never in my 16 years doing Java have I done that. What the heck leads you to believe doing that may fix something at all? .m2 is just a cache, it has pretty much zero impact on whether your stuff will build unless you had installed things there that are not available on a configured repository or something similar, which would of course be your mistake not the tool's.
Well… the cache can be broken. I’m not a Java dev so have no horse in this race but “it’s just a cache” doesn’t mean “it can’t ever be a problem”
There may be other issues as well, because this was an issue that affected Maven throughout my years of using it, in contrast to SBT, which in my experience had this issue much less often (though it definitely did from time to time.)
Didn't stop people from using Javascript.
> I still frequently encounter situations which seem to only get resolved by throwing away my .m2 folder
And I frequently encounter issues which seem to only get resolved by throwing away my node_modules folder (and package-lock.json).
Point being every language has its issues but Java gets knocked down constantly regardless.
Now, 21 is coming and while the changes aren't quite as in your face, the change in philosophy is just as radical.
Java would be better regarded if less time was spent on "look at this cool new feature" and more spent on "this is how you should use Java now". Or even, "here's how to safely modernise your codebase".
Venkat Subramaniam does some good talks on this but I would prefer if the Java language team were leading it.
Do you run `mvn install` for your local project under development, or any of its locally built dependent modules? Are all of those at a snapshot version? If not, I think you'd need to delete it from .m2 (or clear all of .m2) to get updates to it into .m2, since even for the local cache I don't think maven will override a cached version during install.
I would say the culture is not an issue of Java but of popularity. If Go became the shovelware language of choice, you'd see a lot more bad Go.
Go still has composition over inheritance, which is vastly more flexible. Go favors explicit (verbose copy-paste, redundancy, use stdlib first, libraries second) over implicit (magic annotations, frameworks everywhere) and I like when I can understand what's happening without holding 10 files in my brain context. Go's memory usage doesn't trigger the OOM killer every 10 minutes. Go favors copy-pasting because what you copy is short and understandable, and function names don't have 10 words in it.
It's always going to be a personal choice, and even though virtual threads bring java closer to where go is, it's still not there for me.
It favors as a culture. A little copying is better than a new dependency
Just like Java has culture to setup a single http endpoint one typically adds 50-60 little jar files of Spring Boot starter.
No, you add a single annotation..
- Modern Java requires heavy use of Decorators
- JSON handling is tiresome
- As soon as you are using Spring you aren't actually coding Java anymore
- Many mature libraries have dated APIs
- Similar functionality is often implemented multiple times in multiple different libraries. Can be confusing if you start out.
- Java requires a well configure IDE
- However, IDE Support won't matter in a year or two when we have IDE integrated AI support
I guess it depends on the problem you want to solve.
For the use cases I work on I'd prefer Go for its simplicity.Can you expand on that. I'm pretty sure its still Java. There are some additional annotations that help with autowiring and object reuse, but probably affect the code less than Lombok.
Why don’t “enjoy” the simplicity of assembly then? (Not trying to be sarcastic, just I always felt that this logic is flawed. Especially that java is a very simple language, with very few concepts. If you don’t like, you absolutely don’t have to use metaprogramming like Spring)
> Why don’t “enjoy” the simplicity of assembly then?
Now thats just stupid.
Java will always be a mess of layer of abstraction and magical / heavy framework.
Edit: nvm. You can get static muslc binaries with graal https://www.graalvm.org/22.2/reference-manual/native-image/g...
Eh, a billion dollars isn't too bad.
Languages are very complex, you will not get a good picture of their overall strengths and weaknesses if you limit yourself to a very zoomed-in view, like comparing a few features of the languages themselves.
As an example, consider the digital camera market vs the cameras in smartphones.
The digital camera is a clear winner! So many features. The quality of photos taken is objectively better in every metric too.
And yet the digital camera market is dying, completely killed by smartphones.
But why, the features are cleary better right?!
Because we're looking at it wrong, we're tunnel-visioned on the "camera" part. We need to look at what actually matters.
Thankfully, that's simple to answer and is the same for every product in existence. All that matters is the user experience.
Nobody wants extra features on their camera, nobody wants a camera in the first place, nobody wants to take photos either. What people actually want is to preserve the moment of their first born child taking their first steps. The technology is irrelevant as long as it lets the user do what they really want.
Why would I want a digital camera when my smartphone has a decent enough one that is effectively free and always available? A low quality camera on you is infinitely more valuable than the professional camera at home.
Programming languages are no different, just that "users" in this case are programmers, which seems to be confusing to some.
What is the developer experience of using Java vs Go? - that's the real question you need to answer to get to the bottom of this.
Start a new project and write some code in both languages, compare the experiences. Be wary of biases, if you have pre-existing experience in one of the languages it's going to shadow your judgement (the curse of knowledge). Pay attention to aspects of good design - how many pointless decisions do you have to get through before actually shipping code?
Go eliminates whole areas of pain points:
- Dependency management? Go modules.
- Tests? Built-in.
- Code formatting? Built-in autoformatter.
- Compilation time? As fast as it gets.
- Distribution? Single binary.
- Writing code? Minimal ceremony, just make a function.
- Performance? The idiomatic code you write will naturally perform well due to value types, explicit pointers, a culture of straightforward code with no needless indirection. If you need to optimize, the profiling tooling is great and the optimizations themselves straightforward due to intuitive language and GC semantics - just reduce allocations.
- GC? A single implementation, good enough for 99% of cases. Two whole knobs available if you really need to tune it. Performs well due to the language not getting in its way.
- Standard library? Excellent, good balance between batteries-included and bloat. The built-in HTTP server is suitable for 80%+ of workloads.
- Concurrency? Core to the language - syntactically supported green threads. The entire language and its ecosystem are built with it in mind.
- Linting? Community-made linter runner with a curated list of good linters.
- Found a bug in a library? No problem, the library is written in straightforward Go, the same flavor of Go you've been writing. It's of course autoformatted as well.
There you go, a single go-to solution to each problem. The language's designers have gotten all the pointless details out of the way for you. You get to focus on writing code.
Now compare the above points with Java.
Go was designed for developers, with the same philosophy Steve Jobs designed Apple products for users. Java wasn't. Simple as.
[1] https://www.reddit.com/r/rust/comments/xrrjec/virtual_thread...
[2] https://www.open-std.org/JTC1/SC22/WG21/docs/papers/2018/p13...
Also, the cases studies in that paper are platform-level when I believe the language has to be involved; as a runtime has more information when resuming to a suspension point. The paper even acknowledges Go as a successful implementation although with C-compatibility call overhead caveat. In the Java world, the vast majority of programs stay in the Java language. So I'd say that paper would list Loom as the best implementation (And maybe revise their recommendation. It'd be useful to have that author's opinion of Loom in 2023)
The author has a 2 volume, longer book as well if you want more details.
However, something important to remember is that UI frameworks generally require most or all UI interaction to occur on one logical thread. This is because making all the UI code safe for calling from multiple threads would require tons of work, and likely require one massive lock that serializes most ui compontent method calls, or tons of smaller locks. And that is just for the minimum of safety. UI work has tons of implicit state.
User level logic would also likely need additional locking, since even if each method call to a control is thread safe, if you want to perform some kind of conditional action that does not already have some dedicated method, that would pose a problem without some form of synchronization code.
One advantage of the async-await approach in UI scenarios (where continuations always get run on the UI Thread) is that you know that between two awaits, no other code will be running on the UI thread, so can avoid synchronization code. You know if you read a value from a control and then conditional call a method on it, you don't need to worry about racing with another thread. Now whenever an "await" occurs, potentially arbitrary other UI code may have run in the interim, so you may need to reverify the state of things. But this is still much simpler to handle than possibly being preempted at any time, or having another thread literally changing things in parallel.
Green thread based approaches like Virtual Threads do avoid much of the complexity of async-await, but they often do lose those sorts of advantages. Even if the green thread approach has guarantees about the yield points (e.g. only has cooperative yields which only occur when certain specific methods are called), you still lose the ability to locally reason about where those yield points may occur, since any function/method call could potentially call one of those yielding functions, or call something that calls one of them, etc. The only way to regain that info is to "color" the functions again, which was the whole thing peple are trying to avoid with green threads in the first place.
I read about the approach without async/await, but honestly I don’t really get it. Seems to be very dangerous to me, if you give up control about scheduling. But sure, async/await is completely viral and it is everywhere now. Most functions tend to be async.
Not really. Async/Await is mostly about syntax. Transform callback-hell into something that looks like linear code. The underlying threading model doesn't really matter for that.
What is the problem here? Just the per-virtual-thread memory consumption by the variable when it is used (which would be expected)?
Using a ThreadLocal is (usually) a code smell. It's optimized / supposed to be used with just a few threads, not millions.
However, if suddenly you have a million threads then this optimization doesn't work anymore. Sure, the concept of ThreadLocal still works, but in practice you'll end up creating a million of these heavy objects - something you wanted to avoid!
Or is this just an incorrect headline?
I don't think that it's the case for C# task, which also appear to be an higher level construct.
Java verbosity is alive and well.
func CMMetadataFormatDescriptionCreateWithMetadataFormatDescriptionAndMetadataSpecifications(
allocator: CFAllocator?,
sourceDescription: CMMetadataFormatDescription,
metadataSpecifications: CFArray,
formatDescriptionOut: UnsafeMutablePointer<CMMetadataFormatDescription?>
) -> OSStatusBut really, who cares? Who the hell types out the whole method in this day and age? Type the first few chars, up arrow, down arrow, tab. Jump around with vi bindings. I have not written a full line of code in well over a decade.
Although Apple deserves it since their IDE really blows compared to say Idea.
Java API stuff hasn't ever been a huge contributor to verbose code. Yeah I guess some of java.io's stuff isn't great, but the worst AbstractFactoryDelegateImplFacadeProviderVisitor gore was always in the application code.
I guess that will be the biggest impediment to adoption of new features.
Funnily enough, the first versions of Java did not support OS threads and only had green threads for a while. Supporting real threads was a big deal at the time as it allowed you to use more than 1 processor. Of course, processors were single core at the time and most computers only had one of those anyway. Java 1.1 laid the foundations for proper multi threading and green thread support was eventually removed with Java 1.3. With Java 1.5 we got the java.concurrent package which enabled doing more complicated things with locks and other synchronization primitives that were a bit less primitive & brittle than using the synchronized & volatile keywords. That includes implementing green threads on top of real threads. Which is what frameworks like vert.x and others have been doing for ages.
So, in a way we're coming full circle here with virtual threads re-using the thread API, which in turn reused the original green thread APIs in Java 1.0.
Since Java uses very little FFI, it will benefit greatly from this automagical “no more blockingness”, of course only when there is some other available work in the meanwhile. Servers are the best fit for that.
In a typical thread pool if you schedule more long running tasks than you have worker threads, you either have to queue your tasks waiting for a free worker or you have to spawn a new worker. If your tasks are CPU bound and you do not care about latency/fairness, queuing is what you want. If your tasks might do blocking operations, you might underuse your CPU, or you simply want more fairness and not simple FIFO execution; in this case you can spawn new threads but OS threads are costly to spawn and schedule beyond a certain number. Virtual threads are simply cheaper threads as the scheduling is done in userspace and the JVM can in theory be smart about it.
(But it's awesome Java has them now too, other languages getting the feature earlier doesn't really devalue it.)
Virtual threads mean that a blocking thread can yield to any other non blocking thread seamlessly and with very little overhead. .NET Tasks cannot do this as far as I can tell.
.NET might indeed have a virtual thread abstraction and if it does you could of course implement the Task abstraction on top of either virtual threads or OS threads, but what you linked to is not a proof that it does.
(As a note, cooperative scheduling also requires a runtime - Rust might not "have a runtime" by default but you need to opt into one to use async.)
And clearly, adding this feature to Java is going to be like putting a web browser in Windows 95
This means Kotlin's functions can become colored [1] which is a problem.
This said, I'm fully expecting Kotlin to react and pass on this feature to the users. Maybe after figuring out how to deal with Android.
[1] https://journal.stuffwithstuff.com/2015/02/01/what-color-is-...
Goroutines can wrap code you don't want to block callers, but in practice that get hidden behind APIs that return channels.
int someFunc() {
doA();
doB();
byte data[] = readFromSocket();
return doC(data);
}
int callerFunc() {
doX();
System.out.println(someFunc());
doY();
}
when we invoke the callerFunc() in a virtual thread, it executes till the potentially blocking socket reading, creates a callback or future -like object containing doC(), System.out.println(), doY() - such object is called a "continuation", see "continuation passing style". Then the socket reading is iniitated in an async way, with continuation registered as a callback to be invoked upon completion. Then the native OS thread is freed to do any other work.In javascript approach we would need to manually mark some places `async` and use `await`.
@async
int someFunc() {
doA();
doB();
byte[] data = await readFromSocket();
return doC(data);
}
@async
int callerFunc() {
doX();
System.out.println(await someFunc());
doY();
}
So async / await is a poor man's continuation passing style.
The need to differentiate between 'async' and non 'async' code makes it more difficult to refactor or mix code between 'async' and non 'async' domains.Some people argue that it's better to have the explicit distinction. I personally don't see benefits of this.
* FFI has to be pinned, so it will block
There is more to async/await than simply keeping os threads unblocked, they also provide a mechanism for keeping callers unblocked and synchronizing async contexts(parallel or concurrent).
Is Loom addressing this need? Otherwise it's Goroutines(or any green threading solution) without channels and select. The ecosystem will fracture around solutions to address the boiler and future chaining pains.
var executor = Executors.newVirtualThreadPerTaskExecutor();
;; or, for old Java
var executor = Executors.newSingleThreadExecutor();
Future<Integer> f = executor.submit(someFunc);
What is relevant in the new Java VirtualThreads and Javascript async / await
is the possibility to write simple synchronous code, with the performance
similar to callback-based asynchronous code.In this case, I’d love to hear more from experienced Java developers, with existing code-bases, who have tested these new virtual threads out.
Virtual threads isn't just something to "enable". You do need to adapt existing codebases somewhat e.g. use of synchronization.
The upcoming future is likely a mix of reactive and virtual threads where appropriate. Virtual threads is still very good for short lived tasks.
> The JDK can now run up to 10,000 concurrent virtual threads on a small number of operating system (OS) threads, as little as one
OK, but why bother with virtual threads if the JVM could just magically decide to run all my virtual threads on one thread? I guess "efficient" in this context doesn't mean "fast". I want my code to run on all available cores, and not be hobbled by a JVM that decided to hate me today.
If you have 10,000 threads blocked on a sleep (or I/O), then there's no reason to run them on more than one code. In fact, they won't usually be running at all.
This is the use case. It's less about giving the VM a new way to 'hate you today', and more about telling the VM when it can save resources and share system threads.
Edit: There is some potential for unexpected downside to the extent that this introduces a new scheduler for the virtual threads. If every thread is an OS thread, then it's the OS scheduler that controls when they run. With virtual threads, I assume the scheduling policy (deciding when virtual threads get time on OS threads) is controlled by a JVM scheduler that may or may not be as good at making the choices you'd like. But probably best to assume it won't summarily drop everything on a single OS thread and call it a day.