Site icon Sven Ruppert

Connecting REST Services with Vaadin Flow in Core Java

1. Introduction

Why REST integration in Vaadin applications should not be an afterthought

In modern web applications, communication with external services is no longer a special function, but an integral part of a service-oriented architecture. Even if Vaadin Flow, as a UI framework, relies on server-side Java logic to achieve a high degree of coherence between view and data models, the need to communicate with systems outside the application quickly arises. These can be simple public APIs—for example, for displaying weather data or currency conversions—as well as internal company services, such as license verification, user management, or connecting to a central ERP system.

The challenge here lies not in technical feasibility. Still, in structural embedding, REST calls should not appear “incidentally” in the view code, but rather be abstracted and controlled in a cleanly encapsulated service layer. Especially in safety-critical or highly available systems, it’s not just access that matters, but its failure behaviour, fallback strategies, and reusability.

Vaadin Flow offers no built-in mechanism for this, and that’s a good thing. By deliberately avoiding magical abstractions or framework coupling (such as Spring RestTemplate or Feign Clients), it allows maximum control over REST integration. With Java 24 and the now mature java.net.http.HttpClient, all required building blocks are available directly in the Core JDK – performant, maintainable and framework-independent.

This chapter, therefore, marks the beginning of a series of articles that demonstrate how to connect a Vaadin Flow application to an external REST endpoint, without external dependencies, but with a clear focus on readability, architecture, and security. The goal is not only to write functioning HTTP calls, but also to understand REST as part of a clean, modular software architecture.

2. Architecture overview

Separation of UI, service and integration – best practices without framework ballast

A clear architecture is the foundation of any maintainable application, regardless of whether it is monolithic, modular, or service-oriented. This is especially true for Vaadin Flow, as UI components are defined server-side in Java, thus eliminating any rigid boundaries between the underlying layers. This proximity between the user interface and backend logic is one of Vaadin’s most significant advantages, but it also presents an architectural challenge when integrating external systems.

The integration of a REST endpoint should therefore always be done via an intermediary service layer. This refers to a dedicated “adapter” that, on the one hand, encapsulates the connection to the external REST service and, on the other hand, provides a type-safe, stable API for the UI layer. The advantage is obvious: Changes to the format of the remote system or the protocol behaviour do not affect the application structure or the user interface—they remain isolated in the adapter module.

This separation can be elegantly implemented in a Vaadin project without additional frameworks. A standard layered structure consists of the following components:

This modular, three-part structure enables a testable and extensible framework. It enables the targeted simulation of REST access in unit tests or the use of alternative implementations (e.g., for offline modes or mocking in the development system) – all without requiring refactoring of the UI code.

Especially in Java 24, this principle can be excellently complemented with records, sealed types, and functional interfaces, making the resulting architecture not only stable but also concise and modern. The following figure (not included) illustrates how the data flows between the layers, from the UI request to the service, to the adapter, and back – always clearly separated but tightly interlinked by typed interfaces.

With this foundation in mind, we will discuss the technical implementation of REST communication in the next chapter, specifically using the HttpClient, which has been part of the JDK since Java 11 and has now established itself as a high-performance, well-tested standard.

3. HTTP-Client in Java

Introduction to java.net.http.HttpClient as a modern, native solution

Since Java 11, the java.net.http.HttpClient, a powerful, standards-compliant, and thread-safe HTTP client, is available and fully included in the JDK. In Java, this client has long been established as a reliable means of REST communication, especially in projects that deliberately avoid frameworks to achieve maximum control, portability, and predictability.

In contrast to previous solutions, such as HttpURLConnection, which were laborious and error-prone, the modern HttpClient features a straightforward, fluent-based API that supports both synchronous and asynchronous communication using CompletableFuture. It is resource-efficient, supports automatic redirects, HTTP/2, and can be equipped with configurable timeouts, proxies, and authentication.

In practice, the use of the HttpClient begins with its configuration as a reusable instance:

The HttpClient is immutable and thread-safe. Once created, it can be used for any number of requests. This is especially crucial in server applications, as repeated regeneration would not only be inefficient but also potentially problematic for resource utilisation.

For the specific request, a HttpRequest that encapsulates all parameters such as URI, HTTP method, headers and optional body data:

The execution is synchronous with:

HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());

Alternatively, the method sendAsync() can be used to realise non-blocking interactions based on CompletableFuture – a model that can be particularly useful for UI-oriented or reactive applications when blocking HTTP processing should be avoided.

What makes this API particularly special is the strict separation of request construction, client configuration, and result handling—no hidden magic, reflection, or XML-based configuration is used. Control lies entirely with the developer, and the API is understandable and type-safe.

