| > I don’t think that it is any easier in other languages either — object serialization, conversion between language’s object/json/xml/etc, and database access with object relational mapping is just complex. You can make the trivial way trivial, but you have to expose the hard ways as well and that will not be pretty either way. 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. |
Regarding Jackson and JPA the only thing I can tell about these is that their age shows, and they come from a domain and age where the (in my opinion, bad) POJO and Java Beans conventions originate. So I fully agree that things could be much better, and hopefully there will come a renaissance replacing these tools with modern java equivalents, that don’t rely on runtime magic as much, and will use the modern datetime APIs by default, etc. Serialization is especially in need of a huge revamp, hopefully records will make it much better.
Regarding ORMs, have you by chance tried JOOQ? You may prefer it over JPA.
Also, just a small note on Rust - I find it to be an excellent language, but I really don’t think it fights in the same domain as Java. Systems programming is fundamentally different. So writing a huge business application in Rust (or in C++, equivalently) is a suicide mission in my opinion — initial write time may indeed be low for an experienced Rust dev, but with the often changing client requirements that mandate quick changes touching everything, the low level details that leak into the high level view of the app will slow one down (now you also have to change the memory model because this lifetime has to be extended, etc). But I only mention that as an explanation for why Rust is not a replacement for the huge, ever-living business app domain (at least for me)
So all in all, I think that Java fights a good fight, it remains religiously backwards compatible which is painful at times, but is perhaps the biggest value there is; but it is improving with a huge pace with records, sealed classes (giving us algebraic data types), upcoming `with`ers will provide a good syntactic sugar for “modifying” immutable objects, and full-blown pattern matching is coming built on top of these. But the real deal happens under the hood, Loom will make blocking codes magically non-blocking and Valhalla tries to heal the rift between primitives and objects and will provide excellent performance boost. So the part I actually like and defend about Java is this one, not the historic baggage it comes with. But I also work on CRUD apps, and that’s not an exciting domain no matter what.