Migration example - Bookstore Starter

This document shows an example migration from a V8 app to Vaadin Flow. The migration process is done step-by-step and can be seen through the history of its GitHub repository. The idea is to keep the application compilable in order to be able to see the result of migrating steps.

Step 1 - Initial Vaadin Flow configuration


First of all, required maven dependency must be added to pom.xml. The Vaadin 8 dependencies, except vaadin-themes, are kept for now and will be eliminated after the whole application is migrated. The only Vaadin platform dependency is the following:


In this document, migrating custom components and extensions is not covered. So, the widgetset module that includes the following extensions is removed.

  • AttributeExtension

  • ResetButtonForTextField

UI class and Servlet configuration

  • Both UI class and Servlet configuration are optional in Vaadin Flow. However, we can keep them to leverage them in some cases e.g. controlling user access.

  • Since the components are different, the DOM structure has changed. The new components are based on web components and a new theme. Thus the theming is discussed later.

  • In Vaadin Flow the locale is set automatically based on user preferred locale. So, setLocale can be removed.

  • Best practice for setting page title is using PageTitle annotation on each view. So, getPage().setTitle is removed from MyUI::init.

  • All packages names in Vaadin Flow start with com.vaadin.flow. One way to correct them is to remove all import statements starting by com.vaadin and reimport Vaadin Flow classes. For example some equivalent classes in Vaadin Flow are:

  • com.vaadin.ui.UIcom.vaadin.flow.component.UI

  • com.vaadin.server.VaadinRequestcom.vaadin.flow.server.VaadinRequest

  • com.vaadin.ui.TextFieldcom.vaadin.flow.component.textfield.TextField

  • com.vaadin.ui.VerticalLayoutcom.vaadin.flow.component.orderedlayout.VerticalLayout

Test page

In order to verify that Vaadin Flow setup has been done correctly, a simple HelloWorldPage like the following can be added.

import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.html.H1;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;

