Vaadin Tutorial: From Spring Boot to Beautiful UI Fast

 

Transcript:

In this video I will show you how to build a beautiful UI for your Springwood app in pure Java using Vaadin. Vaadin is a Java web framework that lets you build user interfaces completely in Java. It has a huge set of components. Greets, forms, dialogues, layouts, menus, various ready interaction components such as avatars, popovers, confirmation dialogue, who you consent, and so much more. The components are ready out of the box, but they are also easily customizable. Plus, you can design UIs in Figma using Vaadin Figma libraries. The security support is also built in. So out of the box you get server site UI state CSRF protection JSR-303 validation and great integration with spring security.

So I will show you how to integrate body into your application. Build a login page main layout with a grid a dialogue view with details and an edit form which is secured by roles. I will use my new hero watch project for that. It couples Spring Boot, Spring Security and MongoDB. The MongoDB instance is brought up via Docker Compose. The whole code is available on GitHub. The link is in the description, so you can pull the project right now and follow along.

Okay, let's start. If you add the vin dependencies manually, you will need the vin core dependency, springwood starter vin dependency and also profile for building the UI for production. We will have a main layout and login page. First, let's create a login view class. It will extend main which is a Vaadin component that renders as an HTML main element. It also implements before enter observer. So the view can intercept navigation before the routter enters it. Let's start with some useful class level annotations. The root annotation registers the view at the login route. The auto layout set to false tells for not to wrap this view in your app's normal layout. The page title annotation says the browser tab title to login when this view is active. The anonymous allowed annotation allows unauthenticated users to access this route. Okay, let's create a login path constant used for the forums action. Let's add the authentication context from Vaadin Spring Security. It gives you helper methods like is authenticated. Let's also add the login form which is the Vaadin ready made form component. The out-of-the-box login form consists of two input fields, username and password, and two buttons, login and forgot password. And all of that is available at your fingertips with only creating the login form object. Let's make use of spring dependency injection in the login view constructor. Here we also instantiate the login form. Set the form's post target with a set action method. On submit, the browser posts credentials to login. Let's also hide the built-in forgot password button for simplicity sake. The set size full method makes the root main fill the available space. Content div element creates a wrapper div and places the login form inside it. It is useful for styling and centering the form. The add method adds the div with the login form inside to the main component so it appears on the stage. Also, let's create before enter method that uh accepts the before enter event. It fires right before navigation enters this view. If the user is already logged in, we will not show the login page and forward them to the root root. Then we check the query string for error. Spring security appends this when login attempt fails. The login set error method uh flips the component into its error state and shows the invalid username or password message. And here it is our login page with a form. We have a username field, a password field and also you can make the password visible. This button is available out of the box with Vaadin.

Okay, let's now create the main layout for our application. Create the main layout class. It should extend the app layout that gives you a responsive shell with a top navigation bar and left side drawer out of the box. Let's add some annotations. The layout annotation marks this class as a rooting layout. So views can be shown inside it. It means that other views annotated with rooted will use this one as their parent layout. In the class constructor, the shell's UI, which is a header and a drawer, will be built when the layout is created. Let's add the drawer toggle. It is a button that opens and closes the left drawer with a vertical menu. H1 creates a big header text component that says new watch. Let's add some inline styles to the logo to make it nicer. Now the add to navbar method adds the button and the logo into the top navigation bar area of app layout. The root link objects create clickable navigation links labeled civilians and implant locks. They route to the view classes civilian view and implant lock view. We won't look into the implant lock view in this demo. This is only to show you that you can add as many links to other views as you want and they will always be available in the side drawer. Now it's time to create a civilian view that displays a grid with civilians from the database. In this section, you will learn how to create a grid to display the data, how to add filters to filter items in the grid, how to create a dialogue window that displays detailed information about each civilian in the grid, and how to add an edit form to this window that can be visible to admins only.

Okay, let's start with the grid. We will create a foundation now and then add some UI elements as we move along. Let's create a civilian view class that extends a vertical layout. It is a ving view that lays children top to bottom. Add the root annotation that registers this view at the root path and says uh render me inside main layout. So this way we get the nav bar and drawer frame. The permit all annotation means that anyone who has logged in can open this view. Inject the civilian service and add the grid for civilian class. Pulse means not to autogenerate columns because we will define them manually. Add also the callback data provider. We will build it later and use it to populate the grid with data. In the constructor, we build the data provider, set up the grid and add the Lumo utility classes which is a Vance utility for CSS. So we adjust the sizing, we flex the columns, uh we adjust the spacing and say that uh children can wrap elements. Put the grid into view with the add method. Now do the actual grid configuration. First let's bind lazy data provider with a set items method. It accepts callback data provider. Vin will ask for slices of data as we scroll over page. We will build a provider in a separate method in a couple of minutes. Then add two columns national ID and legal name with the add column method. The values will be read via methods references. With the add theme variance method, we enable theme variant wrap cell content. This way long cell text will wrap instead of overflowing. Set size full makes the view fill the available space. Okay. Now we need to build the callback provider. For simplicity sake, uh we load all civilians from the database and uh enable lazy loading at the view level. This way uh we are loading the items that are currently displayed by setting the offset and limit. This uh prevents the front end from loading all available data at once and hanging as a result. But of course in real life it is recommended to use a lazy loading at the repository level for big data sets. The Vadian call back provider uses two functional interfaces, fetch call back and count call back. They allow us to retrieve a stream of data using a query and to count the total number of items. For the fetch call back, we call the service query method that returns the collection of civilians. Then we stream it, skip the offset, take the limit and return a stream of civilians. For the count call back, we return a total number of rows so the grit can pagionate. After you log into the app, you will see a grid with all the civilians from the database and the left side drawer.

