Advent Calendar 2025 – Mass Grid Operations – Part 2
What has happened so far…
In the previous part, the URL shortener overview was significantly expanded. The starting point was the realisation that the last UI was heavily optimised for single operations and thus quickly reached its limits as soon as larger volumes of shortlinks needed to be managed. To resolve this bottleneck, the grid was consistently switched to multiple selection, creating the technical basis for actual mass operations.
Based on this, a context-dependent bulk action bar was created, visible only when a selection is present. It serves as a central control for simultaneous multi-entry actions and seamlessly integrates them into the existing search, filtering, and grid interaction workflow. The decisive factor was not the mere addition of new buttons, but the clean coupling of UI state, selection and action permission.
The first concrete mass operation was the bulk delete. This function exemplified how efficiency and security can be combined: through explicit confirmation dialogues, meaningful previews of the affected entries, consistent feedback, and clear visual cues for destructive actions. This workflow has been supplemented with keyboard shortcuts that speed up deleting larger quantities without bypassing the control mechanisms.
This completes the transition from an entry-centric interface to a professional management view. The overview view now understands selections as a work context and can execute coordinated actions in a controlled manner. The following sections build on this foundation, examining further mass operations, their semantic differences, and their technical implementation in detail.
The source code for this article can be found on GitHub at the following URL: https://github.com/svenruppert/url-shortener/tree/feature/advent-2025-day-08


Bulk Clear Expiry: Reliably and Consistently Remove Expiration Dates
While setting a uniform expiration date is a typical administrative task, the targeted removal of such expiration dates also plays a central role. In many real-world scenarios, temporary restrictions lose their relevance: links should remain valid permanently, test or campaign parameters must be reset, or previously set durations have proven too restrictive. The ability to delete expiration dates for many shortlinks in a single step is, therefore, a crucial building block for effective mass management.

