Signal Hub logoSignal Hub

Java news and articles

Java
Updated just now
Date
Source

9 articles

Dev.to (Java)
~2 min readMay 6, 2026

Adding runtime budget control to a Spring Boot AI agent

We recently wired budget checks into a Java/Spring Boot agent platform. The problem was not that the platform had no limits. It had too many: subscription checks, credit checks, feature limits, and OpenAI calls all had their own logic. The expensive path was simple: calling OpenAI. So we wrapped that path directly. @Service public class CyclesOpenAIService { private final SimpleOpenAI openAI; public CyclesOpenAIService(OpenAIClientProvider clientProvider) { this.openAI = clientProvider.getOpenAI(); } @Cycles(estimate = "1", workspace = "#workspaceId", unit = "CREDITS") public Response runOpenAIRequestCycles(ResponseRequest request, String workspaceId) { return this.openAI.responses().create(request).join(); } } That one annotation does the request-time budget check. Before the OpenAI call runs, Cycles reserves one credit against the user's workspace. If allowed, the call proceeds. If it succeeds, the reservation is committed. If it fails before billable work completes, the reservation is released. The basic flow is: reserve -> execute -> commit or release A few lessons from the integration: The annotation path was the easy part. The admin side was more work: creating wallets, funding budgets, looking up balances, and showing event history. Stable idempotency keys matter. SDK-generated UUIDs are fine for one call, but app-level retries need the same logical request ID. This first version protects OpenAI calls, not every tool call. Tool-call gating is the next step for actions like email, refunds, CRM updates, deploys, and paid APIs. The main takeaway: budget control belongs in the runtime path, not only in dashboards or billing reports. The question is not just: How much did this agent spend? It is: Should this agent, for this workspace, be allowed to take the next expensive action right now? Full field report: https://runcycles.io/blog/how-scalerx-wired-cycles-into-a-java-agent-runtime

Dev.to (Java)
~14 min readMay 6, 2026

Liquid Glass, Material 3, And A Lot Of Plumbing

