PWA jumpstart

A practical explanation of Progressive Web Apps

Sven Ruppert
Progressive Web Apps

© Shutterstock / Flerkvit vector

The ongoing debate over what appliance form is best suited for which app carries on. Should an app be a desktop application or would it work better if it were a web application? And developing for the mobile sector just adds complexity and heats up the debate. However, there is one type of application that everyone agrees on: Progressive Web Apps.

What exactly are progressive web apps (PWA)? What are their defining traits and advantages?  With the help of a few practical examples, we’ll get to know the building blocks of PWAs and how you can make one for yourself.

A quick classification on terms

To start off, I would like to briefly outline the various groups of applications that we will be discussing. This list is obviously incomplete and there are still more things that we could explore, but this is a good starting point.

For the later analysis I use the following classifications:

  • Native Desktop Applications (Windows/OS X/Linux)
  • Web Apps for Desktops (Chrome/Firefox/Opera/…)
  • Web Apps for Mobile Devices (Android/iOS/WindowsMobile/…)
  • Native Mobile Applications (Android/iOS/WindowsMobile/…)

If you take a closer look at these points, you can see how the list leads from one extreme to another, so to speak. There are two versions of Native Apps, which means that the respective development environments must be available with their corresponding specifications. This also limits the range of technologies that a developer can use, but usually such applications are optimally integrated into their respective carrier systems. Still, if this is a necessary criterion then we must follow through right down to the very end.

It’s not always necessary to achieve such a high rate of integration. Quite a lot of things can be developed as web applications. I will not go into any more detail on the differences of the distribution of an application. But you probably can imagine how you could save time and money on infrastructure and processes, if you could offer applications via a browser. However, because nothing in life is free, this approach still involves a certain degree of complexity in other areas.

World of browsers

The world of browsers may look simple at first glance, you can see the cracks if you look closely enough. Not all browsers are equal and their performance can vary wildly, especially if they have to interact with their respective carrier system. There are a lot of options available: browser choice in the consumer sector is more liberal and can be tailored to individual needs. This is in stark contrast to the corporate world with its often extremely regulated processes and centralized IT choices.

However, today we want to focus on the difference between the development of web applications for mobile devices and the desktop applications. As one can easily imagine, screen size and type of interaction are different. In addition, mobile devices are connected in a rapidly changing bandwidth range. Location matters: it makes a big difference whether you are in the middle of a city or traveling by train across the country.

Progressive web apps

In order to get the differences under control and to cope with both desktop and mobile environments without having to develop at least two versions of the application,here are three technologies that are generally agreed upon agreed on by most developers:

  • HTML5
  • CSS3
  • JavaScript

Web applications become practically useful and are able to adapt to their targeted devices thanks to these three technologies. However, later on I will explain that there is another way to make sure everything works with considerable results.


There is at least one basic condition for using web apps: the use of HTTPS. If a connection is not made via localhost, HTTPS is required. This means that it is easy to develop locally, but HTTPS comes into play when it is used in your own network, like if the CI server is to test the application. Fortunately, there are the things like

Optional elements

Optional means something that is desirable but not mandatory. In PWAs, the purpose of these optional elements is to achieve higher integration into the carrier system without creating compulsive hurdles. For example, a PWA should have offline capability and cope with partial loss of internet connection. To protect the limited bandwidth, caching is provided and communication can be initiated on request from the server. This refers to a push of information from the server to the application.

These functions are implemented using ServiceWorkerServiceWorker is a proxy implemented in JavaScript that is switched between the application and the server. This provides offline capability. The push- function is also implemented using ServiceWorker. Here we enter a field that shows up very differently, depending on which browser in which version is used on which operating system. The good news is that all major browser manufacturers have now agreed to support these features. In this example, we will only look at Google Chrome as a reference platform.

An example

To start, we will create a small Vaadin application and then see how we can extend it to a progressive web app with a few adjustments. We will also look at how to test which PWA criteria an existing web app complies with.

Let’s start with our application.

The application itself is quite simple and consists of a table with a list of data. The table can be filtered and new data records can be created.

The details of a data set, as well as the mask for entering new data sets, consist of only a few attributes and three buttons.

If a data record is selected from the table, the input mask with the detailed information about the data record appears on the right-hand side.

The project can be found on GitHub here.

From the web app to PWA

The Lighthouse tool from Google is quite helpful for testing the state of the web application when creating PWA criteria.

In order to make the first attempts with the tool, you can search for audit in Chrome under Other Tools | Developer Tools . The start page of the Lighthouse tool appears and you can now start an audit for the currently displayed page in your browser. The following illustration shows how this can look like (recording on 11.02.2018). We can see that website reaches only 55 out of 100 points, although it is called Wikipedia for reference for Progressive Web Apps. In order to determine what effort is required, one should first critically examine one’s own expectations.

