eBook – Guide Spring Cloud – NPI EA (cat=Spring Cloud)
announcement - icon

Let's get started with a Microservice Architecture with Spring Cloud:

>> Join Pro and download the eBook

eBook – Mockito – NPI EA (tag = Mockito)
announcement - icon

Mocking is an essential part of unit testing, and the Mockito library makes it easy to write clean and intuitive unit tests for your Java code.

Get started with mocking and improve your application tests using our Mockito guide:

Download the eBook

eBook – Reactive – NPI EA (cat=Reactive)
announcement - icon

Spring 5 added support for reactive programming with the Spring WebFlux module, which has been improved upon ever since. Get started with the Reactor project basics and reactive programming in Spring Boot:

>> Join Pro and download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Jackson – NPI EA (cat=Jackson)
announcement - icon

Do JSON right with Jackson

Download the E-book

eBook – HTTP Client – NPI EA (cat=Http Client-Side)
announcement - icon

Get the most out of the Apache HTTP Client

Download the E-book

eBook – Maven – NPI EA (cat = Maven)
announcement - icon

Get Started with Apache Maven:

Download the E-book

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

eBook – RwS – NPI EA (cat=Spring MVC)
announcement - icon

Building a REST API with Spring?

Download the E-book

Course – LS – NPI EA (cat=Jackson)
announcement - icon

Get started with Spring and Spring Boot, through the Learn Spring course:

>> LEARN SPRING
Course – RWSB – NPI EA (cat=REST)
announcement - icon

Explore Spring Boot 3 and Spring 6 in-depth through building a full REST API with the framework:

>> The New “REST With Spring Boot”

Course – LSS – NPI EA (cat=Spring Security)
announcement - icon

Yes, Spring Security can be complex, from the more advanced functionality within the Core to the deep OAuth support in the framework.

I built the security material as two full courses - Core and OAuth, to get practical with these more complex scenarios. We explore when and how to use each feature and code through it on the backing project.

You can explore the course here:

>> Learn Spring Security

Course – LSD – NPI EA (tag=Spring Data JPA)
announcement - icon

Spring Data JPA is a great way to handle the complexity of JPA with the powerful simplicity of Spring Boot.

Get started with Spring Data JPA through the guided reference course:

>> CHECK OUT THE COURSE

Partner – Moderne – NPI EA (cat=Spring Boot)
announcement - icon

Refactor Java code safely — and automatically — with OpenRewrite.

Refactoring big codebases by hand is slow, risky, and easy to put off. That’s where OpenRewrite comes in. The open-source framework for large-scale, automated code transformations helps teams modernize safely and consistently.

Each month, the creators and maintainers of OpenRewrite at Moderne run live, hands-on training sessions — one for newcomers and one for experienced users. You’ll see how recipes work, how to apply them across projects, and how to modernize code with confidence.

Join the next session, bring your questions, and learn how to automate the kind of work that usually eats your sprint time.

Course – Spring Sale 2026 – NPI EA (cat= Baeldung)
announcement - icon

Yes, we're now running our Spring Sale. All Courses are 30% off until 31st March, 2026

>> EXPLORE ACCESS NOW

Partner – Diagrid – NPI EA (cat= Testing)
announcement - icon

In distributed systems, managing multi-step processes (e.g., validating a driver, calculating fares, notifying users) can be difficult. We need to manage state, scattered retry logic, and maintain context when services fail.

Dapr Workflows solves this via Durable Execution which includes automatic state persistence, replaying workflows after failures and built-in resilience through retries, timeouts and error handling.

In this tutorial, we'll see how to orchestrate a multi-step flow for a ride-hailing application by integrating Dapr Workflows and Spring Boot:

>> Dapr Workflows With PubSub

Course – Spring Sale 2026 – NPI (cat=Baeldung)
announcement - icon

Yes, we're now running our Spring Sale. All Courses are 30% off until 31st March, 2026

>> EXPLORE ACCESS NOW

1. Overview

Vaadin Flow is a server-side Java framework for creating web user interfaces.

In this tutorial, we’ll explore how to build a Vaadin Flow based CRUD UI for a Spring Boot based backend. For an introduction to Vaadin Flow refer to this tutorial.

2. Setup

Let’s start by adding Maven dependencies to a standard Spring Boot application:

<dependency>
    <groupId>com.vaadin</groupId>
    <artifactId>vaadin-spring-boot-starter</artifactId>
</dependency>

Vaadin is also a recognized dependency by the Spring Initializr.

We can also add the Vaadin Bill of Materials manually to the project if we’re not using Spring Initializr:

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>com.vaadin</groupId>
            <artifactId>vaadin-bom</artifactId>
            <version>24.3.8</version> <!-- check latest version -->
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

3. Backend Service

