Type Nullability
In the generated TypeScript code, a type either allows undefined or it doesn’t. Vaadin decides this for each type from the Java type and its nullability annotations. A nullable parameter or return value has a type such as string | undefined. A nullable property is optional in its interface, such as name?: string.
Defaults
Without nullability annotations, the types are as follows:
-
Primitive types, such as
intandboolean, are non-nullable. -
All other types are nullable. This includes the items of collections and the values of maps.
-
Optional<T>is always nullable, whatever its annotations.
For example:
Source code
Java
public class Person {
public long id;
public String name;
public List<String> tags;
public int[] scores;
}Source code
TypeScript
interface Person {
id: number;
name?: string;
tags?: Array<string | undefined>;
scores?: Array<number>;
}Non-Nullable Packages
To make the types non-nullable by default, annotate the packages of your services and data classes with the JSpecify @NullMarked annotation, in package-info.java:
Source code
package-info.java
package-info.java@NullMarked
package com.example.application;
import org.jspecify.annotations.NullMarked;The types in the package are then non-nullable, including the items of collections and the values of maps. Mark the exceptions with @Nullable:
Source code
Java
public record Person(String name, @Nullable String nickname, List<String> tags,
List<@Nullable String> notes) {
}Source code
TypeScript
interface Person {
name: string;
nickname?: string;
tags: Array<string>;
notes: Array<string | undefined>;
}The annotation of the package that contains a service applies to the methods of the service. The annotation of the package that contains a bean or a record applies to its properties.
Vaadin reads @NullMarked only on packages. It ignores the annotation on classes and modules. The annotation of a package doesn’t apply to its sub-packages, so each package needs a package-info.java of its own.
The fields of JPA entities that are annotated with @Id or @Version stay nullable, because a new entity doesn’t have them yet.
Annotations
To change the nullability of a single type, annotate the parameter, the method, the field, or the type argument with @NonNull or @Nullable. An annotation on the type overrides the default of the package. For example, in a package without @NullMarked:
Source code
Java
public @NonNull List<@NonNull String> findTags(@NonNull String category) {
// ...
}The generated function takes a category of the type string, and returns a Promise<Array<string>>.
Vaadin recognizes these annotations:
| Effect | Annotations |
|---|---|
Non-nullable |
|
Nullable |
|
Non-nullable package |
|
Use the JSpecify annotations. Spring has deprecated its own nullability annotations in favor of them.
In Kotlin, Vaadin uses the nullable types of Kotlin, when kotlin-reflect is on the class path.
Null Values at Runtime
The types only describe the values. When a value doesn’t match its type, the following happens:
- Arguments
-
A view must pass every argument, also for a nullable parameter. To pass
null, passundefined. The server doesn’t check the arguments against their nullability. A method can getnullfor a non-nullable parameter, for example from a call that doesn’t use the generated function. To reject such calls, add a@NotNullconstraint, as described in Parameter Validation. - Return values
-
When the return type of a method is non-nullable, the server checks the return value. When the value is
null, the call fails with anEndpointResponseErrorwith the status 500. The server also checks the items of collections and arrays, the values of maps, and the properties of beans that have getters. It doesn’t check the components of records. The check of the items ignores annotations on type arguments, so anullitem fails the call, even when the item type is@Nullable.