Hacker Newsnew | past | comments | ask | show | jobs | submit | unscaled's commentslogin

I think Java explicitly refused to take some of the best features of Kotlin, like extension methods, context parameters and operator overloading.

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


> Structured Concurrency

I think the two languages mean slightly different things here. In any case, Java's model is so much more simpler that I don't think they are honestly comparable. In kotlin's case you have to be very on top of your game to have a chance of correctly using it - there is concurrency, parallelism, exception handling all combined into a single abstraction in a non-native way - so your stack traces will be useless/swallowed etc on incorrect usage. Of course the usual caveat applies, just use java's abstraction if you need that.

> Standalone function

Don't really see the benefit, if anything it creates place for style disagreements. A SomethingUtil class was just fine (and findable).

> Properties

Difficult topic with both cons and pros.

> Data classes

Exactly because they are "more powerful" they are strictly worse. A design element is just as much about what it is as it isn't. Copy is good though.

> Delegation

Used a couple of times, but it's not the full blown thing (see manifold)

> Extension method

I will be honest, I really dislike these. They can occasionally help a bit with some DSL, but for the most part they just make code very hard to read. I much prefer a normal static method instead.

> Context parameters

One of the few useful syntactic sugar.

> Operator overloading

Argued to death already :)

> Inline function

Feels more like a hack to support some of these extra features than something you would want to use yourself

> Block syntax

For the rare DSL usecase it's useful. Everywhere else I really dislike it and the accompanying coding style. These .also and similar implicit receiver thingies are just straight up evil.

All in all, there are a few things that are very elegant in kotlin, but I feel they went the c++ c# way of over abstracting just to have a long feature list.


> Hiring developers is easy

> And that "team" nowadays may also consist of many AI agents.

And that's the part where the hireability arguments collapse. Sure Claude Code works pretty well with Java. It also works well with Typescript, Python, Go and Rust. It would use types on all of these languages, and run a type checker or LSP on the dynamic ones. And while Java is statically typed, Rust has a stricter type system that prevents some types of runtime bugs that Java's type system won't like data races and forgetting to release a resource.


> data races

Which are completely safe in Java

> Forgetting to release a resource

I know what you mean and it's definitely a tongue in cheek reply but it's almost like that's what the GC is made for :D bit less sarcastically java has Cleaners, and try-with-resources are a poor man's RAII


Sure it does:

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.


That one works pretty much the same way as NullAway, which is kinda unsurprising because of the name and because of who made it.

Go panics.

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.

> Congratulations. You've added another build tool and sprinkled your code with ugly annotations and ifs and Optional Optional.of(x).map(y) all over the place to get the same thing you'd get by moving to Kotlin.

You deserve the strawman award of the year.

NullAway and JSpecify encourage making as many types non-nullable as possible, thus they can actually also advice about removing redundant null checks. Nullable types become the painful exception that visibly spreads through the codebase, which discourages writing code that relies on null.

Optional doesn't enter the picture at all. NullAway kills their usecase within ones own code. They are anyway only recommend as return types to force others to check for an emoty case, but I think Optional will become fully optional when the Java platform gets nullable types on its own.

Google Error Prone is a code linting tool that's very useful in its own right, and NullAway is just another plugin.


More Java strawman arguments. How do we live with BuilderFactoryFactoryBuilders! Every day we cry ourselves to sleep!

While Java can outperform Go in some cases, the situation is very much the opposite when it comes to Rust.

I also don't see the case for stability. Yes, if you're still on JDK 8, it would probably chug on for a couple of years. But we were talking about greenfield projects and newer JDK go EOL much faster. If you want patches, you'll have to run your app to a newer JDK, which may break a couple of things. Rust (within the same edition) or Go (within the same major version) break less than that.

As far as runtime compatibility goes, Rust and Go apps ship with the runtime. This can be better or worse for you, depending on what is your upgrade story, but I don't see a clear winner here. What I would give to Java over Rust is that you will have far fewer dependencies to take care of if you need to upgrade. But the same goes for Go.

For observability, I feel that with Rust you have a bit less that you need to observe (no GC to worry about). Tokio tracing is great, but observability requires a bit more effort. The go observability story is far worse. So Java probably has an edge here, but not something that ever felt like a game changer. My impression is that for most of the enterprise shops that love Java, observability means collecting unstructured log files through NFS and trying to find a needle in the haystack with primitive tools, but I've been out of touch with this world for a couple of years.

Productivity is something that is dead if you are AI-heavy. Sure, many shops are still wary about AI, and I totally get why, but this is a battle that's already been lost. Without AI, I would say I was about 3 to 4 times more productive in Rust than I was in Java, but ramping up that productivity took at least 1 year of practice. It's not time most companies are willing to spend. With AI, this doesn't matter anymore, for better or worse.