We’ll use an Employee entity with firstName and lastName properties to perform CRUD operations on it. We also define validation rules for the properties so that we can enforce them in the UI we are building:

@Entity
public class Employee {
    @Id
    @GeneratedValue
    private Long id;

    @Size(min = 2, message = "Must have at least 2 characters")
    private String firstName;

    @Size(min = 2, message = "Must have at least 2 characters")
    private String lastName;

    public Employee() {
    }
    
    // Getters and setters
}

Here’s the simple, corresponding Spring Data repository to manage the CRUD operations:

public interface EmployeeRepository extends JpaRepository<Employee, Long> {
    List<Employee> findByLastNameStartsWithIgnoreCase(String lastName);
}

We declare a query method findByLastNameStartsWithIgnoreCase on the EmployeeRepository interface. It will return the list of employees for the given last name.

Let’s also pre-populate the DB with a few sample employees:

@Bean
public CommandLineRunner loadData(EmployeeRepository repository) {
    return (args) -> {
        repository.save(new Employee("Bill", "Gates"));
        repository.save(new Employee("Mark", "Zuckerberg"));
        repository.save(new Employee("Sundar", "Pichai"));
        repository.save(new Employee("Jeff", "Bezos"));
    };
}

4. Vaadin Flow UI

The application we are building will feature a filterable data grid displaying employees and a form for editing and creating employees. We’ll begin by creating the form as a custom component, then create the main layout using standard Vaadin components and our custom form component:

A data grid displaying employees. The first employee is selected and displayed in a form below.

4.1. The EmployeeEditor Form Component

The EmployeeEditor is a custom component that we create by composing existing Vaadin components and defining a custom API.

EmployeeForm uses a VerticalLayout as its base. VerticalLayout is a Layout, which shows the subcomponents in the order of their addition (vertically). By extending Composite<VerticalLayout> instead of VerticalLayout, we don’t expose all the methods of VerticalLayout, giving us full control over the API of our component.

Let’s begin by defining the API of our component so that the user can set the Employee they want to edit, and subscribe to save, cancel, and delete events:

public class EmployeeEditor extends Composite<VerticalLayout> {
    public interface SaveListener {
        void onSave(Employee employee);
    }

    public interface DeleteListener {
        void onDelete(Employee employee);
    }

    public interface CancelListener {
        void onCancel();
    }

    private Employee employee;

    private SaveListener saveListener;
    private DeleteListener deleteListener;
    private CancelListener cancelListener;

    private final Binder<Employee> binder = new BeanValidationBinder<>(Employee.class);

    public void setEmployee(Employee employee) {
        this.employee = employee;
        binder.readBean(employee);
    }

    // Getters and setters
}

The setEmployee method saves a reference to the current employee and reads the bean into the Vaadin BeanValidationBinder so that we can bind it to the input fields of our form and validate them.

Next, we construct the UI of the component using TextField and Button components. We use the binder to map input fields to fields on the model. Reading the bean into the binder means that whenever we call setEmployee, the input field values get updated.

We add all the components to the VerticalLayout that is the root of our composition:

public EmployeeEditor() {
    var firstName = new TextField("First name");
    var lastName = new TextField("Last name");

    var save = new Button("Save", VaadinIcon.CHECK.create());
    var cancel = new Button("Cancel");
    var delete = new Button("Delete", VaadinIcon.TRASH.create());

    binder.forField(firstName).bind("firstName");
    binder.forField(lastName).bind("lastName");

    save.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
    save.addClickListener(e -> save());
    save.addClickShortcut(Key.ENTER);

    delete.addThemeVariants(ButtonVariant.LUMO_ERROR);
    delete.addClickListener(e -> deleteListener.onDelete(employee));

    cancel.addClickListener(e -> cancelListener.onCancel());

    getContent().add(firstName, lastName, new HorizontalLayout(save, cancel, delete));
}

Finally, we handle the save button click by reading the input field values into a new Employee object if field validations pass. We avoid saving the values to the original object as it is bound to other parts of the UI, and we want to avoid creating side effects when using our component. Once we have the updated employee, we call the saveListener to notify the parent component.

4.2. The Main View

The EmployeesView class is the entry point for our application. The @Route(“”) annotation tells Vaadin Flow to map the component to the root path when the application starts.

We extend VerticalLayout as the base for our view, and then construct the UI using standard Vaadin components and the custom EmployeeEditor we created.

Vaadin Flow Views are Spring beans, which means we can auto-wire EmployeeRepository into our view through the constructor:

@Route("")
public class EmployeesView extends VerticalLayout {
    private final EmployeeRepository employeeRepository;

    private final TextField filter;
    private final Grid<Employee> grid;
    private final EmployeeEditor editor;

