Vaadin 8 to 23 Migration

Not yet supported
  • Collapsible columns (currently always hidden)

Application Framework


  • Deprecated Message* classes have been removed

    • MessageListPanel has been replaced by MessageUiComponents#createMessageTable

    • MessagePmo has been replaced by MessageRowPmo

    • MessageRow has been replaced by MessageUiComponents#createMessageComponent

LinkkiUi and navigation

Navigation has been replaced by Routing, using the @Route annotation at the corresponding Component itself. With the new changes, it is no longer necessary to extend the UI as the tasks of LinkkiUi either became oblivious or is overtaken by ApplicationLayout.

Change Migration

The class LinkkiUi is removed.

Remove sub-classes of LinkkiUi.

Change Migration
  • The constructor now takes ApplicationConfig as a parameter.

  • #getApplicationConfig` has been removed.

  • Create a new sub-class of ApplicationLayout (e.g. MyApplicationLayout)

  • In the new subclass, pass an instance of the customized ApplicationConfig to the super constructor. This ApplicationConfig was previously used in the implementation of LinkkiUi.

#createApplicationNavigator has been removed.

No migration needed

ApplicationLayout itself implements RouterLayout which make it possible to use the ApplicationLayout as the layout for routes, previously known as views.
Change Migration

The class is moved from org.linkki.framework.state to org.linkki.framework.ui.application.

Reorganize import in the project where ApplicationConfig is used.

ApplicationConfig#createApplicationLayout has been removed.

Move the implementation of #createApplicationLayout to the implementation of ApplicationLayout.

