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

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:

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.


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)

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

This structure represents the basic assignment and allows the optional specification of an expiration time.

Store-Interface: UrlMappingStore.java

The interface is deliberately kept slim and abstracts the two core operations: insert (with creation) and lookup.

Implementation: InMemoryUrlMappingStore.java

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

Answer:

200 OK

Content-Type: application/json

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


POST Handler: ShortenHandler.java

GET-Handler: RedirectHandler.java

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

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

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.

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.

Leave a Reply