Documentation

Documentation versions (currently viewingVaadin 23)

You are viewing documentation for Vaadin 23. View latest documentation

Upgrade Guide Generator

Instructions for upgrading to the latest Vaadin version. To run applications or components developed with Vaadin 7 or 8 inside an application written using the latest version, see Multiplatform Runtime.

Open in a
new tab
import '@vaadin/button';
import '@vaadin/select';
import '@vaadin/checkbox';
import '@vaadin/checkbox-group';
import '@vaadin/details';
import '@vaadin/notification';
import { html, LitElement } from 'lit';
import { customElement, state } from 'lit/decorators.js';
import type { Checkbox } from '@vaadin/checkbox';
import type { CheckboxGroupValueChangedEvent } from '@vaadin/checkbox-group';
import { Notification } from '@vaadin/notification';
import type { SelectValueChangedEvent } from '@vaadin/select';
import { selectRenderer } from '@vaadin/select/lit.js';
import { applyTheme } from 'Frontend/generated/theme';

const VAADIN_VERSIONS: Record<string, string> = {
  14: '14.12.4',
  15: '15.0.6',
  16: '16.0.5',
  17: '17.0.11',
  18: '18.0.7',
  19: '19.0.9',
  20: '20.0.8',
  21: '21.0.9',
  22: '22.0.28',
  23: '23.5.14',
};

const SIMPLE_VERSIONS = Object.keys(VAADIN_VERSIONS);

const DEFAULT_FROM = '14';
const DEFAULT_TO = '23';

const HARDCODED_VERSIONS_CLASS = 'vaadin-to-version-full';

// Apply the theme, so that overlay elements styles and custom property overrides work as expected
applyTheme(document);

@customElement('upgrade-tool')
export default class UpgradeTool extends LitElement {
  @state()
  private fromVersion = '';

  @state()
  private toVersion = '';

  @state()
  private frameworkValue: string[] = [];

  @state()
  private extraSettingsValue: string[] = [];

  private isFlow = true;
  private isFusion = false;
  private isSpring = true;
  private isTypeScript = false;
  private isCustomStyling = false;
  private isInstructionsDisplayed = false;
  private isFirstUpdated = false;

  protected override render() {
    return html`
      <div>
        <h2>Select your Vaadin versions:</h2>
        <div style="margin-bottom: 10px">${this.createSelectComponents()}</div>
        <vaadin-details>
          <div slot="summary">Earlier Versions</div>
          <ul style="margin-top: 0">
            <li>
              <a href="https://vaadin.com/docs/v14/flow/upgrading/v10-13">
                Upgrading from Vaadin 10–13 to Vaadin 14
              </a>
            </li>
            <li>
              <a href="https://vaadin.com/docs/v14/flow/upgrading/v8">
                Upgrading from Vaadin 8 to Vaadin 14
              </a>
            </li>
          </ul>
        </vaadin-details>
        <div style="margin-bottom: 10px">
          <vaadin-checkbox-group
            label="Framework"
            id="framework-checkbox-group"
            .value="${this.frameworkValue}"
            @value-changed=${this.frameworkChanged}
            theme="horizontal"
          >
            <vaadin-checkbox value="flow" label="Flow" id="flow-checkbox"></vaadin-checkbox>
            <vaadin-checkbox
              value="fusion"
              label="Fusion/Hilla"
              id="fusion-checkbox"
            ></vaadin-checkbox>
          </vaadin-checkbox-group>
        </div>
        <div>
          <vaadin-checkbox-group
            label="I use"
            id="extra-settings-checkbox-group"
            .value="${this.extraSettingsValue}"
            @value-changed=${this.extraSettingsChanged}
            theme="horizontal"
          >
            <vaadin-checkbox
              value="spring"
              label="Spring Boot"
              id="spring-checkbox"
              ?disabled=${this.isFusion || (!this.isFusion && !this.isFlow)}
            ></vaadin-checkbox>
            <vaadin-checkbox
              value="typescript"
              label="TypeScript-based views"
              id="typescript-checkbox"
              ?disabled=${this.isFusion || (!this.isFusion && !this.isFlow)}
            ></vaadin-checkbox>
            <vaadin-checkbox
              value="styling"
              label="Changes to custom styling of components"
              id="styling-checkbox"
            ></vaadin-checkbox>
          </vaadin-checkbox-group>
        </div>
        <vaadin-button
          theme="primary"
          style="width: fit-content; margin-top: 30px; margin-bottom: 30px"
          @click=${this.showUpdateInstructions}
        >
          Show update instructions!
        </vaadin-button>
      </div>
    `;
  }

  createRenderRoot() {
    return this;
  }

  private showUpdateInstructions() {
    this.hideOldInstructions();
    this.replaceHardCodedVersions();

    if (this.isFlow || this.isFusion) {
      this.showElementsWithClassname('all');
    } else {
      Notification.show('Select a framework');
      return;
    }

    const versionSectionElements: HTMLElement[] = this.getVersionSectionElements(
      this.fromVersion,
      this.toVersion
    );
    versionSectionElements.forEach((e) => this.setElementVisible(e, true));

    if (this.isSpring) {
      this.showElementsWithClassname('spring');
    }

    if (this.isTypeScript) {
      this.showElementsWithClassname('ts');
    }

    if (this.isCustomStyling) {
      this.showElementsWithClassname('styling');
    }

    if (this.isFlow) {
      this.showElementsWithClassname('flow');
    }

    if (this.isFusion) {
      this.showElementsWithClassname('fusion');
    }

    // A one time hack for 14 -> 23 upgrade
    this.perform14To23CleanupIfNecessary();

    this.isInstructionsDisplayed = true;
    this.updateUrlParameters();
  }

