<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="http://feeds.feedblitz.com/feedblitz_rss.xslt"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	xmlns:webfeeds="http://webfeeds.org/rss/1.0"  xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0">
<channel>
	<title>Baeldung</title>
	<atom:link href="https://www.baeldung.com/feed" rel="self" type="application/rss+xml" />
	<link>https://www.baeldung.com</link>
	<description>Java, Spring and Web Development tutorials</description>
	<lastBuildDate>Mon, 08 Jun 2026 16:34:00 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
<meta xmlns="http://www.w3.org/1999/xhtml" name="robots" content="noindex" />
<item>
<feedburner:origLink>https://www.baeldung.com/java-codename-one-cross-platform</feedburner:origLink>
		<title>Introduction to Cross-Platform Java Development With Codename One</title>
		<link>https://feeds.feedblitz.com/~/957869417/0/baeldung</link>
					<comments>https://feeds.feedblitz.com/~/957869417/0/baeldung#respond</comments>
		
		<dc:creator><![CDATA[Francesco Galgani]]></dc:creator>
		<pubDate>Mon, 08 Jun 2026 16:34:00 +0000</pubDate>
				<category><![CDATA[Maven]]></category>
		<category><![CDATA[>= Java 17]]></category>
		<category><![CDATA[JSONObject]]></category>
		<category><![CDATA[Maven Basics]]></category>
		<category><![CDATA[REST Basics]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/java-codename-one-cross-platform</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-15-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="max-width:100% !important;height:auto !important;float: left; margin-right: 5px;" fetchpriority="high" /><p>Learn how Codename One handles layouts, navigation, styling, and general functionality to enable cross-platform development via Java and Maven.</p>
The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/957869417/0/baeldung">Introduction to Cross-Platform Java Development With Codename One</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.<div class="fbz_enclosure" style="clear:left"><video controls="controls" style="display:block;padding:0.5em 0;max-width:100%;" ><source src="https://feeds.feedblitz.com/-/957869414/0/baeldung.mp4">Click the icon below to watch.</video><a href="https://feeds.feedblitz.com/-/957869414/0/baeldung.mp4" title="Play video"><img border="0" width="40" height="40" src="https://assets.feedblitz.com/i/movie.png"/></a></div>
<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/957869417/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/957869417/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f07%2fJava-Featured-15-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/24/957869417/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/957869417/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/957869417/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/java-codename-one-cross-platform#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/java-codename-one-cross-platform/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</description>
										<content:encoded><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-15-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="float: left; margin-right: 5px;" decoding="async" srcset="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-15-1024x536.jpg 1024w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-15-300x157.jpg 300w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-15-768x402.jpg 768w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-15-100x52.jpg 100w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-15.jpg 1200w" sizes="(max-width: 580px) 100vw, 580px" /><h2 id="bd-overview" data-id="overview"><strong>1. Overview</strong></h2>
<div class="bd-anchor" id="overview"></div>
<p><strong><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.codenameone.com/">Codename One</a> is an open-source, commercially supported platform that lets Java and Kotlin developers build cross-platform applications from a single codebase</strong>, while integrating with modern Maven-based workflows.</p>
<p>Its cloud-based build model reduces much of the platform-specific setup required for native development, letting us target iOS, Android, JavaScript, Windows, and macOS from the same project.</p>
<p>In this tutorial, we&#8217;ll build a Java 17 app named <em>Daily Routine</em>. It manages daily activities, has language and appearance settings, searches for places through a REST API, and displays map previews.</p>
<h2 id="bd-big-picture" data-id="big-picture">2. Big Picture</h2>
<div class="bd-anchor" id="big-picture"></div>
<p>Let&#8217;s first look at the workflow, the finished app, and the source code we use as a reference.</p>
<h3 id="bd-1-understanding-the-codename-one-workflow" data-id="1-understanding-the-codename-one-workflow"><strong>2.1. Understanding the Codename One Workflow</strong></h3>
<div class="bd-anchor" id="1-understanding-the-codename-one-workflow"></div>
<p><strong>Codename One applications are Maven projects</strong>. During development, the fastest loop is usually local, i.e., we run the app in the simulator, edit Java or Kotlin code, update CSS, and run tests from the IDE or command line:</p>
<a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/wp-content/uploads/2026/06/codenameone-maven-workflow-cropped.png"><img decoding="async" class="alignnone size-full wp-image-252273" src="https://www.baeldung.com/wp-content/uploads/2026/06/codenameone-maven-workflow-cropped.png" alt="Codename One Maven Workflow" /></a>
<p><strong>This workflow is also automation-friendly</strong>: Maven commands, simulator runs, tests, cloud builds, and real-device debugging can be driven from scripts, CI jobs, or AI agents. Java 17 projects generated by the Codename One Initializr include agent-facing guidance for realistic end-to-end <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/codenameone/CodenameOne/discussions/5045">app development with AI assistance</a>.</p>
<h3 id="bd-2-preview-and-sources" data-id="2-preview-and-sources"><strong>2.2. Preview and Sources</strong></h3>
<div class="bd-anchor" id="2-preview-and-sources"></div>
<p>Before setting up the project locally, <strong>let&#8217;s download the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eugenp/tutorials/blob/master/codenameone/ports/javascript/preview.html">JavaScript port</a> and open it as a local HTML file</strong>. This gives us a quick preview of the forms, navigation, activity editor, settings screen, place search, and map preview.</p>
<p>Let&#8217;s see an example with Firefox:</p>
<p>The app is intentionally more complete than a minimal example, as it includes several features:</p>
<ul>
<li>local JSON persistence</li>
<li>service classes</li>
<li>responsive styling</li>
<li>localization</li>
<li>REST integration</li>
<li>diagnostics</li>
<li>UI tests</li>
<li>network error handling</li>
<li>fallback logic</li>
</ul>
<p>Rather than covering every part of the repository line by line, <strong>we use the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eugenp/tutorials/tree/master/codenameone">complete source code</a> as a reference while focusing on the main Codename One patterns</strong>.</p>
<h3 id="bd-3-opening-the-complete-sources-from-github" data-id="3-opening-the-complete-sources-from-github"><strong>2.3. Opening the Complete Sources From GitHub</strong></h3>
<div class="bd-anchor" id="3-opening-the-complete-sources-from-github"></div>
<p>When opening the sources from GitHub, IDE-specific actions may be missing because local IDE metadata usually isn&#8217;t committed.</p>
<p><strong>When using IntelliJ IDEA, we can recreate the Codename One run configurations fairly easily</strong>:</p>
<pre><code class="language-bash">mvn cn1:configure-intellij</code></pre>
<p>This restores actions such as running the simulator, opening Codename One Settings, and updating Codename One. Other IDEs can still open the project as a Maven project, but may require running the needed Maven goals manually.</p>
<h2 id="bd-setting-up-the-project" data-id="setting-up-the-project"><strong>3. Setting Up the Project</strong></h2>
<div class="bd-anchor" id="setting-up-the-project"></div>
<p>Now, let&#8217;s generate a new project, inspect its structure, and run it locally.</p>
<h3 id="bd-1-creating-the-project-with-codename-one-initializr" data-id="1-creating-the-project-with-codename-one-initializr"><strong>3.1. Creating the Project With Codename One Initializr</strong></h3>
<div class="bd-anchor" id="1-creating-the-project-with-codename-one-initializr"></div>
<p>In the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.codenameone.com/initializr/">Codename One Initializr</a>, let&#8217;s use a simple configuration:</p>
<ul>
<li>Main class: <em>DailyRoutine</em></li>
<li>Package: <em>com.baeldung.cn1tutorial</em></li>
<li>Localization: <em>Include resource bundles</em></li>
</ul>
<p>The other defaults are fine. After clicking <em>Generate Project</em>, a multi-module Maven structure appears:</p>
<a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/wp-content/uploads/2026/06/Codename-One-Initializr-New-example-project.png"><img decoding="async" class="aligncenter wp-image-252574 size-full" src="https://www.baeldung.com/wp-content/uploads/2026/06/Codename-One-Initializr-New-example-project.png" alt="Codename One Initializr - New example project" width="552" height="1403" /></a>
<p><strong>We write most of the application in the shared <em>common</em> module</strong>: UI forms, business logic, CSS, localization files, services, and tests. Platform-specific modules for iOS, Android, Java SE, and JavaScript are present, but we usually touch them only for native integration or platform-specific configuration.</p>
<p>The generated <em>AGENTS.md</em> file and <em>.agent-skills</em> directory are meant to guide AI agents, but they also help explain the project structure and workflow.</p>
<h3 id="bd-2-opening-the-project-in-intellij-idea" data-id="2-opening-the-project-in-intellij-idea"><strong>3.2. Opening the Project in IntelliJ IDEA</strong></h3>
<div class="bd-anchor" id="2-opening-the-project-in-intellij-idea"></div>
<p>For this tutorial, we use IntelliJ IDEA, although the Maven structure works with other major Java IDEs.</p>
<p><strong>We should open the project root, not only the <em>common</em> module</strong>, because the root <em>pom.xml</em> defines the full Maven reactor.</p>
<p>When we first open it, a warning informs us that the Java SDK isn&#8217;t configured:</p>
<a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/wp-content/uploads/2026/06/Intellij-IDEA-select-SDK-for-Codename-One.png"><img decoding="async" class="alignnone size-full wp-image-252576" src="https://www.baeldung.com/wp-content/uploads/2026/06/Intellij-IDEA-select-SDK-for-Codename-One.png" alt="Intellij IDEA select SDK for Codename One" /></a>
<p>Let&#8217;s select OpenJDK 17.</p>
<h3 id="bd-3-running-the-app-in-the-simulator" data-id="3-running-the-app-in-the-simulator"><strong>3.3. Running the App in the Simulator</strong></h3>
<div class="bd-anchor" id="3-running-the-app-in-the-simulator"></div>
<p><strong>For UI work, we can treat the simulator as a reliable preview of real devices</strong>, as long as we compare the same platform family and a reasonably matching skin. There are options to try orientation changes, skins, system font size, light and dark styles, and much more:</p>
<p><strong>The first place to inspect if we notice any odd behavior is <em>theme.css</em></strong>. Additionally, certain changes to the simulator settings require us to close and reopen the simulator, so the app can adapt properly.</p>
<h2 id="bd-creating-the-app-skeleton" data-id="creating-the-app-skeleton"><strong>4. Creating the App Skeleton</strong></h2>
<div class="bd-anchor" id="creating-the-app-skeleton"></div>
<p>Now that the project runs, let&#8217;s replace the generated screen with the structure of <em>Daily Routine</em>.</p>
<h3 id="bd-1-the-application-lifecycle" data-id="1-the-application-lifecycle"><strong>4.1. The Application Lifecycle</strong></h3>
<div class="bd-anchor" id="1-the-application-lifecycle"></div>
<p>The Initializr starts with a class that extends the Codename One <em>Lifecycle</em> class and overrides <em>runApp()</em>. The <em>DailyRoutine</em> class keeps that entry point, but adds the setup needed by the app.</p>
<p><strong>In Codename One, a <em>Form</em> represents one screen</strong>:</p>
<pre><code class="language-java">public class DailyRoutine extends Lifecycle {
    private AppContext appContext;
    @Override
    public void init(Object context) {
        super.init(context);
        Toolbar.setGlobalToolbar(true);
        // Configure logging, error handling, toolbar behavior, and network defaults
        // Create repositories, services, localization, and shared application context
        // Load saved settings and activities
        // Apply the initial theme, language, and font scale
    }
    @Override
    public void runApp() {
        if (appContext == null) {
            return;
        }
        showHome();
    }
    @Override
    public void start() {
        super.start();
        Form current = CN.getCurrentForm();
        if (current != null) {
            // App-specific refresh logic omitted for brevity
        }
    }
    public void showHome() {
        new HomeForm(appContext).show();
    }
}</code></pre>
<p>The lifecycle is easier to understand visually:</p>
<a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/wp-content/uploads/2026/06/codenameone-lifecycle-general-diagram-cropped-2.png"><img decoding="async" class="alignnone size-full wp-image-252487" src="https://www.baeldung.com/wp-content/uploads/2026/06/codenameone-lifecycle-general-diagram-cropped-2.png" alt="Codename One applications general lyfecycle" /></a>
<p>The base <em>Lifecycle</em> class provides the default behavior. <strong>We override only the hooks where the app needs extra setup, while calls such as <em>super.init(context)</em> and <em>super.start()</em> keep framework defaults in place</strong>.</p>
<h3 id="bd-2-initializing-services-and-shared-state" data-id="2-initializing-services-and-shared-state">4.2. Initializing Services and Shared State</h3>
<div class="bd-anchor" id="2-initializing-services-and-shared-state"></div>
<p>Once <em>super.init(context)</em> has applied the lifecycle defaults, our <em>init()</em> method creates the objects that the rest of the app uses. Here, <em>context</em> is provided by Codename One itself; it&#8217;s not the <em>AppContext</em> that we create later for the forms.</p>
<p><strong>This approach is a lightweight alternative to a dependency-injection framework</strong>: instead of making forms create repositories or know about application setup, we pass them an <em>AppContext</em> with the services, settings, localized text helpers, and navigation methods they need.</p>
<p>First, let&#8217;s create the persistence layer and the in-memory store:</p>
<pre><code class="language-java">ActivityRepository activityRepository = createActivityRepository();
SettingsRepository settingsRepository = createSettingsRepository();
ActivityStore activityStore = new ActivityStore(activityRepository);</code></pre>
<p><strong><em>ActivityRepository</em> persists the activities, while <em>SettingsRepository</em> stores user preferences</strong>. <em>ActivityStore</em> keeps the current list of activities in memory and delegates loading and saving to the repository.</p>
<p>Next, let&#8217;s create the services used by the UI:</p>
<pre><code class="language-java">Resources themeResources = getTheme();
LocalizationService localizationService =
  createLocalizationService(themeResources);
PlaceSearchService placeSearchService = createPlaceSearchService();</code></pre>
<p><em>getTheme()</em> returns the resources already loaded by the <em>Lifecycle</em> base class. We discuss themes and CSS later; for now, the important point is that <strong>we pass theme resources to <em>LocalizationService</em> because the app localization bundles are stored there</strong> and then installed into <em>UIManager</em>. <em>PlaceSearchService</em> is the abstraction we later implement with the Geoapify REST API.</p>
<p>Then, let&#8217;s load the saved settings and assemble the shared application context:</p>
<pre><code class="language-java">AppSettings settings = settingsRepository.load();
appContext = createAppContext(
    activityStore,
    settingsRepository,
    localizationService,
    placeSearchService,
    settings
);</code></pre>
<p><em>AppContext</em> is the object we pass to the forms. It gives them access to shared state, services, localized text, settings, and navigation helpers, without making each form create those dependencies itself.</p>
<p>Finally, let&#8217;s apply the saved settings and load the activity list:</p>
<pre><code class="language-java">applySettings(settings, false);
activityStore.load();</code></pre>
<p>This way, we keep startup code centralized in the lifecycle class, while the forms can focus on UI and user interaction.</p>
<h3 id="bd-3-navigating-between-forms" data-id="3-navigating-between-forms"><strong>4.3. Navigating Between Forms</strong></h3>
<div class="bd-anchor" id="3-navigating-between-forms"></div>
<p>A layered navigation structure may look more complex than directly creating forms everywhere, but <strong>it keeps the app easier to maintain and extend</strong>:</p>
<a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/wp-content/uploads/2026/06/codenameone-navigation-structure-v6-cropped.png"><img decoding="async" class="alignnone size-full wp-image-252485" src="https://www.baeldung.com/wp-content/uploads/2026/06/codenameone-navigation-structure-v6-cropped.png" alt="How navigation is structured in Daily Routine (Codename One tutorial)" /></a>
<p><em>AppContext</em> acts as a facade: forms call simple navigation methods, while <em>DailyRoutine</em> creates and shows destination forms. All screens inherit from <em>BaseForm</em>, so toolbar, back commands, and side-menu entries stay consistent.</p>
<h2 id="bd-building-responsive-forms-with-layouts-and-css" data-id="building-responsive-forms-with-layouts-and-css"><strong>5. Building Responsive Forms With Layouts and CSS</strong></h2>
<div class="bd-anchor" id="building-responsive-forms-with-layouts-and-css"></div>
<p>So, let&#8217;s look at the UI model: component trees, layout managers, UIIDs, and CSS.</p>
<h3 id="bd-1-forms-containers-and-layout-managers" data-id="1-forms-containers-and-layout-managers"><strong>5.1. Forms, Containers, and Layout Managers</strong></h3>
<div class="bd-anchor" id="1-forms-containers-and-layout-managers"></div>
<p>Codename One UIs are easier to understand as component trees. Components live inside containers, and each container uses a layout manager to position its children.</p>
<p><strong>The official Codename One tutorial on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.codenameone.com/how-do-i/how-do-i-positioning-components-using-layout-managers/">positioning components using layout managers</a> gives a deeper explanation</strong>. For this app, the relation between the home screen and its component tree is fairly straightforward:</p>
<a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/wp-content/uploads/2026/06/Codename-One-UI-Architecture-Form-Component-Tree-and-Overlay-Panes-for-Pixel-Perfect-Design.png"><img decoding="async" class="alignnone size-full wp-image-252514" src="https://www.baeldung.com/wp-content/uploads/2026/06/Codename-One-UI-Architecture-Form-Component-Tree-and-Overlay-Panes-for-Pixel-Perfect-Design.png" alt="Codename One UI Architecture - Form, Component Tree, and Overlay Panes for Pixel Perfect Design" /></a>
<p>Each form has a title area and a content pane, and it can also expose overlay panes whose transparent background lets content underneath remain visible. The floating action button (FAB) is placed in the layered pane, so it floats above the activity list.</p>
<p>This hierarchy of components and overlapping layers is the basis for pixel-perfect designs in Codename One. Instead of hard-coding positions, we combine containers, layout managers, and CSS styling to adapt to device fragmentation: screen sizes, orientations, densities, safe areas, system font settings, and languages.</p>
<h3 id="bd-2-styling-components-with-uiids-and-css" data-id="2-styling-components-with-uiids-and-css"><strong>5.2. Styling Components With UIIDs and CSS</strong></h3>
<div class="bd-anchor" id="2-styling-components-with-uiids-and-css"></div>
<p>After defining the component hierarchy, we style components with UIIDs. <strong>A UIID is a style identifier assigned to a component</strong>, similar to a CSS class name.</p>
<p>The simulator&#8217;s Component Inspector helps us inspect the selected component&#8217;s class, UIID, layout, coordinates, padding, and margin. To open it, we right-click a component in the simulator and choose <em>Inspect Component</em>:</p>
<a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/wp-content/uploads/2026/06/Codename-One-Component-Inspector.png"><img decoding="async" class="alignnone size-full wp-image-252531" src="https://www.baeldung.com/wp-content/uploads/2026/06/Codename-One-Component-Inspector.png" alt="Codename One Component Inspector" /></a>
<p>Here, the selected activity card has UIID <em>ActivityCard</em> and layout <em>LayeredLayout</em>. In Java, we assign the UIID to the card container:</p>
<pre><code class="language-java">Container card = new Container(new LayeredLayout());
card.setUIID("ActivityCard");</code></pre>
<p>Then, CSS defines its visual appearance:</p>
<pre><code class="language-css">ActivityCard {
    background: #ffffff;
    background-color: #ffffff;
    color: #1f2933;
    border: 1px solid #d6dde7;
    border-radius: 3mm;
    font-family: "native:MainLight";
    font-size: 3mm;
    margin: 1.2mm 0 1.2mm 0;
    padding: 1.4mm;
}</code></pre>
<p><strong>Codename One CSS is intentionally close to web CSS</strong>, but has its own properties and conventions. Here, we use native fonts and dimensions in millimeters. The card appears pink in the screenshot because the Component Inspector highlights the selected component.</p>
<p>We can also define pressed and selected states and separate dark-mode rules:</p>
<pre><code class="language-css">ActivityCardPressed,
ActivityCard.pressed,
ActivityCard.selected {
    [...]
}
@media (prefers-color-scheme: dark) {
    ActivityCard {
        [...]
    }
    ActivityCardPressed,
    ActivityCard.pressed,
    ActivityCard.selected {
        [...]
    }
}</code></pre>
<p>The UIID is the bridge between the component tree and the final appearance of the app.</p>
<h3 id="bd-3-supporting-small-screens-large-screens-and-font-scaling" data-id="3-supporting-small-screens-large-screens-and-font-scaling"><strong>5.3. Supporting Small Screens, Large Screens, and Font Scaling</strong></h3>
<div class="bd-anchor" id="3-supporting-small-screens-large-screens-and-font-scaling"></div>
<p>If the user increases the system font size or changes the app font scale from the <em>Settings</em> form, labels may need more room, cards may become taller, and scrollable containers must absorb the extra content. This is useful on large screens, but can become problematic on small screens.</p>
<p>The complete source code includes defensive helpers for edge cases where single-line text doesn&#8217;t fit. These helpers can cap the font size so text stays within the available width. This is useful, but it adds complexity and should be evaluated on a case-by-case basis.</p>
<p><strong>From this point on, we focus on the main Codename One patterns rather than implementation details</strong>. <em>Daily Routine</em> is the reference example, but the goal is to learn how to structure Codename One apps in general.</p>
<h2 id="bd-building-the-core-activity-workflow" data-id="building-the-core-activity-workflow"><strong>6. Building the Core Activity Workflow</strong></h2>
<div class="bd-anchor" id="building-the-core-activity-workflow"></div>
<p>The activity workflow gives us the core of the app: a model, a store, a list screen, an editor, and a details screen.</p>
<h3 id="bd-1-modeling-activities" data-id="1-modeling-activities"><strong>6.1. Modeling Activities</strong></h3>
<div class="bd-anchor" id="1-modeling-activities"></div>
<p>The central domain object is <em>Activity</em>, implemented as a Java record:</p>
<pre><code class="language-java">public record Activity(
        String id,
        String title,
        ActivityCategory category,
        LocalDate date,
        LocalTime time,
        String notes,
        boolean completed,
        PlaceInfo place,
        Instant updatedAt
) [...]</code></pre>
<p>Categories are modeled with an enum. Each category has a storage code, a localization key, and a sort order:</p>
<pre><code class="language-java">public enum ActivityCategory {
    APPOINTMENT("appointment", "category.appointment", 0),
    ROUTINE("routine", "category.routine", 1),
    WORK("work", "category.work", 2),
    [...]
}</code></pre>
<p>This keeps the model simple to persist, localize, and display.</p>
<h3 id="bd-2-listing-activities-on-the-home-form" data-id="2-listing-activities-on-the-home-form"><strong>6.2. Listing Activities on the Home Form</strong></h3>
<div class="bd-anchor" id="2-listing-activities-on-the-home-form"></div>
<p>The home screen reads activities from <em>ActivityStore</em> and rebuilds the list when data changes.</p>
<p>Unlike a web page, a Codename One form doesn&#8217;t always update the visible UI immediately after we change the component hierarchy. <strong>This is a design choice that helps reduce unnecessary layout work, especially in complex component trees</strong>. The final call to <em>revalidate()</em> tells Codename One to lay out the updated <em>listContainer</em> again, so the changes are reflected on screen:</p>
<pre><code class="language-java">private void refreshList() {
    List&lt;Activity&gt; activities =
            context.getActivityStore().getActivitiesInInsertionOrder();
    listContainer.removeAll();
    for (Activity activity : activities) {
        listContainer.add(createCard(activity));
    }
    listContainer.revalidate();
}</code></pre>
<p>The form doesn&#8217;t load JSON directly. Instead, <strong>it uses <em>AppContext</em> to access the current <em>ActivityStore</em></strong>, keeping UI code focused on rendering and user interaction.</p>
<p>Each activity is displayed as a card:</p>
<pre><code class="language-java">private Container createCard(Activity activity) {
    Container card = new Container(new LayeredLayout());
    card.setUIID("ActivityCard");
    Container content = new Container(new BorderLayout());
    Container textColumn = new Container(BoxLayout.y());
    // Add icon, title, category, schedule, place, and status labels
    content.add(BorderLayout.CENTER, textColumn);
    card.add(content);
    Button overlay = createCardOverlayButton(
            "activityCard-" + activity.id(),
            () -&gt; context.showActivityDetails(this, activity)
    );
    card.add(overlay);
    return card;
}</code></pre>
<p>The card structure, CSS styling, and navigation follow the same pattern: component tree for structure, UIID for appearance, and <em>AppContext</em> for navigation.</p>
<h3 id="bd-3-adding-and-editing-activities" data-id="3-adding-and-editing-activities"><strong>6.3. Adding and Editing Activities</strong></h3>
<div class="bd-anchor" id="3-adding-and-editing-activities"></div>
<p>The editor form handles both new and existing activities. When saving, it validates required fields, builds an <em>ActivityDraft</em>, converts it into an <em>Activity</em>, and stores it:</p>
<pre><code class="language-java">private void saveActivity() {
    String title = titleField.getText() == null
            ? ""
            : titleField.getText().trim();
    if (title.length() == 0) {
        Dialog.show(
                context.text("validation.title"),
                context.text("validation.activity.title"),
                context.text("ok"),
                null
        );
        return;
    }
    ActivityDraft draft = new ActivityDraft();
    draft.setTitle(title);
    draft.setCategory(selectedCategory());
    draft.setNotes(notesArea.getText());
    draft.setCompleted(completedSwitch.isValue());
    draft.setPlace(selectedPlace);
    String id = existingActivity == null
            ? IdGenerator.newId()
            : existingActivity.id();
    context.getActivityStore().save(draft.toActivity(id));
    context.showHome();
}</code></pre>
<p>The exact form has more fields, such as date and time. <strong>The pattern is that the form edits a temporary state, while the store receives a complete activity only when the user confirms</strong>.</p>
<h3 id="bd-4-showing-activity-details" data-id="4-showing-activity-details"><strong>6.4. Showing Activity Details</strong></h3>
<div class="bd-anchor" id="4-showing-activity-details"></div>
<p>The details screen shows one activity and offers actions such as editing or toggling completion:</p>
<pre><code class="language-java">public void toggleCompleted() {
    context.getActivityStore().toggleCompleted(activityId);
    refresh();
}</code></pre>
<p>Since <em>Activity</em> is immutable, the store toggles completion by creating a new instance and saving it.</p>
<p>The edit action uses the same navigation pattern:</p>
<pre><code class="language-java">public void editCurrentActivity() {
    context.showActivityForm(this, currentActivity());
}</code></pre>
<p>At this point, the core workflow is complete: list, editor, and details form.</p>
<h2 id="bd-saving-and-testing-data-locally" data-id="saving-and-testing-data-locally"><strong>7. Saving and Testing Data Locally</strong></h2>
<div class="bd-anchor" id="saving-and-testing-data-locally"></div>
<p>The activity workflow now works, but data must survive restarts.</p>
<h3 id="bd-1-persisting-activities-as-json" data-id="1-persisting-activities-as-json"><strong>7.1. Persisting Activities as JSON</strong></h3>
<div class="bd-anchor" id="1-persisting-activities-as-json"></div>
<p>The UI doesn&#8217;t interact with files directly. It calls the store, and the store delegates persistence to <em>ActivityRepository</em>:</p>
<pre><code class="language-java">public class ActivityRepository {
    public List&lt;Activity&gt; loadActivities() throws IOException {
        String path = filePath();
        if (!FileSystemStorage.getInstance().exists(path)) {
            return new ArrayList&lt;&gt;();
        }
        String json = IOUtil.readUtf8File(path);
        return codec.decodeActivities(json);
    }
    public void saveActivities(List&lt;Activity&gt; activities) throws IOException {
        IOUtil.writeUtf8File(
            filePath(),
            codec.encodeActivities(activities)
        );
    }
}</code></pre>
<p>The repository keeps forms independent from file storage, and its logic can be tested without involving forms or user interaction.</p>
<p>The JSON conversion is handled by a codec:</p>
<pre><code class="language-java">String json = codec.encodeActivities(activities);
List&lt;Activity&gt; activities = codec.decodeActivities(json);</code></pre>
<p>Thus, the model stays independent, the repository owns persistence, and the UI works with domain objects.</p>
<h3 id="bd-2-using-the-app-home-directory" data-id="2-using-the-app-home-directory"><strong>7.2. Using the App Home Directory</strong></h3>
<div class="bd-anchor" id="2-using-the-app-home-directory"></div>
<p>Codename One provides an app-specific home directory for local files. The lifecycle creates the repositories, while each repository owns its storage path:</p>
<pre><code class="language-java">protected ActivityRepository createActivityRepository() {
    return new ActivityRepository();
}
protected SettingsRepository createSettingsRepository() {
    return new SettingsRepository();
}</code></pre>
<p>The activity repository stores JSON under the app home directory:</p>
<pre><code class="language-java">public String filePath() {
    return appHomePath() + "activities.json";
}</code></pre>
<p>The settings repository follows the same idea:</p>
<pre><code class="language-java">protected String filePath() {
    return CN.getAppHomePath() + "settings.json";
}</code></pre>
<p>In the complete implementation, both repositories support a test-only override, so tests can write to a dedicated location.</p>
<p>This keeps persistence portable: the actual location differs between targets, but the app uses the same repository abstraction. In the simulator, the JSON files are easy to inspect and edit; on real devices and browser builds, storage is platform-specific and usually less convenient to inspect directly.</p>
<h3 id="bd-3-testing-the-repository" data-id="3-testing-the-repository"><strong>7.3. Testing the Repository</strong></h3>
<div class="bd-anchor" id="3-testing-the-repository"></div>
<p>Codename One includes a testing API based on <em>AbstractTest</em>, usable through the simulator and device testing workflow.</p>
<p>Among the tests in the complete source code, one focused example verifies local persistence:</p>
<pre><code class="language-java">public class ActivityRepositoryRoundTripTest extends AbstractTest {
    @Override
    public boolean runTest() throws Exception {
        String path = FileSystemStorage.getInstance().getAppHomePath()
                + "tests/activity-round-trip-"
                + System.currentTimeMillis()
                + ".json";
        TestActivityRepository repository = new TestActivityRepository(path);
        List&lt;Activity&gt; activities = new ArrayList&lt;&gt;();
        Activity original = createActivity();
        activities.add(original);
        try {
            repository.saveActivities(activities);
            List&lt;Activity&gt; loaded = repository.loadActivities();
            assertEqual(1, loaded.size(), "Exactly one activity should be loaded");
            assertEqual(original, loaded.get(0), "The activity should survive a JSON round trip");
            return true;
        } finally {
            repository.reset();
        }
    }
}</code></pre>
<p><em>TestActivityRepository</em> redirects persistence to a test-specific file. <strong>Codename One also supports JUnit 5</strong> for simulator-side tests on the JavaSE port, while <strong><em>AbstractTest</em> remains the most portable option</strong> inside the Codename One runtime model.</p>
<h2 id="bd-adding-place-autocomplete-rest-and-maps" data-id="adding-place-autocomplete-rest-and-maps"><strong>8. Adding Place Autocomplete, REST, and Maps</strong></h2>
<div class="bd-anchor" id="adding-place-autocomplete-rest-and-maps"></div>
<p>Finally, we add place search and map display: a concise example of REST, JSON parsing, and embedded web content.</p>
<h3 id="bd-1-searching-places-with-geoapify" data-id="1-searching-places-with-geoapify"><strong>8.1. Searching Places With Geoapify</strong></h3>
<div class="bd-anchor" id="1-searching-places-with-geoapify"></div>
<p>The editor lets the user search for a place and attach it to an activity. We use <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.geoapify.com/">Geoapify</a> because it provides autocomplete and map preview APIs, has a generous free tier, and enables the creation of a free API key without a credit card.</p>
<p>The complete source code includes a free example key, lightly obfuscated with XOR, so the demo can run out of the box. XOR isn&#8217;t security; it only avoids storing the key as plain text. For production, we should use a private key. <strong>Even better, the key shouldn&#8217;t live inside the app</strong>: the app should call the backend, and the backend should call Geoapify.</p>
<p>The form depends on a service interface, not on Geoapify directly:</p>
<pre><code class="language-java">public interface PlaceSearchService {
    SearchHandle search(
            AppContext context,
            String query,
            SuccessCallback&lt;List&lt;PlaceSuggestion&gt;&gt; onSuccess,
            FailureCallback&lt;List&lt;PlaceSuggestion&gt;&gt; onFailure
    );
    interface SearchHandle {
        void cancel();
    }
}</code></pre>
<p>The implementation builds an autocomplete URL and submits a Codename One network request:</p>
<pre><code class="language-java">private String buildAutocompleteUrl(AppContext context, String query) {
    StringBuilder builder = new StringBuilder(
            "https://api.geoapify.com/v1/geocode/autocomplete?"
    );
    builder.append("text=").append(Util.encodeUrl(query));
    builder.append("&amp;format=geojson");
    builder.append("&amp;limit=").append(AppConfig.GEOAPIFY_RESULT_LIMIT);
    builder.append("&amp;lang=").append(context.getSettings().languageCode());
    builder.append("&amp;apiKey=").append(AppConfig.geoapifyApiKey());
    return builder.toString();
}</code></pre>
<p>Again, the form depends on an application service, while provider-specific code stays in one place.</p>
<p>When working with callbacks and UI updates, we should also keep Codename One&#8217;s <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.codenameone.com/courses/course-01-java-for-mobile-devices/011-threading-and-the-edt/">EDT (Event Dispatch Thread)</a> in mind, so slow work, such as a network request, doesn&#8217;t block the interface.</p>
<h3 id="bd-2-parsing-the-rest-response" data-id="2-parsing-the-rest-response"><strong>8.2. Parsing the REST Response</strong></h3>
<div class="bd-anchor" id="2-parsing-the-rest-response"></div>
<p>Geoapify returns GeoJSON. The service extracts only the fields the app needs and creates <em>PlaceSuggestion</em> objects:</p>
<pre><code class="language-java">@SuppressWarnings("rawtypes")
private List&lt;PlaceSuggestion&gt; parseSuggestions(String json) throws IOException {
    JSONParser parser = new JSONParser();
    Map&lt;String, Object&gt; root = parser.parseJSON(new StringReader(json));
    List&lt;PlaceSuggestion&gt; suggestions = new ArrayList&lt;&gt;();
    List features = (List) root.get("features");
    // Read properties, geometry, and coordinates
    // Convert each feature into a PlaceSuggestion
    return suggestions;
}</code></pre>
<p>The form receives domain objects, not raw JSON maps.</p>
<h3 id="bd-3-showing-a-map-preview-with-browsercomponent" data-id="3-showing-a-map-preview-with-browsercomponent"><strong>8.3. Showing a Map Preview With BrowserComponent</strong></h3>
<div class="bd-anchor" id="3-showing-a-map-preview-with-browsercomponent"></div>
<p>After the user selects a place, <em>GeoapifyMapView</em> checks whether a native browser component is available, then shows a Leaflet page:</p>
<pre><code class="language-java">browser = new BrowserComponent();
browser.setPinchToZoomEnabled(true);
browser.setNativeScrollingEnabled(true);
browser.setPage(buildMapHtml(place), null);
add(BorderLayout.CENTER, browser);</code></pre>
<p>The page loads Leaflet, uses Geoapify map tiles, centers the map, and adds a marker:</p>
<pre><code class="language-java">private String buildMapHtml(PlaceInfo place) {
    String tileUrl = "https://maps.geoapify.com/v1/tile/"
            + AppConfig.GEOAPIFY_TILE_STYLE
            + "/{z}/{x}/{y}.png?apiKey="
            + AppConfig.geoapifyApiKey();
    return "&lt;!doctype html&gt;&lt;html&gt;&lt;head&gt;"
            + "&lt;link rel=\"stylesheet\" "
            + "href=\"https://unpkg.com/leaflet@1.9.4/dist/leaflet.css\"/&gt;"
            + "&lt;/head&gt;&lt;body&gt;&lt;div id=\"map\"&gt;&lt;/div&gt;"
            + "&lt;script src=\"https://unpkg.com/leaflet@1.9.4/dist/leaflet.js\"&gt;&lt;/script&gt;"
            + "&lt;script&gt;"
            + "var map = L.map('map').setView(["
            + place.latitude() + "," + place.longitude() + "],"
            + AppConfig.MAP_ZOOM + ");"
            + "L.tileLayer('" + tileUrl + "').addTo(map);"
            + "L.marker([" + place.latitude() + ","
            + place.longitude() + "]).addTo(map);"
            + "&lt;/script&gt;&lt;/body&gt;&lt;/html&gt;";
}</code></pre>
<p>The rest of the app works with a normal Codename One component, while the embedded browser handles the map. If native browser support isn&#8217;t available, the complete source code falls back to a static Geoapify map image.</p>
<h2 id="bd-conclusion" data-id="conclusion"><strong>9. Conclusion</strong></h2>
<div class="bd-anchor" id="conclusion"></div>
<p>In this tutorial, <strong>we built a realistic Codename One application with Java 17 and Maven</strong>. Furthermore, we used the <em>Daily Routine</em> application to understand the main patterns behind Codename One development: lifecycle, shared context, navigation, forms, layout managers, UIIDs, CSS, persistence, testing, REST, and map previews.</p>
<p>The same design principle appeared repeatedly: keep forms focused on UI and user interaction, and move persistence, networking, localization, settings, and reusable logic into small services and repositories.</p>
<p>As always, the complete source code for this tutorial is available <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eugenp/tutorials/tree/master/codenameone">over on GitHub</a>.</p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-codename-one-cross-platform">Introduction to Cross-Platform Java Development With Codename One</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.<Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/957869417/0/baeldung">
<div class="fbz_enclosure" style="clear:left"><video controls="controls" style="display:block;padding:0.5em 0;max-width:100%;" ><source src="https://feeds.feedblitz.com/-/957869414/0/baeldung.mp4">Click the icon below to watch.</video><a href="https://feeds.feedblitz.com/-/957869414/0/baeldung.mp4" title="Play video"><img border="0" width="40" height="40" src="https://assets.feedblitz.com/i/movie.png"/></a></div>
<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/957869417/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/957869417/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f07%2fJava-Featured-15-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/24/957869417/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/957869417/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/957869417/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/java-codename-one-cross-platform#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/java-codename-one-cross-platform/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</content:encoded>
					
					<wfw:commentRss>https://feeds.feedblitz.com/~/957869417/0/baeldung/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		<enclosure url="https://feeds.feedblitz.com/-/957869414/0/baeldung.mp4" length="869858" type="video/mp4" />
<enclosure url="https://www.baeldung.com/wp-content/uploads/2026/06/Codename-One-Simulator-example.mp4" length="644905" type="video/mp4" />
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-15-150x150.jpg</webfeeds:featuredImage>
<feedburner:origEnclosureLink>https://www.baeldung.com/wp-content/uploads/2026/06/Baeldung-Codename-One-Tutorial-JavaScript-port-of-the-demo-app.mp4</feedburner:origEnclosureLink>
</item>
<item>
<feedburner:origLink>https://www.baeldung.com/java-weekly-649</feedburner:origLink>
		<title>Java Weekly, Issue 649</title>
		<link>https://feeds.feedblitz.com/~/957780032/0/baeldung</link>
					<comments>https://feeds.feedblitz.com/~/957780032/0/baeldung#respond</comments>
		
		<dc:creator><![CDATA[baeldung]]></dc:creator>
		<pubDate>Fri, 05 Jun 2026 15:34:50 +0000</pubDate>
				<category><![CDATA[Weekly Review]]></category>
		<category><![CDATA[no-ads]]></category>
		<category><![CDATA[no-after-post]]></category>
		<category><![CDATA[no-before-post]]></category>
		<category><![CDATA[no-optins]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/?p=203942</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2016/10/social-Weekly-Reviews-4.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="max-width:100% !important;height:auto !important;float: left; margin-right: 5px;" loading="lazy" /><p>No doubt about it, AI is changing what it means to work in Java.</p>
The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/957780032/0/baeldung">Java Weekly, Issue 649</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/957780032/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/957780032/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2016%2f10%2fsocial-Weekly-Reviews-4.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/24/957780032/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/957780032/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/957780032/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/java-weekly-649#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/java-weekly-649/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</description>
										<content:encoded><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2016/10/social-Weekly-Reviews-4.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="float: left; margin-right: 5px;" decoding="async" loading="lazy" srcset="https://www.baeldung.com/wp-content/uploads/2016/10/social-Weekly-Reviews-4.jpg 952w, https://www.baeldung.com/wp-content/uploads/2016/10/social-Weekly-Reviews-4-300x157.jpg 300w, https://www.baeldung.com/wp-content/uploads/2016/10/social-Weekly-Reviews-4-768x402.jpg 768w" sizes="auto, (max-width: 580px) 100vw, 580px" /><h2 style="text-align: left;" id="bd-spring-and-java" data-id="spring-and-java">1.<strong> Spring and Java</strong></h2>
<div class="bd-anchor" id="spring-and-java"></div>
<p><strong><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://spring.io/blog/2026/06/01/spring_and_security_in_the_times_of_ai">&gt;&gt; Spring and Security In The Times Of AI</a></strong> [<span style="color: #993300;">spring.io</span>]</p>
<p>A candid look at why the May release train moved into June, and why this round of upgrades deserves real attention: AI-assisted scanning is changing the volume of security reports across open source. An interesting read.</p>
<h4><strong>Also worth reading:</strong></h4>
<ul>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://blog.jetbrains.com/idea/2026/05/hibernate-7-4-new-features/" target="_blank" rel="noopener"><strong>Hibernate 7.4 New Features</strong></a> [<span style="color: #800000;">jetbrains.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://foojay.io/today/tiberius-a-security-testing-framework-for-llm-applications-in-java/" target="_blank" rel="noopener"><strong>Tiberius: A Security Testing Framework for LLM Applications in Java</strong></a> [<span style="color: #800000;">foojay.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://inside.java/2026/06/02/jfr-ai-monitor/" target="_blank" rel="noopener"><strong>Intelligent JVM Monitoring: Combining JDK Flight Recorder with AI</strong></a> [<span style="color: #800000;">inside.java</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://in.relation.to/2026/05/29/hibernate-json-dirty-checking-performance/" target="_blank" rel="noopener"><strong>Two Hibernate Annotations That Cut Our JSON Processing Time in Half</strong></a> [<span style="color: #800000;">in.relation.to</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://quarkus.io/blog/ai-skills-migration/" target="_blank" rel="noopener"><strong>Empower your AI migrations to Quarkus with Skills</strong></a> [<span style="color: #800000;">quarkus.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://foojay.io/today/jakarta-ee-is-ready-for-ai-but-dont-just-take-my-word-for-it/" target="_blank" rel="noopener"><strong>Jakarta EE is Ready for AI &#8211; But Don&#8217;t Just Take My Word for It!</strong></a> [<span style="color: #800000;">foojay.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://quarkus.io/blog/mandrel-25-minimum-version/" target="_blank" rel="noopener"><strong>Bumping the minimum officially supported GraalVM version to 25.0</strong></a> [<span style="color: #800000;">quarkus.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://quarkus.io/blog/panache-next-renamed-to-quarkus-data/" target="_blank" rel="noopener"><strong>Quarkus Hibernate with Panache Next renamed to Quarkus Data Hibernate</strong></a> [<span style="color: #800000;">quarkus.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://openjdk.org/jeps/538" target="_blank" rel="noopener"><strong>PEM Encodings of Cryptographic Objects (Third Preview)</strong></a> [<span style="color: #800000;">openjdk.org</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://openjdk.org/jeps/528" target="_blank" rel="noopener"><strong>Post-Mortem Crash Analysis with jcmd</strong></a> [<span style="color: #800000;">openjdk.org</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://openjdk.org/jeps/536" target="_blank" rel="noopener"><strong>JFR In-Process Data Redaction</strong></a> [<span style="color: #800000;">openjdk.org</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://bytecodealliance.org/articles/endive-and-the-next-chapter-of-webassembly-on-the-jvm" target="_blank" rel="noopener"><strong>Endive and the next chapter of WebAssembly on the JVM</strong></a> [<span style="color: #800000;">bytecodealliance.org</span>]</li>
</ul>
<h4><strong>Webinars and presentations:</strong></h4>
<ul>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://inside.java/2026/05/28/podcast-059/" target="_blank" rel="noopener"><strong>Episode 59 &#8220;Java *is* Memory Efficient&#8221; [AtA]</strong></a> [<span style="color: #800000;">inside.java</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://spring.io/blog/2026/06/04/a-bootiful-podcast-marit-van-dijk" target="_blank" rel="noopener"><strong>A Bootiful Podcast: JetBrains&#8217; Marit van Dijk</strong></a> [<span style="color: #800000;">spring.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://spring.io/blog/2026/05/28/a-bootiful-podcast-martijn-verburg" target="_blank" rel="noopener"><strong>A Bootiful Podcast: Microsoft&#8217;s Martijn Verburg</strong></a> [<span style="color: #800000;">spring.io</span>]</li>
</ul>
<h4><strong>Time to upgrade:</strong></h4>
<ul>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://in.relation.to/2026/05/29/hibernate-reactive-4_4_0_Final/" target="_blank" rel="noopener"><strong>Hibernate Reactive 4.4.0.Final released</strong></a> [<span style="color: #800000;">in.relation.to</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://blog.jetbrains.com/idea/2026/06/intellij-idea-2026-1-3/" target="_blank" rel="noopener"><strong>IntelliJ IDEA 2026.1.3 Is Out!</strong></a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://blog.jetbrains.com/idea/2026/06/intellij-idea-2025-3-6/" target="_blank" rel="noopener"><strong>IntelliJ IDEA 2025.3.6 Is Out!</strong></a> [<span style="color: #800000;">jetbrains.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://blog.jetbrains.com/kotlin/2026/06/kotlin-2-4-0-released/" target="_blank" rel="noopener"><strong>Kotlin 2.4.0 Released</strong></a> [<span style="color: #800000;">jetbrains.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://quarkus.io/blog/quarkus-3-36-1-released/" target="_blank" rel="noopener"><strong>Quarkus 3.36.1 &#8211; Maintenance release</strong></a> [<span style="color: #800000;">quarkus.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://spring.io/blog/2026/05/27/spring-ai-2-0-0-M8-available-now" target="_blank" rel="noopener"><strong>Spring AI 2.0.0-M8 is available now</strong></a> [<span style="color: #800000;">spring.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/jetty/jetty.project/releases/tag/jetty-12.1.10" target="_blank" rel="noopener"><strong>Jetty 12.1.10</strong></a>, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/jetty/jetty.project/releases/tag/jetty-12.0.36" target="_blank" rel="noopener"><strong>12.0.36</strong></a>, and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/jetty/jetty.project/releases/tag/jetty-12.0.35" target="_blank" rel="noopener"><strong>12.0.35</strong></a> [<span style="color: #800000;">github.com/jetty</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eclipse-vertx/vert.x/releases/tag/4.5.28" target="_blank" rel="noopener"><strong>Vert.x 4.5.28</strong></a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eclipse-vertx/vert.x/releases/tag/5.1.1" target="_blank" rel="noopener"><strong>5.1.1</strong></a> [<span style="color: #800000;">github.com/eclipse-vertx</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/elastic/elasticsearch/releases/tag/v9.4.2" target="_blank" rel="noopener"><strong>Elasticsearch 9.4.2</strong></a>, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/elastic/elasticsearch/releases/tag/v9.3.5" target="_blank" rel="noopener"><strong>9.3.5</strong></a>, and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/elastic/elasticsearch/releases/tag/v8.19.16" target="_blank" rel="noopener"><strong>8.19.16</strong></a> [<span style="color: #800000;">github.com/elastic</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/Netflix/zuul/releases/tag/v3.6.10" target="_blank" rel="noopener"><strong>Zuul v3.6.10</strong></a> [<span style="color: #800000;">github.com/Netflix</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/micronaut-projects/micronaut-core/releases/tag/v5.0.2" target="_blank" rel="noopener"><strong>Micronaut Core 5.0.2</strong></a>, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/micronaut-projects/micronaut-core/releases/tag/v5.0.1" target="_blank" rel="noopener"><strong>5.0.1</strong></a>, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/micronaut-projects/micronaut-core/releases/tag/v4.10.25" target="_blank" rel="noopener"><strong>4.10.25</strong></a>, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/micronaut-projects/micronaut-core/releases/tag/v4.10.24" target="_blank" rel="noopener"><strong>4.10.24</strong></a>, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/micronaut-projects/micronaut-core/releases/tag/v3.10.8" target="_blank" rel="noopener"><strong>3.10.8</strong></a>, and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/micronaut-projects/micronaut-core/releases/tag/v3.10.7" target="_blank" rel="noopener"><strong>3.10.7</strong></a> [<span style="color: #800000;">github.com/micronaut-projects</span>]</li>
</ul>
<h2 style="text-align: left;" id="bd-technical-amp-musings" data-id="technical-amp-musings">2.<strong> Technical &amp; Musings</strong></h2>
<div class="bd-anchor" id="technical-amp-musings"></div>
<p><strong><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://netflixtechblog.com/from-silos-to-service-topology-why-netflix-built-a-real-time-service-map-0165ba13a7bc">&gt;&gt; From Silos to Service Topology: Why Netflix Built a Real-Time Service Map</a></strong> [<span style="color: #993300;">netflixtechblog.com</span>]</p>
<p>Netflix moved toward a live topology map of service dependencies.</p>
<h4><strong>Also worth reading:</strong></h4>
<ul>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://blog.frankel.ch/ai-gateways/" target="_blank" rel="noopener"><strong>AI gateways: why and how</strong></a> [<span style="color: #800000;">frankel.ch</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://henrikwarne.com/2026/05/31/with-claude-less-coding-more-testing/" target="_blank" rel="noopener"><strong>With Claude: Less Coding, More Testing</strong></a> [<span style="color: #800000;">henrikwarne.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://netflixtechblog.com/dynamically-splitting-wide-partitions-in-cassandra-for-time-series-workloads-0eded064f456" target="_blank" rel="noopener"><strong>Dynamic Repartitioning for Time Series Workloads</strong></a> [<span style="color: #800000;">netflixtechblog.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://krasserm.github.io/2026/05/31/ml-research-workflow/" target="_blank" rel="noopener"><strong>Building an autonomous ML researcher with Claude Code dynamic workflows</strong></a> [<span style="color: #800000;">krasserm.github.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.troyhunt.com/1000-data-breaches-later-the-disclosure-lag-is-worse-than-ever/" target="_blank" rel="noopener"><strong>1,000 Data Breaches Later, the Disclosure Lag is Worse Than Ever</strong></a> [<span style="color: #800000;">troyhunt.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://foojay.io/today/nfc-crypto-biometrics-and-a-new-build-cloud/" target="_blank" rel="noopener"><strong>NFC, Crypto, Biometrics, And A New Build Cloud</strong></a> [<span style="color: #800000;">foojay.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.satisfice.com/blog/archives/488130" target="_blank" rel="noopener"><strong>Humans In The Way</strong></a> [<span style="color: #800000;">satisfice.com</span>]</li>
</ul>
<h2 style="text-align: left;" id="bd-pick-of-the-week" data-id="pick-of-the-week">3.<strong> Pick of the Week</strong></h2>
<div class="bd-anchor" id="pick-of-the-week"></div>
<p><strong><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://sive.rs/subtract">&gt;&gt; Subtract</a></strong> [<span style="color: #993300;">sive.rs</span>]</p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-weekly-649">Java Weekly, Issue 649</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.<Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/957780032/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/957780032/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/957780032/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2016%2f10%2fsocial-Weekly-Reviews-4.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/24/957780032/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/957780032/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/957780032/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/java-weekly-649#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/java-weekly-649/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</content:encoded>
					
					<wfw:commentRss>https://feeds.feedblitz.com/~/957780032/0/baeldung/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2016/10/social-Weekly-Reviews-4-150x150.jpg</webfeeds:featuredImage></item>
<item>
<feedburner:origLink>https://www.baeldung.com/alibaba-nacos-basics</feedburner:origLink>
		<title>Introduction to Alibaba Nacos</title>
		<link>https://feeds.feedblitz.com/~/957626876/0/baeldung</link>
					<comments>https://feeds.feedblitz.com/~/957626876/0/baeldung#respond</comments>
		
		<dc:creator><![CDATA[Graham Cox]]></dc:creator>
		<pubDate>Mon, 01 Jun 2026 22:08:16 +0000</pubDate>
				<category><![CDATA[Architecture]]></category>
		<category><![CDATA[Microservices]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/alibaba-nacos-basics</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-12-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="max-width:100% !important;height:auto !important;float: left; margin-right: 5px;" loading="lazy" /><p>Learn what Alibaba Nacos is, how to set it up, and how to use it for service discovery, centralized configuration management, and coordination in distributed applications.</p>
The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/957626876/0/baeldung">Introduction to Alibaba Nacos</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/957626876/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/957626876/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f07%2fJava-Featured-12-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/24/957626876/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/957626876/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/957626876/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/alibaba-nacos-basics#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/alibaba-nacos-basics/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</description>
										<content:encoded><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-12-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="float: left; margin-right: 5px;" decoding="async" loading="lazy" srcset="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-12-1024x536.jpg 1024w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-12-300x157.jpg 300w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-12-768x402.jpg 768w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-12-100x52.jpg 100w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-12.jpg 1200w" sizes="auto, (max-width: 580px) 100vw, 580px" /><h2 id="bd-introduction" data-id="introduction"><strong>1. Introduction</strong></h2>
<div class="bd-anchor" id="introduction"></div>
<p>When it comes to big data and many potential parties interested in it, distribution is critical. To that end, there are simple and complex solutions.</p>
<p>In this tutorial, we&#8217;ll take a look at <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://nacos.io/">Alibaba Nacos</a>. First, we see what it is. After that, we go over different ways to configure and use Nacos. Finally, we demonstrate what we can do with the platform.</p>
<h2 id="bd-what-is-nacos" data-id="what-is-nacos"><strong>2. What Is Nacos?</strong></h2>
<div class="bd-anchor" id="what-is-nacos"></div>
<p>Nacos is a platform offering various tools:</p>
<ul>
<li>dynamic service discovery</li>
<li>configuration management</li>
<li>distributed locking</li>
</ul>
<p><strong>It&#8217;s mainly used for building highly distributed systems</strong>.</p>
<p>At its core, Nacos provides a service discovery system enabling us to dynamically start and stop instances of different services, and still have other services able to reach them. This is particularly valuable in microservices applications where we might have a larger number of applications that each have a varied number of running instances.</p>
<p>Nacos also offers tools for managing the configuration of applications, providing a way to configure everything in a single place and have the suite of running instances automatically reflect this. Furthermore, we also have tools for distributed locking, enabling distributed applications to share finite resources without conflict.</p>
<p>As expected, there are some more recently introduced features for working with AI Agents &#8211; including MCP Servers, Skills, Prompts, and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.agentcard.net/">AgentCard</a> discovery. However, these are out of scope for this article.</p>
<h2 id="bd-running-nacos" data-id="running-nacos"><strong>3. Running Nacos</strong></h2>
<div class="bd-anchor" id="running-nacos"></div>
<p>The easiest way to get started quickly with Nacos is by using <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/ops/docker-guide">Docker</a>.</p>
<p>In particular, <strong>we can start a standalone Nacos instance using a fairly simple <em>docker-compose.yml</em> file</strong>:</p>
<pre><code class="language-yaml">services:
  nacos:
    image: nacos/nacos-server:latest
    environment:
      - MODE=standalone
      - NACOS_AUTH_TOKEN=U2VjdXJlTmFjb3NBdXRoVG9rZW5Gb3JEZW1vUHVycG9zZXMxMjM=
      - NACOS_AUTH_IDENTITY_KEY=serverIdentity
      - NACOS_AUTH_IDENTITY_VALUE=nacos-demo-node
    ports:
      - "8080:8080"
      - "8848:8848"
      - "9848:9848"</code></pre>
<p>The <em>NACOS_AUTH_TOKEN</em> variable must contain a string that&#8217;s at least 32 characters long and then base-64 encoded. On the other hand, <em>NACOS_AUTH_IDENTITY_KEY</em> and <em>NACOS_AUTH_IDENTITY_VALUE</em> can contain any values that identify the current Nacos instance.</p>
<p>At this point, we can run the setup:</p>
<pre><code class="language-shell">$ docker compose up
.....
nacos-1  | 2026-04-27 14:20:53,304 INFO Nacos Console started successfully in 311 ms</code></pre>
<p>Once started, we can access the console on <em><a href="http://localhost:8080">http://localhost:8080</a></em>.</p>
<p><strong>On first access, we need to configure a password for the <em>nacos</em> user</strong>:</p>
<a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/wp-content/uploads/2026/06/nacos-configure-user.png"><img loading="lazy" decoding="async" class="alignnone wp-image-203893 size-medium" src="https://www.baeldung.com/wp-content/uploads/2026/06/nacos-configure-user-282x300.png" alt="Nacos user configuration" width="282" height="300" srcset="https://www.baeldung.com/wp-content/uploads/2026/06/nacos-configure-user-282x300.png 282w, https://www.baeldung.com/wp-content/uploads/2026/06/nacos-configure-user-962x1024.png 962w, https://www.baeldung.com/wp-content/uploads/2026/06/nacos-configure-user-768x817.png 768w, https://www.baeldung.com/wp-content/uploads/2026/06/nacos-configure-user-100x106.png 100w, https://www.baeldung.com/wp-content/uploads/2026/06/nacos-configure-user-600x639.png 600w, https://www.baeldung.com/wp-content/uploads/2026/06/nacos-configure-user.png 964w" sizes="auto, (max-width: 282px) 100vw, 282px" /></a>
<p>&nbsp;</p>
<p>Now, let&#8217;s log into the management console:</p>
<a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/wp-content/uploads/2026/06/nacos-configuration-scaled.png"><img loading="lazy" decoding="async" class="alignnone wp-image-203894 size-large" src="https://www.baeldung.com/wp-content/uploads/2026/06/nacos-configuration-1024x591.png" alt="Nacos management" width="580" height="335" srcset="https://www.baeldung.com/wp-content/uploads/2026/06/nacos-configuration-1024x591.png 1024w, https://www.baeldung.com/wp-content/uploads/2026/06/nacos-configuration-300x173.png 300w, https://www.baeldung.com/wp-content/uploads/2026/06/nacos-configuration-768x444.png 768w, https://www.baeldung.com/wp-content/uploads/2026/06/nacos-configuration-1536x887.png 1536w, https://www.baeldung.com/wp-content/uploads/2026/06/nacos-configuration-2048x1183.png 2048w, https://www.baeldung.com/wp-content/uploads/2026/06/nacos-configuration-100x58.png 100w, https://www.baeldung.com/wp-content/uploads/2026/06/nacos-configuration-600x347.png 600w" sizes="auto, (max-width: 580px) 100vw, 580px" /></a>
<p>&nbsp;</p>
<p>Running Nacos in this way, we get a set of default data already present to work with, but we can use this UI to configure a custom environment.</p>
<h2 id="bd-using-nacos-with-java-sdk" data-id="using-nacos-with-java-sdk"><strong>4. Using Nacos With Java SDK</strong></h2>
<div class="bd-anchor" id="using-nacos-with-java-sdk"></div>
<p>Now that we&#8217;ve got Nacos running, we can use it from applications.</p>
<p><strong>To do this, we first need to add <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/com.alibaba.nacos/nacos-client">the client dependency</a> to the project</strong>. At the time of writing, the latest version is <a class="vbtn release" href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/com.alibaba.nacos/nacos-client/3.2.1">3.2.1</a>.</p>
<p>If using Maven, we can include this dependency in the <em>pom.xml</em> file:</p>
<pre><code class="language-xml">&lt;dependency&gt;
    &lt;groupId&gt;com.alibaba.nacos&lt;/groupId&gt;
    &lt;artifactId&gt;nacos-client&lt;/artifactId&gt;
    &lt;version&gt;3.2.1&lt;/version&gt;
&lt;/dependency&gt;</code></pre>
<p>To leverage Nacos within the application, we need to know the address of the Nacos server we&#8217;re working with, the namespace we&#8217;re going to use if it&#8217;s not the default, and any other configuration properties for the service in question:</p>
<pre><code class="language-java">Properties properties = new Properties();
properties.setProperty(PropertyKeyConst.SERVER_ADDR, "localhost:8848");
properties.setProperty(PropertyKeyConst.NAMESPACE, "public");</code></pre>
<p>Now, we can then create a client for each of the Nacos services that we require:</p>
<pre><code class="language-java">ConfigService configService = NacosFactory.createConfigService(properties);
NamingService namingService = NacosFactory.createNamingService(properties);
LockService lockService = NacosFactory.createLockService(properties);</code></pre>
<p>These service clients are then ready for us to use.</p>
<h2 id="bd-configuration-management" data-id="configuration-management"><strong>5. Configuration Management</strong></h2>
<div class="bd-anchor" id="configuration-management"></div>
<p>Nacos enables the management of configuration settings in a single, central place, so services can make use of them automatically.</p>
<h3 id="bd-1-managing-configuration" data-id="1-managing-configuration"><strong>5.1. Managing Configuration</strong></h3>
<div class="bd-anchor" id="1-managing-configuration"></div>
<p>In particular, <strong>we can manage the configuration settings from within the management console by selecting the <em>Configurations</em> entry on the sidebar</strong>:</p>
<a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/wp-content/uploads/2026/06/nacos-configuration-scaled.png"><img loading="lazy" decoding="async" class="alignnone wp-image-203894 size-large" src="https://www.baeldung.com/wp-content/uploads/2026/06/nacos-configuration-1024x591.png" alt="Nacos configurations management" width="580" height="335" srcset="https://www.baeldung.com/wp-content/uploads/2026/06/nacos-configuration-1024x591.png 1024w, https://www.baeldung.com/wp-content/uploads/2026/06/nacos-configuration-300x173.png 300w, https://www.baeldung.com/wp-content/uploads/2026/06/nacos-configuration-768x444.png 768w, https://www.baeldung.com/wp-content/uploads/2026/06/nacos-configuration-1536x887.png 1536w, https://www.baeldung.com/wp-content/uploads/2026/06/nacos-configuration-2048x1183.png 2048w, https://www.baeldung.com/wp-content/uploads/2026/06/nacos-configuration-100x58.png 100w, https://www.baeldung.com/wp-content/uploads/2026/06/nacos-configuration-600x347.png 600w" sizes="auto, (max-width: 580px) 100vw, 580px" /></a>
<p>&nbsp;</p>
<p>This section gives us a list of all the configuration entries that are currently in the system, as well as letting us add new ones.</p>
<p>New entries are added using the <em>New Config</em> button at the top:</p>
<a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/wp-content/uploads/2026/06/new-config-scaled.png"><img loading="lazy" decoding="async" class="alignnone wp-image-203895 size-large" src="https://www.baeldung.com/wp-content/uploads/2026/06/new-config-1024x686.png" alt="Nacos create configuration" width="580" height="389" srcset="https://www.baeldung.com/wp-content/uploads/2026/06/new-config-1024x686.png 1024w, https://www.baeldung.com/wp-content/uploads/2026/06/new-config-300x201.png 300w, https://www.baeldung.com/wp-content/uploads/2026/06/new-config-768x514.png 768w, https://www.baeldung.com/wp-content/uploads/2026/06/new-config-1536x1028.png 1536w, https://www.baeldung.com/wp-content/uploads/2026/06/new-config-2048x1371.png 2048w, https://www.baeldung.com/wp-content/uploads/2026/06/new-config-100x67.png 100w, https://www.baeldung.com/wp-content/uploads/2026/06/new-config-600x402.png 600w" sizes="auto, (max-width: 580px) 100vw, 580px" /></a>
<p>&nbsp;</p>
<p>From here, we enter values for each configuration element:</p>
<ul>
<li>Data ID: ID of the configuration entry, typically in the same format as a fully qualified class name</li>
<li>Group: group the configuration entry belongs to, typically representing the product the configuration is for</li>
<li>Format: format that the configuration entry is in, typically one of <em>Text</em>, <em>JSON</em>, <em>XML</em>, <em>YAML</em>, <em>HTML</em>, <em>Properties</em>, or <em>TOML</em></li>
<li>Content: actual configuration entry, in the specified format</li>
</ul>
<p>In addition, we can specify description, application, and tags. However, these are only metadata about the entry and don&#8217;t influence its use at all.</p>
<p>Conveniently, we can see historical versions of configurations:</p>
<a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/wp-content/uploads/2026/06/nacos-config-history-scaled.png"><img loading="lazy" decoding="async" class="alignnone wp-image-203896 size-large" src="https://www.baeldung.com/wp-content/uploads/2026/06/nacos-config-history-1024x334.png" alt="Nacos configuration history" width="580" height="189" srcset="https://www.baeldung.com/wp-content/uploads/2026/06/nacos-config-history-1024x334.png 1024w, https://www.baeldung.com/wp-content/uploads/2026/06/nacos-config-history-300x98.png 300w, https://www.baeldung.com/wp-content/uploads/2026/06/nacos-config-history-768x250.png 768w, https://www.baeldung.com/wp-content/uploads/2026/06/nacos-config-history-1536x501.png 1536w, https://www.baeldung.com/wp-content/uploads/2026/06/nacos-config-history-2048x668.png 2048w, https://www.baeldung.com/wp-content/uploads/2026/06/nacos-config-history-100x33.png 100w, https://www.baeldung.com/wp-content/uploads/2026/06/nacos-config-history-600x196.png 600w" sizes="auto, (max-width: 580px) 100vw, 580px" /></a>
<p>&nbsp;</p>
<p>Thus, we have the ability to edit or delete configuration settings, as well as see the details of the entry and the history of all changes to it.</p>
<h3 id="bd-2-fetching-configuration" data-id="2-fetching-configuration"><strong>5.2. Fetching Configuration</strong></h3>
<div class="bd-anchor" id="2-fetching-configuration"></div>
<p>Once we&#8217;ve got the configuration set up, we need to be able to use it. To that end, <strong>we set configurations via the <em>ConfigService</em> that we created earlier</strong>.</p>
<p>Specifically, perhaps the easiest way is to simply call <em>getConfig()</em> passing in the data ID and group, as well as a timeout in milliseconds:</p>
<pre><code class="language-java">String config = configService.getConfig("com.baeldung.nacos.Example", "DEFAULT_GROUP", 1000);</code></pre>
<p>This way, we immediately get the value of this configuration setting. In the event that the configuration doesn&#8217;t exist, or Nacos can&#8217;t be reached, we get <em>null</em> back instead.</p>
<p>However, this only gives us the raw value. Normally, we need to already know what type it is to know what to do with it. To avoid this, <strong>we can instead use <em>getConfigWithResult()</em> to get back a richer object that includes the type of config setting (text, JSON, or similar), so that we know how to handle that</strong>:</p>
<pre><code class="language-java">ConfigQueryResult config = configService.getConfigWithResult("com.baeldung.nacos.Example", "DEFAULT_GROUP", 1000);
String configType = config.getConfigType();
String configValue = config.getContent();</code></pre>
<p>This way, we always get a result, even if the config setting doesn&#8217;t exist or the Nacos server is unreachable. In those cases, we get a valid <em>ConfigQueryResult</em> object that contains <em>null</em> for the content and config type.</p>
<h3 id="bd-3-listening-to-configuration-changes" data-id="3-listening-to-configuration-changes"><strong>5.3. Listening to Configuration Changes</strong></h3>
<div class="bd-anchor" id="3-listening-to-configuration-changes"></div>
<p>As well as simply fetching the configuration value at a single point in time, <strong>we can register a callback for whenever the value changes</strong>. This way, we can be responsive to configuration changes without needing to manually poll the values ourselves.</p>
<p>In particular, <strong>let&#8217;s register a listener for a specific configuration setting using <em>addListener()</em></strong>:</p>
<pre><code class="language-java">configService.addListener("com.baeldung.nacos.Example", "DEFAULT_GROUP", new AbstractListener() {
    @Override
    public void receiveConfigInfo(String configInfo) {
        LOG.info("Received config info: {}", configInfo);
    }
});
</code></pre>
<p>This block runs every time the config setting changes, with the new value. Unfortunately, this only happens when the value changes, not immediately.</p>
<p><strong>As an alternative, we can use <em>getConfigAndSignListener()</em> to retrieve the current configuration and immediately register a listener</strong>:</p>
<pre><code class="language-java">String config = configService.getConfigAndSignListener("com.baeldung.nacos.Example", "DEFAULT_GROUP", 1000, new AbstractListener() {
    @Override
    public void receiveConfigInfo(String configInfo) {
        LOG.info("Received config info: {}", configInfo);
    }
});
</code></pre>
<p>Thus, we get the current config for use right now, and to also respond whenever the configuration changes as needed. In a way, this is a shorthand for both <em>getConfig()</em> and <em>addListener()</em> in a single call.</p>
<h2 id="bd-service-discovery" data-id="service-discovery"><strong>6. Service Discovery</strong></h2>
<div class="bd-anchor" id="service-discovery"></div>
<p>On top of configuration, <strong>Nacos also provides service discovery of distributed services</strong>. This works by services registering their address and port against a well-known name, and then enabling clients to look up these by name later on.</p>
<h3 id="bd-1-registering-a-service" data-id="1-registering-a-service"><strong>6.1. Registering a Service</strong></h3>
<div class="bd-anchor" id="1-registering-a-service"></div>
<p>To begin with, we register services using the <em>NamingService</em> that we constructed earlier.  <strong>We do this using the <em>registerInstance()</em> method, passing in the service name and address</strong>:</p>
<pre><code class="language-java">namingService.registerInstance("BaeldungTest", "localhost", 8848);</code></pre>
<p>Alternatively, we can pass in an <em>Instance</em> class containing some more details about the service:</p>
<pre><code class="language-java">Instance instance = new Instance();
instance.setIp("localhost");
instance.setPort(8848);
instance.setEnabled(true);
instance.setHealthy(true);
Map&lt;String, String&gt; metadata = new HashMap&lt;&gt;();
metadata.put("Example", "value");
instance.setMetadata(metadata);
namingService.registerInstance("BaeldungTest", instance);
</code></pre>
<p>Notably, if we do this, <strong>we need to ensure that the <em>enabled</em> and <em>healthy</em> flags are set</strong>, since they default to <em>false</em>, and therefore the service won&#8217;t be available for discovery.</p>
<p>If we need to update the service registration &#8211; for example, to change whether the service is healthy &#8211; then we can simply re-register the same service name and address with the new details. Nacos detects that these are the same details and updates them instead of creating a new registration.</p>
<h3 id="bd-2-unregistering-a-service" data-id="2-unregistering-a-service"><strong>6.2. Unregistering a Service</strong></h3>
<div class="bd-anchor" id="2-unregistering-a-service"></div>
<p>Importantly, <strong>the <em>NamingService</em> client automatically unregisters any services that it registered when it closes down</strong>. This means that we don&#8217;t need to do anything special, and as soon as the service shuts down, Nacos should remove it automatically. Still, this depends on other factors such as heartbeat and ephemeral instances.</p>
<p>However, sometimes we need to do this ourselves without shutting down the entire service. To do so, we turn to <em>deregisterInstance()</em>:</p>
<pre><code class="language-java">namingService.deregisterInstance("BaeldungTest", "localhost", 8848);</code></pre>
<p>This takes the service name and either the address and port, or the <em>Instance</em> class, as with <em>registerInstance()</em>. With that information, <em>deregisterInstance</em><em>()</em> immediately removes this address from the Nacos server to prevent discovery.</p>
<h3 id="bd-3-discovering-service-instances" data-id="3-discovering-service-instances"><strong>6.3. Discovering Service Instances</strong></h3>
<div class="bd-anchor" id="3-discovering-service-instances"></div>
<p>Once services are registered, we need to be able to discover them to actually leverage what they offer. Nacos offers several ways that we can achieve this.</p>
<p><strong>Perhaps the most useful way to service discovery is <em>selectOneHealthyInstance()</em></strong>. The method takes the service name and randomly returns one <em>healthy</em> registered instance:</p>
<pre><code class="language-java">Instance baeldungTest = namingService.selectOneHealthyInstance("BaeldungTest");
String address = baeldungTest.getIp();
int port = baeldungTest.getPort();
</code></pre>
<p>If nothing is registered for the given service name, the code throws an <em>IllegalStateException</em> to indicate that fact, since this means there are no services that we can use right now.</p>
<p>Alternatively, <strong><em>selectInstances()</em> and <em>getAllInstances()</em> are able to return every registered instance instead of just one of them</strong>:</p>
<pre><code class="language-java">List&lt;Instance&gt; allInstances = namingService.getAllInstances("BaeldungTest");
List&lt;Instance&gt; allHealthyInstances = namingService.selectInstances("BaeldungTest", true);</code></pre>
<p>Notably, <strong><em>selectInstances()</em> and <em>selectOneHealthyInstance()</em> only return services that are marked as <em>enabled</em>, and where the <em>healthy</em> flag is as expected</strong>. <em>getAllInstances()</em> returns every instance regardless of these flags.</p>
<h3 id="bd-4-listening-to-service-discovery-changes" data-id="4-listening-to-service-discovery-changes"><strong>6.4. Listening to Service Discovery Changes</strong></h3>
<div class="bd-anchor" id="4-listening-to-service-discovery-changes"></div>
<p>In addition to selecting an instance when we need to, we can register a callback to be triggered whenever there&#8217;s a change to the instances for a given service name:</p>
<pre><code class="language-java">namingService.subscribe("BaeldungTest", (event) -&gt; {
    NamingChangeEvent namingChangeEvent = (NamingChangeEvent) event;
    LOG.info("Added Instances: {}", namingChangeEvent.getAddedInstances());
    LOG.info("Removed Instances: {}", namingChangeEvent.getRemovedInstances());
    LOG.info("Modified Instances: {}", namingChangeEvent.getModifiedInstances());
});
</code></pre>
<p>We can then do whatever we want with this information &#8211; for example, we might want to maintain a local cache of instances so that we don&#8217;t need to make a call to Nacos every time we want to look one up.</p>
<h2 id="bd-distributed-locks" data-id="distributed-locks"><strong>7. Distributed Locks</strong></h2>
<div class="bd-anchor" id="distributed-locks"></div>
<p>Another important feature that Nacos has is support for distributed locking of resources. Specifically, <strong>Nacos enables access control to resources so that they can&#8217;t be accessed by different services or threads at the same time</strong>.</p>
<p>To obtain locks, we use the <em>LockService</em> that we created earlier and <strong>call the <em>lock()</em> method, providing a definition of the lock that we want</strong>:</p>
<pre><code class="language-java">Boolean result = lockService.lock(new NLock("Baeldung", 5000L));</code></pre>
<p>When obtaining a lock, we need to provide the identifier of the resource and a maximum time period for the lock, measured in milliseconds. Critically, we receive a result indicating whether we got the lock or not.</p>
<p>Locks release either after the provided time period or by explicitly unlocking them with the <em>unlock()</em> method:</p>
<pre><code class="language-java">lockService.unlock(new NLock("Baeldung", 5000L));</code></pre>
<p>Unlike with service registrations, <strong>locks aren&#8217;t automatically removed when the client disconnects</strong>, so we need to ensure that we manage this sensibly.</p>
<h2 id="bd-summary" data-id="summary"><strong>8. Summary</strong></h2>
<div class="bd-anchor" id="summary"></div>
<p>In this article, we took a quick look at Alibaba Nacos, we saw what it is and how we can use it in applications.</p>
<p>In summary, Nacos is a platform for managing distributed systems by providing service discovery, centralized configuration, and coordination capabilities.</p>
<p>As usual, all of the examples from this article are available <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eugenp/tutorials/tree/master/microservices-modules/nacos">over on GitHub</a>.</p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/alibaba-nacos-basics">Introduction to Alibaba Nacos</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.<Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/957626876/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/957626876/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/957626876/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f07%2fJava-Featured-12-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/24/957626876/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/957626876/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/957626876/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/alibaba-nacos-basics#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/alibaba-nacos-basics/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</content:encoded>
					
					<wfw:commentRss>https://feeds.feedblitz.com/~/957626876/0/baeldung/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-12-150x150.jpg</webfeeds:featuredImage></item>
<item>
<feedburner:origLink>https://www.baeldung.com/java-sock-merchant-problem</feedburner:origLink>
		<title>How to Solve the Sock Merchant Problem in Java</title>
		<link>https://feeds.feedblitz.com/~/957603419/0/baeldung</link>
					<comments>https://feeds.feedblitz.com/~/957603419/0/baeldung#respond</comments>
		
		<dc:creator><![CDATA[Nikhil Bhargav]]></dc:creator>
		<pubDate>Mon, 01 Jun 2026 07:59:55 +0000</pubDate>
				<category><![CDATA[Algorithms]]></category>
		<category><![CDATA[Java Set]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/java-sock-merchant-problem</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/11/Algorithms-Featured-Image-01-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="max-width:100% !important;height:auto !important;float: left; margin-right: 5px;" loading="lazy" /><p>Explore two approaches to solving the Sock Merchant Problem in Java.</p>
The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/957603419/0/baeldung">How to Solve the Sock Merchant Problem in Java</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/957603419/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/957603419/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f11%2fAlgorithms-Featured-Image-01-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/24/957603419/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/957603419/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/957603419/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/java-sock-merchant-problem#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/java-sock-merchant-problem/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</description>
										<content:encoded><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/11/Algorithms-Featured-Image-01-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="float: left; margin-right: 5px;" decoding="async" loading="lazy" srcset="https://www.baeldung.com/wp-content/uploads/2024/11/Algorithms-Featured-Image-01-1024x536.jpg 1024w, https://www.baeldung.com/wp-content/uploads/2024/11/Algorithms-Featured-Image-01-300x157.jpg 300w, https://www.baeldung.com/wp-content/uploads/2024/11/Algorithms-Featured-Image-01-768x402.jpg 768w, https://www.baeldung.com/wp-content/uploads/2024/11/Algorithms-Featured-Image-01-100x52.jpg 100w, https://www.baeldung.com/wp-content/uploads/2024/11/Algorithms-Featured-Image-01-600x314.jpg 600w, https://www.baeldung.com/wp-content/uploads/2024/11/Algorithms-Featured-Image-01.jpg 1200w" sizes="auto, (max-width: 580px) 100vw, 580px" /><h2 id="bd-introduction" data-id="introduction">1. Introduction</h2>
<div class="bd-anchor" id="introduction"></div>
<p>In this tutorial, we&#8217;ll study two Java solutions for the Sock Merchant problem.</p>
<h2 id="bd-the-sock-merchant-problem" data-id="the-sock-merchant-problem">2. The Sock Merchant Problem</h2>
<div class="bd-anchor" id="the-sock-merchant-problem"></div>
<p>In the Sock Merchant problem, we have a merchant with a large pile of socks for sale that must be paired by color. We represent the socks&#8217; colors by an array of integers, <em>socks</em>. <strong>The merchant&#8217;s goal is to determine how many pairs of matching socks there are.</strong></p>
<p>Therefore, we have three inputs:</p>
<ul>
<li><em>n:</em> the number of socks in the collection</li>
<li><em>socks</em>: the array holding the colors of <em>n</em> socks</li>
<li><em>k</em>: the maximal color ID in this array, meaning 1 &lt;= s<em>ocks</em>[<em>i</em>] &lt;= <em>k</em> (for 0 &lt;=<em> i</em> &lt; n).</li>
</ul>
<p>For example, let&#8217;s say we have <em>n </em>= 7 socks with soc<em>ks</em> = [11, 22, 22, 11, 111, 33, 222] and <em>k</em>=222:</p>
<img decoding="async" class="aligncenter size-full wp-image-243945" src="https://www.baeldung.com/wp-content/uploads/2026/06/sockMerchant-1.png" alt="Visualatizaton of Socks color" />
<p>Here, we have only 5 colors. with one pair of color 11 and one of color 222. There are three odd socks left, one of each color. Hence, we return 2 as the number of pairs.</p>
<h2 id="bd-array-based-solution" data-id="array-based-solution">3. Array-Based Solution</h2>
<div class="bd-anchor" id="array-based-solution"></div>
<p data-path-to-node="7"><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-common-array-operations">An array-based solution</a> works best when we know <em>k</em> is small. Here, we use an array <em>counts</em> to count the occurrences of each sock. Then, we iterate through it, dividing each count by two to calculate the total number of pairs:</p>
<pre><code class="language-java">public int countPairsWithArray(int n, int[] socks, int k) {
    int[] counts = new int[k];
    int pairs = 0;
    for (int i = 0; i &lt; n; i++) {
        counts[socks[i]]++;
    }
    for (int count : counts) {
        pairs += count / 2;
    }
    return pairs;
}</code></pre>
<p>Technically, we could omit <em>n</em> from the argument list and use <em>socks.length</em> in the code. We kept <em>n</em> in the method signature to follow the problem formulation more closely and make the solution easier to read.</p>
<p>This solution has time and space complexities of <em>O(n + k)</em>. If <em>k</em> is <em>O(n)</em>, the complexity simplifies to <em>O(n)</em>.</p>
<h2 id="bd-hashset-based-solution" data-id="hashset-based-solution">4. HashSet-Based Solution</h2>
<div class="bd-anchor" id="hashset-based-solution"></div>
<p><strong>An array-based solution degrades performance when the range of color IDs is unbounded, unknown, or very large. </strong>For instance<strong>,</strong> the <em>counts</em> array for a very large <em>k</em> could not only be sparse (mostly 0s) if the number of unique colors is much smaller, but also too large to fit in the memory.</p>
<p>For such cases, we use <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-hashset">HashSet</a>. Here, our HashSet <em>unmatchedSocks </em>stores unmatched socks. As we iterate through the input array,we check if the current sock&#8217;s color is already in <em>unmatchedSocks</em>:</p>
<ul>
<li>if it isn&#8217;t, we add it</li>
<li>otherwise, we have successfully found a pair!</li>
</ul>
<p>Whenever we find a pair, we increment our pair counter and remove that color from  <em>unmatchedSocks, s</em>o that the next pair of that color can be identified correctly:</p>
<pre><code class="language-java">public int countPairsWithSet(int[] socks) {
    Set&lt;Integer&gt; unmatchedSocks = new HashSet&lt;&gt;();
    int pairs = 0;
    for (int sock : socks) {
        if (unmatchedSocks.contains(sock)) {
            pairs++;
            unmatchedSocks.remove(sock);
        } else {
            unmatchedSocks.add(sock);
        }
    }
    return pairs;
}</code></pre>
<p>The HashSet-based solution&#8217;s average time complexity is <em>O(n)</em> since we iterate through the set exactly once, and HashSet operations <em>add()</em> and <em>size()</em> are <em>O(1)</em> on average.</p>
<p>However, <strong><em>HashSet</em> suffers from <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-frog-river-one">collisions</a>, so it has a log-linear <em>O(n log n)</em> worst-case time complexity for case where k is <em>O(n)</em>. </strong>If <em>k</em> is<em> O(1)</em>, the time complexity evaluates to <em>O(n), </em>which is same as that of  the array-based algorithm.</p>
<p>The space complexity is <em>O(n + k)</em>.</p>
<h2 id="bd-testing" data-id="testing">5. Testing</h2>
<div class="bd-anchor" id="testing"></div>
<h3 id="bd-1-array-based-solution-testing" data-id="1-array-based-solution-testing">5.1. Array-based Solution Testing</h3>
<div class="bd-anchor" id="1-array-based-solution-testing"></div>
<p>First, we test the array-based solution.</p>
<p>We create our socks array and pass it along with its length and the maximum color ID:</p>
<pre><code class="language-java">public void givenSockArray_whenUsingArray_thenReturnsCorrectPairCount() {
    SockMerchant merchant = new SockMerchant();
    int[] socks = {11, 22, 22, 11, 33, 3, 33, 111111, 33, 222222};
    int colorMax = 222223;
    int foundPairs = merchant.countPairsWithArray(socks.length, socks, colorMax);
    assertEquals(3, foundPairs);
}</code></pre>
<p>We expect the test to return 3 for success.</p>
<h3 id="bd-2-hashset-based-solution-testing" data-id="2-hashset-based-solution-testing">5.2. HashSet-based Solution Testing</h3>
<div class="bd-anchor" id="2-hashset-based-solution-testing"></div>
<p>Let&#8217;s test the HashSet-based solution:</p>
<pre><code class="language-java">public void givenSockArray_whenUsingSet_thenReturnsCorrectPairCount() {
    SockMerchant merchant = new SockMerchant();
    int[] socks = {11, 22, 22, 11, 33, 3, 33, 111111, 33, 222222};
    int foundPairs = merchant.countPairsWithSet(socks);
    assertEquals(3, actualPairs);
}</code></pre>
<p>As before, we expect the test to return 3 for success.</p>
<h2 id="bd-conclusion" data-id="conclusion">6. Conclusion</h2>
<div class="bd-anchor" id="conclusion"></div>
<p>In this article, we studied two solutions, based on two different <em>Java</em> data structures, for the Sock Merchant problem.</p>
<p>The array-based method is fast and memory-efficient when the color domain is small and the color IDs are contiguous. However, it wastes memory and computation time if the color IDs are sparse or <em>k</em> is exceedingly large.</p>
<p>On the other hand, the <em>HashSet</em> approach is more versatile since it can handle arbitrarily large integers and sparse datasets without allocating massive contiguous blocks of memory. Furthermore, we can adapt the <em>HashSet</em> logic to group custom objects or strings.</p>
<p>As always, the complete code examples are available <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eugenp/tutorials/tree/master/algorithms-modules/algorithms-numeric">over on GitHub</a>.</p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-sock-merchant-problem">How to Solve the Sock Merchant Problem in Java</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.<Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/957603419/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/957603419/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/957603419/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f11%2fAlgorithms-Featured-Image-01-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/24/957603419/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/957603419/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/957603419/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/java-sock-merchant-problem#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/java-sock-merchant-problem/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</content:encoded>
					
					<wfw:commentRss>https://feeds.feedblitz.com/~/957603419/0/baeldung/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2024/11/Algorithms-Featured-Image-01-150x150.jpg</webfeeds:featuredImage></item>
<item>
<feedburner:origLink>https://www.baeldung.com/java-selenium-wait-complex-javascript</feedburner:origLink>
		<title>Waiting for Complex Page With JavaScript to Load in Selenium WebDriver</title>
		<link>https://feeds.feedblitz.com/~/957590264/0/baeldung</link>
					<comments>https://feeds.feedblitz.com/~/957590264/0/baeldung#respond</comments>
		
		<dc:creator><![CDATA[Ulisses Lima]]></dc:creator>
		<pubDate>Sun, 31 May 2026 17:27:48 +0000</pubDate>
				<category><![CDATA[Testing]]></category>
		<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[Selenium]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/java-selenium-wait-complex-javascript</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/11/featured-images-selenium-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="max-width:100% !important;height:auto !important;float: left; margin-right: 5px;" loading="lazy" /><p>Explore various strategies for waiting until a JavaScript-heavy page is fully loaded in Selenium WebDriver.</p>
The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/957590264/0/baeldung">Waiting for Complex Page With JavaScript to Load in Selenium WebDriver</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/957590264/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/957590264/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f11%2ffeatured-images-selenium-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/24/957590264/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/957590264/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/957590264/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/java-selenium-wait-complex-javascript#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/java-selenium-wait-complex-javascript/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</description>
										<content:encoded><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/11/featured-images-selenium-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="float: left; margin-right: 5px;" decoding="async" loading="lazy" srcset="https://www.baeldung.com/wp-content/uploads/2024/11/featured-images-selenium-1024x536.jpg 1024w, https://www.baeldung.com/wp-content/uploads/2024/11/featured-images-selenium-300x157.jpg 300w, https://www.baeldung.com/wp-content/uploads/2024/11/featured-images-selenium-768x402.jpg 768w, https://www.baeldung.com/wp-content/uploads/2024/11/featured-images-selenium-100x52.jpg 100w, https://www.baeldung.com/wp-content/uploads/2024/11/featured-images-selenium-600x314.jpg 600w, https://www.baeldung.com/wp-content/uploads/2024/11/featured-images-selenium.jpg 1200w" sizes="auto, (max-width: 580px) 100vw, 580px" /><h2 id="bd-introduction" data-id="introduction">1. Introduction</h2>
<div class="bd-anchor" id="introduction"></div>
<p>When testing applications with Selenium, waiting for a page to load completely is more complex when it comes to modern pages, as they rely heavily on JavaScript for rendering. These kinds of pages make asynchronous calls and manipulate the DOM after the initial HTML has loaded. Therefore, checking if it&#8217;s loaded isn&#8217;t sufficient to ensure the app is ready for interaction.</p>
<p>Introducing an arbitrary delay is a common but problematic choice. If the page loads faster than the delay, the test wastes time. Otherwise, if the page loads slower due to network conditions or server response time, the test may fail.</p>
<p>In this tutorial, we&#8217;ll explore different strategies for waiting until a JavaScript-heavy page is fully loaded in Selenium <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/selenium-webdriver-page-object"><em>WebDriver</em></a> without introducing arbitrary delays.</p>
<h2 id="bd-problem-and-setup" data-id="problem-and-setup">2. Problem and Setup</h2>
<div class="bd-anchor" id="problem-and-setup"></div>
<p>We&#8217;ll define a page as loaded when all asynchronous operations for the current view have completed. This includes HTTP requests, dynamic DOM updates, and framework-specific processing. Navigating to a new view is treated as a new page load that requires its own wait strategy.</p>
<p>Selenium provides two main wait strategies. With <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/selenium-implicit-explicit-wait#1-implicit-wait">implicit waits</a>, we set a global timeout for element lookups. On the other hand, with <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/selenium-implicit-explicit-wait#2-explicit-wait">explicit waits</a>, we wait for specific conditions using a <em>WebDriverWait</em> instance. We&#8217;ll only use explicit waits, which provide precise control over what conditions to check.</p>
<h3 id="bd-1-creating-a-helper-class" data-id="1-creating-a-helper-class">2.1. Creating a Helper Class</h3>
<div class="bd-anchor" id="1-creating-a-helper-class"></div>
<p><strong>Let&#8217;s create a helper class that encapsulates our waiting logic. It wraps a <em>WebDriverWait</em> instance and requires a <em>timeout</em> value so our tests don&#8217;t wait indefinitely in unexpected scenarios:</strong></p>
<pre><code class="language-java">public class PageLoadHelper {
    private final WebDriverWait wait;
    public PageLoadHelper(WebDriver driver, Duration timeout) {
        this.wait = new WebDriverWait(driver, timeout);
    }
    // ...
}</code></pre>
<p>We&#8217;ll add all the different wait strategies to this class, making them easy to reuse. <strong>We&#8217;ll only need the <em>until()</em> method from <em>WebDriverWait</em>, which polls the condition until it returns true or times out.</strong> This condition is written as a <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-8-lambda-expressions-tips">lambda</a> that takes the current driver:</p>
<pre><code class="language-java">public void waitUntil() { 
    wait.until(driver -&gt; { 
        JavascriptExecutor js = (JavascriptExecutor) driver; 
        return // condition...
    }); 
}</code></pre>
<p><strong>While we won&#8217;t need a specific driver implementation, the only requirement is that it implements the </strong><span style="margin: 0px;padding: 0px"><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.javadoc.io/doc/org.seleniumhq.selenium/selenium-api/4.38.0/org/openqa/selenium/JavascriptExecutor.html" target="_blank" rel="noopener"><strong>JavaScriptExecutor</strong></a><strong> interface</strong></span><strong>.</strong> We need this because the states we want to query are only accessible through the browser&#8217;s JavaScript runtime, not via the standard <em>WebDriver</em> API.</p>
<p>Also, since some of the checks rely on specific frameworks, most methods in this article start with checks such as <em>typeof someFrameworkObject === &#8216;undefined&#8217;</em>. If the framework isn&#8217;t there, the condition returns <em>true</em> immediately, making the method safe to call on any page and useful when chaining.</p>
<h2 id="bd-waiting-for-document-ready-state" data-id="waiting-for-document-ready-state">3. Waiting for Document Ready State</h2>
<div class="bd-anchor" id="waiting-for-document-ready-state"></div>
<p>The most fundamental check is inspecting the browser&#8217;s <em>document.readyState</em> property. <strong>This property becomes <em>complete</em> when the page and all its resources have finished loading.</strong> It&#8217;s the default behavior of <em>driver.get()</em>. It&#8217;s useful for SPA navigation, such as right after clicking a link that changes the view without a full page reload.</p>
<p>Let&#8217;s add a method to our <em>PageLoadHelper</em> that executes a script and waits until it returns the result we&#8217;re looking for:</p>
<pre><code class="language-java">public void waitForDocumentReady() {
    wait.until(driver -&gt; {
        JavascriptExecutor js = (JavascriptExecutor) driver;
        return js.executeScript("return document.readyState")
          .equals("complete");
    });
}</code></pre>
<p><strong>While this covers the initial HTML page load, it doesn&#8217;t apply to asynchronous JavaScript that runs after the DOM is ready.</strong></p>
<h2 id="bd-waiting-for-jquery-ajax-calls-to-complete" data-id="waiting-for-jquery-ajax-calls-to-complete">4. Waiting for jQuery AJAX Calls to Complete</h2>
<div class="bd-anchor" id="waiting-for-jquery-ajax-calls-to-complete"></div>
<p>Many apps still use jQuery for AJAX requests. It maintains an internal counter, <em>jQuery.active</em>, that tracks the number of pending requests. When this reaches zero, all requests have completed.</p>
<p>Let&#8217;s add a method that checks whether there are any pending requests:</p>
<pre><code class="language-java">public void waitForJQueryToFinish() {
    wait.until(driver -&gt; {
        JavascriptExecutor js = (JavascriptExecutor) driver;
        return (Boolean) js.executeScript(
          "return (typeof jQuery === 'undefined') || (jQuery.active === 0)");
    });
}</code></pre>
<p>It first checks whether jQuery is present on the page. If not, the condition returns true immediately.<strong> This type of guard is useful when building a universal test suite, and not all pages use jQuery. </strong></p>
<p>However, if an AJAX request hasn&#8217;t started yet when we call it, the condition sees <em>jQuery.active === 0</em> and returns before any request fires. So, let&#8217;s create another method that waits for a request to start:</p>
<pre><code class="language-java">public void waitForJQueryToStart() {
    wait.until(driver -&gt; {
        JavascriptExecutor js = (JavascriptExecutor) driver;
        return (Boolean) js.executeScript(
          "return (typeof jQuery === 'undefined') || (jQuery.active &gt; 0)");
    });
}</code></pre>
<p><strong>We can then chain these two to ensure that requests start before waiting for them to finish. </strong>But even with this approach, if all requests fire and complete before we have a chance to observe <em>jQuery.active </em>increasing, we&#8217;ll end up waiting until Selenium times out.</p>
<h2 id="bd-waiting-for-modern-angular-apps-to-stabilize" data-id="waiting-for-modern-angular-apps-to-stabilize">5. Waiting for Modern Angular Apps to Stabilize</h2>
<div class="bd-anchor" id="waiting-for-modern-angular-apps-to-stabilize"></div>
<p>Angular 2+ uses RxJS for HTTP calls, and it provides a <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://angular.dev/api/core/Testability">testability API</a> that allows us to check if the app is stable, meaning that it has completed all pending requests:</p>
<pre><code class="language-java">public Boolean waitForModernAngularAppStable() {
    return wait.until(driver -&gt; {
        JavascriptExecutor js = (JavascriptExecutor) driver;
        String script = """
          return (
            typeof getAllAngularTestabilities === 'undefined' 
            || getAllAngularTestabilities().every(t =&gt; t.isStable())
          )
          """;
        return (Boolean) js.executeScript(script);
    });
}</code></pre>
<p><strong>We check <span style="margin: 0px;padding: 0px">whether <em>getAllAngularTestabilities</em> is <em>undefined</em></span> to guard against future API changes.</strong></p>
<h2 id="bd-waiting-for-react-apps-to-be-ready" data-id="waiting-for-react-apps-to-be-ready">6. Waiting for React Apps to Be Ready</h2>
<div class="bd-anchor" id="waiting-for-react-apps-to-be-ready"></div>
<p>React apps don&#8217;t have a built-in JavaScript API we can use to test app readiness universally, so we have to handle it on a case-by-case basis. <strong>A common approach is to make an element visible only after we&#8217;ve finished making all the requests we need.</strong></p>
<p>Something as simple as a &#8220;done&#8221; flag that adds a CSS class to a button is enough:</p>
<pre><code class="language-xml">&lt;button id="doneButton" className={done ? 'visible' : ''}&gt;
    All Done
&lt;/button&gt;</code></pre>
<p><strong>Then, we can add an all-purpose method to wait for elements to be clicked:</strong></p>
<pre><code class="language-java">public void waitForElementToBeClickable(By locator) {
    wait.until(ExpectedConditions.elementToBeClickable(locator));
}</code></pre>
<p>Finally, we can test it by passing the ID of the element:</p>
<pre><code class="language-java">@Test
void whenWaitForElementClickable_thenClickable() {
    driver.get(PAGE_URL);
    pageLoadHelper.waitForElementToBeClickable(By.id("doneButton")); 
    
    assertTrue(driver.findElement(By.id("doneButton")).isDisplayed()); 
}</code></pre>
<p><strong>This approach isn&#8217;t limited to React and can be used with any page.</strong> Most importantly, the <em>done</em> flag must be set when the application is truly ready for interaction. If completing the initial set of requests triggers additional requests, the flag must account for the entire chain before being set to <em>true</em>.</p>
<h2 id="bd-injecting-a-custom-flag-in-the-window-object" data-id="injecting-a-custom-flag-in-the-window-object">7. Injecting a Custom Flag in the Window Object</h2>
<div class="bd-anchor" id="injecting-a-custom-flag-in-the-window-object"></div>
<p>In modern JavaScript apps, we usually use Promises to make requests, and we can use the &#8220;<em>Promise.all()</em>&#8221; function to wait for a set of promises to complete. Most importantly, if we don&#8217;t have access to a universal tool, we can always modify the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.w3schools.com/js/js_window.asp">window</a> variable to include our own flags.</p>
<p><strong>So, if we know the slowest part of the app is completing a set of promises and we can&#8217;t reliably use the app before that, one alternative is creating a simple listener that adds a flag indicating when our app is ready:</strong></p>
<pre><code class="language-javascript">window.appReady = false;
// start promises ...
Promise.all(promises).then(() =&gt; {
    window.appReady = true;
});</code></pre>
<p><strong>It&#8217;s important to initialize the variable to <em>false</em> so we don&#8217;t get <em>undefined</em> when reading its value.</strong> We can then wait until this flag appears when testing the app with Selenium:</p>
<pre><code class="language-java">public void waitForJsAppReady() {
    wait.until(driver -&gt; {
        JavascriptExecutor js = (JavascriptExecutor) driver;
        return (Boolean) js.executeScript(
          """
          return (
            typeof window.appReady === 'undefined' 
            || window.appReady === true
          )
          """);
    });
}</code></pre>
<p>This will work for any app running in a modern browser.</p>
<h2 id="bd-patching-the-fetch-api" data-id="patching-the-fetch-api">8. Patching the Fetch API</h2>
<div class="bd-anchor" id="patching-the-fetch-api"></div>
<p>Another common approach <span style="margin: 0px;padding: 0px">in modern JavaScript apps is to use <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://developer.mozilla.org/en-US/docs/Web/API/Window/fetch" target="_blank" rel="noopener"><em>fetch</em></a>, which also doesn&#8217;t provide a standard way to count</span> pending requests. So, one option is to implement it on our own. We can do this by replacing the fetch implementation with a custom one that increments a global <em>fetchPending</em> counter and delegates the actual execution to the original implementation:</p>
<pre><code class="language-javascript">window.fetchPending = 0;
const originalFetch = window.fetch;
window.fetch = function(...args) {
    window.fetchPending++;
    return originalFetch.apply(this, args)
      .finally(() =&gt; window.fetchPending--);
};</code></pre>
<p><strong>This must be called before the requests we want to track are fired. Then, every request made with the <em>fetch</em> command becomes trackable (even on error, since we decrement it in a <em>finally</em> block).</strong> Finally, we can wait until <em>fetchPending</em> reaches zero, like in our <em>appReady</em> solution.</p>
<p>For requests that fire automatically on page load, the interception must happen before navigation. Selenium now supports this via the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.selenium.dev/documentation/webdriver/bidi/cdp/">Chrome DevTools Protocol</a>, but that is beyond the scope of this article.</p>
<h2 id="bd-summary" data-id="summary">9. Summary</h2>
<div class="bd-anchor" id="summary"></div>
<p>The strategies we covered address different scenarios. Let&#8217;s see how they compare:</p>
<table class="table-styled">
<thead>
<tr>
<th>Approach</th>
<th>Best for</th>
<th>Limitation</th>
</tr>
</thead>
<tbody>
<tr>
<td>document.readyState</td>
<td>All pages; SPA navigation</td>
<td>Already the default for driver.get(); doesn&#8217;t cover post-load async ops</td>
</tr>
<tr>
<td>jQuery.active counter</td>
<td>Pages using jQuery</td>
<td>Race condition if requests haven&#8217;t started; chain with waitForJQueryToStart()</td>
</tr>
<tr>
<td>Angular testability API</td>
<td>Angular 2+ apps</td>
<td>May not work with all Angular configurations</td>
</tr>
<tr>
<td>ExpectedConditions.elementToBeClickable</td>
<td>Any page with a sentinel element</td>
<td>Only checks the DOM state, not pending requests</td>
</tr>
<tr>
<td>Window flag (window.appReady)</td>
<td>Any app with code-level control</td>
<td>Requires modifying the application under test</td>
</tr>
<tr>
<td>Fetch API interception</td>
<td>Apps using the Fetch API</td>
<td>Must intercept before requests fire; auto-load requests require CDP</td>
</tr>
</tbody>
</table>
<p><strong>In practice, combining strategies works best: start with <em>waitForDocumentReady()</em>, apply a framework-specific check, then confirm a meaningful element is interactable.</strong></p>
<h2 id="bd-10-conclusion" data-id="10-conclusion">10. Conclusion</h2>
<div class="bd-anchor" id="10-conclusion"></div>
<p>In this article, we explored several strategies for waiting until JavaScript-heavy pages fully load using Selenium. We started with the basic <em>document.readyState</em> check and covered Selenium&#8217;s built-in <em>WebDriverWait</em> for waiting on element states. We also extended our approach to handle jQuery AJAX calls, modern Angular applications, alternatives to React components, and pure JavaScript apps.</p>
<p>No single strategy covers all cases, and the right choice depends on the technologies the app uses and the level of control we have over its source code. Using a helper class like <em>PageLoadHelper</em> simplifies composing and reusing strategies across a test suite.</p>
<p>As always, the source code is available <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eugenp/tutorials/tree/master/testing-modules/selenium-3">over on GitHub</a>.</p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-selenium-wait-complex-javascript">Waiting for Complex Page With JavaScript to Load in Selenium WebDriver</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.<Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/957590264/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/957590264/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/957590264/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f11%2ffeatured-images-selenium-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/24/957590264/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/957590264/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/957590264/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/java-selenium-wait-complex-javascript#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/java-selenium-wait-complex-javascript/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</content:encoded>
					
					<wfw:commentRss>https://feeds.feedblitz.com/~/957590264/0/baeldung/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2024/11/featured-images-selenium-150x150.jpg</webfeeds:featuredImage></item>
<item>
<feedburner:origLink>https://www.baeldung.com/spring-ai-subagent-orchestration</feedburner:origLink>
		<title>Guide to Subagent Orchestration in Spring AI</title>
		<link>https://feeds.feedblitz.com/~/957590267/0/baeldung</link>
					<comments>https://feeds.feedblitz.com/~/957590267/0/baeldung#comments</comments>
		
		<dc:creator><![CDATA[Sagar Verma]]></dc:creator>
		<pubDate>Sun, 31 May 2026 17:19:02 +0000</pubDate>
				<category><![CDATA[Spring AI]]></category>
		<category><![CDATA[Spring AI ChatClient]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/spring-ai-subagent-orchestration</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Boot-Featured-Image-02-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="max-width:100% !important;height:auto !important;float: left; margin-right: 5px;" loading="lazy" /><p>Learn how to build a subagent orchestration system using Spring AI and Spring AI Community Agent Utils.</p>
The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/957590267/0/baeldung">Guide to Subagent Orchestration in Spring AI</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/957590267/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/957590267/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f11%2fSpring-Boot-Featured-Image-02-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/24/957590267/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/957590267/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/957590267/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/spring-ai-subagent-orchestration#comments"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/spring-ai-subagent-orchestration/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&nbsp;
<div style="clear:left;"><a rel="NOFOLLOW" href="https://www.baeldung.com/spring-ai-subagent-orchestration#comments"><h3>Comments</h3></a><ul><li><a rel="NOFOLLOW" href="https://www.baeldung.com/spring-ai-subagent-orchestration#comment-15695">In reply to Daizcone.   Hey, Daixcone.   We're glad to hear the ...</a> <i>by Ulisses Lima</i><li><a rel="NOFOLLOW" href="https://www.baeldung.com/spring-ai-subagent-orchestration#comment-15693">The article is very well structured and explained. I really ...</a> <i>by Daizcone</i></ul></div>&#160;</div>]]>
</description>
										<content:encoded><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Boot-Featured-Image-02-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="float: left; margin-right: 5px;" decoding="async" loading="lazy" srcset="https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Boot-Featured-Image-02-1024x536.jpg 1024w, https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Boot-Featured-Image-02-300x157.jpg 300w, https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Boot-Featured-Image-02-768x402.jpg 768w, https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Boot-Featured-Image-02-100x52.jpg 100w, https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Boot-Featured-Image-02-600x314.jpg 600w, https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Boot-Featured-Image-02.jpg 1200w" sizes="auto, (max-width: 580px) 100vw, 580px" /><h2 data-section- data-start="165" data-end="178" id="bd-1f89aca" data-id="1f89aca">1. Overview</h2>
<div class="bd-anchor" id="1f89aca"></div>
<p>As AI systems grow more capable, a single “generalist” agent often becomes inefficient. It tries to handle everything in one context window, which leads to noisy prompts, slower responses, and degraded output quality. A better approach is to split responsibilities across specialized agents and let a central orchestrator coordinate them.</p>
<p><strong>This is exactly what <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://spring.io/blog/2026/01/27/spring-ai-agentic-patterns-4-task-subagents">Subagent Orchestration</a> enables in <span class="hover:entity-accent entity-underline inline cursor-pointer align-baseline"><span class="whitespace-normal">Spring AI</span></span>. Using the <em><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/spring-ai-community/spring-ai-agent-utils/blob/main/spring-ai-agent-utils/docs/TaskTools.md">Task</a></em><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/spring-ai-community/spring-ai-agent-utils/blob/main/spring-ai-agent-utils/docs/TaskTools.md"> tool</a> from the <em>spring-ai-agent-utils</em> library, we can build hierarchical agent systems where each subagent works in an isolated context and returns only essential results.</strong></p>
<p>In this tutorial, we&#8217;ll implement this pattern end-to-end using real APIs from the Spring ecosystem. We&#8217;ll learn how the orchestration works, how to configure subagents, and how to build a working system that dynamically delegates tasks.</p>
<h2 data-section- data-start="1058" data-end="1099" id="bd-rng8ig" data-id="rng8ig">2. Understanding Subagent Orchestration</h2>
<div class="bd-anchor" id="rng8ig"></div>
<p><strong>Subagent orchestration is a pattern where a primary AI agent delegates work to smaller, specialized agents</strong>. Each subagent is designed for a specific responsibility and operates in its own context window. This isolation ensures that prompts remain focused and prevents unnecessary information from polluting the reasoning process.</p>
<p><strong>Unlike traditional service orchestration, the delegation decision isn&#8217;t hardcoded. The main agent uses LLM to decide when a task should be delegated</strong>. This decision is based on natural language descriptions provided for each subagent, making the system flexible and adaptive. This approach improves separation of concerns, prompt clarity, maintainability, extensibility, and model specialization.</p>
<h3 id="bd-1-why-use-specialized-subagents" data-id="1-why-use-specialized-subagents">2.1. Why Use Specialized Subagents?</h3>
<div class="bd-anchor" id="1-why-use-specialized-subagents"></div>
<p>Specialized subagents help us create more modular AI systems. For example, in an AI travel assistant, one subagent can search flights, another can recommend hotels, and another can build personalized itineraries based on user preferences. This allows each subagent to focus on a specialized responsibility instead of relying on a single large prompt.</p>
<p>Each subagent receives a focused responsibility instead of competing for context inside a shared workflow. This pattern becomes especially useful in enterprise systems where AI applications continue to grow in complexity over time.</p>
<h3 id="bd-2-how-spring-ai-supports-orchestration" data-id="2-how-spring-ai-supports-orchestration">2.2. How Spring AI Supports Orchestration</h3>
<div class="bd-anchor" id="2-how-spring-ai-supports-orchestration"></div>
<p>Spring AI Community Agent Utils provides orchestration utilities through <em>TaskTool</em> and <em>ClaudeSubagentReferences</em>. These components help us register subagents, load subagent definitions dynamically, delegate tasks automatically, and orchestrate multiple AI workflows.</p>
<p>One of the most interesting aspects of this approach is that subagents can be defined in markdown files instead of Java classes.</p>
<h2 data-section- data-start="3384" data-end="3402" id="bd-1ibx8pa" data-id="1ibx8pa">3. Project Setup</h2>
<div class="bd-anchor" id="1ibx8pa"></div>
<p>Before implementing Subagent Orchestration, we’ll set up a simple <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-boot">Spring Boot</a> application using <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://start.spring.io/" target="_blank" rel="noopener">Spring Initializr</a> with <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/org.springframework.ai/spring-ai-openai-spring-boot-starter/">Spring AI starter OpenAI</a>, <em><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-test/">starter-test</a>,</em> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/org.springaicommunity/spring-ai-agent-utils/">Spring AI Community Agent Utils</a>. We’ll first add the required dependencies and then configure the OpenAI model and markdown-based subagent definitions.</p>
<p>In this application, we&#8217;ll build an AI-powered orchestration system that delegates work to multiple specialized subagents. One subagent reviews the code quality of a Spring Boot application, while another generates concise technical documentation. A central orchestrator analyzes the user request and coordinates these subagents to produce a combined response.</p>
<h3 id="bd-1-adding-maven-dependencies" data-id="1-adding-maven-dependencies">3.1. Adding Maven Dependencies</h3>
<div class="bd-anchor" id="1-adding-maven-dependencies"></div>
<p>First, let&#8217;s configure the required dependencies:</p>
<pre><code class="language-java">&lt;dependencies&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
        &lt;artifactId&gt;spring-boot-starter-test&lt;/artifactId&gt;
        &lt;scope&gt;test&lt;/scope&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;org.springframework.ai&lt;/groupId&gt;
        &lt;artifactId&gt;spring-ai-starter-model-openai&lt;/artifactId&gt;
        &lt;version&gt;2.0.0-M5&lt;/version&gt;
        &lt;scope&gt;compile&lt;/scope&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;org.springaicommunity&lt;/groupId&gt;
        &lt;artifactId&gt;spring-ai-agent-utils&lt;/artifactId&gt;
        &lt;version&gt;0.4.2&lt;/version&gt;
    &lt;/dependency&gt;
&lt;/dependencies&gt;</code></pre>
<p>This configuration adds the core dependencies required for our application. These dependencies provide Spring Boot application support, OpenAI integration through Spring AI, orchestration utilities for subagents, and testing support. After adding the dependencies, we&#8217;ll configure the OpenAI API key.</p>
<h3 data-start="3917" data-end="4034" id="bd-2-configuring-openai-access" data-id="2-configuring-openai-access">3.2. Configuring OpenAI Access</h3>
<div class="bd-anchor" id="2-configuring-openai-access"></div>
<p>Next, we’ll configure the OpenAI API key, the default LLM model, and the location of the markdown-based subagent definitions inside <em data-start="760" data-end="784">application.properties</em>:</p>
<pre><code class="language-yaml">spring.ai.openai.api-key=${OPENAI_API_KEY}
spring.ai.openai.chat.options.model=gpt-4.1-mini
spring.application.name=spring-ai-subagent
agent.tasks.paths=classpath:/agents/*.md</code></pre>
<p>We should replace <em data-start="361" data-end="377">OPENAI_API_KEY</em> with a valid <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://platform.openai.com/settings/organization/api-keys">OpenAI API key</a> or expose it through an environment variable to avoid storing sensitive credentials directly in the codebase.</p>
<p>The <em data-start="520" data-end="539">agent.tasks.paths</em> property points to the markdown-based subagent definitions located under <em data-start="613" data-end="641">src/main/resources/agents/</em>. Spring AI uses these files to dynamically load and register specialized subagents during application startup.</p>
<h2 id="bd-creating-specialized-subagents" data-id="creating-specialized-subagents">4. Creating Specialized Subagents</h2>
<div class="bd-anchor" id="creating-specialized-subagents"></div>
<p>This project uses markdown files to define specialized subagents. Their locations are declared as classpath resources. This approach keeps agent behavior externalized and easy to maintain. First, we’ll create an <em>agents</em> directory under the path <em>src/main/resources/</em>.</p>
<h3 id="bd-1-creating-the-code-reviewer-subagent" data-id="1-creating-the-code-reviewer-subagent">4.1. Creating the Code Reviewer Subagent</h3>
<div class="bd-anchor" id="1-creating-the-code-reviewer-subagent"></div>
<p>Let’s create a subagent responsible for reviewing code quality. The Markdown body acts as the subagent’s system prompt and defines its specialized behavior.</p>
<p>We’ll add a <em>code-reviewer.md</em> file under path <em>src/main/resources/agents/ </em>with the following instructions:</p>
<pre><code class="language-yaml">---
name: code-reviewer
description: &gt;
  Expert code reviewer. Use proactively after writing or modifying code
  to surface quality, security, and readability issues.
tools: Read, Grep, Glob
disallowedTools: Edit, Write
model: sonnet
---
You are a senior code reviewer with expertise in software quality.
**When Invoked:**
1. Run `git diff` to identify recent changes
2. Inspect the modified files and surrounding context
3. Check for issues in the areas listed below
**Review Checklist:**
- Code clarity and readability
- Proper naming conventions
- Error handling and edge cases
- Security vulnerabilities
**Output:** Clear, actionable feedback organized by file, with line references.</code></pre>
<p>This subagent focuses only on code analysis responsibilities. Externalizing the behavior into markdown files makes it easier to evolve prompts without modifying Java classes.</p>
<h3 id="bd-2-creating-the-documentation-writer-subagent" data-id="2-creating-the-documentation-writer-subagent">4.2. Creating the Documentation Writer Subagent</h3>
<div class="bd-anchor" id="2-creating-the-documentation-writer-subagent"></div>
<p>Next, let’s create another subagent that specializes in technical documentation. We’ll add a <em>documentation-writer.md</em> file under <em>src/main/resources/agents/</em>and define the following instructions:</p>
<pre><code class="language-yaml">---
name: documentation-writer
description: &gt;
  Technical documentation specialist for architecture explanations,
  workflow summaries, and concise developer-facing docs.
model: default
---
You are a senior technical documentation specialist.
Your responsibilities:
- Generate concise technical documentation
- Explain Spring Boot and Java application architecture
- Summarize workflows clearly
- Produce developer-friendly explanations
- Keep documentation simple and technically accurate</code></pre>
<p>This subagent focuses entirely on generating developer documentation. Now we have two specialized subagents named <em>code-reviewer</em> and <em>documentation-writer</em>.</p>
<h2 id="bd-configuring-the-main-orchestrator-agent" data-id="configuring-the-main-orchestrator-agent">5. Configuring the Main Orchestrator Agent</h2>
<div class="bd-anchor" id="configuring-the-main-orchestrator-agent"></div>
<p><strong>The orchestrator is responsible for loading subagents, registering orchestration tools, and executing delegated workflows.</strong> This main agent is the entry point that a user interacts with directly. Its underlying large language model (LLM) has access to the <em>Tasktool</em>, which exposes a catalog of available subagents. When the main agent decides that a specialist better handles a portion of a user request, it invokes the <em>Tasktool</em>, passing in the subagent name and a task description.</p>
<h3 id="bd-1-configuring-subagent-references" data-id="1-configuring-subagent-references">5.1. Configuring Subagent References</h3>
<div class="bd-anchor" id="1-configuring-subagent-references"></div>
<p><em>Spring AI Community Agent Utils</em> provides <em>ClaudeSubagentReferences</em> to load markdown-based subagents. The following configuration dynamically loads subagents from the markdown resources directory:</p>
<pre><code class="language-java">@Configuration
public class AgentConfig {
    @Value("${agent.tasks.paths}")
    private List&lt;Resource&gt; agentPaths;
    @Bean
    @Primary
    public ChatClient orchestratorChatClient(ChatClient.Builder chatClientBuilder) {
        SubagentType claudeType = ClaudeSubagentType.builder()
          .chatClientBuilder("default", chatClientBuilder.clone())
          .build();
        ToolCallback taskTool = TaskTool.builder()
          .subagentReferences(ClaudeSubagentReferences.fromResources(agentPaths))
          .subagentTypes(claudeType)
          .build();
        return chatClientBuilder.clone()
          .defaultToolCallbacks(taskTool)
          .build();
    }
}</code></pre>
<p>The two key classes wire together are <em>TaskTool</em> (the tool that the orchestrator&#8217;s LLM calls) and <em>ClaudeSubagentType</em> (which tells the tool how to spawn a subagent and which <em>ChatClient.Builder</em> to use). Note this <em>.clone()</em> calls on <em>chatClientBuilder</em>.<strong> This is important because the orchestrator and each subagent need their own independent <em>ChatClient.Builder</em> instance so they do not share default options or system prompts.</strong></p>
<h3 id="bd-2-creating-the-orchestrator-service" data-id="2-creating-the-orchestrator-service">5.2. Creating the Orchestrator Service</h3>
<div class="bd-anchor" id="2-creating-the-orchestrator-service"></div>
<p>This service acts as the entry point for orchestrated AI requests. The main agent&#8217;s LLM automatically decides whether to delegate to a subagent based on the task descriptions in the Agent Registry:</p>
<pre><code class="language-yaml">@Service
public class OrchestratorService {
    private final ChatClient chatClient;
    public OrchestratorService(ChatClient chatClient) {
        this.chatClient = chatClient;
    }
    public String ask(String userMessage) {
        return chatClient.prompt(userMessage).call().content();
    }
}</code></pre>
<p>The service itself remains very small because the orchestration complexity is handled internally through Spring AI tools. <strong>Notice that there is no manual subagent invocation code here. The delegation happens transparently inside the LLM reasoning loop when the model selects the <em>Task</em> tool.</strong></p>
<h3 id="bd-3-execute-orchestrated-workflows" data-id="3-execute-orchestrated-workflows">5.3. Execute Orchestrated Workflows</h3>
<div class="bd-anchor" id="3-execute-orchestrated-workflows"></div>
<p>Now we&#8217;ll execute orchestrated workflows. The following method triggers multiple specialized subagents:</p>
<pre><code class="language-java">public class SpringAiSubagentApplication {
    private static final Logger logger = LoggerFactory.getLogger(SpringAiSubagentApplication.class);
    public static void main(String[] args) {
        SpringApplication.run(SpringAiSubagentApplication.class, args);
    }
    @Bean
    CommandLineRunner demo(OrchestratorService orchestratorService) {
        return args -&gt; {
            String response = orchestratorService.ask(
              """
              Perform the following tasks:
              - Review the code quality.
              - Generate concise technical documentation like user guide.
              """
            );
            logger.info("{}", response);
        };
    }
}</code></pre>
<p>This prompt allows the orchestrator to delegate responsibilities across multiple subagents. When we run the application, Spring AI loads the markdown-based subagents, routes the request through the orchestration layer, and generates a combined response from the specialized agents.</p>
<p>The following screenshot shows the output generated by the multi-subagent orchestration workflow:</p>
<a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/wp-content/uploads/2026/05/SpringAISubAgentOutput.png"><img decoding="async" class="aligncenter size-large wp-image-245173" src="https://www.baeldung.com/wp-content/uploads/2026/05/SpringAISubAgentOutput-1024x553.png" alt="Spring_AI_SubAgent_Output" /></a>
<p>From the generated response, we can clearly see how each subagent handles its own specialized responsibility. The <em data-start="1192" data-end="1207">code-reviewer</em> subagent focuses on analyzing code quality concerns, while the <em data-start="1289" data-end="1311">documentation-writer</em> subagent generates concise technical documentation. This separation of responsibilities is one of the main advantages of subagent orchestration.</p>
<h2 id="bd-testing-subagent-orchestration" data-id="testing-subagent-orchestration">6. Testing Subagent Orchestration</h2>
<div class="bd-anchor" id="testing-subagent-orchestration"></div>
<p>AI orchestration workflows should still be tested like any other application component. The application includes integration testing for orchestration behavior.</p>
<h3 id="bd-1-testing-subagent-loading" data-id="1-testing-subagent-loading">6.1. Testing Subagent Loading</h3>
<div class="bd-anchor" id="1-testing-subagent-loading"></div>
<p>The following test verifies that Spring AI can load subagent definitions correctly:</p>
<pre><code class="language-java">@Test
void givenSubagentDefinitions_whenLoadingSubagents_thenReferencesAreCreated() {
    var references = ClaudeSubagentReferences.fromResources(agentPaths);
    assertThat(references).isNotNull();
}</code></pre>
<p>This test ensures the orchestration layer can successfully load markdown-based subagents.</p>
<h3 id="bd-2-testing-orchestrated-responses" data-id="2-testing-orchestrated-responses">6.2. Testing Orchestrated Responses</h3>
<div class="bd-anchor" id="2-testing-orchestrated-responses"></div>
<p>Next, let’s verify that the orchestrator can execute requests successfully:</p>
<pre><code class="language-java">@Test
void givenPrompt_whenExecutingOrchestration_thenResponseIsGenerated() {
    List&lt;Resource&gt; agentResources = List.of(
      new ClassPathResource("agents/test-agent.md")
    );
    SubagentType claudeType = ClaudeSubagentType.builder()
      .chatClientBuilder("default", chatClientBuilder.clone())
      .build();
    ToolCallback taskTool = TaskTool.builder()
      .subagentReferences(ClaudeSubagentReferences.fromResources(agentResources))
      .subagentTypes(claudeType)
      .build();
    ChatClient chatClient = chatClientBuilder.clone()
      .defaultToolCallbacks(taskTool)
      .build();
    String result = chatClient.prompt("Explain how the authentication module works.")
      .call()
      .content();
    assertThat(result).isNotBlank();
    assertThat(result).contains("authentication");
}</code></pre>
<p>This integration test validates the orchestration flow. Here, the unit test should mock the model layer and verify the orchestration output. This test validates the service contract and keeps orchestration behavior verifiable without external API calls.</p>
<h2 id="bd-conclusion" data-id="conclusion">7. Conclusion</h2>
<div class="bd-anchor" id="conclusion"></div>
<p>In this article, we built a subagent orchestration system using Spring AI and Spring AI Community Agent Utils. We  created specialized subagents for code review and technical documentation, then orchestrated them through a centralized AI workflow. This approach helps keep AI systems modular, maintainable, and easier to evolve.</p>
<p>We also explored how Spring AI provides a clean and declarative way to build hierarchical multi-agent systems. By isolating each subagent within its own context window, we can reduce the context pollution that often impacts single-agent workflows. The Markdown-based configuration makes it straightforward to define and version subagents without introducing additional Java implementations. We also saw how <em data-start="765" data-end="775">TaskTool</em> acts as the bridge between the orchestrator and specialized subagents.</p>
<p>As AI applications continue to grow in complexity, subagent orchestration offers a practical way to distribute responsibilities among focused agents rather than relying on a single large prompt.</p>
<p>As always, the code for this example is available <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eugenp/tutorials/tree/master/spring-ai-modules/spring-ai-subagent-orchestrator">over on GitHub</a>.</p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-ai-subagent-orchestration">Guide to Subagent Orchestration in Spring AI</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.<Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/957590267/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/957590267/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/957590267/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f11%2fSpring-Boot-Featured-Image-02-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/24/957590267/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/957590267/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/957590267/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/spring-ai-subagent-orchestration#comments"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/spring-ai-subagent-orchestration/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&nbsp;
<div style="clear:left;"><a rel="NOFOLLOW" href="https://www.baeldung.com/spring-ai-subagent-orchestration#comments"><h3>Comments</h3></a><ul><li><a rel="NOFOLLOW" href="https://www.baeldung.com/spring-ai-subagent-orchestration#comment-15695">In reply to Daizcone.   Hey, Daixcone.   We're glad to hear the ...</a> <i>by Ulisses Lima</i><li><a rel="NOFOLLOW" href="https://www.baeldung.com/spring-ai-subagent-orchestration#comment-15693">The article is very well structured and explained. I really ...</a> <i>by Daizcone</i></ul></div>&#160;</div>]]>
</content:encoded>
					
					<wfw:commentRss>https://feeds.feedblitz.com/~/957590267/0/baeldung/feed</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Boot-Featured-Image-02-150x150.jpg</webfeeds:featuredImage></item>
<item>
<feedburner:origLink>https://www.baeldung.com/spring-ai-embed-mcp-server-html-ui</feedburner:origLink>
		<title>Embedding HTML UIs in MCP Servers With Spring AI</title>
		<link>https://feeds.feedblitz.com/~/957590270/0/baeldung</link>
					<comments>https://feeds.feedblitz.com/~/957590270/0/baeldung#respond</comments>
		
		<dc:creator><![CDATA[Emanuel Trandafir]]></dc:creator>
		<pubDate>Sun, 31 May 2026 17:16:19 +0000</pubDate>
				<category><![CDATA[Spring AI]]></category>
		<category><![CDATA[MCP]]></category>
		<category><![CDATA[Spring AI ChatClient]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/spring-ai-embed-mcp-server-html-ui</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2016/10/social-Java-On-Baeldung-2.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="max-width:100% !important;height:auto !important;float: left; margin-right: 5px;" loading="lazy" /><p>Learn how to build a Spring AI MCP server, connect it to Claude Desktop, and embed interactive HTML UIs directly inside AI chat conversations.</p>
The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/957590270/0/baeldung">Embedding HTML UIs in MCP Servers With Spring AI</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/957590270/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/957590270/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2016%2f10%2fsocial-Java-On-Baeldung-2.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/24/957590270/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/957590270/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/957590270/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/spring-ai-embed-mcp-server-html-ui#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/spring-ai-embed-mcp-server-html-ui/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</description>
										<content:encoded><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2016/10/social-Java-On-Baeldung-2.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="float: left; margin-right: 5px;" decoding="async" loading="lazy" srcset="https://www.baeldung.com/wp-content/uploads/2016/10/social-Java-On-Baeldung-2.jpg 952w, https://www.baeldung.com/wp-content/uploads/2016/10/social-Java-On-Baeldung-2-300x157.jpg 300w, https://www.baeldung.com/wp-content/uploads/2016/10/social-Java-On-Baeldung-2-768x402.jpg 768w" sizes="auto, (max-width: 580px) 100vw, 580px" /><h2 id="bd-overview" data-id="overview">1. Overview</h2>
<div class="bd-anchor" id="overview"></div>
<p>MCP (Model Context Protocol) servers are the basis of modern AI handling and development. To that end, knowing how to configure one and integrate it into projects can be essential.</p>
<p>In this tutorial, <strong>we&#8217;ll learn how to build an MCP server with Spring AI and connect it to an AI harness such as Claude Desktop</strong>. To start, we create a simple <em>hello-world</em> tool, test it using the MCP Inspector, and then register it with Claude Desktop as a custom connector.</p>
<p>Once the basics are in place, we&#8217;ll take it one step further and expose a rich, interactive HTML UI — a fortune wheel that randomly picks today&#8217;s sport. In that implementation, we&#8217;ll see how Spring AI&#8217;s <em>@McpTool</em> and <em>@McpResource</em> annotations let the harness render the page inline in the chat and feed the result back into the conversation.</p>
<h2 id="bd-creating-an-mcp-server" data-id="creating-an-mcp-server">2. Creating an MCP Server</h2>
<div class="bd-anchor" id="creating-an-mcp-server"></div>
<p>The <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-ai-model-context-protocol-mcp">Model Context Protocol (MCP)</a> provides a standardized way for AI assistants such as Claude Code or Claude Desktop to invoke some custom functionality whenever the model sees fit. <strong>An MCP server is the lightweight backend that exposes this functionality via <em>tools</em>, <em>resources</em>, and <em>prompts</em></strong>.</p>
<p>Let&#8217;s start by creating a simple <em>hello-world</em> MCP server. First, we add the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/org.springframework.ai/spring-ai-mcp-server-webmvc-spring-boot-starter"><em>spring-ai-starter-mcp-server-webmvc</em></a> dependency to the <em>pom.xml</em>:</p>
<pre><code class="language-xml">&lt;dependency&gt;
  &lt;groupId&gt;org.springframework.ai&lt;/groupId&gt;
  &lt;artifactId&gt;spring-ai-starter-mcp-server-webmvc&lt;/artifactId&gt;
&lt;/dependency&gt;</code></pre>
<p>Additionally, we use <em><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/org.springframework.ai/spring-ai-bom">spring-ai-bom</a></em> for version management:</p>
<pre><code class="language-">&lt;dependencyManagement&gt;
  &lt;dependencies&gt;
    &lt;dependency&gt;
      &lt;groupId&gt;org.springframework.ai&lt;/groupId&gt;
      &lt;artifactId&gt;spring-ai-bom&lt;/artifactId&gt;
      &lt;version&gt;2.0.0-M6&lt;/version&gt;
      &lt;type&gt;pom&lt;/type&gt;
      &lt;scope&gt;import&lt;/scope&gt;
    &lt;/dependency&gt;
  &lt;/dependencies&gt;
&lt;/dependencyManagement&gt;</code></pre>
<p>Then, let&#8217;s configure the server port and the MCP transport protocol that the server exposes. Spring AI supports three transport options:</p>
<ul>
<li><em><strong>stdio</strong></em> for local process-based communication</li>
<li><em><strong>sse</strong></em> for the legacy HTTP transport based on Server-Sent Events</li>
<li><em><strong>streamable</strong></em> for the newer Streamable HTTP transport that handles both requests and streamed responses over a single endpoint</li>
</ul>
<p>Here, we use the <em>streamable</em> protocol. So, let&#8217;s configure it in <em>application.properties:</em></p>
<pre><code class="language-properties">server.port=3001
spring.ai.mcp.server.protocol=STREAMABLE</code></pre>
<p>Finally, we add a <em>tool</em> that says <em>hello</em> from the server back to the user:</p>
<pre><code class="language-java">@SpringBootApplication
class McpUiApplication {
    public static void main(String[] args) {
        SpringApplication.run(McpUiApplication.class, args);
    }
    @McpTool(
        title = "Say Hello",
        name = "say-hello",
        description = "A simple tool that returns a greeting message."
    )
    String sayHello() {
        return "Hello from the MCP UI Application!";
    }
}</code></pre>
<p>As expected, this tool is exposed to the AI agent, and the greeting message is returned to the user whenever the model decides to invoke it.</p>
<h2 id="bd-testing-the-mcp-server" data-id="testing-the-mcp-server">3. Testing the MCP Server</h2>
<div class="bd-anchor" id="testing-the-mcp-server"></div>
<p>There are several ways to connect to and test an MCP server. Let&#8217;s try to use some of them to do that and trigger the <em>Say Hello</em> tool.</p>
<h3 id="bd-1-test-using-mcp-inspector" data-id="1-test-using-mcp-inspector">3.1. Test Using MCP Inspector</h3>
<div class="bd-anchor" id="1-test-using-mcp-inspector"></div>
<p><strong>One convenient option is the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://modelcontextprotocol.io/docs/tools/inspector">MCP Inspector</a>, an interactive developer tool designed for testing and debugging MCP servers</strong>:</p>
<pre><code class="language-bash">npx @modelcontextprotocol/inspector</code></pre>
<p>This command starts a local proxy server along with a web-based UI that we can open in the browser to test the MCP. Using this interface, we can perform different actions:</p>
<ol>
<li>connect to an MCP server</li>
<li>see the <em>resources</em>, <em>prompts</em>, and <em>tools</em> it exposes</li>
<li>trigger tools</li>
</ol>
<p>The interface is fairly straightforward:</p>
<img loading="lazy" decoding="async" class="aligncenter wp-image-8975" src="https://www.baeldung.com/wp-content/uploads/2026/05/mcp_inspector.jpg" alt="MCP Inspector" width="1140" height="736" />
<p>As we can see, we are connected to the MCP server we created using Spring AI, and we successfully ran its <em>Say Hello</em> tool.</p>
<h3 id="bd-2-test-using-claude-desktop" data-id="2-test-using-claude-desktop">3.2. Test Using Claude Desktop</h3>
<div class="bd-anchor" id="2-test-using-claude-desktop"></div>
<p>Alternatively, we can use an AI harness to test the MCP server. After all,<strong> the end goal here is to use the MCP server from a desktop application such as Claude Desktop</strong>.</p>
<p>Firstly, we need to register it in the <em>claude_desktop_config.json</em> file, the location of which varies:</p>
<ul>
<li>on Windows, it&#8217;s <em>%APPDATA%\Claude\claude_desktop_config.json</em></li>
<li>on macOS, it&#8217;s <em>~/Library/Application Support/Claude/claude_desktop_config.json</em></li>
</ul>
<p>Claude Desktop natively supports only the <em>stdio</em> transport for local MCP servers, but the server speaks streamable <em>HTTP</em>. To bridge that gap, we use <em><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.npmjs.com/package/mcp-remote">mcp-remote</a></em> — a small <em>npm</em> utility that runs as a local <em>stdio</em> process and forwards every MCP message to a remote <em>HTTP</em> endpoint. Simply put, Claude Desktop talks to <em>mcp-remote</em> over standard input-output, and <em>mcp-remote</em> relays the traffic to the Spring AI server at <em>http://localhost:3001/mcp</em>:</p>
<pre><code class="language-json">"mcpServers": {
  "sport-spinner": {
    "command": "npx",
    "args": [
      "mcp-remote",
      "http://localhost:3001/mcp",
      "--transport",
      "http-only",
      "--allow-http"
    ]
  }
}</code></pre>
<p>As we can see, we also need to assign a name to the MCP application. Let&#8217;s call it <em>sport-spinner</em>, since we extend it to recommend a sport for the user to practice on any given day.</p>
<p>Finally, if we restart Claude Desktop, we should be able to see the MCP server as a custom connector:</p>
<img loading="lazy" decoding="async" class="aligncenter wp-image-8976" src="https://www.baeldung.com/wp-content/uploads/2026/05/claude-desktop-connector.png" alt="Enable connector" width="748" height="554" />
<p>Therefore, the model can now decide to invoke the tools exposed by the MCP server:</p>
<img loading="lazy" decoding="async" class="aligncenter wp-image-8977" src="https://www.baeldung.com/wp-content/uploads/2026/05/claude-desktop-sayhello.png" alt="Example of usage" width="745" height="372" />
<p><strong>Looks like we managed to connect to the MCP application and return a <em>String</em> that AI Harness can use or display to the user</strong>. Now, let&#8217;s upgrade the application to return HTML that can be rendered in Claude Desktop.</p>
<h2 id="bd-embedding-html" data-id="embedding-html">4. Embedding HTML</h2>
<div class="bd-anchor" id="embedding-html"></div>
<p>So far, the MCP server only returns plain text. However, modern AI harnesses such as Claude Desktop can also render rich, interactive UIs that the server exposes as HTML resources. <strong>We can achieve this by combining an <em>@McpTool</em> that triggers the UI with an <em>@McpResource</em> that serves the actual HTML</strong>.</p>
<p>Let&#8217;s enhance the <em>sport-spinner</em> to display a real fortune wheel that picks today&#8217;s sport. First, we add an HTML file under <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eugenp/tutorials/tree/master/spring-ai-modules/spring-ai-mcp/src/main/resources/static/sport-spinner.html"><em>src/main/resources/static/sport-spinner.html</em></a>, containing the HTML, CSS, and JavaScript that animates the wheel.</p>
<h3 id="bd-1-exposing-the-html-as-an-mcp-resource" data-id="1-exposing-the-html-as-an-mcp-resource">4.1. Exposing the HTML as an MCP Resource</h3>
<div class="bd-anchor" id="1-exposing-the-html-as-an-mcp-resource"></div>
<p>Let&#8217;s create a new Spring bean that loads the HTML file and exposes it via the <em>@McpResource</em> annotation:</p>
<pre><code class="language-java">@Component
class SportSpinnerUI {
    @Value("classpath:/static/sport-spinner.html")
    private Resource sportSpinnerResource;
    @McpResource(
        name = "Sport Spinner App Resource",
        uri = "ui://sport/sport-spinner.html",
        mimeType = "text/html;profile=mcp-app",
        metaProvider = CspMetaProvider.class)
    public String getSportSpinnerResource() throws IOException {
        return sportSpinnerResource.getContentAsString(Charset.defaultCharset());
    }
}</code></pre>
<p>A few things worth highlighting here:</p>
<ul>
<li>the <em>uri</em> uniquely identifies the resource within the MCP server</li>
<li>the <em>mimeType</em> uses the <em>text/html;profile=mcp-app</em> profile &#8211; signaling to the client that this HTML should be rendered as an interactive MCP app rather than displayed as raw text</li>
<li>the <em>metaProvider</em> attaches additional metadata to the resource — in this case, a Content Security Policy that allow-lists <em>unpkg.com</em> so the page can load external scripts from that CDN</li>
</ul>
<p>Let&#8217;s have a look at the <em>CspMetaProvider</em>:</p>
<pre><code class="language-java">class CspMetaProvider implements MetaProvider {
    @Override
    public Map&lt;String, Object&gt; getMeta() {
        return Map.of("ui",
            Map.of("csp",
                Map.of("resourceDomains", List.of("https://unpkg.com"))));
    }
}</code></pre>
<p>At this point, we should be ready to connect the UI to the tool.</p>
<h3 id="bd-2-linking-the-tool-to-the-ui-resource" data-id="2-linking-the-tool-to-the-ui-resource">4.2. Linking the Tool to the UI Resource</h3>
<div class="bd-anchor" id="2-linking-the-tool-to-the-ui-resource"></div>
<p>Finally, let&#8217;s add an MCP tool that instructs the harness to open the wheel:</p>
<pre><code class="language-java">@McpTool(
    title = "Spin Sport Wheel",
    name = "spin-sport-wheel",
    description = "Opens a fortune wheel that spins and randomly picks today's sport.",
    metaProvider = SportSpinnerToolMetaProvider.class)
public String spinSportWheel() {
    return "Opening the sport spinner wheel.";
}</code></pre>
<p><strong>The key element here is <em>metaProvider</em>, which links this tool to the HTML resource we defined earlier</strong>:</p>
<pre><code class="language-java">class SportSpinnerToolMetaProvider implements MetaProvider {
    @Override
    public Map&lt;String, Object&gt; getMeta() {
        return Map.of("ui",
            Map.of("resourceUri", "ui://sport/sport-spinner.html"));
    }
}</code></pre>
<p>When the model invokes <em>spin-sport-wheel</em>, the harness reads this metadata, fetches the resource at <em>ui://sport/sport-spinner.html</em>, and renders it inline in the chat, giving the user a fully interactive wheel instead of a plain text reply.</p>
<p><strong>Let&#8217;s restart Claude Desktop and ask it what sport we should practice today</strong>:</p>
<img decoding="async" class="aligncenter size-full wp-image-8979" src="https://www.baeldung.com/wp-content/uploads/2026/05/claude-desktop-embedded-html.png" alt="Fortune Wheel spin UI" />
<p>As expected, <strong>the HTML from the Spring Boot application is now rendered directly inside the Claude Desktop chat</strong>. At this point, we can spin the wheel to let it pick today&#8217;s sport, with the result fed back into the conversation so the model can react to it.</p>
<h2 id="bd-conclusion" data-id="conclusion">5. Conclusion</h2>
<div class="bd-anchor" id="conclusion"></div>
<p>In this tutorial, we learned how to build an MCP server with Spring AI and connect it to an AI harness such as Claude Desktop.</p>
<p>Specifically, we started with a simple <em>hello-world</em> tool and tested it using the MCP Inspector and Claude Desktop. Then, we took it one step further by exposing a rich, interactive HTML UI through Spring AI&#8217;s <em>@McpTool</em> and <em>@McpResource</em> annotations. As a result, we made the harness render a fortune wheel inline in the chat and feed the result back into the conversation.</p>
<p>As always, the full source code is available <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eugenp/tutorials/tree/master/spring-ai-modules/spring-ai-mcp">over on GitHub</a>.</p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-ai-embed-mcp-server-html-ui">Embedding HTML UIs in MCP Servers With Spring AI</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.<Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/957590270/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/957590270/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/957590270/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2016%2f10%2fsocial-Java-On-Baeldung-2.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/24/957590270/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/957590270/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/957590270/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/spring-ai-embed-mcp-server-html-ui#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/spring-ai-embed-mcp-server-html-ui/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</content:encoded>
					
					<wfw:commentRss>https://feeds.feedblitz.com/~/957590270/0/baeldung/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2016/10/social-Java-On-Baeldung-2-150x150.jpg</webfeeds:featuredImage></item>
<item>
<feedburner:origLink>https://www.baeldung.com/java-weekly-648</feedburner:origLink>
		<title>Java Weekly, Issue 648</title>
		<link>https://feeds.feedblitz.com/~/957491033/0/baeldung</link>
					<comments>https://feeds.feedblitz.com/~/957491033/0/baeldung#respond</comments>
		
		<dc:creator><![CDATA[baeldung]]></dc:creator>
		<pubDate>Fri, 29 May 2026 17:00:42 +0000</pubDate>
				<category><![CDATA[Weekly Review]]></category>
		<category><![CDATA[no-ads]]></category>
		<category><![CDATA[no-after-post]]></category>
		<category><![CDATA[no-before-post]]></category>
		<category><![CDATA[no-optins]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/?p=203862</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2016/10/social-Weekly-Reviews-4.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="max-width:100% !important;height:auto !important;float: left; margin-right: 5px;" loading="lazy" /><p>Getting to value classes and mechanically enforced security in this era of AI. A solid week.</p>
The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/957491033/0/baeldung">Java Weekly, Issue 648</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/957491033/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/957491033/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2016%2f10%2fsocial-Weekly-Reviews-4.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/24/957491033/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/957491033/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/957491033/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/java-weekly-648#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/java-weekly-648/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</description>
										<content:encoded><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2016/10/social-Weekly-Reviews-4.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="float: left; margin-right: 5px;" decoding="async" loading="lazy" srcset="https://www.baeldung.com/wp-content/uploads/2016/10/social-Weekly-Reviews-4.jpg 952w, https://www.baeldung.com/wp-content/uploads/2016/10/social-Weekly-Reviews-4-300x157.jpg 300w, https://www.baeldung.com/wp-content/uploads/2016/10/social-Weekly-Reviews-4-768x402.jpg 768w" sizes="auto, (max-width: 580px) 100vw, 580px" /><h2 style="text-align: left;" id="bd-spring-and-java" data-id="spring-and-java">1.<strong> Spring and Java</strong></h2>
<div class="bd-anchor" id="spring-and-java"></div>
<p><strong><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://cr.openjdk.org/~jrose/values/default-values.html">&gt;&gt; On Default Values for Primitive-Like Classes</a></strong> [<span style="color: #993300;">openjdk.org</span>]</p>
<p>One of the thorniest problems standing between us and value classes: what happens when a value object never gets explicitly initialized? A deep exploration of how primitive-like classes might nominate their own default values, and what that means across the language, the JVM, and the runtime. Good stuff for the weekend.</p>
<p><strong><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://martinfowler.com/articles/vibesec-reckoning.html">&gt;&gt; The VibeSec Reckoning</a></strong> [<span style="color: #993300;">martinfowler.com</span>]</p>
<p>As AI-assisted coding moves into the mainstream, prompting an LLM toward &#8220;secure&#8221; output isn&#8217;t, of course, enough. Security has to be structural and enforced mechanically in the harness.</p>
<h4><strong>Also worth reading:</strong></h4>
<ul>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://inside.java/2026/05/23/java-aot-in-production-at-netflix/" target="_blank" rel="noopener"><strong>Java AOT in Production at Netflix</strong></a> [<span style="color: #800000;">inside.java</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://foojay.io/today/a-new-generation-of-java-libraries-is-born-wasm-becomes-the-implementation-detail/" target="_blank" rel="noopener"><strong>A New Generation of Java Libraries Is Born: Wasm Becomes the Implementation Detail</strong></a> [<span style="color: #800000;">foojay.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://inside.java/2026/05/26/jep523-target-jdk27/" target="_blank" rel="noopener"><strong>JEP targeted to JDK 27: 523: Make G1 the Default Garbage Collector in All Environments</strong></a> [<span style="color: #800000;">inside.java</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://inside.java/2026/05/25/jep537-target-jdk27/" target="_blank" rel="noopener"><strong>JEP targeted to JDK 27: 537: Vector API (12th Incubator)</strong></a> [<span style="color: #800000;">inside.java</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://inside.java/2026/05/26/openjdk-ai-agents/" target="_blank" rel="noopener"><strong>Agentic AI Workflows for OpenJDK Development</strong></a> [<span style="color: #800000;">inside.java</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://quarkus.io/blog/introducing-voting-pattern/" target="_blank" rel="noopener"><strong>Parallel voting and adaptive model selection: smarter agentic AI on a budget</strong></a> [<span style="color: #800000;">quarkus.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://foojay.io/today/context-is-a-budget-eight-levers-and-three-workflow-patterns/" target="_blank" rel="noopener"><strong>Context Is a Budget &#8211; Eight levers and three workflow patterns</strong></a> [<span style="color: #800000;">foojay.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.infoq.com/news/2026/05/tamboui-java-tui/" target="_blank" rel="noopener"><strong>TamboUI Promises to Bring Better Capabilities to Build TUIs in Java</strong></a> [<span style="color: #800000;">infoq.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.infoq.com/articles/schema-proliferation-problem/" target="_blank" rel="noopener"><strong>The Schema Proliferation Problem in Kafka and Flink Pipelines: How to Solve It</strong></a> [<span style="color: #800000;">infoq.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://blog.jetbrains.com/kotlin/2026/05/security-support-policy-for-the-kotlin-standard-library/" target="_blank" rel="noopener"><strong>Introducing a Security Support Policy for the Kotlin Standard Library</strong></a> [<span style="color: #800000;">jetbrains.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://bell-sw.com/news/spring-developers-have-a-blindspot-when-it-comes-to-container-security/" target="_blank" rel="noopener"><strong>Spring Developers Have a Blindspot When It Comes to Container Security</strong></a> [<span style="color: #800000;">bell-sw.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://advancedweb.hu/nixos-first-impressions-writing-system-level-tests/" target="_blank" rel="noopener"><strong>NixOS first impressions: writing system-level tests</strong></a> [<span style="color: #800000;">advancedweb.hu</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://blog.frankel.ch/seasons-time-lapse/2/" target="_blank" rel="noopener"><strong>Seasons time-lapse &#8211; alignment</strong></a> [<span style="color: #800000;">frankel.ch</span>]</li>
</ul>
<h4><strong>Webinars and presentations:</strong></h4>
<ul>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://foojay.io/today/foojay-podcast-96/" target="_blank" rel="noopener"><strong>Foojay Podcast #96: Local AWS Development Without LocalStack: Meet Floci, the GraalVM-Powered Alternative</strong></a> [<span style="color: #800000;">foojay.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.infoq.com/podcasts/chasing-efficient-java-development/" target="_blank" rel="noopener"><strong>Podcast: Chasing Efficient Java Development: From 1BRC to Developing Hardwood AI Natively</strong></a> [<span style="color: #800000;">infoq.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://spring.io/blog/2026/05/21/a-bootiful-podcast-hadi-hariri" target="_blank" rel="noopener"><strong>A Bootiful Podcast: Hadi Hariri, Jetbrains legend</strong></a> [<span style="color: #800000;">spring.io</span>]</li>
</ul>
<h4><strong>Time to upgrade:</strong></h4>
<ul>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://in.relation.to/2026/05/26/orm-74/" target="_blank" rel="noopener"><strong>Hibernate 7.4.0.Final</strong></a> [<span style="color: #800000;">in.relation.to</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://in.relation.to/2026/05/27/hibernate-search-8-4-0-Final/" target="_blank" rel="noopener"><strong>Hibernate Search 8.4.0.Final is out</strong></a> [<span style="color: #800000;">in.relation.to</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.wildfly.org/news/2026/05/21/WildFly-40-is-released/" target="_blank" rel="noopener"><strong>WildFly 40 is released!</strong></a> [<span style="color: #800000;">wildfly.org</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://spring.io/blog/2026/05/23/spring-ai-1-0-8-1-1-7-2-0-0-M7-available-now" target="_blank" rel="noopener"><strong>Spring AI 1.0.8, 1.1.7, 2.0.0-M7 Available Now</strong></a> [<span style="color: #800000;">spring.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://blog.jetbrains.com/ai/2026/05/koog-1-0-is-out-stable-core-better-interop-and-multiplatform-observability/" target="_blank" rel="noopener"><strong>Koog 1.0 Is Out: Stable Core, Better Interop, and Multiplatform Observability</strong></a> [<span style="color: #800000;">jetbrains.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://blog.jetbrains.com/idea/2026/05/intellij-idea-2026-2-eap/" target="_blank" rel="noopener"><strong>IntelliJ IDEA 2026.2 EAP Is Open</strong></a> [<span style="color: #800000;">jetbrains.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eclipse-vertx/vert.x/releases/tag/5.1.0" target="_blank" rel="noopener"><strong>Vert.x 5.1.0</strong></a> [<span style="color: #800000;">github.com/eclipse-vertx</span>]</li>
</ul>
<h2 style="text-align: left;" id="bd-pick-of-the-week" data-id="pick-of-the-week">2.<strong> Pick of the Week</strong></h2>
<div class="bd-anchor" id="pick-of-the-week"></div>
<p><strong><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://martinfowler.com/articles/break-monolith-into-microservices.html">&gt;&gt; How to break a Monolith into Microservices</a></strong> [<span style="color: #993300;">martinfowler.com</span>]</p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-weekly-648">Java Weekly, Issue 648</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.<Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/957491033/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/957491033/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/957491033/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2016%2f10%2fsocial-Weekly-Reviews-4.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/24/957491033/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/957491033/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/957491033/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/java-weekly-648#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/java-weekly-648/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</content:encoded>
					
					<wfw:commentRss>https://feeds.feedblitz.com/~/957491033/0/baeldung/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2016/10/social-Weekly-Reviews-4-150x150.jpg</webfeeds:featuredImage></item>
<item>
<feedburner:origLink>https://www.baeldung.com/jdbc-connector-j-mysql-communications-link-failure</feedburner:origLink>
		<title>How to Fix the Communications link failure Error in Java Applications With MySQL</title>
		<link>https://feeds.feedblitz.com/~/957397595/0/baeldung</link>
					<comments>https://feeds.feedblitz.com/~/957397595/0/baeldung#respond</comments>
		
		<dc:creator><![CDATA[Hiks Gerganov]]></dc:creator>
		<pubDate>Thu, 28 May 2026 02:00:36 +0000</pubDate>
				<category><![CDATA[Persistence]]></category>
		<category><![CDATA[JDBC]]></category>
		<category><![CDATA[MySQL]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/jdbc-connector-j-mysql-communications-link-failure</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/11/Persistence-Featured-Image-02-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="max-width:100% !important;height:auto !important;float: left; margin-right: 5px;" loading="lazy" /><p>Learn how to troubleshoot the "Communications link failure" error in Java/MySQL applications, typically thrown as CommunicationsException.</p>
The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/957397595/0/baeldung">How to Fix the <em>Communications link failure</em> Error in Java Applications With MySQL</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/957397595/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/957397595/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f11%2fPersistence-Featured-Image-02-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/24/957397595/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/957397595/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/957397595/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/jdbc-connector-j-mysql-communications-link-failure#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/jdbc-connector-j-mysql-communications-link-failure/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</description>
										<content:encoded><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/11/Persistence-Featured-Image-02-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="float: left; margin-right: 5px;" decoding="async" loading="lazy" srcset="https://www.baeldung.com/wp-content/uploads/2024/11/Persistence-Featured-Image-02-1024x536.jpg 1024w, https://www.baeldung.com/wp-content/uploads/2024/11/Persistence-Featured-Image-02-300x157.jpg 300w, https://www.baeldung.com/wp-content/uploads/2024/11/Persistence-Featured-Image-02-768x402.jpg 768w, https://www.baeldung.com/wp-content/uploads/2024/11/Persistence-Featured-Image-02-100x52.jpg 100w, https://www.baeldung.com/wp-content/uploads/2024/11/Persistence-Featured-Image-02-600x314.jpg 600w, https://www.baeldung.com/wp-content/uploads/2024/11/Persistence-Featured-Image-02.jpg 1200w" sizes="auto, (max-width: 580px) 100vw, 580px" /><h2 id="bd-overview" data-id="overview">1. Overview</h2>
<div class="bd-anchor" id="overview"></div>
<p>Since MySQL is one of the most common database management systems, we might need a way to establish a stable connection between <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-connect-mysql">MySQL and a Java application</a>. Usually, this happens via the MySQL Connector/J <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-jdbc">JDBC</a> driver. However, a common problem is the <em>Communications link failure</em> error, typically thrown as <em>CommunicationsException</em>. Although the error message may appear simple, the underlying causes can vary significantly.</p>
<p>In this tutorial, <strong>we explain how to fix the <em>Communications link failure</em> error in Java applications using MySQL</strong>. First, we explore what the exception means and when it appears. After that, we go over configuration issues, networking problems, MySQL settings, and other potential causes. Finally, we discuss different ways to diagnose the problem and work around or fix it.</p>
<p>Notably, we mainly refer to MySQL Connector/J <em>8.x</em> used with MySQL Server <em>5.7</em> and <em>8.x</em>. Since there are some differences between older Connector/J <em>5.1.x</em> versions and newer 8.x releases, we&#8217;ll mention them explicitly throughout the text when necessary.</p>
<h2 id="bd-understanding-the-error" data-id="understanding-the-error">2. Understanding the Error</h2>
<div class="bd-anchor" id="understanding-the-error"></div>
<p>Before attempting to fix the issue, let&#8217;s understand how and when the error appears.</p>
<h3 id="bd-1-communicationsexception" data-id="1-communicationsexception">2.1. <em>CommunicationsException</em></h3>
<div class="bd-anchor" id="1-communicationsexception"></div>
<p>Usually, the error happens in Java applications, causing a JDBC <em>CommunicationsEsception</em>:</p>
<pre><code class="language-java">com.mysql.jdbc.exceptions.jdbc4.CommunicationsException:
Communications link failure
The last packet sent successfully to the server was 0 milliseconds ago.
The driver has not received any packets from the server.</code></pre>
<p>Basically, <strong>the JDBC driver attempts to exchange data with the MySQL server, but no data goes through</strong>.</p>
<p>When it comes to Connector/J <em>5.1.x</em>, the exception commonly appears under the <em>com.mysql.jdbc.exceptions.jdbc4.CommunicationsException</em> package. However, Connector/J <em>8.x</em> changed the package to <em>com.mysql.cj.jdbc.exceptions.CommunicationsException</em>. Such changes aren&#8217;t unexpected, and future versions may continue the trend.</p>
<p>In practice, we often encounter this error in two scenarios:</p>
<ul>
<li>during the initial connection attempt</li>
<li>when executing queries on an already established connection</li>
</ul>
<p>Considering this, let&#8217;s inspect common causes.</p>
<h3 id="bd-2-common-causes" data-id="2-common-causes">2.2. Common Causes</h3>
<div class="bd-anchor" id="2-common-causes"></div>
<p>Although the error itself is generic, <strong>several underlying issues appear frequently in production systems</strong>:</p>
<ul>
<li>Our MySQL server isn&#8217;t running</li>
<li>JDBC connection URL contains incorrect hostname or port</li>
<li>DNS resolution fails for the configured host</li>
<li>MySQL isn&#8217;t listening for TCP/IP connections</li>
<li>firewall or proxy rules block database traffic</li>
<li>server closing idle connections</li>
<li>application exhausts available database connections</li>
<li>JDBC driver version incompatible with the server</li>
</ul>
<p>Since multiple factors can lead to the same exception, diagnosing the problem may require multiple steps.</p>
<h2 id="bd-verifying-basic-connectivity" data-id="verifying-basic-connectivity">3. Verifying Basic Connectivity</h2>
<div class="bd-anchor" id="verifying-basic-connectivity"></div>
<p>To begin with, let&#8217;s confirm that the application can reach the database server.</p>
<h3 id="bd-1-verify-mysql-server-is-running" data-id="1-verify-mysql-server-is-running">3.1. Verify MySQL Server Is Running</h3>
<div class="bd-anchor" id="1-verify-mysql-server-is-running"></div>
<p>Perhaps the most straightforward cause for the error is a non-running MySQL server instance. Naturally, <strong>if the service isn&#8217;t up, the JDBC driver can&#8217;t reach it</strong>.</p>
<p>Here, a simple restart can resolve the issue:</p>
<pre><code class="language-bash"># sudo service mysql restart</code></pre>
<p>After the server restarts, the application should be able to reconnect normally. Still, <strong>the problem can also occur due to intermittent restarts,</strong> in which case further debugging might be required.</p>
<h3 id="bd-2-check-jdbc-connection-url" data-id="2-check-jdbc-connection-url">3.2. Check JDBC Connection URL</h3>
<div class="bd-anchor" id="2-check-jdbc-connection-url"></div>
<p>The key parameters of the initial MySQL connection can cause communication failures.</p>
<p>Let&#8217;s analyze a typical MySQL JDBC connection string:</p>
<pre><code class="language-java">jdbc:mysql://localhost:3306/mydatabase</code></pre>
<p>When troubleshooting, we should verify the correctness of at least several parameters:</p>
<ul>
<li>hostname or IP address</li>
<li>port number (default: 3306)</li>
<li>database name</li>
<li>username and password</li>
</ul>
<p>Specifically, <strong>an incorrect port or hostname can prevent the application from connecting to the server entirely</strong>.</p>
<h3 id="bd-3-test-hostname-resolution" data-id="3-test-hostname-resolution">3.3. Test Hostname Resolution</h3>
<div class="bd-anchor" id="3-test-hostname-resolution"></div>
<p>As with any networking issue, the difference between a hostname and a host address is critical. Failure at name resolution is enough to block a connection. There are typically several possible reasons for this:</p>
<ul>
<li>bad operating system configuration</li>
<li>incorrect <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/linux/check-etc-hosts-file-entries">hosts file</a> entries</li>
<li>misconfigured DNS server</li>
<li>DNS server unaware of host</li>
<li>DNS server not responding</li>
</ul>
<p>Any of these factors can prevent the JDBC driver from reaching the server. To troubleshoot, we can try replacing a name like <em>localhost</em> or <em>MYSQL-1</em> with the explicit IP address, such as <em>127.0.0.1</em> or <em>192.168.6.66</em>.</p>
<p>If the connection works with an explicit IP address but not a hostname, DNS or hostname resolution issues are probably the culprit. In contrast, if the connection still fails even with the direct IP address, the root cause is more likely related to networking, firewall rules, incorrect ports, or the MySQL server configuration. Let&#8217;s look at each of these possibilities.</p>
<h2 id="bd-mysql-configuration-issues" data-id="mysql-configuration-issues">4. MySQL Configuration Issues</h2>
<div class="bd-anchor" id="mysql-configuration-issues"></div>
<p>If we can reach the actual hardware that hosts the server, but still experience issues, then MySQL might not be configured to accept network connections as expected. To diagnose this, we often check the options and values in the MySQL configuration file (<a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/linux/mysql-find-my-cnf-command-line"><em>my.cnf</em></a> or <em>my.ini</em>).</p>
<h3 id="bd-1-verify-the-bind-address-setting" data-id="1-verify-the-bind-address-setting">4.1. Verify the <em>bind-address</em> Setting</h3>
<div class="bd-anchor" id="1-verify-the-bind-address-setting"></div>
<p>One common MySQL option is <em>bind-address</em>.</p>
<p>Specifically, <strong>the value of <em>bind-address</em> determines the network interfaces that the server listens on</strong>:</p>
<pre><code class="language-ini">bind-address = 127.0.0.1</code></pre>
<p>In this case, we only expect connections from within <em>localhost</em>.</p>
<p>On the other hand, <strong>we can accept connections to any address or interface</strong>:</p>
<pre><code class="language-ini">bind-address = 0.0.0.0</code></pre>
<p>Checking whether <em>bind-address</em> is properly set with the expected value is critical for diagnosing connectivity issues.</p>
<h3 id="bd-2-disable-skip-networking" data-id="2-disable-skip-networking">4.2. Disable <em>skip-networking</em></h3>
<div class="bd-anchor" id="2-disable-skip-networking"></div>
<p>Some MySQL installations support the <em>skip-networking</em> configuration option, which disables TCP/IP connections entirely.</p>
<p>Thus, if the setting appears in the configuration file, we should comment it out:</p>
<pre><code class="language-ini"># skip-networking</code></pre>
<p>Although this is a rare cause, checking for such edge cases takes little time and can fix the issue. So, it&#8217;s worth exploring.</p>
<h3 id="bd-3-adjust-connection-timeout-settings" data-id="3-adjust-connection-timeout-settings">4.3. Adjust Connection Timeout Settings</h3>
<div class="bd-anchor" id="3-adjust-connection-timeout-settings"></div>
<p>To prevent resource waste, MySQL may close connections that remain idle for too long.</p>
<p>Several parameters control this behavior:</p>
<ul>
<li><em>wait_timeout</em>: maximum time to wait before closing an idle non-interactive connection</li>
<li><em>interactive_timeout</em>: maximum time to wait before closing an idle interactive connection (such as <em>mysql</em> CLI)</li>
<li><em>connect_timeout</em>: maximum time to wait for a client to complete the initial connection handshake</li>
</ul>
<p>As expected, increasing these values can reduce unexpected connection failures:</p>
<pre><code class="language-sql">wait_timeout = 28800
interactive_timeout = 28800</code></pre>
<p>In all cases, after changing the configuration, we should restart the MySQL service.</p>
<h2 id="bd-network-and-environment-issues" data-id="network-and-environment-issues">5. Network and Environment Issues</h2>
<div class="bd-anchor" id="network-and-environment-issues"></div>
<p>Even when MySQL is configured correctly, external factors may still prevent the application from connecting.</p>
<h3 id="bd-1-firewall-and-antivirus-or-proxy-restrictions" data-id="1-firewall-and-antivirus-or-proxy-restrictions">5.1. Firewall and Antivirus or Proxy Restrictions</h3>
<div class="bd-anchor" id="1-firewall-and-antivirus-or-proxy-restrictions"></div>
<p><strong>Network controls can block the traffic between the application and the MySQL server</strong>. For instance, a firewall rule, antivirus software, or a proxy can be the reason for connectivity issues.</p>
<p>To ensure the connection goes through, we can use tools such as <em>ping</em> or <em>telnet</em> for simple checks and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/linux/nmap-command-examples"><em>nmap</em></a> for port testing.</p>
<p>Regardless of the network setup, we should ensure that port <em>3306</em> is open and accessible through the whole chain. Naturally, if MySQL is configured to listen on another port, we should verify connectivity against that specific port instead of <em>3306</em>.</p>
<h3 id="bd-2-ipv6-versus-ipv4-issues" data-id="2-ipv6-versus-ipv4-issues">5.2. IPv6 Versus IPv4 Issues</h3>
<div class="bd-anchor" id="2-ipv6-versus-ipv4-issues"></div>
<p>Some environments resolve names to IPv6 addresses, such as <em>::1</em> for <em>localhost</em> instead of its IPv4 equivalent <em>127.0.0.1</em>. However, <strong>if the Java application environment and the MySQL server don&#8217;t use the same IP version, we can experience connection failures</strong>.</p>
<p>Common solutions:</p>
<ol>
<li>use <em>127.0.0.1</em> instead of <em>localhost</em></li>
<li>start Java with the JVM option <em>-Djava.net.preferIPv4Stack=true</em></li>
</ol>
<p>This forces the application to prefer IPv4 networking.</p>
<h2 id="bd-application-level-causes" data-id="application-level-causes">6. Application-Level Causes</h2>
<div class="bd-anchor" id="application-level-causes"></div>
<p>The way that an application is implemented also affects the establishment and stability of connections.</p>
<h3 id="bd-1-stale-connections-in-connection-pools" data-id="1-stale-connections-in-connection-pools">6.1. Stale Connections in Connection Pools</h3>
<div class="bd-anchor" id="1-stale-connections-in-connection-pools"></div>
<p><strong>Connection pools with stale entries are a common culprit for problematic MySQL connectivity.</strong> When the application tries to execute a query over a connection that the server has already closed, the JDBC driver reports a communication failure.</p>
<p>Usually, the solution is to switch over to a modern connection pool:</p>
<ul>
<li>HikariCP</li>
<li>Apache DBCP</li>
<li>C3P0</li>
</ul>
<p>The reason is that these implementations validate connections before returning them to the application.</p>
<p>In practice, <strong>connection pools may require explicit configuration to validate or recycle stale connections.</strong> For example, HikariCP uses several settings:</p>
<ul>
<li><em>maxLifetime</em></li>
<li><em>idleTimeout</em></li>
<li><em>keepaliveTime</em></li>
</ul>
<p>Apache DBCP and C3P0 support validation queries and connection test options. The exact specifics are outside the scope of this text.</p>
<h3 id="bd-2-connection-pool-exhaustion" data-id="2-connection-pool-exhaustion">6.2. Connection Pool Exhaustion</h3>
<div class="bd-anchor" id="2-connection-pool-exhaustion"></div>
<p><strong>Connection limits may necessitate closing old connections before attempting new ones.</strong> Otherwise, the MySQL server may reject the connection attempt.</p>
<p>In particular, ensuring the JDBC resources are closed correctly is essential:</p>
<pre><code class="language-java">try (Connection conn = dataSource.getConnection()) {
    // execute queries
}</code></pre>
<p>Connection pools can also limit and recycle connections.</p>
<p>Even if the application closes all JDBC resources, the database server may still reject new connections. For example, MySQL may reach the <em>max_connections</em> limit or experience temporary overload conditions. In such cases, the application could receive different exceptions, including errors such as <em>Too many connections</em>, instead of a generic <em>CommunicationsException</em>.</p>
<p>Furthermore, the configured pool size matters significantly. If the connection pool allows more concurrent connections than MySQL accepts, server connections can be depleted. Therefore, <strong>there should be a proper balance between the pool connection size and <em>max_connections</em> in MySQL.</strong></p>
<h3 id="bd-3-update-the-jdbc-driver" data-id="3-update-the-jdbc-driver">6.3. Update the JDBC Driver</h3>
<div class="bd-anchor" id="3-update-the-jdbc-driver"></div>
<p><strong>Older versions of the MySQL Connector/J driver may not work reliably with newer database versions</strong>.</p>
<p>Updating the dependency often resolves compatibility problems:</p>
<pre><code class="language-xml">&lt;dependency&gt;
  &lt;groupId&gt;mysql&lt;/groupId&gt;
  &lt;artifactId&gt;mysql-connector-java&lt;/artifactId&gt;
  &lt;version&gt;8.0.33&lt;/version&gt;
&lt;/dependency&gt;</code></pre>
<p>For Connector/J <em>8.x</em>, the Maven artifact may also appear under the newer coordinates <em>com.mysql:mysql-connector-j</em>, depending on the project setup and repository metadata.</p>
<p>As with other areas of computing, keeping the driver up to date ensures compatibility with the MySQL protocol and improves connection stability.</p>
<h2 id="bd-summary" data-id="summary">7. Summary</h2>
<div class="bd-anchor" id="summary"></div>
<p>In this article, we explored how to diagnose and fix the <em>Communications link failure</em> error in Java applications using MySQL.</p>
<p>This error occurs when the JDBC driver is unable to communicate with the database server. Common causes include server downtime, incorrect connection configuration, MySQL network settings, firewall restrictions, IPv6 resolution problems, stale pooled connections, and connection exhaustion within the application.</p>
<p>Carefully verifying connectivity, reviewing MySQL configuration, using reliable connection pools, updating JDBC drivers, and examining diagnostic logs can significantly reduce communication failures in Java applications that rely on MySQL.</p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/jdbc-connector-j-mysql-communications-link-failure">How to Fix the <em>Communications link failure</em> Error in Java Applications With MySQL</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.<Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/957397595/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/957397595/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/957397595/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f11%2fPersistence-Featured-Image-02-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/24/957397595/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/957397595/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/957397595/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/jdbc-connector-j-mysql-communications-link-failure#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/jdbc-connector-j-mysql-communications-link-failure/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</content:encoded>
					
					<wfw:commentRss>https://feeds.feedblitz.com/~/957397595/0/baeldung/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2024/11/Persistence-Featured-Image-02-150x150.jpg</webfeeds:featuredImage></item>
<item>
<feedburner:origLink>https://www.baeldung.com/java-quantum-resistant-ml-kem-ml-dsa</feedburner:origLink>
		<title>Quantum-Resistant ML-KEM and ML-DSA in Java</title>
		<link>https://feeds.feedblitz.com/~/957346181/0/baeldung</link>
					<comments>https://feeds.feedblitz.com/~/957346181/0/baeldung#respond</comments>
		
		<dc:creator><![CDATA[Burak Gökmen]]></dc:creator>
		<pubDate>Wed, 27 May 2026 05:34:07 +0000</pubDate>
				<category><![CDATA[Security]]></category>
		<category><![CDATA[>= Java 24]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/?p=203818</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-13-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="max-width:100% !important;height:auto !important;float: left; margin-right: 5px;" loading="lazy" /><p>Learn how to use quantum-resistant ML-KEM and ML-DSA in Java to securely establish a shared secret key.</p>
The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/957346181/0/baeldung">Quantum-Resistant ML-KEM and ML-DSA in Java</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/957346181/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/957346181/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f07%2fJava-Featured-13-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/24/957346181/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/957346181/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/957346181/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/java-quantum-resistant-ml-kem-ml-dsa#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/java-quantum-resistant-ml-kem-ml-dsa/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</description>
										<content:encoded><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-13-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="float: left; margin-right: 5px;" decoding="async" loading="lazy" srcset="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-13-1024x536.jpg 1024w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-13-300x157.jpg 300w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-13-768x402.jpg 768w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-13-100x52.jpg 100w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-13.jpg 1200w" sizes="auto, (max-width: 580px) 100vw, 580px" /><h2 id="bd-overview" data-id="overview">1. Overview</h2>
<div class="bd-anchor" id="overview"></div>
<p><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://nvlpubs.nist.gov/nistpubs/fips/nist.fips.203.pdf">ML-KEM</a> (Module-Lattice-Based Key Encapsulation Mechanism) is a post-quantum cryptographic key exchange algorithm that allows two parties to securely establish a shared secret key even against attacks from future quantum computers. <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://nvlpubs.nist.gov/nistpubs/fips/nist.fips.204.pdf">ML-DSA</a> (Module-Lattice-Based Digital Signature Algorithm), on the other hand, is a post-quantum digital signature algorithm that enables secure authentication and message integrity protection, resistant to attacks by quantum computers.</p>
<p>ML-KEM  and ML-DSA  weren’t part of the standard JDK (Java Development Kit) before Java 24. We had to use external libraries like <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-bouncy-castle">Bounty Castle</a> to perform cryptographic operations.</p>
<p>Both algorithms have been part of the native Java API since Java 24. The ML-KEM and ML-DSA implementations within the native Java API were proposed by <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://openjdk.org/jeps/496">JEP 496</a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://openjdk.org/jeps/497">JEP 497</a>, respectively.</p>
<p>In this tutorial, we’ll discuss the usage of quantum-resistant ML-KEM and ML-DSA in Java.</p>
<h2 id="bd-quantum-resistant-ml-kem" data-id="quantum-resistant-ml-kem">2. Quantum-Resistant ML-KEM</h2>
<div class="bd-anchor" id="quantum-resistant-ml-kem"></div>
<p><strong>A <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/cs/symmetric-cryptography#2-key-encapsulation-mechanism">key encapsulation mechanism</a> allows two parties, i.e., a sender and a receiver, to securely exchange a shared secret key over an insecure network</strong>. The mechanism consists of the following steps:</p>
<ul>
<li>The receiver creates a public/private key pair</li>
<li>The sender creates an encrypted shared secret key using the public key</li>
<li>The receiver decrypts the encrypted shared secret key using the private key</li>
</ul>
<p>Consequently, both parties obtain the same secret key. This shared key can be used with symmetric-key algorithms such as AES (Advanced Encryption Standard) and Blowfish.</p>
<p><strong>ML-KEM is based on the M-LWE (Module Learning with Errors) problem, which is computationally hard</strong>. The hardness of the M-LWE problem further depends on the hardness of certain computational problems in module lattices. Therefore, ML-KEM is resistant to both classical and quantum attacks.</p>
<h3 id="bd-1-generating-a-key-pair" data-id="1-generating-a-key-pair">2.1. Generating a Key Pair</h3>
<div class="bd-anchor" id="1-generating-a-key-pair"></div>
<p>Let&#8217;s start by generating an ML-KEM key pair at the receiver side:</p>
<pre><code class="language-java">KeyPairGenerator kpg = KeyPairGenerator.getInstance("ML-KEM");
kpg.initialize(NamedParameterSpec.ML_KEM_768);
KeyPair receiverKeyPair = kpg.generateKeyPair();
PrivateKey privateKey = receiverKeyPair.getPrivate();
PublicKey publicKey = receiverKeyPair.getPublic();</code></pre>
<p>Firstly, we get a <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://docs.oracle.com/en/java/javase/24/docs/api/java.base/java/security/KeyPairGenerator.html"><em>KeyPairGenerator</em></a> object for the ML-KEM algorithm. Then, we choose <em>ML_KEM_768</em> to set the security strength of the keys we’ll generate. <strong>JEP 496 adds support for ML-KEM-512, ML-KEM-768, and ML-KEM-1024</strong>. Indeed, <em>ML_KEM_768</em> is the default option, i.e., if we don’t initialize the <em>KeyPairGenerator</em> object with the <em>initialize()</em> method, the security level will still be <em>ML_KEM_768</em>.</p>
<p>It’s also possible to pass the security strength directly to <em>getInstance()</em>. We don’t have to call the <em>initialize()</em> method in this case, either:</p>
<pre><code class="language-java">KeyPairGenerator kpg = KeyPairGenerator.getInstance("ML-KEM-768");
KeyPair receiverKeyPair = kpg.generateKeyPair();</code></pre>
<p><em>receiverKeyPair</em>, of type <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://docs.oracle.com/javase/8/docs/api/java/security/KeyPair.html"><em>KeyPair</em></a>, holds a public and private key. We can access these keys using the <em>getPublic()</em> and <em>getPrivate()</em> member methods, respectively. It’s safe for the receiver to share the public key with the sender, but the private key must be kept secret.</p>
<h3 id="bd-2-generating-and-encapsulating-a-secret-shared-key" data-id="2-generating-and-encapsulating-a-secret-shared-key">2.2. Generating and Encapsulating a Secret Shared Key</h3>
<div class="bd-anchor" id="2-generating-and-encapsulating-a-secret-shared-key"></div>
<p>Once the sender receives the receiver’s public key, it generates and encapsulates a shared secret key that will be used by both the sender and the receiver:</p>
<pre><code class="language-java">KEM senderKem = KEM.getInstance("ML-KEM");
KEM.Encapsulator encapsulator = senderKem.newEncapsulator(publicKey);
KEM.Encapsulated encapsulated = encapsulator.encapsulate();
SecretKey senderSharedSecret = encapsulated.key();
byte[] ciphertext = encapsulated.encapsulation();</code></pre>
<p><strong><em>senderSharedSecret</em></strong><strong> is the actual secret key used in communication</strong>. But the sender doesn’t send it directly to the receiver. It sends it encrypted by encapsulating it with the receiver’s public key. <strong><em>ciphertext</em> corresponds to the encrypted version of <em>senderSharedSecret</em></strong>.</p>
<h3 id="bd-3-decapsulating-the-secret-shared-key" data-id="3-decapsulating-the-secret-shared-key">2.3. Decapsulating the Secret Shared Key</h3>
<div class="bd-anchor" id="3-decapsulating-the-secret-shared-key"></div>
<p>After the receiver receives the encrypted secret key, it decapsulates the secret key using the private key:</p>
<pre><code class="language-java">KEM receiverKem = KEM.getInstance("ML-KEM");
KEM.Decapsulator decapsulator = receiverKem.newDecapsulator(privateKey);
SecretKey receiverSharedSecret = decapsulator.decapsulate(ciphertext);</code></pre>
<p>The actual secret key, <em>receiverSharedSecret</em>, is extracted by decapsulating the received <em>ciphertext</em>. Consequently, <strong>both sides obtain the same shared secret key for symmetric encryption and decryption</strong>. We can check that both parties use the same key as follows:</p>
<pre><code class="language-java">boolean match = Arrays.equals(senderSharedSecret.getEncoded(), receiverSharedSecret.getEncoded());</code></pre>
<p>The comparison returns <em>true</em>. Therefore, we achieve secure communication over insecure networks. Besides, the method is resistant to quantum attacks, unlike RSA or Diffie-Hellman.</p>
<h2 id="bd-quantum-resistant-ml-dsa" data-id="quantum-resistant-ml-dsa">3. Quantum-Resistant ML-DSA</h2>
<div class="bd-anchor" id="quantum-resistant-ml-dsa"></div>
<p>A <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/cs/introduction-to-cryptography#1-digital-signature">digital signature algorithm</a> is a cryptographic method that verifies the authenticity and integrity of digital messages or documents. The algorithm consists of the following steps:</p>
<ul>
<li>The signer (sender) creates a public/private key pair</li>
<li>The signer creates a signature using the message and the private key</li>
<li>The verifier (receiver) verifies the signature using the received message and the public key</li>
</ul>
<p><strong>Like ML-KEM, ML-DSA is based on the M-LWE problem and on another computationally hard problem, M-SIS (Module Short Integer Solution)</strong>. Therefore, ML-DSA is resistant to both classical and quantum attacks.</p>
<h3 id="bd-1-generating-a-key-pair-1" data-id="1-generating-a-key-pair-1">3.1. Generating a Key Pair</h3>
<div class="bd-anchor" id="1-generating-a-key-pair-1"></div>
<p>Let&#8217;s start by generating an ML-DSA key pair on the signer side:</p>
<pre><code class="language-java">KeyPairGenerator kpg = KeyPairGenerator.getInstance("ML-DSA");
kpg.initialize(NamedParameterSpec.ML_DSA_65);
KeyPair kp = kpg.generateKeyPair();
PrivateKey privateKey = kp.getPrivate();
PublicKey publicKey = kp.getPublic();</code></pre>
<p>Firstly, we get a <em>KeyPairGenerator</em> object for the ML-DSA algorithm. Then, we choose <em>ML_DSA_65</em> to set the security strength of the keys we’ll generate. Indeed, <em>ML_DSA_65</em> is the default option. <strong>JEP 497 adds support for ML-DSA-44, ML-DSA-65, and ML-DSA-87</strong>.</p>
<p>Like ML-KEM, it’s possible to pass the security strength directly to <em>getInstance()</em>.</p>
<h3 id="bd-2-signing" data-id="2-signing">3.2. Signing</h3>
<div class="bd-anchor" id="2-signing"></div>
<p>Then, we, the signer, sign the message to be sent using the private key:</p>
<pre><code class="language-java">Signature signature = Signature.getInstance("ML-DSA");
String message = "This is a test message signed";
byte[] messageBytes = message.getBytes();
signature.initSign(privateKey);
signature.update(messageBytes);
byte[] sigBytes = signature.sign();</code></pre>
<p>Firstly, we get a <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://docs.oracle.com/javase/8/docs/api/java/security/Signature.html"><em>Signature</em></a> object that implements the ML-DSA algorithm. Then, we initialize this object for signing using the <em>initSign()</em> method. We pass the private key to this method. Then, we feed the message to the <em>Signature</em> object using the <em>update()</em> method. Finally, <strong>we create the digital signature using the <em>Signature</em> object’s <em>sign()</em> method</strong>.</p>
<h3 id="bd-3-verification" data-id="3-verification">3.3. Verification</h3>
<div class="bd-anchor" id="3-verification"></div>
<p>Then, having received the message and the signature, we, the verifier, verify the signature using the public key:</p>
<pre><code class="language-java">signature.initVerify(publicKey);
signature.update(messageBytes);
boolean isValid = signature.verify(sigBytes);</code></pre>
<p>This time, we initialize the <em>Signature</em> object for verification using the <em>initVerify()</em> method. Then we feed the message received from the signer to the <em>Signature</em> object using <em>update()</em>. Finally, <strong>we verify the signature using the <em>Signature</em> object’s <em>verify()</em> method</strong>. It returns <em>true</em>. Successful validation means that the signature was created using the corresponding private key.</p>
<h2 id="bd-conclusion" data-id="conclusion">4. Conclusion</h2>
<div class="bd-anchor" id="conclusion"></div>
<p>In this article, we discussed the usage of quantum-resistant ML-KEM and ML-DSA in Java. Firstly, we learned that these algorithms weren’t available in the standard JDK before Java 24.</p>
<p>Then, we saw the usage of the algorithms in Java. We simulated the secret key exchange between a receiver and a sender using ML-KEM. Similarly, we simulated message signing and signature verification between two parties using ML-DSA. As we saw in the examples, Java 24 provided ML-KEM implementations of the <em>KeyPairGenerator</em> and <em>KEM</em> classes, and ML-DSA implementations of the <em>KeyPairGenerator</em> and <em>Signature</em> classes.</p>
<p>As usual, the complete source code for the examples is available <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eugenp/tutorials/tree/master/core-java-modules/core-java-24/">over on GitHub</a>.</p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-quantum-resistant-ml-kem-ml-dsa">Quantum-Resistant ML-KEM and ML-DSA in Java</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.<Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/957346181/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/957346181/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/957346181/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f07%2fJava-Featured-13-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/24/957346181/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/957346181/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/957346181/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/java-quantum-resistant-ml-kem-ml-dsa#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/java-quantum-resistant-ml-kem-ml-dsa/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</content:encoded>
					
					<wfw:commentRss>https://feeds.feedblitz.com/~/957346181/0/baeldung/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-13-150x150.jpg</webfeeds:featuredImage></item>
<item>
<feedburner:origLink>https://www.baeldung.com/java-http-basic-authentication-httpservletrequest</feedburner:origLink>
		<title>Getting HTTP Basic Authentication from HttpServletRequest</title>
		<link>https://feeds.feedblitz.com/~/957111833/0/baeldung</link>
					<comments>https://feeds.feedblitz.com/~/957111833/0/baeldung#respond</comments>
		
		<dc:creator><![CDATA[Hamid Reza Sharifi]]></dc:creator>
		<pubDate>Sat, 23 May 2026 23:11:28 +0000</pubDate>
				<category><![CDATA[Security]]></category>
		<category><![CDATA[Authentication]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/?p=203787</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-11-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="max-width:100% !important;height:auto !important;float: left; margin-right: 5px;" loading="lazy" /><p>Learn how the HTTP Basic Authentication works and how to extract credentials from a HTTP request in a Spring-based application.</p>
The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/957111833/0/baeldung">Getting HTTP Basic Authentication from HttpServletRequest</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/957111833/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/957111833/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f07%2fJava-Featured-11-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/24/957111833/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/957111833/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/957111833/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/java-http-basic-authentication-httpservletrequest#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/java-http-basic-authentication-httpservletrequest/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</description>
										<content:encoded><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-11-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="float: left; margin-right: 5px;" decoding="async" loading="lazy" srcset="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-11-1024x536.jpg 1024w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-11-300x157.jpg 300w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-11-768x402.jpg 768w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-11-100x52.jpg 100w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-11.jpg 1200w" sizes="auto, (max-width: 580px) 100vw, 580px" /><h2 id="bd-introduction" data-id="introduction">1. Introduction</h2><div class="bd-anchor" id="introduction"></div>
<p><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/cs/digest-vs-basic-authentication#introduction-to-basic-authentication">Basic Authentication</a> is the most common security mechanism for HTTP services. Its popularity stems from simplicity and ease of implementation. In this tutorial, we’ll explore how HTTP Basic Authentication works and how to extract credentials, specifically the password, from an incoming HTTP request in a Spring-based application.</p>
<h2 id="bd-http-basic-authentication" data-id="http-basic-authentication">2. HTTP Basic Authentication</h2><div class="bd-anchor" id="http-basic-authentication"></div>
<p>HTTP Basic Authentication is a simple authentication scheme where the client sends credentials in the HTTP request header. <strong>The client sends the request with the credentials included in the <em>Authorization</em> header, and the server then validates them</strong>. The header format is:</p>
<pre><code class="language-plaintext">Authorization: Basic &lt;base64(username:password)&gt;</code></pre>
<p>The username and password are combined into a single string separated by a colon. This string is then encoded using <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-base64-encode-and-decode">Base64</a>. For example, if the username is <em>admin</em> and the password is <em>secret</em>, the combined string is <em>admin:secret</em>. The Base64 encoded version of this string is <em>YWRtaW46c2VjcmV0</em>. The final HTTP header looks like this:</p>
<pre><code class="language-plaintext">Authorization: Basic YWRtaW46c2VjcmV0</code></pre>
<p><strong>It’s important to note that Base64 is encoding, not encryption</strong>. It is easily reversible, which is why HTTPS is strictly required when using Basic Authentication.</p>
<h2 id="bd-maven-dependency" data-id="maven-dependency">3. Maven Dependency</h2><div class="bd-anchor" id="maven-dependency"></div>
<p>Let’s start by importing the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web"><em>spring-boot-starter-web</em></a> dependency to our <em>pom.xml</em>:</p>
<pre><code class="language-xml">&lt;dependency&gt;
    &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
    &lt;artifactId&gt;spring-boot-starter-web&lt;/artifactId&gt;
    &lt;version&gt;3.3.2&lt;/version&gt;
&lt;/dependency&gt;</code></pre>
<h2 id="bd-retrieving-credentials-from-the-authorization-header" data-id="retrieving-credentials-from-the-authorization-header">4. Retrieving Credentials from the Authorization Header</h2><div class="bd-anchor" id="retrieving-credentials-from-the-authorization-header"></div>
<p>In this section, we describe the step-by-step process of retrieving the raw username and password from the incoming HTTP request.</p>
<h3 id="bd-1-manual-extraction-from-the-http-request" data-id="1-manual-extraction-from-the-http-request">4.1. Manual Extraction from the HTTP Request</h3><div class="bd-anchor" id="1-manual-extraction-from-the-http-request"></div>
<p><strong>The most direct way to get the password is to read the <em>Authorization</em> header directly from the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-servlet-request-set-parameter#1-httpservletrequest"><em>HttpServletRequest</em></a> and decode it</strong>. Let’s create a <em>BasicAuthExtractor</em> class and a utility method to do this:</p>
<pre><code class="language-java">public class BasicAuthExtractor {
    public static String[] extractCredentials(String authHeader) {
        if (authHeader != null &amp;&amp; authHeader.startsWith("Basic ")) {
            String base64Credentials = authHeader.substring("Basic ".length()).trim();
            byte[] credDecoded = Base64.getDecoder().decode(base64Credentials);
            String credentials = new String(credDecoded, StandardCharsets.UTF_8);
            final String[] values = credentials.split(":", 2);
            if (values.length == 2) {
                return values;
            }
        }
        return null;
    }
}</code></pre>
<p>The <em>extractCredentials()</em> method first checks whether the <em>Authorization</em> header exists. It also verifies that the header starts with the &#8220;<em>Basic</em> &#8221; prefix. Next, it removes the prefix from the header value. Then, it decodes the remaining Base64-encoded string using <em>Base64.getDecoder()</em>. After decoding, the bytes are converted into a UTF-8 string. The resulting string should follow the <em>username:password</em> format. Finally, the method splits the string on the first colon. It returns a two-element array containing the username and password. If the header is invalid or incorrectly formatted, the method returns <em>null</em>. We can easily use the <em>extractCredentials()</em> method inside a <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-controllers"><em>RestController</em></a>:</p>
<pre><code class="language-java">@GetMapping("/extract")
public ResponseEntity&lt;String&gt; extract(@RequestHeader("Authorization") String authHeader) {
    String[] credentials = BasicAuthExtractor.extractCredentials(authHeader);
    if (credentials != null) {
        String username = credentials[0];
        String password = credentials[1];
        return ResponseEntity.ok("Extracted Username: " + username +
          " Extracted Password: " + password);
    }
    return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}</code></pre>
<p>We use the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-rest-http-headers"><em>@RequestHeader</em></a> annotation for getting the <em>Authorization</em> header value from the incoming request.</p>
<h3 id="bd-2-custom-servlet-filter" data-id="2-custom-servlet-filter">4.2. Custom Servlet Filter</h3><div class="bd-anchor" id="2-custom-servlet-filter"></div>
<p><strong>Another way is to create a custom <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-boot-add-filter">Filter</a> that reads the header, extracts the password, and stores it in a request attribute for later use</strong>. Let&#8217;s create an <em>AuthFilter</em> class to do this:</p>
<pre><code class="language-java">@Component
public class AuthFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
        FilterChain filterChain) throws ServletException, IOException {
        String authHeader = request.getHeader("Authorization");
        String[] credentials = BasicAuthExtractor.extractCredentials(authHeader);
        if (credentials != null) {
            request.setAttribute("rawPassword", credentials[1]);
        }
        filterChain.doFilter(request, response);
    }
}</code></pre>
<p>This filter intercepts every incoming HTTP request and reads the <em>Authorization</em> header. Then, it extracts the password using the <em>extractCredentials()</em> method and stores it as a request attribute before continuing the filter chain. Later, in our service or controller, we can retrieve it safely:</p>
<pre><code class="language-java">String rawPassword = (String) request.getAttribute("rawPassword");</code></pre>
<h2 id="bd-testing-the-basicauthextractor" data-id="testing-the-basicauthextractor">5. Testing the BasicAuthExtractor</h2><div class="bd-anchor" id="testing-the-basicauthextractor"></div>
<p>Let’s start by testing the core extraction logic. First, we need to ensure it correctly handles a valid Basic Authentication header and returns the expected credentials array:</p>
<pre><code class="language-java">@Test
void givenValidHeader_whenExtract_thenReturnCredentialsArray() {
    // Given
    String header = encodeCredentials("admin", "secret");
    // When
    String[] credentials = BasicAuthExtractor.extractCredentials(header);
    // Then
    assertThat(credentials).isNotNull();
    assertThat(credentials).hasSize(2);
    assertThat(credentials[0]).isEqualTo("admin");
    assertThat(credentials[1]).isEqualTo("secret");
}</code></pre>
<p>To keep our tests clean and readable, we utilize a helper method that handles the Base64 encoding and header formatting for us:</p>
<pre><code class="language-java">private String encodeCredentials(String username, String password) {
    String credentials = username + ":" + password;
    return "Basic " + Base64.getEncoder().encodeToString(credentials.getBytes());
}</code></pre>
<p>Additionally, we need to verify that the method safely returns <em>null</em> for invalid headers, such as those missing the <em>Basic</em> prefix or using a different authentication scheme:</p>
<pre><code class="language-java">@Test
void givenMissingBasicPrefix_whenExtract_thenReturnNull() {
    // Given
    String header = "Bearer some-token";
    // When
    String[] credentials = BasicAuthExtractor.extractCredentials(header);
    // Then
    assertThat(credentials).isNull();
}</code></pre>
<h2 id="bd-conclusion" data-id="conclusion">6. Conclusion</h2><div class="bd-anchor" id="conclusion"></div>
<p>In this article, we learned how HTTP Basic Authentication encodes credentials and how to decode them manually using Java’s Base64 utility. We also explored how to capture the raw password using a custom filter. While extracting passwords is sometimes necessary for legacy integrations, it should be avoided whenever possible due to the inherent security risks. As always, the source code is available <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eugenp/tutorials/tree/master/spring-boot-modules/spring-boot-basic-customization-3">over on GitHub</a>.</p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-http-basic-authentication-httpservletrequest">Getting HTTP Basic Authentication from HttpServletRequest</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.<Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/957111833/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/957111833/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/957111833/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f07%2fJava-Featured-11-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/24/957111833/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/957111833/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/957111833/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/java-http-basic-authentication-httpservletrequest#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/java-http-basic-authentication-httpservletrequest/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</content:encoded>
					
					<wfw:commentRss>https://feeds.feedblitz.com/~/957111833/0/baeldung/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-11-150x150.jpg</webfeeds:featuredImage></item>
<item>
<feedburner:origLink>https://www.baeldung.com/java-weekly-647</feedburner:origLink>
		<title>Java Weekly, Issue 647</title>
		<link>https://feeds.feedblitz.com/~/956980241/0/baeldung</link>
					<comments>https://feeds.feedblitz.com/~/956980241/0/baeldung#respond</comments>
		
		<dc:creator><![CDATA[baeldung]]></dc:creator>
		<pubDate>Fri, 22 May 2026 13:41:58 +0000</pubDate>
				<category><![CDATA[Weekly Review]]></category>
		<category><![CDATA[no-ads]]></category>
		<category><![CDATA[no-after-post]]></category>
		<category><![CDATA[no-before-post]]></category>
		<category><![CDATA[no-optins]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/?p=203802</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2016/10/social-Weekly-Reviews-4.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="max-width:100% !important;height:auto !important;float: left; margin-right: 5px;" loading="lazy" /><p>A look at the last few Java releases and feedback loops for agents. A solid week.</p>
The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/956980241/0/baeldung">Java Weekly, Issue 647</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/956980241/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/956980241/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2016%2f10%2fsocial-Weekly-Reviews-4.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/24/956980241/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/956980241/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/956980241/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/java-weekly-647#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/java-weekly-647/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</description>
										<content:encoded><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2016/10/social-Weekly-Reviews-4.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="float: left; margin-right: 5px;" decoding="async" loading="lazy" srcset="https://www.baeldung.com/wp-content/uploads/2016/10/social-Weekly-Reviews-4.jpg 952w, https://www.baeldung.com/wp-content/uploads/2016/10/social-Weekly-Reviews-4-300x157.jpg 300w, https://www.baeldung.com/wp-content/uploads/2016/10/social-Weekly-Reviews-4-768x402.jpg 768w" sizes="auto, (max-width: 580px) 100vw, 580px" /><h2 style="text-align: left;" id="bd-spring-and-java" data-id="spring-and-java">1.<strong> Spring and Java</strong></h2>
<div class="bd-anchor" id="spring-and-java"></div>
<p><strong><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://inside.java/2026/05/19/javaone-better-jdk26/">&gt;&gt; Java 26: Better Language, Better APIs, Better Runtime</a></strong> [<span style="color: #993300;">inside.java</span>]</p>
<p>Nicolai walks through the Java platform&#8217;s recent evolution, from JDK 21 through 25 and into JDK 26. Good way to catch up.</p>
<h4><strong>Also worth reading:</strong></h4>
<ul>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://inside.java/2026/05/18/javaone-caching-agentic-ai/" target="_blank" rel="noopener"><strong>Caching for Agentic Java Systems: Internal, Distributed, and Semantic</strong></a> [<span style="color: #800000;">inside.java</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://foojay.io/today/dont-panic-the-thymeleaf-template-injection-that-only-hurts-if-you-let-it-cve-2026-40478/" target="_blank" rel="noopener"><strong>Don&#8217;t Panic: The Thymeleaf Template Injection That Only Hurts If You Let It (CVE-2026-40478)</strong></a> [<span style="color: #800000;">foojay.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://foojay.io/today/from-zero-really-zero-to-opentelemetry/" target="_blank" rel="noopener"><strong>From Zero (Really Zero) to OpenTelemetry</strong></a> [<span style="color: #800000;">foojay.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://blog.jetbrains.com/kotlin/2026/05/built-for-productivity-what-the-data-shows-about-kotlin/" target="_blank" rel="noopener"><strong>Built for Productivity: What the Data Finally Shows About Kotlin</strong></a> [<span style="color: #800000;">jetbrains.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://foojay.io/today/all-azul-zulu-container-images-explained-ca-sa-and-chainguard/" target="_blank" rel="noopener"><strong>All Azul Zulu Container Images Explained: CA, SA, and Chainguard</strong></a> [<span style="color: #800000;">foojay.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://foojay.io/today/boxlang-ai-series-complete-guide-to-building-ai-agents/" target="_blank" rel="noopener"><strong>BoxLang AI Series: Complete Guide to Building AI Agents</strong></a> [<span style="color: #800000;">foojay.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://inside.java/2026/05/20/quality-heads-up/" target="_blank" rel="noopener"><strong>Quality Outreach Heads-up &#8211; JDK 27: Numeric Fields in JSON Thread Dumps</strong></a> [<span style="color: #800000;">inside.java</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://inside.java/2026/05/17/jep531-target-jdk27/" target="_blank" rel="noopener"><strong>JEP targeted to JDK 27: 531: Lazy Constants (3rd Preview)</strong></a> [<span style="color: #800000;">inside.java</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://inside.java/2026/05/17/quality-heads-up/" target="_blank" rel="noopener"><strong>Quality Outreach Heads-up &#8211; JDK 27: Post-Quantum Hybrid Key Exchange for TLS 1.3</strong></a> [<span style="color: #800000;">inside.java</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://inside.java/2026/05/16/javaone-jcmd-jvm-analysis/" target="_blank" rel="noopener"><strong>Post-Mortem JVM Crash Analysis with jcmd</strong></a> [<span style="color: #800000;">inside.java</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://inside.java/2026/05/15/quality-heads-up/" target="_blank" rel="noopener"><strong>Quality Outreach Heads-up &#8211; JDK 26: Warnings About Final Field Mutation</strong></a> [<span style="color: #800000;">inside.java</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.petrikainulainen.net/programming/unit-testing/getting-started-with-mockk-the-setup/" target="_blank" rel="noopener"><strong>Getting Started With MockK: The Setup</strong></a> [<span style="color: #800000;">petrikainulainen.net</span>]</li>
</ul>
<h4><strong>Webinars and presentations:</strong></h4>
<ul>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://spring.io/blog/2026/05/19/spring-office-hours-podcast-S5E16" target="_blank" rel="noopener"><strong>Spring Office Hours Podcast: S5E16 &#8211; May Release Train Shift &amp; What&#8217;s Coming in Spring Boot 4.1</strong></a> [<span style="color: #800000;">spring.io</span>]</li>
</ul>
<h4><strong>Time to upgrade:</strong></h4>
<ul>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://foojay.io/today/glassfish-8-0-2-released/" target="_blank" rel="noopener"><strong>GlassFish 8.0.2 Released: With important security fixes, other improvements, and commercial support</strong></a> [<span style="color: #800000;">foojay.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://foojay.io/today/boxlang-v1-13-0-compatibility-concurrency-and-formatter-maturity/" target="_blank" rel="noopener"><strong>BoxLang v1.13.0: Compatibility, Concurrency, and Formatter Maturity</strong></a> [<span style="color: #800000;">foojay.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://blog.jetbrains.com/idea/2026/05/intellij-idea-2026-1-2/" target="_blank" rel="noopener"><strong>IntelliJ IDEA 2026.1.2 Is Out!</strong></a> [<span style="color: #800000;">jetbrains.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://blog.jetbrains.com/kotlin/2026/05/compose-multiplatform-1-11-0/" target="_blank" rel="noopener"><strong>Compose Multiplatform 1.11.0 Is Now Available</strong></a> [<span style="color: #800000;">jetbrains.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/quarkusio/quarkus/releases/tag/3.36.0" target="_blank" rel="noopener"><strong>Quarkus 3.36.0</strong></a>, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/quarkusio/quarkus/releases/tag/3.35.4" target="_blank" rel="noopener"><strong>Quarkus 3.35.4</strong></a>, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/quarkusio/quarkus/releases/tag/3.33.2" target="_blank" rel="noopener"><strong>Quarkus 3.33.2</strong></a>, and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/quarkusio/quarkus/releases/tag/3.27.4" target="_blank" rel="noopener"><strong>Quarkus 3.27.4</strong></a> [<span style="color: #800000;">github.com/quarkusio</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/Netflix/zuul/releases/tag/v3.6.8" target="_blank" rel="noopener"><strong>Zuul v3.6.8</strong></a>, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/Netflix/zuul/releases/tag/v3.6.7" target="_blank" rel="noopener"><strong>Zuul v3.6.7</strong></a>, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/Netflix/zuul/releases/tag/v3.6.6" target="_blank" rel="noopener"><strong>Zuul v3.6.6</strong></a>, and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/Netflix/zuul/releases/tag/v3.6.5" target="_blank" rel="noopener"><strong>Zuul v3.6.5</strong></a> [<span style="color: #800000;">github.com/Netflix</span>]</li>
</ul>
<h2 style="text-align: left;" id="bd-technical-amp-musings" data-id="technical-amp-musings">2.<strong> Technical &amp; Musings</strong></h2>
<div class="bd-anchor" id="technical-amp-musings"></div>
<p><strong><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://martinfowler.com/articles/sensors-for-coding-agents.html">&gt;&gt; Maintainability sensors for coding agents</a></strong> [<span style="color: #993300;">martinfowler.com</span>]</p>
<p>A practical look at keeping AI-assisted codebases maintainable, using &#8220;sensors&#8221; such as linting, dependency rules, coupling data, and modularity reviews to give coding agents fast feedback. Good stuff.</p>
<h4><strong>Also worth reading:</strong></h4>
<ul>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://blog.scottlogic.com/2026/05/14/the-human-bottleneck.html" target="_blank" rel="noopener"><strong>The Human Bottleneck</strong></a> [<span style="color: #800000;">scottlogic.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://netflixtechblog.medium.com/the-evolution-of-cassandra-data-movement-at-netflix-6e13329c80a1" target="_blank" rel="noopener"><strong>The Evolution of Cassandra Data Movement at Netflix</strong></a> [<span style="color: #800000;">netflixtechblog.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://blog.frankel.ch/seasons-time-lapse/1/" target="_blank" rel="noopener"><strong>Seasons time-lapse &#8211; the foundations</strong></a> [<span style="color: #800000;">frankel.ch</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://martinfowler.com/bliki/InterrogatoryLLM.html" target="_blank" rel="noopener"><strong>Interrogatory LLM</strong></a> [<span style="color: #800000;">martinfowler.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://blog.scottlogic.com/2026/05/18/talking-about-tech.html" target="_blank" rel="noopener"><strong>Finding Your Voice: A Guide to Technical Communication</strong></a> [<span style="color: #800000;">scottlogic.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://blog.scottlogic.com/2026/05/13/alternative-coding-agents-pi.html" target="_blank" rel="noopener"><strong>Alternative Coding Agents: Pi</strong></a> [<span style="color: #800000;">scottlogic.com</span>]</li>
</ul>
<h2 style="text-align: left;" id="bd-pick-of-the-week" data-id="pick-of-the-week">3.<strong> Pick of the Week</strong></h2>
<div class="bd-anchor" id="pick-of-the-week"></div>
<p><strong><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://markmanson.net/the-backwards-law">&gt;&gt; The Backwards Law—Why the Best Things in Life Must Be Let Go</a></strong> [<span style="color: #993300;">markmanson.net</span>]</p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-weekly-647">Java Weekly, Issue 647</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.<Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/956980241/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/956980241/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/956980241/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2016%2f10%2fsocial-Weekly-Reviews-4.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/24/956980241/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/956980241/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/956980241/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/java-weekly-647#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/java-weekly-647/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</content:encoded>
					
					<wfw:commentRss>https://feeds.feedblitz.com/~/956980241/0/baeldung/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2016/10/social-Weekly-Reviews-4-150x150.jpg</webfeeds:featuredImage></item>
</channel></rss>