public class HelloWorldPage extends Div {
   public HelloWorldPage() {
       this.add(new H1("Hello World!"));

@Route(“”) shows that root path should be routed to this page. After running the application by mvn jetty:run, the “Hello World!” message can be seen in the browser by entering this address:

Here is the link to the repository after the first step.

Step 2 - Access Control and Login Screen

VaadinServletConfiguration and UI class

In Vaadin Flow, defining a Servlet class is optional. So, we don’t have to create an extended class of VaadinServlet, unless we need to change some configuration. Having a UI class is optional too and this class can be removed as well, because the UI class is created by the framework. However, we may have some tasks assigned to our UI class e.g. controlling access. In this example access control is moved to a more suitable place which is described in the following section.

Access Control

BeforeEnter event of UI class is a good place to control access and there is another event named UIInit in VaadinService class that is fired whenever a UI is created. In order to leverage these events, we can create a class extended from VaadinServiceInitListener and add required code in serviceInit method. The result looks like the following piece of code:

public class BookstoreInitListener implements VaadinServiceInitListener {
   public void serviceInit(ServiceInitEvent initEvent) {
       initEvent.getSource().addUIInitListener(uiInitEvent -> {
           uiInitEvent.getUI().addBeforeEnterListener(enterEvent -> {
               // Controlling access can be done here.

MyUI class had an instance of BasicAccessControl and other classes used it via its accessor; now after MyUI class is eliminated, there must be another provider for AccessControl implementation. The selected solution here is using a factory class (AccessControlFactory).

CurrentUser class is also needed to change because it is used in BasicAccessControl class. We need to apply new packages names of Flow that start with com.vaadin.flow. The same should be done in next steps of migration.


This is the first UI screen migrated to Flow. The following items describe what needs to be done in migration process:

  • Instead of CssLayout another equivalent component must be used e.g. FlexLayout or a simple Div.

  • Equivalent of addComponent method is add method.

  • setWidth method in Flow has only one String parameter that includes both measurement unit and width as a number e.g. “15em” or “310px”.

  • Route annotation determines the url associated with this screen.

  • In order to add a theme variant in V10, getElement().getThemeList().add(String) is used. Although theme variant is different from style name, the former can be considered as the successor of the latter. So, addStyleName(String) can be replaced with getElement().getThemeList().add(String). However, in V12 addThemeVariants(…​) API was reintroduced for generated components. Changes in theming from V8 to Vaadin platform is described here.

  • New FormLayout has a method named addFormItem takes a component as a parameter and in addition to adding it to the form, it adds a label beside the component as well.

  • In Flow, instead of Button::setClickShortcut that adds a shortcut key to a button, a keypress event listener should be added. For example, the following piece of code is equivalent to login.setClickShortcut(ShortcutAction.KeyCode.ENTER);.

        .addEventListener("keypress", event -> login())
        .setFilter("event.key == 'Enter'");

An improved keyboard shortcuts API will be available in Vaadin 13.

Some other changes that have been done are not related to Vaadin framework migration process; however, it is a good idea to do such refactorings at the same time as migration.

Here is the link to see the changes in second migration step.

Step 3 - Menu, MainScreen and AboutView

As explained before, instead of CssLayout, FlexLayout is used.

Navigator class is removed in Flow and this is one of many changes in routing and navigation since version 8. So, navigator field is removed from Menu. In addView method it can be seen that navigation is done by RouterLink component.

At this stage a pretty look is not aimed and it will be made nicer in later steps.


In Vaadin 8 version there is a CssLayout that acts as a view container and navigation between different views is done inside the CssLayout. In Vaadin Flow, parent layouts can be defined using a newly introduced RouterLayout interface. Since MainScreen is used as a layout for other views, it must implement RouterLayout interface.


Layout of views can be specified in Route annotation like this @Route(value = "About", layout = MainScreen.class). We don’t need the HelloWorldPage anymore, so it is removed and since it’s good to have a route to root path, RouteAlias annotation is used to add a secondary path for AboutView.

Another thing worth mentioning here is that in Vaadin platform, a component named Icon is added and can be created by calling create method of VaadinIcon enum.

Here is the link to see the changes in step 3.

Step 4 - Product Grid


In Vaadin platform, when DataProvider::fetch method is overridden, query.getOffset() and query.getLimit() must be used to fetch a specific chunk of data. If they are not used it shows that the returned data is incorrect and unexpected. To avoid such mistakes in implemented code, Vaadin platform throws an IllegalStateException to show us what is wrong. So, ProductDataProvider::fetch is fixed in order to use specified offset and limit. The data provider documentation for Vaadin platform can be found here.


The following items briefly describe some of the changes in ProductGrid.

  • There is no HtmlRenderer in Vaadin platform and it must be replaced by other renderers such as TemplateRenderer or ComponentRenderer. In this migration, TemplateRenderer is used. More info and guidance about all kinds of renderers can be found in "Using Renderers" section of Grid document. In TemplateRenderer, apart from HTML markup, Polymer data binding notation can also be used. In ProductGrid, there are three TemplateRenderers:

    • Price and StockCount columns leverage TemplateRenderer to align their text to right.

    • Availability column template uses a Vaadin component named iron-icon to show a circle colored based on availability value. In order to set different styles to the circle, three css classes with equivalent names to three values of availability (Available, Coming and Discontinued) are defined in a css file (grid.css). Also, the dependency of the grid on the css file is defined by adding StyleSheet annotation to ProductGrid class.

  • Grid.Column::setCaption method is renamed to setHeader.

  • setFlexGrow method is called for each column to set grow ratios of them.


This is the page that includes ProductGrid and ProductForm and since ProductForm is going to be migrated in next step, the parts of the code related to it are commented. Like in the other views, a Route annotation is added here with the "Inventory" value. Also, as this view is the main view of the project, the route to root path, the RouteAlias annotation, should be moved here. Other changes in SampleCrudView are the following items.

  • getElement().getThemeList()::add is used to add a theme variant to a component. An improved API for this has been released in V12.

  • In Vaadin 8, in order to get the parameters passed via the URL, View interface must be implemented and the enter method must be overridden. In Vaadin platform, there is an interface named HasUrlParameter that does the job. It is generic, so parameters are safely converted to the given types. More information about URL parameters can be found here.

  • Instead of using HorizontalLayout::setExpandRatio, HorizontalLayout::expand method is used.

Here is the link to see the changes in step four.

Step 5 - Product Form

Since after this step, all Java code is migrated to Vaadin platform, it is time to remove Vaadin 8 dependencies. Besides, keeping both versions may cause some conflicts in their dependencies e.g. jsoup. So, vaadin-server and vaadin-push are removed from pom.xml. Other changes in this step are as follows.

ProductForm Design

The following items are some of the changes from Vaadin 8 to Vaadin platform in design files.

  • In Vaadin 8, Vaadin Designer uses HTML markups to store designed views and they are stored in files with html extension. However, the tags that are used by Vaadin Designer are not standard HTML tags. So, these html files cannot be correctly shown and rendered by browsers. While in Vaadin platform, Polymer template is used to define views and Vaadin Designer also uses it to store designed views.

  • Prefix of the Vaadin components names is changed from v to vaadin.

  • For customizing the look and feel of the components using the provided theme variants, the variants are applied with the theme attribute, instead of the style-name (class name). E.g.

Vaadin 8 version:

<v-button style-name="primary" _id="save">Save</v-button>

Vaadin platform version:

<vaadin-button theme="primary" id="save">Save</vaadin-button>

ProductForm Java Class

ProductFormDesign class is removed and its content is moved to ProductForm class. Actually, this is the recommended pattern in Vaadin platform and it is also supported by Vaadin Designer. In Vaadin 8, Vaadin Designer keeps two classes, a superclass for designer generated code and an inherited class for the code implemented by developer. The following items are some of the changes in ProductForm.

  • HtmlImport and Tag annotations are the required annotations to connect ProductForm class to its design file, ProductFormDesign.html. And unlike Vaadin 8, reading the design file is done automatically and there is not need to call Design.read.

  • Id annotation is used to connect fields to their equivalents in the associated polymer template.

  • In ComboBox, setEmptySelectionAllowed method is renamed to setAllowCustomValue.

  • CheckboxGroup has been released in Vaadin 12.

The commit showing the changes for a migrated product form is here.


Router Exception Handling in Vaadin Flow is described here. Applications can have different views for catching different exceptions. For example, ErrorView catches NotFoundException that is thrown when something goes wrong while resolving navigation routes. And unlike Vaadin 8, there is no need to register ErrorView in a navigator or something like that. It is automatically detected and is used by Flow.

The commit showing the migration of the error view is here.


Apart from some cleaning, a small change that is worth mentioning is the change in how the URL of the browser is updated. In Vaadin 8, page.setUriFragment is called and the new URL must be constructed and passed as a parameter. While in Vaadin Flow, it is done in a more elegant way; navigate method of UI class is called and the view parameter is passed as a parameter to navigate method.

Here is the link to see all changes in step five.

Step 6 - Production Mode

The best practice to have the production mode in Vaadin Flow is adding a profile to pom.xml. So, the production module is no longer needed and is removed and a profile named productionMode is added to pom.xml of ui module. In terms of production mode, there are some differences between Vaadin 8 and Flow. The new production mode of Flow is fully described Here.

Here is the link to see all changes in step six.

Step 7 - Theming the application

This step is still in progress and its documentation will be added here when it is completed.

Are you planning on migrating your application? Get help from Vaadin experts.

Open in a
new tab
export class RenderBanner extends HTMLElement {
  connectedCallback() {

  renderBanner() {
    let bannerWrapper = document.getElementById('tocBanner');

    if (bannerWrapper) {

    let tocEl = document.getElementById('toc');

    // Add an empty ToC div in case page doesn't have one.
    if (!tocEl) {
      const pageTitle = document.querySelector(
        'main > article > header[class^=PageHeader-module--pageHeader]'
      tocEl = document.createElement('div');

      pageTitle?.insertAdjacentElement('afterend', tocEl);

    // Prepare banner container
    bannerWrapper = document.createElement('div');
    bannerWrapper.id = 'tocBanner';

    // Banner elements
    const text = document.querySelector('.toc-banner-source-text')?.innerHTML;
    const link = document.querySelector('.toc-banner-source-link')?.textContent;

    const bannerHtml = `<div class='toc-banner'>
          <a href='${link}'>
            <div class="toc-banner--img"></div>
            <div class='toc-banner--content'>${text}</div>

    bannerWrapper.innerHTML = bannerHtml;

    // Add banner image
    const imgSource = document.querySelector('.toc-banner-source .image');
    const imgTarget = bannerWrapper.querySelector('.toc-banner--img');

    if (imgSource && imgTarget) {