  private hideMatchingElements(types: string[]) {
    const selector = types.map((type) => `[class*="${type}"]`).join(', ');

    document.querySelectorAll<HTMLElement>(selector).forEach((elem) => {
      this.setElementVisible(elem, false);
    });
  }

  private perform14To23CleanupIfNecessary() {
    if (!this.is14To23Upgrade()) {
      return;
    }

    this.hideMatchingElements(['all', 'spring']);
  }

  private is14To23Upgrade() {
    return this.fromVersion === '14' && this.toVersion === '23';
  }

  private showElementsWithClassname(classname: string) {
    this.getElementsByClassname(classname).forEach((e) => this.setElementVisible(e, true));
  }

  private getVersionSectionElements(fromVersion: string, toVersion: string) {
    const elementsToShow: HTMLElement[] = [];
    const classname = `v${fromVersion}-${toVersion}`;
    const elements = this.getElementsByClassname(classname);

    if (elements.length > 0) {
      elementsToShow.push(...elements);
    } else {
      const idx = SIMPLE_VERSIONS.indexOf(fromVersion);
      const nextVersion = SIMPLE_VERSIONS[idx + 1];
      elementsToShow.push(...this.getVersionSectionElements(fromVersion, nextVersion));
      elementsToShow.push(...this.getVersionSectionElements(nextVersion, toVersion));
    }

    return elementsToShow;
  }

  private setElementVisible(element: HTMLElement, isVisible: boolean): void {
    if (isVisible) {
      element.style.setProperty('display', 'block');
    } else {
      element.style.setProperty('display', 'none');
    }
  }

  private getElementsByClassname(classname: string) {
    return [...document.querySelectorAll<HTMLElement>(`.${classname}`)];
  }

  private hideOldInstructions() {
    this.hideMatchingElements([
      'all',
      'flow',
      'fusion',
      'spring',
      'ts',
      'styling',
      'v1',
      'v2',
      'v3',
      'v4',
    ]);

    this.isInstructionsDisplayed = false;
  }

  private replaceHardCodedVersions() {
    this.getElementsByClassname(HARDCODED_VERSIONS_CLASS).forEach((e) => {
      e.textContent = VAADIN_VERSIONS[this.toVersion];
    });
  }

  private createSelectComponents() {
    const fromVersionList = SIMPLE_VERSIONS.slice(0, -1);
    const toVersionList = SIMPLE_VERSIONS.slice(1);
    return html`
      <vaadin-select
        label="From"
        id="from-select"
        style="width: fit-content; margin-right: 10px"
        value=${this.fromVersion}
        @value-changed=${this.fromVersionChanged}
        ${selectRenderer(
          () => html`
            <vaadin-list-box>
              ${fromVersionList.map((v) => html`<vaadin-item value="${v}">${v}</vaadin-item>`)}
            </vaadin-list-box>
          `,
          []
        )}
      ></vaadin-select>
      <vaadin-select
        label="To"
        id="to-select"
        value=${this.toVersion}
        @value-changed=${this.toVersionChanged}
        ${selectRenderer(
          () => html`
            <vaadin-list-box>
              ${toVersionList.map(
                (v) =>
                  html`
                    <vaadin-item value="${v}" ?hidden=${v <= this.fromVersion}>${v}</vaadin-item>
                  `
              )}
            </vaadin-list-box>
          `,
          []
        )}
      ></vaadin-select>
    `;
  }

  private fromVersionChanged(e: SelectValueChangedEvent) {
    if (!this.isFirstUpdated) {
      return;
    }

    const val = e.detail.value;
    if (parseInt(val) >= parseInt(this.toVersion)) {
      const idx = SIMPLE_VERSIONS.indexOf(val);
      this.toVersion = SIMPLE_VERSIONS[idx + 1];
    }
    this.fromVersion = val;
    this.updateUrlParameters();
  }

  private toVersionChanged(e: SelectValueChangedEvent) {
    if (!this.isFirstUpdated) {
      return;
    }

    this.toVersion = e.detail.value;
    this.updateUrlParameters();
  }

  private frameworkChanged(e: CheckboxGroupValueChangedEvent) {
    if (!this.isFirstUpdated) {
      return;
    }

    const val = e.detail.value;
    this.frameworkValue = val;

    this.isFlow = val.includes('flow');

    if (this.frameworkValue.includes('fusion')) {
      this.isFusion = true;
      this.isSpring = true;
      this.isTypeScript = true;

      ['spring', 'typescript'].forEach((v) => {
        if (!this.extraSettingsValue.includes(v)) {
          this.extraSettingsValue.push(v);
        }
        const checkbox = document.getElementById(`${v}-checkbox`) as Checkbox;
        checkbox.checked = true;
      });
    } else {
      this.isFusion = false;
    }

    this.updateUrlParameters();
  }

  private extraSettingsChanged(e: CheckboxGroupValueChangedEvent) {
    if (!this.isFirstUpdated) {
      return;
    }

    const val = e.detail.value;
    this.extraSettingsValue = val;

    this.isSpring = val.includes('spring') || this.isFusion;
    this.isTypeScript = val.includes('typescript') || this.isFusion;
    this.isCustomStyling = val.includes('styling');

    this.updateUrlParameters();
  }

