Null Safety with JSpecify

Spring Framework 7 and Spring Boot 4 annotate the whole portfolio with JSpecify. Your IDE and build can now tell you where a null can appear — before it appears at runtime.

The four annotations

AnnotationMeaning
@NullMarkedin this scope everything is non-null unless marked otherwise
@Nullablethis type may be null
@NonNullthis type is not null (rarely needed inside @NullMarked)
@NullUnmarkedopt a scope back out
<dependency>
    <groupId>org.jspecify</groupId>
    <artifactId>jspecify</artifactId>
</dependency>

Mark your packages

src/main/java/com/example/demo/package-info.java:

@NullMarked
package com.example.demo;

import org.jspecify.annotations.NullMarked;

Everything in the package is now non-null by default — one file per package, no annotations sprinkled over the code.

Then annotate the exceptions

@Service
public class BookService {

    public Book byId(long id) {  }                    // never null

    public @Nullable Book findByTitle(String title) {  }   // may be null

    public List<Book> byAuthor(@Nullable String author) {  }  // parameter may be null
}

@Nullable goes on the type, so generics work as expected:

List<@Nullable String> listOfNullableStrings;   // list is non-null, elements may be null
@Nullable List<String> nullableList;            // list may be null, elements are not

What you get

  • IDE warnings where a nullable value is dereferenced
  • Kotlin sees Spring APIs as proper platform-free types
  • static analysis — NullAway can fail the build on a violation
<!-- NullAway via Error Prone, shortened -->
<compilerArgs>
    <arg>-XepOpt:NullAway:AnnotatedPackages=com.example</arg>
</compilerArgs>
Note

These annotations are compile-time only. Nothing is checked at runtime — the payoff is in the tools.

Optional or @Nullable?

UseWhere
Optional<T>return values of query-style methods, stream chains
@Nullable Tfields, parameters, hot paths, overrides

Never use Optional as a parameter type or a field.

Practical rules

  • prefer designs where null never appears: empty collections, default objects, Optional
  • validate at the edge (@Valid), so the inside of the app can assume non-null
  • Objects.requireNonNull(x, "x") for constructor arguments that must be present
  • do not annotate @Nullable on something you then dereference unchecked

★ Exercises

  1. Add a package-info.java with @NullMarked to your service package.
  2. Mark one repository lookup @Nullable and see what the IDE says at the call site.
  3. Rewrite that method to return Optional<Book> — which reads better here?
  4. Explain the difference between List<@Nullable String> and @Nullable List<String>.
  5. Add Objects.requireNonNull to a constructor and write a test for it.