Name, version, description and copyright are moved to ApplicationInfo.

  • Create a new implementation of ApplicationInfo that contains the existing implementation for name, version, description and copyright.
    If additional information needs to be stored to be displayed in the header and footer, follow the migration steps for the ApplicationHeader and the ApplicationFooter.

  • Return a instance of the new implementation in `ApplicationConfig#getApplicationInfo.

The return type ApplicationHeaderDefinition of ApplicationConfig#ApplicationHeaderDefinition has changed:

  • Until now: creates a ApplicationHeader from an ApplicationMenu

  • New: creates an ApplicationHeader from an ApplicationInfo and a sequence of ApplicationMenuItemDefinitions.

  • If getHeaderDefinition was not overridden, no migration is needed.

  • If a custom implementation of ApplicationHeader was used, consult migration steps of the ApplicationHeader.

ApplicationConfig#ApplicationFooterDefinition has changed: * Until now: creates a ApplicationFooter from an ApplicationConfig * New: creates an ApplicationFooter from an ApplicationInfo

  • If getFooterDefinition was not overridden, no migration is needed.

  • If a custom implementation of ApplicationFooter was used, consult migration steps of the ApplicationFooter.


If no custom implementation of ApplicationHeader is used, no migration is needed.
In custom implementations:

Change Migration

ApplicationHeader#addUserMenu The user menu has been completely removed. This has to be implemented by yourself.

If you want to create a user menu, then overwrite ApplicationHeader#createRightMenuBar and add a MenuItem to the MenuBar

ApplicationHeader uses an ApplicationInfo and Sequence<ApplicationMenuItem> as arguments instead of ApplicationMenu.

Adjust the constructor and the super constructor call within with the new parameters.

ApplicationHeader now extends Composite<HorizontalLayout> instead of HorizontalLayout directly.

Whenever components are added, add the components to getContent() instead.

ApplicationInfoPmo uses ApplicationInfo as constructor argument instead of ApplicationConfig

In a custom ApplicationInfoPmo implementation is used:

  • In subclasses of ApplicationInfoPmo, adjust the constructor and the super constructor call within with the new parameters.

  • If ApplicationHeader#getApplicationInfo, change the return type to the custom subclass.

  • In ApplicationHeader#createApplicationInfoPmo, use the adjusted constructor with #getApplicationInfo.

ApplicationHeader#addRightComponents() visibility has changed from protected to private due to deprecation.

In the subclasses, override and move the code to addRightComponents(HorizontalLayout).

API of ApplicationMenuItemDefinition has changed, resulting in changes in getMenuItemDefinitions.

See migration steps of the ApplicationMenuItemDefinition.


The API of ApplicationMenuItemDefinition is completely reworked. Instead of extending the class for customization, the new API is intended to be easily composable for customization.

Change Migration

Argument position is removed from the constructor as it was not necessary.

Remove the attribute. To ensure the position of the menu item, make sure to put it at the right position in ApplicationConfig#getMenuItemDefinitions.

getName is removed.

Pass the name to the constructor directly instead.

The methods createItem and internalCreateItem are removed. Instead, new constructors are introduced:

  • with name and a Handler that is executed on click.

  • with name and a list of ApplicationMenuItemDefinitions as sub-menu items

  • with name and a URL String to navigate to on click. This method handles both external and internal links.

  • with name and a Class to navigate to. The given class should be a route component.

Use the existing implementation of internalCreateItem and createItem to directly create new instances of ApplicationMenuItemDefinition. A few examples are provided below.

The following code snippet shows a simple ApplicationMenuItem before and after migration:

Table 1. Migration of a simple ApplicationMenuItem
Before migration After migration
    public Sequence<ApplicationMenuItemDefinition> getMenuItemDefinitions() {
        return Sequence.of(
                           new ApplicationMenuItemDefinition("Playground", 0) {

                               protected MenuItem internalCreateItem(ApplicationMenu menu) {
                                   return menu.addItem(getName(), item -> LinkkiUi.getCurrentNavigator()

                           new ApplicationMenuItemDefinition("Playground", "playground", TestScenarioView.class)

For more complex menu items that used to extend from ApplicationMenuItem, consider create menu item definitions directly instead of inheriting. If the implementation need to be customizable, it can be easily done by composing ApplicationMenuItems.
Here is an example of a more complex ApplicationMenuItem. This menu item contains one sub-menu item with caption "New". If only one sub-sub-menu item is given, the sub-menu-item shows a notification on click (caption → New). If multiple sub-sub-menu items are given, those are shown in the sub-menu item (caption → New → sub-sub-menu items).

Table 2. Migration of a complex ApplicationMenuItem
Before migration After migration
public class CustomMenuItemDefinition extends ApplicationMenuItemDefinition {

    private List<MySubSubMenuItem> subSubMenuItems;

    public CustomMenuItemDefinition(String name, int position, List<MySubSubMenuItem> subSubMenuItems) {
        super(name, position);
        this.subSubMenuItems = subSubMenuItems;

    private List<MySubSubMenuItem> getSubSubMenuItems() {
        return subSubMenuItems;

    protected MenuItem internalCreateItem(ApplicationMenu menu) {
        MenuItem newApplicationMenuItem = menu.addItem(getName(), null, null);

        MenuItem newMenuItem = newApplicationMenuItem.addItem("New", null);

        if (getSubSubMenuItems().size() > 1) {
            getSubSubMenuItems().forEach(i -> newMenuItem.addItem(i.getName(), i.getCommand()));
        } else if (getSubSubMenuItems().size() == 1) {
            MySubSubMenuItem onlyItem = getSubSubMenuItems().get(0);
            newMenuItem.addItem(onlyItem.getName(), onlyItem.getCommand());
        return newApplicationMenuItem;

    public static class MySubSubMenuItem {
        private String name;

        public MySubSubMenuItem(String name) {
   = name;

        public String getName() {
            return name;

        public Command getCommand() {
            return i ->;
public class CustomMenuItemDefinitionCreator {

    public static ApplicationMenuItemDefinition createMenuItem(String name,
            String id,
            List<MySubSubMenuItem> subSubMenuItems) {
        return new ApplicationMenuItemDefinition(name, id,

    private static ApplicationMenuItemDefinition createSubMenuItem(List<MySubSubMenuItem> subSubMenuItems) {
        String subMenuName = "New";
        if (subSubMenuItems.size() > 1) {
            return new ApplicationMenuItemDefinition(subMenuName, "new",
                            .map(i -> new ApplicationMenuItemDefinition(i.getName(), i.getId(),
                                    () -> i.getCommand().apply()))
        } else if (subSubMenuItems.size() == 1) {
            MySubSubMenuItem onlyItem = subSubMenuItems.get(0);
            return new ApplicationMenuItemDefinition(onlyItem.getName(), onlyItem.getId(), onlyItem.getCommand());
        } else {
            return new ApplicationMenuItemDefinition(subMenuName, "new", Handler.NOP_HANDLER);

    public static class MySubSubMenuItem {
        private String name;
        private String id;

        public MySubSubMenuItem(String name, String id) {
   = name;
   = id;

        public String getName() {
            return name;

        public String getId() {
            return id;

        public Handler getCommand() {
            return () ->;

If no custom implementation of ApplicationFooter is used, no migration is needed.
In custom implementations:

Change Migration

ApplicationFooter uses an ApplicationInfo as argument instead of ApplicationConfig.

Adjust the constructor and the super constructor call within with the new parameters.

ApplicationFooter now extends Composite<HorizontalLayout> instead of HorizontalLayout directly.

Whenever components are added, add the components to getContent() instead.

ApplicationFooter#buildText uses ApplicationInfo as argument instead of ApplicationConfig.

Adjust the implementation of #buildText to use the given ApplicationInfo. If a custom ApplicationInfo is used, the argument may need to be casted to the custom class.

ApplicationHeader#addRightComponents() visibility has changed from protected to private due to deprecation.

In subclasses, override and move the code to addRightComponents(HorizontalLayout).

Visibility of ApplicationFooter#init is reduced to protected.

This method should not be called externally anymore. Remove external calls.

Change Migration

View interface is no longer necessary.

Remove the interface in all implementations.

Views must be annotated with @Route.

Annotate all views with @Route. If the view was previously annotated with CDIView or @SpringView, you may now need to specify the route explicitly.

Other changes
  • ApplicationNavigator has been removed


  • linkki dialogs are now extending Composite<Dialog> instead of Window.

  • MessageUiComponents are now displayed using Grid instead of Table

TabSheetArea has been replaced by LinkkiTabLayout

  • TabSheetArea#addTabLinkkiTabLayout#addTabSheet

  • TabSheetArea#createContent to add tabs using TabSheetArea#addTab has been removed. Instead, LinkkiTabLayout#addTabSheet should be used

  • TabSheetArea#updateContent has been removed.

SidebarLayout has been replaced by LinkkiTabLayout
Before migration After migration

SidebarLayout sidebarLayout = new SidebarLayout();

LinkkiTabLayout linkkiTabLayout = LinkkiTabLayout.newSidebarLayout();
















LinkkiTabLayout# getTabSheets





Tab visibility can be controlled using LinkkiTabSheetBuilder#visibleWhen and LinkkiTabLayout#updateSheetVisibility.

SidebarSheet has been replaced by LinkkiTabSheet

For performance reasons, components must be created via a supplier and can no longer be set directly.

Before migration After migration

SidebarSheet sidebarSheet = SidebarSheet(id, icon, name, contentSupplier, uiUpdateObserver);*

LinkkiTabSheet linkkiTabSheet = LinkkiTabSheet.builder(id) .caption(icon.create()) .description(name) .content(contentSupplier) .build();





*UiUpdateObserver has been replaced by AfterTabSelectedObserver. For more information see AfterTabSelectedObserver.


  • styleNames() has been replaced by variants(), which now returns an Array of ButtonVariant

  • shortcutKeyCode now returns String[]. Commonly used KeyCodes in linkki are defined in KeyCode. An overview of all supported keys can be found in Vaadin’s Key class.

  • shortcutModifierKeys now returns an Array of KeyModifier. For a list of available modifiers, see Vaadin’s enum class KeyModifier.


The date format of a @UIDateField is now controlled by DateFormats and the locale, the attribute dateFormat has been removed.


Attribute rows does not exists anymore. It has been replaced by height, which returns a String, specifying the height of the component using a number and a CSS unit, for example "5em".


The usage of the ContentMode for @UILabel in Vaadin 8 has changed. Instead of setting Label#setContentMode to true when using Label#htmlContent , you can use getElement().setProperty("innerHTML", "<b>My html content</b>") of the corresponding component, as now used in linkki LabelValueAspectDefinition.


Following *Label methods have been removed, as Label has been replaced by LinkkiText:

  • newLabelWidth100(parent, caption)

  • sizedLabel(parent, caption, contentMode)

  • sizedLabel(parent, caption)

  • newLabelWidthUndefined(parent, caption)

  • labelIcon(parent, fontIcon)

  • newEmptyLabel(layout)

  • newLabelIcon(fontIcon)

  • newLabelFullWidth(caption, contentMode)

  • newLabelFullWidth(caption)

  • newLabelUndefinedWidth(caption, contentMode)

  • newLabelUndefinedWidth(caption)


The setIcon method for Component has been deleted. It is only available for @UIButton and components, that are implementing the HasIcon interface, like the ones created by @UILabel and @UILink.

Resource / Icon

com.vaadin.server.Resource for icons in Vaadin 8 has been replaced by VaadinIcon#create

Tables have been replaced by Grids

In Vaadin 23, Table has been replaced by Grid

  • PmoBasedTablefactory has been replaced by GridComponentCreator

  • @UITableColumn: expandRatio replaced by flexGrow. The flexGrow specifies what amount of the available space inside the table the column should take up and grow (if resized), proportionally to the other columns. If flexGrow is set to 0, the column has a fixed width.


In Vaadin 8, there used to be four types of notification: humanized, warning, error and tray. This categorization does not exist anymore. To display notifications based on the severity, linkki provides a new class NotificationUtil. See the documentation on notifications for more detail.

Aspect annotations


The @BindIcon annotation is available for @UIButton and all components, that are implementing the HasIcon interface. At the moment, these are @UILabel and @UILink. Using htmlContent of the @UILabel will override any icon.


HTML content is no longer support for tooltips.



It is recommended to provide an implementation of I18NProvider as following, to ensure UI#getLocale returns the best matching Locale corresponding to the user agent’s preferences (i.e. the Accept-Language header). If no I18NProvider is available, the default JVM Locale is used.

public class MyI18NProvider implements I18NProvider {


In general, the the Sass precompiler is no longer used by default, which means that any existing styling code that utilized SCSS functionalities needs to be adjusted to work in CSS.

In addition, all applications need to either apply the "linkki" theme or a theme that extends from it to make sure that the linkki components work properly. Detailed instructions are provided in the chapter "Styling".

As the generated HTML has changed drastically, existing styling customizations have to be checked for necessity and, if still necessary, adjusted for the new DOM structure. As Vaadin components are now mostly web components, existing styles that apply to Vaadin components most likely do not work any more. Consult the Vaadin documentation on component styling to adjust the selectors and the documentation on theming for how to include them in the custom theme.


In Vaadin 8, sections created with @UISection have a dynamic label width by default, i.e. the labels are as long as the longest label. For @UIFormSection, LinkkiTheme offered style names to set a fixed width.

This behavior has changed with Vaadin 23. All sections now have a fixed label width by default. It is not possible to have dynamic label width anymore. To set a custom label width, you can use ComponentStyles.setFormItemLabelWidth(Component, String). This replaces the class names LinkkiTheme.LABEL_FIXED_WIDTH and LinkkiTheme.LABEL_FIXED_WIDTH_LONG.

Table 3. Migration of a page with longer fixed width
Before migration After migration
public class MyPage extends AbstractPAge {
    public final void createContent() {
public class MyPage extends AbstractPAge {
    public final void createContent() {
       ComponentStyles.setFormItemLabelWidth(this, "12em");

To apply a custom label width to all sections inside a Component, the surrounding layout such as AbstractPage can be passed as the Component-Parameter.

For more information see ComponentStyles.