While Vaadin is commonly integrated with Spring, this article explores a powerful new full-stack solution: Vaadin with Quarkus. Quarkus provides a lightning-fast runtime and native images, complementing Vaadin’s Java-first UI, which eliminates JavaScript complexity. Readers will learn how to build scalable, reactive Vaadin Flow apps on Quarkus, covering simplified backend development, enhanced productivity with a unified Java stack, and superior deployment optimization compared to traditional approaches.
Introduction
Vaadin and Quarkus are both modern Java frameworks, each excelling in its domain: Vaadin for building rich web UIs in pure Java, and Quarkus for creating cloud-native, container-first Java applications. Together, they offer a compelling alternative to traditional full-stack Java development, especially for enterprise applications.
This article will guide you through setting up a Vaadin Flow application on Quarkus, demonstrating how to leverage Quarkus’ reactive capabilities and Vaadin’s component-based UI to build scalable, maintainable, and high-performance enterprise apps.
Why Quarkus for Vaadin Developers?
Vaadin applications, by nature, could be memory-heavy because they maintain UI state on the server. Quarkus mitigates this by optimizing the underlying Jakarta EE and MicroProfile stacks.
| Capability | Spring + Vaadin | Quarkus + Vaadin |
| Startup Time | Seconds | Milliseconds |
| Memory Footprint | Medium to High | Very Low |
| Native Images | Possible / Complex | First-class |
| Dev Mode | Good | Exceptional |
| Reactive Core | Optional | Built-in |
Vaadin applications, by nature, could be memory-heavy because they maintain UI state on the server. Quarkus mitigates this by optimizing the underlying Jakarta EE and MicroProfile stacks.
Project Setup
Prerequisites
- JDK 17+
- Maven 3.8+
- Your favourite IDE
- Quarkus CLI (optional)
Create a Quarkus Project
Start by generating your project using the Quarkus CLI:
mvn io.quarkus.platform:quarkus-maven-plugin:3.9.0:create \
-DprojectGroupId=com.example \
-DprojectArtifactId=vaadin-with-quarkus \
-DclassName="com.example.vaadin.MainLayout" \
-Dpath="/" \
-Dextensions="com.vaadin:vaadin-quarkus-extension,quarkus-hibernate-orm,quarkus-hibernate-orm-panache,quarkus-jdbc-h2"
cd vaadin-with-quarkus
quarkus dev
Or use the code.quarkus.io online tool to generate a new Quarkus project with the Vaadin extension:

