A comparison of NULL on top of the JVM
We all encountered the dreaded “NullPointerException” at one point, especially at the start of our JVM journey. However, as you learned…
A comparison of NULL on top of the JVM
We all encountered the dreaded “NullPointerException” at one point, especially at the start of our JVM journey. However, as you learned more and more about how to structure your code, you’ve learned when to avoid it, when to check for it and when to embrace it. You might not think about it, but null does creep through a lot of the design of your code.
But is there a different way?
How does Java handle NULLs?
Before we can discuss different ways of handling nulls, we need to see how Java handles them:
- null is a placeholder that can be assigned to any object reference (but not primitives)
- there is no way of knowing if an object reference is null, apart from checking it whenever you need to know.
- calling a method on a null reference will result in a NullPointerException 😱
As we can see, nulls are, fundamentally, risky. This is because there is no safety, all of the responsibility is on the developer.
How NULL handling changed with Java8?
The good
Starting with Java8, we have a new and powerful tool at our disposal: Optional<T>!
You get a type saying to inform all of your consumers that the value might be missing. So instead of doing this
/* @return the username if found, or {null} if no user matches the ID */
public String findUsernameById(int userId) {
return userId == 1 ? "Alice" : null;
}
You can do this:
public Optional<String> findUsernameById(int userId) {
return userId == 1 ? Optional.of("Alice") : Optional.empty();
}
Now, as the caller, you can easily see that you need to check if the value is missing. No need to guess, no need to read each method doc to check if it can be null or not, no needless checks. Also, when accessing the value inside, you are 100% sure that it is not null!
findUsernameById(1).map(
username -> username.toLowerCase() //calling 'toLowerCase()' will never throw a NPE
)
The Bad
However, since all references can be nullable, we might encounter methods that behave like this:
public Optional<String> findUsernameById(int userId) {
return userId == 1 ? Optional.of("Alice") : null;
}
While in practice you usually don’t check if an Optional is null, it does mean that NPE can pop up in unexpected places.
Moreover, because the Optional parameters complicate the API for callers, a strange rule was born in the Java world: never receive Optional as a parameter!
The ugly Because the Optional type was not supported since version 1, an Optional reference can itself be null.
If a method receives an Optional parameter, the caller might pass null, so to make the code robust you will end up just checking for nulls eventually. This defeats the purpose of having Optional, so it is usually avoided. This becomes especially troublesome in legacy applications, where old code interacts with newer code.
While the introduction of Optional was a big step into removing NPEs, it was held back by backwards compatibility. We can only dream of a world where Optional was supported since version 1.
How does Scala handle NULLs?
Scala was released in 2004, so it had plenty of time to see the issues with null. So how did they solve it?
Similar to java8 (but a few years earlier), they introduced the Option[T] type. While it does work very similarly to java8’s Optional, it had one key advantage: the lack of legacy code. So, while NULL is still allowed everywhere, it is strongly discouraged. And since using Option has basically no downsides, it it ubiquitous throughout codebases and libraries.
def findUsernameById(userId: Int): Option[String] = {
if (userId == 1) Some("Alice") else None
}
Now the developer calling our method will know that the value can be missing by just looking at the return type, with no surprises!
While this works great in isolation, we do have a catch: Scala supports Java interoperability.
Let’s say we call this method from the start of the page:
/* @return the username if found, or {null} if no user matches the ID */
public String findUsernameById(int userId) {
return userId == 1 ? "Alice" : null;
}
Scala will not convert this to an Option automatically, so we need to manually check for it. However, since Scala has significantly grown in popularity since the early days, you rarely need to use Java code. And when Java code is needed, there usually is a Scala wrapper around it, to abstract away all the messy stuff.
Because of this, Scala has a pretty robust way to handle missing values. As long as you ignore the ‘null’ keyword.
How does Kotlin handle NULLs?
Kotlin is the most recent big player in the JVM language world. Instead of ignoring nulls, it embraced them. When defining a type, you can easily specify if it is nullable or not.
var username: String = "Bob" // -> username has a value and cannot be null
var username: String = null // -> Will not compile!
var username: String? = null // -> username has a null value, but might change
var username: String? = "Bob" // -> username has a value, but might become null
So our method will now look like this:
fun findUsernameById(userId: Int): String? =
if (userId == 1) "Alice" else null
And if we try to call the lowercase method on it, it will give us an error
findUsernameById(1).toLowerCase() //Will result in a compilation error
However this code will work just fine:
val maybeUsername: string? = findUsernameById(1)
if (maybeUsername != null) {
maybeUsername.toLowerCase() //at this line, maybeUsername is of type 'string', not 'string?'
}
While the above code is not Kotlin idiomatic (you would ideally use the ? operator or the .let method), it does show a little quirk of the Kotlin type system: after checking if a variable is not null, it looses it’s ‘nullable’ type property. This allows you to write cleaner code.
However, just like with Scala, an issue comes up when we look at calling Java libraries from Kotlin. We could consider that all java libraries return a nullable type, but that would make our code full of useless null checks. So, similar to Scala, it is up to the developer to check if it is nullable or not.
Considering we want to call this method, written in a Java library:
/* @return the username if found, or {null} if no user matches the ID */
public String findUsernameById(int userId) {
return userId == 1 ? "Alice" : null;
}
It could look like this:
username: string! = findUsernameById(1) //we do not know if it is nullable or not
username.toLowerCase() //this MIGHT fail with a NPE.
But we could also be safe, and write it like this:
username: string? = findUsernameById(1) //we do not know if it is nullable or not
username?.toLowerCase() //will not throw a NPE
Both versions compile, because we simply cannot know if the result is nullable or not.
How does Clojure handle NULLs?
Closure has gone in a similar way to Scala by ignoring null’s, and replacing with it’s own construct: nil. Nil behaves more like a ‘sane default’ marker, instead of a true ‘missing value’ marker. So, if you use it as a boolean, it will behave like ‘false’. If it’s in the context of a list, it will behave like an empty list. However, calling Java code will still require manually checking for null, in order to avoid NullPointerExceptions.
What can we see in the future?
As we can notice, even with the Optional class, Java still has a basic handling of missing values from the top JVM languages. But this might change, as project Valhalla nears.
One of the proposed changes is adding the ‘!’ sign to types, in order to mark them as ‘not-null’. While nothing is yet finalized, in a few years, we might be writing code like this, with no risks of NPE:
public String! toPrintableFormat(String! username) {
return username.toLowerCase();
}
For more information and explanations on the difficulties surrounding this, you can watch this video: https://www.youtube.com/watch?v=6C1RaVwpCNc
Conclusion
As we can see from the above, handling nulls is not an easy feat. But as developers work on it and new languages appear, things will only become better and better!
메타데이터
- post_id
- 3b0c94e88dca
- slug
- a-comparison-of-null-on-top-of-the-jvm-3b0c94e88dca
- url
- https://medium.com/@devnotes/a-comparison-of-null-on-top-of-the-jvm-3b0c94e88dca
- canonical_url
- https://medium.com/@devnotes/a-comparison-of-null-on-top-of-the-jvm-3b0c94e88dca
- author_url
- https://medium.com/@devnotes
- status
- ok
- fetched_at
- 2026-06-20 20:29:01