I agree that these things are just complex. But, what's interesting to me is that I fully agree with your second sentence and I see it as an indictment against the Java ecosystem around these operations. The common Java frameworks, IMO, serve to make the trivial stuff even more trivial, but then make the complex stuff even more complex. It's exactly the opposite of what I want.
Let's look at JacksonXML for serialization.
Here we have a framework that uses runtime reflection to more-or-less guess how to (de)serialize an object. Figuring it out at runtime is a tough engineering choice already, because it pretty much immediately means that you're going to have to figure out a caching system for what types you've already analyzed, so that performance isn't terrible. And we all know how hard cache is.
But, on top of that, Java uses type-erased generics, so you can't actually reliable use runtime reflection to figure it out! But the compiler certainly won't tell you it's a problem, because Jackson will try to (de)serialize anything you throw at it. You don't even need any annotations for most stuff. It "just works" (TM)... until it doesn't.
So, if you use generics or inheritance or any non-trivial mapping, you have to write a custom serializer. Okay, that's no big deal.
But then you realize that JacksonXML will IGNORE time zone information on an incoming serialized date field that is encoded as an ISO8601 string and just use the current JVM system time zone. Because why the fuck not, I guess?
So, in other words, Jackson makes already-trivial things a little less verbose, it makes non-trivial things a pain, and it even makes some things that should be trivial into a pain.
I can play the same game with JPA/JDBC. In particular, it also does really stupid things with time zones and date-time types. It also can't really handle complex types for similar reasons to JacksonXML.
> For what it worth, java has a really high quality ecosystem for all these things - I would be really interested in what you think as an alternative.
My favorite languages to work with at the moment are Rust, Swift, and Kotlin. All three have way better serialization stories than Java. Rust has serde, which is like JacksonXML, except it's compile-time and your types have to implement a Serialize "trait" (interface). I truly think I'm being honest when I say that I've NEVER had a runtime serialization (type) error in my Rust projects that have used Serde. If it compiles, it works. The same is almost true of Swift and Kotlin (with kotlinx.serialization), except I do think I've encountered some runtime issues in both of those (IIRC, there was a surprising limitation with Swift Dictionary keys needing to be Strings or something). Kotlin's approach is my least favorite of the three.
When it comes to ORM/SQL stuff, I've been using a query builder in Rust that's a delight to use. Basically, when you execute a query, you use the type system to indicate the type you expect the returned rows to be. As long as the type implements a `FromRow` trait, it will "just work" or return an error value or crash (you can choose to call a "safe", error-returning, function or a "crashy" version of the function that assumes success and crashes otherwise). Rust has ad-hoc tuple types, and the library implements its `FromRow` trait for all standard types as well as all tuples up to 13 or so elements, so often you can just write something like `let (id, name) = query<(u32, String)>.execute();` and it will just work. If the `id` column is `null`, then the call will FAIL instead of doing something insane like just making up data (like JDBC returning `0` instead of `null` for nullable int columns).
The Rust query library I'm using is the perfect example for the point you made earlier about making trivial things trivial. This library does require a little bit of boilerplate to implement `FromRow` for your custom object types. It's not really worse than JPA for the simple cases, but it's WAY ahead when it comes to dealing with the non-trivial cases.
> Also, java is solid as a language. Sure, it is not the most modern one, but it is reasonably productive, has great tooling, is very performant and perhaps most importantly, it is observable in a very fine way.
Credit where it's due: Java does have phenomenal tooling and it's very fast (except when you use the frameworks that we're discussing...). I think I'd lump in the observability with the phenomenal tooling.
But, no, I wouldn't call it a solid language. It's far too primitive and bug-prone for writing robust applications. The null issue doesn't really need to explained, the ease with which we can leak resources from Closeable things, the awkwardness of the type system (e.g., being unable to implement an interface for types you don't own, being extremely tedious to define "newtypes" like a `NonEmptyString`, etc), the incompatible-yet-ubiquitous use of runtime reflection and type-erased generics, etc.
I'm sure you're a Java expert, but I'd wonder how many years you think it took you to get to the point where you feel like you aren't bitten by all of the things I've described in this comment. If the answer is more than 1, then I'll go ahead and assert that Java is not a good programming tool for the domain in which you work. I've been working with JVM languages for about 5 years now, and I'll say that I'm now familiar enough to avoid these traps most of the time, but holy shit- it should not have taken nearly that long.