  private updateUrlParameters() {
    const urlParams = new URLSearchParams(window.location.search);
    urlParams.set('from', this.fromVersion);
    urlParams.set('to', this.toVersion);
    urlParams.set('isFlow', String(this.isFlow));
    urlParams.set('isFusion', String(this.isFusion));
    urlParams.set('isSpring', String(this.isSpring));
    urlParams.set('isTypeScript', String(this.isTypeScript));
    urlParams.set('isCustomStyling', String(this.isCustomStyling));
    urlParams.set('isInstructionsDisplayed', String(this.isInstructionsDisplayed));

    const pathname = location.pathname.replace(/\/$/, '');
    window.history.replaceState({}, '', `${pathname}/?${urlParams}`);
  }

  private getParamVal(urlParams: URLSearchParams, param: string) {
    const isParam = urlParams.get(param);

    if (isParam) {
      const property = Boolean(JSON.parse(isParam));
      return property;
    }

    return null;
  }

  private initializeProperties() {
    if (this.isFlow) {
      this.frameworkValue.push('flow');
    }
    if (this.isFusion) {
      this.frameworkValue.push('fusion');
    }

    if (this.isSpring) {
      this.extraSettingsValue.push('spring');
    }
    if (this.isTypeScript) {
      this.extraSettingsValue.push('typescript');
    }
    if (this.isCustomStyling) {
      this.extraSettingsValue.push('styling');
    }

    this.frameworkValue = [...this.frameworkValue];
    this.extraSettingsValue = [...this.extraSettingsValue];
  }

  connectedCallback() {
    super.connectedCallback();
    this.removeLinksFromHeaders();
  }

  private removeLinksFromHeaders() {
    // Remove links from headers as clicking them resets the query parameters.
    const anchors = document.getElementsByTagName('a');
    Array.from(anchors).forEach((a) => {
      const parent = a.parentNode;
      if (parent && parent.nodeName === 'H2') {
        a.remove();
      }
    });
  }

  protected override firstUpdated() {
    const urlParams = new URLSearchParams(window.location.search);
    const fromParam = urlParams.get('from');
    const toParam = urlParams.get('to');

    this.fromVersion = fromParam ?? DEFAULT_FROM;
    this.toVersion = toParam ?? DEFAULT_TO;

    const isFlowParam = this.getParamVal(urlParams, 'isFlow');
    const isFusionParam = this.getParamVal(urlParams, 'isFusion');
    const isSpringParam = this.getParamVal(urlParams, 'isSpring');
    const isTypeScriptParam = this.getParamVal(urlParams, 'isTypeScript');
    const isCustomStylingParam = this.getParamVal(urlParams, 'isCustomStyling');
    const isInstructionsDisplayedParam = this.getParamVal(urlParams, 'isInstructionsDisplayed');

    if (isFlowParam != null) {
      this.isFlow = isFlowParam;
    }
    if (isFusionParam != null) {
      this.isFusion = isFusionParam;
    }
    if (isSpringParam != null) {
      this.isSpring = isSpringParam;
    }
    if (isTypeScriptParam != null) {
      this.isTypeScript = isTypeScriptParam;
    }
    if (isCustomStylingParam != null) {
      this.isCustomStyling = isCustomStylingParam;
    }

    this.initializeProperties();

    this.isFirstUpdated = true;
    this.updateUrlParameters();

    if (isInstructionsDisplayedParam) {
      this.isInstructionsDisplayed = true;
      this.showUpdateInstructions();
    }
  }
}

Before You Start

  • Delete the node_modules folder and either lock file: package-lock.json (with npm) or pnpm-lock.yaml (with pnpm).

  • Edit the pom.xml file and change the Vaadin version to new version.

  • Update Spring Version.

Vaadin is compatible with Spring 5.3.18 or later, and Spring Boot 2.6.6 or later. If your application uses an older version of Spring, update it to a compatible version:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.6.6</version>
</parent>

Upgrading the Spring version might require some non-Vaadin-related changes to your application. See Spring Boot release notes for the list of required changes.

Vaadin 14 to 23 Upgrade Guide

The 14-to-23 upgrading instructions are available in the following separate chapter.

Upgrade Steps | 14 → 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 {

}

Replace Obsolete APIs

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 doesn’t 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. If you have Hilla views, the UI isn’t guaranteed to be created, thus is optional. It’s only available after the user navigates to a server-side route.

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

Upgrade Steps | 15 → 16

Vaadin 16 doesn’t involve any API breaking changes. See the release notes at https://github.com/vaadin/platform/releases/tag/16.0.0.

Upgrade Steps | 16 → 17

Move Annotations to AppShellConfigurator

The only place where configuring the application with certain annotations is supported is in a class that implements AppShellConfigurator.

This applies for v15+ bootstrap mode (the default), but not for v14 legacy bootstrapping. Rather than showing nondeterministic behavior and logging an error, the build fails when any of the following annotations occur outside an AppShellConfigurator class:

Meta.class, PWA.class, Inline.class, Viewport.class, BodySize.class, Push.class

Replace Obsolete APIs