It has been one of those weeks where the diff is bigger than the headline. The headline is short — Codename One now ships modern native themes: an iOS "liquid glass" look and an Android Material 3 look, bundled into the iOS and Android ports, on by default in the Playground, and selectable from a brand new menu in the simulator. The diff behind that headline is several thousand lines across the platform ports, the simulator, the GUI plumbing, and a small army of screenshot tests. What is Codename One? Codename One is an open-source framework for building native iOS, Android, desktop, and web apps from a single Java or Kotlin codebase. Learn more at codenameone.com. The theme behind the work is simple: Codename One should look modern out of the box on every platform we ship to, and it should feel fast. Almost everything in the past week of commits is in service of one of those two goals. The easiest way to see any of this is the Playground. The Playground now defaults to iOS Modern when the device toggle is set to iPhone and Android Material 3 when it is set to Android, in both light and dark mode. No setup, no pom.xml, no build hints — just open the page, drop in any of the standard components, and the modern look is what you get. If the past releases of Codename One looked dated to you, the Playground is where to start. The simulator is the second-easiest place. We will get to that. For most of Codename One's life the iOS native theme has been the venerable iOS 7 flat theme, and the Android native theme has been Holo Light. Both still ship — backwards compatibility has always been one of our most important goals — but they are no longer where we want a brand new app to start. We spent the bulk of this week building two new themes that target current platform aesthetics: iOS Modern — Apple system colors (accent #007aff light / #0a84ff dark, grouped-form surfaces, the system separator palette), pill borders for tabs, an iOS-Settings-style MultiButton, CHECK_CIRCLE-style checkbox glyphs, and translucent surfaces for Dialog and TabsContainer so they read as glass-frosted on top of whatever is behind them. It is not a real UIVisualEffectView backdrop — that is a port-side primitive we have not built yet — but the look is much closer to the iOS 26 vibe than anything we have shipped before. Android Material 3 — the Material 3 baseline tonal palette (primary #6750a4 light / #d0bcff dark, surface-container tiers, elevated containers approximated tonally because real elevation drop-shadows are still on the to-do list), plus all the Material density and padding choices — Roboto-ish proportions, a top-tab bar with the underline-by-color treatment, the standard square checkbox glyph. Each theme covers the usual ~25 UIIDs: base (Component, Form, ContentPane, Container), typography (Label, SecondaryLabel, TertiaryLabel, SpanLabel*), buttons (Button, RaisedButton, FlatButton with .pressed and .disabled), text input, selection controls, toolbar, tabs, side menu, list, MultiButton, dialog/sheet, FAB, and all the supporting separator and popup pieces. Both themes have full light and dark coverage. The shipping CSS sources sit in the repo at native-themes/ios-modern/theme.css and native-themes/android-material/theme.css for anyone who wants to read what each UIID is doing. This is the ShowcaseTheme capture from the new screenshot suite, run on iOS in light and dark. Same Form, same components, swap Display.setDarkMode(...) and re-resolve. The form is built like this: Container row = new Container(BoxLayout.x()); row.add(new Button("Default")); Button raised = new Button("Raised"); raised.setUIID("RaisedButton"); row.add(raised); form.add(row); TextField tf = new TextField("hello@example.com"); form.add(tf); Container toggles = new Container(BoxLayout.x()); CheckBox cb = new CheckBox("Remember me"); cb.setSelected(true); toggles.add(cb); RadioButton rb = new RadioButton("Agree"); rb.setSelected(true); toggles.add(rb); form.add(toggles); SpanLabel body = new SpanLabel("Body copy …"); form.add(body); That gives you the full picture in one screen: The Default button uses the stock Button UIID. The Raised button uses RaisedButton, which cn1-derives from Button and adds a tinted pill on top of the iOS system blue — that is the iOS Modern accent in both modes. The TextField is a single rounded-rect surface with the iOS system gray fill, the same shape Apple uses in Settings. CheckBox and RadioButton use the new optional @checkBoxCheckedIconInt / @radioCheckedIconInt theme constants to swap to CHECK_CIRCLE / CHECK_CIRCLE_OUTLINE glyphs — Reminders-app aesthetic on iOS while Android keeps the standard square check. The SpanLabel body uses the theme's base font and inherits transparent backgrounds so it never paints over a translucent parent. The full screen source is DarkLightShowcaseThemeScreenshotTest.java. Same ShowcaseTheme source on Android. The Material 3 baseline palette gives Default the primary container color and Raised the elevated-surface tone, with the dark variant flipping the relationship correctly via the dark color-role mapping. Padding and font sizing follow Material density, which you can see in how compact the same Form lays out compared to iOS. This is the DialogTheme capture against the screenshot suite's textured diagonal-stripe backdrop. The backdrop is intentional — it lets reviewers see whether anything that is supposed to be translucent actually is. The iOS Modern Dialog uses an rgba surface fill (0.78 alpha in light, 0.95 in dark — dark needs more opacity because bright stripes bleed through) and its DialogBody, DialogTitle, ContentPane, CommandArea sub-UIIDs are transparent so the rounded corners read cleanly. The same trick is applied to TabsContainer and the iOS MultiButton. The native theme is meant to be a starting point — you can layer your own palette on top without forking the theme. Above is the PaletteOverrideTheme capture: the base is iOS Modern, but the test layers a magenta palette on top at runtime via UIManager.addThemeProps(...). RaisedButton, FlatButton, the disabled tone, and the body-copy span all pick up the override in both light and dark — the override seam works at the resource-bundle layer, exactly the same mechanism a user theme uses to override the native theme on a real app. Three pieces, all live: Themes are bundled. The simulator jar-with-dependencies includes both modern themes alongside the four legacy themes (iPhoneTheme, iOS7Theme, androidTheme, android_holo_light) at the root of the jar. The simulator can pick any one of them at runtime without touching the skin repo. A new "Native Theme" menu. Right next to the Skins menu there is now a Native Theme menu with a radio group for the six themes plus "Auto" and "Use skin's embedded theme". Selecting one writes the simulatorNativeTheme Preference, flips the simulator-reload flag, and disposes the current window so the skin reloader kicks in with the new theme. You can sit on a single skin and flip through every native theme in seconds. Build hints know about it. The new nativeTheme, ios.themeMode, and and.themeMode build hints are registered with the simulator's Build Hints UI on launch — labels, types, value lists, descriptions, the lot. (The legacy keys cn1.nativeTheme and cn1.androidTheme are still honored for back-compat.) Set them in the Build Hints dialog, in codenameone_settings.properties, or via -D system properties; they flow through to the device build and the simulator both. The "Auto" choice in the Native Theme menu defers to those build hints — set ios.themeMode=modern in your project's settings and "Auto" previews iOS Modern; flip the same project to ios.themeMode=ios7 and "Auto" previews iOS 7. The explicit menu entries (iOS Modern, iOS 7, etc.) override the hints regardless. -Dcn1.forceSimulatorTheme is still honored as the highest-priority override; pick "Use skin's embedded theme" to bypass the framework theme entirely and get whatever the skin shipped with. The opt-in is the same on iOS and Android. The platform knobs follow a single naming pattern — ios.themeMode and and.themeMode — and accept modern / liquid / auto / ios7 / flat on iOS, modern / material / auto / hololight / legacy on Android. There is a single cross-platform shortcut, nativeTheme=modern, which the iOS builder consults when ios.themeMode is unset and which the Android port reads at runtime as a default for and.themeMode. The legacy aliases cn1.androidTheme and cn1.nativeTheme are still honored for back-compat, as is and.hololight=true. The default for an existing app stays on legacy on every platform. We do not flip a 15-year-old app's look without an opt-in. New apps generated from the initializr ship with nativeTheme=modern, ios.themeMode=modern, and and.themeMode=modern already set in codenameone_settings.properties, so a brand new project starts with the modern themes preselected. The Playground does the same, and Playground project downloads carry the same defaults into the generated codenameone_settings.properties. The HTML5 port has the runtime support for the modern themes but does not bundle them with user apps yet — that is one of the loose ends we want to close in the next round. The other piece of look-and-feel that we want to highlight is StickyHeaderContainer, which finally has a proper home in the framework. It is the iOS-contacts-list / sectioned-material-list component: scroll past a section boundary and the previous header is replaced by the next one. New this week, the swap is animated. A directional slide moves the outgoing header up on a forward scroll and down on a reverse scroll, or you can pick a cross-fade. Above is a six-frame sweep from the screenshot test — the user scrolls through sections A, B, C, D, E and the pinned header recolors to whichever section is currently active at the top of the viewport. The API is small. Build the container, register sections with addSection(header, content), configure the transition style and duration, and add it to a Form: StickyHeaderContainer sticky = new StickyHeaderContainer(); sticky.setTransitionStyle(StickyHeaderContainer.TRANSITION_SLIDE); sticky.setTransitionDurationMillis(250); for (char c = 'A'; c <= 'Z'; c++) { Label header = new Label("" + c, "StickyHeader"); Container items = new Container(BoxLayout.y()); for (int i = 0; i < 5; i++) { items.add(new Label(c + " entry " + i)); } sticky.addSection(header, items); } form.add(BorderLayout.CENTER, sticky); TRANSITION_SLIDE is the default. TRANSITION_FADE cross-fades the outgoing header on top of the incoming one. TRANSITION_NONE keeps the prior instantaneous swap if you want it. Issue #4807 for the original request. Every screenshot in this post is captured by a test that runs the app on a real iOS device, an Android emulator, and headless Chrome, then diffs each capture against a stored golden image. The diff is the test — if the rendered pixels drift, the run fails. For animations the test grabs a series of frames over a fixed-duration transition, then composites them into a single index image. That is how the dual-appearance shots end up as one side-by-side picture per test: …and how the sticky-header animation ends up as a six-frame strip stitched into a GIF: If you want to read the source, the suite lives at scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/. The theme work was the loudest thing this week, but plenty of other commits landed alongside it: SIMD large-allocation fallback. The SIMD path on iOS allocates its working buffers on the stack via alloca for speed. Past a certain buffer size the stack allocation simply fails — there is not enough stack to give, and the request crashes the process. The fix detects that case and falls back to a regular heap allocation when the request is too large to live on the stack. Small SIMD ops keep the fast alloca path; large ones no longer crash. Pluggable AnimationTime clock. Motion, Timeline, MorphAnimation, Image.animate, and Label tickers now all route through a new AnimationTime class that defaults to System.currentTimeMillis() but can be overridden. Tests can drive animations deterministically frame by frame; demos can run in slow motion or fast forward; Motion.slowMotion is no longer the only lever. POSIX character classes for non-ASCII letters. [[:alpha:]], [[:alnum:]], [[:lower:]], and [[:upper:]] silently failed to match anything outside the basic ASCII range — Greek, Cyrillic, CJK ideographs, accented letters, vulgar fractions, currency symbols. They now match the way you would expect, with five regression tests covering the failing cases from the issue. Fail-fast on JDK < 11. The simulator and "Run as desktop app" goals fork the JVM with --add-exports=java.desktop/com.apple.eawt=ALL-UNNAMED, which JDK 8 rejects with the unhelpful "Could not create the Java Virtual Machine". Now the Maven plugin checks the runtime JDK version on entry to cn1:run and cn1:debug and aborts with a friendly message naming the detected version, JAVA_HOME, and a pointer to Adoptium. JDK 11 through 25 is the supported runtime range for the simulator, JDK 8 stays the build-time requirement for the core framework, and JDK 8 is still fully supported at runtime for shipped desktop apps — only the simulator / "Run as desktop app" Maven goals require JDK 11+. Sheet scrolling swipe and animation. Sheet finally drags from the bottom with a real animation instead of snapping in. Issue #4825. Picker positioning. Picker got additional button-positioning options and a small batch of coverage tests. Playground polish. The Playground moved every Dialog.show(...) to InteractionDialog mode so user code calling Dialog.show does not blow away the editor chrome — it renders into the layered pane instead. Error messages got a substantial overhaul. The preview-resolution syntax expanded so the Playground can pick previews from a much wider set of expressions, with a new harness keeping it honest in CI. Deeper refreshTheme(). Form.refreshTheme() has been around forever — it re-resolves the styles on a single Form. The new thing this week is UIManager.getInstance().refreshTheme(), which snapshots the current theme props and theme constants, clears the resolved-style caches, and re-applies the lot. This is what lets the screenshot suite flip dark mode mid-suite and see fresh styles, and what lets a runtime palette override take effect immediately. Most apps will never need to call it directly — palettes typically don't change at runtime, and a Display.setDarkMode(...) call already triggers the right invalidation. It is there if you do change the palette and want the change to stick on the next paint without reloading the theme from disk. Last week's post was about Codename One feeling faster: corrected pixel densities, principled scroll physics, SIMD on iOS, accessibility text scaling. This week is the symbiotic other half — Codename One looking like it belongs on a 2026 phone. Both halves are the same project. There is not much point in shipping a SIMD-accelerated Base64 if the surrounding UI looks like a 2014 app, and there is not much point in shipping a glass-frosted Dialog if the scroll underneath it judders. Neither half is finished. They are both ongoing, and they both depend on community help — bug reports, RFEs, the patient back-and-forth on issue threads where somebody describes a layout problem on an iPhone you do not own. A specific thank you to the people who drove the issues that turned into this week's commits: Thomas (@ThomasH99) filed #4781 (the original "build a liquid glass example" RFE that started this whole effort), #4807 (sticky headers), #4838 (sideways tab swipe), #4841 (the POSIX regex fix), #4819 (picker buttons), and several others; Francesco Galgani (@jsfan3) filed #4825 (sheet swipe animation) and #4824 (light + dark theme by default in initializr); @ddyer0 caught #4811 (the EDT stack overflow) and #4767 (iPad restart Form size); Lucca Biagi (@LuccaPrado) filed #4817 (form creation in IntelliJ). Several of those are RFEs you would not file unless you actually use the framework day-to-day, and that is the kind of feedback that turns into shippable work. We are sitting at 496 open issues as of this post. That is slow but steady progress — the number is moving in the right direction week over week, and the issues that close tend to ship as features or fixes you can see, not as silent triage. If you have a problem, file it. If you have an RFE, file that too. The themes you saw above started as an RFE. You can try the new themes today by opening the Playground, by setting nativeTheme=modern (or ios.themeMode=modern / and.themeMode=modern for finer control) in your project's codenameone_settings.properties, or by picking them from the simulator's new Native Theme menu. New projects from the initializr already have them on. The shipping resources are bundled in the iOS and Android ports as of this week.

