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.
- 1. Introduction
- 2. Architecture overview
- 3. HTTP-Client in Java
- 4a. Der REST-Service als Adapter
- 4b. Der REST-Endpoint in Core Java
- 5. Type-safe data models with records
- 6. Error handling and robustness
- 7. Integration in die Vaadin-UI
- 8. Production-ready safeguards
- 9. Asynchronous extension with CompletableFuture 1. When and how to integrate non-blocking HTTP calls into UI workflows 2. Starting point: Blocking REST calls 3. Solution: Non-blocking via CompletableFuture 4. Error handling in the futures chain 5. Progress indicator and UI states 6. Combination with multiple requests
- 10. Conclusion and outlook 1. REST adapters as a stable bridge in service-oriented architectures 2. Outlook: REST in modular and service-oriented Vaadin applications 3. What remains?
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:
- UI layer: It contains the Vaadin views and components that work exclusively with Java data objects. No network communication or JSON processing takes place here.
- Service shift: It mediates between the UI and integration, potentially coordinates multiple adapters, and is responsible for domain-specific business logic. This layer is unaware of the technical details of the REST protocol.
- Adapter layer: It provides the actual REST access. This includes HTTP calls, header management, timeout handling, JSON deserialization, and error evaluation. This layer has no UI classes or interactive logic.
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:
HttpClient client = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(5))
.version(HttpClient.Version.HTTP_2)
.build();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:
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/data"))
.header("Accept", "application/json")
.GET()
.build();The execution is synchronous with:
HttpResponse
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
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:
- Structure and execution of the HTTP request,
- Interpretation of the HTTP response code,
- Transformation of the response data (e.g. JSON) into a Java model,
- optional: logging, header management, error handling and retry logic.
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:
import java.io.IOException;
import java.net.URI;
import java.net.http.*;
import java.time.Duration;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.example.model.DataObject;
public final class ExternalApiService {
private final HttpClient client;
private final ObjectMapper mapper;
private final URI endpoint;
public ExternalApiService(String baseUrl) {
this.client = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(5))
.build();
this.mapper = new ObjectMapper();
this.endpoint = URI.create(baseUrl + "/data");
}
public DataObject fetchData() throws IOException, InterruptedException {
HttpRequest request = HttpRequest.newBuilder()
.uri(endpoint)
.header("Accept", "application/json")
.GET()
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 200) {
throw new IOException("Unexpected status code: " + response.statusCode());
}
return mapper.readValue(response.body(), DataObject.class);
}
}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:
import com.sun.net.httpserver.HttpServer;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpExchange;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
public class SimpleRestServer {
public static void main(String[] args) throws IOException {
HttpServer server = HttpServer.create(new InetSocketAddress(8080), 0);
server.createContext("/data", new DataHandler());
server.setExecutor(null); // default executor
server.start();
System.out.println("Server is running on http://localhost:8080/data");
}
static class DataHandler implements HttpHandler {
private final ObjectMapper mapper = new ObjectMapper();
@Override
public void handle(HttpExchange exchange) throws IOException {
if (!"GET".equals(exchange.getRequestMethod())) {
exchange.sendResponseHeaders(405, -1); // Method Not Allowed
return;
}
var data = new DataObject("abc123", "42.0");
String response = mapper.writeValueAsString(data);
exchange.getResponseHeaders().add("Content-Type", "application/json");
exchange.sendResponseHeaders(200, response.getBytes().length);
try (OutputStream os = exchange.getResponseBody()) {
os.write(response.getBytes());
}
}
}
public record DataObject(String id, String value) {}
}What is happening here, and why does it make sense#
- The server listens on port 8080 and accepts requests/data in contrast to.
- Only GET requests are accepted; all other HTTP methods receive a status code of 405.
- The result object is an ObjectMapper-serialised instance – this is the same data type (DataObject) that is also used in the REST adapter on the client side.
- The answer will be delivered with the appropriate Content-Type, specifically application/json.
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:
- More createContext(…)-Handler for additional endpoints
- Support for POST, PUT, DELETE with Payload-Parsing
- Path parameters through simple URI parsing
- Authentication logic via header evaluation
- Logging, metrics and request correlation as an extension
- Outsourcing to modules if multiple services are to be created
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
{
"id": "abc123",
"value": "42.0"
}A suitable Java record to represent this structure looks like this:
public record DataObject(String id, String value) {}
This record is:
- Unchangeable : Its fields are final and publicly readable, but not modifiable.
- Comparable : equals() and hashCode() are automatically implemented correctly.
- Structured : The signature also serves as documentation of the expected JSON format.
- Compact : Compared to classic POJOs, there is no boilerplate code.
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:
- semantically distinguishes between transport-related and technical errors,
- provides defined return values or exceptions to which the UI can react specifically,
- and established a comprehensible logging strategy.
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:
if (response.statusCode() == 404) {
return Optional.empty(); // targeted reaction to missing resource
} else if (response.statusCode() != 200) {
throw new IOException("Unexpected status: " + response.statusCode());
}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:
HttpRequest.newBuilder()
.timeout(Duration.ofSeconds(3))
.build();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 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. 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: The user remains able to act : **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.” _ **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: **Retry button or “Try again” action:
**A button can be displayed directly below the error message to re-execute the REST call. This leaves repeatability up to the user—useful, for example, in cases of temporary network errors or rate-limited APIs. **Fallback display or partial information:
**If previous data (e.g., from local storage or a previous session) is available, it can be displayed as a placeholder, along with a note that the current data cannot be loaded at the moment. **Visual placeholders or skeleton layouts:
**Instead of just displaying empty components, a visual framework can be displayed – for example, grey bars that indicate the expected content but symbolise it:“These data are not yet available.” _ **Consistent error design across the entire application:
**It’s recommended to establish a central component or utility class for displaying errors that’s used system-wide. This ensures consistent display regardless of whether a REST error occurs in the dashboard, a dialogue, or a detailed view. 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. 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. 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). 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. 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. Not all errors mean that a request fails permanently. Temporary DNS problems can often be intercepted by targeted retries**,** which can resolve503 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. 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());** } 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. 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. 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. 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. 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. 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: 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: 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: 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. Type safety and predictability: The response data is cast into records – compact, immutable data structures that ensure readability, consistency and clean debugging. 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. 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. 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 Codingpublic class DataView extends VerticalLayout {
private final ExternalApiService apiService;
public DataView() {
this.apiService = new ExternalApiService("https://api.example.com");
try {
DataObject data = apiService.fetchData();
showData(data);
} catch (IOException | InterruptedException e) {
showError(e.getMessage());
}
}
private void showData(DataObject data) {
add(new Span("ID: " + data.id()));
add(new Span("Wert: " + data.value()));
}
private void showError(String message) {
Notification.show("Error loading data: " + message, 5000, Notification.Position.MIDDLE);
}
}UI response to errors#
Visible feedback without blocking interaction#
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.
_
_Asynchronous extension (optional)#
UI ui = UI.getCurrent();
CompletableFuture.supplyAsync(() -> {
try {
return apiService.fetchData();
} catch (Exception e) {
throw new CompletionException(e);
}
}).thenAccept(data -> ui.access(() -> showData(data)))
.exceptionally(ex -> {
ui.access(() -> showError(ex.getCause().getMessage()));
return null;
});8. Production-ready safeguards#
Authentication, time limits, retry logic and logging – what you should pay attention to#
8.1 Authentication: Headers instead of framework magic#
private HttpRequest.Builder authenticatedRequest(URI uri) {
return HttpRequest.newBuilder()
.you(s)
.header("Authorization", "Bearer " + tokenProvider.getCurrentToken());
}8.2 Time limits: Protection against hanging services#
HttpClient client = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(5))
.build();
HttpRequest request = HttpRequest.newBuilder()
.timeout(Duration.ofSeconds(3))
.type(...)
.GET()
.build();8.3 Retry strategies: controlled and limited#
public DataObject fetchDataWithRetry() throws IOException {
int attempts = 3;
for (int i = 1; i <= attempts; i++) {
try {
return fetchData(); // normal method with HttpClient
} catch (IOException | InterruptedException e) {
if (i == attempts) throw new IOException("Maximum attempts reached", e);
try {
Thread.sleep(i * 500L); // linear backoff
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
throw new IOException("Retry aborted", ie);
}
}
}
throw new IllegalStateException("Unreachable code");
}8.4 Logging and Correlation#
8.5 Resilience through convention#
9. Asynchronous extension with CompletableFuture#
When and how to integrate non-blocking HTTP calls into UI workflows#
Starting point: Blocking REST calls#
try {
DataObject data = apiService.fetchData(); // synchronous call
showData(data); // direct display in the UI
} catch (IOException | InterruptedException e) {
showError(e.getMessage());
}Solution: Non-blocking via CompletableFuture#
UI ui = UI.getCurrent();
CompletableFuture.supplyAsync(() -> {
try {
return apiService.fetchData();
} catch (IOException | InterruptedException e) {
throw new CompletionException(e);
}
}).thenAccept(data -> ui.access(() -> showData(data)))
.exceptionally(ex -> {
ui.access(() -> showError(ex.getCause().getMessage()));
return null;
});Error handling in the futures chain#
Progress indicator and UI states#
Button refresh = new Button("Neu laden");
refresh.setEnabled(false);
add(new ProgressBar());
CompletableFuture.supplyAsync(() -> apiService.fetchData())
.thenAccept(data -> ui.access(() -> {
showData(data);
refresh.setEnabled(true);
}))
.exceptionally(ex -> {
ui.access(() -> {
showError("Loading failed");
refresh.setEnabled(true);
});
return null;
});Combination with multiple requests#
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#
Outlook: REST in modular and service-oriented Vaadin applications#
What remains?#
Related
Creating a simple file upload/download application with Vaadin Flow
What is CWE-1007: Insufficient visual discrimination of homoglyphs for you as a user?
If hashCode() lies and equals() is helpless
What are component-based web application frameworks for Java Developers?