A New API for Binding Items to Components:

  • HasDataProvider and HasItems are now replaced by the new HasListDataView, HasLazyDataView and HasDataView interfaces in Grid, Select and CheckBoxGroup. It’s also gradually replaced in other components which have an items binding.

  • setDataProvider() is now deprecated and it’s recommended to use overloaded setItems() methods.

  • setItems() methods now have a return type instead of void.

  • the HasItemsAndComponents interface has been replaced by HasItemComponents to support the Data View API in in-memory binding components.

  • HasHierarchicalDataProvider no longer has setItems() overloads for Collection, Stream and Array.

URL Parameters Template Feature

  • BeforeEvent has a bunch of new methods for forwarding, rerouting and getting the parameters. Some methods are now deprecated or removed.

  • The RouteRegistry and SessionRouteRegistry interfaces are now supplemented with new methods and deprecate getters for route layouts.

  • com.vaadin.flow.server.startup.RouteTarget has been completely removed. This class was internal and shouldn’t have been used. If you have been using it, you can create an issue describing what you needed it for.

Upgrade Steps | 17 → 18

  • Using LitTemplate is recommended over the deprecated PolymerTemplate to do layouts with HTML and UI logic in Java.

It’s recommended to use TypeScript for the template and this has been updated to the examples in the documentation.

  • Starting from Vaadin 18, the initial attribute values in the template are reflected to the server side state when @Id mapping components. This applies to PolymerTemplate, too. More information on the template support is available in this blog post.

Flow Breaking Changes

  • AppShellRegistry method getTitle() is removed It was broken and couldn’t work. Instead, if needed, use getUI().getUIInternals().getAppShellTitle().

  • Having the @Theme annotation on Flow views or router layouts is no longer allowed. The annotation should be on AppShellConfigurator instead. This is now consistent with the @PWA annotation. It’s also cleaner, since you can only have one @Theme per application.

  • AbstractListDataView now requires an extra constructor argument - a callback, which is invoked each time when the component’s filter and/or sorting changes through the data view API.

Fusion Breaking Changes

  • The value property of BinderNode now has optionally undefined type for non-initialized optional fields.

Upgrade Steps | 18 → 19

Fusion Breaking Changes

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 {
    @Id
    private int id;
}

Now in the TypeScript files, instead of using endpoint.getEntity(entity.id), you might need to change to endpoint.getEntity(entity.id!) (if you know that the id is always set when this is called) or add a type guard to explicitly check that id isn’t 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:

@Override
protected void configure(HttpSecurity http) throws Exception {
    ...
    http.authorizeRequests().anyRequest().hasAnyAuthority(Role.getAllRoles());
    ...
}

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

@Override
public void configure(WebSecurity web) {
    web.ignoring().antMatchers(
            // client-side JS code
            "/VAADIN/**",
            ...
            // web application manifest
            "/manifest.webmanifest",
            "/sw.js",
            "/offline-page.html",
            "/sw-runtime-resources-precache.js",
            ...
    );
}

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():

@Override
protected void configure(HttpSecurity http) throws Exception {
    ...
    http

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

    // Restrict access to our application.
    .and().authorizeRequests()

    // Allow all flow internal requests.
    .requestMatchers(SecurityUtils::isFrameworkInternalRequest).permitAll()
    ...
}

Now you need to ignore the service worker initiated requests, otherwise the access attempts are overridden by the service worker requests and Spring can’t 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
        .getParameter(ApplicationConstants.REQUEST_TYPE_PARAMETER);
    // Use Referer in header to check if it's a service 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));
}

Upgrade Steps | 19 → 20

Fusion Breaking Changes

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:

@EnableWebSecurity
@Configuration
public class SecurityConfiguration extends VaadinWebSecurityConfigurerAdapter {

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

    @Override
    protected void configure(WebSecurity web) throws Exception {
        super.configure(web);
        // 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 Cross-Site Request Forgery (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.

package com.vaadin.demo.fusion.security.authentication;

import com.vaadin.flow.spring.security.VaadinWebSecurityConfigurerAdapter;

import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;

/**
 * An example code for demoing the Spring Security configuration, shouldn't affect
 * the doc application itself.
 */
public class SecurityConfigDemo extends VaadinWebSecurityConfigurerAdapter {

  @Override
  protected void configure(HttpSecurity http) throws Exception {
    super.configure(http);
    setLoginView(http, "/login");
  }

  @Override
  protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    // Configure users and roles in memory
    auth.inMemoryAuthentication().withUser("user").password("{noop}password").roles("USER");
  }

  @Override
  public void configure(WebSecurity web) throws Exception {
      super.configure(web);
      web.ignoring().antMatchers("/images/**");
  }
}

Upgrade Steps | 20 → 21

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. See the Lit Upgrade Guide where you can find all 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';

to:

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';

to:

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

Update directive imports from:

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

to:

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.

The Vaadin Web Components No Longer Support <template>

The Vaadin Web Components no longer support <template> to render content. Use renderer functions instead (see the "Replace Template Elements With Renderers" section). Alternatively, you can use the @vaadin/polymer-legacy-adapter package which is created to maintain backward compatibility.

Installation

Install @vaadin/polymer-legacy-adapter as follows:

npm i @vaadin/polymer-legacy-adapter --save

Import @vaadin/polymer-legacy-adapter/template-renderer.js before any other components:

import '@vaadin/polymer-legacy-adapter/template-renderer.js';

Deprecation Warning

By default, template-renderer.js 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>
    <template>
        Content
    </template>
</vaadin-combo-box>

Positive Tab Index No Longer Supported on Input Fields

Setting the tabindex attribute to a value > 0 (for example, by calling setTabIndex(10)) is no longer supported on input field components. However, tabindex values of 0 and -1 are still supported.

Fusion Breaking Changes

Fusion Package Renaming

To give Fusion a better identity, the following Fusion packages were renamed:

