Hilla Documentation

Hilla Quick Start

Learn how to build a small shopping-list application using Hilla in few minutes.

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

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;

    @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;
  1. Add a @NotBlank annotation to ensure that the item’s name is non-null and that it contains at least one non-whitespace character.

  2. Add @NotNull and @Min annotations to ensure that the item’s quantity 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) {
    return item;
  1. Annotating the class with @Endpoint exposes it as a service for client-side views. All public methods of an endpoint are callable from TypeScript.

  2. By default, endpoint access requires an authenticated user. @AnonymousAllowed enables access for anyone. See Configuring Security for more information on endpoint security.

  3. Using the @Nonnull annotation ensures that the TypeScript Generator doesn’t interpret these values as possibly undefined.

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)

  private groceries: GroceryItem[] = []; // (3)
  private binder = new Binder(this, GroceryItemModel); // (4)

  render() {
    return html`
      <div class="p-m">
            label="Item"> </vaadin-text-field> <!--(5)-->
            label="Quantity"></vaadin-number-field> <!--(6)-->
            ?disabled=${this.binder.invalid}>Add</vaadin-button> <!--(7)-->

        <h3>Grocery List</h3>
        <vaadin-grid .items="${this.groceries}" theme="row-stripes" style="max-width: 400px">
          <vaadin-grid-column path="name"></vaadin-grid-column>
          <vaadin-grid-column path="quantity"></vaadin-grid-column>

  async addItem() {
    const groceryItem = await this.binder.submitTo(save); // (9)
    if (groceryItem) { // (10)
      this.groceries = [...this.groceries, groceryItem];

  async firstUpdated() { // (11)
    const groceries = await getGroceries();
    this.groceries = groceries;
  1. Register the new component with the browser. This makes it available as <grocery-view>. The routing in index.ts is already set up to show it when you navigate to the application.

  2. Define the component class that extends from Hilla View class, which itself extends from LitElement.

  3. The list of groceries is private and decorated with @state() so Lit observes it for changes.

  4. 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 that Binder needs. Read more about forms in Binding Data to Forms.

  5. The Text Field component is bound to the name property of a GroceryItem using element expression: ${field(this.binder.model.name)}.

  6. Analogous to the Text Field, the Number Field is bound to the quantity property of a GroceryItem using ${field(this.binder.model.quantity)}.

  7. The click event of the Add button is bound to the addItem() method. The button is disabled if the form is invalid.

  8. Use Hilla Grid to display the current content of the grocery list.

  9. Use binder to submit the form to GroceryEndpoint. The binder validates the input before posting it and the server re-validates it.

  10. If the GroceryItem was saved successfully, update the groceries array and clear the form.

  11. 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.

A running project

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.