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).
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.
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.
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.