Docs

Documentation versions (currently viewingVaadin 24)

Type Nullability

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 String or Integer, 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
@NonNullApi
package com.example.application;
Source code
MyBean.java
public class MyBean {
    public List<String> list;
    public Map<String, Integer> map;
    @Nullable
    public String nullable;
}
Source code
MyBean.ts
export 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., String vs String?).

  • No annotation enforcement: Java nullability annotations like @NonNullApi, @NonNull, and @Nullable are 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-kotlin dependency.

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 */ }