🎄 Tag 5! Der StoreIndicator ist jetzt komplett in der #UI: Live-Status, Events, sauberes Lifecycle-Handling. Dazu ein großes Refactoring mit dem neuen MappingCreator. 👉 Jetzt lesen: https://javapro.io/de/adventskalender-2025-persistence-teil-02/
by @svenruppert

#Java #Vaadin #OpenSource #EclipseStore @vaadin

Advent Calendar – 2025 – Persistence – Part 02

Today, we will finally integrate the StoreIndicator into the UI.

  • Vaadin integration: live status of the store
  • Implementation of the StoreIndicator
  • Refactoring inside – The MappingCreator as a central logic.
  • EclipseStore – The Persistent Foundation
  • Additional improvements in the core
  • Before & After – Impact on the developer experience
  • The source code for this version can be found on GitHub at https://github.com/svenruppert/url-shortener/tree/feature/advent-2025-day-02

    Here’s the screenshot of the version we’re implementing now.

    Vaadin integration: live status of the store

    Now that communication between the client and server has been established via the AdminClient, the focus is on integrating with the Vaadin interface. The goal is to make the current system state visible to the user without requiring manual updates. The mechanism is based on cyclic querying, event control, and a reactive UI concept, all fully implemented in Vaadin.

    A central component of this integration is the StoreIndicator component. It serves as a visual interface between the user and the system state. Its design combines UI representation, data retrieval, and state management. The structure is implemented within the Vaadin component model, with the indicator implemented as an independent class, “StoreIndicator”. This class extends HorizontalLayout and adds several sub-elements to the layout—an icon, a text label, and optional status information. Their behaviour is closely linked to Vaadin’s lifecycle control.

    The central entry point is the onAttach method, which is executed whenever the component is added to the UI tree. At this moment, the polling mechanism starts, calling the AdminClient every 10 seconds to get the current status from the server. Communication remains fully controlled on the server side, while the UI is automatically synchronised.

    @Overrideprotected void onAttach(AttachEvent attachEvent) {    refreshOnce();    UI ui = attachEvent.getUI();    ui.setPollInterval(10000);    ui.addPollListener(e -> refreshOnce());}

    Analogous to the onAttach method, onDetach is also an essential part of the StoreIndicator’s lifecycle. This method is executed when the component is removed from the UI tree, such as when switching between views or closing the session. Here, the polling mechanism is cleanly terminated, and resources such as event subscriptions or listeners are released. This prevents background processes from continuing to send data to a defunct UI component. The code illustrates this process:

    @Overrideprotected void onDetach(DetachEvent detachEvent) {    super.onDetach(detachEvent);    if (subscription != null) {        try {            subscription.close();        } catch (Exception ignored) {        }        subscription = null;    }}

    This implementation ensures that no polling tasks or event listeners remain active after the component is removed. This is especially crucial in server-side frameworks like Vaadin to avoid leaks and unnecessary thread activity. The StoreIndicator thus acts like a well-behaved UI citizen, completing its tasks properly when leaving the scene.

    Each polling cycle triggers the refreshOnce method, which uses the AdminClient to determine the current memory state. This checks whether the server returns the mode “EclipseStore” or “InMemory”. The colour scheme is adjusted accordingly, and the symbol is highlighted. The design is based on Vaadin’s Lumo theme system, which enables clean integration with the application’s corporate design via CSS variables.

    Another central element is the internal event system, implemented via the StoreEvents and StoreConnectionChanged classes. It allows for loose coupling between components, so that any change to the memory state can be published globally and consumed by other UI elements. The StoreIndicator uses this system to propagate state changes, while the OverviewView, for example,  acts as a subscriber and automatically updates itself when the memory state changes.

    StoreEvents.publish(new StoreConnectionChanged(newMode, info.mappings()));

    This architecture avoids direct dependencies between UI components. Instead of explicit references or state propagation via view constructors, an event model is used that reduces complexity while increasing the application’s responsiveness. The result is a highly modular, reactive UI structure that reflects data changes on the backend.

    The way in which Vaadin is used here as a reactive framework deserves special attention. While classic web frameworks propagate state changes via HTTP requests and complete page reloads, Vaadin uses server-side push to deliver targeted UI updates. In combination with the polling model of the StoreIndicator, a hybrid solution is created: On the one hand, the architecture remains comprehensible and straightforward, and on the other hand, it gives the impression of a live application that reacts to changes in real time.

    This integration of event model, UI lifecycle, and data transfer is an example of modern component-oriented design in the Java ecosystem. It shows that reactive behaviour patterns can also be implemented without external reactive frameworks such as Vert.x or Reactor.

    Implementation of the StoreIndicator

    The indicator’s graphical structure is already fully defined in the constructor. It extends HorizontalLayout and uses Vaadin components such as Icon and Span to create a compact, semantically transparent status display. The structure follows the typical Vaadin composition logic: The graphic elements are created, styled and then inserted into the layout via the add method. Particular attention is paid to legibility and visual consistency:

    public StoreIndicator(AdminClient adminClient) {  this.adminClient = adminClient;  setAlignItems(FlexComponent.Alignment.CENTER);  setSpacing(true);  setPadding(false);  dbIcon.setSize("16px");  dbIcon.getStyle().set("color", "var(--lumo-secondary-text-color)");  badge.getStyle()      .set("font-size", "12px")      .set("font-weight", "600")      .set("padding", "0.2rem 0.5rem")      .set("border-radius", "0.4rem")      .set("background-color", "var(--lumo-contrast-10pct)")      .set("color", "var(--lumo-body-text-color)");  details.getStyle()      .set("font-size", "12px")      .set("opacity", "0.8");  add(dbIcon, badge, details);}

    The actual logic of the status update is contained in the refreshOnce method. It calls the AdminClient to determine the current server state. This returns a StoreInfo object containing the mode and the number of saved mappings. The mode dynamically adjusts the indicator’s colour scheme– green for persistent, blue for volatile, and red for erroneous states. This colour coding is based on Vaadin’s Lumo theming and provides immediate visual feedback.

    public void refreshOnce() {  getUI().ifPresent(ui -> ui.access(() -> {    try {      StoreInfo info = adminClient.getStoreInfo();      boolean persistent = "EclipseStore".equalsIgnoreCase(info.mode());      badge.setText(persistent ? "EclipseStore": "InMemory");      if (persistent) {        badge.getStyle()            .set("background-color", "var(--lumo-success-color-10pct)")            .set("color", "var(--lumo-success-text-color)");        dbIcon.getStyle().set("color", "var(--lumo-success-color)");      } else {        badge.getStyle()            .set("background-color", "var(--lumo-primary-color-10pct)")            .set("color", "var(--lumo-primary-text-color)");        dbIcon.getStyle().set("color", "var(--lumo-primary-color)");      }      details.setText("¡ " + info.mappings() + " items");      getElement().setAttribute("title", persistent ? "Persistent via EclipseStore" : "Volatile (InMemory)");      var newMode = persistent ? StoreMode.ECLIPSE_STORE : StoreMode.IN_MEMORY;      if (newMode != lastMode) {        lastMode = newMode;        StoreEvents.publish(new StoreConnectionChanged(newMode, info.mappings()));      }    } catch (Exception e) {      badge.setText("Unavailable");      badge.getStyle()          .set("background-color", "var(--lumo-error-color-10pct)")          .set("color", "var(--lumo-error-text-color)");      dbIcon.getStyle().set("color", "var(--lumo-error-color)");      details.setText("");      getElement().setAttribute("title", "StoreInfo endpoint unavailable");      if (lastMode != StoreMode.UNAVAILABLE) {        lastMode = StoreMode.UNAVAILABLE;        StoreEvents.publish(new StoreConnectionChanged(StoreMode.UNAVAILABLE, 0));      }    }  }));}

    This method is an example of reactive UI design without external frameworks. The combination of ui.access() and PollListener allows periodic updates without blocking the main UI thread model. Error cases are elegantly intercepted via the catch block and propagated via the event bus (StoreEvents.publish), so that other components can also react to failures.

    Refactoring inside – The MappingCreator as a central logic.

    With the introduction of EclipseStore and the parallel continued existence of the InMemory store, the need arose to standardise the generation logic for short URLs. Earlier versions of the application included this logic multiple times – both in the InMemory store and in the later EclipseStore implementation. This redundancy not only led to maintenance costs but also made uniform error handling more difficult. The solution to this problem was to introduce a dedicated component, the MappingCreator, which acts as a central element between validation, alias policy, and persistence mediation.

    The MappingCreator covers the entire process chain from the alias check to the generation of a new short code to the transfer to persistent storage. This component follows the principle of functional composition and is designed to operate independently of the specific storage technology. This means that both the InMemory and EclipseStore approaches can use the exact generation mechanism without duplicating the logic.

    The class is fully implemented in Core Java and does not require any external libraries. It defines a functional interface, ExistsByCode, that checks whether a specific shortcode already exists, and another interface, PutMapping, that stores a newly created mapping. This makes the Creator a generic tool that can take advantage of any implementation of UrlMappingStore . The following code snippet illustrates the structure:

    public final class MappingCreator implements HasLogger {  private final ShortCodeGenerator generator;  private final ExistsByCode exists;  private final PutMapping store;  private final clock clock;  private final Function<ErrorInfo, String> errorMapper;  public MappingCreator(ShortCodeGenerator generator,                        ExistsByCode exists,                        PutMapping store,                        Clock clock,                        Function<ErrorInfo, String> errorMapper) {    this.generator = Objects.requireNonNull(generator);    this.exists = Objects.requireNonNull(exists);    this.store = Objects.requireNonNull(store);    this.clock = Objects.requireNonNullElse(clock, Clock.systemUTC());    this.errorMapper = Objects.requireNonNull(errorMapper);  }

    The complete creation process is then displayed in the create method. The process follows a precisely defined scheme: First, it is checked whether the user has entered an alias. If so, it is checked and normalised via the AliasPolicy’s validate method. If the alias is invalid, the creator creates an error object with HTTP and application code and returns it as a failure result. If no alias is available, the creator automatically generates a unique shortcode with the ShortCodeGenerator, checks for collisions, and repeats the process if necessary.

    public Result<ShortUrlMapping> create(String alias, String url) {  logger().info("createMapping - alias='{}' / url='{}'", alias, url);  final String shortCode;  if (!isNullOrBlank(alias)) {    var aliasCheck = AliasPolicy.validate(alias);    if (aliasCheck.failed()) {      var reason = aliasCheck.reason();      var reasonCode = switch (reason) {        case NULL_OR_BLANK -> "ALIAS_EMPTY";        case TOO_SHORT -> "ALIAS_TOO_SHORT";        case TOO_LONG -> "ALIAS_TOO_LONG";        case INVALID_CHARS -> "ALIAS_INVALID_CHARS";        case RESERVED -> "ALIAS_RESERVED";      };      var errorJson = errorMapper.apply(new ErrorInfo("400", reason.defaultMessage, reasonCode));      return Result.failure(errorJson);    }    var normalized = normalize(alias);    if (exists.test(normalized)) {      var errorJson = errorMapper.apply(new ErrorInfo("409", "normalizedAlias already in use", "ALIAS_CONFLICT"));      return Result.failure(errorJson);    }    shortCode = normalized;  } else {    String gen = normalize(generator.nextCode());    while (exists.test(gen)) {      gen = normalize(generator.nextCode());    }    shortCode = gen;  }  var mapping = new ShortUrlMapping(shortCode, url, Instant.now(clock), Optional.empty());  store.accept(mapping);  return Result.success(mapping);}

    This method shows the functional structure of the component as an example. The creator does not create side effects outside its intended interfaces. It communicates exclusively via the function objects exists and store. Error handling is fully integrated into the result tree, allowing a clean separation between success and failure paths. In this way, logic remains deterministic, testable, and reproducible.

    With the introduction of MappingCreator, the codebase has been significantly streamlined. Both the InMemoryUrlMappingStore and the EclipseStoreUrlMappingStore now use the same generation logic, which substantially improves consistency and extensibility. The Creator is thus not only a refactoring result, but also a strategic architectural element that combines functional abstraction, type safety and test-driven development.

    EclipseStore – The Persistent Foundation

    After the internal generation logic has been unified with the MappingCreator, the implementation of the EclipseStoreUrlMappingStore now serves as the basis for permanent storage of the generated short URLs. This class replaces volatile storage with a fully persistent object model that maintains the application’s state across reboots. While the InMemory store only kept all mappings in a ConcurrentHashMap, in the EclipseStore they are stored within a serializable object, the so-called DataRoot. This structure serves as an anchor for all stored data elements, ensuring the entire object graph remains consistent at all times.

    The central idea of the EclipseStore approach is to implement storage logic not via relational mappings but via object-oriented persistence. This preserves the application’s model in its entirety, without requiring object or database table conversions. The following example shows the definition of the DataRoot:

    public class DataRoot implements Serializable {  @Serial  private static final long serialVersionUID = 1L;  private final Map<String, ShortUrlMapping> mappings = new ConcurrentHashMap<>();  public Map<String, ShortUrlMapping> mappings() {    return mappings;  }}

    In the EclipseStoreUrlMappingStore, when the server starts, it checks whether a root structure already exists. If not, it is recreated and registered as the root node of the memory instance. The following snippet shows the relevant section from the constructor:

    var storagePath = Paths.get(storageDir);Files.createDirectories(storagePath);this.storage = EmbeddedStorage.start(storagePath);DataRoot r = (DataRoot) storage.root();if (r == null) {  storage.setRoot(new DataRoot());  storage.storeRoot();}

    This initialisation ensures that the application has the same persisted state both during a cold boot and after a shutdown. All access to the mappings then takes place directly via the root object. When a new mapping is created, the store calls the storeMappingAndPersist method  , which stores the object in the internal map and synchronises the memory.

    private void storeMappingAndPersist(ShortUrlMapping m) {  var dataRoot = (DataRoot) storage.root();  var mappings = dataRoot.mappings();  mappings.put(m.shortCode(), m);  storage.store(mappings);}

    This immediate write process saves changes immediately. The EclipseStore automatically takes over transaction management at the object level and ensures atomic storage without requiring additional commit logic. This reduces the risk of errors and increases the traceability of the system’s status.

    In addition, the EclipseStoreUrlMappingStore implements all the methods of the UrlMappingStore interface, including find, delete, count,  and existsByCode. These methods operate directly on the object graph and use helper classes, such as UrlMappingFilterHelper, to efficiently execute queries and perform sorting. The key difference to the InMemory store is that changes to the data structure take effect immediately in persistent memory and are therefore available again when the application restarts.

    The implementation of the EclipseStoreUrlMappingStore makes it clear that persistence was not designed as an afterthought, but as an integral part of the architecture. The entire data flow – from alias generation to MappingCreator to storage in DataRoot – follows a consistent design. This not only achieves persistence, but also anchors it conceptually: the memory is not an external component, but part of the living object model of the application.

    Additional improvements in the core

    Parallel to the introduction of the EclipseStore and the standardisation of the mapping logic, the central helper classes in the core module have also been further developed to ensure more consistent data processing. The focus was not on expanding the range of functions, but on making internal processes more precise. In particular, JsonUtils, UrlMappingFilterHelper, and the internal validation logic of the AliasPolicy have been explicitly revised to improve integration between the REST layer, the UI, and memory.

    The JsonUtils class has been fully refactored to improve the security and robustness of data object serialisation and deserialisation. Instead of resorting to external parsers or frameworks, the class implements a well-defined strategy for simple objects and for nested structures. The focus is on stability, readability and deterministic processing. A central component is the toJsonListingPaped method, which is used for tabular representation in the administration interface. It generates a JSON array from a list of mapping objects, including metadata such as the total number and paging information.

    public static String toJsonListingPaged(List<ShortUrlMapping> list, int total) {  var sb = new StringBuilder();  sb.append('{');  sb.append("\"total\":").append(total).append(',');  sb.append("\"items\":[");  for (int i = 0; i < list.size(); i++) {    sb.append(toJson(list.get(i)));    if (i < list.size() - 1) sb.append(',');  }  sb.append("]}");  return sb.toString();}

    This method exemplifies the class’s philosophy: complete control over serialisation and field order. This guarantees that the generated JSON structures correspond precisely to the expected format – an important point when the backend and UI are developed independently of each other. The use of StringBuilder is not a stylistic device, but a deliberate design to minimise garbage objects under high request load.

    Another central component is the UrlMappingFilterHelper, which handles dynamic processing of filter and sorting parameters for REST endpoints. This class abstracts the transformation of the user-entered filters into internal predicate objects, which are then applied to the stored mappings at runtime. The implementation supports flexible queries without requiring specialised query languages or parsers. The following excerpt shows the method that evaluates a URL mapping based on several parameters:

    public static boolean matches(ShortUrlMapping m, UrlMappingListRequest req) {  if (req.codePart() != null && !m.shortCode().contains(req.codePart())) return false;  if (req.urlPart() != null && !m.originalUrl().contains(req.urlPart())) return false;  if (req.from() != null && m.createdAt().isBefore(req.from())) return false;  if (req.to() != null && m.createdAt().isAfter(req.to())) return false;  return true;}

    This compact logic ensures that filter requests are processed directly at the object level. The system does not have to generate intermediate representations or convert data. The result is efficient, storage-level filtering that scales to large datasets. In combination with the store’s advanced counting methods, it provides a high-performance foundation for complex search and administrative functions.

    This area is rounded off by the extension of the AliasPolicy, in particular by the introduction of the helper method failed(). This is used for precise error evaluation and simplifies the control logic in components such as the MappingCreator. Instead of laboriously checking validation results, the developer can directly ask if a validation has failed, and then take fine-grained action based on the return value. This not only improves the readability of the codebase but also the overall system’s error resistance.

    The adjustments above make it clear that there is a well-thought-out concept behind the supposed auxiliary classes. They are not marginal components, but supporting pillars of data consistency and system stability. Through targeted optimisations and a clear methodological framework, a core module is created that serves as the basis for the project’s further development, both functionally and architecturally.

    Before & After – Impact on the developer experience

    With the integration of EclipseStore and the unification of generation logic via MappingCreator, the developer experience in this project has fundamentally changed. What began as a loose interplay of individual components has developed into a precisely orchestrated system that convinces both in its architecture and maintainability. This is particularly evident in the reduction of redundancies, improved testability and increased transparency in the code flow.

    In the past, central operations – especially the creation and verification of new mappings – were scattered across multiple classes. The InMemory store contained its own validation and checking routines, while later extensions had to reimplement the same logic for persistent storage. With the MappingCreator, this process has now been transformed into a clearly defined, reusable unit. This not only eliminates duplicate code but also the risk of different implementations delivering divergent results. The following excerpt shows an example of how the creation of a mapping was previously realised within the store:

    public ShortUrlMapping create(String alias, String url) {  String shortCode = alias != null ? alias : generator.nextCode();  if (map.containsKey(shortCode)) {    throw new IllegalStateException("Alias already exists: " + shortCode);  }  var mapping = new ShortUrlMapping(shortCode, url, Instant.now(), Optional.empty());  map.put(shortCode, mapping);  return mapping;}

    This logic was functionally correct, but inflexible. Neither validation nor error objects nor alias rules were built in, and there was no uniform handling of failures or conflicts. With the introduction of MappingCreators and the centralised error structure, this process has been redesigned. The result is a deterministic, controlled, and traceable generation of short URLs, in which every step – from input to persistence – occurs via clearly defined interfaces.

    The second significant difference concerns how the memory is addressed. Previously, the entire application was designed for volatile data retention, which meant that each reboot resulted in a complete loss of the stored mappings. With the EclipseStore, this state has been fundamentally changed. The following code shows how the Store initialises its persistent database when the application starts:

    var storagePath = Paths.get(storageDir);Files.createDirectories(storagePath);this.storage = EmbeddedStorage.start(storagePath);DataRoot r = (DataRoot) storage.root();if (r == null) {  storage.setRoot(new DataRoot());  storage.storeRoot();}

    These few lines mark the crucial difference between temporary and permanent system states. The DataRoot’s data structure is loaded at startup, changes are immediately synchronised, and the state is maintained across reboots. From a developer’s point of view, this means that unit tests, integration tests and runtime behaviour can now consistently access the same database. The difference between development and production disappears because both use the same data flow and persistence logic.

    This standardisation also directly affects the user interface. The Vaadin components, such as OverviewView and StoreIndicator, now obtain their data via clearly defined interfaces that are decoupled from the persistence layer. State changes of the store or new mappings trigger events that are consumed by the UI components, without explicit dependencies. This creates a consistent, reactive system that works coherently from the data source to the surface.

    This development has fundamentally changed how developers interact with code. Bugs are easier to reproduce, tests are more stable, and extensions can be modular. The code is no longer a collection of independent methods, but a structured system in which each component has its place and responsibility.

    Cheers Sven

    #Advent2025 #EclipseStore #Java #Vaadin

    🎄Tag 4 im Adventskalender! Unser #URLShortener bekommt Persistenz – dank #EclipseStore. Und: Die UI zeigt jetzt live, ob der Server im InMemory- oder Persistent-Modus läuft.

    Warum ist das ein Gamechanger? Lese: https://javapro.io/de/adventskalender-2025-persistence-teil-01/
    By @SvenRupper

    #Vaadin #OpenSource @vaadin

    Advent Calendar – 2025 – Persistence – Part 01

    Visible change: When the UI shows the memory state

    With the end of the last day of the Advent calendar, our URL shortener was fully functional: the admin interface could filter, sort, and display data page by page – performant, cleanly typed, and fully implemented in Core Java. But behind this surface lurked an invisible problem: All data existed only in memory. As soon as the server was restarted, the entire database was lost.

    The source code for this version can be found on GitHub at https://github.com/svenruppert/url-shortener/tree/feature/advent-2025-day-02

    Here’s the screenshot of the version we’re going to implement now.

    Why pure in-memory data is reaching its limits

    This volatility was acceptable for early testing, but as the functionality depth increased, clear limits became apparent. In-memory data has three fundamental drawbacks:

    • Volatility: Each reboot resets the state – a behaviour that leads to unusable test and production data in real admin systems.
    • Missing history: Statistical evaluations or comparisons across sessions are impossible because the state is not persisted over time.
    • Invisible state: The user does not see in the UI whether they are working with persistent or volatile memory.

    This meant that the application remained technically correct, but operationally incomplete. A real administration interface not only needs functions, but also reliability and transparency about the system status.

    Motivation for the introduction of EclipseStore

    This is where EclipseStore comes  into play.The project aims to make a seamless transition from in-memory data structures to persistent object graph storage – without ORM, without database, without a break in the architecture.
    This makes it perfectly in line with the philosophy of this project: type safety, simplicity and complete control over the data flow.

    EclipseStore stores the same object graph that we already use in memory permanently on disk.No mapping, no SQL, no external dependency injection layer – the application remains pure Java. By integrating EclipseStoreUrlMappingStore,  the shortener can run in two modes:

    • InMemory for quick tests and volatile sessions,
    • EclipseStore for productive, persistent runtimes.

    This duality not only enables real operational data, but also a clean transition between the development and production environments – an essential aspect if you want to systematically upgrade applications without rewriting them.

    Goal: Permanent storage and live status in the UI

    But persistence alone was not the goal of this chapter; it was at least as necessary that the condition became visible. If the server is in persistent mode, the user interface should show this immediately – preferably in real time.

    This is precisely where the new status display in the MainLayout comes in: A small icon with colour feedback (green, blue, red) signals whether the system is currently volatile, persistent or unreachable. Underneath are mechanisms that cyclically query the server state, distribute events, and automatically update the UI when the mode changes.

    This merges the backend and frontend into a standard, reactive system: The interface no longer becomes just a window on stored data, but a visible mirror of the system state.
    This coupling of persistence and UI status forms the foundation for all upcoming enhancements – especially for security, rights management and operational diagnostics.

    The new MainLayout – context and status at a glance

    After we have created the theoretical basis in the previous chapter and understood why volatile InMemory data is not sufficient and how EclipseStore enables persistence, we now turn our attention to the user interface. Every architectural decision becomes tangible only when it is reflected in the UI, both visually and functionally.

    The most visible expression of this change is in the MainLayout. With the introduction of EclipseStore, it received a new, subtle, but highly effective addition: the StoreIndicator. This small but central component is located in the application header and shows at a glance whether the shortener is currently using a volatile InMemory store or a persistent EclipseStore.

    This transforms a previously static header into a dynamic control centre that is not only navigative, but also informative – a visual link between the user interface and the system state.

    Architectural Concept – Visibility as a System Function

    In the original Vaadin interface, the header layout consisted only of a title and the navigation structure. The new component now expands this area: between the title “URL Shortener” and the menu, an icon with a label appears, whose colour and text change depending on the memory status.

    The concept behind it is deliberately kept minimalist: Status information should not be displayed via dialogues or messages, but should be permanently present, similar to a status bar in professional administration tools.

    Technical integration

    The integration was carried out via the existing MainLayout, which serves as the central layout template in Vaadin. There, a StoreIndicator is added when building the header :

    var adminClient = AdminClientFactory.newInstance();var storeIndicator = new StoreIndicator(adminClient);storeIndicator.getStyle().set("margin-left", "auto"); Align RightHorizontalLayout headerRow = new HorizontalLayout(toggle, appTitle, new Span(), storeIndicator);headerRow.setWidthFull();headerRow.setAlignItems(FlexComponent.Alignment.CENTER);

    The StoreIndicator internally uses an AdminClient that periodically queries the server endpoint /store/info. This provides JSON information about the current mode, the number of saved mappings, and the time of server start.

    UI design and colour code

    For better orientation, a three-level colour scheme has been introduced:

    • 🟢 EclipseStore active: Persistent persistence, data is written to disk.
    • 🔵 In-Memory Mode: Volatile memory, suitable for testing and fast development cycles.
    • 🔴 Error condition: Connection unreachable or response invalid.

    This colour system is implemented consistently with Vaadin’s Lumo design system and ensures that the status indicator blends harmoniously into the UI.

    Effect on user experience

    The effect is subtle but profound:
    Users can immediately recognise whether they are working with a persistent or a temporary system. At the same time, there is greater trust in the application, as the interface now provides transparency into the system’s status. This aspect is often neglected in professional web applications.

    In the next chapter, we will look at how the StoreIndicator works technically: from the polling mechanism to the event architecture to its self-updating in the UI.

    Live Status Component: The StoreIndicator in Detail

    After the visible integration in the MainLayout, let’s now turn our attention to the StoreIndicator’s actual functionality. This central component displays the system’s state in real time. While the header in the UI provides the context, the StoreIndicator is the heart that makes data movement visible and gives immediate feedback to the user.

    Architecture and Purpose

    The StoreIndicator was designed as a standalone Vaadin component that extends HorizontalLayout. It combines logic, display and event control in a clearly defined unit. Their goal: to regularly retrieve the current memory state (InMemory, EclipseStore or error state) and display it visually.

    To do this, the component uses periodic polling, which queries the server endpoint /store/info via an AdminClient . The response includes metadata such as memory mode, number of saved mappings, and the time of server startup. Based on this data, the display changes dynamically.

    Lifecycle in the Vaadin UI

    When the user interface is attached (onAttach), polling is activated. The UI retrieves the current system information at fixed intervals (every 10 seconds). When removing (onDetach), this background activity is terminated again, keeping the application resource-efficient.

    @Overrideprotected void onAttach(AttachEvent attachEvent) {    refreshOnce();    UI ui = attachEvent.getUI();    ui.setPollInterval(10000); every 10 seconds    ui.addPollListener(e -> refreshOnce());}

    Communication with the backend

    Communication between the frontend and the backend occurs via the AdminClient. This calls the JSON endpoint /store/info and converts the response to a StoreInfo object:

    StoreInfo info = adminClient.getStoreInfo();

    boolean persistent = “EclipseStore”.equalsIgnoreCase(info.mode());

    On this basis, color, icon and text are updated. The visual status is based on three states:

    • 🟢 EclipseStore active – persistent storage, data is stored on disks.
    • 🔵 InMemory mode – volatile storage, ideal for testing and development.
    • 🔴 Error condition – Connection lost or server unreachable.

    These states are immediately visible in the UI and follow the colour values of Vaadin’s Lumo theme, which ensures a consistent design.

    Event-Based Update

    In addition to polling, the StoreIndicator relies on an event-based notification system. Via StoreEvents.publish(), it sends a global event whenever the storage state changes. Components such as the OverviewView can subscribe to this event and automatically reload when changes are made.

    StoreEvents.publish(new StoreConnectionChanged(newMode, info.mappings()));

    This loosely coupled event system avoids direct dependencies between UI components and allows for flexible extensibility – a pattern that can also be applied to other UI areas.

    The StoreIndicator, which displays the system’s state in real time, operates resource-efficiently and enhances user confidence in the application. It turns the abstract term “persistence mode” into a visual experience – a perfect example of how backend information can be elegantly integrated into the UI.

    Cheers Sven

    #Advent2025 #EclipseStore #Java #Vaadin

    Dec 09 at 5 PM CET - Last FREE #JAVAPRO online training opportunity in 2025! Join the #EclipseStore Advanced course with C. Kuemmel. Share this with business partners & friends. Limited tickets!

    Get free tickets: https://pretix.eu/impuls/advanced/

    @EclipseFdn @microstreamOne #Persistence

    🎄Heute öffnet sich Türchen 1 unseres #Java #URLShortener Adventskalenders. 24 Tage voller Features, Deep Dives & Architektur-Einblicke.

    Warum ein eigener URL-Shortener?
    Jetzt lesen: https://javapro.io/de/url%e2%80%91shortener-adventskalender-2025/
    Autor: SvenRuppert

    #OpenSource #Vaadin #Jetty #EclipseStore

    Hone your skills! Join the last free #EclipseStore Advanced online training with C. KĂźmmel in 2025! Dec 09 at 5-9 PM CET. Max 50 bookings. First come, first served!

    Grab your free ticket: https://pretix.eu/impuls/advanced/

    @microstreamOne @EclipseFdn #Microservices #Databases #MicroStream

    Introduction to the URL‑Shortener Advent Calendar 2025

    December 2025 is all about a project that has grown steadily in recent months: the Java-based URL Shortener, an open-source project implemented entirely with Core Java, Jetty, and Vaadin Flow. The Advent calendar accompanies users every day with a new feature, a technical deep dive, or an architectural improvement – from the basic data structure and REST handlers to UI components and security aspects.

  • Motivation: Why your own URL shortener?
  • What can users expect in the Advent calendar?
  • Reasons for the choice of technology
  • Core Java (Java 24)
  • Jetty as an embedded web server
  • Vaadin Flow for the UI
  • EclipseStore as a persistence solution
  • Reference to previously published articles
  • Why, as an Advent calendar?
  • Motivation: Why your own URL shortener?

    The entire project is developed openly on GitHub. There, users will not only find the current state of the code, but also all historical development steps, patches, branches, and PRs discussed in the context of the Advent calendar. The source code can be viewed at https://github.com/svenruppert/url-shortener.

    Commercial URL shorteners are practical – but often non-transparent. They collect usage data, offer proprietary APIs, or block essential functions behind paywalls. The goal of this project is therefore a completely self-powered, transparent, and secure shortener suitable for internal networks, development teams, companies, and hobby projects alike.

    The project pursues three central basic principles: transparency, in which every decision is documented in the code in a comprehensible way; didactics, as the project is not only intended to be a tool, but above all a learning platform; and extensibility, because each feature is designed to be modular and can therefore be easily adapted or expanded.

    What can users expect in the Advent calendar?

    The technical journey through Advent is visible both in terms of content and directly in the repository. Each day is created as its own Git branch, starting with feature/advent-2025-day-01 and continuing until the final Christmas door. This allows the project’s development to be traced in small, clearly defined steps – including all refactorings, architectural adjustments, and new functions. In the same structure, the Advent Calendar presents a precisely outlined feature every day, which is reproduced using the associated patch or PR. Supplementary code excerpts from the core and UI modules, architectural explanations and references to typical anti-patterns deepen the understanding. Each door is rounded off with suitable visualisations and a compact summary, so that the project’s development becomes transparent and tangible step by step.

    Reasons for the choice of technology

    For the URL shortener, technologies were deliberately chosen that are both practical and didactically valuable. Every decision supports the project’s goal: to create a modern yet easy-to-understand system that requires no unnecessary dependencies.

    Core Java (Java 24)

    The project’s basis is pure Core Java. The reasons for this are:

    The use of pure core Java enables maximum transparency: Without any framework magic, users can see exactly how data models, handlers, error handling or serialisation are structured. By deliberately avoiding additional libraries, the attack surface is reduced, and the risk of supply chain problems is minimised. At the same time, this approach makes it very clear to developers how robust and comprehensive the JDK already is today: Many tasks that used to require external dependencies can now be easily solved with onboard tools.

    Jetty as an embedded web server

    A deliberate alternative to Spring Boot or Jakarta EE:
    Jetty is characterised by its lightweight design and rapid launch time, which are especially beneficial in development scenarios. At the same time, Jetty offers complete control over routing and servlets, so that HTTP mechanics can be precisely demonstrated and implemented in a targeted manner. Thanks to its modular structure, Jetty is ideal for small, well-defined microservices while remaining a stable, proven technology whose long production history speaks for high reliability.

    Jetty offers just the right balance between simplicity and technical relevance for this open source project.

    Vaadin Flow for the UI

    The project uses Vaadin Flow to implement an entirely server-side, Java-focused UI that does not require an additional JavaScript or TypeScript stack, making it particularly suitable for developers who want to focus entirely on Java. Instead of relying on a complex front-end ecosystem, Vaadin enables end-to-end development in a language, significantly reducing the cognitive load and flattening the learning curve. The component-based architecture enables a productive and structured way of working, in which user interfaces can be clearly modelled and reused. At the same time, server-side rendering eliminates the need for direct REST calls from the browser, increasing security and reducing attack surfaces. Despite this technical simplicity, Vaadin offers modern styling and a user experience that is reminiscent of the professionalism of enterprise applications. This makes the framework ideal for internal tools, administrative interfaces and corporate projects where security, robustness and long-term maintainability are paramount.

    EclipseStore as a persistence solution

    EclipseStore replaces classic databases with an object-oriented persistence approach that deliberately does not require ORM layers, entities, table models or complex abstractions. Instead of having to convert data into a relational structure, it remains in its natural form: Java records, lists and other object structures are directly persisted. This eliminates the usual mapping logic, significantly reducing the complexity of the persistence layer and making the architecture more straightforward to understand.

    EclipseStore shows its strength especially in small, focused services. Persistence fits seamlessly into the domain model without forcing developers to adapt their data structures to a relational mindset. This direct approach not only leads to more elegant modelling but also to excellent performance. Since EclipseStore manages in-memory-compliant object graphs, data access is high-speed and does not require any additional caching mechanisms or complex optimisations. The result is a lightweight yet powerful persistence system ideal for a modern, compact Java service.

    Didactically, EclipseStore provides valuable insight into how persistent data models can operate when not constrained by relational database constraints. Developers gain a deeper understanding of what object lifecycles, data flows, and modelling decisions look like in an environment that is designed entirely from the Java world. This makes EclipseStore particularly suitable for this URL shortener.

    Reference to previously published articles

    Before we get into the Advent calendar, it’s worth taking a look at the previous publications, which already present the basics, architecture and the first UI concepts of the project. These articles form the foundation on which the content of the Advent calendar is built:

    These items offer a first technical introduction and are perfect as preparation for the individual doors of the Advent calendar.

    Why, as an Advent calendar?

    A project like a URL shortener doesn’t happen in a weekend – it consists of many small steps, refactorings, decisions and improvements. An Advent calendar offers the perfect format for this: short, daily, understandable bites that come together to form an overall understanding.

    It is, at the same time, a review of the project’s journey and a motivating start to the new year: a project that users can run, expand, change, or completely rethink.

    #eclipsestore #java #jetty #urlshortener #vaadin

    Rethinking how #Java applications handle data: not around the database, but away from it. @markus Kett highlights #EclipseDataGrid as a shift in architectural thinking.

    Read now: https://javapro.io/2025/07/28/next-generation-caching-and-in-memory-searching/

    @EclipseFdn #OpenSource #Persistence #MicroStream #EclipseStore #JAVAPRO

    Today! Join the FREE #EclipseStore Fundamentals online training with Christian Kuemmel, powered by #JAVAPRO! NOV 12 at 5:00 - 9:00 PM CET

    Get your free ticket: https://bit.ly/4h6cmlB

    @JAVAPROmagazin #Persistence #Microservices #MicroStream @microstreamOne @EclipseFdn