Java Top

Get started with Spring 5 and Spring Boot 2, through the Learn Spring course:

> CHECK OUT THE COURSE

1. Overview

NullPointerExceptions are a common problem. One way we can protect our code is to add annotations such as @NotNull to our method parameters.

By using @NotNull, we indicate that we must never call our method with a null if we want to avoid an exception. However, by itself, that's not enough. Let's learn why.

2. @NotNull Annotation on a Method Parameter

First, let's create a class with a method that simply returns the length of a String.

Let's also add a @NotNull annotation to our parameter:

public class NotNullMethodParameter {
    public int validateNotNull(@NotNull String data) {
        return data.length();
    }
}

When we import NotNull, we should note that there are several implementations of a @NotNull annotation. So, we need to make sure that it's from the right package.

We'll use the javax.validation.constraints package.

Now, let's create a NotNullMethodParameter and call our method with a null parameter:

NotNullMethodParameter notNullMethodParameter = new NotNullMethodParameter();
notNullMethodParameter.doesNotValidate(null);

Despite our NotNull annotation, we get a NullPointerException:

java.lang.NullPointerException

Our annotation has no effect because there's no validator to enforce it.

3. Adding a Validator

So, let's add Hibernate Validator, the javax.validation reference implementation, to recognize our @NotNull.

Besides our validator, we also need to add a dependency for the expression language (EL) that it uses for rendering messages:

<dependency>
    <groupId>org.hibernate.validator</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>6.2.3.Final</version>
</dependency>

<dependency>
    <groupId>org.glassfish</groupId>
    <artifactId>javax.el</artifactId>
    <version>3.0.0</version>
</dependency>

When we don't include the EL dependency, we get a ValidationException to remind us:

javax.validation.ValidationException: HV000183: Unable to initialize 'javax.el.ExpressionFactory'. Check that you have the EL dependencies on the classpath, or use ParameterMessageInterpolator instead

With our dependencies in place, we can enforce our @NotNull annotation.

So, let's create a validator using the default ValidatorFactory:

ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();

And then, let's validate our argument as the first line of our annotated method:

validator.validate(myString);

Now, when we call our method with a null parameter, our @NotNull is enforced:

java.lang.IllegalArgumentException: HV000116: The object to be validated must not be null.

This is great, but having to add a call to our validator inside every annotated method results in a lot of boilerplate.

4. Spring Boot

Fortunately, there's a much simpler approach that we can use in our Spring Boot applications.

4.1. Spring Boot Validation

First, let's add the Maven dependency for validation with Spring Boot:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
    <version>2.7.1</version>
</dependency>

Our spring-boot-starter-validation dependency brings in all we need for Spring Boot and validation. This means that we can remove our earlier Hibernate and EL dependencies to keep our pom.xml clean.

Now, let's create a Spring-managed Component, ensuring we add the @Validated annotation. Let's create it with a validateNotNull method that takes a String parameter and returns the length of our data, and annotate our parameter with @NotNull:

@Component
@Validated
public class ValidatingComponent {
    public int validateNotNull(@NotNull String data) {
        return data.length();
    }
}

Finally, let's create a SpringBootTest with our ValidatingComponent autowired in. Let's also add a test with null as a method parameter:

@SpringBootTest
class ValidatingComponentTest {
    @Autowired ValidatingComponent component;

    @Test
    void givenNull_whenValidate_thenConstraintViolationException() {
        assertThrows(ConstraintViolationException.class, () -> component.validate(null));
    }
}

The ConstraintViolationException that we get has our parameter name and a ‘must not be null' message:

javax.validation.ConstraintViolationException: validate.data: must not be null

We can learn more about annotating our methods in our method constraints article.

4.2. A Cautionary Word

Although this works for our public method, let's see what happens when we add another method that isn't annotated but that calls our original annotated method:

public String callAnnotatedMethod(String data) {
    return validateNotNull(data);
}

Our NullPointerException returns. Spring doesn't enforce NotNull constraints when we invoke the annotated method from another method that resides in the same class.

4.3. Jakarta and Spring Boot 3.0

With Jakarta, the validation package names recently changed from javax.validation to jakarta.validation. Spring Boot 3.0 is based on Jakarta and so uses the newer jakarta.validation packages. This is also the case for versions of hibernate-validator from 7.0.* and onwards. This means that when we upgrade, we'll need to change the package names we use in our validation annotations.

5. Conclusion

In this article, we learned how to use the @NotNull annotation on a method parameter in a standard Java application. We also learned how to use Spring Boot's @Validated annotation to simplify our Spring Bean method parameter validation while also noting its limitations. Finally, we noted that we should expect to change our javax packages to jakarta when we update our Spring Boot projects to 3.0.

As usual, all the code samples shown in this article are available over on GitHub.

Java bottom

Get started with Spring 5 and Spring Boot 2, through the Learn Spring course:

>> CHECK OUT THE COURSE
Generic footer banner
guest
0 Comments
Inline Feedbacks
View all comments