Documentation

Documentation versions (currently viewingVaadin 23)
New Acceleration Kits: Observability Kit, SSO Kit, and Swing Kit. Read the blog post.

Recommended Vaadin 14 to 23 Upgrade Changes

Bootstrapping Changes

Vaadin 14 server-side bootstrapping has been deprecated in Vaadin 23 and replaced with a client-side bootstrapping model. This new model requires several annotations typically placed on the MainLayout / MainView class to be moved to a class implementing the AppShellConfigurator interface. For example, the PWA annotation:

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

}

Moreover, the new bootstrapping model requires replacing the usages of the V10-14 BootstrapHandler APIs with their IndexHtmlRequestHandler API counterparts, as described in the 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 and later, with client-side bootstrapping, the index.html page includes only the basic HTML markup and links to the TypeScript UI code. If you have client-side/Hilla views, the UI isn’t guaranteed to be created, and so it’s optional. It’s available only 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 to use the flag in Configuration Properties.

Replace Deprecated APIs

  • JavaScript execution APIs executeJavaScript() and callFunction() in Element and Page should be replaced with similarly named methods that give access to the return value executeJs() and callJsFunction().

  • ExecutionCanceler is replaced with PendingJavaScriptResult.

  • BootstrapListener is deprecated in favor of IndexHtmlRequestListener when using client-side bootstrapping.

  • The PageConfigurator interface is deprecated; customize initial page settings instead by overriding AppShellConfigurator::configurePage().

  • setDataProvider() is now deprecated (in Grid, Select, RadioButtonGroup, CheckboxGroup, ListBox and ComboBox) and it’s recommended to use the overloaded setItems() methods. Read more about the dataview API which was originally introduced with Vaadin 17.

  • TemplateRenderer for Grid and Combo Box (which is based on Polymer templates) is now deprecated in favor of LitRenderer. For example:

    TemplateRenderer.of("<order-card"
    	+ "  header='[[item.header]]'"
    	+ "  order-card='[[item.orderCard]]'"
    	+ "  on-card-click='cardClick'>"
    	+ "</order-card>");
    LitRenderer.of("<order-card"
    	+ "  .header='${item.header}'"
    	+ "  .orderCard='${item.orderCard}'"
    	+ "  @card-click='${cardClick}'>"
    	+ "</order-card>");

For more information, check the Grid’s Lit Renderers guide. Also check the following external blog post for the differences between Polymer and Lit syntax.

Update Source Control Settings

Vaadin 23 involves some minor changes in the layout and content of Vaadin projects. Be sure to visit the Source Control section to check the directory layout of a Vaadin 23 application, and update your version control settings accordingly.

Use Security Helpers

Vaadin 23 comes with a view-based access control mechanism that makes it easier, faster, and safer for Vaadin developers to utilize security in their web applications. To enable this access-control mechanism, see the instructions for a Spring project or the instructions for a plain-Java project.

Migrate from PolymerTemplate to LitTemplate

Click and read if you are using PolymerTemplate.

In the latest Vaadin versions, the PolymerTemplate class is deprecated and it’s recommended instead to use a LitTemplate.

In trivial cases, it’s enough to replace the PolymerTemplate superclass with LitTemplate in Java and then migrate the PolymerElement based client view to be based on LitElement instead. See this external blog post for a more detailed explanation of the PolymerElement to LitElement migration process.

public class HelloWorld extends PolymerTemplate<HelloWorldModel> {...}
public class HelloWorld extends LitTemplate {...}
import {PolymerElement, html} from '@polymer/polymer/polymer-element.js';

