> And yet the care and feeding (tuning) of the GC consumes countless millennia of human time per year.

This is a feature not a bug. When I write C++ code I need to make a lot of decisions in terms of memory behavior that would deeply impact performance. I can benchmark locally but production is the part that matters and I'm mostly blind there. An observability solution might be able to detect problems if I used the default C allocator under the hood, but in that case my performance would also be terrible to begin with.

Java lets me detect production performance issues and even pinpoint them to specific objects in a shipping application. This can translate to feedback to developers that could be very valuable. But it also allows me to tune GC behavior in production to get better performance with zero code changes.

Furthermore, the object deletion cost is deferred so if your app works in performance peeks it can be very efficient assuming it has enough memory allocated to it.

The downside is that Java will probably always need more RAM than non-GCd languages (the amount varies). That's a reasonable tradeoff for most since RAM is still cheaper than CPU/GPU.

> But we've been promised the ultimate compacting pauseless GC for decades, and yet I see outage after outage of huge systems root caused to "look, we found a brand new way that the GC can explode (or just thrash) on us.

There is no "one size fits all". Java has such GCs but right now the default JVM ships with several GCs since every workload has different requirements. This means you need to tune it which is a challenge.

If you see bad performance and don't know what to do then I would blame your observability provider. Good ones actually provide recommendations on how to improve a thrashing JVM.

> One can make the case for which is better (well, least bad) of Java or C++ error messages, but neither can come close to Rust error message quality and cleanliness.

Rust is great. But it is a young language with less baggage. It has no binary compatibility to deal with and it doesn't offer many of the language features Java (or for that matter C++) have. Those are all good things for Rust, but I wouldn't build the type of apps I build in Java using Rust.

> Like I said I'm impressed that Java managed to make error messages worse than C++, but you can disagree.

I just spent 20 minutes the other day looking at 100 errors in std::vector which I didn't touch at all due to closing brackets appearing in the wrong place... C++ error messages are at a different level compared to Java. Maybe I'm too used to Java but I never spend time on these things and the IDE is fantastic at handling 98% of the stuff.

> Go is not my favorite language, but here I think it's better than Java. RAII beats defer, but at least defer makes the programmer add the cleanup at resource acquisition, not four pages further down, possibly behind a null pointer check in case the finally clause triggers before the resource acquisition.

Go was built for simple quick solutions at the system level. Typically in such cases you would need to handle the error as it happens.

Java was built for libraries/frameworks/APIs. E.g. if Spring runs into an error it can't handle it since it doesn't know whether you want to show the error to the user, recover, log etc. Throw is the only reasonable option. This lets me build a nesting doll of libraries and dependencies that can defer decision making and even add a systemic solution that eventually handles that. E.g. in Java I can define a recovery policy (retry/circuit breakers etc.) with an annotation and the system would handle all of that for me.

In a typical Java backend application I don't write catch at all. You let the exceptions propagate and then write error handlers at the top level to define behaviors for the system/user. This also makes tracking issues in production much easier as you get a clean stack trace that includes all the details you need to fix most issues.