The return is always made via a HttpResponse<T>, where the type T is determined by the selected BodyHandler—typically String, byte[], InputStream, or Void. This separation allows for efficient processing of both simple text responses and binary data.

In the next chapter, I’ll show how this HTTP infrastructure can be converted into a production-ready adapter class, including header handling, error checking, and JSON deserialization into Java records. The goal is not just functional communication, but maintainable, robust, and testable code.

4a. Der REST-Service als Adapter

Building a lean Java class for REST communication with object mapping

After the technical basics of the HttpClient are established in Java 24, the central question arises: How can REST calls be encapsulated so that they remain reusable, extensible, and UI-independent? The answer lies in the adapter principle, explicitly implemented as a standalone service class that encapsulates access to a specific REST endpoint and provides a type-safe API for the application core.

Such an adapter is responsible for:

However, these tasks shouldn’t be performed in the UI layer or presenter classes. Instead, a final-declared Java class is recommended, whose methods are specifically designed to load or send specific domain-specific data objects – for example: fetchUserData(), submitOrder(), validateLicenseKey().

A minimal adapter for a GET call with a JSON response could look like this:

The aim of this class is not to be flexible for any endpoint or generic API, but rather to be concrete and targeted to serve exactly one external use case. This clarity is an advantage, not a disadvantage: It facilitates refactoring, testability, and secure extension if, for example, new headers, authentication mechanisms, or error codes are introduced.

The class encapsulates the entire technical aspect of HTTP communication. It is entirely independent of the UI and has no views, components, or user interactions. It can therefore be easily integrated into JUnit tests—either directly or via interface derivation for mocking.

The record class used DataObject serves as a transfer object that is automatically deserialised from JSON by the ObjectMapper:

public record DataObject(String id, String value) {}

This combination of a concise object structure and a stable communication layer forms the backbone of a clean REST integration—without frameworks, without magic, but with a clear separation of responsibilities. Errors can be specifically intercepted, additional parameters can be easily incorporated, and the architecture remains transparent.

4b. Der REST-Endpoint in Core Java

How to create an HTTP endpoint without frameworks with minimal effort

A REST endpoint is essentially nothing more than a specialised HTTP handler. While Spring or JakartaEE use controllers, annotations, and automatic serialisation, a pure Java approach requires a bit more manual work, but is rewarded with complete control over behaviour, performance, security boundaries, and dependencies.

Since Java 18, the com.sun.net.httpserver.HttpServer API, a lightweight HTTP server, has been available. It is ideal for simple REST endpoints—for example, as a test mock, an internal microservice, or in this case, as a local data source for Vaadin. In combination with a JSON mapping (e.g.ObjectMapper from Jackson), it creates a REST backend without any external platform.

Minimal example: REST endpoint that returns JSON

The following class starts an HTTP server on port 8080, which, in a GET on /data, returns a JSON object:

What is happening here, and why does it make sense

This implementation is minimal but functional. It can be tested immediately, run locally, and queried from the Vaadin UI via the adapter described in Chapter 4a. It requires no XML, no DI container, no JAR hierarchies—just plain Java.

Outlook: Modular expansion of REST servers

A REST server based on this principle can be easily expanded:

This setup can be used particularly elegantly in development and testing, for example, to simulate external services or to reproduce specific error cases (404, 500).

5. Type-safe data models with records

How Java Records provide clarity and security

The data model plays a central role in the communication between a Vaadin Flow application and an external REST service. It bridges the gap between the JSON representation of the remote resource and the object representation processed in Java. The clearer, more type-safe, and immutable this model is, the more robust the application architecture becomes, especially when the REST interface changes or when used in parallel UI contexts.

Since Java 16, so-called Records– a language feature explicitly designed for this type of data structure: compact, immutable objects with semantically unambiguous data transport characteristics. A record is not a replacement for a complete domain entity with behaviour, but the ideal representation for structured response data from REST endpoints.

An example: A REST service delivers JSON responses like

A suitable Java record to represent this structure looks like this:

public record DataObject(String id, String value) {}

This record is:

Deserialization from JSON with an ObjectMapper (as shown in Chapter 4) works directly and without further annotations as long as the field names match. This promotes readability and reduces the risk of hidden deserialization errors.

Additionally, records are well-suited for use as DTOs (Data Transfer Objects). For example, when multiple external REST endpoints deliver different aspects of the same domain concept, each with its own response structure. Strict type safety of records then protects against unintentional mixing or incorrect field usage.

Records can also be nested to represent more complex JSON structures—for example, when an object contains a list of other objects or provides structured metadata. Here, too, developers benefit from the expressiveness, readability, and stability that records offer over traditional getter and setter classes.