This will create a standard Quarkus project with a simple landing page and Hibernate dependencies installed.
Note: If the Vaadin Quarkus extension is not available in the registry, you can manually add the following dependencies to your pom.xml.
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-bom</artifactId>
<version>${vaadin.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>io.quarkus.platform</groupId>
<artifactId>quarkus-bom</artifactId>
<version>${quarkus.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- Vaadin Quarkus Extension - single dependency for full integration -->
<dependency>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-quarkus-extension</artifactId>
</dependency>
<!-- Quarkus Panache for simplified JPA -->
<dependency>
<groupId>com.vaadin</groupId>
<artifactId>quarkus-hibernate-orm</artifactId>
</dependency>
<dependency>
<groupId>com.vaadin</groupId>
<artifactId>quarkus-hibernate-orm-panache</artifactId>
</dependency>
<!-- H2 in-memory DB for development -->
<dependency>
<groupId>com.vaadin</groupId>
<artifactId>quarkus-jdbc-h2</artifactId>
</dependency>
</dependencies>
Replace the ${quarkus.version} and ${vaadin.version} accordingly.
Run in DEV Mode
quarkus dev
Open http://localhost:8080. You should see the Quarkus Getting Started page.

Building the Application
We’ll build a small Product Catalogue application that demonstrates Vaadin’s component model, CDI injection, Quarkus services, and Hibernate ORM, all within a single Java project.
The Domain Model
We’ll create Product entity. Using Quarkus Panache, we can simplify the DAO layer by extending PanacheEntity.
src/main/java/com/example/demo/Product.java
package com.example.example;
import java.math.BigDecimal;
import java.util.List;
import io.quarkus.hibernate.orm.panache.PanacheEntity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Table;
@Entity
@Table(name = "products")
public class Product extends PanacheEntity {
// 'id' (Long) is inherited from PanacheEntity
@Column(nullable = false, unique = true)
public String sku;
@Column(nullable = false)
public String name;
@Column(nullable = false, precision = 10, scale = 2)
public BigDecimal price;
@Column(nullable = false)
public int stock = 0;
// --- Named queries as static helpers ---
public static List<Product> listAllOrdered() {
return list("ORDER BY name");
}
}
CDI Service (Backend Logic)
Quarkus uses standard CDI. @ApplicationScoped marks this service as a singleton; @Transactional handles transactions declaratively.
src/main/java/com/example/demo/ProductService.java
package com.example.example;
import java.util.List;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.transaction.Transactional;
@ApplicationScoped
public class ProductService {
public List<Product> findAll() {
return Product.listAllOrdered();
}
@Transactional
public void save(Product product) {
if (product.id == null) {
// New entity — no id yet, safe to persist (INSERT)
product.persist();
} else {
// Detached entity — re-attach and flush changes (UPDATE)
Product.getEntityManager().merge(product);
}
}
@Transactional
public void delete(Long id) {
Product.deleteById(id);
}
@Transactional
public void adjustStock(Long id, int delta) {
Product p = Product.findById(id);
if (p != null) {
p.stock = Math.max(0, p.stock + delta);
}
}
}
The Interactive UI (The Vaadin Side)
Now for the exciting part: the UI. Vaadin views are just Java classes annotated with @Route. No HTML to write, no JavaScript event handlers, no REST endpoints to consume. We will inject our ProductService directly into the view.
src/main/java/com/example/demo/ProductView.java
package com.example.demo;
import java.math.BigDecimal;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.button.ButtonVariant;
import com.vaadin.flow.component.dialog.Dialog;
import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.html.H2;
import com.vaadin.flow.component.notification.Notification;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.textfield.BigDecimalField;
import com.vaadin.flow.component.textfield.IntegerField;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import jakarta.inject.Inject;
@Route("products")
@PageTitle("Products")
public class ProductView extends VerticalLayout {
private final ProductService productService;
private final Grid<Product> grid = new Grid<>(Product.class, false);
@Inject
public ProductView(ProductService productService) {
this.productService = productService;
setSizeFull();
add(new H2("Product Catalogue"));
add(buildToolbar());
add(buildGrid());
refresh();
}
private HorizontalLayout buildToolbar() {
Button addBtn = new Button("Add Product", e -> openDialog(null));
addBtn.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
return new HorizontalLayout(addBtn);
}
private Grid<Product> buildGrid() {
grid.addColumn(p -> p.sku).setHeader("SKU").setWidth("120px");
grid.addColumn(p -> p.name).setHeader("Name").setFlexGrow(1);
grid.addColumn(p -> "€" + p.price).setHeader("Price");
grid.addColumn(p -> p.stock).setHeader("Stock");
grid.addComponentColumn(p -> {
Button edit = new Button("Edit", e -> openDialog(p));
Button del = new Button("Delete", e -> {
productService.delete(p.id);
refresh();
Notification.show("Deleted " + p.name);
});
Button inc = new Button("+1", e -> {
productService.adjustStock(p.id, 1);
refresh();
});
Button dec = new Button("-1", e -> {
productService.adjustStock(p.id, -1);
refresh();
});
edit.addThemeVariants(ButtonVariant.LUMO_SMALL);
del.addThemeVariants(
ButtonVariant.LUMO_ERROR, ButtonVariant.LUMO_SMALL);
inc.addThemeVariants(
ButtonVariant.LUMO_SMALL, ButtonVariant.LUMO_SUCCESS);
dec.addThemeVariants(ButtonVariant.LUMO_SMALL);
return new HorizontalLayout(edit, del, inc, dec);
}).setHeader("Actions");
grid.setSizeFull();
return grid;
}
private void openDialog(Product existing) {
Dialog dialog = new Dialog();
dialog.setHeaderTitle(existing == null ? "New Product" :
"Edit Product");
TextField skuField = new TextField("SKU");
TextField nameField = new TextField("Name");
BigDecimalField priceField = new BigDecimalField("Price (€)");
IntegerField stockField = new IntegerField("Initial Stock");
stockField.setMin(0);
if (existing != null) {
skuField.setValue(existing.sku);
nameField.setValue(existing.name);
priceField.setValue(existing.price);
stockField.setValue(existing.stock);
}
dialog.add(new VerticalLayout(skuField, nameField,
priceField, stockField));
Button save = new Button("Save", e -> {
if (skuField.isEmpty() || nameField.isEmpty()
|| priceField.isEmpty()) {
Notification.show("All fields are required");
return;
}
Product p = existing != null ? existing : new Product();
p.sku = skuField.getValue();
p.name = nameField.getValue();
p.price = priceField.getValue() != null ?
priceField.getValue() : BigDecimal.ZERO;
p.stock = stockField.getValue() != null ?
stockField.getValue() : 0;
productService.save(p);
refresh();
dialog.close();
Notification.show("Saved " + p.name);
});
save.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
dialog.getFooter().add(new Button("Cancel",
e -> dialog.close()), save);
dialog.open();
}
private void refresh() {
grid.setItems(productService.findAll());
}
}
Application Configuration
Quarkus uses application.properties for all runtime configurations. Place this in src/main/resources/:
# Vaadin
vaadin.launch-browser=true
# Quarkus HTTP
quarkus.http.port=8080
quarkus.live-reload.instrumentation=true
# Database (H2 in-memory for demo)
quarkus.datasource.db-kind=h2
quarkus.datasource.jdbc.url=jdbc:h2:mem:products-db
quarkus.hibernate-orm.database.generation=drop-and-create
Running the Application
Development Mode
Quarkus dev mode provides hot reload for Java and Vaadin:
mvn quarkus:dev
Visit http://localhost:8080/products. Changes to services or views are reflected without a restart.



JVM Production Build
Building for production is now a single Maven command. For a standard JVM deployment:
mvn package -Pproduction
java -jar target/quarkus-app/quarkus-run.jar
Native Executable
GraalVM native compilation produces a self-contained executable with near-instant startup:
mvn package -Pnative -Dquarkus.native.container-build=true
./target/vaadin-with-quarkus-1.0.0-SNAPSHOT-runner
A native Quarkus + Vaadin app starts in under 100ms using 80–120MB of heap versus 500–800MB for an equivalent Spring Boot JVM deployment.
Wrap Up
Why This Matters
- No controllers
- No REST boilerplate
- No JavaScript
- Fully type-safe end-to-end Java
CDI vs Spring
| Concept | Spring | Quarkus |
| Dependency Injection | @Autowired | @Inject |
| Scope | @Service | @ApplicationScoped |
| Startup | Reflection-heavy | Build-time optimized |
Dev Mode Experience
- Hot reload for Java and UI
- No restart on view changes
- Browser auto-launch
- Near-instant feedback
Native Image Build
Typical results:
- Startup: < 100ms
- Memory: < 120MB
- Perfect for serverless, edge, and Kubernetes
Key Takeaways
- A unified Java stack from UI to backend services
- Faster startup times and reduced memory usage compared to traditional frameworks
- A simpler dependency injection model using Jakarta CDI
- A powerful live-reload development workflow
- Seamless native image support for cloud-native deployments
Conclusion
Vaadin and Quarkus together provide a modern, efficient, and scalable full-stack Java solution for enterprise applications. By unifying the Java stack, you reduce complexity, improve productivity, and optimize deployment, making it an excellent choice for your next project.