It's true that our non-idiomatic Java usage denies us some of the benefits typically associated with Java programming. Automatic memory management and the old "Write Once, Run Anywhere" paradigm are difficult to maintain due to our reliance on native libraries and manual memory management.
I see two classes of reasons for choosing Java:
1. Historical: The QuestDB codebase predates Rust. According to Wikipedia, the initial Rust release was in 2015. The oldest commit in the QuestDB repo is from 2014: https://github.com/questdb/questdb/commit/95b8095427c4e2c781... What were the options back in 2014? C++? Too complicated. C? Too low-level. Pretty much anything else? Either too slow or too exotic.
2. Technical: Java, even without GC or WORA, still offers some advantage. 2a: The tooling is robust, especially when compared to C++. This starts with build systems (don't get me started on CMake!), and extends to aspects like observability. Stacktraces in Java are taken for granted. What's the state of stacktrace walking/printing in C++? I think it boils down to either Boost, C++23, or some other form of black magic. (I might be wrong here tho) 2b: It's a simpler language, especially when compared to C++ or even Rust. This makes it easier to hire people and also attracts external contributors: https://github.com/questdb/questdb/graphs/contributors 2c: The HotSpot JIT still provides us with solid peak performance without having to mess with PGO, etc. 2d: Concurrency is easier with Java's managed memory, eliminating the need for hazard pointers and the like.
Note Discord themselves has used Rust for bottlenecks with Erlang/Elixir/Beam[1].
[0]: https://questdb.io/customers/
[1]: https://discord.com/blog/using-rust-to-scale-elixir-for-11-m...
I'd say, if C++ performance is not essential, Java might actually be a good choice. It's very fast and GC issues could be worked around.
I will agree on the other points, however, but I wonder how much of a gain that is against the pain of working against the language. Hard to say.
You can still write a safe/minimal wrapper around that with just the actual API you need. (Instead of allowing everything to just peek/poke around in arbitrary off-heap memory.)
You can theoretically, but nobody has shown persuasively how to do it. The problem is when anything can be unsafe, everything can. Small safe abstractions using unsafe primitives "under the hood" in a safe language are king.
Although - I have known a trading firm that wrote their platform in Java, offloading any disk and network access to some limited C++ and JNI. In their case I believe they simply allocated large buffers that C++ pushed and pulled data from. The benefit of this strategy was their Quants could build memory-safe strategies and in a much friendlier language. For them if eliminated the risk of bad code and lost time to debugging awful C++ errors.
To my knowledge it all worked for them quite well, they made good money and any minor differences of speed in Java were negligible to them.
In some sense it reads a lot like they just needed an excuse to use Rust. Not that Rust is a bad choice for the areas they list as possible candidates for non-java code, it's just a really odd choice for Java shop, but then again, so is fighting the GC.
It is incredible fascinating work though.