class HelloWorld extends PolymerElement {
    static get template() {
        return html`
            <div>
            ...`
import {LitElement, html} from 'lit';

class HelloWorld extends LitElement {
    render() {
        return html`
            <div>
            ...`
Note
Update @Id package name
If you use @Id for binding to template components, the package name should be changed from com.vaadin.flow.component.polymertemplate.Id to com.vaadin.flow.component.template.Id for LitTemplate.

In general, the concept with PolymerTemplate and LitTemplate is mostly the same, but there are some differences that need to be taken into account when migrating.

Replace EventHandler with ClientCallable

The @EventHandler annotation used with PolymerTemplate can’t be used with LitTemplate. With LitTemplate, it’s recommended to handle client-to-server communication primarily with @ClientCallable methods. See the @ClientCallable documentation for more details.

@EventHandler
private void handleClick() {
    System.out.println("Hello world!");
}
@ClientCallable
private void handleClick() {
    System.out.println("Hello world!");
}
html`<button on-click="handleClick">Click me</button>`;
html`<button @click="${e => this.$server.handleClick()}">Click me</button>`;

Migrating from TemplateModel

LitTemplate doesn’t support the TemplateModel API and its utilities. With Vaadin 23, it’s recommended to handle server-to-client data communication with the Vaadin Element APIs instead.

For example, the getElement().setProperty("propertyName", value) method can be used to set primitive property values for the client-side view. To set a simple bean as an object property for the view, use the getElement().setPropertyBean("propertyName", bean) method.

public interface Model extends TemplateModel {
    void setReview(boolean review);

    void setItem(Order order);
}

getModel().setReview(true);
getModel().setItem(order);
getElement().setProperty("review", true);
getElement().setPropertyBean("item", order);

If your TemplateModel used annotations such as @Encode (to convert the bean properties) or @Include (to limit the properties being sent to the client), then setPropertyBean() isn’t a good fit, as it automatically converts the whole bean to a JsonObject as is.

Instead, you can build a custom JsonObject manually with the Elemental JSON API and then send it to the client using the getElement().setPropertyJson("propertyName", jsonObject) method.

public interface Model extends TemplateModel {
    @Include({ "id", "name", "totalPrice" })
    @Encode(value = CurrencyFormatter.class, path = "totalPrice")
    void setItem(Order order);
}

getModel().setItem(order);
getElement().setPropertyJson('item', toJson(order));

private JsonObject toJson(Order order) {
    JsonObject json = Json.createObject();
    json.addProperty("id", order.getId());
    json.addProperty("name", order.getName());
    json.addProperty("totalPrice", new CurrencyFormatter().encode(order.getTotalPrice()));
    return json;
}
Tip
Using Java Data Transfer Objects instead of JsonObject
As an alternative to building a JsonObject manually, you can create a Java DTO class containing only the fields needed by the client and pass instances of that DTO to setPropertyBean().

If a property value gets modified by the client logic, the update needs to be communicated back to the server. For this, you can use a @ClientCallable method.

@ClientCallable
public void setProperty(String value) {
    // handle updated property on the server
}
updated(changedProperties) {
    if (changedProperties.has("propertyName")) {
        this.$server.setProperty(this.propertyName);
    }
}
Note
Declare all reactive client-side properties
With LitElement, it’s especially important to have all the reactive properties explicitly declared in the client view. Declaring the properties makes sure that the view gets re-rendered whenever a property value changes.
static get properties() {
    return {
        item: {
            type: Object,
        },
        review: {
            type: Boolean,
        }
    };
}
@property()
item: Order;

@property()
review: boolean;

Replace Template Elements with Renderers

Certain Vaadin Web Components require the application to explicitly define how to render some parts of their content. For example, the <vaadin-dialog> component needs to know how to render the content of the overlay.

With PolymerTemplate, it was possible to use a <template> element for this purpose. This approach isn’t recommended with LitTemplate and you should favor using renderer functions instead.

import { PolymerElement, html } from '@polymer/polymer/polymer-element.js';

...

static get template() {
  return html`
    <vaadin-dialog>
      <template>
        <h1>Title</h1>
        <p>Content</p>
      </template>
    </vaadin-dialog>
  `;
}
import { html, LitElement, render } from 'lit';

...

render() {
  return html`
    <vaadin-dialog .renderer="${this.dialogRenderer}"></vaadin-dialog>
  `;
}

dialogRenderer(root) {
  render(html`
    <h1>Title</h1>
    <p>Content</p>
  `, root);
}

A renderer function is a JavaScript function that the component calls whenever it needs some parts of its content to be updated. The function is called with the following arguments:

  • root: the DOM element that the renderer should fill with the content.

  • rendererOwner: the element the renderer is attached to.

  • model: (optional) the data that the renderer should use to render the content. Includes properties such as index and item.

Updating the Content Dynamically

Sometimes the component content needs to be updated dynamically. Typically, this is due to some change in the state properties of the view.

With PolymerElement based views and the <template> API, much of this happened automatically.

static get template() {
  return html`
    <vaadin-dialog>
      <template>
        <h1>[[title]]</h1>
        <p>Content</p>
      </template>
    </vaadin-dialog>
  `;
}

When the title property of the view changes, the content of the <h1> element gets updated.

With LitElement and the renderer functions, some more wiring is needed. Consider the following content in the LitElement based view:

render() {
  return html`
    <h1 id="view-title">${this.title}</h1>

    <vaadin-dialog .renderer="${this.dialogRenderer}"></vaadin-dialog>
  `;
}

dialogRenderer(root) {
  render(html`
    <h1 id="dialog-title">${this.title}</h1>
    <p>Content</p>
  `, root);
}

In this case, when the state property title changes, LitElement automatically re-renders the view. As a result, the <h1> element with the ID view-title is updated with the new value, but the <h1> element inside the renderer function isn’t.

This is because changes in the reactive properties only cause the view to re-render, but not the components. To get components to re-render, they need to be explicitly requested to do so. One way to do this is to call the component’s requestContentUpdate() function inside the updated() lifecycle callback.

updated(changedProperties) {
  if (changedProperties.has('title')) {
    this.renderRoot.querySelector('vaadin-dialog').requestContentUpdate();
  }
}

One important thing to note is that if you reference this inside a renderer function, the view should be bound as the function’s this context in the constructor.

constructor() {
  super();
  this.dialogRenderer = this.dialogRenderer.bind(this);
}

Update Your Templates

Click and read if you are using Lit templates.

Use Short Package Names

Use the updated short package names for component imports. This means dropping the vaadin- prefix from the component name, so that, for example, @vaadin/vaadin-text-field becomes @vaadin/text-field.

The following exceptions should be taken into account, however:

  • @vaadin/vaadin-text-field/vaadin-email-field.js@vaadin/email-field

  • @vaadin/vaadin-text-field/vaadin-integer-field.js@vaadin/integer-field

  • @vaadin/vaadin-text-field/vaadin-number-field.js@vaadin/number-field

  • @vaadin/vaadin-text-field/vaadin-password-field.js@vaadin/password-field

  • @vaadin/vaadin-text-field/vaadin-text-area.js@vaadin/text-area

  • @vaadin/vaadin-checkbox/vaadin-checkbox-group.js@vaadin/checkbox-group

  • @vaadin/vaadin-messages/vaadin-message-list.js@vaadin/message-list

  • @vaadin/vaadin-messages/vaadin-message-input.js@vaadin/message-input

  • @vaadin/vaadin-ordered-layout/vaadin-vertical-layout.js@vaadin/vertical-layout

  • @vaadin/vaadin-ordered-layout/vaadin-horizontal-layout.js@vaadin/horizontal-layout

  • @vaadin/vaadin-ordered-layout/vaadin-scroller.js@vaadin/scroller

  • @vaadin/vaadin-radio-button@vaadin/radio-group

Replace Iron Components

Click and read if you are using Iron components.

Earlier versions of Vaadin promoted using IronIcon for icons and IronList for long item lists. In the latest version of Vaadin, these components have been deprecated and replaced with Icon and VirtualList.

Replacing IronIcon with Icon

/* Before */
import com.vaadin.flow.component.icon.IronIcon;

IronIcon icon = new IronIcon("vaadin", "moon");

/* After */
import com.vaadin.flow.component.icon.Icon;

Icon icon = new Icon("vaadin", "moon");

After the update, the Web Component name changes from iron-icon to vaadin-icon, so you need to update any selectors in the application styles targeting iron-icon to vaadin-icon.

If you used the --iron-icon-width and --iron-icon-height properties to set the <iron-icon> size, for <vaadin-icon> you can use width and height instead.

/* Before */
iron-icon {
    --iron-icon-width: 32px;
    --iron-icon-height: 32px;
}

/* After */
vaadin-icon {
    width: 32px;
    height: 32px;
}

If you want to keep using a custom property to control the icon size and don’t want to modify the value of --lumo-icon-size-m, you can, for example, define custom properties for the <vaadin-icon> element in your theme.

:host {
    width: var(--my-custom-prop-for-icon-width, --lumo-icon-size-m);
    height: var(--my-custom-prop-for-icon-height, --lumo-icon-size-m);
}

If you have used PolymerTemplate and have manual imports for the <iron-icon> element or for the vaadin-icons icon set, these need to be updated as well.

/* Before */
import '@polymer/iron-icon/iron-icon.js';
import '@vaadin/vaadin-icons/vaadin-icons.js';

/* After */
import '@vaadin/icon';
import '@vaadin/icons';

If you have defined your own SVG icons previously using <iron-iconset-svg>, use <vaadin-iconset> instead.

/* Before */
import '@polymer/iron-iconset-svg/iron-iconset-svg.js';

const template = document.createElement('template');
template.innerHTML = `
    <iron-iconset-svg name="myapp" size="24">
        <svg>
            <defs>
                <g id="my-icon"><path d="..."></path></g>
            </defs>
        </svg>
    </iron-iconset-svg>`;
document.head.appendChild(template.content);

/* After */
import '@vaadin/icon';

const template = document.createElement('template');
template.innerHTML = `
  <vaadin-iconset name="myapp" size="24">
      <svg>
          <defs>
              <g id="my-icon"><path d="..."></path></g>
          </defs>
      </svg>
  </vaadin-iconset>`;
document.head.appendChild(template.content);

Replacing IronList with VirtualList

VirtualList is mostly a drop-in replacement for IronList. One noticeable difference is that the setGridLayout(true) API has been dropped.

/* Before */
import com.vaadin.flow.component.ironlist.IronList;

IronList list = new IronList();

/* After */
import com.vaadin.flow.component.virtuallist.VirtualList;

VirtualList list = new VirtualList();

After the update, the Web Component name changes from iron-list to vaadin-virtual-list, so you need to update any selectors in the application styles targeting iron-list to vaadin-virtual-list.

Replace JsModule Style Sheets with CssImports

Click and read if you have styles imported with @JsModule annotations.

Loading style sheets with the @JsModule annotation in Flow is deprecated. JS-wrapped style sheets should be refactored to regular CSS files and loaded with @CssImport, or, preferably, using the new theme folder format recommended for styling applications since Vaadin 19.

Move the CSS Out of JsModule Files

Styles loaded with @JsModule are wrapped in <dom-module> and <custom-style> tags inside JavaScript files:

import '@polymer/polymer/lib/elements/custom-style.js';
const $_documentContainer = document.createElement('template');

$_documentContainer.innerHTML = `
<custom-style>
  <style>
    /* Global CSS */
    html {...}
  </style>
</custom-style>

<dom-module id="my-text-field-styles" theme-for="vaadin-text-field">
  <template>
    <style>
	  /* CSS for vaadin-text-field */
      :host {...}
	</style>
  </template>
</dom-module>`;
document.head.appendChild($_documentContainer.content);

Move the contents of each <dom-module> and <custom-style> tag into its own CSS file:

/* Global CSS */
html {...}
/* CSS for vaadin-text-field */
:host {...}

Replace JsModule Annotations with CssImport

Replace all @JsModule annotations with @CssImport annotations for each style sheet.

Styles that were applied to specific Vaadin components with a theme-for attribute in their dom-module need a corresponding themeFor parameter in their CssImport.

@JsModule("./styles/shared-styles.js")
@CssImport("./styles/styles.css")
@CssImport(value="./styles/text-field.css", themeFor="vaadin-text-field")

Refactor Styles to a Custom Theme Folder

Although importing styles using @CssImport still works, the recommended way to apply CSS to Vaadin applications is using the theme folder format introduced in Vaadin 19.

Note
Doesn’t work with the Material theme
The new custom theme format doesn’t yet work with the Material theme. Applications using Material should stick to @CssImport based styling.
  1. Create a themes folder under the project’s frontend folder.

  2. Create a folder inside themes named, for example, according to the project (my-theme is used as an example here).

  3. Create a style sheet called styles.css inside the folder (or, if you already have one, move it there).

  4. Create a components folder inside the same folder.

    You should now have a folders structure like this:

    frontend
    └── themes
        └── my-theme
            ├── components/
            └── styles.css
  5. Move all Vaadin component style sheets that have a themeFor parameter (or, if refactoring from JsModules, a theme-for attribute in the dom-module) into the components folder and rename them to correspond to the themeFor value (the component’s HTML element name).

    @CssImport(value="text-field.css", themeFor="vaadin-text-field")
    frontend/themes/my-theme/components/vaadin-text-field.css
  6. Move other style sheets next to styles.css

    frontend
    └── themes
        └── my-theme
            ├── components/
            ├── styles.css
            ├── navigation.css
    		    ├── dashboard.css
            └── ...

    and import each one into styles.css using @import

    @import 'navigation.css';
    @import 'dashboard.css';
  7. Remove all @CssImport annotations.

  8. Apply the theme using the @Theme annotation, passing the theme folder’s name as a parameter.

    @Theme("my-theme")
    public class Application extends SpringBootServletInitializer
                             implements AppShellConfigurator {
      ...
    }
  9. See the theme folder format documentation for more details on the new format.