Okay, so right now we display all civilians. But in most cases, we need some sort of filtering. Well, let's add the filters. First, let's add some UI elements. We will have three integer fields for filtering by lot numbers and uh also a text field to search for civilian by national ID. Let's also add a button that will clear all filter fields when clicked. We will configure the filters in a separate method just like uh the grid. So in the constructor, we only call this method. In the constructor, we also create a new horizontal layout and add the filters and the button there so that they are displayed in the horizontal row. The set default vertical component alignment method aligns the child components so their ends line up. This way we align them with the grid header. We also add the filters to the add method to display them over the grid. Let's move on to configuring the filters. Has value creates one generic listener that calls the refresh method whenever any filter value changes. The while generics let one variable hold listeners for different field types. And now let's subscribe that listener to all four inputs. As a result, changing any filter triggers the refresh method, which we will define as well. Let's also add a click listener to the clear button that will trigger clearing all filter fields. The refresh method is super simple. We just call the inbuilt refresh all method of the data provider to update the provider when filters are applied or cleared. We remember that the build provider method uses the service query method to retrieve the data. So, we need to improve it. a bit to add filtered search so that it decides which backend method it should call based on the current filter values. In our logic only one filter can be applied at a time. So we check whether a filter field has value fetch civilians and return. If you want to implement combined filtering, we need to change a service query to compute a single predicate and query with it. Now our main view has filters. You can search for civilian by national ID or select all civilians with a lot number greater than, lesser than or equal to the specified number.

Our grid displays only the national ID and the legal name of a civilian. Let's add a dialogue window that will pop up when we click on the civilian to display more detailed information about this person. For that we need to add a listener to the grid uh via a single select method. It switches the grid selection model to a single select and listens for selection changes. And here we will call the open civilian dialogue method when some civilian in a grid is selected. In this method, we create the variance dialogue. We set the dialog's header title to the civilian's legal name and uh we also give the dialogue a fixed width. So a nice spacious card is displayed. Now let's create a tab labeled details. We will add one more tab later. Then we wrap the tab or as many tabs as we create in a tabs component which provides a clickable tab strip using the component class. We need to build the actual content for the details tab. For that we use a separate build details panel method which we will define later. In a map that accepts tab in the component we map each tab to its corresponding content component. Even with one tab, this sets up the pattern for multiple tabs so you can conveniently add them later. The div pages creates container that will hold the tab pages. We also set the CSS position hide all pages initially uh with set visible false and then we show the details page by default with set visible true set uh to details panel. Add selected change listener reacts when the user switches tabs. So we hide all pages initially and then we show the page associated with the currently selected tab. The add method adds the top bar and the pages container to the dialogue body. The get footer add new button method adds a close button to the dialogue footer. Clicking it closes the dialogue. The open method opens the dialogue.

