Open-source project
vaadin/hilla avatar
vaadin/hilla

Hilla: Spring Boot endpoints that generate their own TypeScript clients

Build better business applications, faster. No more juggling REST endpoints or deciphering GraphQL queries. Hilla seamlessly connects Spring Boot and React to accelerate application development.

983 stars77 forksJavaApache-2.0

At a glance

What is it?
Hilla is a Vaadin framework that joins a Spring Boot Java backend to a reactive TypeScript front end by generating endpoint clients and entity types from the Java classes themselves. The payoff is compile time type safety between the two halves, and the cost is a stack that is hard to unbundle.
Who is it for?
Hilla suits a Java shop that wants a TypeScript front end with the type contract enforced at compile time and no hand written API layer, and it is the wrong tool for a team with a non Spring backend, a front end framework outside the React and Lit line, or a requirement to expose the same API to third party clients that were not generated by Hilla.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository received new commits within the last day.
What is it written in?
Mainly Java, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The backend and the frontend share one generated contract

Hilla integrates a Spring Boot Java backend with a reactive TypeScript front end, and the integration is not a hand written API client. The framework reads your Java classes and generates the TypeScript that the front end consumes.

The readme's example shows the shape of that. On the server side there is a service class annotated with `@BrowserCallable` and `@AnonymousAllowed`, holding a repository and returning a list of `Person` entities. The `Person` class is a plain JPA style entity with an `@Id`, a `@GeneratedValue` and `@Nonnull` fields, one of which carries an `@Email` annotation.

On the client side the generated modules are imported by path.

ts
// Type info is automatically generated based on Java
import Person from 'Frontend/generated/com/vaadin/hilla/demo/entity/Person';
import { PersonService } from 'Frontend/generated/endpoints';

async function getPeopleWithPhoneNumber() {
  const people: Person[] = await PersonService.findAll();

  // Compile error: The property is 'phone', not 'phoneNumber'
  return people.filter((person) => !!person.phoneNumber);
}

console.log('People with phone numbers: ', getPeopleWithPhoneNumber());

The point of the example is the comment on the compile error. The generated `Person` type has a `phone` property, because that is the Java field name, so `person.phoneNumber` fails to compile rather than failing at runtime with an undefined. That is the entire value proposition in one line, and it is the reason the project describes itself as offering type-safe server communication.

The generated directory is part of your application contract

Two import paths carry the whole client side surface. `Frontend/generated/endpoints` gives you a service object per annotated server class, and `Frontend/generated/com/vaadin/hilla/demo/entity/Person` gives you the entity type. Both sit under a generated directory, which means they are derived artefacts rather than files you maintain.

That has consequences in both directions. When you rename a Java field, the TypeScript property changes with it, and any front end code touching the old name stops compiling. That is the intended behaviour, and it is why the readme's example demonstrates a compile error rather than a runtime one.

The annotations also carry access rules. `@BrowserCallable` marks the class as callable from the browser, and `@AnonymousAllowed` marks it as reachable without authentication. The security model of the application therefore lives partly in Java annotations next to the methods, which is a reasonable place for it, but it does mean the boundary between public and protected endpoints is enforced by the annotation rather than by a configuration file.

The generated naming also tells you something about the coupling. The entity import path contains the Java package, `com/vaadin/hilla/demo/entity/Person`, so a Java package rename is a front end import change. For a business application where a domain model is shared across layers, that coupling is usually what you want. For a project where the wire format is meant to be stable and separate from the Java model, it is not.

A TypeScript workspace driven by Nx, on top of Maven

The repository is a monorepo with two ecosystems side by side. Java modules live under `./packages/java`, TypeScript modules under `./packages/ts`, and the readme tells you which directory to edit depending on what you changed.

The JavaScript side is an npm workspace. The package.json is named `@vaadin/hilla`, is marked private, and declares workspaces for `packages/ts/*` and several Java test directories including the Gradle and Spring test modules, with one path explicitly excluded. The scripts are all Nx fan-outs, for example `nx run-many -p packages/ts/* -t build --all --output-style stream`, and the aggregate `build` script runs an alignment step through Maven first, then a prepare step with `tsx scripts/prepare/index.ts`, then an install, then the Nx build. Linting, typechecking and tests follow the same fan-out pattern.

So the build order matters and is enforced by the scripts rather than left to the reader. `build:align` invokes `mvn -pl com.vaadin:hilla-scripts -q`, which is how the Java side and the TypeScript side are kept in step before anything is compiled.

The repository also carries an nx.json, an eslint configuration, a prettier configuration and lint-staged settings, and the topics list React, Lit, LitElement and TypeScript, so the front end side of the framework spans both of the component models Vaadin works with.

Building the framework needs Node 22, npm 10 and JDK 17

The readme lists the toolchain requirements for developing Hilla itself: Node.js `>= 22 LTS`, npm `>=10` with a version 3 lockfile, JDK `>=17` and Maven `>=3`. The full sequence clones the repository, installs and builds the front end, installs the Java artifacts, and then runs the tests.

sh
git clone https://github.com/vaadin/hilla.git
npm install
npm run build
mvn clean spotless:apply install -DskipTests
npm test
mvn verify -Pproduction

Two of those steps are worth calling out. `mvn clean spotless:apply install` applies the code formatter before installing, so formatting is part of the build rather than a separate chore, and `mvn verify -Pproduction` runs the production profile verification as the final gate.

For an application developer rather than a framework contributor, the readme points to the getting started tutorials at vaadin.com/docs/latest/getting-started, and the project documentation lives at vaadin.com/docs/latest/hilla. The front page is hilla.dev.

