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 {
}
See set of annotations to modify the Bootstrap page for more details.
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-sideUI
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, theUI
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()
andcallFunction()
inElement
andPage
should be replaced with similarly named methods that give access to the return valueexecuteJs()
andcallJsFunction()
. -
ExecutionCanceler
is replaced withPendingJavaScriptResult
. -
BootstrapListener
is deprecated in favor ofIndexHtmlRequestListener
when using client-side bootstrapping. -
The
PageConfigurator
interface is deprecated; customize initial page settings instead by overridingAppShellConfigurator::configurePage()
. -
setDataProvider()
is now deprecated (inGrid
,Select
,RadioButtonGroup
,CheckboxGroup
,ListBox
andComboBox
) and it’s recommended to use the overloadedsetItems()
methods. Read more about thedataview
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 ofLitRenderer
. 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 asindex
anditem
.
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.
|
-
Create a
themes
folder under the project’sfrontend
folder. -
Create a folder inside
themes
named, for example, according to the project (my-theme
is used as an example here). -
Create a style sheet called
styles.css
inside the folder (or, if you already have one, move it there). -
Create a
components
folder inside the same folder.You should now have a folders structure like this:
frontend └── themes └── my-theme ├── components/ └── styles.css
-
Move all Vaadin component style sheets that have a
themeFor
parameter (or, if refactoring from JsModules, atheme-for
attribute in thedom-module
) into thecomponents
folder and rename them to correspond to thethemeFor
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
-
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';
-
Remove all
@CssImport
annotations. -
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 { ... }
-
See the theme folder format documentation for more details on the new format.