Finally, let's populate the details panel. In the build details panel, create a form layout that provides a form style layout for uh label and value pairs related to the civilian. Then we just add all required readonly items to this form. Setting a label and retrieving a relevant value from the civilian object. The dialog window should also display information about implants of a civilian. So let's create a grid for implants and populate each column with values from the implant object. Each column auto sizes and flex grows to take space. After that, we fix the grid height to 200 pixels so we get a small scrollable area instead of a giant table. We also enable text wrapping in cells to avoid the overflow cut offs. Let's create a vertical layout to stack the form and the grid vertically. And then we just remove extra padding spacing and make the content fill available space within the dialog content area. Now, when you click on the civilian in the grid, a window pops up that displays the detailed information about the civilian and their implants. Finally, let's add another tab to our dialogue window that enables the users with the role admin edit the civilians. First, let's create a has a role method that accepts the required role and uses security context holder to verify whether the user has this role. Now add a new tab called edit tab to the open civilian dialogue method. Use the has ro method to check whether the user is admin. If yes, add the new tab to the tabs. Create the edit panel component. Add it to the map together with the edit tab and to the diff as well. We will build this component in a separate method and build edit form. Here we create a text field and two check boxes for civilian data we can edit. Then we create a val binder for the civilian bin type. The binder handles syncing UI fields with bin properties and validation. Then using the bind method of the binder, we surprise bind the text fields and checkboxes to the relevant civilian values. The read bin method reads values from the civilian bin into the bound fields and sets initial UI state. Let's also add the save button with a click listener. Here we call the write bin if valid method of the binder. It tries to write the current field values into the civilian bean but uh only if all bound fields are valid. This method returns true on success. If true we call the civilian service to persist the updated civilian. Then we call our refresh method to update the greet and close the dial window. Let's also add the cancel button to close the dialogue window without persisting any changes. And finally, we return a vertical stack containing the input fields and the two buttons. The caller will add this to the dialogue. If you log in as a user to the application, you won't see this edit tab. But if you log in as the admin, you will see the edit tab in the tile window. You can change the values and the updated data will be immediately displayed in the grid. The UI already looks quite nice. If you don't need custom design, you can use it as is. You can also use the light or dark theme provided by Vin out of the box. But in this video, I will also show you quickly how to add the custom styles. Go to the main class of our application and add the theme annotation here. point to the directory with our custom theme. In our case, it's called cyber. Also, you can enable the dark theme by default here. Vin by default looks for styles under main front end themes directory. So there you create the cyber directory and vin picks up all the CSS and JSON files from this directory. This demo uses two CSS files, one for the login page and one for the main view. I won't describe the uh CSS styling in detail here. You can find more information in the official documentation. So when you add the custom themes, you can run the application. And voila, we now have a beautiful cyberpunk themed UI. We have only touched the tip of the iceberg of what you can do with Vin. I encourage you to try out this framework and drop me a comment if you want more ving centered videos. And of course, like, subscribe, and until next time.

Summary

This video shows how to build a complete UI for a Spring Boot application using Vaadin, a Java web framework that provides rich, customizable UI components entirely in Java. The guide walks through creating a login page, a main layout with navigation, and a data grid that supports filtering, lazy loading, and detailed dialogs. It also demonstrates how to build a multi-tab dialog with read-only details and an admin-only edit form using Vaadin Binder for validation and data binding. The video explains how to integrate Spring Security, handle database data from MongoDB, and build responsive layouts. Finally, it shows how to apply a custom cyberpunk theme and encourages exploring Vaadin’s broader capabilities.

About Catherine

Java developer passionate about Spring Boot. Writer. Developer Advocate at BellSoft

Social Media

Videos
card image
Oct 7, 2025
Master Java Profiling in 2025: Tools, Techniques, and Real-World Tips

In this complete guide to Java profiling, you will learn sampling and instrumentation techniques, compare the 7 best tools (JFR, VisualVM, Async Profiler, JProfiler, YourKit, Digma.ai, New Relic), and master how to detect memory leaks and analyze CPU usage.

Videos
card image
Sep 25, 2025
Should You Still Use LOMBOK in 2025?

Have you ever wondered how @Data, @Builder, and @Getter actually work? Lombok doesn’t just generate code — it rewrites your AST during compilation using internal, unofficial APIs. And while many developers enjoy the reduced boilerplate, few realize the hidden risks behind it.

Further watching

Videos
card image
Nov 6, 2025
Docker Container Image Security: 13 Best Practices

This video presents 13 practical recommendations for reducing your attack surface and detecting malicious activity more quickly. You’ll learn how to create simple, immutable, and deterministic images using multi-stage builds, distroless bases, and non-root users. We cover SBOM generation with Syft, provenance verification with Cosign, CVE scanning workflows, and secret management strategies. From choosing LTS base images like Alpaquita Linux to implementing host-level protections, these practices will help you confidently deliver secure containers. It’s ideal for Java developers, DevOps engineers, and architects building production-grade infrastructure.

Videos
card image
Oct 23, 2025
Top 7 JavaFX Testing Mistakes You Need To Avoid!

Stop making these common JavaFX testing mistakes! No more random NullPointerExceptions or deadlocks — in this video, we’ll show you how to fix the 7 most frequent TestFX issues when testing JavaFX applications. Learn how to work with FX threads, integrate with Spring Boot, avoid event-queue race conditions, handle pixel-level test differences, set up headless continuous integration with Monocle, and properly separate business logic from UI tests.

Videos
card image
Oct 16, 2025
All 7 Java Garbage Collectors Explained

In this complete guide to Java garbage collection, you will learn how the JVM memory model works, understand the differences between the Serial, Parallel, G1, ZGC, Shenandoah, CMS, and Epsilon collectors, and determine which garbage collector is best suited for your application's performance — from single-threaded programs to massive terabyte-scale heaps.