Search

Dark theme | Light theme

October 9, 2023

Groovy Goodness: Using NullCheck Annotation To Prevent NullPointerException

In Groovy we can apply the @NullCheck annotation to a class, constructor or method. The annotation is an AST (Abstract Syntax Tree) transformation and will insert code that checks for null values in methods or constructors. If a null value is passed to an annotated method or constructor, it will throw an IllegalArgumentException. Without the annotation we could have a NullPointerException if we try to invoke a method on the value we pass as argument. The annotation has an optional property includeGenerated which by default is false. If we set it to true then the null checks are also applied to generated methods and constructors. This is very useful if we apply other AST transformations to our class that generates additional code.

In the following example we use the @NullCheck annotation for a method and at class level:

import groovy.transform.NullCheck

@NullCheck
String upper(String value) {
    "Upper:" + value.toUpperCase()
}

assert upper("groovy") == "Upper:GROOVY"

try {
    upper(null)
} catch (IllegalArgumentException e) {
    assert e.message == "value cannot be null"
}
import groovy.transform.NullCheck

// Apply null check for all constructors and methods.
@NullCheck
class Language {
    private String name

    Language(String name) {
       this.name = name;
    }

    String upper(String prefix) {
        return prefix + name.toUpperCase();
    }
}


def groovy = new Language("groovy")

assert groovy.upper("Upper:") == "Upper:GROOVY"

// Method arguments are checked.
try {
    groovy.upper(null)
} catch (IllegalArgumentException e) {
    assert e.message == "prefix cannot be null"
}

// Constructor argument is also checked.
try {
    def lang = new Language(null)
} catch (IllegalArgumentException e) {
    assert e.message == "name cannot be null"
}

In the following example we set the includeGenerated property to true to also generate null checks for generated code like the constructor generated by @TupleConstructor:

import groovy.transform.NullCheck
import groovy.transform.TupleConstructor

@NullCheck(includeGenerated = true)
@TupleConstructor
class Language {
    final String name

    String upper(String prefix) {
        return prefix + name.toUpperCase();
    }
}

// Constructor is generated by @TupleConstructor and
// @NullCheck is applied to the generated constructor.
try {
    def lang = new Language(null)
} catch (IllegalArgumentException e) {
    assert e.message == "name cannot be null"
}

Written with Groovy 4.0.13