I'm not arguing that Java is not chosen often for greenfield projects. It's clearly extremely popular in many circles, especially outside startups and big tech. But I think the reason Java is chosen have little to do with the reasons you've mentioned above and more with organizational preferences.


> I also don't see the case for stability. Yes, if you're still on JDK 8, it would probably chug on for a couple of years. But we were talking about greenfield projects and newer JDK go EOL much faster. If you want patches, you'll have to run your app to a newer JDK, which may break a couple of things. Rust (within the same edition) or Go (within the same major version) break less than that.

Java also breaks very few things. Breaking binary compatibility is a no-go since it's a core promise of the platform. The only thing in the surface language that has ever been changed is the meaning of the underscore as an identifier, as well as the behavior of == in upcoming Project Valhalla.

> As far as runtime compatibility goes, Rust and Go apps ship with the runtime.

Java applications can also be shipped together with the runtime.

> Productivity is something that is dead if you are AI-heavy.

Nevertheless, making constructs available to express intent more clearly should also help LLMs to not go off the rails.


> the situation is very much the opposite when it comes to Rust.

It isn't, and the problem isn't Rust specifically, but all low-level languages. They can offer very good performance (often better than Java) when small. But as they evolve over time, or are very large to begin with, they become much harder to keep performant. This is for pretty fundamental constraints of low-level language that I mention in another comment here, and this performance problem with large programs written in low-level languages was well known before Java even existed. The JVM was designed, at least in part, to address it.

One of the things that drew me to Java (from years of C++, even though I still work in C++ when I work on the JVM) is precisely how it addresses those performance issues we ran into with C++ five years into a project.

> As far as runtime compatibility goes, Rust and Go apps ship with the runtime. This can be better or worse for you, depending on what is your upgrade story, but I don't see a clear winner here.

I wasn't talking about "runtime compatibility" but of overall version compatibility. Java has an unmatched compatibility record - not perfect, but better than anything else (with at least a medium-sized standard library).

> For observability, I feel that with Rust you have a bit less that you need to observe (no GC to worry about).

Memory management is very often a bigger issue without a GC than with a moving GC. Time and again we see Rust or C++ programs spend 30-50% on memory management.

> Productivity is something that is dead if you are AI-heavy.

Really? Have you had AI write a good medium-sized (say 100-500 KLOC) program or maintain one over a long period of time without very close reviews? The only people I've seen who don't know about the ticking time-bomb agents leave in the codebase are the people who don't look.

> With AI, this doesn't matter anymore, for better or worse.

You may be talking about small programs. I agree that for small programs, low-level languages can offer excellent performance, and AI can be okayish, and you can get some observability you can live with, but I'm talking about large programs.

> But I think the reason Java is chosen have little to do with the reasons you've mentioned above and more with organizational preferences.

Those organisational preferences are due to a long record of delivering on the things I mentioned. Java has an exceptionally low "regret factor", i.e. people who regret choosing it five, ten, or fifteen years into a project (which is when the problems usually start).


> They can offer very good performance (often better than Java) when small. But as they evolve over time, or are very large to begin with, they become much harder to keep performant.

Generally, efficient memory management is orthogonal to object oriented design. Meaning, as your complexity grows and your business logic changes, it often means the optimal memory management changes because the lifecycle and relationship between objects change.

For a web server for instance, you have both request/response as well as various transactional memory requirements. In Java, the role of the garbage collector is to adapt to whatever the best memory policy is based on runtime behavior, rather than statically defined rules. One could say that the evolutionary and revolutionary changes in garbage collectors as well as the multitude of tuning parameters comes from this being a really hard task.

If you have a services architecture, the runtime advantages of Java go down significantly.

> I wasn't talking about "runtime compatibility" but of overall version compatibility. Java has an unmatched compatibility record.

I would say both matter significantly more again in a monolithic architecture. It matters a lot more when you are trying to deploy your software into a single application server, or trying to avoid version incompatibilities when integrating large amounts of code into a single executable.

> Time and again we see Rust or C++ programs spend 30-50% on memory management.

I've seen plenty of Java applications spend 30 seconds or longer because they had to do a full garbage collection back in the day. I even had one customer who maxed out Java to utilize all the memory in their server and hit a 13 minute production pause due to otherwise unoptimized GC (promoting many temporary transactional objects to the mature generation until it eventually exhausted memory).

The different strategies for memory management (static vs dynamic) ultimately still require recognizing, diagnosing and correcting issues. GC provides unique challenges because the tuning mechanism is decoupled from the actual code. GC challenges can also often go undiagnosed until staging/production workloads hit them, precisely because they are dynamic behaviors.