Dev.to (Java)
~3 min readMay 6, 2026

Mastering Java 25 Primitive Types in Patterns and Switch Tutorial

Mastering Java 25 Primitive Types in Patterns and Switch Tutorial A comprehensive guide to using Java 25 primitive types in patterns and switch statements, covering best practices and common pitfalls Java's type system is a fundamental aspect of the language, and understanding how to work with its 25 primitive types is crucial for any developer. However, using these types in patterns and switch statements can be tricky, and many developers struggle to get it right. The problem is that the Java language specification does not provide clear guidelines on how to use primitive types in patterns and switch statements, leading to confusion and errors. Furthermore, the introduction of pattern matching in Java 14 and 15 has added new complexity to the language, making it even more challenging for developers to master. The lack of clear guidelines and best practices for using primitive types in patterns and switch statements can lead to a range of problems, from subtle bugs to full-blown errors. For example, using the wrong type in a pattern can result in a ClassCastException at runtime, while using the wrong type in a switch statement can lead to unexpected behavior. To make matters worse, the Java compiler does not always provide clear error messages, making it difficult for developers to diagnose and fix problems. In addition to the technical challenges, the use of primitive types in patterns and switch statements can also have a significant impact on code readability and maintainability. When used correctly, primitive types can make code more concise and expressive, but when used incorrectly, they can make code confusing and difficult to understand. Therefore, it is essential for developers to have a deep understanding of how to use primitive types in patterns and switch statements, and to follow best practices and guidelines to ensure that their code is correct, readable, and maintainable. How to use Java's 25 primitive types in patterns and switch statements Best practices for working with primitive types in Java How to avoid common pitfalls and errors when using primitive types How to use pattern matching in Java 14 and 15 to simplify code How to improve code readability and maintainability using primitive types How to troubleshoot and debug issues related to primitive types in patterns and switch statements public class PrimitiveTypeExample { public static void main(String[] args) { int number = 10; switch (number) { case 10 -> System.out.println("The number is 10"); default -> System.out.println("The number is not 10"); } } } Java's primitive types can be used in patterns and switch statements to simplify code and improve readability The use of primitive types in patterns and switch statements requires careful attention to type safety and best practices Pattern matching in Java 14 and 15 provides a powerful way to simplify code and reduce errors Following best practices and guidelines is essential to ensure that code is correct, readable, and maintainable Read the complete guide with step-by-step examples, common mistakes, and production tips: Mastering Java 25 Primitive Types in Patterns and Switch Tutorial

