Hilla Quick Start
Hilla is a modern frontend framework for Java backends, integrating a Spring Boot backend with a reactive TypeScript frontend. It includes all the UI components and tools you need so you can focus on building your app instead of configuring stuff.
In this guide, you learn how to build a small but fully functional shopping-list application using Hilla.
What You Need
-
About 10 minutes
-
Node 16.14 or later
-
JDK 11 or later, for example, Eclipse Temurin JDK.
Step 1: Create a Hilla Project
Use the Vaadin CLI to create a new project:
npx @vaadin/cli init --preset hilla-quickstart-tutorial hilla-grocery-app
Unpack the downloaded zip into a folder on your computer, and import the project in the IDE of your choice.
The pre-configured starter project includes an empty Grocery
view written in Typescript that you will modify as described further below.
Step 2: Define the Data Model
For the data model, define a plain old Java object (POJO) by creating a new GroceryItem.java
file in src/main/java/com/example/application/
with the following content:
package com.example.application;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
public class GroceryItem {
@NotBlank // (1)
private String name;
@NotNull
@Min(value = 1) // (2)
private Integer quantity;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getQuantity() {
return quantity;
}
public void setQuantity(int quantity) {
this.quantity = quantity;
}
}
-
Add a
@NotBlank
annotation to ensure that the item’sname
is non-null and that it contains at least one non-whitespace character. -
Add
@NotNull
and@Min
annotations to ensure that the item’squantity
is a non-null integer that’s greater than 0.
Step 3: Create a Typed Server Endpoint
Hilla enables your frontend to have secure, type-safe access to the server through REST-like endpoints.
Create a new GroceryEndpoint.java
file in src/main/java/com/example/application/
with the following content:
package com.example.application;
import java.util.ArrayList;
import java.util.List;
import com.vaadin.flow.server.auth.AnonymousAllowed;
import dev.hilla.Endpoint;
import dev.hilla.Nonnull;
@Endpoint // (1)
@AnonymousAllowed // (2)
public class GroceryEndpoint {
private final List<GroceryItem> groceryList = new ArrayList<>();
public @Nonnull List<@Nonnull GroceryItem> getGroceries() { // (3)
return groceryList;
}
public GroceryItem save(GroceryItem item) {
groceryList.add(item);
return item;
}
}
-
Annotating the class with
@Endpoint
exposes it as a service for client-side views. All public methods of an endpoint are callable from TypeScript. -
By default, endpoint access requires an authenticated user.
@AnonymousAllowed
enables access for anyone. See Configuring Security for more information on endpoint security. -
Using the
@Nonnull
annotation ensures that the TypeScript Generator doesn’t interpret these values as possiblyundefined
.
Step 4: Create a Reactive UI in TypeScript
Hilla uses the Lit library to create client-side views. Lit is a lightweight and highly performant library for building reactive UI.
Open frontend/views/grocery/grocery-view.ts
and replace its contents with the following:
import '@vaadin/button';
import '@vaadin/text-field';
import '@vaadin/number-field';
import '@vaadin/grid/vaadin-grid';
import { html } from 'lit';
import { customElement, state } from 'lit/decorators.js';
import { View } from 'Frontend/views/view';
import { Binder, field } from '@hilla/form';
import { getGroceries, save } from 'Frontend/generated/GroceryEndpoint';
import GroceryItem from 'Frontend/generated/com/example/application/GroceryItem';
import GroceryItemModel from 'Frontend/generated/com/example/application/GroceryItemModel';
@customElement('grocery-view') // (1)
export class GroceryView extends View { // (2)
@state()
private groceries: GroceryItem[] = []; // (3)
private binder = new Binder(this, GroceryItemModel); // (4)
render() {
return html`
<div class="p-m">
<div>
<vaadin-text-field
${field(this.binder.model.name)}
label="Item"> </vaadin-text-field> <!--(5)-->
<vaadin-number-field
${field(this.binder.model.quantity)}
has-controls
label="Quantity"></vaadin-number-field> <!--(6)-->
<vaadin-button
theme="primary"
@click=${this.addItem}
?disabled=${this.binder.invalid}>Add</vaadin-button> <!--(7)-->
</div>
<h3>Grocery List</h3>
<vaadin-grid .items="${this.groceries}" theme="row-stripes" style="max-width: 400px">
<!--(8)-->
<vaadin-grid-column path="name"></vaadin-grid-column>
<vaadin-grid-column path="quantity"></vaadin-grid-column>
</vaadin-grid>
</div>
`;
}
async addItem() {
const groceryItem = await this.binder.submitTo(save); // (9)
if (groceryItem) { // (10)
this.groceries = [...this.groceries, groceryItem];
this.binder.clear();
}
}
async firstUpdated() { // (11)
const groceries = await getGroceries();
this.groceries = groceries;
}
}
-
Register the new component with the browser. This makes it available as
<grocery-view>
. The routing inindex.ts
is already set up to show it when you navigate to the application. -
Define the component class that extends from Hilla
View
class, which itself extends fromLitElement
. -
The list of
groceries
is private and decorated with@state()
so Lit observes it for changes. -
A Hilla
Binder
is used to handle the form state for creating new GroceryItems.GroceryItemModel
is automatically generated by Hilla. It describes the data types and validations thatBinder
needs. Read more about forms in Binding Data to Forms. -
The Text Field component is bound to the
name
property of aGroceryItem
using element expression:${field(this.binder.model.name)}
. -
Analogous to the Text Field, the Number Field is bound to the
quantity
property of aGroceryItem
using${field(this.binder.model.quantity)}
. -
The click event of the Add button is bound to the
addItem()
method. The button is disabled if the form is invalid. -
Use Hilla Grid to display the current content of the grocery list.
-
Use binder to submit the form to
GroceryEndpoint
. The binder validates the input before posting it and the server re-validates it. -
If the
GroceryItem
was saved successfully, update thegroceries
array and clear the form. -
Retrieve the list of groceries from the server upon the view’s first rendering.
Step 5: Run the Application
To run the project in your IDE, launch Application.java
, which is located under src/main/java/com/example/application/
.
Alternatively, you can run the project from the command line by typing mvnw
(on Windows), or ./mvnw
(on macOS or Linux).
Then, in your browser, open localhost:8080/grocery
.

Go Further
Congratulations on finishing the tutorial! Now you have a taste of how Hilla empowers you to quickly build web apps that integrate a Java backend with a reactive TypeScript frontend.
Continue exploring Hilla in the following resources:
If you get stuck or need help, please reach out to the Hilla Community in Discord.
The full source code of this project is available on GitHub.