Comments (4)
Java gets a lot of flack in these parts, mostly by people who don't use Java and pull opinions out of their arse, but you'd be hard pressed watching both talks and not being impressed by the effort that's going into continuously improving the language.
I really appreciate the work done to evolve Java. They seem to be able to both fix earlier mistakes (and there were many!) and new ones from happening. While I see other languages struggling with the weight of complexity added over time, Java seems to get simpler and easier even with significant updates to the language spec.
It's hard to imagine Java without Spring, everyone uses Spring.
I’m sure they will commercialise the Spring framework and force fragmentation. Not unlike what happened to Java and Docker.
Vertx on the other hand, which it is built on and which itself builds on Netty, is excellent, and very performant for typical backend services. It now has support for virtual threads too.
Quarkus is just compile time stuff around vertx, and you write simple synchronous code. Again, DO NOT use vertx directly for building APIs. It'd be like using just expressjs and writing your api specs manually... in 2025...
Deviating from Quarkus's standard behaviour was comparatively difficult, either tracking down the correct configuration properties and what to change them to, or which api to implement and override and what it needs to do.
I prefer to develop OpenAPI specs separately from the code too, mainly because it's a joint effort with other teams. Vertx can incorporate them easily.
My advice: If you are just building a regular service that doesn't need to scale (and 90% of apps do not need Twitter scale), just stick with sync.
What does the highlighted part above mean?
I know what Netty is, but did not understand that phrase above,
edit: People also forget that Eclipse RAP still exists and although it's probably not that "modern", it works.
Have you tried one of the latest versions?
Disclaimer: I work for Vaadin.
I don't pay for Vaadin, you don't need to unless you want the extra components, and even if you do it's affordable, much cheaper than building the same system in React.
- Java's exception creates stack trace by default
- Type erasure which prevents you from doing anything "fancy" with try-catch + generic's type parameter
- You can only throw something that extends `Throwable` rather than any type `E`
- etc
But yeah sure, if Java somehow provided a standard interface & an associated operator like Rust's Try trait to handle checked exception, it would probably have a much better reputation than it has now.
At $DAYJOB, I'm currently migrating a non-performance sensitive system to a functional style, which requires defining basic functional types like Option<> and Result<>. So far I think it works pretty well and certainly improves edge case handling through explicit type declaration.
My current stable incarnation is something like this:
sealed interface Result<T,E> {
record Ok<T,E>(T value) implements Result<T,E> {}
record Error<T,E>(E error) implements Result<T,E> {}
}
Some additional thoughts:- Using only 1 type parameter in `Result<T>` & `Ok<T>` hides the possible failure state. `Result<Integer>` contain less information in type signature than `Integer throwing() throws CustomException`, leaving you fall back on catching `Exception` and handling them manually. Which kind of defeats the typechecking value of `Result<>`
- A Java developer might find it unusual to see `Ok<T,E>` and realize that the type `E` is never used in `Ok`. It's called "phantom types" in other language. While Java's generic prevents something like C++'s template specialization or due to type erasure, in this case, it helps the type system track which type is which.
- I would suggest removing the type constraint `E extends Exception`, mirroring Rust's Result<> & Haskell's Either. This restriction also prohibits using a sum type `E` for an error.
- In case you want to rethrow type `Result<?,E>` where `E extends CustomException`, maybe use a static function with an appropriate type constraint on `E`
sealed class Result<T,E> {
public static <E extends Exception> void throwIfAnyError(Result<?,E>... );
}
- I like the fact that overriding a method with reference type args + functional interface args triggers an "ambiguous type method call" error if you try to use bare null. This behavior is pretty handy to ensure anti-null behavior on `Option.orElse(T)` & `Option.orElse(Supplier<T>)`. Leaving `Option.get()` as a way to "get the nullable value" and a "code smell indicator" switch (result) {
case Ok(var value) -> println(value);
case Err(var ex) -> switch (ex) {
case HttpException httpEx -> {
// do something like a retry
}
default -> {
// if not, do something else
}
}
}Improved checked exception handling in Streams would be nice, but that's my only gripe with them. They force you to stop and think, which is a good thing.
There are some checked exceptions that should probably be unchecked, like UnsupportedEncodingException thrown by ByteArrayOutputStream::toString which takes a charset name. I'm sure there are other subclasses of IOException that could be an IllegalArgumentException instead. But those few inconveniences don't invalidate the concept of checked exceptions.
What is the problem with Checked Exceptions? Don't they just provide type checking? I would think that developers would want to know which function might or might not execute a non-local goto in middle of execution and handle that properly, since debugging that will be a mess.
(a) pollute many methods on many layers of your class hierarchy with `throws X`, potentially also polluting your external API
(b) catch, wrap and re-throw the checked exception at every call site that could throw it
(c) Use Lombok's `@SneakyThrows` to convert a checked exception into an unchecked one. Which they advise against using on production code, and which I have definitely never used on production code.
There are specific situations where checked exceptions work well - where you want to say to specific code "I can fail in this specific way, and there is something sensible you can do about it". But those are fairly rare in my experience.
Personally, I think the problem isn't the checked exception itself, but rather the exception handling mechanism & popularity of unchecked exceptions. This led to some people misusing empty catch blocks or rethrowing with unchecked exception. Java 8+ functional interfaces / lambdas also don't play nicely with checked exceptions. There's no new keyword/feature/interaction between lambdas & checked exception, only old try-catch.
To this day (Java 24 and soon LTS 25), AFAIK there's 0 plan to improve Java checked exception. And there are a lot of quirks if you try to use checked exception with modern Java features. It's just all around painful to work with, and I prefer to use Result<T,E>-like with `sealed` sum-type nowadays.
It's quite weird the direction Java, C#, Javascript communities take on this. Lots of people just go with unchecked or using nullable reference. Which is fine for a throwaway program, but quite painful in a larger project. What do you mean a subtype / child class doesn't provide a supposedly "must be implemented" method `abstractMethod()` and instead throws an unchecked exception when you call it?
You can even see this in JDK standard library, take java.util.Collection.removeAll() for example (https://docs.oracle.com/javase/8/docs/api/java/util/Collecti...). A class implementing `Collection` interface might or might not throw unchecked exception, yet you can still do this and get runtime error:
Collection<T> c = new ImmutableList<T>();
c.removeAll();
Meanwhile, modern PLs with a better type system (e.g. Haskell, Rust, Zig) don't shy away from using Result<>-like and provides special syntax like Rust's Try trait operator (?) or Haskell's bind (>>=). This is pretty much similar to Java's checked exception in a lot of ways (explicit function signature declaration, type system enforcement, short-circuiting property, non-local goto). It's kind of sad that Java completely neglected checked exceptions, leaving them like an abandoned child rather than improving them like other modern PLs.But other, even more common source of hate is that many people simply do not want to handle them. They prefer if they can kind of pretend that stuff does not exists and look like "10x fast developers". You are technically faster if you ignore all that stuff while someone else fixes your code later on in different ticket.
If all that code throwing IOException could express better the type of fault, and if the error handling could them in a more nuanced, less awkward manner, it would be much better than it is.
wut?
Sometimes I want to write a small program for a one-off task and handling Exceptions is absolutely unnecessary.
All Java needs is better syntax to handle exceptions. Nobody wants to open a whole block to handle an expected exception.
``` switch (f.get()) {
case Box(String s) when isGoodString(s) -> score(100);
case Box(String s) -> score(50);
case null -> score(0);
case throws CancellationException ce -> ...ce...
case throws ExecutionException ee -> ...ee...
case throws InterruptedException ie -> ...ie...
}
```I have a friend who maintains a gazillion JVM based apps for the Norwegian tax authorities and he's a Kotlin skeptic. He's pretty tired of maintaining and porting various small apps and microservices from arcane old and discarded Scala web frameworks etc. Given how locked-in Kotlin is to a single company, I kind of get his skepticism.
High performance Java is awesome and can be competitive with C++ and Rust for non trivial systems, thanks to much better development cycle, debugging and high quality libraries.
Most the benefits is the JVM, so Kotlin has those too, but I don’t feel Kotlin is enough of an improvement to warrant the downsides (at least for me). But both Kotlin, Scala and Clojure are great for the Java ecosystem.
Jetbrains considered using Scala before they created Kotlin. I think they were right. With Kotlin they went for a bit more conservative approach. Mainly undoing things that aren't so great in Java language and unlikely to change for language compatibility reasons, and avoiding some of the pitfalls with Scala. I think Scala saw a bit of wild growth of features and frameworks that Kotlin largely avoided. For better or worse, there has been a bit of a Scala diaspora with people flocking to things like Rust, Elixir, and indeed some to Kotlin.
Weather people like it or not, Kotlin plays very nice with existing Java code bases. By design. It was designed as a drop in replacement. It's why it caught on with Android developers before it even was released. It's a bit different from Scala in that respect, which while it worked alright with Java code bases really nudged you away from that to the point where you'd be swapping out your build tools, frameworks, etc.
At this point Kotlin is arguably a better language to use with any of the popular Java frameworks. I'm not really aware of any exceptions to this. Spring is a good example (arguably the most popular server framework for Java and the JVM). IMHO you are missing out if you are not using that with Kotlin. The Spring people certainly seem to agree. They've invested lots of time and effort to make Kotlin a first class citizen in the ecosystem. Documentation is dual Kotlin/Java, the framework ships with lots of extension functions for Kotlin and Kotlin specific features, and expanded Kotlin support is one of the headline features for the upcoming major version. And it's not like the previous version was particularly lacking on that front. They've been supporting the Kotlin ecosystem for many years now.
Nowadays I prefer Java. It's not ideal, but it aligns 100% with JVM. Kotlin was good in Java 7 times, when Java development felt frozen.
The same thing with coroutines and virtual threads. Or with Kotlin data classes and Java records. Kotlin was first, Java then implements something to solve the same issue but it's not the same way as Kotlin.
With Ktor there is the beginnings of that. It can actually compile to native; but with some limitations. There's also the web assembly compiler which would make sense for things like server less and edge computing type use cases. Both are a bit neglected from a server side perspective by Jetbrains. But the potential is there. And the whole Kotlin native path could be a lot easier and more lightweight than dealing with Graal.
The weak spot for Kotlin native outside the IOS ecosystem is compiler maturity, the library ecosystem, and lack of support for system libraries (e.g. posix and WASI for wasm).
Those are fixable problems. But it's only partially there currently and I would not recommend it for that reason. However, I think the whole effort with IOS native has moved things forward quite a bit in the last two years. Getting Kotlin to the level of Go in terms of compilers, tools, performance, and libraries on Linux and wasm would be similar in scope. But a few years of focus on that could make a lot of difference. IMHO, Kotlin could be perfect as a system programming language with some effort.
That's factually incorrect. Oracle owns the trademarks, but the language proper is governed by the community, most development is done in the open as part of OpenJDK, which is the reference implementation.
edit: grammar