Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

Background tasks and Push

Normally a Vaadin server changes the screen only in response to a user action: a button click brings a response with changes. But some operations are long: a report takes ten seconds to compute, and the interface must not freeze all that time. The solution is to do the work in a background thread and let the server send the result to the browser by itself. Sending without a request from the browser is called push.

Turning Push on

Add the annotation to the main class:

// src/main/java/com/example/blog/Application.java (fragment)
@SpringBootApplication
@StyleSheet(Lumo.STYLESHEET)
@StyleSheet(Lumo.UTILITY_STYLESHEET)
@StyleSheet("styles.css")
@Push
public class Application implements AppShellConfigurator {

The import is com.vaadin.flow.component.page.Push. Now the browser keeps a permanent connection (WebSocket) to the server, and the server can send changes at any moment.

A long operation in the service

As an example, let us count the words in all posts with an artificial 2-second delay:

// src/main/java/com/example/blog/PostService.java (new methods)
public long countWords() {
    sleep(2000);
    return repository.findAll().stream()
            .map(Post::getContent)
            .filter(text -> text != null && !text.isBlank())
            .mapToLong(text -> text.trim().split("\\s+").length)
            .sum();
}
 
private static void sleep(long millis) {
    try {
        Thread.sleep(millis);
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    }
}

The stats view

// src/main/java/com/example/blog/views/StatsView.java
package com.example.blog.views;
 
import com.example.blog.PostService;
import com.vaadin.flow.component.UI;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.html.Span;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import jakarta.annotation.security.PermitAll;
import java.util.concurrent.CompletableFuture;
 
@Route("stats")
@PageTitle("Stats")
@PermitAll
public class StatsView extends VerticalLayout {
 
    private final Span result = new Span("Press the button to count words");
 
    public StatsView(PostService service) {
        Button count = new Button("Count words");
        count.addClickListener(event -> {
            UI ui = UI.getCurrent();
            count.setEnabled(false);
            result.setText("Counting...");
            CompletableFuture.supplyAsync(service::countWords)
                    .thenAccept(words -> ui.access(() -> {
                        result.setText("Words in all posts: " + words);
                        count.setEnabled(true);
                    }));
        });
        add(count, result);
    }
}

Add a "Stats" item to the MainLayout menu and open /stats. After a click the button becomes inactive right away, "Counting..." appears, and 2 seconds later the text changes to "Words in all posts: 9" by itself — with no new clicks.

What matters here

StepWhy
UI ui = UI.getCurrent()Remember the tab while still in the listener thread: in the background UI.getCurrent() returns null
CompletableFuture.supplyAsync(...)The long work goes to another thread, and the listener finishes at once
ui.access(() -> ...)Locks the user's session, changes components and sends the changes via push

While counting runs, the user can use the menu and other views. If they leave the page, the changes simply go nowhere.

Common mistake

A component is changed right in the background thread, without ui.access:

// src/main/java/com/example/blog/views/StatsView.java (wrong)
CompletableFuture.supplyAsync(service::countWords)
        .thenAccept(words -> result.setText("Words: " + words));

If the JVM runs with assert checks on (tests run this way), you see right away:

java.lang.AssertionError: Cannot access state in VaadinSession or UI without locking the session.

Without the checks there may be no error, but the change does not reach the browser or breaks the session state. Change components from another thread only inside ui.access(...). And without @Push even correct code shows the result only after the user's next action.

Thanks for reading!

Donate to the site — we work hard for you.