Today’s update introduces another practical development step for the URL shortener. After the last few days were dedicated to UI refinement and the better structuring of detailed dialogues and form logic, the focus is now on an aspect that plays a significant role in the everyday life of many users: the flexible management of multiple aliases per target URL.
You can find the source code for this development status on GitHub underhttps://github.com/svenruppert/url-shortener/tree/feature/advent-2025-day-06
Here are the relevant screenshots of the Vaadin application.



What was previously only possible sequentially – an alias, a destination address – will now become a dynamic workspace. Users can add new aliases to existing short links, create variants for different use cases, or manage parallel campaigns without creating new records each time. The MultiAliasEditorStrict forms the core of this new workflow: inline validation, fast feedback, clear status indicators and an event flow that propagates changes directly into the OverviewView. The application responds immediately to user input and keeps the entire interface consistent, without requiring manual reloads or context switching.
But this is much more than just a UI comfort update. The increased flexibility in the alias structure inevitably leads to higher demands on server-side stability – especially on the stability of forwarders. Once several aliases point to the same destination address, the redirect logic must be deterministic and reliably detect erroneous requests. Therefore, a consistent, centralised request check was introduced in parallel with the UI extension to secure all forwarding operations and ensure predictable HTTP behaviour.
These changes combine two sides of the same coin: greater user freedom in their daily work and a robust technical foundation that reliably supports it. In the following, we will therefore take a closer look at the revised redirect implementation and the new security mechanisms that underpin stable, traceable, and maintainable redirect behaviour.
More Robust Redirects and HTTP Security#
In parallel with the extensive improvements to the user interface, the server-side component of the URL shortener has also been revised. A central focus was on increasing the stability and security of HTTP communication. Especially when handling redirects, several methods have had to carry out similar checks – for example, whether the correct HTTP method was used or whether a request was incomplete. These repetitions led to inconsistent behaviour and made maintenance difficult.
In the process, a consistent request-handling system was introduced to centralise these checks. The code for the RedirectHandler clearly shows how a robust redirect is now implemented:
package com.svenruppert.urlshortener.server.handler;
import com.svenruppert.urlshortener.core.http.HttpStatus;
import com.svenruppert.urlshortener.core.store.UrlMappingStore;
import com.svenruppert.urlshortener.server.util.RequestMethodUtils;
import com.svenruppert.urlshortener.server.util.ResponseWriter;
import com.sun.net.httpserver.HttpExchange;
import java.io.IOException;
public class RedirectHandler
implements HttpHandler, HasLogger {
private final UrlMappingLookup store;
public RedirectHandler(UrlMappingLookup store) {
this.store = store;
}
@Override
public void handle(HttpExchange exchange)
throws IOException {
if (! RequestMethodUtils.requireGet(exchange)) return;
final String path = exchange.getRequestURI().getPath(); e.g. "/ABC123"
if (path == null || !path.startsWith(PATH_REDIRECT)) {
exchange.sendResponseHeaders(400, -1);
return;
}
final String code = path.substring((PATH_REDIRECT).length());
if (code.isBlank()) {
exchange.sendResponseHeaders(400, -1);
return;
}
logger().info("looking for short code {}", code);
Optional<String> target = store
.findByShortCode(code)
.map(ShortUrlMapping::originalUrl);
if (target.isPresent()) {
exchange.getResponseHeaders().add("Location", target.get());
exchange.sendResponseHeaders(302, -1);
} else {
exchange.sendResponseHeaders(404, -1);
}
}
}The RedirectHandler class ensures that only valid GET requests are accepted. Invalid methods – such as POST or DELETE – are caught by the RequestMethodUtils helper class. This prevents inadvertent writes to a read-only resource.
The check logic in RequestMethodUtils is deliberately kept simple and centralises all recurring security checks. The following excerpt shows how she works internally:
package com.svenruppert.urlshortener.server.util;
import com.svenruppert.urlshortener.core.http.HttpStatus;
import com.sun.net.httpserver.HttpExchange;
import java.io.IOException;
public class RequestMethodUtils {
public static boolean requireGet(HttpExchange exchange)
throws IOException {
HasLogger.staticLogger().info("handle ... {} ", exchange.getRequestMethod());
Objects.requireNonNull(exchange, "exchange");
if (!" GET".equalsIgnoreCase(exchange.getRequestMethod())) {
writeJson(exchange, METHOD_NOT_ALLOWED, "Only GET is allowed for this endpoint.");
return false;
}
return true;
}
SNIP...
}This helper class serves as a compact security wall: every request-processing method calls it at the beginning. This creates predictable, consistent behaviour across the HTTP stack. This is particularly relevant for forwarding, as unauthorised or erroneous methods are reliably blocked.
Response generation is handled by ResponseWriter, which handles clean header configuration and uniform error output. In contrast to earlier implementations, which manually generated JSON or text responses in several places, there is now a central method:
public static void writeJson(HttpExchange ex, HttpStatus httpStatus, String message)
throws IOException {
HasLogger.staticLogger().info("writeJson {}, {}", httpStatus, message);
byte[] data = message.getBytes(UTF_8);
Headers h = ex.getResponseHeaders();
h.set(CONTENT_TYPE, JSON_CONTENT_TYPE);
ex.sendResponseHeaders(httpStatus.code(), data.length);
try (OutputStream os = ex.getResponseBody()) {
os.write(data);
} catch (Exception e) {
HasLogger.staticLogger().info("writeJson {} (catch)", e.getMessage());
byte[] body = ("{\"error\":\"" + e.getMessage() + "\"}").getBytes(StandardCharsets.UTF_8);
try {
ex.getResponseHeaders().set(CONTENT_TYPE, JSON_CONTENT_TYPE);
ex.sendResponseHeaders(INTERNAL_SERVER_ERROR.code(), body.length);
ex.getResponseBody().write(body);
} catch (Exception ignoredI) {
HasLogger.staticLogger().info("writeJson (catch - ignored I) {} ", ignoredI.getMessage());
}
} finally {
try {
ex.close();
} catch (Exception ignoredII) {
HasLogger.staticLogger().info("writeJson (catch - ignored II) {} ", ignoredII.getMessage());
}
}
}The merging of these helper classes yields a well-structured server architecture. Each handler is leaner, easier to read, and easier to test. For users, this results in a more reliable redirect: error messages are displayed consistently, HTTP status codes match the expected behaviour exactly, and unauthorised methods do not cause side effects.
This also gives the URL shortener additional robustness at the protocol level. Error handling is traceable, behaviour is transparent, and server components follow the same principles of clarity and reusability introduced in the UI. In this way, the system’s technical integrity is strengthened, as is user confidence in its stability.
Conclusion: User Comfort Meets Clean Design#
With the completion of this development step, the URL shortener has developed into a much more mature system – both technically and conceptually. What started as a simple platform for creating and managing individual shortlinks has become a full-fledged application that balances user needs, security, and maintainability. The transition from a one-alias logic to a flexible multi-alias approach is probably the most visible progress. Still, the real strength of this update lies in the design’s consistent coherence.
From the user’s perspective, a tool is now available that makes workflows more fluid, consistent, and traceable. The detail-dialogue and the Create View form a harmonious pair: both use the same editor, the exact validation mechanisms and the same event flow. Actions are immediately visible, and changes are automatically displayed. The result is a surface that hides its technical complexity and instead focuses on efficiency and clarity.
A clear development is also noticeable on the architectural level. The interplay of Vaadin EventBus, component-based UI structure and centralised HTTP handling has resulted in a maintenance-friendly, extensible system. Every level – from the user interface to the validation logic to the server – follows the same principles: clear responsibilities, loose coupling, and transparent behaviour. This uniformity is evident not only in the code but above all in everyday use.
This step thus sets a clear benchmark for future project expansions. The concept of multiple aliases, the consistent reactivity of the UI, and the clean server design will not remain isolated features in the coming development phases; they will serve as a structural basis. Working on the user experience has shown that technical design and interaction quality are not mutually exclusive – on the contrary, they reinforce each other when they are consistently coordinated.
Cheers Sven





