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.
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.
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.
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.
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 when it relates to new versions of Java. 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.
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.
> 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.
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.
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 ...
> 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.
> You mean it needs 15 lines whete C# needs one? ;)
Can you elaborate? The async/await approach is much more than 1 line when you need to switch a call between them. Whereas the green thread approach does not need anything.
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)
Sure, we can find factors for "verbosity increase" and "difficulty to understand a line" where what you say is also true.
I think that java found a decent spot where both factors are low. It's not a particularly verbose language, especially in modern times (records, type inference, I even mention lambdas because some people live in caves), like if you do POJOs and stuff with getter setters than you get a no information increasing extra 3+3 lines of code per field and that's most of it.
Method bodies are not more verbose than other typical languages.
> 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
> 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.
- 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.
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.
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.
> 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.
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.
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.
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.
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.
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.
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.
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
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.
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).
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.
> 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.
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.
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?)
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.
>> 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.
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.
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.
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.
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.
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.
> 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.)
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.
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.
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.
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.
> 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.
The former is for people who are tired and just want to finish their work. The latter is for people who want to do "cool" work more quickly than ever before-- maybe without finishing.
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?
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.
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.
"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.
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.
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.
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.
They accept Object not because they're not type safe. They accept Object because key `equals` method might return `true` for unrelated classes by design, and collections interfaces have to accommodate for that. In other words, you might put key of class C1 into the map, and later use `get` with key of class C2. And if these classes happen to implement `equals` accepting each other, the collections are supposed to work.
So the core issue is that `Object.equals` method isn't type safe. Collections API just follow that.
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.
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"
> 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.
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.
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.
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.
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.
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. 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.
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.
> 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.
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.
> 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.
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
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.
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.
Oh I shouldn't have said "usually," it depends. The typical net/http recovers from panics inside handlers and sends error 500. Some other servers don't.
If you spawn your own goroutine, you also need to wrap things in recover() or else a panic crashes the entire process.
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.
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.
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?
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).
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.
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.
> 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!)
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.
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.
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
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.
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.
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.
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.
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.
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.
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.
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.
These days, Java is mostly used in greenfield projects for 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, streaming services, retail, hospitality, healthcare etc.. It's usually not a first pick for more interesting and talked-about software, such as Python type checkers, JS bundlers, or TUI file managers.
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. 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.
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.
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.
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#.
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#.
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?
Wasn't the discussion about greenfield projects? Or by greenfield do you mean internal greenfield projects at existing companies that already use Java?
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?
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.
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.
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.
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.
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.
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.
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.
Java is still excellent when you need to have software that ships and gets integrated into a customer's environment, or need to support a diverse set of such integrations yourself.
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)
A big issue with java at big companies is that the JVM deployment is managed by sysadmin and devs can't touch it. To bless a java upgrade therefore means getting a defensive bureaucracy to take on risk, however miniscule, and since most Java improvements only help devs (rarely do they enable critical user-demanded features), sysadmins won't budge.
I don't know for what Java is good. I've startet coding with Java but it's si complex and server with spring is not very efficient I mean you can make android apps with it but you can make android apps with python, c and kotlin too. You only need the libraries to compile it to dex format for android. So for what do you need Java?
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.
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.
Do people just not type fast, ignorant of how IDEs work, or what exactly is the deal? I'd rather have the clarity of everything above. It tells me exactly what I need to know and what I can do with it.
257 comments
[ 1.1 ms ] story [ 90.7 ms ] threadThey are useful for neural information retrieval (RAG, memory), which relies heavily on content vectorization and similarity matching using their dot products.
Who's there?
long pause
Java
https://lmax-exchange.github.io/disruptor/
I think it was called "When nano seconds matter", or something similar.
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 when it relates to new versions of Java. 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.
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.
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 ...
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? ;)
Can you elaborate? The async/await approach is much more than 1 line when you need to switch a call between them. Whereas the green thread approach does not need anything.
var x = someFunction() waitForSomeOtherFunction()
That's semantically equivalent in the two languages.
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 ;)
One reads much faster than they write, I don't really see a problem with easily guessable auto complete.
I think that java found a decent spot where both factors are low. It's not a particularly verbose language, especially in modern times (records, type inference, I even mention lambdas because some people live in caves), like if you do POJOs and stuff with getter setters than you get a no information increasing extra 3+3 lines of code per field and that's most of it.
Method bodies are not more verbose than other typical languages.
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".
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.
Looks like Java's "there is no simpler syntax than plain old synchronous code" is just a lot of extra manual wrangling of stuff
They won't block the thread, and you can have millions of them.
Like it's no accident that c# with their goldfish attention span wanted to ship virtual threads as well next to all their millions of features.
I hope you know and understand the difference. Then strangely enough all my examples will make sense.
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.
I Java? Yes. Because of the language design, and not because of some inherent "encapsulation" or something.
Somehow `x with { a = b }` doesn't break encapsulation and offers nice DX. But object initializers? Lol
> And a class will be able to choose which things can be set, which is not the case for initializers.
The class in C# can easily chose what can and cannot be set. Because unlike Java it actually cares about things like this.
> That code won't look that much different with async/await.
Lol. https://news.ycombinator.com/item?id=49726868
- 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.
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.
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.
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.
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.
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.
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.
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.
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.
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?)
https://en.wikipedia.org/wiki/Log4Shell
https://learn.microsoft.com/en-us/dotnet/core/install/window...
I mean it's short and concise and there are additional resources that provide more detail. IMHO it's not a bad thing.
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.
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.
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.
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 :)
.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.
The vibe selects the audience perhaps.
The former is for people who are tired and just want to finish their work. The latter is for people who want to do "cool" work more quickly than ever before-- maybe without finishing.
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
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.
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.
So the core issue is that `Object.equals` method isn't type safe. Collections API just follow that.
Are you sure this egg is actually viable?
I mean, I'd love to see it, but...
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.
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.
- Annotation processing: if you know Lombok, MapStruct.
- Class loader.
- Reflection.
- Garbage collection.
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.
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
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.
Otherwise, yeah, you're still in for a world of pain.
If you spawn your own goroutine, you also need to wrap things in recover() or else a panic crashes the entire process.
* 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.
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
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!)
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.
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.
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.
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.
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 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.
medtech - .NET and TypeScript.
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#.
"We are a Java house" is not a Greenfield thing. It's picking what you already use internally.
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?
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.
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.
When you want to make a game and support modding with pretty much zero development effort.
I guess if you use/like Intellij it's ok.
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.
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/
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.
You can essentially live all your java life without ever interacting with Oracle.
Just use the language the puts bread on your table. All languages have their own 'baggage'.