Null-safety :: Spring Framework

2 min read Original article ↗

Spring null-safety annotations @Nullable, @NonNull, @NonNullApi, and @NonNullFields in the org.springframework.lang package were introduced in Spring Framework 5 when JSpecify did not exist, and the best option at that time was to leverage meta-annotations from JSR 305 (a dormant but widespread JSR). They are deprecated as of Spring Framework 7 in favor of JSpecify annotations, which provide significant enhancements such as properly defined specifications, a canonical dependency with no split-package issues, better tooling, better Kotlin integration, and the capability to specify nullability more precisely for more use cases.

A key difference is that Spring’s deprecated null-safety annotations, which follow JSR 305 semantics, apply to fields, parameters, and return values; while JSpecify annotations apply to type usage. This subtle difference is pretty significant in practice, since it allows developers to differentiate between the nullness of elements and the nullness of arrays/varargs as well as to define the nullness of generic types.

That means array and varargs null-safety declarations have to be updated to keep the same semantics. For example @Nullable Object[] array with Spring annotations needs to be changed to Object @Nullable [] array with JSpecify annotations. The same applies to varargs.

It is also recommended to move field and return value annotations closer to the type and on the same line, for example:

  • For fields, instead of @Nullable private String field with Spring annotations, use private @Nullable String field with JSpecify annotations.

  • For method return types, instead of @Nullable public String method() with Spring annotations, use public @Nullable String method() with JSpecify annotations.

Also, with JSpecify, you do not need to specify @NonNull when overriding a type usage annotated with @Nullable in the super method to "undo" the nullable declaration in null-marked code. Just declare it unannotated, and the null-marked defaults will apply (type usage is considered non-null unless explicitly annotated as nullable).