In the context of a Vaadin application, this has an immediate positive effect: Records can be used directly in UI components, for example, for display in Grid, FormLayout or custom components. The data objects themselves contain no presentation logic, but remain purely structural—and this is precisely what is desirable in a separated architecture.

In the next chapter, we will demonstrate how this structured REST communication can be utilised in the UI layer and identify patterns that have proven successful in practice for effectively linking error handling, response logic, and user interaction.

6. Error handling and robustness

Handling status codes, IO problems and termination conditions

In practice, a REST call is more than just an HTTP request. It is an insecure operation over a potentially unstable medium, with numerous possible sources of error, including network problems, timeout behaviour, invalid response formats, rejected requests, or temporary server errors. The quality of a REST integration is therefore evident, not in successful communication, but instead in its behaviour in the event of a mistake.

Especially in Vaadin Flow applications that remain active on the server side for extended periods, a single failed REST call can result in a view not being initialised correctly, a user action being suspended, or incomplete data being displayed. Therefore, the goal is to implement error handling that:

HTTP status codes as a starting point

Even the interpretation of the response code requires care. While 200 OK and 201 Created usually indicate success, other codes such as 204 No Content, 404 Not Found, or 409 Conflict can be entirely intentional – and should not be treated as errors across the board. The business context is crucial: A “not found” error can be deliberate and trigger a specific UI response, such as an empty display or an alternative form.

The adapter should therefore not perform generic error handling across all code, but rather explicitly evaluate what makes sense in the respective application scenario. Example:

Dealing with IO errors

Transport errors – such as timeouts, DNS problems, or connection failures – typically trigger IOException or InterruptedException. These should not be swallowed in the adapter or RuntimeException. Instead, the caller is signalled that the response is not usable. An error message can then be displayed in the UI without the application entering an undefined state.

Logging with Thought

Errors that cannot be explained by user interaction should be logged on the server side, but not necessarily displayed to the user. A short Logger.warn(…) An entry can help here, for example, to make external API errors traceable without flooding log files with irrelevant details. It is recommended to use different log levels depending on the error type: INFO for normal non-detectable behaviour, WARN for inconsistent responses, and ERROR only for real failures or bugs.

Retry and Timeout

In production systems, it can be useful to automatically retry certain errors (e.g., 503 Service Unavailable or IOException) with backoff and limitation. However, this logic should not be implemented in the UI code, but rather in the adapter or a delegated “RetryHandler.” Timeouts should also be set explicitly:

Without defined time limits, a REST call can inadvertently lead to a blocking UI thread, especially when called synchronously.

Control instead of surprise

The central goal of any error handling is to ensure the Predictability of behaviour: A REST adapter should always provide defined semantics—either a correct result object, a declared exception, or an optional error that is explicitly checked. No hidden null values, no logic in catch blocks, no silent terminations.

The resulting UI remains able to react in a controlled manner, whether by displaying notifications, activating a retry button, or switching to offline data. The result is not only a more robust architecture but also a better user experience.

In the next chapter, I will show how these REST results are integrated into the UI layer of a Vaadin application, including concrete examples of loading operations, state changes, and error messages in the user context.

7. Integration in die Vaadin-UI

Example use in the view layer – synchronous and understandable

The clean separation between REST communication and UI logic is a central principle of maintainable software architecture. However, there must come a point where external data actually ends up in the user interface—be it in a table, a form, or as part of an interactive visualisation. The integration of the REST adapter into the Vaadin UI is ideally done in this case. synchronous, type-confident and consciously visible in the code – instead of hidden “magic” or automatic binding.

At the centre is a view, e.g., a class that is a VerticalLayout or Composite<Div> and inherits. The REST adapter can be called within this view, typically when constructing the view or during a targeted user interaction. The following example demonstrates a simple use case: When loading the view, a dataset should be retrieved from an external REST service and displayed.

This type of integration is deliberately kept simple, but it demonstrates a central principle: The REST adapter is an explicit component of view initialisation. There’s no hidden binding, no automatic magic, just traceable, step-by-step data flow. This not only facilitates debugging but also makes state transitions visible and controllable.

UI response to errors

Visible feedback without blocking interaction

The integration of an external REST endpoint into a Vaadin Flow application must not only be technically reliable, but it must also, and most importantly, behave predictably and stably within the user interface. In production scenarios, it is perfectly normal for REST services to be temporarily unavailable, provide slow responses, or fail with an error status, such as 500 Internal Server Error, 403 Forbidden, or 429 Too Many Requests. What matters is not the error itself, but how it affects the user experience.