Dev.to (Java)
~2 min readMay 6, 2026

LeetCode 387: Finding the First Unique Character - Frequency Mapping in Java

Difficulty: Easy Topics: Hash Table, String, Queue, Counting Platform: Leetcode Given a string s, find the first non-repeating character in it and return its index. If it does not exist, return -1. Problem Statement Simplified Mistakes and Learning `Input: s = "leetcode" Output: 0 Explanation: The character 'l' at index 0 is the first character that does not occur at any other index.` `Input: s = "loveleetcode" Output: 2` Remove all the duplicating characters Then get the first character Get the first character index. If no unique then -1. Algorithm Convert the string to a char array. Intitialize a HashMap to store the character and the repeating counts. Intitialize a for loop Put the character in the HashMap with getOrDefault to check if the character already preset or not, if yes then increment by 1. End for loop Intitialize a for loop Check if the first value is 1 if yes then return the for loop index else continue the for loop End for loop return -1 First, convert the string to a char array. Then initialize a HashMap so that we can store each character with its repeating counts. class Solution { public int firstUniqChar(String s) { char[] character = s.toCharArray(); Map <Character, Integer> map= new HashMap<>(); for(int i=0;i<character.length;i++){ map.put(character[i],map.getOrDefault(character[i],0)+1); } for(int i=0;i<character.length;i++){ if(map.get(character[i])==1){ return i; } } return -1; } } Time Complexity: O(n) Space Complexity: O(n)

