Java has decade(s) of cruft and breaking changes which LLMs were trained on. It's hard to compare. Plus Go compilation speed/test running provides quick iteration for LLMs.
There is a decently long list of breaking changes now. Removing JavaEE modules from the JDK, and restricting sun.misc.Unsafe, are the ones people usually run into.
A long enough list of small changes eventually equates to a big change. People generally can't update applications from Java 8 or below to a new one without code updates.
If hadoop did it, so can you. I'm talking about a project that stretched Java 8 to, and arguably beyond, its intended operational boundaries. Unlikely that you’re leaning on this boundary. It's Spring Boot upgrades that will be giving you troubles.
Not really. It has a pretty bare bones OOP (single inheritance, interface), primitives and objects, generics and pretty much that's it.
Newer features fit very nicely and didn't increase the language surface (records are just a normal class with some methods auto-generated, while sealed types are just a restriction on who can subtype an interface -- and yet these give full ADT support for the language that improves readability and type safety).
Annotations add a seemingly infinite amount of new semantics. You can’t predict anything with confidence just looking at the code without also studying the annotation processors in depth, which regular Java tools don’t help you with.
Also, java annotation processors are strictly "append-only", they can't change code written as is. They may subclass it, or build new classes that make use of the annotations.
In my experience LLMs are more than happy with annotations, especially from the widely used ones (spring/jakarta ee, lombok though it's not an annotation processor, etc).
If you think about it, an annotation like @Path("/endpoint") is very informative for both humans and LLMs alike, being local to what it attaches to. Within an agent, the code serving the endpoint will be immediately visible in the context returned by a simple string search, no need to do another round of "find wherever this codebase registers routes".
That metaprogramming is how a lot of places end up doing Java, because Java is a bad language that needs crutches like that. Or at least needed until whatever newer version fixed limitations of the old one, but all that old code is still around.
Biggest thing was lack of cooperative multitasking until virtual threads (Project Loom). And much older, lack of lambdas before Java 8.
It’s even simpler to have a “register” method that takes the path specification, the HTTP verb, and a function to handle the request.
There is no case where annotations are superior to the meta programming facilities built into the language proper. Annotations only became popular because Java didn’t have easy syntax for function references or anonymous functions for a very long time.
Also, hard disagree on "register" method being simpler. A register method is code, while an annotation is a static declaration, ergo data. The former can happen in a helper function, renaming it from "register" to "path", changing/modifying the parameters, or putting it into some while loop dynamically generating endpoints.
At the very extreme it is Turing complete to figure out what endpoints are registered.
10+ years of lambdas, but you can still tell from new code that the language used to not have lambdas. Plenty of code is older than that too.
The annotations are static, yeah. That's one advantage. I would still rather not do that. A lot of people were happy not to need that anymore in like NodeJS.
Any company that's 10 years old will have code that's 10 years old in every language they use. And every language has some flaw that was "fixed recently" but that's not relevant in a mature codebase. Like "Python packaging is fixed now with uv" no it's not. And especially anything to do with threading vs cooperative multitasking will stick around.
Mostly old code will keep working, but there are exceptions like Python 2->3 breakage that deserves all the criticism it got.
Sandboxing is a completely orthogonal issue and WASM is probably not a good direct target for LLMs.
Of course writing a language that compiles to Wasm is certainly a way, but you would have to sandbox also all the other tools that is used during development (e.g. agents can just call grep/find/etc).
How are JVM startup times nowadays? Frankly the need to ship with a JVM (modulo graal admittedly) is a drawback, eg for CLI tools - which the author listed as a requirement. Go's static executable story, combined with it's competitive performance - without C++ or rust contortions - is a strong combo when your focus is on startup time and deployment simplicity. (If, otoh, I cared a lot about GC I'd definitely prefer Java - eg for non-fast algo trading.)
Java is a fine language, tech stack, and ecosystem, but I agree with the author and parent commenter that this is a sweet spot for Go. Their decision to use it makes a lot of sense.
Exactly, the propping up of Go seems unfounded. Java in it's newest iterations make it more compelling as a target, and people, especially young people, overlook it because of its stigma as enterprise cruft.
Most of the young people like me learned Java as a first language in AP Computer Science, then probably again in college. We were tested thoroughly on all the OOP details and required to use that in projects. The most popular video game is/was also in Java, and people love to mod it.
But still nobody wants to use it given the choice, because it sucks. 75% of the code you write is boilerplate mainly thanks to OOP, the com.foo.bar stuff is an eyesore, and it somehow uses even more RAM than Python. That's without getting into the enterprise cruft land where each method has 3 annotations on it, where the annotations themselves are maintained by an entire outsourced team.
Well, you should have learned about the language something in the meanwhile then.
75% boilerplate, come on. It has records and pattern matching now. Many of the cases of boilerplate are strictly self inflicted. And this criticism is absolutely laughable in a thread about Go, where you have to repeat some error ritual every 3 lines, losing the signal in all the noise. Java is a much more concise language than Go.
com.foo.bar solves a very real issue that is a big problem in e.g. the Node and Rust ecosystem (package name squatting).
And it uses as much RAM as you set it to use. It's true of every managed language that more memory equals less work to do, that is better throughput. But Java's GCs are the state of the art, it will work just fine with half the pre-configured memory.
Records and pattern-matching didn't exist back then and are only used in new codebases. That isn't enough to fix it anyway.
The Go error handling is bad enough that it makes the lang not a top choice for me. Seems like entire vision of Go was being the opposite of C++ in every way, and that resulted in something halfway decent, but it unfortunately doesn't use exceptions. You get stuff done in fewer loc than in Java at least, and it always had solid m:n greenthreading, and the GC just works without having to tune it.
However, the cause it not really Java as such, but massive frameworks and tooling (e.g. Maven). Maybe AI will bring some fresh air to Java, because old truths might no longer be valid. But it will be hard to get rid of old habits because it manifested in AI training data.
"AI guys" use Claude CLI that renders to a terminal via freakin' react.
Come on, java starts up fast enough and the memory usage can be set (better throughput vs less memory, it's a classic tradeoff. Go just defaults to worse throughput).
Really, without some fat framework doing all kinds of initialization stuff, java code starts up practically instantly.
Java takes flags for min/initial/max memory that, as a strict law of nature, are always wrong the first time you try. And it holds onto unused memory unless you pass another flag. Idk exactly why there's no reasonable default for that, but probably cause it's in a VM. No other language has this problem.
The Java "compiled" code isn't a native binary like in Go, it really does run in a VM. I honestly don't know if that's why they handle memory differently though.
> Come on, java starts up fast enough and the memory usage can be set
Yeah it can be adjusted, on the other hand you have a language that just works.
Starts instantly and memory usage is bounded by usage, not guesstimate of how much the JVM will need.
> Really, without some fat framework doing all kinds of initialization stuff, java code starts up practically instantly
Have you many java projects that didn't use some fat framework?
Good for you if you did, but 100% of java project I had the displeasure of touching were huge mess with deprecated frameworks on ancient java versions.
The points about tooling certainly do not apply to Java, for example, Maven vs Gradle (which is a huge mess in and of itself - do you use Kotlin or Groovy), and which plugins are you willing to pull in? Or is Ant still the thing? SpotBugs, Spotless? What test framework do you use? Do you use Mockito or one of the myriad alternatives?
Go has _none_ of this nonsense, and it's better for it. Nor does Rust, FWIW, which IMO is also a better target language than Java for just about everything.
Gradle could be used as an AI benchenmark on its own! The syntax of plugin DSLs changes all the time. Special credits could be achieved for handling old (Groovy) and new versions (Kotlin) of Gradle itself.
I mostly write Go code (and have barely had to write any code myself in the past months), but today I had to do some work in a Java project and Claude Code was a terrible experience.
It really felt like using AI tooling of a year or two ago. It wasn’t understanding my prompts, going on tangents, not following the existing style and idioms. Maybe Claude was hungover or doesn’t like mondays, but the contrast with Go was surprising.
One example is that I wanted to add an extra prometheus metric to keep track of an edge case in some for loop. All it had to do was define a counter and increment it. For some reason it would define the counter the line before increment it, instead of defining it next to the other counters outside of the for loop. Technically not wrong (defining a counter is idempotent), but who does that? Especially when the other counters are defined elsewhere in the same function?
Anyway, n=1 but I feel it has an easier time with Go.
Well, there was a Claude outage today, maybe related :D
My n=1 is that it is pretty good with Java, on par with other popular languages like Python and JS, in line with these 3 probably being a good chunk if not the majority of training data.
It's an even more popular language with even more training data and also has a better type system so more validation on LLM output, etc.