In the example shown, a non-blocking notification is used in the event of an error. It appears in the centre of the screen and briefly informs the user that the data loading failed. This approach is recommended for several reasons:

  1. The user remains able to act:
    A notification doesn’t interrupt the interaction flow. No dialogue opens, no loading process freezes, and no navigation is blocked. The application remains fully usable, which is especially beneficial in multi-part views or for later, optional loading operations.
  2. The error message is visible in context:
    Instead of hiding technical details in logs or simply not displaying “something,” the user is actively informed about the error—transparently, but without technical depth. The information isn’t intended for analysis, but rather to correct expectations: “Something should have appeared here, but is currently unavailable.”
  3. The application does not exit into an undefined state:
    There’s no exception chaining into the UI thread, no empty interface without explanation, and no sudden switching to another route. The user experience remains consistent, ​​even in the event of an error.

For more sophisticated scenarios, further reaction patterns are also available:

In summary, this approach pursues one central goal: Errors may occur, but they should not take over the control flow. The UI remains in the hands of the user, not the infrastructure. This creates a resilient interface that looks professional and inspires trust, even when systems in the background aren’t working perfectly.

Asynchronous extension (optional)

In some cases, asynchronous integration can be useful – for example, when dealing with long loading times or interactions that must not block. This can also be achieved with Vaadin and the HttpClient. A typical strategy is to load the data in a background thread and then use UI.access(…) back to the UI thread:

This variant is not necessary, but it is helpful in scenarios with many concurrent REST requests or slow-responding APIs.

Integrating a REST adapter into a Vaadin flow view is best done in a structured, explicit, and UI-specific manner. The view is not responsible for constructing the request or interpreting the HTTP code—it only consumes the Java objects provided by the adapter. Errors are handled, states are modelled consciously, and the user interface remains responsive and understandable.

In the next chapter, we’ll take a look at production-ready extensions: authentication, header handling, retry strategies, and logging—everything that makes REST access robust and secure.

8. Production-ready safeguards

Authentication, time limits, retry logic and logging – what you should pay attention to

A REST adapter that works reliably in development and test environments is far from production-ready. As soon as a Vaadin Flow application is embedded in real infrastructures—with access to external APIs, network latencies, security policies, and operational requirements—additional protection mechanisms must be established. These not only address error cases, but also, and most importantly, Non-fault cases under challenging conditions, such as authentication, latency, rate limiting, logging requirements, or the protection of sensitive data.

The following discussion demonstrates how production-ready REST adapters can be developed using the resources of the Java Development Kit (JDK).

8.1 Authentication: Headers instead of framework magic

External APIs typically require authentication, which can be in the form of a static token, a Basic Auth combination, or an OAuth 2.0 bearer token. In production-based scenarios, multiple variants are often used simultaneously – for example, an API that expects separate public and admin keys.

Instead of shifting this logic to the HTTP client construction, it is recommended to provide your helper methods in the adapter, for example:

This not only isolates the technical mechanism (header), but also abstracts the source of supply (e.g., token rotation, expiration time). The token itself can be renewed regularly, read from a secrets store, or dynamically calculated, without affecting the call point in the UI code.

8.2 Time limits: Protection against hanging services

Without explicitly set time limits, the HttpClient theoretically has unlimited waiting time for a response, which can be fatal in server-side applications. To control latency and protect UI threads, timeouts should be set both in the client and per request:

This separation allows global parameters to be differentiated from the behaviour of individual calls – e.g., for particularly sensitive endpoints or third-party interfaces with historically fluctuating response times.

8.3 Retry strategies: controlled and limited

Not all errors mean that a request fails permanently. Temporary DNS problems can often be intercepted by targeted retries, which can resolve 503 Service Unavailable errors or connectionless gateways – provided the retries are limited, staggered in time, and context-dependent.

An example of a simple Retry loop without an external library:

Retry logic must never grow uncontrollably, so that it performs logging and that it is only active in the case of explicitly temporary errors, not in the case of 401, 403 or 404.

8.4 Logging and Correlation

In production-ready systems, REST calls are a relevant component of auditing, error analysis, and performance monitoring. Therefore, each call should be systematically recorded in the log, but in a differentiated manner:

Additionally, a correlation token should be included with each request, for example, as a UUID in the X-Correlation-ID-Header, allowing REST calls to be traced across multiple systems. This can also be encapsulated centrally in the adapter:

private HttpRequest.Builder withCorrelation(HttpRequest.Builder builder) {

    return builder.header(“X-Correlation-ID”, UUID.randomUUID().toString());

}

8.5 Resilience through convention

