Part II – UrlShortener – first Implementation
1. Introduction to implementation
1.1 Objectives and differentiation from the architectural part
The first part of this series focused on theory: We explained why a URL shortener is not just a convenience tool, but a security-relevant element of digital infrastructure. We discussed models for collision detection, entropy distribution, and forwarding logic, as well as analysed architectural variants – from stateless redirect services to domain-specific validation mechanisms.
This second part now turns to the concrete implementation. We develop a first working version of a URL shortener in Java 24, consciously without the use of frameworks such as Spring Boot or Jakarta EE. The goal is to achieve a transparent, modularly structured solution that provides all core functions: URL shortening, secure storage of mappings, HTTP forwarding, and optional presentation via a Vaadin-based user interface.
Particular attention is paid to the clean separation between encoding, storage, API, and UI. The entire application is delivered as a monolithic artefact – specifically, a classic WAR file, which is compatible with standard servlet containers such as Jetty or Tomcat. This decision enables rapid deployment and facilitates onboarding and testability.
1.2 Technological guardrails: WAR, Vaadin, Core JDK
The implementation is based on a modern, yet deliberately lean technology stack. Only the built-in tools of the JDK and Vaadin Flow are used as the UI framework. The decision to use Vaadin is based on the requirement to implement interactive administration interfaces without additional JavaScript or separate front-end logic, entirely in Java.
The project is a multi-module structure. The separation between core logic, API layer, and UI remains visible and maintainable in the code. Maven is used as the build tool, supplemented by a WAR packaging plugin that creates a classic servlet deployment structure. The use of Java 24 enables the utilisation of modern language tools, including records, pattern matching, sequenced collections, and virtual threads.
The goal is a production-oriented, comprehensible implementation that can be used both as a learning resource and as a starting point for further product development.
1.3 Overview of the components
The application consists of the following core components:
- A Base62-Encoder, which transforms consecutive IDs into URL-compatible short forms
- A Mapping-Store, which manages the mapping between the short link and the original URL
- A REST service, which allows URL shortening and resolution via redirect
- One optional UI based on Vaadin Flow for manual management of mappings
and a configurable WAR deployment that integrates all components
The architecture follows the principle: “As little as possible, as much as necessary.” Each part of the application is modular and allows for later splitting if necessary – for example, into separate services for reading, writing or analysis.
In the next chapter, we will focus on the concrete project structure and the module structure.
2. Project structure and module organisation
2.1 Structure of a modular WAR project
The first executable version of the URL shortener is realised as a monolithic Java application, which is in the form of a classic WAR. The project’s structure is based on a clear, layered architecture, which is prepared for later decomposition. The project is organised modularly, distinguishing between core logic, HTTP interface, and user interface. This separation not only allows for better maintainability but also forms the basis for the service decomposition planned in Part III or IV.
The project consists of three main modules:
- shortener-core: Contains all business logic, including URL encoding, data model and store interfaces.
- shortener-api: Implements the REST API based on the Java HTTP server (com.sun.net.httpserver.HttpServer).
- shortener-ui-required: Optional UI module with Vaadin Flow for managing and visualising mappings.
These modules are distributed via a central WAR project (shortener war), which handles the delivery configuration and combines all dependencies. The WAR project is the only one that handles servlet-specific aspects (e.g., web.xml, I require a Servlet) – the remaining modules remain entirely independent of it.
2.2 Separation of domain, API and UI code
2.2 Separation of Domain, API, and UI Code
The modularisation of the project is based on the principle of technological isolation: The core business logic must know nothing about HTTP, servlet containers, or UI frameworks. This way, it remains fully testable, interchangeable, and reusable—for example, for future CLI or event-based variants of the shortener.
The core module defines all central interfaces (UrlMappingStore, ShortCodeEncoder) as well as the base classes (ShortUrlMapping, Base62Encoder). These components do not contain any I/O logic.
The api module is responsible for parsing HTTP requests, routing, and generating redirects and JSON responses. It accesses the core logic internally but remains detached from UI aspects.
The ui-vaadin module uses Vaadin Flow to implement a web-based interface, integrates the core logic directly, and is initialised in the WAR via a dedicated servlet definition.
Additional modules can be optionally added—for example, for persistence, monitoring, or analysis—without compromising the coherence of the structure.
2.3 Tooling und Build (Maven, JDK 24, WAR-Plugin)
The build system is based on the current version of Maven. Each module is managed as a standalone Maven project with its pom.xml, with shortener-war configured as the parent WAR application. WAR packaging is handled using the standard servlet model, allowing the resulting file to be easily deployed in Tomcat, Jetty, or any Servlet 4.0+ compatible container.
Java 24 is required at runtime, which is particularly relevant for modern language features such as record, pattern matching, and SequencedMap. Release 21 or higher is recommended as the target platform to ensure compatibility with modern runtimes.
Vaadin Flow integration is handled purely on the server side via the Vaadin Servlet and does not require a separate front-end build pipeline. Resources such as themes and icons are loaded entirely from the classpath.
3. URL encoding: Base62 and ID generation
3.1 Design of a stable short link scheme
The key requirement for a URL shortener is to generate unique, shortest possible character strings that serve as keys for accessing the original URL. To meet this requirement, the first implementation utilises a sequential ID scheme that assigns a consecutive numeric ID to each new URL. This ID is then converted into a URL-compatible format—specifically, Base62.
Base62 includes the 26 uppercase letters, the 26 lowercase letters, and the 10 decimal digits. Unlike Base64, Base62 does not contain special characters such as +, /, or =, making it ideal for URLs: The generated strings are readable, system-friendly, and easily transferable in all contexts.
The resulting scheme is thus based on a two-step process:
- Assigning a unique numeric ID (e.g., 1, 2, 3, …)
- Converting this ID to a Base62 string (e.g., 1 → b, 2 → c, …)
This method guarantees unique and unguessable codes, especially if the ID count does not start at 0 or if codes are additionally shuffled.
3.2 Implementation of a Base62 encoder
The Base62-Encoder is used as a standalone utility class in the core module. It contains two static methods:
- encode(long value): converts a positive integer to a Base62 string
- decode(String input): converts a Base62 string back to an integer
The alphabet is defined internally as a constant character string, and the conversion process is carried out purely mathematically, comparable to the representation of a number in another place value system.
This implementation creates a deterministic, stable, and thread-safe encoder that requires no external libraries. The resulting codes are significantly shorter than the underlying decimal number and contain no special characters—a key advantage for embedded or typed links.
For exceptional cases—such as custom aliases—the encoder remains optional, as such aliases can be stored directly as separate strings. However, by default, the Base62 encoder is the preferred method.
3.3 Alternatives: Random, Hashing, Custom Aliases
In addition to the sequential approach, there are other methods for generating short links that can be considered in later stages of development:
- Random-based tokens (z. B. UUID, SecureRandom) increase unpredictability, but require collision detection and additional memory overhead.
- The hashing process (e.g., SHA-1 of the destination URL) guarantees stability but is prone to collisions under high load or identical destination addresses.
- Custom aliases enable readable, short links (e.g., /helloMax), but require additional checking for collisions, syntactic validity, and protection of reserved terms.
For the first version, we focus on the sequential model with Base62 transformation – a stable and straightforward approach.
3.4 Implementation: Base62Encoder.java
The goal is to provide a simple utility class that converts integers to Base62 strings and vice versa. This class is thread-safe, stateless, and implemented without any external dependencies.
First, the complete source code:
public final class Base62Encoder {
private static final String ALPHABET
= "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
private static final int BASE = ALPHABET.length();
private Base62Encoder() {
}
public static String encode(long number) {
if (number < 0) {
throw new IllegalArgumentException("Only non-negative values supported");
}
StringBuilder result = new StringBuilder();
do {
int remainder = (int) (number % BASE);
result.insert(0, ALPHABET.charAt(reminder));
number = number / BASE;
} while (number > 0);
return result.toString();
}
public static long decode(String input) {
if (input == null || input.isEmpty()) {
throw new IllegalArgumentException("Input must not be null or empty");
}
long result = 0;
for (char c : input.toCharArray()) {
int index = ALPHABET.indexOf(c);
if (index == -1) {
throw new IllegalArgumentException("Invalid character in Base62 string: " + c);
}
result = result * BASE + index;
}
return result;
}
}
3.5 What is happening here – and why?
This class encapsulates all Base62 encoding behaviour in two static methods. The character set consists of digits (0–9), lowercase letters (a–z), and uppercase letters (A–Z), resulting in exactly 62 different characters.
- The method encode(long number) converts an integer into its inverse representation in the base 62 place value system. The remainder of the division by 62 is successively calculated, and the corresponding character is inserted. The result is a short, URL-friendly string.
- The decode(String input) method reverses this process: it converts a Base62 string back into its numeric representation. Each character is replaced by its index in the alphabet and weighted accordingly.
This implementation is robust against invalid input, operates entirely in memory, and can be used directly in ID generation or URL mappings.
Implementation: ShortCodeGenerator.java
The class abstracts ID generation from the concrete storage mechanism. It is suitable for both the in-memory version and future persistent variants. The generator is thread-safe and uses only JDK resources.
public final class ShortCodeGenerator {
private final AtomicLong counter;
public ShortCodeGenerator(long initialValue) {
this.counter = new AtomicLong(initialValue);
}
public String nextCode() {
long id = counter.getAndIncrement();
return Base62Encoder.encode(id);
}
public long currentId() {
return counter.get();
}
}
Explanation
This class encapsulates a sequential counter that is incremented each time nextCode() generates a new, unique short code. The output is based on a monotonically increasing ID encoded into a Base62 string.
The getAndIncrement() method of AtomicLong is non-blocking and thus highly performant, even under high concurrency conditions. The generated code is unambiguous, compact, and deterministic—properties that are ideal for auditing, logging, and subsequent analysis.
The constructor allows you to configure the initial value. This is useful, for example, if you want to continue a persistent counter after a restart.
Example usage (e.g. in the mapping store)
ShortCodeGenerator generator = new ShortCodeGenerator(1L);
String shortCode = generator.nextCode(); // z. B. "b", "c", "d", ...
4. Mapping Store: Storage of the mapping
4.1 Interface-Design: UrlMappingStore
The mapping store forms the heart of the URL shortener. It manages the mapping between a short code (e.g.kY7zD) and the corresponding target URL (https://example.org/foo/bar). At the same time, it takes control over multiple uses, expiration times, and potential aliases.
In the first stage of development, a purely in-memory-based solution is used. This is fast, simple, and ideal for starting—even if it is lost upon reboot. Persistence is deliberately postponed to a later phase.
The store is abstracted via a simple interface. This interface allows for later substitution (e.g., with a file- or database-based version) without affecting the API or UI components.
4.2 In-memory implementation with ConcurrentHashMap
The first concrete implementation utilises a ConcurrentHashMap to ensure reliable access even under heavy load. Each mapping entry is represented by a simple record object (ShortUrlMapping) that contains the target URL, creation time, and optional expiration information.
The combination of ConcurrentHashMap and ShortCodeGenerator allows deterministic and thread-safe ID assignment without the need for explicit synchronisation. This creates a high-performance solution that operates reliably even under high load.
4.3 Extensibility for later persistence
All entries are accessible via a central interface. This interface is used not only for storage and retrieval but also forms the basis for later extensions, such as a persistence layer with flat files or EclipseStore, process control via TTL, or even event-driven backends.
The data structure can be extended with metrics, validity logic, or audit fields without requiring changes to the API – a classic approach to interface orientation in the sense of the open/closed principles.
4.4 Implementation
public record ShortUrlMapping(
String shortCode,
String originalUrl,
Instant createdAt,
Optional<Instant> expiresAt
) {}
This structure represents the basic assignment and allows the optional specification of an expiration time.
Store-Interface: UrlMappingStore.java
public interface UrlMappingStore {
ShortUrlMapping createMapping(String originalUrl);
Optional<ShortUrlMapping> findByShortCode(String shortCode);
boolean exists(String shortCode);
List<ShortUrlMapping> findAll();
boolean delete(String shortCode);
int mappingCount();
}
The interface is deliberately kept slim and abstracts the two core operations: insert (with creation) and lookup.
Implementation: InMemoryUrlMappingStore.java
public class InMemoryUrlMappingStore
implements UrlMappingStore, HasLogger {
private final ConcurrentHashMap<String, ShortUrlMapping> store
= new ConcurrentHashMap<>();
private final ShortCodeGenerator generator;
public InMemoryUrlMappingStore() {
this.generator = new ShortCodeGenerator(1L);
}
@Override
public ShortUrlMapping createMapping(String originalUrl) {
logger().info("originalUrl: {} ->", originalUrl);
String code = generator.nextCode();
ShortUrlMapping shortMapping = new ShortUrlMapping(
code,
originalUrl,
Instant.now(),
Optional.empty()
);
store.put(code, shortMapping);
return shortMapping;
}
@Override
public Optional<ShortUrlMapping> findByShortCode(String shortCode) {
return Optional.ofNullable(store.get(shortCode));
}
@Override
public boolean exists(String shortCode) {
return store.containsKey(shortCode);
}
@Override
public List<ShortUrlMapping> findAll() {
return new ArrayList<>(store.values());
}
@Override
public boolean delete(String shortCode) {
return store.remove(shortCode) != null;
}
@Override
public int mappingCount() {
return store.size();
}
}
4.5 Why so?
The use of a ConcurrentHashMap ensures that concurrent write and read operations can be handled consistently and efficiently. The combination with AtomicLong in ShortCodeGenerator prevents collisions. The interface allows for a persistent implementation to be introduced later without changing the API or UI behaviour.
5. HTTP API with Java tools
5.1 HTTP-Server mit com.sun.net.httpserver.HttpServer
Instead of relying on heavyweight frameworks like Spring or Jakarta EE, we use the lightweight HTTP server implementation that the JDK already includes in the package com.sun.net.httpserver. This API, although rudimentary, is performant, stable, and perfectly sufficient for our use case.
The server is configured in just a few lines, requires no XML or annotation-based mappings, and can be controlled entirely programmatically. For each path, we define a separate HTTP handler that receives the request, processes it, and returns a structured HTTP response.
5.2 POST /shorten: Shorten URL
The first endpoint allows a long URL to be passed over an HTTP POST to the shortener. In response, the server returns the generated short form, in the simplest case as a JSON object with the shortCode.
Example request:
POST /shorten
Content-Type: application/json
{
"url": "https://example.com/some/very/long/path"
}
Answer:
200 OK
Content-Type: application/json
{
"shortCode": "kY7zD"
}
Missing or invalid entries are marked with 400 Bad Request answered.
5.3 GET /{code}: Redirect to the original URL
When calling a shortcode (e.g.GET /kY7zD), the server checks whether a mapping exists. If so, a HTTP 302 redirect to the original address. If the code is unknown or expired, a 404 Not Found error will be displayed.
This redirection is stateless and allows for later isolation into a read-only redirect service.
5.4 Implementation
Starting point: ShortenerServer.java
public class ShortenerServer
implements HasLogger {
private HttpServer server;
public static void main(String[] args)
throws IOException {
new ShortenerServer().init();
}
public void heat()
throws IOException {
our store = new InMemoryUrlMappingStore();
this.server = HttpServer.create(new InetSocketAddress(8080), 0);
server.createContext("/shorten", new ShortenHandler(store));
server.createContext("/", new RedirectHandler(store));
server.setExecutor(null); // default executor
server.start();
System.out.println("URL Shortener server running at http://localhost:8080");
}
public void shutdown() {
if (server != null) {
server.stop(0);
System.out.println("URL Shortener server stopped");
}
}
}
POST Handler: ShortenHandler.java
public class ShortenHandler
implements HttpHandler, HasLogger {
private final UrlMappingStorestore;
public ShortenHandler(UrlMappingStore store) {
this.store = store;
}
@Override
public void handle(HttpExchange exchange)
throws IOException {
if (!"POST".equalsIgnoreCase(exchange.getRequestMethod())) {
exchange.sendResponseHeaders(405, -1);
return;
}
InputStream body = exchange.getRequestBody();
Map<String, String> payload = parseJson(body);
String originalUrl = payload.get("url");
logger().info("Received request to shorten url: {}", originalUrl);
if (originalUrl == null || originalUrl.isBlank()) {
exchange.sendResponseHeaders(400, -1);
return;
}
ShortUrlMapping mapping = store.createMapping(originalUrl);
logger().info("Created mapping for {} -> {}", originalUrl, mapping.shortCode());
byte[] response = toJson(Map.of("shortCode", mapping.shortCode())).getBytes(StandardCharsets.UTF_8);
exchange.getResponseHeaders().add("Content-Type", "application/json");
exchange.sendResponseHeaders(200, response.length);
try (OutputStream os = exchange.getResponseBody()) {
os.write(response);
}
}
}
GET-Handler: RedirectHandler.java
public class RedirectHandler
implements HttpHandler , HasLogger {
private final UrlMappingStorestore;
public RedirectHandler(UrlMappingStore store) {
this.store = store;
}
@Override
public void handle(HttpExchange exchange)
throws IOException {
our requestURI = exchange.getRequestURI();
our fullPath = requestURI.getPath();
logger().info("Full path: {}", fullPath);
String path = fullPath.substring(1); // strip leading '/'
logger().info("Path: {}", path);
if (path.isEmpty()) {
exchange.sendResponseHeaders(400, -1);
return;
}
Optional<String> target = store
.findByShortCode(path)
.map(ShortUrlMapping::originalUrl);
if (target.isPresent()) {
exchange.getResponseHeaders().add("Location", target.get());
exchange.sendResponseHeaders(302, -1);
} else {
exchange.sendResponseHeaders(404, -1);
}
}
}
JsonUtils.java (Minimal Java JSON without external libraries)
Since we do not want to use external dependencies such as Jackson or Gson in this first implementation, we need our own utility class to process simple JSON objects, specifically:
- String → Map<String, String>: for processing HTTP POST payloads (/shorten)
- Map<String, String> → JSON-String: to generate responses (e.g.{ “shortCode”: “abc123” })
This class is sufficient for simple key-value structures, as used in the shortener. It is not intended for nested objects or arrays, but as a pragmatic solution for the start.
Implementation: JsonUtils.java
public final class JsonUtils {
private JsonUtils() { }
public static Map<String, String> parseJson(InputStream input)
throws IOException {
String json = readInputStream(input).trim();
return parseJson(json);
}
@NotNull
public static Map<String, String> parseJson(String json)
throws IOException {
if (!json.startsWith("{") || !json.endsWith("}")) {
throw new IOException("Invalid JSON object");
}
Map<String, String> result = new HashMap<>();
// Remove curly braces
String body = json.substring(1, json.length() - 1).trim();
if (body.isEmpty()) {
return Collections.emptyMap();
}
// Separate key-value pairs with commas
String[] entries = body.split(",");
Arrays.stream(entries)
.map(entry -> entry.split(":", 2))
.filter(parts -> parts.length == 2)
.forEachOrdered(parts -> {
String key = unquote(parts[0].trim());
String value = unquote(parts[1].trim());
result.put(key, value);
});
return result;
}
public static String toJson(Map<String, String> map) {
StringBuilder sb = new StringBuilder();
sb.append("{");
boolean first = true;
for (Map.Entry<String, String> entry : map.entrySet()) {
if (!first) {
sb.append(",");
}
sb.append("\"").append(escape(entry.getKey())).append("\":");
sb.append("\"").append(escape(entry.getValue())).append("\"");
first = false;
}
sb.append("}");
return sb.toString();
}
private static String readInputStream(InputStream input)
throws IOException {
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(input, StandardCharsets.UTF_8))) {
return reader.lines().collect(joining());
}
}
private static String unquote(String s) {
if (s.startsWith("\"") && s.endsWith("\"") && s.length() >= 2) {
return s.substring(1, s.length() - 1);
}
return s;
}
private static String escape(String s) {
// simple escape logic for quotes
return s.replace("\"", "\\\"");
}
}
Properties and limitations
This implementation is:
- fully JDK-based(no third-party libraries)
- for flat JSON objects suitable, i.e.{ “key”: “value” }
- robust against trivial parsing errors, but without JSON Schema validation
- Consciously minimalistic to stay within the scope of the prototype
It is sufficient for:
- POST /shorten(Client sends{ “url”: “…” })
- Response to this POST (server sends{ “shortCode”: “…” })
Example use
InputStream body = exchange.getRequestBody();
Map<String, String> input = JsonUtils.parseJson(body);
String shortCode = "abc123";
String response = JsonUtils.toJson(Map.of("shortCode", shortCode));
For productive systems, the following is recommended in the future:
- Gson(lightweight, idiomatic)
- Jackson(extensive, also for DTO binding)
- Json-B(Standard-API, Jakarta conform)
However, for our first implementation in the Core JDK, the solution shown above deliberately remains the appropriate middle ground.
5.6 Core Java Client Implementation
The class URLShortenerClient functions as a minimalist HTTP client for interacting with a URL shortener service. Its structure allows connection to a configurable or locally running server, with the default address being http://localhost:8080/. This enables easy integration into local development environments, test runs, or automated system tests without the need for additional configuration.
At the heart of the functionality is the method shortenURL(String originalUrl). It initiates an HTTP POST call against the server endpoint/shorten, transmits the URL to be shortened in a simple JSON document and immediately evaluates the server’s response. Successful completion is indicated exclusively by a status code.200 OK In this case, the method extracts the contained shortCode using the static auxiliary method extractShortCode() from the class JsonUtils. If the server returns a different HTTP code instead, the process will be aborted with a corresponding IOException, which enforces explicit error handling at the application level. This maintains a clear semantic separation between regular usage and exception situations.
The second central method,resolveShortcode(String shortCode), is used to explicitly resolve a short URL. It sends a GET request directly to the server’s root context, supplemented by the passed code. The behaviour of this method largely corresponds to that of a web browser, with the difference that automatic redirects have been deliberately deactivated. This way, the method can determine the actual target address, if present, from the HTTP header field. Location and return it as a result. It clearly distinguishes between valid redirects (status 301 or 302), non-existent codes (status 404), and other unexpected responses. In the latter case, an IOException is thrown, analogous to the shortening process.
Technically speaking, the URLShortenerClient is exclusively composed of the Java SE API, namely HttpURLConnection, TYPE and stream-based input and output routines. All communication is UTF-8 encoded, ensuring high interoperability with modern JSON-based REST interfaces. The class also implements the interface HasLogger, which suggests a project-wide logging infrastructure and implicitly supports good traceability in server communication.
This client is particularly recommended for integration tests, command-line tools, or administrative scripts that require specific URLs to be shortened or verified. Due to its lean structure, the class is also suitable as a starting point for further abstractions, such as service-oriented encapsulation in larger architectures.
public class URLShortenerClient
implements HasLogger {
protected static final String DEFAULT_SERVER_PORT = "8080";
protected static final String DEFAULT_SERVER_URL = "http://localhost:" + DEFAULT_SERVER_PORT;
protected static final String SHORTEN_URL_ENDPOINT = "/shorten";
protected static final String REDIRECT_URL_ENDPOINT = "/";
private final TYPE serverBase;
public URLShortenerClient(String serverBaseUrl) {
this.serverBase = TYPE.create(serverBaseUrl.endsWith("/") ? serverBaseUrl : serverBaseUrl + "/");
}
public URLShortenerClient() {
this.serverBase = TYPE.create(DEFAULT_SERVER_URL);
}
/**
* String originalUrl = "https://svenruppert.com";
*
* @param originalUrl
* @return
* @throws IOException
*/
public String shortenURL(String originalUrl)
throws IOException {
our serverURL = serverBase.toURL();
// --- Step 1: POST to the /shorten endpoint with a valid URL ---
URL shortenUrl = URI.create(serverURL + SHORTEN_URL_ENDPOINT).toURL();
HttpURLConnection connection = (HttpURLConnection) shortenUrl.openConnection();
connection.setRequestMethod("POST");
connection.setDoOutput(true);
connection.setRequestProperty("Content-Type", "application/json");
String body = "{\"url\":\"" + originalUrl + "\"}";
try (OutputStream os = connection.getOutputStream()) {
os.write(body.getBytes());
}
int status = connection.getResponseCode();
if (status == 200) {
try (InputStream is = connection.getInputStream()) {
String jsonResponse = new String(is.readAllBytes(), UTF_8);
String extractedShortCode = JsonUtils.extractShortCode(jsonResponse);
logger().info("extractedShortCode .. {}", extractedShortCode);
return extractedShortCode;
}
} else {
throw new IOException("Server returned status " + status);
}
}
public String resolveShortcode(String shortCode)
throws IOException {
logger().info("Resolving shortCode: {}", shortCode);
URL url = URI.create(DEFAULT_SERVER_URL + REDIRECT_URL_ENDPOINT + shortCode).toURL();
logger().info("url .. {}", url);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setInstanceFollowRedirects(false);
int responseCode = connection.getResponseCode();
logger().info("responseCode .. {}", responseCode);
if (responseCode == 302 || responseCode == 301) {
our location = connection.getHeaderField("Location");
logger().info("location .. {}", location);
return location;
} else if (responseCode == 404) {
return null;
} else {
throw new IOException("Unexpected response: " + responseCode);
}
}
}
5.7 Summary
With just a few lines, you can create a functional HTTP API that serves as an ideal testbed and proof of concept. The structure is minimal but open to extensions such as error objects, rate limiting, or logging. What’s particularly remarkable is that the entire API works without servlet containers, external frameworks, or reflection—ideal for embedded applications and lightweight deployments.
In the next blog post, we will create a graphical interface to map user interactions.
Happy Coding
Sven
Discover more from Sven Ruppert
Subscribe to get the latest posts sent to your email.