    public EmployeesView(EmployeeRepository repo) {
        employeeRepository = repo;

        // Create components
        var addButton = new Button("New employee", VaadinIcon.PLUS.create());
        filter = new TextField();
        grid = new Grid<>(Employee.class);
        editor = new EmployeeEditor();

        // Compose layout
        var actionsLayout = new HorizontalLayout(filter, addButton);
        add(actionsLayout, grid, editor);
    }
}

4.3. Configuring Components and Data

Next, we create two helper methods: one for updating the grid based on a search string, and one for handling employee selection:

private void updateEmployees(String filterText) {
    if (filterText.isEmpty()) {
        grid.setItems(employeeRepository.findAll());
    } else {
        grid.setItems(employeeRepository.findByLastNameStartsWithIgnoreCase(filterText));
    }
}

private void editEmployee(Employee employee) {
    editor.setEmployee(employee);

    if (employee != null) {
        editor.setVisible(true);
    } else {
        // Deselect grid
        grid.asSingleSelect().setValue(null);
        editor.setVisible(false);
    }
}

Finally, we configure the components:

  • We configure the EmployeeEditor component to be hidden initially and define listeners for handling the save, delete, and cancel events.
  • We set ValueChangeMode.LAZY on the filter TextField to call updateEmployees with the filter value whenever the user stops typing.
  • We define a fixed 200px height for the grid and call editEmployee whenever the selected row changes.
public EmployeesView(EmployeeRepository repo) {
    // Component creation code from above

    // Configure components
    configureEditor();

    addButton.addClickListener(e -> editEmployee(new Employee()));

    filter.setPlaceholder("Filter by last name");
    filter.setValueChangeMode(ValueChangeMode.EAGER);
    filter.addValueChangeListener(e -> updateEmployees(e.getValue()));

    grid.setHeight("200px");
    grid.asSingleSelect().addValueChangeListener(e -> editEmployee(e.getValue()));

    // List customers
    updateEmployees("");
}

private void configureEditor() {
    editor.setVisible(false);

    editor.setSaveListener(employee -> {
        var saved = employeeRepository.save(employee);
        updateEmployees(filter.getValue());
        editor.setEmployee(null);
        grid.asSingleSelect().setValue(saved);
    });

    editor.setDeleteListener(employee -> {
        employeeRepository.delete(employee);
        updateEmployees(filter.getValue());
        editEmployee(null);
    });

    editor.setCancelListener(() -> {
        editEmployee(null);
    });
}

4.4. Running the Application

We can start the application with Maven:

mvn spring-boot:run

The application is now running on localhost:8080:

A data grid displaying employees. The first employee is selected and displayed in a form below.

5. Conclusion

In this article, we wrote a full-featured CRUD UI application using Spring Boot and Spring Data JPA for persistence.

The code backing this article is available on GitHub. Once you're logged in as a Baeldung Pro Member, start learning and coding on the project.
Baeldung Pro – NPI EA (cat = Baeldung)
announcement - icon

Baeldung Pro comes with both absolutely No-Ads as well as finally with Dark Mode, for a clean learning experience:

>> Explore a clean Baeldung

Once the early-adopter seats are all used, the price will go up and stay at $33/year.

eBook – HTTP Client – NPI EA (cat=HTTP Client-Side)
announcement - icon

The Apache HTTP Client is a very robust library, suitable for both simple and advanced use cases when testing HTTP endpoints. Check out our guide covering basic request and response handling, as well as security, cookies, timeouts, and more:

>> Download the eBook

eBook – Java Concurrency – NPI EA (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

Course – LS – NPI EA (cat=REST)

announcement - icon

Get started with Spring Boot and with core Spring, through the Learn Spring course:

>> CHECK OUT THE COURSE

Partner – Moderne – NPI EA (tag=Refactoring)
announcement - icon

Modern Java teams move fast — but codebases don’t always keep up. Frameworks change, dependencies drift, and tech debt builds until it starts to drag on delivery. OpenRewrite was built to fix that: an open-source refactoring engine that automates repetitive code changes while keeping developer intent intact.

The monthly training series, led by the creators and maintainers of OpenRewrite at Moderne, walks through real-world migrations and modernization patterns. Whether you’re new to recipes or ready to write your own, you’ll learn practical ways to refactor safely and at scale.

If you’ve ever wished refactoring felt as natural — and as fast — as writing code, this is a good place to start.

Course – Spring Sale 2026 – NPI EA (cat= Baeldung)
announcement - icon

Yes, we're now running our Spring Sale. All Courses are 30% off until 31st March, 2026

>> EXPLORE ACCESS NOW

Course – Spring Sale 2026 – NPI (All)
announcement - icon

Yes, we're now running our Spring Sale. All Courses are 30% off until 31st March, 2026

>> EXPLORE ACCESS NOW

eBook Jackson – NPI EA – 3 (cat = Jackson)