It was so early in the game none of those frame works are ready. What they do under the hood when I looked wasn't a lot. I just wanted some sort of abstraction over the model apis and the ability to use the native api if the abstraction wasn't good enough. I ended up using Spring AI. Its working well for me at the moment. I dipped into the native APIS when I needed a new feature (web search).
Out of all the others Crew AI was my second choice. All of those frameworks seem parasitic. One your on the platform you are locked in. Some were open source but if you wanted to do anything useful you needed an API key and you could see that features were going to be locked behind some sort of payment.
Honestly I think you could get a lot done with one of the CLI's like Claude Code running in a VM.
Proper null safety (sometimes called void safety) is to actually systematically eliminate null values, to force in the type system a path of either handling or explicitly crashing. This is what many newer expressive multi-paradigm languages have been able to achieve (and something functional programming languages have been doing for ages), but remains out of reach for Java. Java does throw an exception on errant null value access, but allows the programmer to forget to handle it by making it a `RuntimeException`, and by the time you might try to handle it, you've lost all of the semantics of what went wrong - what value was actually missing and what a missing value truly means in the domain.
> Catching exceptions, logging them, and continuing seems to be rather common. It's not like Rust and Go, where unexpected panics in libraries are often treated as security vulnerabilities because panics are expected to take down entire services, instead of just stopping processing of the current request.
Comparing exceptions to panics is a category error. Rust for example has great facilities for bubbling up errors as values. Part of why you want to avoid panicking so much is that you don't need to do it, because it is just as easy to create structured errors that can be ignored by the consumer if needed. Java exceptions should be compared to how errors are actually handled in Rust code, it turns out they end up being fairly similar in what you get out of it.