  • 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’s nullable in Java, it’s 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
person.address.street
// Use the question mark to deal with undefinable properties in Vaadin 21
person.address?.street


// 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 {
    @Nonnull
    private Address address;
}
Tip
@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.

Upgrade Steps | 21 → 22

Component TypeScript APIs

Input Fields: Removed Support for Positive Tab Index

The following general changes have been made to all input field components:

  • Removed support for using positive tabindex values (for example, tabindex="1") on all input field components. This doesn’t cause errors but has no effect. However, setting tabindex to 0 or -1 is still supported. It’s recommended to ensure that input fields are in the correct order in the DOM, instead of overriding the tab order with tabindex.

Checkbox

  • A new label property was introduced to set plain text labels in client-side code, and this should be used instead of wrapping the text inside the element tags:

    <!-- Before -->
    <vaadin-checkbox>Foo</vaadin-checkbox>
    
    <!-- After -->
    <vaadin-checkbox label="Foo">
  • Similarly, rich (HTML) labels should be applied using the new label slot:

    <!-- Before -->
    <vaadin-checkbox>
      Foo <b>Bar</b>
    </vaadin-checkbox>
    
    <!-- After -->
    <vaadin-checkbox>
      <label slot="label">Foo <b>Bar</b></label>
    </vaadin-checkbox>

Combo Box

  • No longer extends vaadin-text-field.

  • Label, error message and the native input element are now slotted elements in light DOM instead of inside the component’s shadow DOM.

  • Dropped support for iron-input and paper-input from vaadin-combo-box-light.

Date Picker

  • No longer extends vaadin-text-field.

  • Label, error message and the native input element are now slotted elements in light DOM instead of inside the component’s shadow DOM.

  • Removed the i18n.calendar property, as it was unneeded since the toggle button is no longer announced by screen readers.

Grid

  • heightByRows property renamed allRowsVisible.

Number Field

  • No longer extends vaadin-text-field.

  • Label, error message and the native input element are now slotted elements in light DOM instead of inside the component’s shadow DOM.

  • Removed maxlength, minlength and pattern properties, which didn’t work anyway.

Password Field

  • Extends the updated vaadin-text-field. See Text Field changes for details.

  • Added the property i18n.reveal to provide an accessible label for the password reveal button.

Radio Button

  • New label property and label slot introduced to set plain-text and HTML labels, respectively:

    <!-- Before -->
    
    <vaadin-radio-button>Label</vaadin-radio-button>
    
    <vaadin-radio-button>
      <b>This</b> is a <i>rich</i> label
    </vaadin-radio-button>
    
    
    <!-- After -->
    
    <vaadin-radio-button label="Label"></vaadin-radio-button>
    
    <vaadin-radio-button>
      <label slot="label">
        <b>This</b> is a <i>rich</i> label
      </label>
    </vaadin-radio-button>

Text Field

  • The native <input> and <label> elements are now slotted children, instead of being inside the component’s shadow DOM.

  • The i18n.clear property has been removed as the clear button is no longer visible to screen readers.

Time Picker

  • No longer extends vaadin-text-field.

  • The i18n.selector property was removed as the toggle button is no longer visible to screen readers.

Upload

  • i18n.file.clear method renamed i18n.file.remove.

Input Fields: Removed Support for Positive Tab Index

The following general changes have been made to all input field components:

  • Removed support for using positive tabindex values (for example, setTabIndex(1)) on all input field components. This doesn’t cause errors but has no effect. However, setting tabindex to 0 or -1 is still supported. It’s recommended to ensure that input fields are in the correct order in the DOM, instead of overriding the tab order with tabindex.

Fusion Breaking Changes

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';

to:

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

TypeScript 4.4 Default Catch Variable Type

Vaadin has changed the TypeScript dependency version to 4.4. Starting from this version, the default catch variable type changes in TypeScript from any to unknown. As a result, there need to be changes to the error handling code that didn’t involve instanceof guards.

For example:

try {
  await DataEndpoint.getViewData();
} catch (error) {
  console.log(error.message); // Error TS2571: Object is of type 'unknown'.

  if (error instanceof Error) {
    console.log(error.message); // Works.
  }
}

try {
  await DataEndpoint.getViewData();
} catch (error: any) {
  console.log(error.message); // Works, but using `any` isn't recommended.
}

Positive Tab Index No Longer Supported on Input Fields

Setting the tabindex attribute to a value > 0 is no longer supported on input field components. However, tabindex values of 0 and -1 are still supported.

Changes Affecting Custom Styling of Components

Input Field Components

These changes apply to all input field components, except Checkbox. See the sections for individual components for details and other component-specific changes.

Labels

Labels have been changed to slotted child elements. While the [part="label"] selector still works, some derived selectors may need to be adjusted. The :empty selector no longer works correctly to distinguish empty and non-empty labels, and should be replaced by the [has-label] attribute selector:

/* Before */
[part="label"]:not(:empty) {...}

/* After */
:host([has-label]) [part="label"] {...}
/* Or, to target the slotted element itself */
:host([has-label]) ::slotted(label) {...}

Setting an input field’s label in Flow no longer generates a label attribute on the component’s root element. Selectors using it need to be rewritten to use the has-label attribute instead:

/* Before */
:host([label]) {...}

/* After */
:host([has-label]) {...}

Error Messages

Error messages have been changed to slotted child elements. While the [part="error-message"] selector still works, some derived selectors may need to be adjusted. The :empty selector no longer works correctly on these and should be replaced with attribute selectors for the appropriate states:

/* Before */
[part="error-message"]:not(:empty) {...}

/* After */
[has-error-message] [part="error-message"] {...}

Required Indicators

Required indicators now have their own shadow parts, instead of being pseudo-element children of the label part:

/* Before */
[part="label"]::after {...}

/* After */
[part="required-indicator"]::after {...}

Default Text Field Width

The CSS property that defines the default width of text input fields was renamed to vaadin-field-default-width:

html {
  /* Before */
  --vaadin-text-field-default-width: 20em;

  /* After */
  --vaadin-field-default-width: 20em;
}

Text Field Derivatives Refactored

The following components used to be based on the Text Field component, and were mostly styled through it:

  • Combo Box

  • Date Picker

  • Number Field and Integer Field

  • Select

  • Time Picker

These have now been refactored to no longer be based on Text Field, which means that they need to be styled separately from it. See the separate upgrade guides for each of these below.

Button

Styles Inherited by Select

The Select component has been refactored to use an internal button which inherits styles applied to the Button component. To get rid of unwanted Button styles in Select, you need to either override them or scope Button styles to only apply to actual Buttons by specifying the element name in their selectors:

/* Before */
:host {...}
:host([theme~="primary"]) {...}

/* After */
:host(vaadin-button) {...}
:host(vaadin-button[theme~="primary"]) {...}

Disabled Button Styles

The styling of disabled buttons has changed from reduced opacity to a separate grayscale coloring, which may affect custom styling of buttons: secondary and tertiary buttons now use --lumo-disabled-text-color for text, while primary buttons use --lumo-contrast-30pct for background and --lumo-base-color for text.

Charts

The default colors in charts have changed in Vaadin 23 to better match the Lumo and Material themes. You can revert to the old color scheme by switching to the “classic” theme:

chart.getElement().setAttribute("theme", "classic");

Checkbox and Radio Button

Unlike most input field components, Checkboxes and Radio Buttons no longer have a label shadow part, so the label must be targeted as a child element:

/* Before */
[part="label"] {...}

/* After */
::slotted(label) {...}

Checkbox Group

See changes common to all input field components.

Combo Box

Styles No Longer Inherited from Text Field

This component is no longer based on Text Field, so all styles previously applied via Text Field need to be applied to it separately:

  • Using a theme folder, place the styles in /components/vaadin-combo-box.css

  • Or, in Flow, with @CssImport(…​, themeFor="vaadin-combo-box")

Overlay and List Items

The contents of vaadin-combo-box-item are now slotted child elements. This mainly affects styles applied to custom renderers:

/* Before */
[content].some-classname {...}

/* After */
::slotted(.some-classname) {...}

The selection checkmarks in them have been moved to their own shadow parts:

/* Before */
:host::before {...}

/* After */
[part="checkmark"]::before {...}

Miscellaneous

The vaadin-text-field-container internal wrapper has been renamed vaadin-combo-box-container. (Note: this is an internal element whose styling isn’t supported).

Confirm Dialog

Styles should now target the vaadin-confirm-dialog-overlay element instead of vaadin-confirm-dialog.

CRUD

Depending on the editor position, styles for the CRUD’s editor should now target either the vaadin-crud element itself (for aside and bottom positions) or vaadin-crud-dialog-overlay (for the overlay position), instead of vaadin-dialog-layout.

Date Picker

Styles No Longer Inherited from Text Field

This component is no longer based on Text Field, so all styles previously applied via Text Field need to be applied to it separately:

  • Using a theme folder, place the styles in /components/vaadin-date-picker.css

  • Or, in Flow, with @CssImport(…​, themeFor="vaadin-date-picker")`

Miscellaneous

  • The vaadin-text-field-container internal wrapper has been renamed vaadin-date-picker-container. (Note: this is an internal element whose styling isn’t supported).

  • The calendar overlay’s week number text color has changed from --lumo-tertiary-text-color to --lumo-secondary-text-color.

Date Time Picker

Styles No Longer Inherited from Custom Field

This component is no longer based on Custom Field, so all styles previously applied via Custom Field need to be applied to Date Time Picker separately:

  • Using a theme folder, place the styles in /components/vaadin-date-time-picker.css

  • Or, in Flow, with @CssImport(…​, themeFor="vaadin-date-time-picker")

Slotted Date Picker and Time Picker

The Date Picker and Time Picker sub-fields are now slotted children of the Date Time Picker:

/* Before */
[part="date"] {...}
[part="time"] {...}

/* After */
::slotted([slot="date-picker"]) {...}
::slotted([slot="time-picker"]) {...}

The internal slot-container wrapper has been renamed slots. (Note: this is an internal element whose styling isn’t supported).

Grid

The color and opacity of inactive sort indicators were changed from --lumo-body-text-color at 0.2 opacity (0.6 on hover) to --lumo-tertiary-text-color (--lumo-body-text-color on hover) at 1.0 opacity.

Horizontal and Vertical Layout

Vertical Layout and Horizontal Layout have been refactored to use the CSS gap property for spacing between components, instead of margins. This makes it possible to use flex-wrap to wrap the contents of these layouts without spacing conflicts. Custom margins applied to components in these layouts are now applied in addition to the spacing, instead of overriding the spacing. While the best approach is to refactor all custom margins applied to elements inside layouts with spacing, a quick fix is to subtract var(--lumo-space-m) from affected margins (margin-top for Vertical Layouts and margin-left for Horizontal Layouts):

/* Before */
.some-layout-child {
  margin-left: 50px;
}

/* After */
.some-layout-child {
  margin-left: calc(50px - var(--lumo-space-m));
}

Icons

Icons are now rendered as vaadin-icon elements instead of iron-icon.

/* Before */
::slotted(iron-icon) {...}

/* After */
::slotted(vaadin-icon) {...}

Number Field and Integer Field

Styles No Longer Inherited from Text Field

This component is no longer based on Text Field, so all styles previously applied via Text Field need to be applied to it separately:

  • Using a theme folder, place the styles in /components/vaadin-number-field.css

  • Or, in Flow, with @CssImport(…​, themeFor="vaadin-number-field")

Password Field

All styles are still inherited from Text Field, so the same changes apply to it.

Radio Button Group

Select

See changes common to all input field components.

Styles No Longer Inherited from Text Field

This component is no longer based on Text Field, so all styles previously applied via Text Field need to be applied to it separately:

  • Using a theme folder, place the styles in /components/vaadin-select.css

  • Or, in Flow, with @CssImport(…​, themeFor="vaadin-select")`

