The other night I hopped on the train to
attend an Eclipse DemoCamp, and hearing again about the Java 8 null type annotations for advanced null analysis new in Eclipse Luna (v4.4) motivated me to finally try this out myself a little to gain some first experiences.
To get started, use an
Eclipse 4.4 (Luna), with a
Java SE v8 JDK, and a project configured like this:
You'll also need the org.eclipse.jdt.annotation-2.0.0.*.jar (NOT the v1!) on your project's classpath, e.g. like this in your pom.xml if you use Maven for dependency management (similar for others) :
<dependencies><dependency><groupId>org.eclipse.jdt</groupId><artifactId>org.eclipse.jdt.annotation</artifactId><version>2.0.0.v20140415-1436</version></dependency> </dependencies><repositories><repository><id>eclipse-staging</id><name>Eclipse.org Staging</name><url>https://repo.eclipse.org/content/repositories/eclipse-staging/</url></repository></repositories>
Now, if you had a code going something like this:
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
public class Example {
public void foo(@NonNull String s) {System.out.println("hello " + s);}
public void bar(String arg) {foo(arg);}}you'd get a warning "Null type safety (type annotations): The expression of type 'String' needs unchecked conversion to conform to '@NonNull String'" in the foo(arg) line - because you told it with an explicit @NonNull annotation that foo's String s arg shall never be null - but its not sure it is.
If you you declared bar(@Nullable String arg), you'd even get an error, not just a warning, plainly stating "Null type mismatch (type annotations): required '@NonNull String' but this expression has type '@Nullable String'". This works, to a point, through more indirect nested call chains as well, not just for trivial examples like above.
Now, instead of peppering your code with loads of @NonNull everywhere, you might opt for the sane a simpler choice that, normally, you really do NOT ever want to accept null arguments or return null - unless you have a very good reason to, and explicitly say so. This can be declared by putting a file name package-info.java into each of your Java packages (each meaning.. well really each - not only one single package-info.java at your root package, unfortunately), containing:
@NonNullByDefault
package your.package;
import org.eclipse.jdt.annotation.NonNullByDefault;
For new code, this seems like a code idea. For moving existing code to this, you could migrate in 2 steps: first enable the project configuration shown above and work through anything it flags, then (later) add (some) @NonNullByDefault, and work through impacts. If you enable the “Missing @NonNullByDefault annotation on package” preference as Warning or even Error you'll get a “A default nullness annotation has not been specified for the package ...” reminder if you create new packages and forget to copy the package-info.java.
I've found that there are limits to how smart it can be, in many cases easy to address; for example: assertThat(something, not(nullValue())); isn't understood, only Assert.assertNotNull(something); is.
If you were previously in the habit of doing
return null; in some not-quite-implemented-work-in-progress-class, you'll now have to use e.g.
throw new UnsupportedOperationException(); // TODO FIXME.This is also a great opportunity to convert some of your existing to or start writing new code with the
Null Object pattern, and learn how to use
the (new Java 8) java.util.Optional wrapper (“monad”). Basically every time you find yourself adding @Nullable somewhere (after having enabled @NonNullByDefault on the packages), ask yourself if an Optional wouldn’t be a better fit? -- BTW, if you cannot move to Java 8 in your project just yet for whatever reason, maybe this is an inspiration to use
Google Guava's Optional, which doesn't require Java 8?
One “Aha!” moments I had when doing a conversion on a toy project was when the compiler was able to telling me that e.g. com.google.common.base.Preconditions.checkNotNull() kind of parameter checks, or simple if (arg == null) throw new IllegalArgumentException() are now no longer needed - guaranteed(“Contradictory null annotations: method was inferred as '@NonNull T checkNotNull(@NonNull @Nullable T)', but only one of '@NonNull' and '@Nullable' can be effective at any location”) - nice! Cleaner more readable code less cluttered with non-null precondition checks - AND enforced by the compiler instead of at run-time - you can have your cake AND eat it, too?
It's not all an entirely smooth and easy ride though. For example, I've found funky generics can be quite a bit of fun; you quickly hit “Null type mismatch (type annotations): 'null' is not compatible to the free type variable 'T'” from this kind of code:
public class SomeClass<T> {
public T foo() {
return null;
}
}
This happens even WITHOUT (yet) using
@NonNullByDefault - generics are orthogonal to that. The technical reason for this
is explained in the doc. The solution, if null T’s are valid, is to either use:
public class SomeClass<T> {
public @Nullable T foo() {
return null;
}
}
or
public class SomeClass<@Nullable T> {
public T foo() {
return null;
}
}
Another and IMHO ultimately probably better alternative may be to just switch foo() to return an Optional<T> as explained above - if it’s new code or existing code you can still change.
Apart from generics, based on my short experience in applying this to a toy project, is when you start interfacing with existing Java code available to you only in binary and not source form. Until this takes off more, if ever, most such external code of course won't be null annotated yet. (There is also the problem of the non-standardization of these null annotations & JSR 305 non-adoption - but that's beyond this post.) So in packages where you extend or heavily interface with code from external binary libraries
@NonNullByDefault may not be feasible in the short term. But there is a hope -
Enhancement 331651 might come up with a solution for this.
Lastly, while toying with this and preparing this article, I've come across a few more points than mentioned above, and opened issues incl. #
438785, #
438469, #
438467 as bugs and/or for clarification (may just be misunderstandings on my part).
Will you convert your code to be null safe, and never ever get a NullPointerException anymore? The End of the World as we know it? ;-)