Dev.to (Java)
~10 min readMay 6, 2026

When Your Full-Stack Framework Gets Deprecated — Planning an OSS Migration from Hilla

The Bad News Every developer dreads the moment: the framework you built your application on is being discontinued. That's exactly what happened to us when Vaadin announced the deprecation of Hilla. Our system is a business application providing the following features: User registration and authentication Product search and browsing Online payment processing Multi-device support (web browsers and staffed/unstaffed business terminals) The tech stack: Spring Boot 3.x on the backend, a Lit + TypeScript SPA on the frontend. Around 20 views, 30 components, and 20 dialogs — a modest but non-trivial codebase with real-world requirements like authentication, payments, and multi-device support. For those unfamiliar, Hilla was Vaadin's full-stack framework that bridged Spring Boot backends with Lit-based frontends. It provided: Hilla Endpoints — Type-safe RPC from TypeScript to Java. Annotate a Java method with @Endpoint, and a TypeScript client stub is generated automatically. Authentication helpers — @vaadin/hilla-frontend handled auth state, login utilities, and Spring Security integration. Client-side routing — @vaadin/router for SPA navigation with auth guards. Connection state detection — @vaadin/common-frontend provided online/offline awareness tied to endpoint calls. Build tooling — A Vite plugin that handled code generation, theme application, and output placement into Spring Boot resources. The developer experience was genuinely good. Writing a Java method and immediately calling it from TypeScript with full type safety felt almost magical. It lowered the barrier for Spring Boot engineers to work on frontend code significantly. Then, in 2024, Vaadin announced that Hilla (specifically the Lit-based full-stack integration) would no longer be maintained. A React-based Hilla was offered as an alternative, but rewriting our entire Lit component library was not an option. The only path forward: replace everything Hilla provided with general-purpose open-source libraries. The deprecation announcement was the trigger, but if we're being honest, Hilla had been causing technical headaches well before that. Hilla extended Spring Security and Spring Session internals in non-standard ways. This made it effectively impossible to use standard session-sharing solutions like Spring Session + Redis out of the box. To achieve horizontal scaling behind a load balancer, we had to resort to some aggressive workarounds: Reverse-engineering Vaadin's internal classes (security configuration, request utilities) Using Java Reflection to access private methods and extract the framework's internal path resolution logic Extending the standard CSRF token with custom fields to build a token-based authentication mechanism Implementing a frontend middleware that detected 401 responses and transparently re-authenticated using the custom token // Accessing a framework-internal private method via reflection private String applyUrlMapping(String path) { try { Method method = RequestUtil.class.getDeclaredMethod( "applyUrlMapping", String.class); method.setAccessible(true); return (String) method.invoke(requestUtil, path); } catch (Exception e) { return "/"; } } // Frontend: transparent re-authentication on 401 const unauthorizedHandler = async (context, next) => { const response = await next(context); if (response.status === 401 && await store.reAuthenticate()) { location.reload(); } return response; }; This combination achieved sticky-session-free load balancing. But it was fragile — every Vaadin version upgrade risked breaking the reflection-based hacks, and the maintenance burden was significant. The other pain point was Hilla's custom servlet mappings and internal redirects. Hilla registered its own servlet mappings to handle frontend routing, with internal redirects that intercepted requests before they reached Spring's standard handler mappings. This interfered with Swagger UI's static resource serving and the ALB's (Application Load Balancer) path-based routing rules. Debugging 404 errors became an exercise in tracing request flow through multiple interception layers. Ultimately, we had to give up on serving API documentation (Swagger UI) for our external data API entirely. These weren't temporary annoyances — they were structural problems inherent to running Hilla alongside standard Spring ecosystem tools. The deprecation was the catalyst, but the conviction that migration would eliminate this technical debt is what made us commit to a full rewrite. The first task in any framework migration is a dependency audit. You need to draw a clear line between "what the framework was doing for you" and "what you built yourself." After a thorough review, we identified five areas requiring replacement: The largest blast radius by far. Nearly every view depended on Hilla Endpoints for data fetching and mutations. // Before: Hilla Endpoints import { SomeEndpoint } from "Frontend/generated/endpoints"; const details = await SomeEndpoint.getItemDetails(itemId); We needed a code-generation solution that could produce typed TypeScript clients from our existing API definitions. Hilla abstracted away the mechanics of authentication — CSRF token handling, auth state polling, login/logout flows. All of this was bundled in @vaadin/hilla-frontend and integrated seamlessly with Spring Security. Client-side routing for 20 views, including route guards for authenticated pages and role-based access. Offline detection that was tightly coupled to Hilla's endpoint call mechanism. With business terminals in the mix, graceful handling of network interruptions was a hard requirement. Hilla wrapped Vite with its own plugins for endpoint code generation, theme processing, and placing build output into Spring Boot's static resources directory. We needed to reconstruct this pipeline from scratch. Not everything needed replacing, and this was the silver lining: Vaadin Web Components — <vaadin-grid>, <vaadin-text-field>, and friends work independently of Hilla. No migration needed. Lit — Our template engine. Framework-agnostic by nature. MobX — State management. We had adopted it independently of Hilla. The fact that UI components and the template engine survived intact was huge. It meant the migration could focus almost entirely on "how data is accessed" and "how screens get navigated to," leaving the visual layer untouched. With the scope clear, we moved to selecting replacements. Area Before (Hilla) After (OSS) API Client Hilla Endpoints OpenAPI Generator (TypeScript) Auth @vaadin/hilla-frontend fetch + Spring Security RememberMe Routing @vaadin/router universal-router Offline detection @vaadin/common-frontend Custom ConnectionStateService Build Hilla/Vite integration Vite (standalone) State management MobX MobX (unchanged) UI Components Vaadin Web Components + Lit Vaadin Web Components + Lit (unchanged) Here is the reasoning behind each choice. Our first instinct was to write fetch wrappers by hand. With 20+ endpoints, that idea lasted about five minutes. We weren't willing to give up type safety either. Fortunately, we already had a separate set of REST APIs for external data provision, documented with SpringDoc. OpenAPI schemas were already part of our workflow. Extending the same annotation approach to our frontend-facing APIs would give us a ready-made foundation for client generation. Generating a TypeScript client from that schema was the natural next step. We chose OpenAPI Generator's typescript-fetch generator, integrated as a Maven plugin so client code is regenerated on every build. // After: OpenAPI Generator import { api } from "@/api/api-client"; const details = await api.getItemDetails({ itemId }); The call syntax changes slightly — parameters become an object literal, which is OpenAPI Generator's convention — but type safety is preserved. Your IDE still catches mismatched types at development time. We considered several frontend auth libraries but ultimately went with plain fetch calls against Spring Security endpoints. The reason was pragmatic: our auth flow was straightforward. Form-based login, RememberMe cookies, CSRF tokens — Spring Security handles all of this natively. We did have to implement what Hilla had been hiding — fetching the CSRF token before login, managing authentication state, wiring up the auth context in MobX — but being free from Vaadin's non-standard extensions meant we could align with Spring Security's standard configuration patterns. The overall authentication flow became much easier to reason about. The routing library decision required the most deliberation. Our candidates: Vaadin Router — Still available as a standalone package post-Hilla, but its long-term maintenance was uncertain. We were trying to reduce Vaadin coupling, not maintain it. React Router / Vue Router — Framework-specific. Incompatible with our Lit + Web Components stack. universal-router — Framework-agnostic, lightweight, excellent TypeScript support. The deciding factor was framework independence. Our Lit + Web Components architecture was intentionally decoupled from any specific framework. The router should follow the same philosophy. As a bonus, universal-router's API was similar enough to @vaadin/router that route definitions could be migrated with minimal rewriting. The ConnectionState from @vaadin/common-frontend was tightly coupled to Hilla's endpoint mechanism. With Hilla Endpoints gone, it couldn't function as-is. We could have relied on navigator.onLine and the browser's online/offline events, but that only detects local network connectivity — not whether the server is actually reachable. For business terminals that might have a LAN connection but lose internet access, this distinction matters. We built a ConnectionStateService that derives connection state from actual API call results. If requests start failing with network errors, the state transitions to offline. When they succeed again, it transitions back. Simple, but grounded in reality rather than browser heuristics. With technology choices locked in, the question became: in what order do we migrate 70+ TypeScript files? The "big bang" approach — rewrite everything at once — was off the table. Too much risk, too hard to debug when things go wrong. Instead, we adopted a layered migration strategy. The first wave targeted the infrastructure layer — everything that sits between the UI and the outside world: Replace the API client (Hilla Endpoints → OpenAPI Generator) Implement the authentication service Replace the routing foundation Implement connection state detection Migrate shared utilities Critically, no UI components were touched in this step. The goal was to stabilize the foundation before building on top of it. Checkpoint: npx tsc --noEmit We used TypeScript's compiler as our Step 1 acceptance test. Running npx tsc --noEmit verifies that all types resolve, all imports exist, and all function signatures match — without actually building anything. When this produced zero errors, we had confidence that the API call signatures were correct and the dependency graph was intact. With the infrastructure stable, we moved to views, components, and dialogs: Rewrite route definitions for universal-router Replace API calls in each view with the new client Wire up authentication guards Remove all Hilla-specific imports Checkpoint: npm run build The acceptance test for Step 2 was a full production build. Vite's build process plus TypeScript compilation with zero errors meant the migration was mechanically complete. The final step was a file-by-file comparison between the pre-migration and post-migration codebases. We categorized every difference into one of two buckets: Intentional changes — Different API call syntax, new routing API usage, restructured auth flow. Expected and correct. Needs fixing — Logic accidentally dropped during migration, edge cases not handled, subtle behavioral changes. This review was tedious but invaluable. It caught several unintended behavioral changes that would have been production bugs. When you're making sweeping changes across dozens of files, it's easy to accidentally delete a null check or change the order of operations. The layered approach exists to isolate failure domains. When Step 1 stabilizes the API client and auth infrastructure, Step 2 can focus purely on UI concerns. If a view doesn't render correctly after migration, you know the problem is in the view code — not in the underlying API client or auth service. Without this separation, debugging becomes a nightmare of "is this a plumbing issue or a UI issue?" This series covers the full migration across five articles. Part Topic Summary Part 1 (this article) Migration strategy Dependency audit, technology selection, migration plan Part 2 OpenAPI Generator Replacing Hilla Endpoints — circular dependencies, Date types, and Base64 encoding gotchas Part 3 Routing + state management universal-router + MobX + Lit integration — MobX strict mode pitfalls, real device vs. DevTools behavior gaps Part 4 Spring Security Multi-chain security architecture + Spring Session — 10 filter chains, SessionRepositoryFilter, and AuthenticationManager design Part 5 Final polish Path unification, packaging, embedded services — finally enabling Swagger UI, trailing slash headaches, fat JAR scope issues, config import patterns Starting from Part 2, we'll dive into the specific problems we encountered and how we solved them, with plenty of code examples. Framework migrations never go exactly as planned — that's precisely why they're worth documenting.

