REST and Services

This section documents the current Vaadin release line — Vaadin 24 LTS / 25.x, Java 17+, Spring Boot 3 / Jakarta EE 10 — as published at the official Vaadin documentation, which is the reference these pages are written and verified against. No specific patch version is pinned. Flow (server-side Java) is the authoring style used throughout, with Hilla / React shown where it differs; Vaadin 7 and the pre-Flow architecture appear only as migration contrast.

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production, since Vaadin ships major releases roughly twice a year and its ecosystem iterates.

This section’s bibliography lists the reference material consulted while preparing these pages.

A Vaadin UI talks to the server through Vaadin’s own channel, so it needs no REST API for itself. REST still matters at the edges: exposing data to other clients, and consuming third-party APIs. This page follows the REST API guide and Business Logic.

A service layer

Put domain logic in @Service beans, not in views. A view is a thin adapter: gather input, call a service, render the result. The same service then backs a Flow view, a Hilla endpoint (Hilla and React Views), a REST controller, and a scheduled job — one implementation, many entry points.

@Service
public class InvoiceService {
    @Transactional
    public Invoice issue(IssueInvoiceCommand command) { /* ... */ }
    public List<Invoice> outstanding() { /* ... */ }
}

Exposing REST alongside Flow

A Spring @RestController and Flow routes live in the same application under different paths — Flow serves the UI, the controller serves /api/**:

@RestController
@RequestMapping("/api/invoices")
public class InvoiceController {

    private final InvoiceService service;
    public InvoiceController(InvoiceService service) { this.service = service; }

    @GetMapping
    public List<Invoice> outstanding() { return service.outstanding(); }

    @PostMapping
    public ResponseEntity<Invoice> issue(@RequestBody @Valid IssueInvoiceCommand cmd) {
        return ResponseEntity.status(HttpStatus.CREATED).body(service.issue(cmd));
    }
}

Secure /api/** in the same SecurityFilterChain as the Vaadin routes — see Security. If a browser client on another origin will call this API, configure CORS deliberately; What is CORS? explains the exchange.

Calling an external API from a view

Do the call on a background thread, then push the result with UI.access(…​) so the UI thread is never blocked (Server Push):

private final RestClient rest = RestClient.create("https://api.example.com");

private void loadRates() {
    UI ui = UI.getCurrent();
    executor.execute(() -> {
        Rates rates = rest.get().uri("/rates").retrieve().body(Rates.class);
        ui.access(() -> {
            table.setItems(rates.entries());
            spinner.setVisible(false);
        });
    });
}

Use WebClient where you want reactive streaming, RestClient (or the older RestTemplate) for a plain blocking call. Keep the base URL and credentials in configuration, not in the view.

Endpoint or REST?

Use When

Hilla @BrowserCallable

The consumer is your own Hilla / React frontend — you get a generated, type-safe client and shared validation with no API contract to hand-maintain.

REST @RestController

The consumer is a third party, a mobile app, another service, or anything that needs a stable, documented HTTP contract (OpenAPI, versioned paths).

See also