Vaadin 10 minimal project


Since a few days, I’ve started exploring Vaadin 10, the new and shiny version of Vaadin.

Vaadin 10 comes with a new library of controls, called “flow”. The old code remains functional, as long as the package of the control classes are renamed.

Surprisingly, the old approach of starting a new project with an Apache Maven archetype is not available anymore. Probably because of marketing reasons (that’s my personal speculation), getting a new project in Vaadin now requires to sign up on their web site and use the on-line wizard. The startup project is a fully fledged application, composed of two tabs. The new application has CSS, Polymer templates, HTML. The good news is that it compiles and runs from the first attempt, which is something to expect, since it has no database or other external dependencies. The bad news is the increased complexity of the “bare bones” application. How complex would it be when such a project will reach production capabilities?

So my first attempt was to simplify the existing project. I would like to have the basic No-HTML/CSS/JS layout of the previous Vaadin version.

The old App/UI/View approach has been deprecated: it is still available, but behind the scene. Instead of this, we will define “routes”, in a way similar to Express JS. Since we are in Java, the routes are annotation of Vaadin components.

Below is a full Vaadin 10 “Hello world” application.

package com.razvan;

import com.vaadin.flow.component.Text;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;

@Route(value = "")
@PageTitle("My Test page")
public class TestGUI extends VerticalLayout {
	
	public TestGUI() {
		add(new Text("Hello Vaadin 10"));
	}
}

The empty string inside the Route annotation instructs Vaadin to serve this as the root page. Calling the application with any other path will cause a “route not found” error.

The annotated class needs to be a Component class. Failing to comply will result in a silent failure of serving the desired path.

Compiling the above file requires Maven, Vaadin dependencies and Maven workflow for production. The following is the XML content; it is mainly the POM file created by the wizard, very little could be removed:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.razvan</groupId>
	<artifactId>minvaadin10</artifactId>
	<name>Minimalistic Vaadin 10 project</name>
	<version>1.0-SNAPSHOT</version>
	<packaging>war</packaging>
	<properties>
		<maven.compiler.source>10</maven.compiler.source>
		<maven.compiler.target>10</maven.compiler.target>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<failOnMissingWebXml>false</failOnMissingWebXml>
		<vaadin.version>10.0.0.rc2</vaadin.version>
		<jetty.version>9.4.11.v20180605</jetty.version>
	</properties>

	<pluginRepositories>
		<pluginRepository>
			<id>vaadin-prereleases</id>
			<url>https://maven.vaadin.com/vaadin-prereleases</url>
		</pluginRepository>
	</pluginRepositories>

	<repositories>
		<repository>
			<id>vaadin-addons</id>
			<url>http://maven.vaadin.com/vaadin-addons</url>
		</repository>
		<repository>
			<id>vaadin-prereleases</id>
			<url>https://maven.vaadin.com/vaadin-prereleases</url>
		</repository>
	</repositories>

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

	<dependencies>
		<dependency>
			<groupId>com.vaadin</groupId>
			<artifactId>vaadin-core</artifactId>
		</dependency>

		<!-- Added to provide logging output as Flow uses -->
		<!-- the unbound SLF4J no-operation (NOP) logger implementation -->
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-simple</artifactId>
		</dependency>

		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>javax.servlet-api</artifactId>
			<version>3.1.0</version>
			<scope>provided</scope>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.eclipse.jetty</groupId>
				<artifactId>jetty-maven-plugin</artifactId>
				<version>${jetty.version}</version>
			</plugin>
		</plugins>
	</build>

	<profiles>
		<profile>
			<id>productionMode</id>
			<activation>
				<property>
					<name>vaadin.productionMode</name>
				</property>
			</activation>

			<properties>
				<transpilation.output>${project.build.directory}/build</transpilation.output>
			</properties>

			<dependencies>
				<dependency>
					<groupId>com.vaadin</groupId>
					<artifactId>flow-server-production-mode</artifactId>
				</dependency>
			</dependencies>

			<build>
				<plugins>
					<plugin>
						<groupId>com.vaadin</groupId>
						<artifactId>vaadin-maven-plugin</artifactId>
						<version>${vaadin.version}</version>
						<executions>
							<execution>
								<goals>
									<goal>copy-production-files</goal>
									<goal>package-for-production</goal>
								</goals>
								<configuration>
									<transpileOutputDirectory>${transpilation.output}</transpileOutputDirectory>
								</configuration>
							</execution>
						</executions>
					</plugin>

					<plugin>
						<groupId>org.apache.maven.plugins</groupId>
						<artifactId>maven-war-plugin</artifactId>
						<version>3.1.0</version>
						<configuration>
							<webResources>
								<resource>
									<directory>${transpilation.output}</directory>
								</resource>
							</webResources>
						</configuration>
					</plugin>

					<plugin>
						<groupId>org.eclipse.jetty</groupId>
						<artifactId>jetty-maven-plugin</artifactId>
						<version>${jetty.version}</version>
						<configuration>
							<webAppConfig>
								<resourceBases>
									<resourceBase>${transpilation.output}</resourceBase>
								</resourceBases>
							</webAppConfig>
						</configuration>
					</plugin>
				</plugins>
			</build>
		</profile>
	</profiles>