Dev.to (Java)
~3 min readMay 6, 2026

LeetCode 819: Finding the Most Common Word - String Cleaning and Counting in Java

Difficulty: Easy Topics: Array, Math, Hash Table, String, Counting Platform: Leetcode Given a string paragraph and a string array of the banned words banned, return the most frequent word that is not banned. It is guaranteed there is at least one word that is not banned, and that the answer is unique. The words in paragraph are case-insensitive and the answer should be returned in lowercase. Note that words can not contain punctuation symbols. Problem Statement Simplified Mistakes and Learning Input: paragraph = "Bob hit a ball, the hit BALL flew far after it was hit.", banned = ["hit"] Output: "ball" Explanation: "hit" occurs 3 times, but it is a banned word. "ball" occurs twice (and no other word does), so it is the most frequent non-banned word in the paragraph. Note that words in the paragraph are not case sensitive, that punctuation is ignored (even if adjacent to words, such as "ball,"), and that "hit" isn't the answer even though it occurs more because it is banned. Input: paragraph = "a.", banned = [] Output: "a" Initialize a HashMap and HashSet for paragraph String and banned array. Remove everything from string other than [a-z] and make it lowercase. Initialize a String array and add the string words to it using split function. Initialize HashMap and HashSet for string array and banned word array respectively. Initialize an int max and string maxWord to check and return the max repeating word. Initialize a for loop and add the banned array to HashSet. end for loop Initialize a for loop. Store the current word in String array to a string. If not a word in HashSet. Put in HashMap and increment the value if not already present. If value is greater than max. Assign the value to max. Assign word to maxWord. end if. end if end for loop. return max word. First, remove everything from string other than [a-z] and make it lowercase. Then split the string and store it in an array. Initialize a HashSet to store the string — so that the banned word array can be stored and compared with hashmap. Initialize an int max and string maxWord to check and return the max repeating word. Initialize a for loop which will go thorugh the banned words array and add them to HashSet. Then we will loop through the string array and check the HashSet and current word from String array. If same then we will move to next word from String array. If not then we will add it to HashMap and if the word already exists then we will increment the value of it ( using map.getOrDefault(word,0) it will return the current value, if the word doesnt exists then it wil return 0 otherwise the current value of that word from HashMap ). Then we will check of this value is greater than max, if yes then we will set this value as new max and this word as new maxWord. class Solution { public String mostCommonWord(String paragraph, String[] banned) { paragraph= paragraph.toLowerCase().replaceAll("[^a-z]"," "); String[] words= paragraph.split("\\s+"); Map<String,Integer> map= new HashMap<>(); Set<String> bannedWord=new HashSet<>(); int counter=0; int max=-1; String maxWord=""; for (int i=0;i<banned.length;i++){ bannedWord.add(banned[i].toLowerCase()); } for(int i=0;i<words.length;i++){ String word=words[i]; if(!bannedWord.contains(word)){ map.put(word,map.getOrDefault(word,0)+1); if(map.get(word)>max){ max=map.get(word); maxWord=word; } } } return maxWord; } } Time Complexity: O(n+m) Space Complexity: O(n+m)