New List Item Element

List items are now vaadin-select-item elements instead of vaadin-item (although they extend the latter, so styles applied to vaadin-item apply to vaadin-select-item as well).

/* Before (in styles.css) */
vaadin-select-overlay vaadin-item {...}

/* After (in styles.css) */
vaadin-select-item {...}

The contents of vaadin-select-item are slotted child elements. This mainly affects styles applied to custom renderers:

/* Before */
[content].some-classname {...}

/* After */
::slotted(.some-classname) {...}

Selection Checkmarks

The selection checkmarks in them have been moved to their own shadow parts:

/* Before */
:host::before {...}

/* After */
[part="checkmark"]::before {...}

Value Displayed in Field

The value displayed in the field uses the new item element too, and is now a child of a new vaadin-select-value-button internal component, and is easiest to access as a regular child element of Select:

/* Before (in vaadin-item) */
[part="value"] vaadin-item {...}

/* After (in styles.css) */
vaadin-select vaadin-select-item {...}

Placeholder Text

The value placeholder text needs to be targeted a bit differently from other similar fields:

/* Before */
[part="value"]:placeholder-shown {...}

/* After */
::slotted([placeholder]) {...}

Tabs

The color of inactive tabs has been changed from --lumo-contrast-60pct to --lumo-secondary-text-color.

Text Area

See changes common to all input field components.

Slotted Native Input Element

The native <textarea> element is now a slotted child element, and the value shadow part has been removed:

/* Before */
[part="value"] {...}

/* After */
::slotted(textarea) {...}

This also affects selectors for the placeholder text:

/* Before */
[part="value"]::placeholder {...}
/* or */
[part="value"]:placeholder-shown {...}

/* After */
::slotted(textarea:placeholder-shown) {...}

Text Field

See changes common to all input field components.

Other Text Input Components No Longer Based on Text Field

The following components that used to be based on Text Field are no longer so, and need to be styled separately instead of inheriting styles from Text Field:

  • Combo Box

  • Date Picker

  • Number Field

  • Select

  • Time Picker

However, the same structural changes were made to these as to Text Field, so the following changes and corresponding instructions apply to them as well.

Slotted Native Input Element

The native <input> element is now a slotted child element, and the value shadow part has been removed:

/* Before */
[part="value"] {...}

/* After */
::slotted(input) {...}

This also affects selectors for the placeholder text:

/* Before */
[part="value"]::placeholder {...}
/* or */
[part="value"]:placeholder-shown {...}

/* After */
::slotted(input:placeholder-shown) {...}

Placeholder Text Color

Placeholder text now uses the --lumo-secondary-text-color color property, instead of --lumo-body-text-color with 0.5 opacity.

Time Picker

Styles No Longer Inherited from Text Field

This component is no longer based on Text Field, so all styles previously applied via Text Field need to be applied to it separately:

  • Using a theme folder, place the styles in /components/vaadin-time-picker.css

  • Or, in Flow, with @CssImport(…​, themeFor="vaadin-time-picker")

Upload

The clear-button part was renamed remove-button:

/* Before */
[part="clear-button"] {...}

/* After */
[part="remove-button"] {...}

Upgrade Steps | 22 → 23

npm as the Default Frontend Package Manager

Starting from Vaadin 23, npm is used as the default frontend package manager, since the performance of npm has been improved and it’s possible to lock dependency versions with the npm overrides feature. pnpm is still supported. To continue using pnpm, set the pnpm.enable flag to true.