> Time and again we see Rust or C++ programs spend 30-50% on memory management.

30-50% of what?


Oh, sorry, missed a few words. Their CPU time.

Gotcha, that bit makes more sense now. That's quite the statistic!

I’m curious where you have seen this.

It's quite common in concurrent services that non-experts write. But the more interesting cases are things like Moka. In a simple evaluation (and, of course, not much can be extrapolated from any benchmark) Java's old Caffeine library had lower latencies in all percentiles at twice the throughput as Moka (at 90% cache hit rate), as the latter spent 41% of CPU (on top of the cost of malloc/free) on epoch based reclamation.

> and, of course, not much can be extrapolated from any benchmark

Right, so one case (which I certainly believe is possible) is very different from “time and time again.”


By time and time again I meant concurrent services that are written by people who are not experts at low-level programming. The irony is that they don't see "CPU spent on memory management" as they do in Java not because there's less of it - quite often it's much, much more - but because it's simply not measured and reported.

As for the caching test, it's just technically interesting, because the JVM was designed to address the performance issues we suffered from in large C++ programs (all the JVM engineers are, of course, C++ people), both due to compilation and to memory management, and we regularly compare both our compilation and memory management algorithms to other approaches, and it just so happens that last week one of our GC engineers compared Caffeine to Moka and saw how CPU-intensive the memory management work is compared to ZGC (he was particularly interested in this because caching is one of the more challenging workloads for generational moving GCs because a cache deals with many old objects, whereas generational GCs tend to focus more on young objects, and he wanted to make sure that our GCs help reduce the high memory-management overheads associated with low-level languages even in this challenging scenario).


I'm not sure what enterprise-level collaboration means. In my experience, "enterprise" usually means: "Let's use tools that are 10 years behind, buggier than average, and have lots of half-baked features, none of which we need".

I'm not sure what kind of tools you mean, but unless you're looking for something that just works exactly the way EJBs do for some mysterious reasons, I don't see why you can't do most "enterprisey" things with Rust or Go. Or Python or TypeScript for that matter.


> Let's use tools that are 10 years behind

Yes and that's exactly what modern tooling is missing. Try to develop for node.js 0.2.12 on today's update of Visual Studio Code. See? No enterprise-level collaboration for ya.


I've found the challenge of running a non-existent version on a tree that was EOL 16 years ago is typically keeping it up with internal security standards, and not one of new collaborative development.

> internal

Exactly. Never updated, never discussed, never challenged. Set in stone.


> Go has null pointer dereference problem.

Which Java famously does not have.

> Rust is too low-level for typical enterprise app where requirements changes twice a day. You end up spending time and tokens fighting with borrow checker.

In my experience, you do not spend tokens fighting with the borrow checker anymore, newer models are smarter. But it might not be ideal for a lot of CRUD applications.

> C# is MS product, which is no-go for some folks.

This is 2026, it's not 1996 anymore. .Net works on Linux and Microsoft is as friendly towards open source and open standards as a Big Tech company can be.

If anything, it was Oracle which more recently sued another company for using a JDK alternative. And this was a lawsuit that, if accepted, could have put the entire idea of API compatibility in danger and deal a severe blow to the Open Source movement.

Anyone who is morally bothered by MS but is unfazed by this is probably just mentally stuck in the 1990s.

> Kotlin probably would be the answer.

I love Kotlin, but I'm afraid that's not the case. The conservative organizations that choose Java out of inertia, would keep choosing Java over Kotlin, even if Kotlin is a better JVM language which is facing no downside.

For anyone who doesn't need to be on the JVM or work with JVM tooling, Kotlin doesn't cut it. It doesn't have null pointer dereference problem in theory... Only it does in practice if you're using any Java API that may return null (all these bang-decorated "Platform types"). Generic type erasure can only be overcome in inline functions with reified types. And building and deploying artifacts without docker is still a mess.

I found Kotlin extremely publishing for Java shops in the past, and I've converted multiple departments totaling over hundreds of employees to use Kotlin. But that was before AI. The rationale was simple: Java is an entrenched language that leads to bloated code, slow development cycles and way too many avoidable bugs in productions. Kotlin solves some if these issues, and it's very easy to learn for a Java engineer, while still letting you keep all of your tools and libraries. And as a language (putting ecosystem aside), I find it better than either Go or Typescript, and far more ergonomic than Rust[1].

But all of these arguments die with AI. Rust is just as ergonomic as any other popular language today if you're using an agent, and the fact that an engineer spent their lifetime writing Spring Boot programs in Java you don't have time to let them learn a new stack from scratch doesn't matter anymore.