The release picture is worth noting too. Three releases were published on the same day, 2026-10-19: Hilla 25.3.3, Hilla 24.10.11 and Hilla 24.9.25. That pattern suggests several supported lines are patched in parallel, which is good news for stability and relevant news for anyone deciding which line to standardise on.

Testing a local Java change means pointing the app at SNAPSHOT

The readme describes a workflow for trying a Java change in a real application. You set the Vaadin version to `SNAPSHOT` in your application's `pom.xml`, which requires the Vaadin pre-release repositories, and it gives the exact repository and plugin repository blocks to add.

For the TypeScript side there is a different path. You build and pack the modified package, which produces a `.tgz` file in the package root.

sh
npm run build && npm pack

Then in the application you install that tarball directly.

sh
npm install <path-to-tgz>

The readme closes with a detail that saves an afternoon: remember to delete the line that points at the package in `package.json` when you are done testing, because the next run will add back the corresponding default package. A locally packed tarball left in `package.json` silently pins your application to your working copy.

This two-step local testing arrangement is a consequence of the framework being split across a Maven and an npm ecosystem. There is no single `install from source` step; there are two, one per package manager, and each has its own cleanup requirement.

Where Hilla is the wrong tool

The main limitation is the stack commitment. Hilla assumes a Spring Boot backend and a TypeScript front end in the React and Lit line. The repository topic list includes Vaadin's own components alongside React, Lit and LitElement, and the generated endpoints import path is tied to the Java package structure. A team that wants a REST or GraphQL API for third party consumers, or a front end written in something other than TypeScript, gets none of the benefit and all of the coupling.

The second limitation is that the generated layer is a closed contract between two halves of the same application. That is exactly what makes it safe, and exactly what limits it: the endpoints are described from the Java side, so the API shape follows the domain model rather than an independently designed contract. The readme's own framing, that there is no juggling of REST endpoints and no deciphering of GraphQL queries, is the trade being made.

The third is the build weight. Even to run the framework's own tests you need Node 22, npm 10, JDK 17 and Maven 3, with a build that fans out through Nx and calls into Maven first. That is not a problem for a company with a platform team, and it is friction for a small team maintaining one internal application.

Fourth, the readme does not document how to version the generated code for clients who are not generated by Hilla, and it does not describe a rollback procedure for a bad release. The licences are simpler: the project is Apache-2.0, so it can be used in commercial products without the copyleft obligations that would apply to a GPL component.

How generated endpoints differ from a REST or GraphQL layer

The alternative for a Java and TypeScript team is to build the API boundary yourself, either as REST controllers with a typed client, or as a GraphQL schema with a generated query layer.

A hand written REST layer gives you an independent contract. You can version it, publish it, and let clients that are not TypeScript use it. The cost is that the types live in two places, a Java DTO and a TypeScript interface, and nothing stops them drifting. The type safety in a hand written layer is a convention that review and tests enforce, not a compiler.

A GraphQL layer solves the type drift differently, by making the schema the contract and letting codegen produce the client. The cost there is a schema language, a resolver layer and a query runtime, and the readme's mention of deciphering GraphQL queries points at the part users actually experience, the client side.

Hilla's approach removes the separate contract entirely for the case where both halves ship together. The Java class is the contract, the TypeScript is generated from it, and a rename on one side becomes a compile error on the other. That is a stronger guarantee than either convention based approach, and it comes with the constraint that the API is not separately designed or separately versioned.

So the decision comes down to who consumes the backend. One application, two halves, released together: Hilla's model is a good fit. Several consumers, including ones in other languages: a separately designed and versioned API is the better model, and Hilla is not the tool for it.

Editorial conclusion

Hilla suits a Java shop that wants a TypeScript front end with the type contract enforced at compile time and no hand written API layer, and it is the wrong tool for a team with a non Spring backend, a front end framework outside the React and Lit line, or a requirement to expose the same API to third party clients that were not generated by Hilla. Before adopting it, confirm that the release line you depend on is still being published, since 25.3.3, 24.10.11 and 24.9.25 were all published on 2026-10-19, and plan the Node.js 22, npm 10 and JDK 17 toolchain the build needs.

Frequently asked questions

What is Hilla?

Hilla is a Vaadin framework that integrates a Spring Boot Java backend with a reactive TypeScript front end, generating type-safe endpoints and entity types from the Java classes. The project is licensed under Apache-2.0 and documents itself at hilla.dev.

How does Hilla generate TypeScript from Java?

Classes annotated with `@BrowserCallable` are exposed to the browser and their types are generated into a generated directory on the client, for example `Frontend/generated/endpoints` for services and a package path per entity. The readme's example shows a compile error when the front end uses a property name that does not match the Java field.

What do I need to build Hilla from source?

Node.js 22 LTS or newer, npm 10 or newer, JDK 17 or newer and Maven 3 or newer. The readme's sequence is `git clone`, `npm install`, `npm run build`, `mvn clean spotless:apply install -DskipTests`, `npm test` and `mvn verify -Pproduction`.

How do I test a local change in a real application?

For Java changes, set the Vaadin version to `SNAPSHOT` in your application's `pom.xml` and add the Vaadin pre-release repositories. For TypeScript changes, run `npm run build && npm pack` in the package and then `npm install <path-to-tgz>` in the application, removing the package.json entry afterwards.

How do I control who can call a Hilla endpoint?

The readme's example combines `@BrowserCallable`, which marks the class as callable from the browser, with `@AnonymousAllowed`, which allows the call without authentication. Access rules therefore live in Java annotations next to the methods.

Official sources

  1. License: Apache-2.0
  2. Project website
  3. README
  4. Releases
  5. vaadin/hilla on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/vaadin-hilla.svg)](https://hysenlabs.com/projects/vaadin-hilla)

Was this useful?