Type Nullability
- Endpoint Functions
- Data Class Properties
- Collection Item Types
@NonNullApi- Kotlin Native Nullability Support
Types that are set as non_nullable are in essence required. Whereas types that are set as nullable are not required and thereby optional. By default, types are mapped and generated using the Java rules:
-
Any primitive type, such as
int, is non-nullable. -
Any reference type, such as
StringorInteger, is nullable. -
A collection accepts
null, unless the collection item type is primitive. -
A map accepts
null, unless the collection item type is primitive.
Any of these nullable types can be made non-nullable by applying a @NonNull annotation. Vaadin recommends using @org.jspecify.annotations.NonNull. The full list of supported annotations is as follows:
-
org.jspecify.annotations.NonNull(Recommended) -
org.springframework.lang.NonNull -
jakarta.annotation.Nonnull -
com.vaadin.hilla.Nonnull(Deprecated)
Endpoint Functions
For an endpoint function, nullable elements are as follows: Function Parameter Type or Function Return Type.
For Function Parameter Types, arguments cannot be omitted, even when the parameter types are nullable. To receive a null parameter value in Java, send an undefined argument in the endpoint function call.
Source code
Original Java endpoint class
import org.jspecify.annotations.NonNull;
@Endpoint
class PersonEndpoint {
// Person must have at least the first and last name.
public void setFullName(@NonNull String firstName, @NonNull String lastName, String middleName) {
// omitted code
}
// Full name must exist.
@NonNull
public String getFullName() {
// omitted code
}
// Person should have no connections with other people. If they have,
// the connection cannot be null.
public Map<String, @NonNull String> getConnections() {
// omitted code
}
}Source code
Generated TypeScript endpoint functions
export async function setName(
firstName: string,
lastName: string,
middleName: string | undefined
) {
return client.call('PersonEndpoint', 'setFullName', {firstName, lastName, middleName});
}
export async function getFullName(): Promise<string> {
return client.call('PersonEndpoint', 'getFullName');
}
export async function getConnections(): Promise<Record<string, string> | undefined> {
return client.call('PersonEndpoint', 'getConnections');
}Data Class Properties
Properties of data classes are nullable. Unlike the function parameters, all nullable properties can be omitted.
Source code
Original properties in Java data class
public class MyBean {
private long id;
@NonNull
private String value;
private String description;
private Map<String, String> map;
@NonNull
private List<String> list;
}Source code
Generated properties in TypeScript data interface
export default interface MyBean {
id: number;
value: string;
description?: string;
map?: Record<string, string | undefined>;
list: Array<string | undefined>;
}Collection Item Types
The collection item type is nullable.
Source code
Original properties in Java data class
public class MyBean {
private List<String> list;
private List<@NonNull String> nonNullableList;
private Map<String, String> map;
private Map<String, @NonNull String> nonNullableMap;
}Source code
Generated properties in TypeScript data interface
export default interface MyBean {
list?: Array<string | undefined>;
nonNullableList?: Array<string>;
map?: Record<string, string | undefined>;
nonNullableMap?: Record<string, string>;
}@NonNullApi
Along with @NonNull annotations, you could also use package-level @NonNullApi annotations. It would make all the nullable types in a package non-nullable by default. All nested types — List and Map items, etc. — are also affected.
By default, the following annotation is supported: org.springframework.lang.NonNullApi.
To make any type nullable, you must add a @Nullable annotation to it. Vaadin recommends using @org.jspecify.annotations.Nullable. The full list of supported annotations is as follows:
-
org.jspecify.annotations.Nullable(Recommended) -
jakarta.annotation.Nullable -
org.springframework.lang.Nullable -
com.vaadin.hilla.Nullable(Deprecated)
Source code
package-info.java
package-info.java@NonNullApi
package com.example.application;Source code
MyBean.java
MyBean.javapublic class MyBean {
public List<String> list;
public Map<String, Integer> map;
@Nullable
public String nullable;
}Source code
MyBean.ts
MyBean.tsexport default interface MyBean {
list: Array<string>;
map: Record<string, number>;
nullable?: string;
}Kotlin Native Nullability Support
Starting with Vaadin 24.8, Hilla supports Kotlin’s native type system regarding nullability. When using Kotlin, the language’s built-in nullability markers (?) are automatically recognized and properly mapped to TypeScript types, eliminating the need for additional annotations.
Key characteristics of this feature:
-
Language-based nullability: For calculating the type nullability of Kotlin source code, only the language’s typing is taken into account (e.g.,
StringvsString?). -
No annotation enforcement: Java nullability annotations like
@NonNullApi,@NonNull, and@Nullableare disregarded when processing Kotlin sources. -
Kotlin-specific processing: The feature exclusively affects classes from Kotlin codebases; Java entities and endpoints maintain their existing annotation-based nullability handling.
-
Mixed inheritance support: When a Kotlin endpoint extends Java-based classes (like
CrudRepositoryService), inherited methods retain Java processing while new Kotlin implementations use language-based nullability detection. -
Default activation: The underlying parser plugin activates automatically and can be disabled by excluding the
hilla-parser-jvm-plugin-nonnull-kotlindependency.
Source code
Kotlin data class with native nullability
data class Person(
val id: Long, // Non-nullable by default
val firstName: String, // Non-nullable
val lastName: String, // Non-nullable
val middleName: String?, // Nullable
val connections: Map<String, String>? // Nullable map
)Source code
Generated TypeScript interface
export default interface Person {
id: number;
firstName: string;
lastName: string;
middleName?: string;
connections?: Record<string, string>;
}Maven Configuration Requirement
For Kotlin’s native nullability to work correctly with Hilla, the Kotlin compiler must preserve Java method parameter names. This requires the -java-parameters compiler flag.
In Maven projects, you need to explicitly configure the kotlin-maven-plugin:
Source code
pom.xml
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<configuration>
<args>
<arg>-java-parameters</arg>
</args>
</configuration>
</plugin>|
Note
| Gradle projects automatically include this configuration through the Kotlin Gradle plugin, so no additional setup is required. |
Kotlin Nullability Priority
When using Kotlin, the language’s native nullability takes precedence over Java annotations. Kotlin’s type system (String vs String?) determines the nullability of generated TypeScript types, and Java annotations like @NonNull or @Nullable are ignored in Kotlin code.
Source code
Kotlin endpoint with native nullability
@Endpoint
class PersonEndpoint {
// firstName and lastName are non-nullable by default in Kotlin
fun setFullName(firstName: String, lastName: String, middleName: String?) {
// omitted code
}
// Return type is non-nullable
fun getFullName(): String {
// omitted code
}
// Map is nullable, but values are non-nullable
fun getConnections(): Map<String, String>? {
// omitted code
}
}Source code
Generated TypeScript endpoint functions
export async function setFullName(
firstName: string,
lastName: string,
middleName: string | undefined
) { /* omitted code */ }
export async function getFullName(): Promise<string> { /* omitted code */ }
export async function getConnections(): Promise<Record<string, string> | undefined> { /* omitted code */ }