Sure, there are many companies where letting AI write the code is still not acceptable, but most of these workplaces will accept AI agents sooner than they accept Kotlin.

I feel a bit sad since I like many ideas about Kotlin (especially how amenable it is for making DSLs) but we've lost that opportunity

--

[1] Unless you have to write highly concurrent code without any data races.


> Rust is just as ergonomic as any other popular language today if you're using an agent

Have you worked on large (>500KLOC) codebases with an agent? Not only do you have to be an expert at the language, but even if you're lucky and everything is fine, Java code is likely to be particularly fast by comparison, because the agents aren't very good at manual optimisation, especially as the code grows (they're even worse than humans at that, and humans aren't great at manual optimisation of large codebases, either, which is one of the problems the JVM set out to solve; in fact, agent-written code in a low-level language gets pretty slow well below that size). Oh, and the long build times certainly don't help.


Have you worked on large (>500KLOC) codebases with an agent?

Yes. But keep in mind KLOCs are not easily comparable across languages. Java is notoriously verbose. A 500KLOC codebase in Java would usually be half that size in Rust. If your argument is that large codebases makes life harder for agents, you should go with a less verbose language.

I'm not sure what "manual optimization" means (isn't it a bit of an oxymoron when the agent does it?), but if your agent has the proper tools (e.g. ast-grep, rg, semble) it can deal with large codebases. Would the agent create slop? Yes. But it wouldn't be worse on the slop that humans created on every moderately-sized Java project I've worked on.

> in fact, agent-written code in a low-level language gets pretty slow well below that size

I've never seen this happening. I've seen agents writing suboptimal Rust code (e.g. copies instead of Cow). But while this occassionally happens with Rust, I've never seen an agent optimizing for Java where necessary (e.g. using object pools to avoid GC churn). Java is not magic.


> is notoriously verbose. A 500KLOC codebase in Java would usually be half that size in Rust

Lol, no way. Especially that rust is pretty verbose all things together (which makes sense, given it's a low level language - ergo you have to literally express more things about the code)


> it's a low level language - ergo you have to literally express more things about the code

That isn't really a comparison of the languages as much as the standard runtimes and ecosystems. It is important to consider that each have comparable components.

So you aren't comparing a no_std rust project against a comparable JavaCard, but say Diesel vs Hybernate code examples around ORM.


Comparable components, but almost every line of Rust code expresses information about the lifetime of objects - either implicitly (quite often), or explicitly.

Meanwhile in java it's a constant "Arc<JavaType>", and scopes don't mark "drop points"


> A 500KLOC codebase in Java would usually be half that size in Rust

Ok, so you barely know either Java or Rust.

> If your argument is that large codebases makes life harder for agents, you should go with a less verbose language.

You mean, like Rust??? But no, that's not my argument. Agents have a hard time keeping up the architecture in large software (and the differences between verbose languages like Java, Go, Rust, and C++ vs less verbose ones like Python and JS don't make much of a difference). So they either make a mess or they do the simple thing, and the simple thing in low-level languages is often slow.

> But it wouldn't be worse on the slop that humans created on every moderately-sized Java project I've worked on.

Yeah, I don't think you've actually tried it.

> I've never seen an agent optimizing for Java where necessary (e.g. using object pools to avoid GC churn). Java is not magic.

Object pools are far less efficient than Java's GCs, but while state-of-the-art compiler and memory management technology is certainly not magic, I suggest you learn more about these things if you want to make informed decisions.


> Which Java famously does not have.

Hmm? Java gets null dereferences all the time, that's what a NPE is. The VM takes on the extra plumbing to surface a dereference of a null pointer in a recoverable way to code. On Windows this is done using SEH, on Unix it is handling SIGFAULT - but each NPE corresponds to a null pointer dereference that java then tries to clean up.

That the language does not have a way to have compiler enforced "never null" is actually a huge productivity drain, specifically because you have to do your own defensive measures against null or attempt cleanup/recovery when it happens.

Even languages like Swift which use Optional (e.g. a maybe monad) to provide a concept of nilability still internally will hit null pointer dereferences on occasion with faulty bridged code/bindings. However, they treat this as a non-recoverable violation of invariants - a developer shouldn't be trying to recover from incorrect code at runtime.


OP already answered that one:

They used a closed list of 3 vendors in prioritized order, and got 429ed out of two of them, while the third one stopped serving the mode.

This is less of a problem if you're running an agent locally and routing your problem to OpenRouter - you can pin to one or two models for consistency and just switch models when something goes bad. But the article is specifically about production traffic.


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

Search: