Changes by Version

Changes in Vaadin 22

The following instructions apply when upgrading from a version before Vaadin 22.

Fusion Frontend npm Package

Fusion frontend code was moved from @vaadin/flow-frontend to @vaadin/fusion-frontend. Fusion module imports need to be updated correspondingly.

For example, importing Fusion EndpointError needs to be changed as follows, from:

import { EndpointError } from '@vaadin/flow-frontend';


import { EndpointError } from '@vaadin/fusion-frontend';

Read-Only Access

To follow TypeScript best practices, all interfaces Fusion generates for the endpoint data now provide read-only access to their properties. All collections are also read-only now.

To find if there is any Fusion-related mutation in your project, run the TypeScript check:

tsc --noEmit

Then, if there is any error, convert your code to use the immutable change of the data.

For example, convert the object change to object copy-with-change:

// Replace this
const object: User = {first: "John", last: "Doe"};
object.first = "Jane";

// with
const object: User = {first: "John", last: "Doe"};
const changed: User = {...object, last: "Jane"};

Collections should be updated as well:

// Replace this
const arr = [1, 2, 3];

// with
const arr = [1, 2, 3]
const changed = [...arr, 4, 5, 6];

If you still need the mutation instead of copying, for example in case you send the ReadonlyArray to the third-party function that receives only mutable Array, you can explicitly cast ReadonlyArray to Array:

import thirdPartyFunction from 'third-party-function';

const arr: ReadonlyArray<number> = [1, 2, 3];

thirdPartyFunction(arr as Array<number>);

Changes in Vaadin 21

The following instructions apply when upgrading from a version before Vaadin 21.

Fusion Package Renaming

In order to give Fusion a better identity, we renamed the following Fusion packages:

  • com.vaadin.flow.server.connect to com.vaadin.fusion

  • com.vaadin.flow.server.frontend.fusion to com.vaadin.fusion.frontend

  • com.vaadin.flow.server.startup.fusion to com.vaadin.fusion.startup

TypeScript Code Generation Nullability Change

Previously, all the Java types are generated as required in TypeScript. Now it changes so that for any type that is nullable in Java, it is optional in TypeScript, that is, the value could be undefined. See the GitHub issue.

To upgrade, you can either: Update the client-side code to handle undefinable value.

// The address property is never undefined in Vaadin 20
// Use the question mark to deal with undefinable properties in Vaadin 21

// The list returned from the endpoint is never undefined in Vaadin 20
const items = await endpoint.list();
// Use the double question mark to give a default value in Vaadin 21
const item = (await endpoint.list()) ?? [];

Or use @Nonnull annotation on the server-side to keep the same code generation behavior as before.

public class Person {
    private Address address;
@Nonnull annotation
You can use any @Nonnull annotations available or even your own one. Vaadin uses case-insensitive string comparison for checking the annotation.

See Type Nullability for more details.

The Vaadin Web Components No Longer Support <template>

The Vaadin Web Components no longer support <template> to render content. Please, use renderer functions instead. Alternatively, you can use the @vaadin/vaadin-template-renderer package which is created to maintain backward compatibility.


Install vaadin-template-renderer as follows:

npm i @vaadin/vaadin-template-renderer --save

Import vaadin-template-renderer before any other components:

import '@vaadin/vaadin-template-renderer';

Deprecation Warning

By default, vaadin-template-renderer shows a deprecation warning when <template> is used with a component.

To suppress the warning, add the suppress-template-warning attribute to the component:

<vaadin-combo-box suppress-template-warning>

Lit Templates Use Lit 2

Previously, Lit templates were based on LitElement 2.x and lit-html 1.x. New Lit 2.0 (which includes LitElement 3.x and lit-html 2.x) is used for Lit templates in the latest Vaadin version. Some changes are required to be able to use your existing template with Lit 2.0. Please refer to the Lit Upgrade Guide to find all the necessary changes. Most of the necessary changes are with imports.

Use the lit package. For example, change imports of html and LitElement from:

import { html, LitElement } from 'lit-element';


import { html, LitElement } from 'lit';

Update decorator imports. Decorators have been moved to a separate module. For example, change customElement and property decorator imports from:

import { customElement, property } from 'lit-element';


import { customElement, property } from 'lit/decorators.js';

Update directive imports from:

import { repeat } from 'lit-html/directives/repeat.js';


import { repeat } from 'lit/directives/repeat.js';

Some Lit APIs have been renamed. The most noticeable change is that the @internalProperty decorator has been renamed to @state.

Changes in Vaadin 20

The following instructions apply when upgrading from a version before Vaadin 20.

Endpoints Access is Denied by Default

Previously, endpoints (methods in classes with @Endpoint annotation) without security annotations (one of @DenyAll, @PermitAll, @RolesAllowed, @AnonymousAllowed) were accessible by all authenticated users. To avoid inadvertent exposure of methods as endpoints, @DenyAll is now the default. This means that you need to add explicit security annotations to the endpoints that you want to make accessible (either at the class level or the method level).

Default Spring Security Configuration

A default class for Spring Security configuration is available as VaadinWebSecurityConfigurerAdapter. Extend this class instead of the default WebSecurityConfigurerAdapter to automatically get a configuration that allows Vaadin specific requests to pass through security while requiring authorization for all other requests:

public class SecurityConfiguration extends VaadinWebSecurityConfigurerAdapter {

    protected void configure(HttpSecurity http) throws Exception {
        // app's own HttpSecurity configuration as needed ...

    protected void configure(WebSecurity web) throws Exception {
        // app's own WebSecurity configuration as needed...

VaadinWebSecurityConfigurerAdapter configures authentication for all routes by default. Modify this behavior with your own followup configuration as needed. It also bypasses framework internal and static resources (/VAADIN/**, sw.js …​). Previously, these had to be explicitly matched and ignored in the app.

VaadinWebSecurityConfigurerAdapter also configures Spring CSRF token for login and Fusion endpoint requests, so you no longer need to ignore Spring CSRF protection for them like before with http.csrf().ignoringAntMatchers("/login", "/connect/**");

The client-side login() method now needs the Spring CSRF token returned from a login success handler VaadinSavedRequestAwareAuthenticationSuccessHandler. You can update your login view configuration with the setLoginView() helper, which sets up the login success handler automatically.

  protected void configure(HttpSecurity http) throws Exception {
    setLoginView(http, "/login");

Changes in Vaadin 19

These instructions apply when upgrading from a version before Vaadin 19.

TypeScript Configuration Now Includes Frontend Path Alias

The default tsconfig.json content was changed to introduce the Frontend import path alias.

The content of the tsconfig.json file is not updated automatically if it existed before the migration.

If you do not have any own modifications in this file, you can delete the old tsconfig.json file. vaadin-maven-plugin creates the file automatically with the new defaults next time when building the project or running the development mode.

You can also manually enable Frontend import path prefix in the existing tsconfig.json file by adding the following compiler options:

  "compilerOptions": {
    "baseUrl": "frontend",
    "paths": {
      "Frontend/*": [

Generated @Id Field Is Now of Optional Type in TypeScript

A field with @Id annotation in Java is now of optional type in the generated TypeScript code. Given an entity with an id field:

public class Entity {
    private int id;

Now in the TypeScript files, instead of using endpoint.getEntity(, you might need to change to endpoint.getEntity(!) (if you know that the id is always set when this is called) or add a type guard to explicitly check that id is not undefined.

You need to ignore one more static file, /sw-runtime-resources-precache.js, if you use HttpSecurity.authorizeRequests() to do role-based authorization in your security configuration as follows:

protected void configure(HttpSecurity http) throws Exception {

In such case, you need to add one more file /sw-runtime-resources-precache.js to the static resource list that Spring Security bypasses:

public void configure(WebSecurity web) {
            // client-side JS code
            // web application manifest

Ignore the Service Worker Initiated Requests

Another potential Spring Security related breaking change is about using HttpSecurity.requestCache() to redirect the user to the intended page after login.

An example of using HttpSecurity.requestCache():

protected void configure(HttpSecurity http) throws Exception {

    // Register our CustomRequestCache, that saves unauthorized access attempts, so
    // the user is redirected after login.
    .requestCache().requestCache(new CustomRequestCache())

    // Restrict access to our application.

    // Allow all flow internal requests.

Now you need to ignore the service worker initiated requests, otherwise the access attempts are overridden by the service worker requests and Spring cannot redirect you to the intended page. This can be done by inspecting the Referer header of the request.

The SecurityUtils::isFrameworkInternalRequest() can be updated as follows to also include the service worker initiated requests:

static boolean isFrameworkInternalRequest(HttpServletRequest request) {
    final String parameterValue = request
    // Use Referer in header to check if it is a sevice worker
    // initiated request
    String referer = request.getHeader("Referer");
    boolean isServiceWorkInitiated = (referer != null
                && referer.endsWith("sw.js"));
    return isServiceWorkInitiated
            || parameterValue != null
            && Stream.of(RequestType.values())
                .anyMatch(r -> r.getIdentifier().equals(parameterValue));

Changes in Vaadin 15

These instructions apply when upgrading from a version before Vaadin 15.

Update Main Layout/View Annotations

Several annotations typically placed on the MainLayout / MainView class must be moved to a class that implements the AppShellConfigurator interface, for example:

@PWA(name = "My Vaadin App", shortName = "my-app")
public class AppShell implements AppShellConfigurator {


Breaking API Changes

A set of API breaking changes and their replacements are listed below:

  • Property synchronization methods in Element are replaced with similar API in DomListenerRegistration: getSynchronizedPropertyEvents, getSynchronizedProperties, removeSynchronizedPropertyEvent, removeSynchronizedProperty, addSynchronizedPropertyEvent, addSynchronizedProperty, synchronizeProperty.

  • JavaScript execution APIs executeJavaScript and callFunction in Element and Page are replaced with similarly named methods that give access to the return value executeJs and callJsFunction:

  • Miscellaneous Element methods: Element(String, boolean), addEventListener(String, DomEventListener, String…​)

  • Device and platform detection methods WebBrowser#isIOS(), WebBrowser#isIPad(), BrowserDetails#isSafariOrIOS(), BrowserDetails#isIOS(), BrowserDetails#isIPad() are replaced with method in ExtendedClientDetails: isIPad(), isIOS()

  • Methods JsModule#loadMode() and Page#addJsModule(String, LoadMode) for setting the load mode of JsModule are removed since it does not function with JavaScript modules.

  • The construction methods BeforeEvent(NavigationEvent, Class<?>) and BeforeEvent(Router, NavigationTrigger, Location, Class<?>, UI) in BeforeEvent are replaced with BeforeEvent(NavigationEvent, Class, List) and BeforeEvent(Router, NavigationTrigger, Location, Class, UI, List)

  • Methods getUrl(), getUrlBase() and getRoutes() in Router are replaced with methods getUrl(), getUrlBase() and getAvailableRoutes() in RouterConfiguration. The resolve() method in Router is replaced with the resolve() method in RouteUtil. The getRoutesByParent() method in Router is removed and has no replacement.

  • ServletHelper is replaced with HandlerHelper

  • ExecutionCanceler is replaced with PendingJavaScriptResult

  • The getBodyAttributes method in AbstractTheme, Lumo and Material is replaced with getHtmlAttributes

  • The removeDataGenerator method in HasDataGenerators and CompositeDataGenerator is removed in favor of using the registration returned from addDataGenerator(DataGenerator)

  • The methods preventsDefault and stopsPropagation in ShortcutRegistration are replaced with isBrowserDefaultAllowed ` and `isEventPropagationAllowed

  • The safeEscapeForHtml method in VaadinServlet is removed in favor of using org.jsoup.nodes.Entities#escape(String)

  • The static method getInstance in ApplicationRouteRegistry is removed in favor of the instance method.

  • The protected instance method getApplicationUrl from VaadinServlet is removed

Bootstrapping Changes

For applications upgraded from earlier versions of Vaadin, client-side bootstrapping requires replacing the usages of the V10-14 BootstrapHandler APIs with their IndexHtmlRequestHandler API counterparts as described in IndexHtmlRequestListener interface section.

The reason for this API change is that with client-side bootstrapping the initial page HTML generation is separated from loading the Flow client and creating a server-side UI instance.

  • In Vaadin 10 to 14 these two steps are combined and the index.html page includes the code and configuration needed to start the Flow client engine and link the browser page to the server-side UI instance.

  • In Vaadin 15+ with client-side bootstrapping the index.html page includes only the basic HTML markup and links to the TypeScript UI code. When adding routes in TypeScript, the UI is not guaranteed to be created, thus is optional. It will be only available after the user navigates to a server-side route.

It is also possible to continue using the bootstrapping mode in V10-14 with the useDeprecatedV14Bootstrapping flag. See how the use the flag in Configuration Properties.