A stable REST adapter is not “intelligent” in the sense of being dynamic, but somewhat predictable, complete, and conservative. Every response is either a result or an exception—never a silence. The data formats are stable and defensively parsed. If expectations are not met, a defined termination occurs. This protects both the UI logic and the user experience, forming the basis for maintainable systems with precise error semantics.

9. Asynchronous extension with CompletableFuture

When and how to integrate non-blocking HTTP calls into UI workflows

In Vaadin Flow applications, the code is traditionally structured synchronously – views respond to user actions, load data, and display results. However, as soon as REST calls come into play, which can take longer than a few milliseconds, the question inevitably arises: How can I offload HTTP communication without blocking the UI thread – and without having to resort to a reactive framework?

The answer to this is provided by the JDK integrated class CompletableFuture. It allows you to declaratively model asynchronous workflows, precisely control concurrency, and return the result to the UI in a controlled manner upon success or failure. In conjunction with Vaadin’s server-side UI model, only one measure is crucial: access to UI components must occur in the correct thread context.

Starting point: Blocking REST calls

In a synchronous example, the call typically looks like this:

As long as response times are low and the view is already being constructed synchronously, this is entirely unproblematic. It becomes critical when REST calls follow user actions—for example, after clicking “Refresh” or during filter operations on large data sets. Here, a blocking call would lead to a noticeable delay in the UI.

Solution: Non-blocking via CompletableFuture

To offload the REST call without blocking the UI, the access can be moved to a separate background thread, and the result can be safely returned to the UI later:

What is happening here is architecturally remarkable:

This separation of calculation (REST call) and presentation (Vaadin components) corresponds to a classic principle of responsive design, only on the server-side level.

Error handling in the futures chain

Another advantage: Error handling is modelled clearly and separately, via. exceptionally(…) The original exception object is available within this method. In many cases, a targeted display for the user is sufficient; ​​however, logging, metric collection, or retry logic can be added if necessary.

Progress indicator and UI states

In asynchronous scenarios, it’s recommended to also provide visual feedback about the loading status—for example, through a spinner, a progress bar, or the targeted deactivation of interaction elements. Example:

Combination with multiple requests

Parallel REST calls can also be made by CompletableFuture.allOf(…) Orchestrate. Data from multiple services can be loaded independently and then analysed together. This technique is beneficial for dashboards, complex forms, or multi-API compositions.

Conclusion of this chapter:
CompletableFuture provides an elegant way to integrate REST calls into Vaadin in a non-blocking manner, without any additional libraries. The control is type-safe and has minimal overhead. Combined with Vaadin’s UI.access(…) mechanism, this creates reactive yet deterministic behaviour that is both technically and ergonomically compelling.

10. Conclusion and outlook

REST adapters as a stable bridge in service-oriented architectures

The integration of external REST services into Vaadin Flow applications is far more than a technical detail. It is a conceptual component of a modular, maintainable, and externally communicative application. This blog post has shown how a REST connection without framework dependencies can be implemented in a structured and robust manner, purely using the onboard resources of the Java Core JDK from version 11, in idiomatic form for Java 24.

The focus is on an adapter that has three key features:

  1. Technical clarity: The adapter takes full responsibility for handling HTTP requests, deserialising JSON, error handling, and optional authentication. The UI layer remains completely decoupled from it.
  2. Type safety and predictability: The response data is cast into records – compact, immutable data structures that ensure readability, consistency and clean debugging.
  3. Error robustness and extensibility: With time limits, logging, retry strategies and asynchronous access, the adapter can be made production-ready – without magic, but with conscious design.

What initially starts as a simple way to “just add an HTTP call” to an application becomes architecturally viable communication module This approach is particularly advantageous for applications that grow over time: The REST adapter can evolve into a module that bundles various external systems, maintains version control, remains testable, and still interacts cleanly with the UI.

Outlook: REST in modular and service-oriented Vaadin applications

With increasing complexity and system size, the need to distribute responsibilities grows: Data storage, business logic, UI, and integrations should be independently deployable, testable, and versionable. A typical architecture then moves toward:

In all these scenarios, the REST adapter remains a central link – lean, deliberately modelled, but with precise semantics. Precisely because Vaadin Flow doesn’t attempt to automatically abstract or generically bind REST, the developer retains maximum control over requests, data structures, lifecycles, and user interaction.

What remains?

Anyone building a Vaadin application that consumes REST should not treat REST as a workaround or side path, but as an equal interface to the world outside the UI. A stable, testable REST connection is a quality feature. And it can be achieved in Core Java with surprisingly few resources – if it is implemented consciously, structured, and in transparent layers.

Happy Coding

Exit mobile version