Flow Breaking Changes

  • Java version requirement changes from Java 8 to Java 11

    For Maven update the java.version

    <properties>
        <java.version>11</java.version>
    </properties>

    or compiler.source and compiler.target

    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
    </properties>
  • For Java 9+ maven-failsafe-plugin requires jaxb-impl to be added

    The dependency can be added directly in the plugin definition as:

    <plugin>
        <artifactId>maven-failsafe-plugin</artifactId>
        <dependencies>
            <dependency>
                <groupId>com.sun.xml.bind</groupId>
                <artifactId>jaxb-impl</artifactId>
                <version>${jaxb.version}</version>
            </dependency>
        </dependencies>
    </plugin>
  • Frontend package manager changes from pnpm to npm

    The default package manager is now npm, but if using a version older than 8.3 it’s recommended to use pnpm instead.

    To keep using pnpm the configuration pnpm.enable should be set.

    <plugin>
        <groupId>com.vaadin</groupId>
        <artifactId>flow-maven-plugin</artifactId>
        <configuration>
            <pnpmEnable>true</pnpmEnable>
        </configuration>
    </plugin>
    vaadin {
      pnpmEnable = true
    }

    Or using the system property vaadin.pnpm.enable=true

    Note
    Running npm without a package-lock.json is slow for the initial installation. A project that comes from pnpm doesn’t have an npm package-lock.json, as it uses pnpm-lock.yaml.
  • Charts CSS styling mode no longer requires importing and including the default charts theme, so these should be removed.

    /* Before */
    @JsModule("@vaadin/vaadin-charts/theme/vaadin-chart-default-theme")
    @CssImport(value = "my-chart-styles.css", themeFor = "vaadin-chart", include = "vaadin-chart-default-theme")
    
    /* After */
    @CssImport(value = "my-chart-styles.css", themeFor = "vaadin-chart")

Changes

  • Recommended minimum version of npm is v8.3

    This is to have the support for overrides to lock transitive dependencies versions.

  • When the global node isn’t compatible, the version in ~/.vaadin is used.

    If a version doesn’t exist or is too old it is automatically downloaded and installed.

Hilla as a Standalone Platform

Fusion used to be part of the Vaadin platform, now it’s separated from Vaadin and packaged as a standalone platform called Hilla. Hilla and Vaadin work seamlessly together, as long as the versions match. They share the same minor and patch versions; the difference is that Vaadin has 23 as the major version, while Hilla has 1.

Use Hilla Dependency Instead of Vaadin

For a pure Fusion application, you can update the Vaadin dependency and related artifacts to Hilla. The benefit is that some unnecessary dependencies are excluded, for example, the server-side Flow components. Here is an example of pom.xml file updates:

<!-- use hilla version instead of vaadin -->
<hilla.version>1.0.0</hilla.version>

<!-- use hilla bom instead of vaadin -->
<dependency>
    <groupId>dev.hilla</groupId>
    <artifactId>hilla-bom</artifactId>
    <version>${hilla.version}</version>
    <type>pom</type>
    <scope>import</scope>
</dependency>

<!-- use hilla dependency instead of vaadin -->
<dependency>
    <groupId>dev.hilla</groupId>
    <artifactId>hilla</artifactId>
</dependency>

<!-- use hilla-spring-boot-starter instead of vaadin-spring-boot-starter -->
<dependency>
    <groupId>dev.hilla</groupId>
    <artifactId>hilla-spring-boot-starter</artifactId>
</dependency>

<!-- use hilla-maven-plugin instead of vaadin-maven-plugin -->
<groupId>dev.hilla</groupId>
<artifactId>hilla-maven-plugin</artifactId>
<version>${hilla.version}</version>

There’s no hilla-core dependency; both vaadin and vaadin-core should be replaced with hilla.

Flow-Hilla Hybrid Projects

If a Flow application needs the Hilla features, such as Endpoint, it can be updated to also include the hilla dependency in the pom.xml file. Here is an example of how to do that:

<vaadin.version>23.0.0</vaadin.version>
<!-- add the hilla version -->
<hilla.version>1.0.0</hilla.version>

<dependency>
    <groupId>com.vaadin</groupId>
    <artifactId>vaadin-bom</artifactId>
    <version>${vaadin.version}</version>
    <type>pom</type>
    <scope>import</scope>
</dependency>

<!-- add the hilla bom -->
<dependency>
    <groupId>dev.hilla</groupId>
    <artifactId>hilla-bom</artifactId>
    <version>${hilla.version}</version>
    <type>pom</type>
    <scope>import</scope>
</dependency>

<dependency>
    <groupId>com.vaadin</groupId>
    <artifactId>vaadin</artifactId>
</dependency>

<!-- add the hilla dependency -->
<dependency>
    <groupId>dev.hilla</groupId>
    <artifactId>hilla</artifactId>
</dependency>

Java Package Renaming

The com.vaadin.fusion package was renamed to dev.hilla, so the Java imports need to be updated accordingly.

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

import com.vaadin.fusion.Endpoint;

to:

import dev.hilla.Endpoint;

Front-End npm Package Renaming

The front-end code was moved from @vaadin/flow-frontend to @vaadin/fusion-frontend, and from @vaadin/form to @hilla/form. The module imports need to be updated correspondingly.

For example, importing EndpointError, Binder and field need to be changed as follows, from:

import { EndpointError } from '@vaadin/fusion-frontend';
import { Binder, field } from '@vaadin/form';

to:

import { EndpointError } from '@hilla/frontend';
import { Binder, field } from '@hilla/form';

Add Hilla Package to whitelisted-packages List

Make sure that dev.hilla is added to the vaadin.whitelisted-packages property inside application.properties file. For example, the property should be changed to look something as follows:

vaadin.whitelisted-packages = com.vaadin,org.vaadin,com.example.application,dev.hilla

After You Finish

  • Run the following command:

mvn clean install