The bulk clear expiry function follows a similar interaction pattern to setting an expiration date. Still, it is fundamentally different in its consistency and semantics: while setting introduces a new value, deleting an expiration date explicitly returns to the “does not automatically expire” state. The architecture must ensure that this state is clearly communicated and implemented correctly.
The removal of the expiration time must be understood explicitly, not as an implicit side effect, but as a deliberate operation that requires its own confirmation. This ensures both the security of user interactions and the traceability of changes.
The focus is on the interaction among dialogue logic, API calls, and the changed semantics of the edit endpoint, which now distinguishes between “set new expiration date” and “delete expiration date”. This makes the interaction between the UI and the backend clearly comprehensible and demonstrates how reliable mass changes can be integrated cleanly.
The entry point of the functionality lies in the method confirmBulkClearExpirySelected(), which – analogous to the bulk delete – first validates the current selection and then opens a confirmation dialogue:
private void confirmBulkClearExpirySelected() {
var selected = grid.getSelectedItems();
if (selected.isEmpty()) {
Notification.show("No entries selected");
return;
}
Dialog dialog = new Dialog();
dialog.setHeaderTitle("Remove expiry for " + selected.size() + " short links?");
dialog.add(new Text(
"This will remove the expiry date from all selected short links. "
+ "They will no longer expire automatically."
));
Button cancel = new Button("Cancel", _ -> dialog.close());
Button confirm = new Button("Remove expiry", _ -> {
dialog.close();
bulkClearExpiry(selected);
});
confirm.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
dialog.getFooter().add(new HorizontalLayout(cancel, confirm));
dialog.open();
}
The structure of this method makes it clear that removing the expiration time is modelled as a standalone operation that requires confirmation. First, an empty selection is excluded; then a dialogue is created whose title explicitly names both the operation type (“Remove expiry”) and the number of affected entries. The actual content of the dialogue explains the consequence of the action in plain language: The links “will no longer expire automatically”. This means that semantics are conveyed not only implicitly by the UI but also explicitly by language.
The actual implementation of the mass operation is carried out in the outsourced method bulkClearExpiry(...), which encapsulates the transition from the UI layer to the client API:
private void bulkClearExpiry(Set<ShortUrlMapping> selected) {
if (selected.isEmpty()) {
Notification.show("No entries selected");
return;
}
int success = 0;
int failed = 0;
for (var m : selected) {
try {
boolean ok = urlShortenerClient.edit(
m.shortCode(),
m.originalUrl()
);
if (ok) {
success++;
} else {
failed++;
}
} catch (IOException ex) {
logger().error("Bulk clear expiry failed for {}", m.shortCode(), ex);
failed++;
}
}
grid.deselectAll();
safeRefresh();
Notification.show("Cleared expiry: " + success + " • Failed: " + failed);
}
The signature of the edit call is particularly remarkable. In contrast to the bulk set expiry, only the shortcode and original URL are transferred; the instant and shortcode are not. This API is explicitly implemented in the URLShortenerClient in such a way that the absence of an expiration date is semantically interpreted as “delete expiry”:
public boolean edit(String shortCode, String newUrl)
throws IOException {
return edit(shortCode, newUrl, null);
}
public boolean edit(String shortCode, String newUrl, Instant expiresAtOrNull)
throws IOException {
if (shortCode == null || shortCode.isBlank()) {
throw new IllegalArgumentException("shortCode must not be null/blank");
}
if (newUrl == null || newUrl.isBlank()) {
throw new IllegalArgumentException("newUrl must not be null/blank");
}
final URI uri = serverBaseAdmin.resolve(PATH_ADMIN_EDIT + "/" + shortCode);
final URL url = uri.toURL();
logger().info("edit - {}", url);
// ... Request set-up and dispatch ...
}
Finally, on the server side, this semantics is anchored in the store implementations. Both the in-memory store and the EclipseStore-based persistence layer now interpret a null value for expiredAt as a signal to remove the expiration time entirely:
InMemoryUrlMappingStore
var originalOrNewUrl = url != null ? url : shortUrlMappingOLD.originalUrl();
var instant = Optional.ofNullable(expiredAt);
var shortUrlMapping = new ShortUrlMapping(shortCode, originalOrNewUrl,
shortUrlMappingOLD.createdAt(), instant);
store.put(shortUrlMapping.shortCode(), shortUrlMapping);
EclipseUrlMappingStore
var originalOrNewUrl = url != null ? url : shortUrlMappingOLD.originalUrl();
Optional<Instant> instant = Optional.ofNullable(expiredAt);
var shortUrlMapping = new ShortUrlMapping(shortCode, originalOrNewUrl,
shortUrlMappingOLD.createdAt(), instant);
urlMappings.put(shortUrlMapping.shortCode(), shortUrlMapping);
storage.store(dataRoot().shortUrlMappings());
Instead of replacing a missing value with the previous expiration date, expiredAt is now consistently converted to Optional.ofNullable(…). The result is a clear division of semantics: if an instant is passed, the edit operation sets a new expiration date; if null is passed, an empty optional is created, which corresponds to a complete removal of the existing expiration date. This change enables bulkClearExpiry(…) not only to overwrite expiry information covertly but also to delete it.
The combination of a confirmation dialogue, dedicated mass operation, and precisely defined edit semantics creates a continuous path from the user click to the permanently changed data structure. Bulk-Clear-Expiry is not just a “special case” of the edit function, but a deliberately modelled, independent mass operation with clear, technically and professionally comprehensible meaning.
Keyboard interaction as an accelerator for mass operations
With the introduction of mass grid operations, efficient interaction is becoming increasingly important. While mouse gestures and explicit buttons offer high visibility and explainability, keyboard shortcuts are most effective when users perform recurring actions. In the context of the Overview view, this means that central measurement operations should not only be accessible via the bulk bar but also be able to be triggered directly via the keyboard.
Keyboard interaction fulfils two roles. On the one hand, it serves as a direct accelerator of already established workflows. If you are used to working with keyboard shortcuts, you can trigger searches and deletions without detours via the mouse, reducing noticeable friction, especially with frequent context switches between code, console, and browser. On the other hand, it serves as a semantic condensation of the UI: the presence of a shortcut makes it clear that certain operations are not treated as edge cases but as primary interaction paths.
Today, this idea is being fleshed out in two prominent places. On the one hand, the global search in the overview view has a dedicated shortcut, allowing you to focus directly on the search field. This starts at the interaction’s beginning: a single keyboard shortcut is enough to switch from the web interface’s arbitrary state to a precisely defined search mode. On the other hand, the bulk delete operation is bound to the delete key only when a selection is present in the grid. The keyboard thus serves as an equal trigger for the same confirmation dialogue, which would otherwise be accessible only via the bulk bar.
It is essential for this integration that the keyboard shortcuts are not implemented as hidden, hard-to-discover functions, but as a consistent extension of the existing interaction model. They draw on concepts that have already been introduced – global search, multiple selection, bulk dialogues – and link them to a fluid, operable overall system. If you work exclusively with the mouse, you can use all functions as usual; those who prefer keyboard shortcuts, on the other hand, get much faster access to the same function space.
In the following, we will show how these shortcuts are technically integrated, how they interact with the grid state model, and which protective mechanisms prevent mass operations from being unintentionally triggered.
The central entry point is the addShortCuts() method, which bundles all global keyboard shortcuts of the Overview view:
private void addShortCuts() {
UI current = UI.getCurrent();
current.addShortcutListener(_ -> {
if (globalSearch.isEnabled()) globalSearch.focus();
},
Key.KEY_K, KeyModifier.META);
// Bulk delete via Delete-Key
current.addShortcutListener(_ -> {
if (!grid.getSelectedItems().isEmpty()) {
confirmBulkDeleteSelected();
}
},
Key.DELETE);
}
The method defines two keyboard interactions, each of which fulfils different semantic roles:
Meta + K – Focus on the global search field
The combination of KeyModifier.META (⌘ on macOS, Windows logo key on Windows) and the letter K address an established UI pattern in code editors and command palettes. With a single keystroke, the user can go directly to the global search, regardless of which UI element was previously in focus.
The protection provided by the following conditions is remarkable:
if (globalSearch.isEnabled()) globalSearch.focus();
This prevents the shortcut from forcing an interaction that would not make sense semantically in the current UI state – for example, if a dialogue is currently open or the search has been deactivated due to active filter modes. The keyboard interaction thus fits respectfully into the overall system.
Delete – Trigger bulk delete if there is a selection
The second central key combination binds the delete key directly to the bulk delete function. The shortcut applies to the entire overview view, but is only activated if there is actually a multiple or single selection:
if (!grid.getSelectedItems().isEmpty()) {
confirmBulkDeleteSelected();
}
This ensures that the delete dialogue appears only when there is an object context; therefore, it is not possible to trigger an operation without a target by randomly pressing Delete. This protection mechanism is essential for mass operations, as the delete key is often used unconsciously and intuitively.
Integration into the UI‑state model
Both shortcuts interfere with the grid’s state logic. The search shortcut uses the activation logic of the globalSearch field, while the delete shortcut is explicitly bound to the grid selection. This creates a natural coupling between keyboard interaction and UI state: Shortcuts are not detached from the UI, but follow the same semantic structure as mouse interaction via the bulk bar.
Why it matters: Acceleration without side effects
While shortcuts are often seen as optional conveniences, they play a crucial role in mass operations. Those who manage large amounts of data frequently switch between different tools – IDE, browser, and terminal. The ability to perform central operations without loss of focus and without visual navigation reduces cognitive load and increases speed. At the same time, the built-in protection queries ensure that no unintentional deletion actions are triggered.
This shows that keyboard interaction is not just a nice extra feature, but an integral part of an efficient, error-robust workflow for mass grid operations, how these shortcuts are integrated into the overview view, how they interact with the state model of the grid, and where protection mechanisms have been deliberately implemented to prevent unintentional mass operations.
A consistent visual language for high-risk mass operations
With the introduction of mass operations, both the functional and visual levels become more complex. Where previously individual actions were placed in isolation in the grid, there are now interconnected chains of interactions that have the potential to have a significant impact on the database – especially in operations such as deleting several shortlinks or changing all expiration dates. This increased impact requires a visual language that creates clarity, clearly highlights risks and helps users to perform each operation consciously and safely.
An essential goal of this visual language is to establish clear semantic distinctions among action types. While harmless operations – such as opening a detailed dialogue – may be unagitated and neutral, risky or destructive actions must be immediately recognisable as such. The introduction of the bulk action bar makes this differentiation imperative: it bundles both low-risk and high-risk actions in a small space, creating visual clarity, a prerequisite for safe usability.
The implementation of these principles follows a strict pattern: by targeting Lumo theme variants, buttons are not only styled but also semantically enhanced. Destructive actions are consistently marked with LUMO_ERROR, whereas accompanying or secondary actions are given the more subtle LUMO_TERTIARY_INLINE. This contrast creates an immediately apparent hierarchy, highlighting potentially dangerous actions without visually cluttering the interface.
The technical basis of this visual language is first evident in the implementation of the bulk action bar. A clear distinction is made here between destructive and non-destructive actions. The corresponding code from the OverviewView clearly shows this:
bulkDeleteBtn.addThemeVariants(LUMO_ERROR, LUMO_TERTIARY_INLINE);
bulkSetExpiryBtn.addThemeVariants(LUMO_TERTIARY_INLINE);
bulkClearExpiryBtn.addThemeVariants(LUMO_TERTIARY_INLINE);
Only the Delete button bears the LUMO_ERROR mark. This signals at a glance that the action could have irreversible effects. The other two buttons – “Set expiry” and “Clear expiry” – deliberately remain inconspicuous. Their functions are operational, but not destructive, which is why LUMO_TERTIARY_INLINE offers the appropriate visual restraint here.
This differentiation also continues at the row level in the grid. Each row has two buttons that play completely different roles visually:
Button delete = new Button(new Icon(VaadinIcon.TRASH));
delete.addThemeVariants(LUMO_ERROR, ButtonVariant.LUMO_TERTIARY);
The delete button is red, making it immediately recognisable. The corresponding detail button is visually restrained:
var details = new Button(new Icon(VaadinIcon.SEARCH));
details.addThemeVariants(LUMO_TERTIARY_INLINE);
The user immediately recognises which of the two actions is inconsequential and which is critical. This visual semantics is not cosmetic; it reduces misclicks and improves security, especially in fast workflows. This logic continues in the confirmation dialogues. In the case of bulk delete, the confirmation button is marked twice:
Button confirm = new Button("Delete", _ -> { ... });
confirm.addThemeVariants(ButtonVariant.LUMO_PRIMARY, LUMO_ERROR);
LUMO_PRIMARY marks the main action in the dialogue, LUMO_ERROR immediately makes it clear that this main action is destructive. The red colouring was deliberately chosen to increase attention and prevent accidental confirmations.
However, different semantics apply when removing an expiration date. The process is a mass operation, but not destructive. Therefore, the application explicitly does not use a red marking:
Button confirm = new Button("Remove expiry", _ -> { ... });
confirm.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
Here, only the primary theme is set – the action is essential, but not dangerous. The UI thus distinguishes between critical and relevant, which is crucial for intuitive usability.
In summary, the consistent use of Lumo themes creates a clear, recognisable hierarchy:
LUMO_ERROR→ destructive, potentially irreversibleLUMO_PRIMARY→ central affirmative actionLUMO_TERTIARY/LUMO_TERTIARY_INLINE→ harmless or accompanying actions
This visual grammar helps users recognise the seriousness of an action without reading the text. It is precisely this clarity that is indispensable when operations are applied to many data sets rather than a single one.
Conclusion: From individual case to professional mass editing
With today’s update, the URL shortener’s overview view reaches a new level of functionality. What was previously a tool for individual case management is now developing into a full-fledged interface for professional mass editing. The fundamental expansion consists not only in the introduction of several bulk operations, but above all in how these operations are embedded within a consistent operating concept.
In the previous chapters, it became apparent that the introduction of multiple selection, bulk bar, dialogue mechanisms and a finely graded visual language is not a loose juxtaposition of individual features. Instead, all elements are intertwined: the search structures the data space, the selection defines the area of action, the bulk bar makes the mass operations visible, and the dialogues ensure conscious, comprehensible decisions. Finally, visual semantics provide orientation and support safe operation even in fast or routine workflows.
This combination of architecture and interaction design provides the foundation for functions that can be added in future expansion stages, including automated workflows, grouped changes, role-based sharing, and even domain-specific mass transformations. These changes thus not only provide a feature package but also the foundation for a productive, reliable, and extensible mass-operations architecture.
Cheers Sven
Discover more from Sven Ruppert
Subscribe to get the latest posts sent to your email.