Null Safety in Java and Kotlin

In diesem Artikel betrachen wir Möglichkeiten die Null-Sicherheit in Java zu verbessern und sehen dabei einen der Gründe warum Kotlin ein gute Alternative bietet.

In this article we will look at one of the main reasons for using Kotlin over Java: Null Safety.

While null references have been called a “billion-dollar mistake”, null to represent absence of value is an important concept. The problem in an object-oriented language like Java is that all types are nullable and that there is no native way to express that a variable is never null.

In short, null is not the problem. Not knowing whether a variable can be null or not is.

From a design perspective, when you have an optional attribute, you will have specified how to deal with its absence. For example, when your customer may or may not have an email, you will have defined how to contact him in either case.

For mandatory attributes, on the other hand, we generally don’t define what should happen if the value is absent (because, well, that should never happen). You may be tempted to write, just to be safe, null-checks anyway, but it would not serve you well. Writing an if-not-null check is easy, but the problem is the (implicit) else that comes with it. You could write a log message or throw another exception in the else branch, but at this point, you’re better of just dereferencing your variable and possibly letting a NullPointerException happen. You should have some global error handling in place anyway to deal with uncaught exceptions in a reasonable way, e.g. notify and apologize to the user and inform your programmers (or yourself) that they (or you) screwed up.

The bottom line is, you have to treat optional attributes differently than mandatory ones. Without a proper way to convey (non-)nullability, programmers are required to always mentally carry the nullability information in their head. This works for small familiar projects, but breaks down when facing a larger code base. It becomes a significant effort to backtrace whether a variable could be null or not, and it may make some of us anxious to change anything at all. This is killing productivity, especially when you’re lacking unit tests as well.

Since this is an age-old problem, there exists several attempts to address it in Java:

1. Static code analysis:

The oldest approach was to introduce annotations to distinguish between non-null and nullable variables, and let external tools or the IDE check for correctness. Over the years, a lot of different annotations with the same intend have been introduced. There was an official proposal, the JSR-305, led by the creator of FindBugs, William Pugh, that was meant to replace the competing vendor annotations. Unfortunately, it was never accepted into the JDK. It was scheduled for Java 7, so by now I wouldn’t expect it to be revived at any point.

When I say a lot, I mean a lot. These are the annotations that I’m aware of (and I’m sure there are more):

Many IDEs (e.g. IntelliJ IDEA) can be configured to understand any of these annotations. However, I’m always unhappy with a solution that requires IDE or external tool support. If you want to use any of these annotations in your project, the most important thing is to pick one vendor and consistently use the annotations across your code base.

Notably, the Spring framework has recently also adopted their own nullability annotations.

2. Java 8 Optional

Java 8 introduced the Optional class as another (partial) solution. Optional is a generic class that can wrap any object and provides safe-access methods. The main drawback is that it should only be used for return values. Instead of using an Optional method parameter you should create overloaded methods with and without the optional parameter instead (that’s a good API design anyway). Local variables and class members should usually not be using Optional either. The limited use and its verbosity make the Optional a rather controversial solution. A good summary for how and when to use Optionals can be found in the Spring Data Wiki or in the [Guava Wiki].

For the record, Google’s Guava also provides an Optional type that is very similar, but not interchangeable with Java 8’s Optional (it precedes Java 8 and was likely an inspiration for the latter implementation).

3. Lombok

There’s another tool worth mentioning, Project Lombokis used to reduce boilerplate in Java by generating getters, setters, and constructors. (It’s also “a total hack” according to one of its founders.) It provides a @NotNull annotation as well, but unlike the annotations mentioned before, it is not used for static analysis. Instead it introduces a not-null check in your generated code, as can be seen in the documentation. Because of this, it is limited to accessors and constructors and has no nullable counterpart.

I’ve tried Lombok many years ago and at that time tooling support wasn’t great and I wasn’t quite happy with it. When we saw it being used in some mature projects (e.g. Spring), we reconsidered. Nowadays it can be set up with Maven or Gradle and it works seamlessly. While it’s @NotNull annotation is not enough to make your whole code base safe, not having to write and maintain boilerplate code is a very good thing.

4. Bean Validation

When you’re working with Spring, Java EE, JPA/Hibernate, or similar technologies, you have another tool at your disposal: The Bean Validation specificationand it’s (only) implementation Hibernate Validator. The main advantage of bean validation over writing checks and throwing IllegalArgumentExceptions in your accessors, is that you can validate your bean as a whole and at the right time (e.g. right before persisting an entity).

Of course, there is a @NotNull constraint as well. It’s important to understand that this annotation is not checked at compile time, but only at the time of validation. For example, when you annotate an attribute of your entity class with @NotNull, Hibernate Validator will throw a ConstraintViolationExceptionwhen you try to persist the entity with the attribute set to null. It can be null during any other time, which may be necessary, for example, while building the object.

5. Databases

On the database level, you also have to consider the nullability of your columns. Usually you would map a non-null attribute to a non-nullable column. However, there are cases where that’s not possible. For example, when using single table inheritance, so you may want to use bean validations in addition to database-level constraints.

6. Kotlin

Finally, there’s a satisfactory solution on the JVM. With Kotlin, you have a sound language design with null safety at its core. Kotlin types are not-null by default and there’s a convenient way to express nullability. The compiler checks for correctness, no matter what IDE or editor you use. If you happen to work in a pure Kotlin project, you will probably not face any surprises. There are some caveats when interacting with Java, working with ORM, or serialization. The null safety of Kotlin was the main reason for us to consider it, but Kotlin provides many more syntactic sweets and goodies. This article was supposed to be about Kotlin, but describing the mess that Java is due to its age and endless backwards compatibility, took up all my time. We will take a closer look at the specifics of Kotlin in a future article. For now, just some recommendations.

Recommendations

If you start writing a project targeting the JVM today, I would highly recommend using Kotlin. If you don’t want to learn a new language, or you’re working with an existing code base, I would still recommend Kotlin 😉 – learning it and migrating from Java is easy. If you insist not to, it’s probably a good idea to use any of the nullability annotations and Lombok to reduce boilerplate.

Regardless of your language choice, Bean Validation with Hibernate Validator can help a lot with domain-driven design and defining constraints beyond just nullability.

If you’re developing a web application you’re most likely also using JavaScript. Now what could possibly be worse than nullable by default types without nullability constraints? Not just one, but two ways to represent absence of a value – the pain of undefined and null in JavaScript! Fortunately, like Kotlin can help us here on the JVM, TypeScript can help with this in a Node or browser environment.

Stay tuned for more tales of interest!

If you have any questions, feel free to reach out to our famous and glorious back end developer Dario.

0 Kommentare

Dein Kommentar

Want to join the discussion?
Feel free to contribute!

Schreibe einen Kommentar