Dev.to (Java)
~6 min readMay 6, 2026

Method Overriding in Java

beginnersbook.com programiz.com hyperskill.org occurs when a subclass provides a specific implementation for a method that is already defined in its superclass or parent class *Before Method Overriding,Learn Inheritance * Example : Method Overriding class Animal { public void displayInfo() { System.out.println("I am an animal."); } } class Dog extends Animal { @Override public void displayInfo() { System.out.println("I am a dog."); } } class Main { public static void main(String[] args) { Dog d1 = new Dog(); d1.displayInfo(); } } Output: Explanation When we call displayInfo() using the d1 object (object of the subclass), the method inside the subclass Dog is called. The displayInfo() method of the subclass overrides the same method of the superclass. Key Rules for Overriding To override a method in Java, the child method must have the same name, parameters, and return type as the parent method. Declaring a method in sub class which is already present in parent class is known as method overriding. The method in parent class is called overridden method and the method in child class is called overriding method. Overriding is done so that a child class can give its own implementation to a method which is already provided by the parent class. It requires an "IS-A" relationship (inheritance), and the overriding method cannot be more restrictive in access than the original. final, static, and private methods cannot be overridden, and constructors are not overridden. Argument list: The argument list of overriding method (method of child class) must match the Overridden method(the method of parent class). The data types of the arguments and their sequence should exactly match. Cannot reduce access The parameter list must not change: the overriding method must take the same number and type of parameters as the overridden method – otherwise, you would just be overloading the method. The return type must not change (Note: if the method returns an object, a subclass of that object is allowed as the return type). The access modifier must be either the same or a less restrictive one (for example, if the overridden method is protected, you can declare the overriding method as public, but not private). Thrown checked exceptions, if any, can be removed or reduced by the overriding method. This means that the overriding method can throw the same checked exception as the overridden method, or a subclass of that checked exception, but not a broader exception. This restriction does not apply to unchecked exceptions. There are several rules for methods of subclasses which should override methods of a superclass: To verify these rules, there is a special annotation @override. It allows you to know whether a method will actually be overridden or not, which can be helpful if you have any question about the method's behavior. If for some reason, the compiler decides that the method cannot be overridden, it will generate a compiler error. But remember that this annotation is not required, it's only for convenience. Output Using the @override annotation is recommended to trigger compiler checks, and the super keyword can be used to call the base method. The @override annotation catches mistakes like typos in method names In Java, annotations are the metadata that we used to provide information to the compiler. Here, the @override annotation specifies the compiler that the method after this annotation overrides the method of the superclass. While not compulsory, it is good practice to use the @override annotation when overriding a method: this annotation will check that the method is being overridden correctly, and will warn you if that's not the case. It is not mandatory to use @override. However, when we use this, the method should follow all the rules of overriding. Otherwise, the compiler will generate an error.....to be discussed Java picks which method to run at run time, based on the actual object type, not just the reference variable type. private, static and final methods cannot be overridden as they are local to the class. We should always override abstract methods of the superclass Special Cases in Overriding 1. Calling Parent Method Using super 2. Final Methods Cannot Be Overridden 3. Static Methods 4. Private Methods 5. Covariant Return Types Why Do We Use Method Overriding? Advantage of method overriding This is helpful when a class has several child classes, so if a child class needs to use the parent class method, it can use it and the other classes that want to have different implementation can use overriding feature to make changes without touching the parent class code. Method Overriding and Dynamic Method Dispatch In dynamic method dispatch the object can call the overriding methods of child class and all the non-overridden methods of base class but it cannot call the methods which are newly declared in the child class. The super keyword we override a method in child class, then call to the method using child class object calls the overridden method. Can we access the method of the superclass after overriding? Example : Use of super Keyword class Animal { public void displayInfo() { System.out.println("I am an animal."); } } class Dog extends Animal { public void displayInfo() { super.displayInfo(); System.out.println("I am a dog."); } } class Main { public static void main(String[] args) { Dog d1 = new Dog(); d1.displayInfo(); } } Output: the subclass Dog overrides the method displayInfo() of the superclass Animal. It is important to note that constructors in Java are not inherited. Hence, there is no such thing as constructor overriding in Java. However, we can call the constructor of the superclass from its subclasses. For that, we use super(). Access Specifiers in Method Overriding Example : Access Specifier in Overriding class Animal { protected void displayInfo() { System.out.println("I am an animal."); } } class Dog extends Animal { public void displayInfo() { System.out.println("I am a dog."); } } class Main { public static void main(String[] args) { Dog d1 = new Dog(); d1.displayInfo(); } } Output: Explanation Overriding Abstract Methods method overloading and method overriding What is Method Overloading in Java? Key Rules of Method Overloading The overloaded and overloading methods must be in the same class (Note: this includes any methods inherited, even implicitly, from a superclass). The method parameters must change: either the number or the type of parameters must be different in the two methods. The return type can be freely modified. The access modifier (public, private, and so on) can be freely modified. Thrown exceptions, if any, can be freely modified. Forbidding overriding public final void method() { // do something } if you try to override this method in a subclass, a compile-time error will occur. Overriding and overloading methods together We can also override and overload an instance method in a subclass at the same time. Overloaded methods do not override superclass instance methods. They are new methods, unique to the subclass.