Rendered at 18:01:23 GMT+0000 (Coordinated Universal Time) with Cloudflare Workers.
moomin 1 days ago [-]
C# dev here: it’s amazing how different this is from a Microsoft release. First off, Oracle are doing versions at approximately twice the cadence. But also, and I’m guessing this is a function of the much larger Java audience: things rarely get two preview versions in a proper release. Updates in beta versions, yes, all the time. It also feels like Microsoft are bundling a lot more into the platform and leaving less to the community. Again, probably an artifact of the different sizes of the communities. Also, the page reads like an open source “We’re finished, we’re tired.” announcement rather than the razzmatazz of a Microsoft release.
kllrnohj 1 days ago [-]
> First off, Oracle are doing versions at approximately twice the cadence
Is that actually a good thing?
But Oracle started playing a game, for better or worse, where they decided to couple the "language version" with a single specific runtime's release schedule. For example, in "Java 27" there are exactly 0 language changes and 1 minor feature addition to the TLS library.
Everything else is OpenJDK runtime internals which don't impact the language or how you use it. So if you don't use OpenJDK (such as if you use Oracle's other runtime, GraalVM), then Java 27 basically doesn't even exist at all. Skimming the past couple of C# releases, it doesn't look like Microsoft is playing that game, so the release cadence will of course be different.
dwaite 13 minutes ago [-]
Historically, Java had a real habit of delaying new major versions for years as features hardened. Some of those features even lost relevance before their first shipping version.
So now they have a precise release cadence that features can fall into. If it is a large feature, it better get worked in incrementally (via feature previews) because it is unlikely to be able to land completely within the release window.
One could pessimistically say the faster release cadence partially serves to provide more opportunities for extended support revenue, though.
pron 1 days ago [-]
The Java language and runtime have been co-designed as a unified platform for many years now. Virtually every significant feature has language, library, and VM people working on it, and we often don't even know when we start how much of the feature would be in the language, library, or VM. Consequently, there is no "language version" or a "runtime version". There's only a platform version, which is defined in a single spec approved by the JCP (https://openjdk.org/projects/jdk/27/spec/). This also makes things easier with regards to compatibility and evolution.
kllrnohj 1 days ago [-]
> There's only a platform version,
And of the 4 non-preview JSRs in the Java 27 release, only 1 of them is actually part of the "platform version".
The other 3 are strictly changes to Hotspot internals with no platform involvement at all. They did not change any aspect of any Java platform in any way whatsoever. That is what I'm referring to. I'm not referring to the fact that the core library, language syntax, and runtime specs are all part of the same version. I'm referring to the fact that Hotspot specific behaviors and adjustments are also branded as being part of the platform release.
Like there's no Java 27 platform spec that says that G1 is the default garbage collector. That would of course be an absurd platform spec change. But that is still somehow a "feature" of the Java 27 release according to Oracle.
pron 1 days ago [-]
Right. There's a "Java SE" (platorm spec) version, and a JDK version that corresponds to it, but not everything in the JDK affects or is dictated by the spec.
BTW, Java is developed "code first", which means that we first work on the implementation in OpenJDK, and then extract the relevant spec changes from it.
> But that is still somehow a "feature" of the Java 27 release according to Oracle.
It's a feature of the OpenJDK JDK, which is, indeed, the Java implementation done by Oracle (with contributions from others). The language is very careful, as you can see in the announcement: "JDK 27, the reference implementation of Java 27". The Java SE 27 spec is here: https://www.jcp.org/en/jsr/detail?id=402
jonenst 23 hours ago [-]
I wish there was a canonical write up on the governance of "java" and its history, it has changed a lot over the years (not just once I guess) and has a lot of fine prints. I find it hard to understand the hidden reasons and behind-the-scenes conflicts/compromises. There could probably be a whole book about this I guess.
flossly 20 hours ago [-]
One of the more prolific opensource projects, but apart from the JEP process not very open access (which is a fair choice the contributor('s employer) can make).
I'd read it.
okeuro49 11 hours ago [-]
Read the small book by O'Reilly "Java the legend".
hyperpape 1 days ago [-]
You’re only counting JEPs, which are only for more involved features. There are lots of changes to the JDK apis that are used by other runtimes. See, for instance: https://javaalmanac.io/jdk/27/apidiff/26/.
Admittedly, the terminology here is almost designed to be maximally confusing, and I’ve never read a good post that laid out how everything relates.
kllrnohj 1 days ago [-]
Fair, although even there I don't know if I'd call that "lots" at just 23 added or modified methods that aren't in preview
hyperpape 1 days ago [-]
There are also bug-fixes and performance improvements that are not going to show on the page I linked.
I do think it’s plausible this is a smaller release. Not that this was the real point of the discussion, but I think it’s still just a good idea to have more than one release a year. It keeps things moving smoothly, and lowers the cost of missing a release, which has beneficial effects.
flakes 1 days ago [-]
I started programming when Java 6 was relatively new. Back then it was about 3-4 years in between releases. Although I don’t write much Java any more, I’m happy to see changes shipping more frequently now.
cogman10 1 days ago [-]
There's not really a second C# runtime. Java has several, some based on the Openjdk, but a few that are completely new like OpenJ9 and Graal.
The closest C# has is mono.
This sort of thing is bound to happen with that situation. Heck, it happens with C++ whenever a new C++ version comes out. Some C++11 features took years to make their way into all the compilers.
vlovich123 1 days ago [-]
Graal is based on OpenJDK. OpenJ9 while using a separate JVM and JIT leverage the openjdk class path as well as the build environment and various other things.
I don’t think there’s a single alternate implementation that doesn’t leverage a good chunk of openjdk somehow
samus 13 hours ago [-]
For the simple reason that class files with JVM bytecode are the standardized intermediate representation. Therefore, duplicating the frontend is wasted effort.
pjmlp 11 hours ago [-]
Up to a point.
Embedded systems versions tend to have their own ways, which is why despite everything Android using Dex isn't a first in the Java ecosystem.
samus 10 hours ago [-]
IMHO, Dex is a historical artifact. In the past it was thought that the format provides benefits for JIT compilation because it's register based, which turned out to not be case.
pjmlp 10 hours ago [-]
Lots of "improvements" on Dalvik over J2ME was Google's marketing to sidestep Sun, speaking as ex-Nokia, coupled with the experience of Java on Symbian devices and Sony Ericson.
All these years afterwards it quite clear that there is just similar fragmentation, and implementation differences between all OEMs selling every kind of devices, and as you say the format doesn't really provide that much benefits.
What ART has going for it, are all the improvements they started on Android 7 and later, by having a mix of handwritten interpreter in Assembly, JIT compiler with cache, AOT compilation with the device on idle, and sharing of PGO metadata via the PlayStore across devices.
Ironically Windows Phone did it first, with MDIL on Windows Phone 8 followed by .NET Native on Windows Phone 10, using compilation via the Windows Store, but Microsoft fumbled the delivery.
3 hours ago [-]
pjmlp 12 hours ago [-]
PTC and Aicas for example.
1 days ago [-]
saghm 1 days ago [-]
I didn't read the parent comment as being particularly positive in their description; it didn't sound like it was being stated as a good thing to me.
PaulHoule 1 days ago [-]
One difference I think about is generics. Java and .NET bolted generics on to an existing system. Java used type erasure in such a way that a List<OfThat> is really just a List. Type erasure has a lot of limitations. If I am coding in Java for days I never get into trouble with it because I know how to color in the lines. But do some balls-to-the-walls metaprogramming and then it is annoying that you can't write
Expression<Integer> add(Expression<Integer> a, Expression<Integer> b);
Expression<Double> add(Expression<Double> a, Expression<Double> b);
because in the end they both look like
Expression add(Expression a, Expression b)
we have ways to cope, like unerasing the types by rewriting the names... And now you've got a reason to do balls to the walls metaprogramming! Similarly if I do a lot of C# or Scala or something I will get into the habit of doing things I can't do in Java.
.NET on the other hand did not keep backwards compatibility, so a List is not a List<X> so .NET had a schism where some API functions use generic collections and others use non-generics which was annoying in its own way.
Something like that is how all methods in Java are virtual whereas methods in C# may or not be virtual. All-virtual is probably not the best for performance, but it is simple for understanding. You never have to think "do I make this virtual or not?" or think "is that method virtual or not and does that have consequences for how I use it?"
That is unfortunately more about adding another edge case for the non-reified Java generics system where it has been forced to partially reify, rather than really addressing the larger complaint.
AgentME 20 hours ago [-]
That's about letting you do Expression<int>, not about removing type erasure or otherwise allowing overloads based on type parameters.
gf000 13 hours ago [-]
It's one end of the same problem.
int is not an Object, so just erasing is no longer a valid approach, you need to specialize the class/method itself to use int-specific byte code.
PaulHoule 1 days ago [-]
I am looking forward to it, and many other things planned for Java!
pjmlp 1 days ago [-]
Polyglot dev here, that uses both ecosystems, Java since 1996, .NET before it was announced to the public in 2001, only available to selected Microsoft partners.
A big difference between both ecosystems is that the Java world is like C and C++, even though Java isn't defined by ISO or ECMA, since Sun days the main implementation is only a reference, there are official documents for everything, and there is a plethora of implementations, with various kinds of JIT, GC and AOT approaches.
You can pick the real time versions for embedded from PTC and Aicas, the cloud first from IBM and Azul with finance markets in mind, the Android cousin, the various implementations for M2M gateways, copiers and phone dashboards (Ricoh, Xerox, Cisco), IoT with microEJ, and many more.
Whereas Microsoft hardly cares about ECMA nowadays, most of Mono/Xamarin is gone replaced by Core CLR and modern .NET, .NET Compact is gone, community maintained and so on.
That alone, regardless of the languages on top of JVM, or CLR, makes a big difference on the audiences when one silos themselves to a single ecosystem.
bel8 23 hours ago [-]
interesting. So less specialization on one side but also less fragmentation.
dcminter 1 days ago [-]
Java releases used to be glacial multi-year affairs. Lots of discussion of features that then missed the release train and you knew they'd not be with you for another multi-year period.
They made a conscious decision to switch to a regular six-month cadence and it's been all the better for it. The preview-mechanism has been terrific there too, allowing half-baked features to be aired without absolutely committing to something that turns out to be flawed.
Edit: Ninja-ed by Romario77's sibling comment :)
petilon 1 days ago [-]
> Microsoft are bundling a lot more into the platform and leaving less to the community.
This is a good thing. In Java everything has multiple community offerings, so before doing anything you have to evaluate the community offerings and decide which one to go with. If you go with the wrong one you may end up having to switch at some point, and that can be painful. This happens so often that most of the time spent when using Java is doing these evaluations and comparisons. With C# you just use the one built into .NET platform. Saves a ton of time.
PaulHoule 1 days ago [-]
Sometimes the thing built into the .NET platform is great, sometime it is just crap but developers will use it anyway and it sets back the ecosystem.
There is this division of labor between systems programmers and application programmers and often we think systems programmers are better because they know more about algorithms and data structures and compilers and assembly language and such. On the other hand, application developers understand how to reconcile the mental model of managers and employees and customers with computers, reality and common sense and, once they get experienced, see the commonalities between all the run-of-the-mill bizapps that we are coding all the time.
Application programmers do a lot better at applications framework than systems programmers and make things like Ruby on Rails and Spring. Systems programmers make terrible things like ASP.NET MVC (I worked out a way to do MVC with ordinary ASP.NET, why couldn't they, with access to the platform internals?)
moronicles 21 hours ago [-]
[dead]
throwaway91033 1 days ago [-]
If someone wants to create a project by assembling bits and pieces from different open source products they can, but many just go for Spring (Boot) and call it a day.
All of my projects are based on Spring and I don't really have to look outside of that ecosystem. It almost acts as an aggregator of different open source solutions and often works by abstracting the functionality so that differences are not that big. I recently switched messaging providers and didn't have to change much of my code.
rwyinuse 1 days ago [-]
Yep, with Spring Boot development is so easy, and even decade old projects are mostly easy to upgrade. I don't have experience from C# or .NET development, but at least compared to Python and especially JS ecosystems it's so much better.
whizzter 1 days ago [-]
Generally fairly painless in recent years, you need to divide the .NET timeline into original timeline ( .NET 1.0 -> Framework 4.8 ) and "core" lineage/timeline.
The Core (smaller, but also properly crossplatform) project begun in 2014, fairly major rewrites with breaking changes in the new releases up until 2019 (.NET Core 3.1) and 2020 (the 5.0 release that became the official major "unification" with most parts of Framework having newer alternatives and being "complete" even if 6.0 and 7.0 patched holes).
Projects started with core 3.0/3.1 in 2019 have a pretty easy and clear upgrade path without major breaking changes up until today.
It's not JS/Node volatility, and the cleanups in the language/runtime were well worth it in hindsight (still maintaining old 4.8 applications running under IIS), also 4.8 is still nominally supported so there's no immediate stress in upgrading (There are better semantics today, but with huge projects those semantic differences, mainly no lazy-loading by default are a risk).
hirvi74 1 days ago [-]
> Python and especially JS ecosystems it's so much better.
That's a pretty low bar to beat.
PaulHoule 1 days ago [-]
10 years ago I was working at a place that was building Python systems that had dependency graphs too complicated for pip to handle. I was able to solve the problem for my system with a "wheelhouse" system that could compute a list of wheels that could be installed to build it but the confidence of my team in Python had flagged.
I had a sheaf of notes about the problem and figured out the math to build a proper dependency resolver for Python and tested out a lot of ideas such as being able to use http range requests to get the metadata out of wheels on PyPi without having to download the whole wheel.
The problem I had no solution for though was "how to stop developers from trashing the environment that the dependency manager runs in." The data scientists I worked with had an astonishing target for wrecking anything at all. Myself I would have my poetry's environment got bad for reasons I didn't understand every few months ago.
I also found the Python community just didn't care that pip didn't really work right. The most seductive form of blub is "I can accept using things that fail intermittently." I got a job coding Java and Javascript and never built the package manager.
Then uv came along and managed to sell itself as "crazy fast" which did connect with people more than "correct". Written in rust, uv would have beaten my system in the fast department, and since it is a binary, there is no way anyone can screw up a Python it depends on -- as I see it, both technical and marketing genius!
robertlagrant 6 hours ago [-]
Before uv came along, pipenv, Poetry, and (much older) Conda all were trying to solve this problem. It's a huge problem for Python that not all languages experience, because Python packages can contain all sorts. At one point if you wanted to install Scipy you had to drag in (and compile, if I remember correctly!) Fortran, of all things[0].
Well Java has a kind of xenophobia that really resists bringing in foreign code but Python is at it's best when it accesses wrappers around C and Fortran.
I used conda back in that period, it had a correct solver, and it was easy to manage my own packages, but it was slow in the technical sense of "it takes forever to build an environment" and slow in the business sense in that you got something curated which was not always the greatest or the latest but would, back in the day, "just work." Actually you could vendorize any software you need and have your own conda wheels, like I made wheels with different versions of CUDA drivers which are just DLLs so you could be running models with two versions of tensorflow that required two different versions of CUDA and never have to touch the NVIDIA installer.
But today it is a more "just works" experience to use PyPi instead of conda so I don't use conda.
Poetry was a big improvement over pip but I don't believe the resolver was 100% correct (like from looking at the source code) and performance was not that good, not so much because it was written in Python but because it did not have a proper cache, did not exploit concurrency. The Python way would be to use a world class SMT solver for the CPU intensive bit but when the bits hit the bus Rust is better at exploiting concurrency.
So I am happy to have uv.
MBCook 1 days ago [-]
Yep. Anything else is probably in Apache Commons somewhere.
pjmlp 11 hours ago [-]
Which is why there are so many complaints about doing FOSS in .NET, as many companies won't use anything that isn't blessed by Microsoft, and there is an history of Microsoft cloning FOSS projects.
bob1029 1 days ago [-]
> If you go with the wrong one you may end up having to switch at some point, and that can be painful.
> Also, the page reads like an open source “We’re finished, we’re tired.” announcement rather than the razzmatazz of a Microsoft release.
I think this is because the JRE/JDK upstream releases are a bit like Linux kernel releases: all the major first-party feature development goes on in subprojects that maintain their own "living forks" during feature development, with the teams on these features doing PRs against the fork's own "main"; that "fork's main" having its own subproject maintainers who ensure a mess isn't made of it; and then those maintainers eventually polishing up that fork-main into a single big one-shot PR to upstream once the feature-as-a-whole is ready.
(Compare/contrast: the Linux kernel's mm, rt, and kvm feature development efforts.)
Because of this, the top-level "project maintainers" (i.e. the people who decide what gets merged into upstream main) aren't really the same people as these subproject people who care deeply about these new features. They want to ship stuff people want, but they personally mostly deal all day with requests to merge 1. small bugfixes, and 2. features so small that no JEP is needed.
But then, every once in a while, they have to deal with a request to merge one of these huge subproject upstreaming PRs. And sure, it's already heavily reviewed by the subproject's maintainers, who they trust. But they do still have to audit it and learn it and create a stabilized release path for it. "Handover" stuff. And that's tiring!
So, given that the toplevel project maintainers write the release notes, I'm not surprised they come off as weary about releases.
(That being said, for purely PR reasons, the toplevel maintainers could ask the subproject staff to contribute their perspective to the release notes of a release that merges their work? But this could also just-as-well be a separate blog post—which would probably be better for sharing. I don't think I've ever seen a centralized Java blog [is there one?] but I think the subproject teams do tend to have them.)
Romario77 1 days ago [-]
the release cycle time was a deliberate choice.
Java tried to do fairly large updates and sometimes the release cycle would be very unpredictable as things would slip and take much longer than anticipated.
So to make it more predictable and to keep updates coming they switched to 6 months cadence with long term support (LTS) every two years.
This I think is a pretty good way of doing things, makes people who plan things figure out how to split feature development into these 6 months cycles, it made JEPs more granular and I think it made project Valhalla possible, if they tried doing it the old way it would never happen.
Splitting things in small chunks clarified what needs to be done and the path forward. It still takes very long time, but doesn't cause big incompatible changes and I think overall Java has good progress without being stalled.
dcminter 1 days ago [-]
I remember Dolphin being a particularly painful one, and as I recall it ended up with some weird compromises (wasn't erasure supposed to avoid needing to update the bytecode format, but then annotations required it anyway? Something along those lines)
Romario77 23 hours ago [-]
yeah - they took forever, one reason being that Sun was in financial troubles and then acquisition took a long time.
Second was about licensing and Apache Harmony.
So, eventually they dropped most of the big things that were planned - Project Lambda with closures, Project Jigsaw with modularisation, Collection Literals. They eventually came back, but took a while to implement, so it was a prudent decision to make.
theandrewbailey 1 days ago [-]
I never thought of Java being the 'move fast ~and break things~' alternative over .NET, but Java has a faster release schedule to get features out sooner.
leapingdog 1 days ago [-]
I don't think modern Java is a 'move fast ~and break things~' environment. It's a comparatively stable platform with an enviable focus on backwards compatibility. The maintainers have talked about "last mover's advantage" when it comes to introducing new language features. Java has a checkered history when it comes to novel programming language features, so I think this is good.
Granted, the maintainers are more inclined to deprecate and remove parts of the API than has historically been the case but it is mostly obsolete things like applets. And you may need to keep a close eye on runtime flags and their effects.
MaxBarraclough 20 hours ago [-]
Move fast and break things does not describe Java well at all.
Their process is still very deliberate, they go to some lengths to avoid getting it wrong when they add new features to the standard. New features have to get through their preview phase successfully before becoming final. [0]
They're also pretty committed to not breaking existing source code or bytecode.
One of the releases was that, though - either 9 or 11, with the package reorgs that broke everything. OK... it wasn't fast.
But Java 8 was stable (as in APIs, not judging its quality here) and since then it's gotten good again.
madduci 1 days ago [-]
Seriously, how many are using always the latest releases of Java instead the LTS ones? With LTS ones you have ~2/3 years between the versions.
throwaway91033 1 days ago [-]
We often use the latest version of Java at my work place. We haven't had any issues with upgrading, so there's no benefit of waiting for an LTS. There's no big process behind it either. The developers just quietly change the version as part of keeping the project up to date (BAU)
It may be that we are shielded from edge cases because we are based on Spring, which is probably the most tested piece of software before new versions of Java are released. But it's my impression that the risk of upgrading to a new version of Java is not the same today as it was in the past. The only advantage of an LTS is that it is supported longer, so that you can postpone the upgrade if you really want. It's not as if the intermediate releases are inferior or less safe.
varikin 1 days ago [-]
At my last job, we only used LTS in production. Upgrading Java was always a long process, but that's more due a legacy monolithic app across thousands of servers.
You can almost think of the LTS releases as a major release and the non-LTS as a minor release, so really this could be 25.2. The current Java release schedule is to maintain a consistent and predictable release cadence instead of pushing big new features every 6 months.
gf000 13 hours ago [-]
It's pretty much the opposite.
A fixed release schedule makes development more relaxed so it can be properly done, no need to rush for some release date.
If it's not yet ready, there is 6 more months to get it merged.
troupo 1 days ago [-]
> Java has a faster release schedule to get features out sooner
While still being behind on most features?
MBCook 1 days ago [-]
To misquote Bart Simpson:
> Let me get this straight: we're behind the [other languages] and we're going to catch up to them by going slower than they are?
Gotta go faster if you ever wanna catch up. However, Java is also purposefully slow. Everything is extremely considered. And while it means it takes a while before you get a feature it tends to be pretty good.
za3faran 24 hours ago [-]
Which features exactly? Java got exhaustive pattern matching before C# (through sealed interfaces), it has switch expressions, multi-line strings, green threads and structured concurrency, is getting value types, and even type classes in the work.
jayd16 3 hours ago [-]
Even in the small list of features in your retort you had to switch to future tense.
joe_mwangi 22 hours ago [-]
Typeclasses caught me by surprise. Smart move by the java team.
troupo 21 hours ago [-]
Granted, I havent' used either for a couple of years, so my knowledge is a little rusty. Yes, some of them are "just syntax sugar", but man oh man does it make C# such a pleasant language to work with.
Used to work at a company which had services both in Java and C#, so some of Java's decisions or indecisions felt like pain points when switching between the two:
- Proper IEnumerable with proper iterators that in turn enables Linq (but in general permeates everything and is insanely easy to use and build upon). E.g. building an async service that behaves like an IEnumerable? Implement two methods.
Collection is halfway there, but I honestly cannot remember what was irking me about it in comparison to C#.
- Properties. Yeah, yeah, sealed classes, records and all that. Often you still need plain old classes.
- object initialisers. Which makes constructing anything a breeze. And on top of that you don't need manual .of methods for anything Colleciton-like if it'sa an IEnumerable.
- extension methods.
- named and optional arguments in functions
- null coalescing operator
- generics over primitive types (unless it was already implemented, I remember seeing a JEP about it)
- async/await. Yes, I know: different approaches to concurrency and all that. A lot of unnecessary verbiage could still probably be hidden behind a friendlier syntax.
- (sadly impossible in JVM to type erasure, only including this because I remember needing it many moons ago) generics metadata in runtime
- .... definitely a bunch more I don't remember at this point ...
gf000 13 hours ago [-]
At the same time, all this syntactic sugar makes the language's surface area gigantic. Like it's almost C++-level complex, and then you would have to properly understand all the interactions between this matrix of features.
That's absolutely a valid language design and many people prefer that, but I personally prefer a bit smaller language with a bit more IDE auto complete, but where you never have to think about what exactly does a line do.
(And then there is also Go that falls off the other edge of the cliff with useless if err checks spamming the code making actually functioning error handling hard)
troupo 12 hours ago [-]
> a bit smaller language with a bit more IDE auto complete, but where you never have to think about what exactly does a line do.
If you need IDE to autocomplete, then you definitely spend more time to understand what a line does ;)
jayd16 2 hours ago [-]
Good list.
- Value types is a huge one to add if we're looking at what is actually in the wild.
- Scopeless `using` declarations are nice for RAII like behavior.
- IMO C# builds are actually way way nicer than Java. Sln and .csproj files and nuget are actually a lot easier to deal with than javac/ant/mvn/Gradle. Maybe that's more a .NET thing than a C# feature.
samus 12 hours ago [-]
> - object initialisers
Probably not a good idea since they break encapsulation by exposing internals of the class. There is work on withers, which should make defining builders far simpler.
> - extension methods.
They make code harder to understand. If they ever come they would have to be declared at the top of each source file.
> - null coalescing operator
Maybe we'll get it, maybe not, but they want to first introduce proper nullable types, lest there is a risk of painting themselves into a corner.
> - async/await
There is a fork in the road, and Java has gone into the direction that leads to virtual threads and Structured Concurrency, for the simple reason that there is no simpler syntax than plain old synchronous code.
> - ... generics metadata in runtime
There are plans to add a kind of reified generics, so maybe we'll get it.
troupo 9 hours ago [-]
> Probably not a good idea since they break encapsulation by exposing internals of the class.
> for the simple reason that there is no simpler syntax than plain old synchronous code.
But it's not synchronous code, is it? It's easily dozens of lines wrangling Futures, and Thread initialisers, and Executors, and...
Java always opts out for "let the developer handle all the complexity all the time even for the simplest most used parts of the code".
samus 3 hours ago [-]
> And thousands of manual get/set functions don't?
My statement doesn't apply to mere data carrier classes. Anyway, getters and setters are an antipattern as well since one can just as well make all the fields public.
> Thousands of lines of builders don't?
With withers most of these will go away. And a class will be able to choose which things can be set, which is not the case for initializers.
> But it's not synchronous code, is it? It's easily dozens of lines wrangling Futures, and Thread initialisers, and Executors, and..
That code won't look that much different with async/await.
gf000 7 hours ago [-]
> And thousands of manual get/set functions don't?
By definition, they don't.
They are verbose and hard to maintain, but if ever in the future you would want to keep the same API surface but change the internal implementation detail, they let you.
> But it's not synchronous code, is it? It's easily dozens of lines wrangling Futures, and Thread initialisers, and Executors, and...
No, it's done under the hood by the JVM. You only ever see a blocking call on a new "thread", via debugger via everything. Best of both worlds
troupo 6 hours ago [-]
> but if ever in the future you would want to keep the same API surface but change the internal implementation detail, they let you.
So do properties in C# which object initialization relies on. With significantly less manual code, or the need for tedious builder chains and withers.
`{ prop = x }` is no more encapsulation breaking than ` .setProp(x) `, but actually makes developer experience better.
> No, it's done under the hood by the JVM. You only ever see a blocking call on a new "thread", via debugger via everything. Best of both worlds
What's Java's equivalent of
x = await someFunction()
await waitForSomeOtherFunction()
gf000 4 hours ago [-]
If the two calls are sequential then simply:
var x = someFunction()
someOtherFunction()
If you would have written
var xTask = SomeFunctionAsync();
await WaitForSomeOtherFunctionAsync();
string x = await xTask;
then it would be:
try (var scope = StructuredTaskScope.open()) { // JDK 24+ preview feature
var x = scope.fork(() -> someFunction());
scope.fork(() -> waitForSomeOtherFunction());
scope.join();
String result = x.get(); // already completed
}
za3faran 14 hours ago [-]
> Proper IEnumerable with proper iterators that in turn enables Linq
Are you referring to generators?
> Properties
As far as I'm aware, it is a deliberate choice not to implement them, and I can see their point of view.
> object initialisers
I believe the same justification applies here, it's mainly syntactic sugar, and can result in certain undesired behavior by bypassing constructors where validation can happen.
> extension methods
Typeclasses are currently being explored, which are a superior approach.
> named and optional arguments in functions
Those would be nice (at least named arguments). I can see how optional arguments could complicate things.
> generics over primitive types
As you mentioned, it's in the works
> async/await... verbiage could still probably be hidden behind a friendlier syntax
The approach they took does not need any extra syntax.
> I believe the same justification applies here, it's mainly syntactic sugar, and can result in certain undesired behavior by bypassing constructors where validation can happen.
This is mostly due language design. Java heavily relies on properties and provides no facilities for them. Hence the builder pattern instead of object initializers.
> The approach they took does not need any extra syntax.
You mean it needs 15 lines whete C# needs one? ;)
hitekker 19 hours ago [-]
> the page reads like an open source “We’re finished, we’re tired.” rather than the razzmatazz of a Microsoft release.
The vibe selects the audience perhaps.
People who are tired and just want to finish their work like the first style. People who want to do more cool work more quickly, maybe without finishing, like the second. Depends on the work, I suppose.
Phelinofist 1 days ago [-]
> Also, the page reads like an open source “We’re finished, we’re tired.”
I mean it's short and concise and there are additional resources that provide more detail. IMHO it's not a bad thing.
roflburger 17 hours ago [-]
Because there is nothing in it, that's why this release, along with majority of the recent ones drop like wet farts. How many previews of the vector API would you like?
samus 11 hours ago [-]
I read nine JEPs. Sure, some are re-Previews, but they are important since they often contain improvements from community feedback. Specifically, it would be quite unwise to finalize the Vector API before Project Valhalla. Apart from that, I'm sure that there are lots of minor visible changes that didn't get a JEP.
Anyway, not every release can be filled to the brim with new features, and people were also kinda busy whipping Project Valhalla into shape. INHO it's still preferable to stick to a predictable schedule instead of creating uncertainty in the community.
Areading314 16 hours ago [-]
> Java 27: “We’re finished, we’re tired.”
Seems about right
hirvi74 1 days ago [-]
I kind of wish C# would slowdown releases in some areas. I have not been a huge fan of some of the changes in the past year. I love the performance changes and bits of functionality here and there, but the syntax-sugar is getting annoying.
whizzter 1 days ago [-]
People complain, but most of the actually used changes are in things that continually used where I find painpoints.
Not 100% on board with the collection expression changes (I found fluent Linq chains usually more readable), but they're improving painpoints so I think it'll work out in the end hopefully.
kittoes 1 days ago [-]
Interesting take, how is optional functionality annoying? Isn't the fact that it's just sugar a huge benefit? Us old timers can simply stick to what we're familiar with.
fourseventy 1 days ago [-]
Because unless you are the only person maintaining your codebase other people in your organization will start using the cool new syntax sugar and optional functionality. So you will be forced to deal with it as it starts showing up in your codebase.
samus 11 hours ago [-]
I'm sure that there is a tool like Checkstyle that can be used to ban features.
pjc50 1 days ago [-]
I don't get this either. You can even lock the language level if you really don't want it, or you can just ignore it.
hirvi74 23 hours ago [-]
(GP here)
> Isn't the fact that it's just sugar a huge benefit?
My main gripe is that I cannot remember what is allowed and not allowed between multiple versions of the same language. On a daily basis I hop between apps versioned in .NET Framework 4.8 all the way to .NET 10. I have to constant remember, are nullable types allowed here? What about 'new(); vs. new Object();', new collection syntax, new switch syntax, new extensions syntax, etc..
Plus, I just find it obnoxious that the same thing can be written so many different ways. I can think of 7 ways to assign a new empty List<T>.
List<T> foo = new List<T>();
var foo = new List<T>();
List<T> foo = new();
List<T> foo = new List<T> { };
var foo = new List<T> { };
List<T> foo = [];
var foo = (List<T>)[];
There are probably more that I am forgetting. What irks me most is Java is older than C#, and from what I can remember, it is not nearly this ridiculous in terms of syntactical sugar. So, what is the true benefit behind all this sugar? It hardly saves any keystrokes in the age of autocomplete in IDEs.
I am inclined to believe most of the sugar is an attempt to make the language appeal to a newer generations of programmers. But I would argue features are more attractive than syntactical sugar. I believe Rust is truly impressive language. In my opinion, its syntax is uglier than sin, but that does not seem to deter many from using Rust.
troupo 21 hours ago [-]
Some of those ways come from just plain object initialisers. Which are amazing and sorely needed in Java.
That's why you get `new List<T> { };` Because it could be `new ComplexObject { <fileds and properties> }`.
Same for `new`.
Some come from type inference which Java also has.
That's why you can have `List<T> foo = new List<T>();` and `var foo = new List<T>();`
It's not really "7 ways to assign a new empty List<T>". It's "7 ways to create an object", and Java several of them, too.
samus 11 hours ago [-]
> Some of those ways come from just plain object initialisers. Which are amazing and sorely needed in Java.
They really aren't, and they are IMHO an antipattern since they break encapsulation. One might argue that encapsulation doesn't matter with mere data classes, but Java will cater to that use case by introducing withers.
troupo 9 hours ago [-]
> They really aren't, and they are IMHO an antipattern since they break encapsulation.
They don't. Java had to come up with the extremely verbose builder pattern for the exact same thing. And withers are basically the same tedious manual builder pattern, just with a different name.
With withers they will become less verbose. In the best case you'll only need to define a value type and a constructor taking an instance of that.
tancop 1 days ago [-]
Most new syntax features make code more readable. For those that don't there are company style guides and `AGENTS.md`. The C++ philosophy comes down to "if it works ship it" and I don't think you're expected to use every single new feature.
ygra 1 days ago [-]
You don't really have to use the latest C# version, though. Install the latest .NET and you get the performance improvements without usually having to change anything about your code.
_the_inflator 2 hours ago [-]
I worked at a MS "fanboy" company around 2011-2013. Highly competent guys, really Senior Devs, C#, MS SQL, as well as using graph data - with one distinction: it must be MS.
Open Source? No way. Git? No, they relied as die hard MS believers on the MS software called Team Foundation or something like that, that was integrated into Visual Studio Pro - sorry, I forgot about it, I considered it kind of bloat and outdated. Also I couldn't stand the nomenclature. A project was called "Solution" - I died inside, because this sounded like utter nonsense to me, because how do they know it would be one in the end?
While JetBrains as well as Linux quickly iterated through everything and got traction as well as a cadence that overall kind of was paced around sprint cycles that lasted two or four weeks, the company finally started to break up with project management and implemented Scrum.
As the JavaScript guy, the only one, because a customer wanted a SaaS "solution" but with static web content this wasn't really dynamic. I knew one of the founders who was a managing partner and he asked me to join as Web Developer.
Overall, all were very skeptical towards me because how could someone bet on JavaScript at the time? Well I turned the argument around and said the same about C# with its closed source walled garden approach to everything relying on MS to solve their problems with no way of giving feedback while there was no real release cycle and roadmap available - hopium and copium.
Statically typed languages for the win they said, blabla. I wasn't against static types, but did pure magic in JS, that they saw me as magician and I got some fans and I found one team mate who wanted to be coached by me on JS, Ajax and stuff.
So, there you have it. History.
I think there are pros and cons to any approach as always. Both language suffer from feature creep.
C# is still tightly knit into some products from MS and there are some backwards compatibility issues to take care of that limit certain progress and need substantial change.
Java isn't that way and was near dead and went OS. That's why they moved to the current model. Former versions were also hardly changed, have a look at everything before Java Version 12 or so.
Java wasn't community driven all the time.
So, C# has its merits, TypeScript for the win, so MS won over JavaScript ironically but only on the outside.
I shocked my MS fanboy colleagues when I really gave them a shock therapy regarding security when they mocked me with the examples given by MS why JavaScript was so bad and C# would beat it. We all know the infamous type coercion examples with mixed types, arrays etc.
So I shocked them with eval function of course but then gave them nightmares and mental overload with Function.prototype.toString and new Function() trickery.
It blew their mind, there was nothing remotely available in their world. Not introspection, nothing.
JavaScript was kind of assembler like I said. Highly flexible, you need to use modules, like jQuery did but have to build your own.
So TypeScript used exactly this flexibility: compiling to JavaScript. A metalanguage.
For the true insider, JavaScript won.
32oqa9 1 days ago [-]
It's a normal open source announcement without the corporate bullshit. No fatigue. No doom. Just facts! /s
BatchJob 1 days ago [-]
razzmatazz? Do you mean marketing lies and self aggrandizement for merely doing shoddy work?
jayd16 1 days ago [-]
Mads Torgersen's previews and such are enjoyable and upbeat, for example.
Betelbuddy 1 days ago [-]
>> Oracle are doing versions at approximately twice the cadence.
This has nothing to do with Oracle. All good that you hear from Java in the last few years, is the great community and good old people from Sun working at Oracle.
GrumpyGoblin 1 days ago [-]
Someone doesn't know their Java history. Oracle bought Java 16 years ago in 2010. At that time Sun had been working on Java 7 for over 4 years with no release date in sight. Oracle trimmed the fat and released Java 7 in less than a year. And since then has kept a regular release cadence. Sun would probably still be working on Java 7.
Romario77 1 days ago [-]
it wasn't about Oracle trimming the fat. Java 8 took 3 years to release and then Java 9 another 3 years.
They had to commit to half a year release cycles and LTRs every 2 years. Since then the releases became a lot more predictable. Whatever is not ready is not released (or is there as a preview feature).
This more agile approach is a lot better in my experience and we see that the changes made are more relevant and what people actually want.
grodriguez100 1 days ago [-]
“The good old people from Sun working at Oracle” is now Oracle as well.
aaronax 1 days ago [-]
"The Java Story" documentary by CultRepo on YouTube is pretty great. A major topic is the release cadence which some other comments here are mentioning / joking about.
Horffupolde 1 days ago [-]
Serious question: when should one use Java for greenfield projects in 2026?
pron 1 days ago [-]
These days, Java is mostly used in greenfield software that has to be very reliable, very performant, and last for many years. So it's often the first choice for banking, telecom, finance, government, defence, manufacturing control, logistics and shipping, media streaming, retail, hospitality, healthcare etc.. It's usually not a first pick for more exciting software, such as Python type checkers, JS bundlers, or TUI file managers.
throwaway91033 1 days ago [-]
I think of Java/Kotlin and Spring as a secret weapon for startups. My workplace was a startup 5 years ago and it's amazing how things just worked as expected, leaving us more time to focus on the product. We have tried a few alternatives over the years, such as a few services in Rust, but the people who implemented those have usually seen the advantage of using a stable ecosystem after a few years. It's unfortunately something you need to experience yourself instead of being told by someone else.
rohan_ 1 days ago [-]
This is quite the take - i doubt most startups building in these spaces are using Java.
pron 1 days ago [-]
First, they do. Second, most software is not only not produced by software startups, it's not even produced by software companies. Do you know how much software a bank, or a credit card company, or a telecom provider, or a car manufacturer (like BMW), or a shipping company (like FedEx), or a defence company (like Boeing), or a large retailer (like Walmart) write in house?
rohan_ 21 hours ago [-]
Wasn't the discussion about greenfield projects? Or by greenfield do you mean internal greenfield projects at existing companies that already use Java?
cromka 19 hours ago [-]
Greenfield doesn't imply startups, does it?
cute_boi 4 hours ago [-]
I used to work on bank, and their java code was very bad, although it works as they have been using same code for 30 years lol.
chasd00 1 days ago [-]
i can't speak to the others but banking and healthcare is virtually all java top to bottom. The big healthcare EMR/EHR systems are Java and every bank i've ever worked with (i use to do a lot of integrations with the big banks) was all Java. I have friends in those areas and whenever they start up a new project it's still always Java since that's where their skills are and what's on the "approved tech." list.
doublepg23 24 hours ago [-]
in my healthcare experience (claims processing, medtech) Java existed for sure but it was always as a "legacy" system they were moving away from.
nitwit005 21 hours ago [-]
Until quite recently, the reasonably feature complete open source libraries available for things like DICOM or HL7 were old C/C++ libraries, Java, and C#. That often created a choice between Java and C#. People not doing Windows based development tend not to be interested in C#.
adzm 19 hours ago [-]
Cross platform C# is certainly getting huge in healthcare and medtech recently though.
pjmlp 11 hours ago [-]
Is it? My experience from 2014 - 2018, was that C# was only used in the lifesciences software for Windows, and as wrappers around device drivers mostly written in COM.
Everything that was done on the backend side was done in Java, although there were some exceptions for .NET deployments.
So we ended up with mixed skills teams where depending on the ticket, you would be coding Java or C#.
wk_end 24 hours ago [-]
What were they moving towards?
doublepg23 23 hours ago [-]
claims processing - TypeScript, lots of ETL tools with Go.
medtech - .NET and TypeScript.
Mashimo 21 hours ago [-]
We embedded angular in our old Java client for hospitals. The user thinks he just opens a new window, but it's chrome in Java, which opens angular frontend, for our Java backend.
9 hours ago [-]
1 days ago [-]
layer8 21 hours ago [-]
From what I see, most software being created in these spaces isn’t made by startups.
pjmlp 11 hours ago [-]
If they want to target Android customers, most likely they have to anyway.
1 days ago [-]
dzonga 5 hours ago [-]
yeah the performance, reliability & ecosystem of the JVM is unmatched.
if you gonna work in a big team or need a project with lots of devs then yeah go for the JVM.
but if you're doing things on the smaller / small scale side. - just use JS/TS or python. you benefit from cheap runtimes such as Cloudflare workers.
astrodust 1 days ago [-]
Why not Go, Rust, or C#?
pron 1 days ago [-]
They lack in performance, stability (compatibility), observability (telemetry), productivity, or some combination thereof. They are chosen, of course (especially C#; Go and Rust are far behind), but not as much as Java.
misiek08 1 days ago [-]
Saying that Go lacks in those is just showing how people are making software those days. It’s just terrifying.
As to Rust - we all, hopefully, agree that it’s great language, but not for some startup making websites or Mongo based, boring backends. It’s great for the stable, system level products.
pron 1 days ago [-]
I don't know what compiler and GC quality has to do with how people are making software these days, and I don't think state-of-the-art optimising compilers and GCs are terrifying at all. Go opts for more traditional, simpler algorithms under the assumption that for many purposes they're good enough. That may be so, but sometimes workloads really are very demanding, and you need the best performance.
ssimpson 1 days ago [-]
and the costs they are willing to pay. Go/Rust just kill everything else (except maybe C++) for performance and resource needs. JVM requires so many resources just to run small apps.
pron 1 days ago [-]
Quite the opposite, and the reason is that you can't extrapolate from small programs to large ones. Low-level languages (like C++) incur some significant overheads as they grow large (because of essential constraints of low-level languages that prevent them from doing certain optimisations that matter mostly in large programs), and these are exactly the overheads the JVM is designed to reduce. In small or short-lived programs, the situation is different, because Java does have some warmup costs and some fixed memory overheads that matter when you're small or short-lived. Go's compiler and GC are pretty basic, and are certainly good enough for smaller things, but don't scale as well to high workloads. Just the other day a colleague tested Caffeine, an old and well-established Java caching library, and Moka, a Rust caching library with the same workload. Caffeine had the same latency as Moka across all percentiles at twice the throughput.
p2detar 23 hours ago [-]
I use Java every day but just to point out that your info about Go‘s GC seems out of date. They switched to Green Tea in 1.25 (I think?) - new GC that even has AVX-512 optimizations. Not sure what you mean by basic about the compiler but it‘s very fast and supports a large set of platforms. That‘s not basic to me.
We are using JDK25 and are considering rewriting parts of our product to Go because of lower memory pressure and faster startup time, i.e., cloud friendly. I actually love both languages.
pron 22 hours ago [-]
> I use Java every day but just to point out that your info about Go‘s GC seems out of date.
I'm well aware that Go's GC has improved, but the moving algorithm was designed not just to be fast for a GC, but to be faster than no GC. So Go's new GC is good - for a mark and sweep collector. But it can't compete with a moving collector (the only thing that can is arenas, which are user-friendly only in Zig).
> We are using JDK25 and are considering rewriting parts of our product to Go because of lower memory pressure and faster startup time, i.e., cloud friendly.
Java probably will never have perfect warmup, but it's getting very good - https://openjdk.org/jeps/544 - probably in JDK 28.
As for memory, I think Java's memory strategy is generally misunderstood and I've given a talk about it: https://youtu.be/xr73mR7ii9M The footprint overhead exists to compensate for CPU utilisation when the CPU utilisation is more disruptive than memory usage. The problem is that many Java developers - and I'm not blaming them - don't understand this tradeoff and how to configure the JVM for optimal resource usage, but the great news is that a solution is coming soon, too - https://openjdk.org/jeps/8377305 - also possibly in JDK 28.
So it's very likely that both of these issues will be resolved six months from today, and you'd still get to enjoy better performance and telemetry than all alternatives.
zozbot234 10 hours ago [-]
CPU utilization is a red herring. Unless you're doing heavy number crunching (which these days heavily favors GPUs) the practical bottleneck on CPU utilization for large general purpose programs (especially when spanning multiple cores) is memory bandwidth. And moving GC is terrible for memory bandwidth compared to both Go-style concurrent GC (which doesn't have to do bulk moves) and manual memory management.
pron 5 hours ago [-]
> And moving GC is terrible for memory bandwidth compared to both Go-style concurrent GC (which doesn't have to do bulk moves) and manual memory management.
This is not true. The whole point of the algorithm - the reason it was designed - is that the amount of moving is well below what's required in a non-moving collector. The downside is that the algorithm is more complicated and requires an FFI layer for FFI, but even though non-moving collectors are far simpler to implement, every language/runtime that can use moving collectors uses them (and all of those can also use non-moving collectors, too, as Java did earlier on; concurrent mark-and-sweep collectors like Go's or Java's old CMS are easier to make). Whatever you say about the complexity of moving collectors or their impact to latency before the recent invention of pauseless moving collectors, they are widely recognised fact that as the most efficient general purpose memory management solution (but also the most elaborate).
You could argue about certain workloads, but it is ridiculous to claim that the world's top memory management researchers worked for years to come up with an algorithm to be more efficient than mark-and-sweep collectors and malloc/free failed to notice that it has to move objects around a lot (the whole point of the algorithm is that it does not), and then every language that can use the algorithm chooses to use it because they also failed to notice that the algorithm that is so much more costly to implement is so obviously worse.
BTW, Go's reason for using a simpler, older style mark-and-sweep collector isn't that it's better (Google's larger V8 team opted for a moving collector), but that Go can get away with a simpler, less efficient GC because the allocation rate is lower (and we can argue over that, but at least that would be an argument over something that could actually be controversial).
Anyway, if you're interested to know how moving collectors really work, and how they were created to be more efficient than any non-moving general memory management strategy, I go through the basics in a recent talk I gave: https://youtu.be/xr73mR7ii9M
nixon_why69 13 hours ago [-]
A couple data points, I like Java but I've seen metrics of container fleets at multiple companies that were memory constrained with low CPU usage sitting around underutilized. The reason in both cases was a bunch of memory-heavy yet CPU-efficient Java processes.
pron 5 hours ago [-]
When CPU utilisation is low, the heap can be set much smaller. Many don't know that, so in the next year we'll have the VM do it automatically: https://openjdk.org/jeps/8377305
The amount of memory a Java program uses is whatever the setting is, not how much it "needs", because the need depends on the preference of the CPU/RAM tradeoff. But again, not many understand that, so we're making that automatic.
nixon_why69 58 minutes ago [-]
I'm sure both of the cases I'm thinking of could have been tuned better. Just saying that it's a default case that I've seen 2 places land, both of which had a lot of smart engineers following best practices. Maybe its food for thought for you in your position
the-smug-one 22 hours ago [-]
Go's compiler is fast because it doesn't do as many advanced (read: computationally expensive) optimizations as other compilers do. No clue about Green Tea and how awesome it is :-).
Lower memory pressure is certainly a difficult thing to beat Go at, Java (OpenJDK) is probably never gonna get there. You get a lot of other stuff, like better peak performance, instead.
Btw, have you tried Leyden/AOT for better startup times? Curious about your experiences with that.
p2detar 10 hours ago [-]
> Btw, have you tried Leyden/AOT for better startup times? Curious about your experiences with that.
Nope, not yet. It's a good question given that up to now we used to deliver our product only on-premises and Windows Server-only, but this year we are now finally going with the Cloud, which means Docker containers and Linux.
If I remember correctly Leyden required some sort of warm-up and training data collection before being able to effectively execute AOT, right? I need to freshen up my info on that.
I did try GraalVM-compiled Java executables a couple of years ago and they were not bad, but the binaries were quite big (not a showstopper though) and the class-loading issues were kind of a PITA.
gf000 12 hours ago [-]
Actually, javac itself is plenty fast, pretty similar to Go's (it also barely does any optimization)
It's usually the build systems that add quite some overhead.
someone_19 23 hours ago [-]
> because of essential constraints of low-level languages that prevent them from doing certain optimisations that matter mostly in large programs
Which specific optimizations are you referring to?
In my experience, this is largely a myth; compared to Rust, you actually get even faster code right away.
JIT is effective for languages where the source code lacks sufficient information (dynamic typing, where anything can be null).
pron 22 hours ago [-]
> Which specific optimizations are you referring to?
A JIT with speculative optimisation and a moving GC.
There are two constraints in low-level languages that trump any of their performance goals, one technical and one a matter of preference.
The technical limitation is that they must use stable pointers (because they need to be low-level and so having an FFI layer that separates "hardware pointers" from "language pointers", as we have in Java defeats their main purpose). This means that you need to translate data storage or code storage to hardware addresses, and that interferes with both moving collection and with JIT compilation.
The other constraint is that low-level languages value worst-case performance over the average-case and even amortised performance. These languages prefer an operation (e.g. dynamic dispatch) to be slow as long as it's never too slow. With a JIT (and I describe more later), virtual dispatch can be super-fast almost all the time, but occassionally, you'll hit a trap because the speculation was wrong, and then you need to deoptimise and recompile.
> In my experience, this is largely a myth; compared to Rust, you actually get even faster code right away.
We wouldn't be doing it in the first place if it was a myth. In a low-level language, you can get very fast code if you do some manual optimisations, but they don't easily scale as the program grows and evolves, because they're viral. The two most basic examples are dynamic dispatch (which is the most general mechanism, which scales the best in terms of program evolution) and shared heap objects (again, the most general mechanism). These become more common and less easily avoided over time, and they're slow in low-level languages because of the constraints I mentioned.
That low-level languages make it harder and harder to preserve good performance over time as they evolve and grow is a problem familiar to those who've worked for years on large software written in a low level language (as I have). The JVM was designed, among other things, to solve this performance problem in large programs.
> JIT is effective for languages where the source code lacks sufficient information (dynamic typing, where anything can be null).
A JIT can make such languages decently fast, but that's not how it's used in Java. In Java it is used for speculative optimisation, which allows far more aggressive optimisation than an AOT compiler can do. E.g. by default, Java inlines and specialises virtual calls 15 levels deep. An AOT compiler can't do that or its code will explode. We get around it with selective use of templates in C++ (or comptime in Zig), but it has to be selective, and it's viral.
someone_19 21 hours ago [-]
Thank you for the reply.
Do you mind a reasoned discussion?
> A JIT with speculative optimisation and a moving GC.
Idiomatic Rust, through its concepts of ownership and borrowing, encourages a pattern where you receive data as an argument or create it directly, perform operations on it, and then discard it via RAII. This bears some resemblance to functional programming. This approach does not apply to buffers of unknown size, which still require heap allocation; unfortunately, Rust lacks automatic buffer reuse. However, such optimization is theoretically possible. The stack is definitely faster than anything else.
> This means that you need to translate data storage or code storage to hardware addresses, and that interferes with both moving collection and with JIT compilation.
You don't need GC if you allocate data on stack. You also do not need to dereference the pointer.
> dynamic dispatch
You mentioned templates. In Rust, traits that are monomorphized - much like templates-are the standard approach; using vtables or `dyn trait` is a relatively rare use case. This stems from the fact that all code is known at compile time and there is no dynamic loading, allowing the compiler to eliminate polymorphism from the code entirely.
> and shared heap objects
This might be considered convenient, but in my view, it also leads to code that is harder to maintain when objects can be modified from multiple places. However, I think that is outside the scope of the current discussion.
> We get around it with selective use of templates in C++ (or comptime in Zig), but it has to be selective, and it's viral.
Yes, monomorphization is the default solution in Rust. It is not always viral either, because when using it, you often define specific types, and they do not spread beyond that scope.
I suppose you could say that the programming style I am talking about is complex, inconvenient, unmaintainable, and so on. What I mean is, assuming this programming style is sufficiently convenient—and perhaps even has its own advantages - then none of the optimizations you listed offer an edge, and the Rust code will definitely be faster.
gf000 12 hours ago [-]
> The stack is definitely faster than anything else
I have seen it mentioned everywhere, but is this actually true?
I mean, of course it is faster than random cold memory, but is it actually faster than a hot, in-cache part of the heap? It is not special in any other way, AFAIK.
And for what it's worth, what pron mentioned, Java uses a pretty similar structure for initial allocation, a thread local buffer where you just pointer bump. Another thread can then in the background copy still alive objects from this "arena" and then reset the whole thing.
someone_19 8 hours ago [-]
> I have seen it mentioned everywhere, but is this actually true?
Yes, it just adding or subtraction int to stack pointer register. I’m not certain, but the only thing that might be faster is accessing data at a fixed address - that is, global variables.
gf000 4 hours ago [-]
That's the way of getting the address itself, that's unrelated to how fast the actual memory read/write is.
Stack is fast because it is frequently "touched" staying in cache. If you were to continuously read write a small segment of the heap, I don't think it would fair any worse than "the stack". This was my point
pron 19 hours ago [-]
> However, such optimization is theoretically possible. The stack is definitely faster than anything else.
What you're describing isn't a stack, but an automatic arena, and this optimisation is easier to do in Java. It's easier to do in Java because it requires setting a "current arena" or inlining, both of which Java can do more easily, and then either the arena will be heap allocated (which will be slower in Rust) or associated with the thread, which is not something low-level languages tend to do.
> You don't need GC if you allocate data on stack. You also do not need to dereference the pointer.
Moving collectors don't need to dereference anything (they don't know and don't want to know when an object is "dead"), and stack allocation works in both languages, only, as you pointed out, is not quite general (not every data structure with a known lifetime can be allocated on the stack).
> You mentioned templates. In Rust, traits that are monomorphized - much like templates-are the standard approach; using vtables or `dyn trait` is a relatively rare use case. This stems from the fact that all code is known at compile time and there is no dynamic loading, allowing the compiler to eliminate polymorphism from the code entirely.
Sure, except Java does this automatically, and it can do it more aggressively. Dynamic dispatch is rare in low-level languages because it's expensive in those languages. But it's not easy to avoid as programs get larger. That is exactly one of the problems in large programs that the JVM set out to solve.
> This might be considered convenient, but in my view, it also leads to code that is harder to maintain when objects can be modified from multiple places. However, I think that is outside the scope of the current discussion.
I agree that whether it has downsides is outside the scope of this discussion, but the point is that as programs evolve and grow, the abstractions tend to be more general, and low-level languages suffer from "abstraction cost", where the more general abstraction (which becomes more common over time) is more expensive. Again, this is exactly why large C++ programs suffered from performance issues and what the JVM tried to address.
> Yes, monomorphization is the default solution in Rust.
... and in C++. But it is viral, and Java monomorphises without suffering from "zero overhead abstractions".
The ability to move pointers, both to data and to code, opens up the possibility of using JITs and moving GCs, which are very powerful optimisations. A JIT does impose two further tradeoffs (aside from the need for an FFI layer), though, which are warmup and the possibility of deoptimisation. We can now cache the generated machine code from one execution to another (https://openjdk.org/jeps/544), but the possibility of deoptimisation remains (in fact, it's what enables the aggressive speculative optimisations), which means you gain average (or even amortised) performance at the cost of the worst case.
Anyway, the JVM was designed as a solution for the performance issues low-level languages suffer from as programs grow and/or evolve. It comes with tradeoffs, but those most affect small or short-lived programs.
The thing to remember is that low-level languages are not optimised for performance but for low-level control (i.e. pointers are direct addresses etc.). Such control can translate to good performance when programs are small (see next) but it becomes a practical hindrance to performance when they're large.
> I suppose you could say that the programming style I am talking about is complex, inconvenient, unmaintainable, and so on. What I mean is, assuming this programming style is sufficiently convenient—and perhaps even has its own advantages
That advantage is a performance advantage. The question isn't "does there exist (in the mathematical sense) some program that is fast?" but "how fast is the program we can write within the budget we have?" When programs are small, manual optimisation is practical; when they grow large - not so much. And that's excluding the matter of a moving collector, which is just hard to compete with on speed regardless of program size, unless you use areans, but they're not at all easy to use in most low-level languages except Zig.
> and the Rust code will definitely be faster.
This is true only in the abstract mathematical sense. The reason we don't write programs that we want to be fast in Assembly (which is faster than anything in the same sense: for any program in any language, there exists and Assembly program that's at least as fast) is not because other languages are fast enough, but because in practice the programs we can actually write in the budget we have will be faster than the Assembly programs we could write. Of course, that could change when AI is able to generate perfect low-level code, but when that happens, it might as well generate machine code directly.
someone_19 6 hours ago [-]
> Assembly (which is faster than anything in the same sense: for any program in any language, there exists and Assembly program that's at least as fast)
At least you aren't claiming that the JVM is ~1.5 faster than perfectly written assembly :)
I disagree with a lot of what you’re writing. However, we’ve reached the point where we need to run benchmarks and analyze the generated code (this is easy to do for compiled languages using https://godbolt.org/, but for the JVM, it can be a bit more complex, given the warm-up factor).
So, there is one fundamental point I started with:
> JIT is effective for languages where the source code lacks sufficient information (dynamic typing, where anything can be null)
And your answer is:
> A JIT can make such languages decently fast, but that's not how it's used in Java. In Java it is used for speculative optimisation, which allows far more aggressive optimisation than an AOT compiler can do.
Essentially, you are saying that the compiler can apply aggressive optimizations when it knows what is happening in the code.
But I say that JIT is needed so the compiler can figure out what is happening in the code and perform aggressive optimizations.
There are many things that can be inferred from the code without needing to execute it. The question is how difficult it is to make such an inference: in one scenario, the compiler might attempt to track whether specific data changes-and, if it can prove this, mark the data as immutable and apply certain optimizations-whereas in another, it might already possess the information that the data is immutable.
Moreover, information about immutability is useful not only to the compiler but also to the programmer. Just like information about types: it benefits both the compiler and the programmer. Imagine a fan of JS or Python joining our conversation and claiming that both Java and Rust are low-level languages because you have to specify types - something they view as complex and a hindrance to development speed.
The same applies to the GC: the compiler can perform more optimizations when it knows when memory needs to be cleared (move it to stack or even place the data on registers). The JVM attempts to do this (via escape analysis), but there are limitations; consequently, data ends up on the heap, and GC operations come at a cost (due to data movement).
Rust simply makes it easy to obtain far more information, enabling aggressive optimizations that are both immediate and guaranteed.
There remain a small number of cases, such as `switch` statements - where one branch executes 99% of the time, while the other 99 branches execute only 1% of the time. In such instances, the JIT could indeed perform further optimizations; however, I am not even sure if the overhead of monitoring wouldn't outweigh the benefits. And the question is when and how to perform PGO, or whether to perform it at all.
pron 5 hours ago [-]
> There are many things that can be inferred from the code without needing to execute it. The question is how difficult it is to make such an inference: in one scenario, the compiler might attempt to track whether specific data changes-and, if it can prove this, mark the data as immutable and apply certain optimizations-whereas in another, it might already possess the information that the data is immutable.
Yes, and the important point is that when it comes to knowing things statically, abstraction and optimisation are in conflict. The whole point of abstraction is that the implementation details aren't known. So in C++ we always suffer from this problem called "zero overhead abstractions" or "abstraction costs", which means that to give the compiler the information it needs, we have to use less general abstractions, which are viral and harm evolution. What a JIT does is allow the compiler to learn the very things that abstraction hides; yes, it's a virtual call, yes, it could target anything, but I've seen it hit the same target 1000 out of the last 1000 times, so I speculate that this will continue and I'll inline even though I could be wrong.
> The same applies to the GC: the compiler can perform more optimizations when it knows when memory needs to be cleared
I understand why this could be true in theory, but in practice the problem is:
1. not that the compiler knows when an object is unreachable, but that the generated code has to do something at that point, and
2. the most efficient known memory management algorithms - moving collectors and arenas, both work in nearly the same way - are entirely predicated on freeing memory in bulk and on not doing anything when an object becomes unreachable, and so the knowledge of when an object becomes unreachable doesn't help them.
So it is true that C and C++ and Rust always statically know when an object is dead, and you could say that hypothetically they don't need to do anything with that information, but in practice they all act on that information immediately and that's inefficient.
> There remain a small number of cases, such as `switch` statements - where one branch executes 99% of the time, while the other 99 branches execute only 1% of the time.
So the main practical benefit of a JIT isn't that at all, but that it can do the "mother of all optimisations" - inlining - far more aggressively. Inlining is important because it cracks open the abstraction boundary of the inlined subroutine, and allows the compiler to further specialise and optimise things, now with the appropriate context.
Anyway, all of these fundamental questions and differences between languages with more statically known information and figuring out "unprovable" information in practice were very well known before the JVM was built to address the performance problems we had suffered from in large C++ programs. So we can argue over which workloads are helped by this and which aren't, but there is no way to say which is usually faster in the absract (because, again, these considerations were known and taken into account). It's merely an empirical question, and not one that's easy to settle. After more than 25 years of working with C++ and almost 20 years of working with Java, my default is that low-level wins on performance (if written by experts) in smaller programs, and Java wins on performance in larger programs, but of course, there are many caveats in either direction.
NovaX 16 hours ago [-]
> Caffeine had the same latency as Moka across all percentiles at twice the throughput.
Caffeine's next release has roughly 25% higher read throughput, with unchanged write throughput, thanks to fixing a false sharing mistake. That won't be visible in real workloads, but is fun nonetheless (500M reads/s on 8 cores).
Capricorn2481 10 hours ago [-]
> Saying that Go lacks in those is just showing how people are making software those days. It’s just terrifying
I'm not following. Saying Go lacks in X shows how poor other software is? Can you connect the dots?
brabel 1 days ago [-]
I think you have a biased view. The number of stuff written in Rust in the last couple of years has absolutely exploded. For example, I see a lot of projects now that provide SDKs in Rust but don’t bother with Java. And I say this as someone who still writes most of my code ( or tell my LLM to write) in Java.
pron 1 days ago [-]
There's a difference between number of programs and number of LOC (the latter is related to the number of people involved). I am not aware of any SDK targeting the industries I mentioned that "doesn't bother with Java". It's not only a popular choice in those industries, it's not only among the top choices, but it's the top choice by a large margin. Look at wanted ads in those industries to see that. Overall, there are only two languages as popular as Java or more, and they are JS and Python: https://www.devjobsscanner.com/blog/top-8-most-demanded-prog...
1 days ago [-]
Mawr 1 days ago [-]
Yeah I mean he literally works on Java at Oracle, so may just be a little biased.
Doesn't bother to disclose it of course, because what, you don't check everyone's profile in every discussion to make sure they're not biased? What, you don't just know who every user on this site works for? You dummy you :)
pron 1 days ago [-]
It's disclosed right there in my profile (I don't see your professional affiliation disclosed in your comment; or your profile, for that matter). Of course, I, like other runtime and compiler people, joined the Java team because we wanted to work on the most advanced compiler and runtime tech. I perfectly understand people who want to work on smaller, newer, potentially insurgent products, but I took the chance to work on the cutting edge of compiler and runtime engineering, and Java is where it's at these days (I'm not saying it's the only one, but it's a very small club).
wk_end 23 hours ago [-]
GP's snark is unwarranted, but it's probably good practice to disclose your professional affiliation explicitly in comments related to it, even if you have already disclosed it in your profile.
I was reading your comments on Java, nodding my head, upvoting, without checking your profile and realizing that you're a member of the Java team. Knowing that doesn't mean I now suddenly disagree with you or anything. But while in an ideal world it doesn't matter who's saying something when evaluating it, there's some human factors at play - I'd like to turn up my internal sense of skepticism when dealing with someone, effectively, selling something their salary depends on; even if you're being entirely earnest, it's ultimately a sales pitch, and I feel bamboozled for not recognizing it - that'd make me appreciate transparency.
(FWIW, even though I prefer being coy about my place-of-work, I have no professional relation to this conversation. I've never used Java in my 9-5 and I haven't even really used it in earnest since, like, version 5 back in high school. I think it's always been underrated by the hacker crowd, though!)
pron 23 hours ago [-]
I agree that it matters, but whether and how to do it depends on the standard practice in the relevant forum. On HN, it's rare for people to disclose affiliation even in their profile, so I think I'm already better than the norm here on HN in that regard.
Capricorn2481 10 hours ago [-]
> I'd like to turn up my internal sense of skepticism when dealing with someone, effectively, selling something their salary depends on
This is really, really silly. Java is many times beyond the position where its developers need to desperately convince people to use it. This is a person who has unique technical expertise in the area whose credentials are smack dab on their profile, not hidden from you. Their closeness to the domain at hand should make you less skeptical of what they are saying.
unscaled 22 hours ago [-]
While Java can outperform Go in some cases, the situation is very much the opposite when it comes to Rust.
I also don't see the case for stability. Yes, if you're still on JDK 8, it would probably chug on for a couple of years. But we were talking about greenfield projects and newer JDK go EOL much faster. If you want patches, you'll have to run your app to a newer JDK, which may break a couple of things. Rust (within the same edition) or Go (within the same major version) break less than that.
As far as runtime compatibility goes, Rust and Go apps ship with the runtime. This can be better or worse for you, depending on what is your upgrade story, but I don't see a clear winner here. What I would give to Java over Rust is that you will have far fewer dependencies to take care of if you need to upgrade. But the same goes for Go.
For observability, I feel that with Rust you have a bit less that you need to observe (no GC to worry about). Tokio tracing is great, but observability requires a bit more effort. The go observability story is far worse. So Java probably has an edge here, but not something that ever felt like a game changer. My impression is that for most of the enterprise shops that love Java, observability means collecting unstructured log files through NFS and trying to find a needle in the haystack with primitive tools, but I've been out of touch with this world for a couple of years.
Productivity is something that is dead if you are AI-heavy. Sure, many shops are still wary about AI, and I totally get why, but this is a battle that's already been lost. Without AI, I would say I was about 3 to 4 times more productive in Rust than I was in Java, but ramping up that productivity took at least 1 year of practice. It's not time most companies are willing to spend. With AI, this doesn't matter anymore, for better or worse.
I'm not arguing that Java is not chosen often for greenfield projects. It's clearly extremely popular in many circles, especially outside startups and big tech. But I think the reason Java is chosen have little to do with the reasons you've mentioned above and more with organizational preferences.
samus 11 hours ago [-]
> I also don't see the case for stability. Yes, if you're still on JDK 8, it would probably chug on for a couple of years. But we were talking about greenfield projects and newer JDK go EOL much faster. If you want patches, you'll have to run your app to a newer JDK, which may break a couple of things. Rust (within the same edition) or Go (within the same major version) break less than that.
Java also breaks very few things. Breaking binary compatibility is a no-go since it's a core promise of the platform. The only thing in the surface language that has ever been changed is the meaning of the underscore as an identifier, as well as the behavior of == in upcoming Project Valhalla.
> As far as runtime compatibility goes, Rust and Go apps ship with the runtime.
Java applications can also be shipped together with the runtime.
> Productivity is something that is dead if you are AI-heavy.
Nevertheless, making constructs available to express intent more clearly should also help LLMs to not go off the rails.
pron 22 hours ago [-]
> the situation is very much the opposite when it comes to Rust.
It isn't, and the problem isn't Rust specifically, but all low-level languages. They can offer very good performance (often better than Java) when small. But as they evolve over time, or are very large to begin with, they become much harder to keep performant. This is for pretty fundamental constraints of low-level language that I mention in another comment here, and this performance problem with large programs written in low-level languages was well known before Java even existed. The JVM was designed, at least in part, to address it.
One of the things that drew me to Java (from years of C++, even though I still work in C++ when I work on the JVM) is precisely how it addresses those performance issues we ran into with C++ five years into a project.
> As far as runtime compatibility goes, Rust and Go apps ship with the runtime. This can be better or worse for you, depending on what is your upgrade story, but I don't see a clear winner here.
I wasn't talking about "runtime compatibility" but of overall version compatibility. Java has an unmatched compatibility record - not perfect, but better than anything else (with at least a medium-sized standard library).
> For observability, I feel that with Rust you have a bit less that you need to observe (no GC to worry about).
Memory management is very often a bigger issue without a GC than with a moving GC. Time and again we see Rust or C++ programs spend 30-50% on memory management.
> Productivity is something that is dead if you are AI-heavy.
Really? Have you had AI write a good medium-sized (say 100-500 KLOC) program or maintain one over a long period of time without very close reviews? The only people I've seen who don't know about the ticking time-bomb agents leave in the codebase are the people who don't look.
> With AI, this doesn't matter anymore, for better or worse.
You may be talking about small programs. I agree that for small programs, low-level languages can offer excellent performance, and AI can be okayish, and you can get some observability you can live with, but I'm talking about large programs.
> But I think the reason Java is chosen have little to do with the reasons you've mentioned above and more with organizational preferences.
Those organisational preferences are due to a long record of delivering on the things I mentioned. Java has an exceptionally low "regret factor", i.e. people who regret choosing it five, ten, or fifteen years into a project (which is when the problems usually start).
aw1621107 20 hours ago [-]
> Time and again we see Rust or C++ programs spend 30-50% on memory management.
30-50% of what?
pron 18 hours ago [-]
Oh, sorry, missed a few words. Their CPU time.
steveklabnik 5 hours ago [-]
I’m curious where you have seen this.
pron 3 hours ago [-]
It's quite common in concurrent services that non-experts write. But the more interesting cases are things like Moka. In a simple evaluation (and, of course, not much can be extrapolated from any benchmark) Java's old Caffeine library had lower latencies in all percentiles at twice the throughput as Moka (at 90% cache hit rate), as the latter spent 41% of CPU (on top of the cost of malloc/free) on epoch based reclamation.
steveklabnik 2 hours ago [-]
> and, of course, not much can be extrapolated from any benchmark
Right, so one case (which I certainly believe is possible) is very different from “time and time again.”
pron 51 minutes ago [-]
By time and time again I meant concurrent services that are written by people who are not experts at low-level programming. The irony is that they don't see "CPU spent on memory management" as they do in Java not because there's less of it - quite often it's much, much more - but because it's simply not measured and reported.
As for the caching test, it's just technically interesting, because the JVM was designed to address the performance issues we suffered from in large C++ programs (all the JVM engineers are, of course, C++ people), both due to compilation and to memory management, and we regularly compare both our compilation and memory management algorithms to other approaches, and it just so happens that last week one of our GC engineers compared Caffeine to Moka and saw how CPU-intensive the memory management work is compared to ZGC (he was particularly interested in this because caching is one of the more challenging workloads for generational moving GCs because a cache deals with many old objects, whereas generational GCs tend to focus more on young objects, and he wanted to make sure that our GCs help reduce the high memory-management overheads associated with low-level languages even in this challenging scenario).
aw1621107 17 hours ago [-]
Gotcha, that bit makes more sense now. That's quite the statistic!
akkad33 1 days ago [-]
I don't agree. If anything these newer languages have better tooling and new projects are always built from ground up to support open standards like open telemetry
pron 1 days ago [-]
Open telemetry is about how telemetry data is reported, not how it's collected. It's hard to compete with JFR on the breadth and depth of low-overhead, in production telemetry, built into the standard library and the JVM itself.
Almondsetat 1 days ago [-]
Go and Rust have much worse tooling for enterprise-level collaboration
unscaled 23 hours ago [-]
I'm not sure what enterprise-level collaboration means. In my experience, "enterprise" usually means: "Let's use tools that are 10 years behind, buggier than average, and have lots of half-baked features, none of which we need".
I'm not sure what kind of tools you mean, but unless you're looking for something that just works exactly the way EJBs do for some mysterious reasons, I don't see why you can't do most "enterprisey" things with Rust or Go. Or Python or TypeScript for that matter.
egorfine 20 hours ago [-]
> Let's use tools that are 10 years behind
Yes and that's exactly what modern tooling is missing. Try to develop for node.js 0.2.12 on today's update of Visual Studio Code. See? No enterprise-level collaboration for ya.
doublepg23 1 days ago [-]
Do they?
It felt like every dev that worked on our Java behemoth at a previous job was elated to switch to Go.
akkad33 1 days ago [-]
I don't think they do. I work in a maven shop and half of the people don't even know what to do when maven fails inexplicably
esafak 1 days ago [-]
Haven't they heard of Gradle or Bazel?
dimaaan 24 hours ago [-]
Go has null pointer dereference problem.
Rust is too low-level for typical enterprise app where requirements changes twice a day. You end up spending time and tokens fighting with borrow checker.
C# is MS product, which is no-go for some folks.
Kotlin probably would be the answer.
unscaled 23 hours ago [-]
> Go has null pointer dereference problem.
Which Java famously does not have.
> Rust is too low-level for typical enterprise app where requirements changes twice a day. You end up spending time and tokens fighting with borrow checker.
In my experience, you do not spend tokens fighting with the borrow checker anymore, newer models are smarter. But it might not be ideal for a lot of CRUD applications.
> C# is MS product, which is no-go for some folks.
This is 2026, it's not 1996 anymore. .Net works on Linux and Microsoft is as friendly towards open source and open standards as a Big Tech company can be.
If anything, it was Oracle which more recently sued another company for using a JDK alternative. And this was a lawsuit that, if accepted, could have put the entire idea of API compatibility in danger and deal a severe blow to the Open Source movement.
Anyone who is morally bothered by MS but is unfazed by this is probably just mentally stuck in the 1990s.
> Kotlin probably would be the answer.
I love Kotlin, but I'm afraid that's not the case. The conservative organizations that choose Java out of inertia, would keep choosing Java over Kotlin, even if Kotlin is a better JVM language which is facing no downside.
For anyone who doesn't need to be on the JVM or work with JVM tooling, Kotlin doesn't cut it. It doesn't have null pointer dereference problem in theory... Only it does in practice if you're using any Java API that may return null (all these bang-decorated "Platform types"). Generic type erasure can only be overcome in inline functions with reified types. And building and deploying artifacts without docker is still a mess.
I found Kotlin extremely publishing for Java shops in the past, and I've converted multiple departments totaling over hundreds of employees to use Kotlin. But that was before AI. The rationale was simple: Java is an entrenched language that leads to bloated code, slow development cycles and way too many avoidable bugs in productions. Kotlin solves some if these issues, and it's very easy to learn for a Java engineer, while still letting you keep all of your tools and libraries. And as a language (putting ecosystem aside), I find it better than either Go or Typescript, and far more ergonomic than Rust[1].
But all of these arguments die with AI. Rust is just as ergonomic as any other popular language today if you're using an agent, and the fact that an engineer spent their lifetime writing Spring Boot programs in Java you don't have time to let them learn a new stack from scratch doesn't matter anymore.
Sure, there are many companies where letting AI write the code is still not acceptable, but most of these workplaces will accept AI agents sooner than they accept Kotlin.
I feel a bit sad since I like many ideas about Kotlin (especially how amenable it is for making DSLs) but we've lost that opportunity
--
[1] Unless you have to write highly concurrent code without any data races.
pron 18 hours ago [-]
> Rust is just as ergonomic as any other popular language today if you're using an agent
Have you worked on large (>500KLOC) codebases with an agent? Not only do you have to be an expert at the language, but even if you're lucky and everything is fine, Java code is likely to be particularly fast by comparison, because the agents aren't very good at manual optimisation, especially as the code grows (they're even worse than humans at that, and humans aren't great at manual optimisation of large codebases, either, which is one of the problems the JVM set out to solve; in fact, agent-written code in a low-level language gets pretty slow well below that size). Oh, and the long build times certainly don't help.
unscaled 13 hours ago [-]
Have you worked on large (>500KLOC) codebases with an agent?
Yes. But keep in mind KLOCs are not easily comparable across languages. Java is notoriously verbose. A 500KLOC codebase in Java would usually be half that size in Rust. If your argument is that large codebases makes life harder for agents, you should go with a less verbose language.
I'm not sure what "manual optimization" means (isn't it a bit of an oxymoron when the agent does it?), but if your agent has the proper tools (e.g. ast-grep, rg, semble) it can deal with large codebases. Would the agent create slop? Yes. But it wouldn't be worse on the slop that humans created on every moderately-sized Java project I've worked on.
> in fact, agent-written code in a low-level language gets pretty slow well below that size
I've never seen this happening. I've seen agents writing suboptimal Rust code (e.g. copies instead of Cow). But while this occassionally happens with Rust, I've never seen an agent optimizing for Java where necessary (e.g. using object pools to avoid GC churn). Java is not magic.
gf000 12 hours ago [-]
> is notoriously verbose. A 500KLOC codebase in Java would usually be half that size in Rust
Lol, no way. Especially that rust is pretty verbose all things together (which makes sense, given it's a low level language - ergo you have to literally express more things about the code)
pron 3 hours ago [-]
> A 500KLOC codebase in Java would usually be half that size in Rust
Ok, so you barely know either Java or Rust.
> If your argument is that large codebases makes life harder for agents, you should go with a less verbose language.
You mean, like Rust??? But no, that's not my argument. Agents have a hard time keeping up the architecture in large software (and the differences between verbose languages like Java, Go, Rust, and C++ vs less verbose ones like Python and JS don't make much of a difference). So they either make a mess or they do the simple thing, and the simple thing in low-level languages is often slow.
> But it wouldn't be worse on the slop that humans created on every moderately-sized Java project I've worked on.
Yeah, I don't think you've actually tried it.
> I've never seen an agent optimizing for Java where necessary (e.g. using object pools to avoid GC churn). Java is not magic.
Object pools are far less efficient than Java's GCs, but while state-of-the-art compiler and memory management technology is certainly not magic, I suggest you learn more about these things if you want to make informed decisions.
wolvesechoes 6 hours ago [-]
First, I like reading your comments, as they are pretty illuminating, also in the way they expose how superstitious programmers can be.
Second, I wonder where, roughly, would you put a transition from small programs where low-level langs are fine, to programs large enough to heavily benefit from JVM tradeoffs? And how this transition is affected by a stuff like Graal Native?
azuanrb 1 days ago [-]
Any mainstream language can be reliable. Java is a good choice for greenfield projects when it fits the organisation’s existing stack, expertise, or the broader industry ecosystem. Just like any other language.
Banks, telcos, etc. aren’t monoliths either. They use plenty of different languages depending on the team, system, and requirements. Java isn’t inherently the choice for greenfield software just because reliability matters.
marginalia_nu 1 days ago [-]
Not at all a bad choice.
It's stable to the point of boring, and there's no shortage of people who know the language and can work with it, it's got best in class tooling, decades worth of libraries almost all very mature. Most of the language's issues are from legacy code bases coded in a style that isn't really relevant to a greenfield project.
gentlewater 1 days ago [-]
It’s still missing null safety, right? Which means it’s still a hard no for me.
msgilligan 1 days ago [-]
The ecosystem has (at long last) standardized on JSpecify (https://jspecify.dev) for nullability annotations. JSpecify allows you to annotate a package or module with `@NullMarked` and your IDE and build (via ErrorProne+NullAway, typically) will check for null safety.
If you develop a library in Java and use it from Kotlin, the built-in Kotlin null-safety will recognize the JSpecify annotations on the library.
We have a java monorepo of relatively large size and sophistication, driving our entire fintech, and I haven't seen a NPE for years.
Use NullAway and it basically makes the problem go away. Our application won't build if it detects a potential NPE.
unscaled 22 hours ago [-]
Congratulations. You've added another build tool and sprinkled your code with ugly annotations and ifs and Optional Optional.of(x).map(y) all over the place to get the same thing you'd get by moving to Kotlin.
I get it why this seems like a less drastic change, but this saddens me. Kotlin solves more issues with the type system (smart casts, reified types, immutability by default), without sacrificing readability. Unless I can see a solution in Java that makes dealing with NPEs as easier for lazy developers as ignoring them, I don't consider it a solved issue.
munksbeer 20 hours ago [-]
I don't really think about it too much, it works fine. We don't use Optionals, I'm not sure why you brought that up. I'm not a dogmatic person in this respect, rather pragmatic. I'm sure Kotlin is great, and I'd enjoy writing it, but for now, the vast majority of the finance world runs on Java, so it's what we use. I find it easy to work with, which counts for a lot.
samus 11 hours ago [-]
> Congratulations. You've added another build tool and sprinkled your code with ugly annotations and ifs and Optional Optional.of(x).map(y) all over the place to get the same thing you'd get by moving to Kotlin.
You deserve the strawman award of the year.
NullAway and JSpecify encourage making as many types non-nullable as possible, thus they can actually also advice about removing redundant null checks. Nullable types become the painful exception that visibly spreads through the codebase, which discourages writing code that relies on null.
Optional doesn't enter the picture at all. NullAway kills their usecase within ones own code. They are anyway only recommend as return types to force others to check for an emoty case, but I think Optional will become fully optional when the Java platform gets nullable types on its own.
Google Error Prone is a code linting tool that's very useful in its own right, and NullAway is just another plugin.
smrtinsert 15 hours ago [-]
More Java strawman arguments. How do we live with BuilderFactoryFactoryBuilders! Every day we cry ourselves to sleep!
LelouBil 1 days ago [-]
You can either use Kotlin then, or simply use java and nullability annotations, they have good support in both IDEs and analysis tools
gentlewater 1 days ago [-]
I use Kotlin as my main language and I’m very satisfied with it.
winrid 1 days ago [-]
I have a couple 50k+ loc java projects written entirely by LLMs at this point that have never thrown an NPE.
what_hn 1 days ago [-]
Same here with go, then Again go doesn't throw!
gf000 12 hours ago [-]
It just swallows errors, so you don't even know about it!
xdavidliu 1 days ago [-]
surely 'throw a npe' means something very similar to something that Go does
mahboi 23 hours ago [-]
It's a panic in Go, not an exception. In practice that's usually a whole process crash. You can catch panics and kinda use them like exceptions, but it's not conventional.
samus 11 hours ago [-]
So the whole webserver dies instead of just a worker thread unwinding its stack?
mahboi 60 minutes ago [-]
Oh I shouldn't have said "usually," it depends. The typical net/http recovers from a panic inside a handler and sends error 500. I don't know if that was always the default. Where I worked before, our own Go servers didn't recover, and it did cause outages.
The thing is, panics aren't exactly meant to be safely recoverable like exceptions are. They're like Rust panics. Say there's a panic in the middle of modifying some global state like a database connection. Hence complaints about the net/http recover like https://github.com/golang/go/issues/25245
Not exactly the same solution as JSpecify, since it doesn't rely on annotations, but it's also more ergonomic.
I'm not comparing this to "null-restricted types", since that's a draft JEP that hasn't made it even into a preview feature. Go also had multiple proposals for explicit nilability in types, and while they probably have less prospect of ever seeing the light of day compared to Project Valhalla, as things currently stand, Go is in the same position as Java: They are both extremely prone to NEPs out-of-the-box and they both have external tooling that can help you avoid them.
Java null checkers have more comprehensive coverage potential compared to Go, but Go is the more ergonomic one here. You don't need a single extra annotation on your code.
samus 11 hours ago [-]
That one works pretty much the same way as NullAway, which is kinda unsurprising because of the name and because of who made it.
marginalia_nu 1 days ago [-]
Have you tried not returning null or constructing incomplete objects?
gentlewater 1 days ago [-]
Can I trust code I’ve written myself with no guarantees from the language? Maybe. Can I trust code written by dozens of other developers (and/or agents) working on the same project over multiple years? Definitely not.
mahboi 23 hours ago [-]
Can you explain what the issue is with nullability here? Is the concern that someone's code returns null in normal circumstances but doesn't document that well, so you don't check if null? Cause if it's an error situation, one way or another some exception has to be thrown.
marginalia_nu 1 days ago [-]
Why don't you have any coding standards? If you're working with agents in particular, catching and enforcing this stuff should be easier than ever.
saghm 1 days ago [-]
Most engineers do not have the ability to impose rules by fiat on all of their coworkers. It seems like you're misunderstanding the nature of working on a codebase as an IC when other developers contribute to it. If all of my coworkers don't want a lint rule I propose, I don't get to add it. If all of my other coworkers want to write code in a certain way and approve each other's MRs with code written in that way, I don't get to veto it.
ndriscoll 1 days ago [-]
Most engineers don't get to decide to use a language either. Usually someone with the clout to pick a language has the clout to set style requirements too.
throw34234 1 days ago [-]
They do get to decide which jobs they take. And the languages involved are one of the easiest filters. A lot easier than checking whether the code a company actually writes is any good.
Theoretically you don’t need to write AbstractFactoryProvider in Java, but looking at languages mentioned in job offers, I have a pretty good idea of which of them have a high probability of working with such code and which do not, even if all of them say they have the best code ever.
saghm 4 hours ago [-]
There are plenty of reasons why someone might not have a wide range of job options available and need to prioritize based on other factors than the language they have to use.
For concrete example, it took me a long time to find a job last year due to only fully remote being viable since my wife's autoimmune condition means I'd be risking her health by commuting, and nowadays most places seem to either expect hybrid if you live near an office (I'm within the geographic limits of NYC despite being nowhere near Manhattan), restrict by time zone (there were quite a few jobs I was interested in where they only would accept remote with Pacific or Mountain Time), or have onerous travel requirements (multiple opportunities I interviewed for didn't work out because they expected me to fly to the west coast every couple of months, which between the time there and jet lag would mean I'm not productive close to a quarter of the time).
I was in a fortunate position to be able to hold out for a while and ended up finding a fully job with my preferred language after around eight months, but I had already come up with a timeline for when I should start relaxing certain constraints if it went on longer. Programming language was literally the first constraint that I was going to drop if it lasted a few more months because prioritizing my wife's health is non-negotiable, and I'd rather work in a language I don't like as much on something that I don't feel is actively making the world a worse place than work in my favorite language on adtech or at some cryptocurrency startup. It's not clear to me why it would be a problem for me to care about using my non-favorite programming language well if I happened to be employed to write it.
mahboi 23 hours ago [-]
Yeah I don't take Java jobs. There's nothing wrong with Java per se, but it usually has implications.
saghm 1 days ago [-]
I don't disagree, but that sounds more like a response to the person asking "what's the argument for picking Java?" than one to the someone who finds "Have you tried not returning null or constructing incomplete objects?" and "Why don't you have any coding standards?" to be poor takes.
ndriscoll 1 days ago [-]
If someone's asking "why Java" or is saying nulls make it a hard no, then you'd assume that they have a choice in the first place, which generally means they also have some ability to set coding standards at the same time that they're choosing a language.
Scala technically allows you to use nulls or throw exceptions pretty much wherever (necessary for Java compatibility), but it's not an issue because people simply don't outside of super niche situations (generally some low-level thing, or a shim). Similar to `unsafe` in Rust. Or casts in all sorts of languages.
saghm 24 hours ago [-]
> If someone's asking "why Java" or is saying nulls make it a hard no, then you'd assume that they have a choice in the first place, which generally means they also have some ability to set coding standards at the same time that they're choosing a language.
I don't understand that logic. I sometimes ask people to explain why they think a certain policy should be implemented by the government after they state their support for it, but I don't have the ability to set government policy. I have trouble imagining you genuinely assume that any time someone asks you why something should be the way you say that you think they have the ability to change it if you convince them.
ndriscoll 23 hours ago [-]
Of course you assume that; if you're talking about what a policy should be, then you work in a hypothetical world where the policy can be chosen. You don't say "but what about some other minor detail! That would require an additional policy choice, and we can't change related policies."
Like if I think my business should open an hour earlier, and you say "but the employees won't be there yet so who will open the doors!" obviously the solution is to also change the work schedule. When you have closely related policies, generally the same person/people are empowered to make both changes.
saghm 4 hours ago [-]
Yes, a hypothetical world, not necessarily the real one. The first comment you responded to from me was me responding to someone who said "Why don't you have any coding standards?". It sounds like the answer to it that you're proposing is "I do, but they're just all hypothetical", which I guess isn't technically wrong but it's entirely irrelevant to the real-world circumstances that you still haven't addressed in any way from what I can tell.
ndriscoll 2 hours ago [-]
Precise wording aside, the essential content of the back-and-forth here is:
> When should one use Java on projects?
> One shouldn't consider Java because it lets you use null.
> That's easily solvable by just not using null.
> But you can't just do that. People will use it.
> You can just do that. Tell them not to.
Like I'm not seeing the issue. This is like saying you can't use Rust because people will use `unsafe` because it lets them do C programmer things, and then claiming it is simply impossible to tell them not to do that (and set tool policies to flag anyone attempting to).
In the real world, if you're in a position to even ask "why use Java for a new project?" then you are presumably also in a position to have "don't use nulls" be a satisfying answer to "what about nulls?" If someone is asked what technology to use for a project in the first place, they are almost certainly also asked about how it will be used. The hypothetical here is not "do you have coding standards" but "are you a decision maker," and when the original question is "when should one decide to do X," you have to accept as a premise that you are placing yourself in the role of a decision maker in the first place.
mahboi 23 hours ago [-]
Everyone has to deal with other code that might not even be from the same org. The "check it in CI" answer isn't an excuse either. You're bolting on so much extra crap that way.
I just don't see why nullability is a problem in the first place.
gjadi 1 days ago [-]
Null Analysis can be checked and enforced by the CI.
mrkeen 1 days ago [-]
The standard library writers can go first.
saghm 1 days ago [-]
Do you never use external libraries?
marginalia_nu 22 hours ago [-]
I don't think I've encountered an external library that returned partially constructed objects returned nulls (at least not without a @Nullable). There are probably cases of this existing, but those types of libraries don't tend to see a lot of users.
what_hn 1 days ago [-]
With agents, we're getting there.
saghm 1 days ago [-]
I admit I haven't worked in Java for years, but no project I've seen at my current company (the only one I've worked at since agents have been a useful thing) is anywhere close to removing all dependencies. From what I've seen, people want to spend tokens on new things, not things that are already known to exist. Even if you can reinvent the wheel, it's not something that an employer is going to be particularly happy to subsidize.
wavemode 1 days ago [-]
Nullability annotations + tooling makes this a non-issue in practice.
misiek08 1 days ago [-]
I’m not sure how long it will take, but please - can we stop saying that annotations like @IHopeThisWontBeNull is a toy for kids and, having so many years of incidents caused by those and having LLMs to write and fix the code, we can rely on language and compiler already?
wavemode 1 days ago [-]
You're drawing a distinction that doesn't matter in practice. If you encounter a NullPointerException incident then you either didn't annotate your code or you didn't run the tooling. (In fact before even running your CI suite, any serious IDE will tell you immediately that you've mishandled null somewhere.)
I get that some people feel like it ought to be built-in to the language rather than a separate tool... but people's personal feelings are irrelevant to the lived experience of my day-to-day work, where worrying about null is truly a thing of the past.
ivan_gammel 1 days ago [-]
It is really not a big deal nowadays, the problem of the same scale as having index out of bounds error (no language has good defence against this, yet it is not a catastrophe).
samus 11 hours ago [-]
The problem with array out of bounds is that you'd need to reason about arithmetic expressions over natural numbers. However, in general that's a very thorny problem since one runs squarely into [Gödel's incompleteness theorems](https://en.wikipedia.org/wiki/G%C3%B6del%27s_incompleteness_...). Working around it requires painful restrictions or cause uncertainty over whether the compiler will apply certain optimizations.
pmontra 1 days ago [-]
That's expected (the index out of bound). You have an array, and maybe it grows, you read a number from input, you don't check it against the size of the array because you want to torture the language, use it to get the element at that index and... I'm sure that there is a surprisingly number of different designs of what it should happen and a number of designed ways to ensure that it doesn't happen. But a runtime error is expected.
ivan_gammel 1 days ago [-]
yes, so are the NPEs - both are runtime errors indicating a bug in the code. NPE was a major source of irritation 20 years ago, but what many people do not know is that debugging NPEs in Java is easier now - they carry more information about the source. And the culture has evolved.
cesarb 19 hours ago [-]
> but what many people do not know is that debugging NPEs in Java is easier now - they carry more information about the source
Unfortunately, no, they don't. Not after your application has been running for a while; newer JVMs arbitrarily decide you don't need the stack trace anymore, and all you see in your logs is "NullPointerException" (unless you still have the logs from several weeks ago, just after the last JVM restart, which might still have the full stack trace). Older JVMs were better, since they always had the full stack trace; debugging NPEs was easier with them.
Unlike C it is trivial to catch a NullPointerException and confine the crash to the unit of work. And unlike C you are not talking about insanely dangerous pointers, you're just talking about an NPE.
I'll admit it's a hassle when something wasn't initialized properly and then you get a null pointer exception at some unrelated code much later. It's not always easy to debug. Catastrophic? No!
There are a lot of third party tools that can check for null safety and a lot of work is being done to make Java's initialization safer but also a little more flexible, there is
and there are all sorts of practical answers. Nulls in Java are low on my list of annoyances, way behind front end programmers who pepper my CSS files with "!important" because they don't know about precedence (though maybe they think my .clazz.clazz.clazz selector is brain dead!)
well_ackshually 1 days ago [-]
Any serious project will be using NullAway and annotating everything (or, indeed, using Kotlin).
Otherwise, yeah, you're still in for a world of pain.
t0mas88 1 days ago [-]
When building boring web applications with a sizeable team that need to run for a long time. Hiring developers is easy since there are many, there is nearly no magic and the language is quite strict and type safe so it works well with a large team.
And that "team" nowadays may also consist of many AI agents. In my experience Claude Code for example works very well with a typed, slightly boring language with lots of framework and library support. Because it doesn't compile when you get something wrong, instead of getting a vague runtime issue that Claude can't always see.
BoppreH 1 days ago [-]
> there is nearly no magic
I agree with the rest, but there's definitely a lot of magic in Java. This is from both what features the languages makes available (many) and how the community uses them (often). I've had so many hard-to-debug issues in Java over the years due to reflection, annotations, and bytecode manipulation shenanigans.
And another positive point for Java: checked exceptions. It's verbose, but knowing exactly in which ways a function can fail is extremely helpful for building robust applications.
kccqzy 1 days ago [-]
A lot of that is coding style. I’ve also seen a lot of hard-to-debug issues in Python caused by reflection, weird decorators that muck around with name-mangled symbols, and bytecode manipulation. You can even manipulate the traceback object so it’s more difficult to make sense of why the exception comes from.
It took me quite a long time to accept that the recommended unit testing library manipulates bytecode so that the exception message for `assert a == b` prints the values for both.
msluyter 1 days ago [-]
I haven't really been in the java space for a while now, but I recall there being a fair bit of criticism[1][2] of checked exceptions over the years.
WRT magic, I've generally thought that was a result of frameworks - Spring, for example. In the past, my feeling was that these impose a sort of meta/configuration language that itself is not checkable at compile time, so you'd get weird runtime errors that are somewhat inexplicable. This was like... 2018 though, so perhaps things have improved.
BoppreH 1 days ago [-]
I'd argue that checked exceptions are still worth it, even though all the problems pointed out do exist. And that's because it works to inform consumers of what a producer is doing. Haskell has the IO and Maybe monads; Java communicates the same information through IOException and other domain exceptions.
Many times I've decided to switch from one function to another, or even an entirely new library, because the checked exceptions told me that it was doing far more than I expected, and I was not comfortable introducing those new failure modes.
It's far from perfect, one still has to handle nulls and wrapped/merged exceptions, but overall I like this language feature.
voidfunc 1 days ago [-]
Checked exceptions are controversial mostly because a lot of the core APIs use them in places where it's pointless to check, like IOException.
Using them correctly can be great tho.
dmux 1 days ago [-]
>in places where it's pointless to check, like IOException
Can you explain why this is pointless? In my mind, this being a checked exception would hopefully be a hint that I should think about this failure-case and make an explicit decision whether to handle it or not. Network connection failed? Maybe I retry. Maybe I store that data somewhere else as a fall back. Isn't this similar to Go programmers needing to check if err is not nil?
mrkeen 1 days ago [-]
I don't think I can recall a time where I routed-around-the-damage on the basis of a particular typed exception.
As soon as you consider retrying a network failure, you immediately need to start thinking about distributed systems failures, idempotency, and all that good stuff.
As soon as you start thinking about the above, it becomes immediately obvious that low-level calls should not be able to decide to re-run themselves.
dmux 1 days ago [-]
>I don't think I can recall a time...
I appreciate that there is a _ton_ of different experiences out there when it comes to solving problems, but I _have_ encountered exactly the case I was describing, which is what led me to my original question. Isn't the fact that it was a checked exception that led you to "consider retrying a network failure, you immediately need to start thinking about distributed systems failures, idempotency, and all that good stuff" worth it as opposed to an unchecked exception you may not realize is being thrown?
PaulHoule 1 days ago [-]
Mostly I think they are a mistake, like in ordinary application code instead of catching close to the throw you want to do a lot of
try {
...
} finally() {
...
}
to make sure things get torn down that have to be torn down and let the exception go to the top of the unit of work and probably to whatever drives the work unit. You can probably do better than logging the raw exception and moving on to the next work unit but you can do much worse. That is, you want a default "sloppy" error handling approach that's correct that you can do without thinking and avoid other kinds of "sloppy" coding encouraged by checked exception such as catching exceptions locally without doing the right thing globally.
Occasionally though I have built something really sensitive, like an authentication filter for a web site which has at least 5 ways to log in and in that I have a hierarchy of exceptions and use checked exceptions heavily to document all the ways things can go wrong and felt like "the type system really has my back here" but that is like 5% of the Java I write.
samus 10 hours ago [-]
> avoid other kinds of "sloppy" coding encouraged by checked exception such as catching exceptions locally without doing the right thing globally.
That's a code style and code review issue; each project has so set standards regarding how errors are dealt with and enforce them throughput the codebase.
PaulHoule 12 minutes ago [-]
Of course!
But from a quality standpoint there are three concerns:
(1) Do you actually do the code review, do you actually enforce the style?
I worked on a Scala project where the dev manager thought it was preferable to handle errors with monads and would be vociferous about what a great practice that was compared to exceptions and that code review was central to how we do things... but if you looked at the code most of the time errors just got dropped silently and that was the same for many practices that the dev manager told me were doing but that we don't. He still posts on LinkedIn complaining about other dev managers who say they do code review but really don't. Practically that code didn't consistently give the right answers and poor error handling was one reason, another was that they never really understood that teardown was just as important as initialization.
(2) Is your documented practice correct? Is it really doing the right thing?
In a lot of cases there really is a right and wrong way to do things (e.g. uv resolves Python dependencies properly, pip doesn't) but it's less clear in error handling, like sometimes things went wrong and there is no way you can make it right and you can do the best that you can.
The global nature of the problem is vexing. Like an IOException might really be a BackhoeCutAFiberSomewhereInWisconsinException and a segmentation fault is occasionally a YouAskedForAOneAndGotAZeroInsteadException and it's not just academic because, given an exception, you want to answer questions like "Should I retry this operation? How long should I wait before I retry this operation?"
(3) Is this practice something you can sustain? How hard is to do? How much cognitive load does it add and how does it interact with other practices? "Throw up as much as you can", "tear down in finally {}", "otherwise handle local consequences of errors and rethrow" and "really catch errors at the drivers of units of work" is a practice that really works in many languages and is pretty easy to do right, even code that is written without a lot of care will do the right thing or something close by default. I've seen a lot of "no plan for error handling" or "bad plan for error handling"... like I was traumatized by the first C program I saw in a 1984 issue of Byte magazine which was using errno to handle errors which vastly complicated very simple code because the error path was intimately wound with the happy path and in cases like that there tend to be bugs in both of them. When I saw Exceptions in Java I remembered that old C program and thought "I love this!"
ndriscoll 1 days ago [-]
Scala's ZIO also demonstrates that they're a great idea and can be perfectly ergonomic, but you need type inference, which Java devs were resistant to for a long time (maybe still are? I remember lots of "how will I ever know what `val a = new Animal()` is???"). If you infer the exception type, they're basically invisible except for when you forget to have some place in your program to handle them, which is exactly what you want.
cavoirom 1 days ago [-]
I agree, to name a few:
- Annotation processing: if you know Lombok, MapStruct.
- Class loader.
- Reflection.
- Garbage collection.
samus 10 hours ago [-]
Annotation processors were actually carefully designed to prohibit what Lombok does. Lombok hacks into javac and manipulates the AST. Unsurprisingly, there is breakage with every Java release and with other tools that work similarly, like Google Error Prone, which gets a pass since it's read-only and the build will still work if you turn it off.
Class loader and reflection shenanigans can be shut down with the module system.
Garbage collection matters when you stress the JVM to its limits. Don't do that.
speed_spread 22 hours ago [-]
You forgot runtime agents!
IMO compile-time annotation processors such as Lombok and MapStruct are far from the most magic part of Java. They're straightforward code generators. Their impacts is localized to where they get applied and you can actually see the code that's generated. They're very good for diminishing boilerplate. They're no worse than Rust's very standard #[derive(xyz)] proc macros.
Having the code being generated on the fly (instead of a one-shot) means it follows the rest of the structure it's derived from i.e. equals() and hashCode() don't risk to be forgotten when adding a field to a class (hello maddening Map<> lookup errors)
Also, yes, Lombok is _funky_ in how it works but there are "pure" alternatives like AutoBuilder and AutoValue if one cares.
samus 10 hours ago [-]
Dynamic runtime agents are deprecated functionality. In a few releases agents have to be specified at JVM startup. Mockito (I bet it's the most common user of that feature) and current JVMs already warn about it.
Another issue with Lombok is that it requires IDEs and other tools to be aware of it. Missing integration with other annotation processors only causes "definition of external element not found"-style errors.
theandrewbailey 1 days ago [-]
> but knowing exactly in which ways a function can fail is extremely helpful for building robust applications
I've worked on Java apps that have failed in mysterious ways that no exception could explain. Meanwhile, the overhead of having to call out certain exceptions but not others in language syntax is a bit excessive.
For example, decoding a byte array (or URL encoded form field) into a UTF-8 string means handling a theoretical UnsupportedEncodingException. What the fuck? How the hell can one have a JVM that doesn't support UTF-8? Why does my code need boilerplate that will never run because there might be some broken-ass JVM out there that that doesn't support UTF-8? How did it launch a web server, safely load all the libraries, and accept a web request, and route it to my code without blowing up? "But the encoding scheme might change..." No, it won't change. It's always going to be UTF-8. It will always be UTF-8. If it's not, let it blow up.
kilink 24 hours ago [-]
That's less of a thing nowadays if you use the newer APIs that accept Charset instances instead of the charset name as a String.
Horffupolde 1 days ago [-]
Perhaps you are the trip you expected to blow up first.
cesarb 19 hours ago [-]
> And another positive point for Java: checked exceptions. It's verbose, but knowing exactly in which ways a function can fail is extremely helpful for building robust applications.
Sorry, but no, Java has the worst of both worlds here. It has checked exceptions AND unchecked exceptions, AND errors which are like unchecked exceptions but won't get caught by a normal catch-all (you're not supposed to catch Throwable, but it's the only way to prevent some dynamically loaded plugin code ten layers deep in the stack from breaking your invariants or stopping your periodic scheduled task due to an errant NoSuchMethodError or NoClassDefFoundError).
And you can't easily use checked exceptions with Java8-style functional code, since interfaces like Function aren't generic on the exception type. Which leads to aberrations like UncheckedIOException, which exists only to make IOException usable in the functional world.
unscaled 14 hours ago [-]
> Hiring developers is easy
> And that "team" nowadays may also consist of many AI agents.
And that's the part where the hireability arguments collapse. Sure Claude Code works pretty well with Java. It also works well with Typescript, Python, Go and Rust. It would use types on all of these languages, and run a type checker or LSP on the dynamic ones. And while Java is statically typed, Rust has a stricter type system that prevents some types of runtime bugs that Java's type system won't like data races and forgetting to release a resource.
gf000 9 hours ago [-]
> data races
Which are completely safe in Java
> Forgetting to release a resource
I know what you mean and it's definitely a tongue in cheek reply but it's almost like that's what the GC is made for :D bit less sarcastically java has Cleaners, and try-with-resources are a poor man's RAII
pmontra 1 days ago [-]
It works well enough with plain Ruby and plain Javascript. It ported a Rails 7, Vue 2, vuetify 2, vuex app to Rails 8 (ok, easy, I did it myself at least once), Vue 3, Vuetify 4, Pinia. I had to visually check the SPA, of course.
vladavetisian 20 hours ago [-]
Re: Java 27. I work with TON and this looks like something I can build. No pressure, but I can walk you through the approach if you want.
rwyinuse 1 days ago [-]
Java & Spring is a good choice whenever you want your application to work and be maintainable 10 years from now, without having to replace the framework and half of the libraries you used. I see few good reasons to ever use something with unstable ecosystem (like Javascript with NodeJS) over Java these days.
ulimn 1 days ago [-]
Not just the time, but the environment, the OS as well. Where you can run JVM, there's a pretty good chance you can run your app.
That's important since this capability has been removed from OpenJDK a long time ago.
jjice 1 days ago [-]
Stability is a good point, although I am curious where JS and Node stand there now. They're not at the level of Java or .NET by any means, but the JS ecosystem has definitely begun to slowdown over the last few years. I've used express for the server and winston for logging for years and years now and they've very stable at this point.
I guess I'm asking this as an open question: Where are we in the "move fast vs stable" spectrum with Node these days? Definitely not rock solid, but it's moving in that direction I feel.
chasd00 1 days ago [-]
It's a stable known stack. It's not hard to find Java developers and the AI Agents are probably pretty good at writing Java too. A Java backend will just sit there and do its job happily forever and you can bolt on whatever front-end you want. Spring Boot has been kind of the standard way to do Java web applications for probably a decade if not longer. It works fine has all the bells and whistles when you're ready for them and most Java developers who work on the web know Spring already.
As for an individual developer doing a side project, you should use Java if you haven't used it before to get exposure to it. It's a fundamental component of enterprise software and if you've never used it before take the time to learn something new.
mands 1 days ago [-]
Good question - am using for a greenfield AI startup in SF. Been a great decision so far: great ecosystem, bulletproof runtime, fantastic performance and new quality features arriving on a steady schedule. "Boring technology" at its finest.
When you are already familiar with it or work in a Java shop, there are better options if you are starting from scratch, but if you already have 50 guys that know Java it's a pretty big ask for all of them to switch.
doublepg23 1 days ago [-]
Why not Kotlin?
dorkypunk 1 days ago [-]
Most organizations that use Java tend to be pretty conservative with their technology picks and nowadays with newer Java versions the only real gap with Kotlin is null-safety which is supposed to also come to Java at some point.
There is also an organization culture component most of the time, one our engineers actually proposed to use Kotlin for one of the new projects but it got rejected because "We are a Java shop"
unscaled 14 hours ago [-]
> Most organizations that use Java tend to be pretty conservative with their technology picks
That part i s true.
> with newer Java versions the only real gap with Kotlin is null-safety
But that part isn't. Kotlin has:
- Structured Concurrency: Coming to Java sometime in the future, but it's been in preview for very long now.
- Standalone functions that don't have to live in classes
- Properties
- Property delegation
- Data classes: more powerful than records. Can be used for large DTOs that you can modify with copy(). Java needs something like Lombok to make records more useful.
- Extension methods
- Context parameters
- Operator overloading
- Implementation delegation
- Inline functions (which can receive returning closures and reified types)
- Block syntax (supports `it` for unnamed arguments)
- Sequence abstractions: more powerful and more efficient than Java streams due to the inlining and block syntax.
This is just a partial list, but Kotlin clearly has a lot of things that Java doesn't. If you only personally care about NPEs that's fine, but that's not the only thing.
gf000 11 hours ago [-]
> Structured Concurrency
I think the two languages mean slightly different things here. In any case, Java's model is so much more simpler that I don't think they are honestly comparable. In kotlin's case you have to be very on top of your game to have a chance of correctly using it - there is concurrency, parallelism, exception handling all combined into a single abstraction in a non-native way - so your stack traces will be useless/swallowed etc on incorrect usage. Of course the usual caveat applies, just use java's abstraction if you need that.
> Standalone function
Don't really see the benefit, if anything it creates place for style disagreements. A SomethingUtil class was just fine (and findable).
> Properties
Difficult topic with both cons and pros.
> Data classes
Exactly because they are "more powerful" they are strictly worse. A design element is just as much about what it is as it isn't. Copy is good though.
> Delegation
Used a couple of times, but it's not the full blown thing (see manifold)
> Extension method
I will be honest, I really dislike these. They can occasionally help a bit with some DSL, but for the most part they just make code very hard to read. I much prefer a normal static method instead.
> Context parameters
One of the few useful syntactic sugar.
> Operator overloading
Argued to death already :)
> Inline function
Feels more like a hack to support some of these extra features than something you would want to use yourself
> Block syntax
For the rare DSL usecase it's useful. Everywhere else I really dislike it and the accompanying coding style. These .also and similar implicit receiver thingies are just straight up evil.
All in all, there are a few things that are very elegant in kotlin, but I feel they went the c++ c# way of over abstracting just to have a long feature list.
doublepg23 1 days ago [-]
Are you guys keeping up with the Java LTS releases?
mikert89 1 days ago [-]
I think Java took all the best features of kotlin
unscaled 13 hours ago [-]
I think Java explicitly refused to take some of the best features of Kotlin, like extension methods, context parameters and operator overloading.
946789987649 1 days ago [-]
but still has all the historic bad decisions of itself
za3faran 24 hours ago [-]
Many of those are getting addressed. Value types are in the works, generics specialization for primitives, and even type classes.
946789987649 11 hours ago [-]
Right, but doesn't take away from the fact that Java maintains backwards compatibility and so all these legacy decisions will generally forever exist.
za3faran 24 hours ago [-]
What better options are there if starting from scratch? The only thing IMO that comes somewhat close is C# for general backend systems.
kllrnohj 1 days ago [-]
When you think a garbage collected language is a good fit for whatever you're building?
kfir 1 days ago [-]
Wouldn't you go with golang in that case?
ndriscoll 1 days ago [-]
Golang gives you none of nice features of a modern language while being about the same performance tier as Scala or Java, so there's basically no reason not to use Scala.
Thaxll 1 days ago [-]
Golang is faster and use way less memory than Java, never wonder why you never see Kubernetes controller / sidecar is Java?
As for Scala it's pretty much a dead language, no one work with it and it's impossible to find dev for it.
10 years ago I was moving Scala code back to regular Java.
weego 1 days ago [-]
It's sad - I spent a good 12 years writing Scala every day and it was the ideal language for my brain. Until it wasn't - sbt got too complex for it's own good, everything became "very smart" developers over-using implicit conversions, you couldn't find a project that wasn't an opinion war on cats vs whatever. It collapsed on the weight of it's own smugness.
go and kotlin aren't it, gleam scratches the itch but I can't justify writing code that would impossible to hire for.
scala situation is a real shame.
ndriscoll 1 days ago [-]
Every job I've has has used different languages so I don't really understand the need to find a dev for a specific language. I went from network firmware in C to banking application servers in Scala and it took like 2 weeks to ramp up. Not a big deal. Now I write lower level networking stuff again in Go, which seems like its just worse than e.g. C-with-templates (and occasional classes) style C++ so I don't really understand why people like it.
I think it used to be common to just look for smart people and assume they can run with whatever stack. Wasn't that the point of abstract algorithm questions etc. (basically an IQ test)?
logicchains 1 days ago [-]
>I think it used to be common to just look for smart people and assume they can run with whatever stack. Wasn't that the point of abstract algorithm questions etc. (basically an IQ test)?
Lots of companies where software isn't the focus see it as a cost center, so they'd prefer to hire lower-IQ specialists instead of higher-IQ generalists, because the latter are more expensive/have more options.
za3faran 21 hours ago [-]
Golang is not faster than Java, the opposite in fact. With native image, Java can be used for sidecar applications as well.
1 days ago [-]
jeffbee 1 days ago [-]
Java is almost always significantly faster than Go because the Go runtime does a poor job of exploiting large memory page, doesn't support text-on-huge-pages, and barely supports profile-guided optimization. With HotSpot you get all of this and more for free. Go is fine but Java is peak.
davidee 1 days ago [-]
This made me chuckle.
Aside: Scala dev here - but I only talk about how wonderful it is with people I trust (mostly Go and Rust developers I used to work with).
Also, Scala Native means I don’t always have to worry about the JVM depending on the use case.
ScalaJS is fun too.
eklavya 1 days ago [-]
I ported a moderate sized java project to golang. Test suite runs order of magnitude faster now. There isn't much change in terms of the architecture. Pretty much the same algos and data structures. The whole dev tooling runs on a 16 gb mac without swapping now. I used vs code for both
ndriscoll 1 days ago [-]
IME they're both in a place where Rust is maybe ~40% faster for a decent CRUD web application server, but with go you need to write much lower level code to get there (e.g. using composable generic iterators will ruin your allocations, so it's all manual for loops). You can write idiomatic high level Scala and get the same performance. Which could be as simple as the go compiler offers no ability to force inlining and has way too low of a complexity threshold, but that basically makes reusable code unusable in high performance situations.
The whole go team's philosophy tends to also revolve around assuming their users don't know what they're doing, which is annoying. Like an inline keyword: thinking you know better than me doesn't mean I'm not going to inline it; it means I'm going to manually write it inline myself in the code, and then think the language sucks because it's tedious, error-prone, and verbose. Or they tend to mark lots of stuff private for no reason, and e.g. with TLS 1.3 they just ignore your config because they think they know better, etc.
gf000 10 hours ago [-]
So you compare the build tool of A and B on a short-lived job type where java is knowingly not its strongest? How is that a meaningful comparison?
eklavya 5 hours ago [-]
I am not sure I understand your point. It's a meaningful comparison based on my situation. But even if it isn't, what are you arguing for? I change my use case rather than the tool/lang? Sorry if I misunderstood.
jeffbee 1 days ago [-]
Isn't this mostly about java cold start costs? It might be that other people are optimizing for steady-state performance, not transient startup performance.
eklavya 16 hours ago [-]
One particular test was running for 20 minutes, doing repetitive calculations, hopefully enough to get jitted. It finishes much much faster now. I could have profiled to check what was going on but the test was simple and the dev tooling and the ram usage was a major concern for me. Also gradle upgrades were painful.
Java tooling taking up a lot of ram was a major motivation for me. I have done a lot of Scala as well. I don't think either Java or Scala in the real world beat go on performance for most cases. I don't doubt that in some cases jvm can do better but at least before Valhalla delivers all the promises, in real world, I am doubtful.
I have been a Java/Scala user almost for the majority of my career. I doubt I would pick jvm over golang going forward though. Also not having to deal with OOP is a plus.
pron 1 days ago [-]
You could, but it's not as fast as Java, especially under heavy workloads, its telemetry is nowhere near as good, and it's much less popular.
mahboi 23 hours ago [-]
It's unsafe in many applications due to not having exceptions
topbanana 1 days ago [-]
I would, or C#
foolfoolz 1 days ago [-]
java is a great language for server side projects. it is actively maintained, the biggest issues with it have JEPs, and it’s very friendly to AI authors
topbanana 1 days ago [-]
When you work at a Java shop. Kotlin is much nicer if you have to run on the JVM but aren't restricted to Java.
mahboi 23 hours ago [-]
I don't understand the point of Kotlin anymore, now that Java has virtual threads and other stuff.
joe_mwangi 22 hours ago [-]
Once it gets nullness types, hackernews is gonna explode!
mahboi 21 hours ago [-]
Wait is this why people keep complaining about NPE? Is the nullness type thing a dog whistle for Kotlin? lol
joe_mwangi 20 hours ago [-]
Yes.. lol. The biggest argument now is Kotlin having nullness by default in the language. Just check around the comment section. Now java is planning to have them which will further help jvm to optimise for performance. Not sure what the next argument will be after.
nonethewiser 1 days ago [-]
Here is a niche one:
When you want to make a game and support modding with pretty much zero development effort.
stickfigure 23 hours ago [-]
Often? It's a relatively modern language with enough functional programming features to keep mid-high blub programmers happy. Yeah there's cruft; all mature languages have cruft.
Kotlin is the obvious replacement, but the tooling isn't as good and the community isn't as large. Java keeps getting better, and in ways that diverge from Kotlin - eg, virtual threads vs async/await/coloring. From the perspective of language design, I prefer Kotlin. But I keep picking Java anyway, and I don't see that changing soon.
Go is openly hostile to functional programming. Dynamic languages aren't even in the running. Rust is too low-level for line-of-business software. C# is too Microsoft. The remaining alternatives are too obscure.
skeletal88 1 days ago [-]
Same question for .net or C#
Why should anyone use it over Java?
Ms is hostile towards its developers, it creates new versions of things, deprecates previous versions, uses confusing naming for newer versions.. etc.
bitgeist 1 days ago [-]
Microsoft has come a long way since Satya Nadella took over in early 2014. The open-sourcing of .NET Core that same year was a huge step forward. Seeing a 'Microsoft Loves Linux' slide that year was something I did not have on my bingo card. VS Code and the GitHub acquisition demonstrated Microsoft's interest in fostering good relations with developers instead of alienating them. I do wish GitHub had stayed independent, though.
Microsoft is a business and will always put their business objectives first. In my opinion, they have a non-zero amount of evilness. I do not support them jamming Copilot into every available crevice. I still think they make dumb choices, like every imperfect organization. However, C# is a powerful and intuitive language, and for Microsoft shops that already run a lot of Windows and SQL Server it makes a lot of sense.
No shade to the JVM. I've mostly enjoyed my time in that space. I do believe the choice between Java and Kotlin, the wide variety of vendor JDK distributions and IDE fragmentation make the JVM stack a bit more difficult for newcomers to break into.
tester756 1 days ago [-]
Consistent ecosystem with state of the art tooling (Visual Studio)
Majority of things provided from MS instead of having to rely on 3rd party, especially nowadays when supply-chain issues are huge concern
dimaaan 1 days ago [-]
Null pointer dereference problem.
Solved for greenfield C# projects and Kotlin
Because your employer is dick-deep in Microsoft psychosis.
I've been a .NET dev for a decade now. It's perfectly serviceable, but I wouldn't say I truly love the language anymore, but I would take it over Java any day. Entity Framework and LINQ are gifts from the Gods. I have never used an ORM that even comes remotely close.
Also, C# is big in the gaming world. I am working on a game right now, and I was not impressed with what many other languages had to offer. It seems like the kings are still C(++) and C#. Of course, Java can create games, but I would argue that is a "could vs. should" kind of decision.
Unity, Godot, Monogame, Raylib, XNA, FNA, etc. all can use or rely on C#. I have not seen Java be compatible with any of those -- except maybe Raylib? I do not know of anyone nor any games that use it though.
cromka 19 hours ago [-]
ASP.Net Core is also a pretty good alternative to the ubiquitous nodejs? Especially wherever multi threading is required.
mahboi 23 hours ago [-]
It's decent for backends. I'd rather use JS, but there can be performance or ecosystem reasons to use Java. They fixed a lot of the gaps it had. JS used to have a big edge in async-await while Go had n-m multithreading, but now Java has the latter.
mikert89 1 days ago [-]
When you’ll have tons of low skilled devs contributing to an important but boring application that will last a while
pie_flavor 1 days ago [-]
Java-the-language blows, but Kotlin does not, and Java-the-platform is on the Pareto frontier of oldest-yet-most-usable open-source ecosystems. I prefer Rust, and the gaps where it doesn't apply, C# fits my use cases better, but Kotlin/JVM is a rock solid development platform.
hobo123 1 days ago [-]
I once tried Kotlin for a pet project, but after a while switched it back to Java, since it's good enough, IDE support is better, it's much faster.
anecdotal evidence: I‘m working on a product in circular economy space at the moment (chemical trader). I chose Java because it just works and allows us to focus on business, no npm supply chain drama, no „how can I integrate my go microservice with a customer SOAP endpoint“ problem, time to hire under 2 months etc.
23 hours ago [-]
pjc50 1 days ago [-]
JavaFX is fairly high up for "I want to run the same UI on Mac and Windows, and I don't want it to be an Electron web app in a box".
whartung 1 days ago [-]
JavaFX is a hidden gem. I really like the programming model with its binding and scene graph and CSS.
It's not as portable as Swing, as it has some platform specific binary components to it. But it works fine on mainstream platforms. For me Swing portability is not worth giving up the FX model.
Just be aware that if you happen to bundle in the Web view component, you're basically adding WebKit to your distribution. I did this with a small project because I wanted to have a "help" screen with Markdown -> HTML. Easy, but "expensive". It simply adds a big chunk (10-20Mb) to your distribution.
(Now I have a very crude Markdown renderer for this task, which is a 100 lines code, and I'm working on a better one -- but I have yet to pull the trigger on the latest FX with its new Rich Text component, which could change everything.)
One hot tip with cross platform FX, however. Embed your fonts. The font suite is not common across the distributions, and the CSS does not honor the font fall back (i.e. if not XXX font, then YYY font), so if the runtime doesn't have your specific font, it collapses to the System font. So, embedding the fonts you use helps a lot with cross platform stability. Plenty of free fonts, I have not had a real problem with this. But it can be one of those O.o moments when you test on other platforms and encounter it the first time.
DanielHB 1 days ago [-]
Java and C# seem to be the best ways of making code-first OpenAPI based servers.
C# LINQ also seem to be the best compromise between ORM and raw SQL queries, although I never used it myself.
I have been severely disappointed in all similar solutions for Go at least and I imagine Rust does not have something better given it has a smaller community-base.
Python and NodeJS have some very neat solutions for this stuff too, but both are "slow" dynamic languages. I personally dislike python with a passion and NodeJS stuff is extremely community-driven and therefor often unreliable. Prisma (NodeJS ORM) for example just did a major overhaul and is now pushing a completely different API.
If you are making boring REST API to SQL Database it seems like Java and C# are the best options.
CharlieDigital 19 hours ago [-]
C# gRPC story is really, really good, too.
DarkNova6 1 days ago [-]
Yeah. Why use a statically typed reliable language with a good ecosystem if I can also vibecode in Python.
orangesilk 1 days ago [-]
Jruby is nice - runs Ruby on a Java Virtual Machine with full concurrency and Ahead Of Time compilation
1 days ago [-]
soco 1 days ago [-]
Whoever works with, or chooses Java, is not doing it for the language itself, be it beautiful or not. Java has a huge ecosystem, from battle tested integrations to optimized images to build pipelines to whatever, so at the same you're buying access to all this world (yes, more than an environment). And of course transferable skills. I'm not saying Java is alone offering this, also not saying every feature is the best, but you can have them all, and even choose from different options.
marcosdumay 1 days ago [-]
Java's ecosystem is lingering since Oracle brought the language, and it's at the point where you should really look if the things you want to use are still in the state of the art, or if they felt behind every other language.
And if you are starting from scratch, whatever part of the ecosystem you use, I'm not optimist on its situation improving with time.
za3faran 14 hours ago [-]
It hasn't been lingering since they were bought by Oracle. We've seen a rapid increase in the pace of feature and performance development.
rzmmm 1 days ago [-]
I use Java for hobby projects, I think it's design choices make it a nice minimalist language for "classic OOP" style: dynamic dispatch, encapsulation etc.
Nowadays a lot of code is written with mostly procedural style with some functional characteristics, I wouldn't use Java for that.
exabrial 1 days ago [-]
roughly 100% of the time
arein3 1 days ago [-]
Project Valhalla will go into Java 28 (next year, and preview version).
Fingers crossed I'll manage to use null type safety in my lifetime.
pregnenolone 1 days ago [-]
> Project Valhalla will go into Java 28
Unfortunatley without specialized generics and without the performance benefits that are supposed to come with it. They were too slow with Valhalla.
rf15 1 days ago [-]
universal null type safety has sadly been discarded as a core concept of Valhalla in my Understanding; that being said, the proposals in Valhalla have null-safety as a side effect, but only under certain conditions.
MrBuddyCasino 1 days ago [-]
When (if) null safety ships, I‘m not sure I can justify using Kotlin any longer.
Java simply got too nice.
hn8726 1 days ago [-]
Multiplatform and Compose come to mind as Kotlin differentiators. I agree rest of the language actually slowly falls behind
esafak 22 hours ago [-]
Falls behind how? Isn't it still ahead? Kotlin keeps changing too, you know!
joe_mwangi 22 hours ago [-]
Really? I don't thinks so. There is a reason JEP 539 is in preview. There is a reason internal annotations exists in current valhalla jdk prototype and upcoming java 28 such as @jdk.internal.vm.annotation.NullRestricted, @jdk.internal.value.ValueClass.newNullRestrictedNonAtomicArray. Also, there is a reason value classes are allowed to be null. This is because nullness types will be a key factor to the java language.
retrodaredevil 1 days ago [-]
NullAway with JSpecify annotations are a really good way to add null safety to Java applications. Even enforces nullability at the generic level.
rendaw 1 days ago [-]
The elephant in the room is the standard library (collections). It isn't even type safe yet, because some methods were around before generics were added. And collections are too core for anyone to be able to agree on a 3rd party standard.
retrodaredevil 4 hours ago [-]
I mean, I guess you could say stuff like get(Object) and contains(Object) aren't type safe, but I've never seen that as an issue in practice. Plus there are some ErrorProne checks that'll tell you if you're doing something wrong in regards to calling contains(Object) with the wrong type.
brabel 1 days ago [-]
You really need to dare to try something else, Kotlin has been available for many years and the cost for a Java shop is super small since the same tools work with both.
I’ve used also Dart which is lots of fun. Even Typescript can be a good alternative depending on what you’re doing. All have nullability guarantees and a nicer type system than Java while being in the same ballpark in terms of performance.
karussell 1 days ago [-]
A first step of Valhalla that is ...
1 days ago [-]
pineappletooth_ 1 days ago [-]
Still pretty fun that here banks are still using Java 8, where i work they use java 17 and you can still find work requirements asking for java 7 (mostly in goverment entities)
declan_roberts 1 days ago [-]
That's one benefit of using Java actually. You can always find Java developers.
MisterMunchkin 12 hours ago [-]
But why? Surely you can just run the original code on the new version?
jan_m_savage 12 hours ago [-]
This thread, which I read most of, reads like a 'how to be a language-war thread without explicitly being one'. :)
Just use the language the (keeps putting) puts bread on your table. All languages have their own 'baggage'.
Wrt to AI, I agree with pron's comments that for very large code bases, AI can't do 4$hit.
Good4boothee 9 hours ago [-]
Not a fan of JEP-531, looks the repeat of Optional. Are we really so afraid of extending syntax that we are going to add a class instead of field modifier?
private static final Logger log = Logger.getLogger(Whatever.class);
This was already verbose enough that Lombok has @Log4j for it, adding `lazy` keyword in front of `final` won't make it any worse.
svcrunch 1 days ago [-]
I've been using the Vector APIs for years now, and I'm still waiting for them to GA!
They are useful for neural information retrieval (RAG, memory), which relies heavily on content vectorization and similarity matching using their dot products.
Java consistently introduces lots of exciting functionality into the language. That's a big part of my dislike and active avoidance of it.
tpoacher 1 days ago [-]
In the absence of a sarcasm tag, would you care to explain your reasoning?
liampulles 19 hours ago [-]
I have a strong belief (mostly borne out by my years as Java dev) that the larger the possibility space of a language (and its ecosystem) the more room there is for inappropriate use and unclear code. Add time and an assortment of rolling devs of mixed ability level, and crap mounts faster then a "simpler" language.
Let me be clear: Do I think it is possible to write good, clear, performant code in Java? Of course - Java can be used to write great software. The problem is that in Java there exist an extraordinary set of variations of a sufficient implementation, many of which are package protected abstract static horrors shows. And that will manifest over time if you add different individual developers. Else the project must engage in bureaucracy and control, where you have meetings over style conventions or hardline architects who come to constrain the joy of programming in the devs.
Its much better when the language itself constrains you, then everyone can just move on. I feel Go, though certainly not perfect, meets this niche.
gf000 10 hours ago [-]
Java is a very small language, all things considered. Compare to kotlin, c#, rust it's absolutely tiny
liampulles 19 hours ago [-]
Also - and this is very subjective - I find developers who have similarly low view of software development and high view of simple languages, to be very good team mates. We can laugh at the realities and move on pragmatically.
tpoacher 11 hours ago [-]
I see. I tend to agree in principle. I felt this with julia for example and it put me off a bit even though I was very keen on it at first
In the specific case of java however I don't think this applies much; the vast majority of improvements are aimed at under the hood optimisations rather than syntax. And I feel that any changes in syntax have been quite incremental, intuitive, and reasonable.
AND they stay around as preview features forever, so this may inflate the perception of features entering the language, when in fact it's the same one feature being mildly iterated on.
E.g. the main syntactical change in this version seems to be the use of primitives in switch statements, which was already discussed for a while, and is itself a meaningful change brought about by the introduction of switch expressions.
liampulles 7 hours ago [-]
Fair.
za3faran 14 hours ago [-]
Golang tried to be simple, but reality hit and they started adding features (generics, and now generics on method, iterators, fixed loop variable scoping). Yet when you program in it, you can feel so much resistance because it took a naive approach to simplicity.
liampulles 11 hours ago [-]
I agree with you, generics reduced the simplicity. Still, better than many alternatives.
1 days ago [-]
rf15 1 days ago [-]
> Twelfth Incubator
Are you sure this egg is actually viable?
I mean, I'd love to see it, but...
papercrane 1 days ago [-]
The JDK team made a decision awhile ago to hold the Vector API until value types are final. That's why the API has been incubating so long.
okokwhatever 1 days ago [-]
Love Java vs Hate Oracle... My life is a shit show
Good4boothee 9 hours ago [-]
I mean, thanks to crazy data center over-investment there is real chance that Oracle will need to sell some of their assets. Somehow I can't imagine many potential buyers that would be a good custodian of Java.
znpy 11 hours ago [-]
Java is open source and you can get jvm and jdk from other vendors (eg: Red Hat).
You can essentially live all your java life without ever interacting with Oracle.
taspeotis 1 days ago [-]
Knock knock
Who's there?
long pause
Java
Betelbuddy 1 days ago [-]
High Speed Trading team using Java looking puzzled ....
This thing is a-ma-zing. It is truly a wonder. It is something to behold. The "Disruptor" pattern.-
pohl 1 days ago [-]
the long pause is the JVM starting up
1 days ago [-]
what_hn 1 days ago [-]
Agentically convert to rust, get more trades faster
lazystone 1 days ago [-]
Oh, I see, jokes from 30 years ago!
HelloUsername 1 days ago [-]
The long pause between the knock and the answer took 30 years
Boereck 1 days ago [-]
Alternatively:
Knock, knock
who's there
It's the 90s, wanting their jokes back
emil-lp 1 days ago [-]
Why do they want the jokes back?
smrtinsert 15 hours ago [-]
What else are you supposed when keeping up with platforms proves too difficult!
nairboon 1 days ago [-]
And still funny today, not all jokes survive that long.
hobo123 1 days ago [-]
If I'm not mistaken, OpenJ9 runtime offers some kind of precompilation and fast startup.
winrid 1 days ago [-]
Don't put a 30gb heap on m4 boxes and you'll be okay
mahboi 18 hours ago [-]
*OOM-kills 20 other programs on your PC while booting up*
nsxwolf 1 days ago [-]
G1 is now the default garbage collector in 27.
taspeotis 1 days ago [-]
Sorry I only just set -xXx360NoScopexXx=4G to run that latest version without hitting OOM and you are right, it is indeed quite fast with G1.
paulddraper 1 days ago [-]
You misunderstand, xxx360NoScopexxx is now the default.
azatom 1 days ago [-]
It's on you bc it was (arguably but still) dead 17 years ago.
edit: don't get me wrong, i am not here to hate java, but bc i am also ... hm necrophil :)
taspeotis 1 days ago [-]
17 years is a rookie number. Take C# auto-properties, for example: 19 years ago!
Now try asking, "When did Java get auto-properties?"
azatom 1 days ago [-]
I never cared about those kind of syntax sugars. I was happy with IDE/static checkers, lombok, mapstruct, etc.
I missed generators like that virtual threading for example.
btw: i was referring a single point when oracle bought sun and "closed java".
marginalia_nu 1 days ago [-]
News at 11.
Language that runs on the explicit design philosophy of letting other languages experiment first and then incorporating their lessons learned once the dust has settled is late to implement a feature.
aitoolcrux 1 days ago [-]
[flagged]
gtadesktop1 1 days ago [-]
.
paulsen 1 days ago [-]
Why do I need Kotlin, or Python, or C when Assembly is enough?
gtadesktop1 1 days ago [-]
Yes true point. But why use Java? I mean you said another language that is an alternative for Java.
paulsen 1 days ago [-]
Well, ask yourself this, why use a hammer to drive a nail when a rock can do about the same?
Same reasoning here, Java is "enough" in of a lot of areas that make it desirable for a lot of things, it is fast enough, stable enough, has lots of libraries and apis for a lot of things you may want to do, and the jvm makes it reasonably portable, among other things.
Is that actually a good thing?
But Oracle started playing a game, for better or worse, where they decided to couple the "language version" with a single specific runtime's release schedule. For example, in "Java 27" there are exactly 0 language changes and 1 minor feature addition to the TLS library.
Everything else is OpenJDK runtime internals which don't impact the language or how you use it. So if you don't use OpenJDK (such as if you use Oracle's other runtime, GraalVM), then Java 27 basically doesn't even exist at all. Skimming the past couple of C# releases, it doesn't look like Microsoft is playing that game, so the release cadence will of course be different.
So now they have a precise release cadence that features can fall into. If it is a large feature, it better get worked in incrementally (via feature previews) because it is unlikely to be able to land completely within the release window.
One could pessimistically say the faster release cadence partially serves to provide more opportunities for extended support revenue, though.
And of the 4 non-preview JSRs in the Java 27 release, only 1 of them is actually part of the "platform version".
The other 3 are strictly changes to Hotspot internals with no platform involvement at all. They did not change any aspect of any Java platform in any way whatsoever. That is what I'm referring to. I'm not referring to the fact that the core library, language syntax, and runtime specs are all part of the same version. I'm referring to the fact that Hotspot specific behaviors and adjustments are also branded as being part of the platform release.
Like there's no Java 27 platform spec that says that G1 is the default garbage collector. That would of course be an absurd platform spec change. But that is still somehow a "feature" of the Java 27 release according to Oracle.
BTW, Java is developed "code first", which means that we first work on the implementation in OpenJDK, and then extract the relevant spec changes from it.
> But that is still somehow a "feature" of the Java 27 release according to Oracle.
It's a feature of the OpenJDK JDK, which is, indeed, the Java implementation done by Oracle (with contributions from others). The language is very careful, as you can see in the announcement: "JDK 27, the reference implementation of Java 27". The Java SE 27 spec is here: https://www.jcp.org/en/jsr/detail?id=402
I'd read it.
Admittedly, the terminology here is almost designed to be maximally confusing, and I’ve never read a good post that laid out how everything relates.
I do think it’s plausible this is a smaller release. Not that this was the real point of the discussion, but I think it’s still just a good idea to have more than one release a year. It keeps things moving smoothly, and lowers the cost of missing a release, which has beneficial effects.
The closest C# has is mono.
This sort of thing is bound to happen with that situation. Heck, it happens with C++ whenever a new C++ version comes out. Some C++11 features took years to make their way into all the compilers.
I don’t think there’s a single alternate implementation that doesn’t leverage a good chunk of openjdk somehow
Embedded systems versions tend to have their own ways, which is why despite everything Android using Dex isn't a first in the Java ecosystem.
All these years afterwards it quite clear that there is just similar fragmentation, and implementation differences between all OEMs selling every kind of devices, and as you say the format doesn't really provide that much benefits.
What ART has going for it, are all the improvements they started on Android 7 and later, by having a mix of handwritten interpreter in Assembly, JIT compiler with cache, AOT compilation with the device on idle, and sharing of PGO metadata via the PlayStore across devices.
Ironically Windows Phone did it first, with MDIL on Windows Phone 8 followed by .NET Native on Windows Phone 10, using compilation via the Windows Store, but Microsoft fumbled the delivery.
.NET on the other hand did not keep backwards compatibility, so a List is not a List<X> so .NET had a schism where some API functions use generic collections and others use non-generics which was annoying in its own way.
Something like that is how all methods in Java are virtual whereas methods in C# may or not be virtual. All-virtual is probably not the best for performance, but it is simple for understanding. You never have to think "do I make this virtual or not?" or think "is that method virtual or not and does that have consequences for how I use it?"
int is not an Object, so just erasing is no longer a valid approach, you need to specialize the class/method itself to use int-specific byte code.
A big difference between both ecosystems is that the Java world is like C and C++, even though Java isn't defined by ISO or ECMA, since Sun days the main implementation is only a reference, there are official documents for everything, and there is a plethora of implementations, with various kinds of JIT, GC and AOT approaches.
You can pick the real time versions for embedded from PTC and Aicas, the cloud first from IBM and Azul with finance markets in mind, the Android cousin, the various implementations for M2M gateways, copiers and phone dashboards (Ricoh, Xerox, Cisco), IoT with microEJ, and many more.
Whereas Microsoft hardly cares about ECMA nowadays, most of Mono/Xamarin is gone replaced by Core CLR and modern .NET, .NET Compact is gone, community maintained and so on.
That alone, regardless of the languages on top of JVM, or CLR, makes a big difference on the audiences when one silos themselves to a single ecosystem.
They made a conscious decision to switch to a regular six-month cadence and it's been all the better for it. The preview-mechanism has been terrific there too, allowing half-baked features to be aired without absolutely committing to something that turns out to be flawed.
Edit: Ninja-ed by Romario77's sibling comment :)
This is a good thing. In Java everything has multiple community offerings, so before doing anything you have to evaluate the community offerings and decide which one to go with. If you go with the wrong one you may end up having to switch at some point, and that can be painful. This happens so often that most of the time spent when using Java is doing these evaluations and comparisons. With C# you just use the one built into .NET platform. Saves a ton of time.
There is this division of labor between systems programmers and application programmers and often we think systems programmers are better because they know more about algorithms and data structures and compilers and assembly language and such. On the other hand, application developers understand how to reconcile the mental model of managers and employees and customers with computers, reality and common sense and, once they get experienced, see the commonalities between all the run-of-the-mill bizapps that we are coding all the time.
Application programmers do a lot better at applications framework than systems programmers and make things like Ruby on Rails and Spring. Systems programmers make terrible things like ASP.NET MVC (I worked out a way to do MVC with ordinary ASP.NET, why couldn't they, with access to the platform internals?)
All of my projects are based on Spring and I don't really have to look outside of that ecosystem. It almost acts as an aggregator of different open source solutions and often works by abstracting the functionality so that differences are not that big. I recently switched messaging providers and didn't have to change much of my code.
The Core (smaller, but also properly crossplatform) project begun in 2014, fairly major rewrites with breaking changes in the new releases up until 2019 (.NET Core 3.1) and 2020 (the 5.0 release that became the official major "unification" with most parts of Framework having newer alternatives and being "complete" even if 6.0 and 7.0 patched holes).
Projects started with core 3.0/3.1 in 2019 have a pretty easy and clear upgrade path without major breaking changes up until today.
It's not JS/Node volatility, and the cleanups in the language/runtime were well worth it in hindsight (still maintaining old 4.8 applications running under IIS), also 4.8 is still nominally supported so there's no immediate stress in upgrading (There are better semantics today, but with huge projects those semantic differences, mainly no lazy-loading by default are a risk).
That's a pretty low bar to beat.
I had a sheaf of notes about the problem and figured out the math to build a proper dependency resolver for Python and tested out a lot of ideas such as being able to use http range requests to get the metadata out of wheels on PyPi without having to download the whole wheel.
The problem I had no solution for though was "how to stop developers from trashing the environment that the dependency manager runs in." The data scientists I worked with had an astonishing target for wrecking anything at all. Myself I would have my poetry's environment got bad for reasons I didn't understand every few months ago.
I also found the Python community just didn't care that pip didn't really work right. The most seductive form of blub is "I can accept using things that fail intermittently." I got a job coding Java and Javascript and never built the package manager.
Then uv came along and managed to sell itself as "crazy fast" which did connect with people more than "correct". Written in rust, uv would have beaten my system in the fast department, and since it is a binary, there is no way anyone can screw up a Python it depends on -- as I see it, both technical and marketing genius!
[0] https://stackoverflow.com/a/14822245/61938
I used conda back in that period, it had a correct solver, and it was easy to manage my own packages, but it was slow in the technical sense of "it takes forever to build an environment" and slow in the business sense in that you got something curated which was not always the greatest or the latest but would, back in the day, "just work." Actually you could vendorize any software you need and have your own conda wheels, like I made wheels with different versions of CUDA drivers which are just DLLs so you could be running models with two versions of tensorflow that required two different versions of CUDA and never have to touch the NVIDIA installer.
But today it is a more "just works" experience to use PyPi instead of conda so I don't use conda.
Poetry was a big improvement over pip but I don't believe the resolver was 100% correct (like from looking at the source code) and performance was not that good, not so much because it was written in Python but because it did not have a proper cache, did not exploit concurrency. The Python way would be to use a world class SMT solver for the CPU intensive bit but when the bits hit the bus Rust is better at exploiting concurrency.
So I am happy to have uv.
https://en.wikipedia.org/wiki/Log4Shell
https://learn.microsoft.com/en-us/dotnet/core/install/window...
I think this is because the JRE/JDK upstream releases are a bit like Linux kernel releases: all the major first-party feature development goes on in subprojects that maintain their own "living forks" during feature development, with the teams on these features doing PRs against the fork's own "main"; that "fork's main" having its own subproject maintainers who ensure a mess isn't made of it; and then those maintainers eventually polishing up that fork-main into a single big one-shot PR to upstream once the feature-as-a-whole is ready.
(Compare/contrast: the Linux kernel's mm, rt, and kvm feature development efforts.)
Because of this, the top-level "project maintainers" (i.e. the people who decide what gets merged into upstream main) aren't really the same people as these subproject people who care deeply about these new features. They want to ship stuff people want, but they personally mostly deal all day with requests to merge 1. small bugfixes, and 2. features so small that no JEP is needed.
But then, every once in a while, they have to deal with a request to merge one of these huge subproject upstreaming PRs. And sure, it's already heavily reviewed by the subproject's maintainers, who they trust. But they do still have to audit it and learn it and create a stabilized release path for it. "Handover" stuff. And that's tiring!
So, given that the toplevel project maintainers write the release notes, I'm not surprised they come off as weary about releases.
(That being said, for purely PR reasons, the toplevel maintainers could ask the subproject staff to contribute their perspective to the release notes of a release that merges their work? But this could also just-as-well be a separate blog post—which would probably be better for sharing. I don't think I've ever seen a centralized Java blog [is there one?] but I think the subproject teams do tend to have them.)
Java tried to do fairly large updates and sometimes the release cycle would be very unpredictable as things would slip and take much longer than anticipated.
So to make it more predictable and to keep updates coming they switched to 6 months cadence with long term support (LTS) every two years.
This I think is a pretty good way of doing things, makes people who plan things figure out how to split feature development into these 6 months cycles, it made JEPs more granular and I think it made project Valhalla possible, if they tried doing it the old way it would never happen.
Splitting things in small chunks clarified what needs to be done and the path forward. It still takes very long time, but doesn't cause big incompatible changes and I think overall Java has good progress without being stalled.
Second was about licensing and Apache Harmony.
So, eventually they dropped most of the big things that were planned - Project Lambda with closures, Project Jigsaw with modularisation, Collection Literals. They eventually came back, but took a while to implement, so it was a prudent decision to make.
Granted, the maintainers are more inclined to deprecate and remove parts of the API than has historically been the case but it is mostly obsolete things like applets. And you may need to keep a close eye on runtime flags and their effects.
Their process is still very deliberate, they go to some lengths to avoid getting it wrong when they add new features to the standard. New features have to get through their preview phase successfully before becoming final. [0]
They're also pretty committed to not breaking existing source code or bytecode.
[0] https://openjdk.org/jeps/12 JEP 12: Preview Features
But Java 8 was stable (as in APIs, not judging its quality here) and since then it's gotten good again.
It may be that we are shielded from edge cases because we are based on Spring, which is probably the most tested piece of software before new versions of Java are released. But it's my impression that the risk of upgrading to a new version of Java is not the same today as it was in the past. The only advantage of an LTS is that it is supported longer, so that you can postpone the upgrade if you really want. It's not as if the intermediate releases are inferior or less safe.
You can almost think of the LTS releases as a major release and the non-LTS as a minor release, so really this could be 25.2. The current Java release schedule is to maintain a consistent and predictable release cadence instead of pushing big new features every 6 months.
A fixed release schedule makes development more relaxed so it can be properly done, no need to rush for some release date.
If it's not yet ready, there is 6 more months to get it merged.
While still being behind on most features?
> Let me get this straight: we're behind the [other languages] and we're going to catch up to them by going slower than they are?
Gotta go faster if you ever wanna catch up. However, Java is also purposefully slow. Everything is extremely considered. And while it means it takes a while before you get a feature it tends to be pretty good.
Used to work at a company which had services both in Java and C#, so some of Java's decisions or indecisions felt like pain points when switching between the two:
- Proper IEnumerable with proper iterators that in turn enables Linq (but in general permeates everything and is insanely easy to use and build upon). E.g. building an async service that behaves like an IEnumerable? Implement two methods.
Collection is halfway there, but I honestly cannot remember what was irking me about it in comparison to C#.
- Properties. Yeah, yeah, sealed classes, records and all that. Often you still need plain old classes.
- object initialisers. Which makes constructing anything a breeze. And on top of that you don't need manual .of methods for anything Colleciton-like if it'sa an IEnumerable.
- extension methods.
- named and optional arguments in functions
- null coalescing operator
- generics over primitive types (unless it was already implemented, I remember seeing a JEP about it)
- async/await. Yes, I know: different approaches to concurrency and all that. A lot of unnecessary verbiage could still probably be hidden behind a friendlier syntax.
- (sadly impossible in JVM to type erasure, only including this because I remember needing it many moons ago) generics metadata in runtime
- .... definitely a bunch more I don't remember at this point ...
That's absolutely a valid language design and many people prefer that, but I personally prefer a bit smaller language with a bit more IDE auto complete, but where you never have to think about what exactly does a line do.
(And then there is also Go that falls off the other edge of the cliff with useless if err checks spamming the code making actually functioning error handling hard)
If you need IDE to autocomplete, then you definitely spend more time to understand what a line does ;)
- Value types is a huge one to add if we're looking at what is actually in the wild.
- Scopeless `using` declarations are nice for RAII like behavior.
- IMO C# builds are actually way way nicer than Java. Sln and .csproj files and nuget are actually a lot easier to deal with than javac/ant/mvn/Gradle. Maybe that's more a .NET thing than a C# feature.
Probably not a good idea since they break encapsulation by exposing internals of the class. There is work on withers, which should make defining builders far simpler.
> - extension methods.
They make code harder to understand. If they ever come they would have to be declared at the top of each source file.
> - null coalescing operator
Maybe we'll get it, maybe not, but they want to first introduce proper nullable types, lest there is a risk of painting themselves into a corner.
> - async/await
There is a fork in the road, and Java has gone into the direction that leads to virtual threads and Structured Concurrency, for the simple reason that there is no simpler syntax than plain old synchronous code.
> - ... generics metadata in runtime
There are plans to add a kind of reified generics, so maybe we'll get it.
And thousands of manual get/set functions don't?
Thousands of lines of builders don't?
Object initializers are that plus much better handling of fields/properties that doesn't require hundreds of lines of tedious manual code: https://learn.microsoft.com/en-us/dotnet/csharp/programming-...
> for the simple reason that there is no simpler syntax than plain old synchronous code.
But it's not synchronous code, is it? It's easily dozens of lines wrangling Futures, and Thread initialisers, and Executors, and...
Java always opts out for "let the developer handle all the complexity all the time even for the simplest most used parts of the code".
My statement doesn't apply to mere data carrier classes. Anyway, getters and setters are an antipattern as well since one can just as well make all the fields public.
> Thousands of lines of builders don't?
With withers most of these will go away. And a class will be able to choose which things can be set, which is not the case for initializers.
> But it's not synchronous code, is it? It's easily dozens of lines wrangling Futures, and Thread initialisers, and Executors, and..
That code won't look that much different with async/await.
By definition, they don't.
They are verbose and hard to maintain, but if ever in the future you would want to keep the same API surface but change the internal implementation detail, they let you.
> But it's not synchronous code, is it? It's easily dozens of lines wrangling Futures, and Thread initialisers, and Executors, and...
No, it's done under the hood by the JVM. You only ever see a blocking call on a new "thread", via debugger via everything. Best of both worlds
So do properties in C# which object initialization relies on. With significantly less manual code, or the need for tedious builder chains and withers.
`{ prop = x }` is no more encapsulation breaking than ` .setProp(x) `, but actually makes developer experience better.
> No, it's done under the hood by the JVM. You only ever see a blocking call on a new "thread", via debugger via everything. Best of both worlds
What's Java's equivalent of
Are you referring to generators?
> Properties
As far as I'm aware, it is a deliberate choice not to implement them, and I can see their point of view.
> object initialisers
I believe the same justification applies here, it's mainly syntactic sugar, and can result in certain undesired behavior by bypassing constructors where validation can happen.
> extension methods
Typeclasses are currently being explored, which are a superior approach.
> named and optional arguments in functions
Those would be nice (at least named arguments). I can see how optional arguments could complicate things.
> generics over primitive types
As you mentioned, it's in the works
> async/await... verbiage could still probably be hidden behind a friendlier syntax
The approach they took does not need any extra syntax.
Both I guess.
Main thing is https://learn.microsoft.com/en-us/dotnet/api/system.collecti... which seems to be everywhere in the language and the library.
> I believe the same justification applies here, it's mainly syntactic sugar, and can result in certain undesired behavior by bypassing constructors where validation can happen.
This is mostly due language design. Java heavily relies on properties and provides no facilities for them. Hence the builder pattern instead of object initializers.
In C# object initializers synergize with properties: https://learn.microsoft.com/en-us/dotnet/csharp/programming-...
> The approach they took does not need any extra syntax.
You mean it needs 15 lines whete C# needs one? ;)
The vibe selects the audience perhaps.
People who are tired and just want to finish their work like the first style. People who want to do more cool work more quickly, maybe without finishing, like the second. Depends on the work, I suppose.
I mean it's short and concise and there are additional resources that provide more detail. IMHO it's not a bad thing.
Anyway, not every release can be filled to the brim with new features, and people were also kinda busy whipping Project Valhalla into shape. INHO it's still preferable to stick to a predictable schedule instead of creating uncertainty in the community.
Seems about right
Not 100% on board with the collection expression changes (I found fluent Linq chains usually more readable), but they're improving painpoints so I think it'll work out in the end hopefully.
> Isn't the fact that it's just sugar a huge benefit?
My main gripe is that I cannot remember what is allowed and not allowed between multiple versions of the same language. On a daily basis I hop between apps versioned in .NET Framework 4.8 all the way to .NET 10. I have to constant remember, are nullable types allowed here? What about 'new(); vs. new Object();', new collection syntax, new switch syntax, new extensions syntax, etc..
Plus, I just find it obnoxious that the same thing can be written so many different ways. I can think of 7 ways to assign a new empty List<T>.
List<T> foo = new List<T>();
var foo = new List<T>();
List<T> foo = new();
List<T> foo = new List<T> { };
var foo = new List<T> { };
List<T> foo = [];
var foo = (List<T>)[];
There are probably more that I am forgetting. What irks me most is Java is older than C#, and from what I can remember, it is not nearly this ridiculous in terms of syntactical sugar. So, what is the true benefit behind all this sugar? It hardly saves any keystrokes in the age of autocomplete in IDEs.
I am inclined to believe most of the sugar is an attempt to make the language appeal to a newer generations of programmers. But I would argue features are more attractive than syntactical sugar. I believe Rust is truly impressive language. In my opinion, its syntax is uglier than sin, but that does not seem to deter many from using Rust.
That's why you get `new List<T> { };` Because it could be `new ComplexObject { <fileds and properties> }`.
Same for `new`.
Some come from type inference which Java also has.
That's why you can have `List<T> foo = new List<T>();` and `var foo = new List<T>();`
It's not really "7 ways to assign a new empty List<T>". It's "7 ways to create an object", and Java several of them, too.
They really aren't, and they are IMHO an antipattern since they break encapsulation. One might argue that encapsulation doesn't matter with mere data classes, but Java will cater to that use case by introducing withers.
They don't. Java had to come up with the extremely verbose builder pattern for the exact same thing. And withers are basically the same tedious manual builder pattern, just with a different name.
For withers C# just has the with keyword: https://learn.microsoft.com/en-us/dotnet/csharp/language-ref...
Open Source? No way. Git? No, they relied as die hard MS believers on the MS software called Team Foundation or something like that, that was integrated into Visual Studio Pro - sorry, I forgot about it, I considered it kind of bloat and outdated. Also I couldn't stand the nomenclature. A project was called "Solution" - I died inside, because this sounded like utter nonsense to me, because how do they know it would be one in the end?
While JetBrains as well as Linux quickly iterated through everything and got traction as well as a cadence that overall kind of was paced around sprint cycles that lasted two or four weeks, the company finally started to break up with project management and implemented Scrum.
As the JavaScript guy, the only one, because a customer wanted a SaaS "solution" but with static web content this wasn't really dynamic. I knew one of the founders who was a managing partner and he asked me to join as Web Developer.
Overall, all were very skeptical towards me because how could someone bet on JavaScript at the time? Well I turned the argument around and said the same about C# with its closed source walled garden approach to everything relying on MS to solve their problems with no way of giving feedback while there was no real release cycle and roadmap available - hopium and copium.
Statically typed languages for the win they said, blabla. I wasn't against static types, but did pure magic in JS, that they saw me as magician and I got some fans and I found one team mate who wanted to be coached by me on JS, Ajax and stuff.
So, there you have it. History.
I think there are pros and cons to any approach as always. Both language suffer from feature creep.
C# is still tightly knit into some products from MS and there are some backwards compatibility issues to take care of that limit certain progress and need substantial change.
Java isn't that way and was near dead and went OS. That's why they moved to the current model. Former versions were also hardly changed, have a look at everything before Java Version 12 or so.
Java wasn't community driven all the time.
So, C# has its merits, TypeScript for the win, so MS won over JavaScript ironically but only on the outside.
I shocked my MS fanboy colleagues when I really gave them a shock therapy regarding security when they mocked me with the examples given by MS why JavaScript was so bad and C# would beat it. We all know the infamous type coercion examples with mixed types, arrays etc.
So I shocked them with eval function of course but then gave them nightmares and mental overload with Function.prototype.toString and new Function() trickery.
It blew their mind, there was nothing remotely available in their world. Not introspection, nothing.
JavaScript was kind of assembler like I said. Highly flexible, you need to use modules, like jQuery did but have to build your own.
So TypeScript used exactly this flexibility: compiling to JavaScript. A metalanguage.
For the true insider, JavaScript won.
This has nothing to do with Oracle. All good that you hear from Java in the last few years, is the great community and good old people from Sun working at Oracle.
They had to commit to half a year release cycles and LTRs every 2 years. Since then the releases became a lot more predictable. Whatever is not ready is not released (or is there as a preview feature).
This more agile approach is a lot better in my experience and we see that the changes made are more relevant and what people actually want.
Everything that was done on the backend side was done in Java, although there were some exceptions for .NET deployments.
So we ended up with mixed skills teams where depending on the ticket, you would be coding Java or C#.
medtech - .NET and TypeScript.
if you gonna work in a big team or need a project with lots of devs then yeah go for the JVM.
but if you're doing things on the smaller / small scale side. - just use JS/TS or python. you benefit from cheap runtimes such as Cloudflare workers.
As to Rust - we all, hopefully, agree that it’s great language, but not for some startup making websites or Mongo based, boring backends. It’s great for the stable, system level products.
We are using JDK25 and are considering rewriting parts of our product to Go because of lower memory pressure and faster startup time, i.e., cloud friendly. I actually love both languages.
I'm well aware that Go's GC has improved, but the moving algorithm was designed not just to be fast for a GC, but to be faster than no GC. So Go's new GC is good - for a mark and sweep collector. But it can't compete with a moving collector (the only thing that can is arenas, which are user-friendly only in Zig).
> We are using JDK25 and are considering rewriting parts of our product to Go because of lower memory pressure and faster startup time, i.e., cloud friendly.
Java probably will never have perfect warmup, but it's getting very good - https://openjdk.org/jeps/544 - probably in JDK 28.
As for memory, I think Java's memory strategy is generally misunderstood and I've given a talk about it: https://youtu.be/xr73mR7ii9M The footprint overhead exists to compensate for CPU utilisation when the CPU utilisation is more disruptive than memory usage. The problem is that many Java developers - and I'm not blaming them - don't understand this tradeoff and how to configure the JVM for optimal resource usage, but the great news is that a solution is coming soon, too - https://openjdk.org/jeps/8377305 - also possibly in JDK 28.
So it's very likely that both of these issues will be resolved six months from today, and you'd still get to enjoy better performance and telemetry than all alternatives.
This is not true. The whole point of the algorithm - the reason it was designed - is that the amount of moving is well below what's required in a non-moving collector. The downside is that the algorithm is more complicated and requires an FFI layer for FFI, but even though non-moving collectors are far simpler to implement, every language/runtime that can use moving collectors uses them (and all of those can also use non-moving collectors, too, as Java did earlier on; concurrent mark-and-sweep collectors like Go's or Java's old CMS are easier to make). Whatever you say about the complexity of moving collectors or their impact to latency before the recent invention of pauseless moving collectors, they are widely recognised fact that as the most efficient general purpose memory management solution (but also the most elaborate).
You could argue about certain workloads, but it is ridiculous to claim that the world's top memory management researchers worked for years to come up with an algorithm to be more efficient than mark-and-sweep collectors and malloc/free failed to notice that it has to move objects around a lot (the whole point of the algorithm is that it does not), and then every language that can use the algorithm chooses to use it because they also failed to notice that the algorithm that is so much more costly to implement is so obviously worse.
BTW, Go's reason for using a simpler, older style mark-and-sweep collector isn't that it's better (Google's larger V8 team opted for a moving collector), but that Go can get away with a simpler, less efficient GC because the allocation rate is lower (and we can argue over that, but at least that would be an argument over something that could actually be controversial).
Anyway, if you're interested to know how moving collectors really work, and how they were created to be more efficient than any non-moving general memory management strategy, I go through the basics in a recent talk I gave: https://youtu.be/xr73mR7ii9M
The amount of memory a Java program uses is whatever the setting is, not how much it "needs", because the need depends on the preference of the CPU/RAM tradeoff. But again, not many understand that, so we're making that automatic.
Lower memory pressure is certainly a difficult thing to beat Go at, Java (OpenJDK) is probably never gonna get there. You get a lot of other stuff, like better peak performance, instead.
Btw, have you tried Leyden/AOT for better startup times? Curious about your experiences with that.
Nope, not yet. It's a good question given that up to now we used to deliver our product only on-premises and Windows Server-only, but this year we are now finally going with the Cloud, which means Docker containers and Linux.
If I remember correctly Leyden required some sort of warm-up and training data collection before being able to effectively execute AOT, right? I need to freshen up my info on that.
I did try GraalVM-compiled Java executables a couple of years ago and they were not bad, but the binaries were quite big (not a showstopper though) and the class-loading issues were kind of a PITA.
It's usually the build systems that add quite some overhead.
Which specific optimizations are you referring to?
In my experience, this is largely a myth; compared to Rust, you actually get even faster code right away.
JIT is effective for languages where the source code lacks sufficient information (dynamic typing, where anything can be null).
A JIT with speculative optimisation and a moving GC.
There are two constraints in low-level languages that trump any of their performance goals, one technical and one a matter of preference.
The technical limitation is that they must use stable pointers (because they need to be low-level and so having an FFI layer that separates "hardware pointers" from "language pointers", as we have in Java defeats their main purpose). This means that you need to translate data storage or code storage to hardware addresses, and that interferes with both moving collection and with JIT compilation.
The other constraint is that low-level languages value worst-case performance over the average-case and even amortised performance. These languages prefer an operation (e.g. dynamic dispatch) to be slow as long as it's never too slow. With a JIT (and I describe more later), virtual dispatch can be super-fast almost all the time, but occassionally, you'll hit a trap because the speculation was wrong, and then you need to deoptimise and recompile.
> In my experience, this is largely a myth; compared to Rust, you actually get even faster code right away.
We wouldn't be doing it in the first place if it was a myth. In a low-level language, you can get very fast code if you do some manual optimisations, but they don't easily scale as the program grows and evolves, because they're viral. The two most basic examples are dynamic dispatch (which is the most general mechanism, which scales the best in terms of program evolution) and shared heap objects (again, the most general mechanism). These become more common and less easily avoided over time, and they're slow in low-level languages because of the constraints I mentioned.
That low-level languages make it harder and harder to preserve good performance over time as they evolve and grow is a problem familiar to those who've worked for years on large software written in a low level language (as I have). The JVM was designed, among other things, to solve this performance problem in large programs.
> JIT is effective for languages where the source code lacks sufficient information (dynamic typing, where anything can be null).
A JIT can make such languages decently fast, but that's not how it's used in Java. In Java it is used for speculative optimisation, which allows far more aggressive optimisation than an AOT compiler can do. E.g. by default, Java inlines and specialises virtual calls 15 levels deep. An AOT compiler can't do that or its code will explode. We get around it with selective use of templates in C++ (or comptime in Zig), but it has to be selective, and it's viral.
Do you mind a reasoned discussion?
> A JIT with speculative optimisation and a moving GC.
Idiomatic Rust, through its concepts of ownership and borrowing, encourages a pattern where you receive data as an argument or create it directly, perform operations on it, and then discard it via RAII. This bears some resemblance to functional programming. This approach does not apply to buffers of unknown size, which still require heap allocation; unfortunately, Rust lacks automatic buffer reuse. However, such optimization is theoretically possible. The stack is definitely faster than anything else.
> This means that you need to translate data storage or code storage to hardware addresses, and that interferes with both moving collection and with JIT compilation.
You don't need GC if you allocate data on stack. You also do not need to dereference the pointer.
> dynamic dispatch
You mentioned templates. In Rust, traits that are monomorphized - much like templates-are the standard approach; using vtables or `dyn trait` is a relatively rare use case. This stems from the fact that all code is known at compile time and there is no dynamic loading, allowing the compiler to eliminate polymorphism from the code entirely.
> and shared heap objects
This might be considered convenient, but in my view, it also leads to code that is harder to maintain when objects can be modified from multiple places. However, I think that is outside the scope of the current discussion.
> We get around it with selective use of templates in C++ (or comptime in Zig), but it has to be selective, and it's viral.
Yes, monomorphization is the default solution in Rust. It is not always viral either, because when using it, you often define specific types, and they do not spread beyond that scope.
I suppose you could say that the programming style I am talking about is complex, inconvenient, unmaintainable, and so on. What I mean is, assuming this programming style is sufficiently convenient—and perhaps even has its own advantages - then none of the optimizations you listed offer an edge, and the Rust code will definitely be faster.
I have seen it mentioned everywhere, but is this actually true?
I mean, of course it is faster than random cold memory, but is it actually faster than a hot, in-cache part of the heap? It is not special in any other way, AFAIK.
And for what it's worth, what pron mentioned, Java uses a pretty similar structure for initial allocation, a thread local buffer where you just pointer bump. Another thread can then in the background copy still alive objects from this "arena" and then reset the whole thing.
Yes, it just adding or subtraction int to stack pointer register. I’m not certain, but the only thing that might be faster is accessing data at a fixed address - that is, global variables.
Stack is fast because it is frequently "touched" staying in cache. If you were to continuously read write a small segment of the heap, I don't think it would fair any worse than "the stack". This was my point
What you're describing isn't a stack, but an automatic arena, and this optimisation is easier to do in Java. It's easier to do in Java because it requires setting a "current arena" or inlining, both of which Java can do more easily, and then either the arena will be heap allocated (which will be slower in Rust) or associated with the thread, which is not something low-level languages tend to do.
> You don't need GC if you allocate data on stack. You also do not need to dereference the pointer.
Moving collectors don't need to dereference anything (they don't know and don't want to know when an object is "dead"), and stack allocation works in both languages, only, as you pointed out, is not quite general (not every data structure with a known lifetime can be allocated on the stack).
> You mentioned templates. In Rust, traits that are monomorphized - much like templates-are the standard approach; using vtables or `dyn trait` is a relatively rare use case. This stems from the fact that all code is known at compile time and there is no dynamic loading, allowing the compiler to eliminate polymorphism from the code entirely.
Sure, except Java does this automatically, and it can do it more aggressively. Dynamic dispatch is rare in low-level languages because it's expensive in those languages. But it's not easy to avoid as programs get larger. That is exactly one of the problems in large programs that the JVM set out to solve.
> This might be considered convenient, but in my view, it also leads to code that is harder to maintain when objects can be modified from multiple places. However, I think that is outside the scope of the current discussion.
I agree that whether it has downsides is outside the scope of this discussion, but the point is that as programs evolve and grow, the abstractions tend to be more general, and low-level languages suffer from "abstraction cost", where the more general abstraction (which becomes more common over time) is more expensive. Again, this is exactly why large C++ programs suffered from performance issues and what the JVM tried to address.
> Yes, monomorphization is the default solution in Rust.
... and in C++. But it is viral, and Java monomorphises without suffering from "zero overhead abstractions".
The ability to move pointers, both to data and to code, opens up the possibility of using JITs and moving GCs, which are very powerful optimisations. A JIT does impose two further tradeoffs (aside from the need for an FFI layer), though, which are warmup and the possibility of deoptimisation. We can now cache the generated machine code from one execution to another (https://openjdk.org/jeps/544), but the possibility of deoptimisation remains (in fact, it's what enables the aggressive speculative optimisations), which means you gain average (or even amortised) performance at the cost of the worst case.
Anyway, the JVM was designed as a solution for the performance issues low-level languages suffer from as programs grow and/or evolve. It comes with tradeoffs, but those most affect small or short-lived programs.
The thing to remember is that low-level languages are not optimised for performance but for low-level control (i.e. pointers are direct addresses etc.). Such control can translate to good performance when programs are small (see next) but it becomes a practical hindrance to performance when they're large.
> I suppose you could say that the programming style I am talking about is complex, inconvenient, unmaintainable, and so on. What I mean is, assuming this programming style is sufficiently convenient—and perhaps even has its own advantages
That advantage is a performance advantage. The question isn't "does there exist (in the mathematical sense) some program that is fast?" but "how fast is the program we can write within the budget we have?" When programs are small, manual optimisation is practical; when they grow large - not so much. And that's excluding the matter of a moving collector, which is just hard to compete with on speed regardless of program size, unless you use areans, but they're not at all easy to use in most low-level languages except Zig.
> and the Rust code will definitely be faster.
This is true only in the abstract mathematical sense. The reason we don't write programs that we want to be fast in Assembly (which is faster than anything in the same sense: for any program in any language, there exists and Assembly program that's at least as fast) is not because other languages are fast enough, but because in practice the programs we can actually write in the budget we have will be faster than the Assembly programs we could write. Of course, that could change when AI is able to generate perfect low-level code, but when that happens, it might as well generate machine code directly.
At least you aren't claiming that the JVM is ~1.5 faster than perfectly written assembly :)
I disagree with a lot of what you’re writing. However, we’ve reached the point where we need to run benchmarks and analyze the generated code (this is easy to do for compiled languages using https://godbolt.org/, but for the JVM, it can be a bit more complex, given the warm-up factor).
So, there is one fundamental point I started with:
> JIT is effective for languages where the source code lacks sufficient information (dynamic typing, where anything can be null)
And your answer is:
> A JIT can make such languages decently fast, but that's not how it's used in Java. In Java it is used for speculative optimisation, which allows far more aggressive optimisation than an AOT compiler can do.
Essentially, you are saying that the compiler can apply aggressive optimizations when it knows what is happening in the code.
But I say that JIT is needed so the compiler can figure out what is happening in the code and perform aggressive optimizations.
There are many things that can be inferred from the code without needing to execute it. The question is how difficult it is to make such an inference: in one scenario, the compiler might attempt to track whether specific data changes-and, if it can prove this, mark the data as immutable and apply certain optimizations-whereas in another, it might already possess the information that the data is immutable.
Moreover, information about immutability is useful not only to the compiler but also to the programmer. Just like information about types: it benefits both the compiler and the programmer. Imagine a fan of JS or Python joining our conversation and claiming that both Java and Rust are low-level languages because you have to specify types - something they view as complex and a hindrance to development speed.
The same applies to the GC: the compiler can perform more optimizations when it knows when memory needs to be cleared (move it to stack or even place the data on registers). The JVM attempts to do this (via escape analysis), but there are limitations; consequently, data ends up on the heap, and GC operations come at a cost (due to data movement).
Rust simply makes it easy to obtain far more information, enabling aggressive optimizations that are both immediate and guaranteed.
There remain a small number of cases, such as `switch` statements - where one branch executes 99% of the time, while the other 99 branches execute only 1% of the time. In such instances, the JIT could indeed perform further optimizations; however, I am not even sure if the overhead of monitoring wouldn't outweigh the benefits. And the question is when and how to perform PGO, or whether to perform it at all.
Yes, and the important point is that when it comes to knowing things statically, abstraction and optimisation are in conflict. The whole point of abstraction is that the implementation details aren't known. So in C++ we always suffer from this problem called "zero overhead abstractions" or "abstraction costs", which means that to give the compiler the information it needs, we have to use less general abstractions, which are viral and harm evolution. What a JIT does is allow the compiler to learn the very things that abstraction hides; yes, it's a virtual call, yes, it could target anything, but I've seen it hit the same target 1000 out of the last 1000 times, so I speculate that this will continue and I'll inline even though I could be wrong.
> The same applies to the GC: the compiler can perform more optimizations when it knows when memory needs to be cleared
I understand why this could be true in theory, but in practice the problem is:
1. not that the compiler knows when an object is unreachable, but that the generated code has to do something at that point, and
2. the most efficient known memory management algorithms - moving collectors and arenas, both work in nearly the same way - are entirely predicated on freeing memory in bulk and on not doing anything when an object becomes unreachable, and so the knowledge of when an object becomes unreachable doesn't help them.
So it is true that C and C++ and Rust always statically know when an object is dead, and you could say that hypothetically they don't need to do anything with that information, but in practice they all act on that information immediately and that's inefficient.
> There remain a small number of cases, such as `switch` statements - where one branch executes 99% of the time, while the other 99 branches execute only 1% of the time.
So the main practical benefit of a JIT isn't that at all, but that it can do the "mother of all optimisations" - inlining - far more aggressively. Inlining is important because it cracks open the abstraction boundary of the inlined subroutine, and allows the compiler to further specialise and optimise things, now with the appropriate context.
Anyway, all of these fundamental questions and differences between languages with more statically known information and figuring out "unprovable" information in practice were very well known before the JVM was built to address the performance problems we had suffered from in large C++ programs. So we can argue over which workloads are helped by this and which aren't, but there is no way to say which is usually faster in the absract (because, again, these considerations were known and taken into account). It's merely an empirical question, and not one that's easy to settle. After more than 25 years of working with C++ and almost 20 years of working with Java, my default is that low-level wins on performance (if written by experts) in smaller programs, and Java wins on performance in larger programs, but of course, there are many caveats in either direction.
Caffeine's next release has roughly 25% higher read throughput, with unchanged write throughput, thanks to fixing a false sharing mistake. That won't be visible in real workloads, but is fun nonetheless (500M reads/s on 8 cores).
I'm not following. Saying Go lacks in X shows how poor other software is? Can you connect the dots?
Doesn't bother to disclose it of course, because what, you don't check everyone's profile in every discussion to make sure they're not biased? What, you don't just know who every user on this site works for? You dummy you :)
I was reading your comments on Java, nodding my head, upvoting, without checking your profile and realizing that you're a member of the Java team. Knowing that doesn't mean I now suddenly disagree with you or anything. But while in an ideal world it doesn't matter who's saying something when evaluating it, there's some human factors at play - I'd like to turn up my internal sense of skepticism when dealing with someone, effectively, selling something their salary depends on; even if you're being entirely earnest, it's ultimately a sales pitch, and I feel bamboozled for not recognizing it - that'd make me appreciate transparency.
(FWIW, even though I prefer being coy about my place-of-work, I have no professional relation to this conversation. I've never used Java in my 9-5 and I haven't even really used it in earnest since, like, version 5 back in high school. I think it's always been underrated by the hacker crowd, though!)
This is really, really silly. Java is many times beyond the position where its developers need to desperately convince people to use it. This is a person who has unique technical expertise in the area whose credentials are smack dab on their profile, not hidden from you. Their closeness to the domain at hand should make you less skeptical of what they are saying.
I also don't see the case for stability. Yes, if you're still on JDK 8, it would probably chug on for a couple of years. But we were talking about greenfield projects and newer JDK go EOL much faster. If you want patches, you'll have to run your app to a newer JDK, which may break a couple of things. Rust (within the same edition) or Go (within the same major version) break less than that.
As far as runtime compatibility goes, Rust and Go apps ship with the runtime. This can be better or worse for you, depending on what is your upgrade story, but I don't see a clear winner here. What I would give to Java over Rust is that you will have far fewer dependencies to take care of if you need to upgrade. But the same goes for Go.
For observability, I feel that with Rust you have a bit less that you need to observe (no GC to worry about). Tokio tracing is great, but observability requires a bit more effort. The go observability story is far worse. So Java probably has an edge here, but not something that ever felt like a game changer. My impression is that for most of the enterprise shops that love Java, observability means collecting unstructured log files through NFS and trying to find a needle in the haystack with primitive tools, but I've been out of touch with this world for a couple of years.
Productivity is something that is dead if you are AI-heavy. Sure, many shops are still wary about AI, and I totally get why, but this is a battle that's already been lost. Without AI, I would say I was about 3 to 4 times more productive in Rust than I was in Java, but ramping up that productivity took at least 1 year of practice. It's not time most companies are willing to spend. With AI, this doesn't matter anymore, for better or worse.
I'm not arguing that Java is not chosen often for greenfield projects. It's clearly extremely popular in many circles, especially outside startups and big tech. But I think the reason Java is chosen have little to do with the reasons you've mentioned above and more with organizational preferences.
Java also breaks very few things. Breaking binary compatibility is a no-go since it's a core promise of the platform. The only thing in the surface language that has ever been changed is the meaning of the underscore as an identifier, as well as the behavior of == in upcoming Project Valhalla.
> As far as runtime compatibility goes, Rust and Go apps ship with the runtime.
Java applications can also be shipped together with the runtime.
> Productivity is something that is dead if you are AI-heavy.
Nevertheless, making constructs available to express intent more clearly should also help LLMs to not go off the rails.
It isn't, and the problem isn't Rust specifically, but all low-level languages. They can offer very good performance (often better than Java) when small. But as they evolve over time, or are very large to begin with, they become much harder to keep performant. This is for pretty fundamental constraints of low-level language that I mention in another comment here, and this performance problem with large programs written in low-level languages was well known before Java even existed. The JVM was designed, at least in part, to address it.
One of the things that drew me to Java (from years of C++, even though I still work in C++ when I work on the JVM) is precisely how it addresses those performance issues we ran into with C++ five years into a project.
> As far as runtime compatibility goes, Rust and Go apps ship with the runtime. This can be better or worse for you, depending on what is your upgrade story, but I don't see a clear winner here.
I wasn't talking about "runtime compatibility" but of overall version compatibility. Java has an unmatched compatibility record - not perfect, but better than anything else (with at least a medium-sized standard library).
> For observability, I feel that with Rust you have a bit less that you need to observe (no GC to worry about).
Memory management is very often a bigger issue without a GC than with a moving GC. Time and again we see Rust or C++ programs spend 30-50% on memory management.
> Productivity is something that is dead if you are AI-heavy.
Really? Have you had AI write a good medium-sized (say 100-500 KLOC) program or maintain one over a long period of time without very close reviews? The only people I've seen who don't know about the ticking time-bomb agents leave in the codebase are the people who don't look.
> With AI, this doesn't matter anymore, for better or worse.
You may be talking about small programs. I agree that for small programs, low-level languages can offer excellent performance, and AI can be okayish, and you can get some observability you can live with, but I'm talking about large programs.
> But I think the reason Java is chosen have little to do with the reasons you've mentioned above and more with organizational preferences.
Those organisational preferences are due to a long record of delivering on the things I mentioned. Java has an exceptionally low "regret factor", i.e. people who regret choosing it five, ten, or fifteen years into a project (which is when the problems usually start).
30-50% of what?
Right, so one case (which I certainly believe is possible) is very different from “time and time again.”
As for the caching test, it's just technically interesting, because the JVM was designed to address the performance issues we suffered from in large C++ programs (all the JVM engineers are, of course, C++ people), both due to compilation and to memory management, and we regularly compare both our compilation and memory management algorithms to other approaches, and it just so happens that last week one of our GC engineers compared Caffeine to Moka and saw how CPU-intensive the memory management work is compared to ZGC (he was particularly interested in this because caching is one of the more challenging workloads for generational moving GCs because a cache deals with many old objects, whereas generational GCs tend to focus more on young objects, and he wanted to make sure that our GCs help reduce the high memory-management overheads associated with low-level languages even in this challenging scenario).
I'm not sure what kind of tools you mean, but unless you're looking for something that just works exactly the way EJBs do for some mysterious reasons, I don't see why you can't do most "enterprisey" things with Rust or Go. Or Python or TypeScript for that matter.
Yes and that's exactly what modern tooling is missing. Try to develop for node.js 0.2.12 on today's update of Visual Studio Code. See? No enterprise-level collaboration for ya.
It felt like every dev that worked on our Java behemoth at a previous job was elated to switch to Go.
Rust is too low-level for typical enterprise app where requirements changes twice a day. You end up spending time and tokens fighting with borrow checker.
C# is MS product, which is no-go for some folks.
Kotlin probably would be the answer.
Which Java famously does not have.
> Rust is too low-level for typical enterprise app where requirements changes twice a day. You end up spending time and tokens fighting with borrow checker.
In my experience, you do not spend tokens fighting with the borrow checker anymore, newer models are smarter. But it might not be ideal for a lot of CRUD applications.
> C# is MS product, which is no-go for some folks.
This is 2026, it's not 1996 anymore. .Net works on Linux and Microsoft is as friendly towards open source and open standards as a Big Tech company can be.
If anything, it was Oracle which more recently sued another company for using a JDK alternative. And this was a lawsuit that, if accepted, could have put the entire idea of API compatibility in danger and deal a severe blow to the Open Source movement.
Anyone who is morally bothered by MS but is unfazed by this is probably just mentally stuck in the 1990s.
> Kotlin probably would be the answer.
I love Kotlin, but I'm afraid that's not the case. The conservative organizations that choose Java out of inertia, would keep choosing Java over Kotlin, even if Kotlin is a better JVM language which is facing no downside.
For anyone who doesn't need to be on the JVM or work with JVM tooling, Kotlin doesn't cut it. It doesn't have null pointer dereference problem in theory... Only it does in practice if you're using any Java API that may return null (all these bang-decorated "Platform types"). Generic type erasure can only be overcome in inline functions with reified types. And building and deploying artifacts without docker is still a mess.
I found Kotlin extremely publishing for Java shops in the past, and I've converted multiple departments totaling over hundreds of employees to use Kotlin. But that was before AI. The rationale was simple: Java is an entrenched language that leads to bloated code, slow development cycles and way too many avoidable bugs in productions. Kotlin solves some if these issues, and it's very easy to learn for a Java engineer, while still letting you keep all of your tools and libraries. And as a language (putting ecosystem aside), I find it better than either Go or Typescript, and far more ergonomic than Rust[1].
But all of these arguments die with AI. Rust is just as ergonomic as any other popular language today if you're using an agent, and the fact that an engineer spent their lifetime writing Spring Boot programs in Java you don't have time to let them learn a new stack from scratch doesn't matter anymore.
Sure, there are many companies where letting AI write the code is still not acceptable, but most of these workplaces will accept AI agents sooner than they accept Kotlin.
I feel a bit sad since I like many ideas about Kotlin (especially how amenable it is for making DSLs) but we've lost that opportunity
--
[1] Unless you have to write highly concurrent code without any data races.
Have you worked on large (>500KLOC) codebases with an agent? Not only do you have to be an expert at the language, but even if you're lucky and everything is fine, Java code is likely to be particularly fast by comparison, because the agents aren't very good at manual optimisation, especially as the code grows (they're even worse than humans at that, and humans aren't great at manual optimisation of large codebases, either, which is one of the problems the JVM set out to solve; in fact, agent-written code in a low-level language gets pretty slow well below that size). Oh, and the long build times certainly don't help.
Yes. But keep in mind KLOCs are not easily comparable across languages. Java is notoriously verbose. A 500KLOC codebase in Java would usually be half that size in Rust. If your argument is that large codebases makes life harder for agents, you should go with a less verbose language.
I'm not sure what "manual optimization" means (isn't it a bit of an oxymoron when the agent does it?), but if your agent has the proper tools (e.g. ast-grep, rg, semble) it can deal with large codebases. Would the agent create slop? Yes. But it wouldn't be worse on the slop that humans created on every moderately-sized Java project I've worked on.
> in fact, agent-written code in a low-level language gets pretty slow well below that size
I've never seen this happening. I've seen agents writing suboptimal Rust code (e.g. copies instead of Cow). But while this occassionally happens with Rust, I've never seen an agent optimizing for Java where necessary (e.g. using object pools to avoid GC churn). Java is not magic.
Lol, no way. Especially that rust is pretty verbose all things together (which makes sense, given it's a low level language - ergo you have to literally express more things about the code)
Ok, so you barely know either Java or Rust.
> If your argument is that large codebases makes life harder for agents, you should go with a less verbose language.
You mean, like Rust??? But no, that's not my argument. Agents have a hard time keeping up the architecture in large software (and the differences between verbose languages like Java, Go, Rust, and C++ vs less verbose ones like Python and JS don't make much of a difference). So they either make a mess or they do the simple thing, and the simple thing in low-level languages is often slow.
> But it wouldn't be worse on the slop that humans created on every moderately-sized Java project I've worked on.
Yeah, I don't think you've actually tried it.
> I've never seen an agent optimizing for Java where necessary (e.g. using object pools to avoid GC churn). Java is not magic.
Object pools are far less efficient than Java's GCs, but while state-of-the-art compiler and memory management technology is certainly not magic, I suggest you learn more about these things if you want to make informed decisions.
Second, I wonder where, roughly, would you put a transition from small programs where low-level langs are fine, to programs large enough to heavily benefit from JVM tradeoffs? And how this transition is affected by a stuff like Graal Native?
Banks, telcos, etc. aren’t monoliths either. They use plenty of different languages depending on the team, system, and requirements. Java isn’t inherently the choice for greenfield software just because reliability matters.
It's stable to the point of boring, and there's no shortage of people who know the language and can work with it, it's got best in class tooling, decades worth of libraries almost all very mature. Most of the language's issues are from legacy code bases coded in a style that isn't really relevant to a greenfield project.
If you develop a library in Java and use it from Kotlin, the built-in Kotlin null-safety will recognize the JSpecify annotations on the library.
Null-restricted types are on the roadmap. See: https://openjdk.org/jeps/8303099
Use NullAway and it basically makes the problem go away. Our application won't build if it detects a potential NPE.
I get it why this seems like a less drastic change, but this saddens me. Kotlin solves more issues with the type system (smart casts, reified types, immutability by default), without sacrificing readability. Unless I can see a solution in Java that makes dealing with NPEs as easier for lazy developers as ignoring them, I don't consider it a solved issue.
You deserve the strawman award of the year.
NullAway and JSpecify encourage making as many types non-nullable as possible, thus they can actually also advice about removing redundant null checks. Nullable types become the painful exception that visibly spreads through the codebase, which discourages writing code that relies on null.
Optional doesn't enter the picture at all. NullAway kills their usecase within ones own code. They are anyway only recommend as return types to force others to check for an emoty case, but I think Optional will become fully optional when the Java platform gets nullable types on its own.
Google Error Prone is a code linting tool that's very useful in its own right, and NullAway is just another plugin.
The thing is, panics aren't exactly meant to be safely recoverable like exceptions are. They're like Rust panics. Say there's a panic in the middle of modifying some global state like a database connection. Hence complaints about the net/http recover like https://github.com/golang/go/issues/25245
* https://jspecify.dev/docs/user-guide/
* https://openjdk.org/jeps/8303099
https://github.com/uber-go/nilaway
Not exactly the same solution as JSpecify, since it doesn't rely on annotations, but it's also more ergonomic.
I'm not comparing this to "null-restricted types", since that's a draft JEP that hasn't made it even into a preview feature. Go also had multiple proposals for explicit nilability in types, and while they probably have less prospect of ever seeing the light of day compared to Project Valhalla, as things currently stand, Go is in the same position as Java: They are both extremely prone to NEPs out-of-the-box and they both have external tooling that can help you avoid them.
Java null checkers have more comprehensive coverage potential compared to Go, but Go is the more ergonomic one here. You don't need a single extra annotation on your code.
Theoretically you don’t need to write AbstractFactoryProvider in Java, but looking at languages mentioned in job offers, I have a pretty good idea of which of them have a high probability of working with such code and which do not, even if all of them say they have the best code ever.
For concrete example, it took me a long time to find a job last year due to only fully remote being viable since my wife's autoimmune condition means I'd be risking her health by commuting, and nowadays most places seem to either expect hybrid if you live near an office (I'm within the geographic limits of NYC despite being nowhere near Manhattan), restrict by time zone (there were quite a few jobs I was interested in where they only would accept remote with Pacific or Mountain Time), or have onerous travel requirements (multiple opportunities I interviewed for didn't work out because they expected me to fly to the west coast every couple of months, which between the time there and jet lag would mean I'm not productive close to a quarter of the time).
I was in a fortunate position to be able to hold out for a while and ended up finding a fully job with my preferred language after around eight months, but I had already come up with a timeline for when I should start relaxing certain constraints if it went on longer. Programming language was literally the first constraint that I was going to drop if it lasted a few more months because prioritizing my wife's health is non-negotiable, and I'd rather work in a language I don't like as much on something that I don't feel is actively making the world a worse place than work in my favorite language on adtech or at some cryptocurrency startup. It's not clear to me why it would be a problem for me to care about using my non-favorite programming language well if I happened to be employed to write it.
Scala technically allows you to use nulls or throw exceptions pretty much wherever (necessary for Java compatibility), but it's not an issue because people simply don't outside of super niche situations (generally some low-level thing, or a shim). Similar to `unsafe` in Rust. Or casts in all sorts of languages.
I don't understand that logic. I sometimes ask people to explain why they think a certain policy should be implemented by the government after they state their support for it, but I don't have the ability to set government policy. I have trouble imagining you genuinely assume that any time someone asks you why something should be the way you say that you think they have the ability to change it if you convince them.
Like if I think my business should open an hour earlier, and you say "but the employees won't be there yet so who will open the doors!" obviously the solution is to also change the work schedule. When you have closely related policies, generally the same person/people are empowered to make both changes.
> When should one use Java on projects?
> One shouldn't consider Java because it lets you use null.
> That's easily solvable by just not using null.
> But you can't just do that. People will use it.
> You can just do that. Tell them not to.
Like I'm not seeing the issue. This is like saying you can't use Rust because people will use `unsafe` because it lets them do C programmer things, and then claiming it is simply impossible to tell them not to do that (and set tool policies to flag anyone attempting to).
In the real world, if you're in a position to even ask "why use Java for a new project?" then you are presumably also in a position to have "don't use nulls" be a satisfying answer to "what about nulls?" If someone is asked what technology to use for a project in the first place, they are almost certainly also asked about how it will be used. The hypothetical here is not "do you have coding standards" but "are you a decision maker," and when the original question is "when should one decide to do X," you have to accept as a premise that you are placing yourself in the role of a decision maker in the first place.
I just don't see why nullability is a problem in the first place.
I get that some people feel like it ought to be built-in to the language rather than a separate tool... but people's personal feelings are irrelevant to the lived experience of my day-to-day work, where worrying about null is truly a thing of the past.
Unfortunately, no, they don't. Not after your application has been running for a while; newer JVMs arbitrarily decide you don't need the stack trace anymore, and all you see in your logs is "NullPointerException" (unless you still have the logs from several weeks ago, just after the last JVM restart, which might still have the full stack trace). Older JVMs were better, since they always had the full stack trace; debugging NPEs was easier with them.
Unlike C it is trivial to catch a NullPointerException and confine the crash to the unit of work. And unlike C you are not talking about insanely dangerous pointers, you're just talking about an NPE.
I'll admit it's a hassle when something wasn't initialized properly and then you get a null pointer exception at some unrelated code much later. It's not always easy to debug. Catastrophic? No!
There are a lot of third party tools that can check for null safety and a lot of work is being done to make Java's initialization safer but also a little more flexible, there is
https://openjdk.org/jeps/8303099
and there are all sorts of practical answers. Nulls in Java are low on my list of annoyances, way behind front end programmers who pepper my CSS files with "!important" because they don't know about precedence (though maybe they think my .clazz.clazz.clazz selector is brain dead!)
Otherwise, yeah, you're still in for a world of pain.
And that "team" nowadays may also consist of many AI agents. In my experience Claude Code for example works very well with a typed, slightly boring language with lots of framework and library support. Because it doesn't compile when you get something wrong, instead of getting a vague runtime issue that Claude can't always see.
I agree with the rest, but there's definitely a lot of magic in Java. This is from both what features the languages makes available (many) and how the community uses them (often). I've had so many hard-to-debug issues in Java over the years due to reflection, annotations, and bytecode manipulation shenanigans.
And another positive point for Java: checked exceptions. It's verbose, but knowing exactly in which ways a function can fail is extremely helpful for building robust applications.
It took me quite a long time to accept that the recommended unit testing library manipulates bytecode so that the exception message for `assert a == b` prints the values for both.
[1] https://www.javacodegeeks.com/2026/01/javas-checked-exceptio...
[2] https://reflectoring.io/do-not-use-checked-exceptions/
WRT magic, I've generally thought that was a result of frameworks - Spring, for example. In the past, my feeling was that these impose a sort of meta/configuration language that itself is not checkable at compile time, so you'd get weird runtime errors that are somewhat inexplicable. This was like... 2018 though, so perhaps things have improved.
Many times I've decided to switch from one function to another, or even an entirely new library, because the checked exceptions told me that it was doing far more than I expected, and I was not comfortable introducing those new failure modes.
It's far from perfect, one still has to handle nulls and wrapped/merged exceptions, but overall I like this language feature.
Using them correctly can be great tho.
Can you explain why this is pointless? In my mind, this being a checked exception would hopefully be a hint that I should think about this failure-case and make an explicit decision whether to handle it or not. Network connection failed? Maybe I retry. Maybe I store that data somewhere else as a fall back. Isn't this similar to Go programmers needing to check if err is not nil?
As soon as you consider retrying a network failure, you immediately need to start thinking about distributed systems failures, idempotency, and all that good stuff.
As soon as you start thinking about the above, it becomes immediately obvious that low-level calls should not be able to decide to re-run themselves.
I appreciate that there is a _ton_ of different experiences out there when it comes to solving problems, but I _have_ encountered exactly the case I was describing, which is what led me to my original question. Isn't the fact that it was a checked exception that led you to "consider retrying a network failure, you immediately need to start thinking about distributed systems failures, idempotency, and all that good stuff" worth it as opposed to an unchecked exception you may not realize is being thrown?
Occasionally though I have built something really sensitive, like an authentication filter for a web site which has at least 5 ways to log in and in that I have a hierarchy of exceptions and use checked exceptions heavily to document all the ways things can go wrong and felt like "the type system really has my back here" but that is like 5% of the Java I write.
That's a code style and code review issue; each project has so set standards regarding how errors are dealt with and enforce them throughput the codebase.
But from a quality standpoint there are three concerns:
(1) Do you actually do the code review, do you actually enforce the style?
I worked on a Scala project where the dev manager thought it was preferable to handle errors with monads and would be vociferous about what a great practice that was compared to exceptions and that code review was central to how we do things... but if you looked at the code most of the time errors just got dropped silently and that was the same for many practices that the dev manager told me were doing but that we don't. He still posts on LinkedIn complaining about other dev managers who say they do code review but really don't. Practically that code didn't consistently give the right answers and poor error handling was one reason, another was that they never really understood that teardown was just as important as initialization.
(2) Is your documented practice correct? Is it really doing the right thing?
In a lot of cases there really is a right and wrong way to do things (e.g. uv resolves Python dependencies properly, pip doesn't) but it's less clear in error handling, like sometimes things went wrong and there is no way you can make it right and you can do the best that you can.
The global nature of the problem is vexing. Like an IOException might really be a BackhoeCutAFiberSomewhereInWisconsinException and a segmentation fault is occasionally a YouAskedForAOneAndGotAZeroInsteadException and it's not just academic because, given an exception, you want to answer questions like "Should I retry this operation? How long should I wait before I retry this operation?"
(3) Is this practice something you can sustain? How hard is to do? How much cognitive load does it add and how does it interact with other practices? "Throw up as much as you can", "tear down in finally {}", "otherwise handle local consequences of errors and rethrow" and "really catch errors at the drivers of units of work" is a practice that really works in many languages and is pretty easy to do right, even code that is written without a lot of care will do the right thing or something close by default. I've seen a lot of "no plan for error handling" or "bad plan for error handling"... like I was traumatized by the first C program I saw in a 1984 issue of Byte magazine which was using errno to handle errors which vastly complicated very simple code because the error path was intimately wound with the happy path and in cases like that there tend to be bugs in both of them. When I saw Exceptions in Java I remembered that old C program and thought "I love this!"
- Annotation processing: if you know Lombok, MapStruct.
- Class loader.
- Reflection.
- Garbage collection.
Class loader and reflection shenanigans can be shut down with the module system.
Garbage collection matters when you stress the JVM to its limits. Don't do that.
IMO compile-time annotation processors such as Lombok and MapStruct are far from the most magic part of Java. They're straightforward code generators. Their impacts is localized to where they get applied and you can actually see the code that's generated. They're very good for diminishing boilerplate. They're no worse than Rust's very standard #[derive(xyz)] proc macros.
Having the code being generated on the fly (instead of a one-shot) means it follows the rest of the structure it's derived from i.e. equals() and hashCode() don't risk to be forgotten when adding a field to a class (hello maddening Map<> lookup errors)
Also, yes, Lombok is _funky_ in how it works but there are "pure" alternatives like AutoBuilder and AutoValue if one cares.
Another issue with Lombok is that it requires IDEs and other tools to be aware of it. Missing integration with other annotation processors only causes "definition of external element not found"-style errors.
I've worked on Java apps that have failed in mysterious ways that no exception could explain. Meanwhile, the overhead of having to call out certain exceptions but not others in language syntax is a bit excessive.
For example, decoding a byte array (or URL encoded form field) into a UTF-8 string means handling a theoretical UnsupportedEncodingException. What the fuck? How the hell can one have a JVM that doesn't support UTF-8? Why does my code need boilerplate that will never run because there might be some broken-ass JVM out there that that doesn't support UTF-8? How did it launch a web server, safely load all the libraries, and accept a web request, and route it to my code without blowing up? "But the encoding scheme might change..." No, it won't change. It's always going to be UTF-8. It will always be UTF-8. If it's not, let it blow up.
Sorry, but no, Java has the worst of both worlds here. It has checked exceptions AND unchecked exceptions, AND errors which are like unchecked exceptions but won't get caught by a normal catch-all (you're not supposed to catch Throwable, but it's the only way to prevent some dynamically loaded plugin code ten layers deep in the stack from breaking your invariants or stopping your periodic scheduled task due to an errant NoSuchMethodError or NoClassDefFoundError).
And you can't easily use checked exceptions with Java8-style functional code, since interfaces like Function aren't generic on the exception type. Which leads to aberrations like UncheckedIOException, which exists only to make IOException usable in the functional world.
> And that "team" nowadays may also consist of many AI agents.
And that's the part where the hireability arguments collapse. Sure Claude Code works pretty well with Java. It also works well with Typescript, Python, Go and Rust. It would use types on all of these languages, and run a type checker or LSP on the dynamic ones. And while Java is statically typed, Rust has a stricter type system that prevents some types of runtime bugs that Java's type system won't like data races and forgetting to release a resource.
Which are completely safe in Java
> Forgetting to release a resource
I know what you mean and it's definitely a tongue in cheek reply but it's almost like that's what the GC is made for :D bit less sarcastically java has Cleaners, and try-with-resources are a poor man's RAII
I guess I'm asking this as an open question: Where are we in the "move fast vs stable" spectrum with Node these days? Definitely not rock solid, but it's moving in that direction I feel.
As for an individual developer doing a side project, you should use Java if you haven't used it before to get exposure to it. It's a fundamental component of enterprise software and if you've never used it before take the time to learn something new.
At the risk of a shameless plug - blogged about it recently at https://mandeepgill.net/2026/08/31/java-for-an-ai-startup/
That part i s true.
> with newer Java versions the only real gap with Kotlin is null-safety
But that part isn't. Kotlin has:
- Structured Concurrency: Coming to Java sometime in the future, but it's been in preview for very long now.
- Standalone functions that don't have to live in classes
- Properties
- Property delegation
- Data classes: more powerful than records. Can be used for large DTOs that you can modify with copy(). Java needs something like Lombok to make records more useful.
- Extension methods
- Context parameters
- Operator overloading
- Implementation delegation
- Inline functions (which can receive returning closures and reified types)
- Block syntax (supports `it` for unnamed arguments)
- Sequence abstractions: more powerful and more efficient than Java streams due to the inlining and block syntax.
This is just a partial list, but Kotlin clearly has a lot of things that Java doesn't. If you only personally care about NPEs that's fine, but that's not the only thing.
I think the two languages mean slightly different things here. In any case, Java's model is so much more simpler that I don't think they are honestly comparable. In kotlin's case you have to be very on top of your game to have a chance of correctly using it - there is concurrency, parallelism, exception handling all combined into a single abstraction in a non-native way - so your stack traces will be useless/swallowed etc on incorrect usage. Of course the usual caveat applies, just use java's abstraction if you need that.
> Standalone function
Don't really see the benefit, if anything it creates place for style disagreements. A SomethingUtil class was just fine (and findable).
> Properties
Difficult topic with both cons and pros.
> Data classes
Exactly because they are "more powerful" they are strictly worse. A design element is just as much about what it is as it isn't. Copy is good though.
> Delegation
Used a couple of times, but it's not the full blown thing (see manifold)
> Extension method
I will be honest, I really dislike these. They can occasionally help a bit with some DSL, but for the most part they just make code very hard to read. I much prefer a normal static method instead.
> Context parameters
One of the few useful syntactic sugar.
> Operator overloading
Argued to death already :)
> Inline function
Feels more like a hack to support some of these extra features than something you would want to use yourself
> Block syntax
For the rare DSL usecase it's useful. Everywhere else I really dislike it and the accompanying coding style. These .also and similar implicit receiver thingies are just straight up evil.
All in all, there are a few things that are very elegant in kotlin, but I feel they went the c++ c# way of over abstracting just to have a long feature list.
As for Scala it's pretty much a dead language, no one work with it and it's impossible to find dev for it.
10 years ago I was moving Scala code back to regular Java.
go and kotlin aren't it, gleam scratches the itch but I can't justify writing code that would impossible to hire for.
scala situation is a real shame.
I think it used to be common to just look for smart people and assume they can run with whatever stack. Wasn't that the point of abstract algorithm questions etc. (basically an IQ test)?
Lots of companies where software isn't the focus see it as a cost center, so they'd prefer to hire lower-IQ specialists instead of higher-IQ generalists, because the latter are more expensive/have more options.
Aside: Scala dev here - but I only talk about how wonderful it is with people I trust (mostly Go and Rust developers I used to work with).
Also, Scala Native means I don’t always have to worry about the JVM depending on the use case.
ScalaJS is fun too.
The whole go team's philosophy tends to also revolve around assuming their users don't know what they're doing, which is annoying. Like an inline keyword: thinking you know better than me doesn't mean I'm not going to inline it; it means I'm going to manually write it inline myself in the code, and then think the language sucks because it's tedious, error-prone, and verbose. Or they tend to mark lots of stuff private for no reason, and e.g. with TLS 1.3 they just ignore your config because they think they know better, etc.
Java tooling taking up a lot of ram was a major motivation for me. I have done a lot of Scala as well. I don't think either Java or Scala in the real world beat go on performance for most cases. I don't doubt that in some cases jvm can do better but at least before Valhalla delivers all the promises, in real world, I am doubtful.
I have been a Java/Scala user almost for the majority of my career. I doubt I would pick jvm over golang going forward though. Also not having to deal with OOP is a plus.
When you want to make a game and support modding with pretty much zero development effort.
Kotlin is the obvious replacement, but the tooling isn't as good and the community isn't as large. Java keeps getting better, and in ways that diverge from Kotlin - eg, virtual threads vs async/await/coloring. From the perspective of language design, I prefer Kotlin. But I keep picking Java anyway, and I don't see that changing soon.
Go is openly hostile to functional programming. Dynamic languages aren't even in the running. Rust is too low-level for line-of-business software. C# is too Microsoft. The remaining alternatives are too obscure.
Why should anyone use it over Java? Ms is hostile towards its developers, it creates new versions of things, deprecates previous versions, uses confusing naming for newer versions.. etc.
Microsoft is a business and will always put their business objectives first. In my opinion, they have a non-zero amount of evilness. I do not support them jamming Copilot into every available crevice. I still think they make dumb choices, like every imperfect organization. However, C# is a powerful and intuitive language, and for Microsoft shops that already run a lot of Windows and SQL Server it makes a lot of sense.
No shade to the JVM. I've mostly enjoyed my time in that space. I do believe the choice between Java and Kotlin, the wide variety of vendor JDK distributions and IDE fragmentation make the JVM stack a bit more difficult for newcomers to break into.
Majority of things provided from MS instead of having to rely on 3rd party, especially nowadays when supply-chain issues are huge concern
* https://jspecify.dev/docs/user-guide/
* https://openjdk.org/jeps/8303099
I've been a .NET dev for a decade now. It's perfectly serviceable, but I wouldn't say I truly love the language anymore, but I would take it over Java any day. Entity Framework and LINQ are gifts from the Gods. I have never used an ORM that even comes remotely close.
Also, C# is big in the gaming world. I am working on a game right now, and I was not impressed with what many other languages had to offer. It seems like the kings are still C(++) and C#. Of course, Java can create games, but I would argue that is a "could vs. should" kind of decision.
Unity, Godot, Monogame, Raylib, XNA, FNA, etc. all can use or rely on C#. I have not seen Java be compatible with any of those -- except maybe Raylib? I do not know of anyone nor any games that use it though.
I guess if you use/like Intellij it's ok.
It's not as portable as Swing, as it has some platform specific binary components to it. But it works fine on mainstream platforms. For me Swing portability is not worth giving up the FX model.
Just be aware that if you happen to bundle in the Web view component, you're basically adding WebKit to your distribution. I did this with a small project because I wanted to have a "help" screen with Markdown -> HTML. Easy, but "expensive". It simply adds a big chunk (10-20Mb) to your distribution.
(Now I have a very crude Markdown renderer for this task, which is a 100 lines code, and I'm working on a better one -- but I have yet to pull the trigger on the latest FX with its new Rich Text component, which could change everything.)
One hot tip with cross platform FX, however. Embed your fonts. The font suite is not common across the distributions, and the CSS does not honor the font fall back (i.e. if not XXX font, then YYY font), so if the runtime doesn't have your specific font, it collapses to the System font. So, embedding the fonts you use helps a lot with cross platform stability. Plenty of free fonts, I have not had a real problem with this. But it can be one of those O.o moments when you test on other platforms and encounter it the first time.
C# LINQ also seem to be the best compromise between ORM and raw SQL queries, although I never used it myself.
I have been severely disappointed in all similar solutions for Go at least and I imagine Rust does not have something better given it has a smaller community-base.
Python and NodeJS have some very neat solutions for this stuff too, but both are "slow" dynamic languages. I personally dislike python with a passion and NodeJS stuff is extremely community-driven and therefor often unreliable. Prisma (NodeJS ORM) for example just did a major overhaul and is now pushing a completely different API.
If you are making boring REST API to SQL Database it seems like Java and C# are the best options.
And if you are starting from scratch, whatever part of the ecosystem you use, I'm not optimist on its situation improving with time.
Nowadays a lot of code is written with mostly procedural style with some functional characteristics, I wouldn't use Java for that.
Fingers crossed I'll manage to use null type safety in my lifetime.
Unfortunatley without specialized generics and without the performance benefits that are supposed to come with it. They were too slow with Valhalla.
Java simply got too nice.
Just use the language the (keeps putting) puts bread on your table. All languages have their own 'baggage'.
Wrt to AI, I agree with pron's comments that for very large code bases, AI can't do 4$hit.
They are useful for neural information retrieval (RAG, memory), which relies heavily on content vectorization and similarity matching using their dot products.
Let me be clear: Do I think it is possible to write good, clear, performant code in Java? Of course - Java can be used to write great software. The problem is that in Java there exist an extraordinary set of variations of a sufficient implementation, many of which are package protected abstract static horrors shows. And that will manifest over time if you add different individual developers. Else the project must engage in bureaucracy and control, where you have meetings over style conventions or hardline architects who come to constrain the joy of programming in the devs.
Its much better when the language itself constrains you, then everyone can just move on. I feel Go, though certainly not perfect, meets this niche.
In the specific case of java however I don't think this applies much; the vast majority of improvements are aimed at under the hood optimisations rather than syntax. And I feel that any changes in syntax have been quite incremental, intuitive, and reasonable.
AND they stay around as preview features forever, so this may inflate the perception of features entering the language, when in fact it's the same one feature being mildly iterated on.
E.g. the main syntactical change in this version seems to be the use of primitives in switch statements, which was already discussed for a while, and is itself a meaningful change brought about by the introduction of switch expressions.
Are you sure this egg is actually viable?
I mean, I'd love to see it, but...
You can essentially live all your java life without ever interacting with Oracle.
Who's there?
long pause
Java
https://lmax-exchange.github.io/disruptor/
Knock, knock
who's there
It's the 90s, wanting their jokes back
edit: don't get me wrong, i am not here to hate java, but bc i am also ... hm necrophil :)
Now try asking, "When did Java get auto-properties?"
I missed generators like that virtual threading for example.
btw: i was referring a single point when oracle bought sun and "closed java".
Language that runs on the explicit design philosophy of letting other languages experiment first and then incorporating their lessons learned once the dust has settled is late to implement a feature.
Same reasoning here, Java is "enough" in of a lot of areas that make it desirable for a lot of things, it is fast enough, stable enough, has lots of libraries and apis for a lot of things you may want to do, and the jvm makes it reasonably portable, among other things.