After the tool has completed the analysis, a report is displayed. Among other things, it describes the criteria for progressive web apps that our application does not meet.

Vaadin app without PWA adjustments

Let’s now move on to our basic application. If we test the Vaadin application without any modifications, we get the following result. The first test uses IP to access the locally running web app. Here we reach 18 out of 100 points.

The second test uses the same localyl running Instance of the Web-App, but accesses the app during the test using IP Here we reach 27 of 100 points. The reason for this is that access via does not necessarily require HTTPS. It is assumed indirectly that this is a development environment or that access is secured. In order to ensure the comparability of the tests, it is therefore important that they are always carried out in the same way.

Adjustments to the Vaadin App

Now, let’s take a look at some of the changes that need to be made to a Vaadin application in order for it to be considered a PWA.

Delivery of static files

When converting to a progressive web app, we will have to deliver some static files. There are different ways to do this. In this example, we will use another servlet for the delivery. This servlet is not derived from VaadinServlet, but directly from HttpServlet. The only task of the servlet is to provide a certain amount of files on request, including the correct mime type.

It does not matter whether it is a GET or POST request. Both are treated in the same way.

protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
  serveDataFile(req, resp);
protected void doPost(HttpServletRequest req, HttpServletResponse resp) {
  serveDataFile(req, resp);

The implementation of the serveDataFile method determines which file is to be delivered based on the parameters passed. Since the amount of files is very manageable, we will simply build a direct mapping. The mapping consists of a pair of active URLs on which it is listened to file.

@WebServlet(asyncSupported = true,
            urlPatterns = {
                SLASH + SERVICE_WORKER,
                SLASH + MANIFEST,
                SLASH + VAADIN + SLASH + APP_JS,
public class PWAServlet extends HttpServlet implements HasLogger {
  public static final String SLASH          = "/";
  public static final String SERVICE_WORKER = "sw.js";
  public static final String APP_JS         = "app.js";
  public static final String MANIFEST       = "manifest.json";
  public static final String VAADIN         = "VAADIN";
The implementation uses elements from the open source project Functional Reactive, located on GitHub.
private CheckedFunction<String, InputStream> asStream() {
  return PWAServlet.class::getResourceAsStream;
private CheckedBiFunction<InputStream, HttpServletResponse, Integer> write() {
  return (inputStream, httpServletResponse) 
         -> copy(inputStream, 
private void serveDataFile(HttpServletRequest request, 
                           HttpServletResponse response) {
  final String url = request.getRequestURL().toString();
  logger().info("url for = " + url);
      matchCase(() -> failure("nothing matched " + url)),
      matchCase(() -> url.contains(SERVICE_WORKER), 
                () -> success(SLASH + SERVICE_WORKER)),
      matchCase(() -> url.contains(APP_JS), 
                () -> success(SLASH + VAADIN + SLASH + APP_JS)),
      matchCase(() -> url.contains(MANIFEST), 
                () -> success(SLASH + MANIFEST))
      resourceToLoad -> {
        final Result ressourceStream 
              = asStream().apply(resourceToLoad);
        ressourceStream.ifAbsent(() -> logger().warning("resource was not available"));
        ressourceStream.ifPresent(inputStream -> {
              matchCase(() -> success("text/plain")),
              matchCase(() -> resourceToLoad.endsWith("js"), 
                        () -> success("application/javascript")),
              matchCase(() -> resourceToLoad.endsWith("json"), 
                        () -> success("application/json"))
          write().apply(inputStream, response);
      failed -> logger().warning("failed .. " + failed)


Now the given requests will be answered by this servlet. Let’s get to the files themselves.

First, let’s take a look at the manifest.json. This is a description in which various attributes of the application are defined.

  "short_name": "Java PWA",
  "name": "Vaadin PWA",
  "display": "standalone",
  "icons": [
      "src": "./images/vaadinlogo-192x192.png",
      "sizes": "192x192",
      "type": "image/png"
      "src": "./images/splashscreen-512x384.jpg",
      "sizes": "512x512",
      "type": "image/jpg"
  "start_url": "./microservice/",
  "theme_color": "#404549",
  "background_color": "#34373b"

Here are some important attributues you need to be aware of:

  • short_name: Application short name
  • name: Application name
  • long: The default language used by default
  • icons: Icons that should be used, e.g. HomeScreen
  • start_url: The application’s base URL (HTTPS)

We establish the connection to Vaadin in the class extends UI, or in the specific case in the class MyUI. The class is annotated with @Link (rel = "manifest", href = "/manifest. json").


The app.js file is the point at which the ServiceWorker is installed. As with the file manifest.json, the connection is established by annotation in the MyUI class.

if ('serviceWorker' in navigator) {
        .register('./sw.js', {scope: './'})
        .then((reg) => {
            if (reg.installing) {
                console.log('Service worker installing');
            } else if (reg.waiting) {
                console.log('Service worker installed');
            } else if ( {
                console.log('Service worker active');
        .catch((error) => {
        // Registration failed
            console.log('Registration failed with ' + error); 
    // Communicate with the service worker using MessageChannel API.
    function sendMessage(message) {
        return new Promise((resolve, reject) => {
            const messageChannel = new MessageChannel();
            messageChannel.port1.onmessage = function (event) {
                resolve(`Direct message from SW: ${}`);
                .postMessage(message, [messageChannel.port2])


Let us now come to the ServiceWorker itself. The address of this file is given indirectly in app.js line 4:  .register('./sw.js',{scope:'./'})

The ServiceWorker is the actual logic that realizes the offline functionality. You can be very creative here. In our example, it is only an absolute minimum implementation. A listing of this file is too long for it to fit here, so I refer to the sources in the git repository itself. The resources directory contains the file: sw.js.


The MyUI class is the linchpin at which everything comes together in the Vaadin application to provide rudimentary PWA functionality.

@Link(rel = "manifest", href = "./manifest.json")
              @Meta(name = "viewport", content = "width=device-width, initial-scale=1"),
              @Meta(name = "theme-color", content = "#404549"),
              @Meta(name = "description", content = "some content"),
@Title("Vaadin PWA Jumpstart")
public class MyUI extends UI implements HasLogger {

But let’s have a look at how the test now runs after these changes have been made. We reach 91 out of 100 points because we don’t use HTTPS.

Lighthouse in Docker

Let’s get back to the tool Lighthouse. The report that Lighthouse generates after the test can be exported in formats like .xml,.pdf, .html and others. Therefore, the question arises immediately how to automate the execution of this test, in order to archive the result at a suitable place and evaluate it if necessary.

This is actually pretty easy. You can run Chrome – including Lighthouse headless – in a Docker container. This means that this process can be easily outsourced and becomes part of a CI/CD line.

In order to avoid the problem with HTTPS, the test is executed locally in the application running in the Docker container. The reports themselves are saved in a directory linked to the container. Of course, the report can also be loaded to an external destination via scp or similar. For example, a web server could always offer the last version in the local network for reviews.

The Docker image is quite small and is based on an Ubuntu. Only the required elements are installed and a startup of the Vaadin application is added. A previous mvn clean install will run outside of the container. Just as well, the repository with the last SNAPSHOT can be taken as a source.

FROM ubuntu:latest
MAINTAINER Sven Ruppert <sven.ruppe[email protected]>
USER root
RUN apt-get -y update && \
  apt-get -y install --no-install-recommends \ 
  -y curl chromium-browser software-properties-common && \
  rm -fr /var/lib/apt/lists/* /tmp/* /var/tmp/*
RUN curl -sL | \
    JABBA_COMMAND="install [email protected] -o /jdk" bash
RUN curl -sL | bash -
RUN apt-get install --no-install-recommends -y nodejs && \
    rm -fr /var/lib/apt/lists/* /tmp/* /var/tmp/*
RUN npm install -g lighthouse
RUN useradd -ms /bin/bash  -g root lighthouse
USER lighthouse
RUN mkdir /home/lighthouse/report
WORKDIR /home/lighthouse/report
ENTRYPOINT nohup java -jar fat.jar & \
           lighthouse --chrome-flags="--headless --no-sandbox" ${lighthouse_url}

For convenience, I start the container with docker-compose, because I also use it later with docker-compose. Here, I refer to the JAR file to be used and the directory in which the report is to be made available after the test.

version: '3.3'
    container_name: vaadin-lighthouse
    build: _lighthouse/
          - 9222:9222
          - 8080:8080
          - ./_lighthouse/home/lighthouse/report:/home/lighthouse/report
          - ../target/helloworld-1.0.0-SNAPSHOT.jar:/home/lighthouse/report/fat.jar
      - vaadin
      - lighthouse_url=

When the test is finished, we will receive a. html file with a timestamp. In the directory ./_lighthouse/home/lighthouse/report


We have looked at what we can do to make changes to a Vaadin 8 application with PWA features. We used the open source project Lighthouse as a test tool along with Google. Since these tests are also possible in a Docker container, you can also create a CI / CD line and perform the tests automatically.

The project is located on GitHub here.

If you have any questions or comments, feel free to contact us via Twitter at the following address  @SvenRuppert or by mail to [email protected]

Happy Coding!


Sven Ruppert

Sven Ruppert is a Developer Advocate at Vaadin. In his free time, he speaks at international and national conferences, writes articles for IT magazines and for tech portals.

Find him on Twitter: @SvenRuppert

1 Comment
Inline Feedbacks
View all comments
Lenovo Support
Lenovo Support
3 years ago

Here I get all the information about the Progressive app. I am also a user of a progressive app for my mobile strategy. This information and detailing pattern is just amazing which can help anyone who wants to know about the Progressive app.