</project>

Here is how it runs by calling mvn clean install jetty:run:

Hello world screenshot
Vaadin 10 - Hello world

The default profile of this project will run in development context. Compiling the project here is very fast (bye bye, GWT), but load performance is modest. The bare page requires more than 800ms to load. It seems that the project does not require running any Vaadin step, simply packaging the Vaadin servlet together with the application works like a charm. This is a great advantage for development, since waiting for the project to build is a productivity killer.

Timing the cold refresh of the hello world application  - Development
Timing the cold refresh of the hello world application - Development

However, running the production profile (using mvn clean install jetty:run -P productionMode) reduces the application load (from localhost) to 90 milliseconds.

Timing the cold refresh of the hello world application  - Production
Timing the cold refresh of the hello world application - Production

You can download my minimal startup project here, under a “free as in free beer” license.

Moving on, I’ve tested the Grid, my favorite control.

Below is a fairly simple implementation of the Grid control.

package com.razvan;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;

enum colorType{Red,Green,Blue,Yellow,White,Black};

class Record {
	private String name;
	private Integer age;
	private Double value;
	private Double value2;
	private Boolean check;
	private colorType color;
	
    // here come the ritual getters/setters	
}

class RandomRecordGenerator {
	private final static List<String> first= Arrays.asList("John","Alice","Mary","Tom","Helen","Bob","Tiffany");
	private final static List<String> last= Arrays.asList("Smith","Johnson","Ingram","Lincoln","Smithson","Brandon","Richardson");
	private Random rand = new Random();

	private String getRandom(List<String> list) {
		return list.get(rand.nextInt(list.size()));
	}
	
	public Record getRandomRecord() {
		Record r = new Record();
		char letter = (char) ((int)'A' + rand.nextInt(26));
		r.setName(getRandom(first) + " " + letter  + ". " + getRandom(last));		
		r.setAge(rand.nextInt(100) + 1);
		r.setCheck(rand.nextBoolean());
		r.setValue(rand.nextDouble()*100);
		if (rand.nextInt(10) > 3) // otherwise, value2 will remain null
			r.setValue2(rand.nextDouble()*100);
		r.setColor(colorType.values()[rand.nextInt(colorType.values().length)]);		
		return r;
	}
}

@Route(value = "")
@PageTitle("My Test page")
public class TestGUI extends VerticalLayout {

	private static Logger log =  LoggerFactory.getLogger(TestGUI.class);
	
	static List<Record> rec = new ArrayList<>();
	static {
		RandomRecordGenerator rrg = new RandomRecordGenerator();
		log.info("Starting generate data");
		for (int i = 0 ; i != 10000;i++)
			rec.add(rrg.getRandomRecord());
		log.info("Done generate data");
	}
	
	public TestGUI() {
		Grid<Record> grid = new Grid<>();
		grid.setItems(rec);
		grid.addColumn(name->name.getName()).setHeader("Name").setSortable(true);
		grid.addColumn(name->name.getAge()).setHeader("Age").setSortable(true);
		grid.addColumn(name->name.getValue()).setHeader("Value").setSortable(true);
		grid.addColumn(name->name.getValue2()).setHeader("Value2").setSortable(true);
		grid.addColumn(name->name.getCheck()).setHeader("Check").setSortable(true);
		grid.addColumn(name->name.getColor()).setHeader("Color").setSortable(true);
		grid.setSizeFull();
		grid.addSelectionListener(x->{
			if (x.getAllSelectedItems().size() > 0)
				log.info("Selection changed to: "+x.getFirstSelectedItem().get().getName());
			else
				log.info("No element selected");
			});
		add(grid);
		setSizeFull();
	}
}

The project above is available here, under a “free as in free beer” license.

The application uses a list of Record elements as data model. It will present each class member as a column in the grid. The data model is randomly initialized with a large number of records. I’ve experienced with both 10,000 and 10,000,000.

Initialization time of the application raised to 600 milliseconds, in production mode. This is still a great result, considering the past version of Vaadin. Subsequent actions take less time, under 50ms. Of course, it will be added time spent by the actually useful code you will will add behind the controls and the Internet latency.

Even when working with 10 million, the control behaves very fast (because on the client side, it is a virtual table of about 300 rows). Sorting the large dataset is slow and not scalable, since the default implementation will attempt to sort the data model on the server side. A database table with index would be the advised approach here.

In conclusion, the new Vaadin is a great tool. By eliminating the GWT, the library gained speed and simplicity. Still unexplored, the capabilities to customize the look and feel of the page by using HTML, CSS and Polymer can be helpful to integrate the existing look&feel with the application.