<?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>Sat, 27 Jun 2026 18:53:49 +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-kafka-commitfailedexception</feedburner:origLink>
		<title>Understanding and Avoiding CommitFailedException in Kafka</title>
		<link>https://feeds.feedblitz.com/~/958534139/0/baeldung</link>
					<comments>https://feeds.feedblitz.com/~/958534139/0/baeldung#respond</comments>
		
		<dc:creator><![CDATA[Saikat Chakraborty]]></dc:creator>
		<pubDate>Sat, 27 Jun 2026 18:53:49 +0000</pubDate>
				<category><![CDATA[Data]]></category>
		<category><![CDATA[Kafka]]></category>
		<category><![CDATA[Messaging]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/java-kafka-commitfailedexception</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/11/Data-Featured-Image-05-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="A robot with a vacuum hand that is pulling in various bits of data and outputing graphs and charts with its other hand. On the far left are the words &quot;Data on Baeldung&quot;." style="max-width:100% !important;height:auto !important;float: left; margin-right: 5px;" fetchpriority="high" /><p>Learn about what causes the CommitFailedException when working with Kafka in Java and some approaches to help avoid it occurring.</p>
The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/958534139/0/baeldung">Understanding and Avoiding CommitFailedException in Kafka</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/958534139/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/958534139/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f11%2fData-Featured-Image-05-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/958534139/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/958534139/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/958534139/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-kafka-commitfailedexception#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-kafka-commitfailedexception/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/Data-Featured-Image-05-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="A robot with a vacuum hand that is pulling in various bits of data and outputing graphs and charts with its other hand. On the far left are the words &quot;Data on Baeldung&quot;." style="float: left; margin-right: 5px;" decoding="async" srcset="https://www.baeldung.com/wp-content/uploads/2024/11/Data-Featured-Image-05-1024x536.jpg 1024w, https://www.baeldung.com/wp-content/uploads/2024/11/Data-Featured-Image-05-300x157.jpg 300w, https://www.baeldung.com/wp-content/uploads/2024/11/Data-Featured-Image-05-768x402.jpg 768w, https://www.baeldung.com/wp-content/uploads/2024/11/Data-Featured-Image-05-100x52.jpg 100w, https://www.baeldung.com/wp-content/uploads/2024/11/Data-Featured-Image-05-600x314.jpg 600w, https://www.baeldung.com/wp-content/uploads/2024/11/Data-Featured-Image-05.jpg 1200w" sizes="(max-width: 580px) 100vw, 580px" /><h2 id="bd-overview" data-id="overview">1. Overview</h2>
<div class="bd-anchor" id="overview"></div>
<p>Kafka provides a high degree of flexibility in committing offsets for processed messages in the consumer. It includes synchronous, asynchronous, and auto-commit options.</p>
<p>While this reduces the complexity of the commit process, unexpected errors can still occur.</p>
<p>In this tutorial, we’ll learn about a commit offset-related failure in a <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/apache-kafka">Kafka</a> consumer application. We&#8217;ll debug the root cause of the resulting <em>CommitFailedException</em> and implement a few solutions to avoid it where possible.</p>
<h2 id="bd-implementing-a-consumer-service-to-demonstrate-the-issue" data-id="implementing-a-consumer-service-to-demonstrate-the-issue">2. Implementing a Consumer Service to Demonstrate the Issue</h2>
<div class="bd-anchor" id="implementing-a-consumer-service-to-demonstrate-the-issue"></div>
<p>Let&#8217;s build a simple <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/kafka-create-listener-consumer-api">consumer</a> application that processes records sequentially.</p>
<p>We&#8217;ll implement the <em>consume</em> method in the <em>KafkaConsumerService </em>class:</p>
<pre><code class="language-java">public class KafkaConsumerService {
    private final KafkaConsumer&lt;String, String&gt; consumer;
    private final AtomicBoolean running = new AtomicBoolean(true);
    public void consume() {
        try {
            while (running.get()) {
                ConsumerRecords&lt;String, String&gt; records = consumer.poll(Duration.ofMillis(100));
                if (records.isEmpty()) {
                    continue;
                }
                records.forEach(this::simulateDBCall);
                consumer.commitSync();
            }
        } catch (WakeupException ex) {
            if (running.get()) {
                log.error("Error in the Kafka Consumer with exception", ex);
                throw ex;
            }
        } finally {
            consumer.close();
        }
    }
}</code></pre>
<p>In the code above, we process records sequentially and then commit the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/kafka-consumer-offset">offset</a>.</p>
<p>To simulate a database call, we&#8217;ll add a delay in the above <em>simulateDBCall </em>method:</p>
<pre><code class="language-java">private void simulateDBCall(ConsumerRecord&lt;String, String&gt; record) {
    try {
        log.info("Simulating a DB call - record key {} value {}", record.key(), record.value());
        Thread.sleep(150L);
    } catch (InterruptedException ex) {
        Thread.currentThread()
          .interrupt();
        throw new RuntimeException(ex);
    }
}</code></pre>
<p>The delay will cause the consumer to wait around 150 ms before the next record.</p>
<h2 id="bd-testing-the-service" data-id="testing-the-service">3. Testing the Service</h2>
<div class="bd-anchor" id="testing-the-service"></div>
<p>Next, let&#8217;s test the consumer service. Note that we use <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/docker-test-containers">Testcontainers</a> in our test harness to provide a local Kafka broker for testing; full details are available in the supporting GitHub repository linked at the end of this article.</p>
<p>First, we&#8217;ll include the consumer-related configs in the test class:</p>
<pre><code class="language-java">private static Properties getConsumerConfig() {
    Properties consumerProperties = new Properties();
    consumerProperties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, KAFKA_CONTAINER.getBootstrapServers());
    consumerProperties.put(ConsumerConfig.GROUP_ID_CONFIG, "consumer-app");
    consumerProperties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
    consumerProperties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
    consumerProperties.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest");
    consumerProperties.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "false");
    consumerProperties.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, 1);
    consumerProperties.put(ConsumerConfig.MAX_POLL_INTERVAL_MS_CONFIG, 50);
    
    return consumerProperties;
}</code></pre>
<p>In the config above, we&#8217;re polling for one record with a maximum poll interval of 50 ms and manually committing the offset.</p>
<p>We&#8217;ll implement a test case by producing, consuming a message, and verifying the offset:</p>
<pre><code class="language-java">@Test
void givenProducerMessageIsSent_whenConsumerIsRunning_thenConsumerOffsetIsCommitted() {
    KafkaConsumerService kafkaConsumerService = new KafkaConsumerService(getConsumerConfig(), "test-topic");
    Thread th = new Thread(kafkaConsumerService::consume);
    th.start();
    try (KafkaProducer&lt;String, String&gt; producer = new KafkaProducer&lt;&gt;(getProducerConfig())) {
        producer.send(new ProducerRecord&lt;&gt;("test-topic", "x1", "test"));
        producer.flush();
    }
    Awaitility.await()
      .atMost(30, TimeUnit.SECONDS)
      .pollInterval(2, TimeUnit.SECONDS)
      .untilAsserted(() -&gt; {
          TopicPartition topicPartition = new TopicPartition("test-topic", 0);
          Map&lt;TopicPartition, OffsetAndMetadata&gt; committedOffsets;
          try (AdminClient adminClient = AdminClient.create(getAdminProps())) {
              ListConsumerGroupOffsetsResult result = adminClient.listConsumerGroupOffsets("consumer-app");
              committedOffsets = result.partitionsToOffsetAndMetadata()
                .get();
          }
          assertNotNull(committedOffsets);
          assertNotNull(committedOffsets.get(topicPartition));
          assertEquals(1L, committedOffsets.get(topicPartition)
            .offset());
      });
    kafkaConsumerService.shutdown();
}</code></pre>
<p>Instead of offset committed, we&#8217;ll see the following error log with the test case also failing:</p>
<pre><code class="language-java">org.apache.kafka.clients.consumer.CommitFailedException: Offset commit cannot be completed since the consumer is not part of an active group for auto partition assignment; it is likely that the consumer was kicked out of the group.
	at org.apache.kafka.clients.consumer.internals.ConsumerCoordinator.sendOffsetCommitRequest(ConsumerCoordinator.java:1280)
	...
	at org.apache.kafka.clients.consumer.KafkaConsumer.commitSync(KafkaConsumer.java:918)
	at com.baeldung.kafka.commitfailure.sequential.KafkaConsumerService.consume(KafkaConsumerService.java:34)
</code></pre>
<p>From the log above, it&#8217;s obvious that the <em>CommitFailedException</em> was thrown while committing the offset in the <em>consume</em> method. Also, it says that the consumer is no longer part of the group. Kafka gives some hints in the class <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://kafka.apache.org/39/javadoc/org/apache/kafka/clients/consumer/CommitFailedException.html">documentation</a> on the possible reason.</p>
<h2 id="bd-root-cause-of-the-commitfailedexception" data-id="root-cause-of-the-commitfailedexception">4. Root Cause of the <em>CommitFailedException</em></h2>
<div class="bd-anchor" id="root-cause-of-the-commitfailedexception"></div>
<p>We can monitor and debug the root cause with multiple options, including container logs, CLI, and Kafka UI tools.</p>
<p>First, we&#8217;ll check the Kafka container logs for the error:</p>
<pre><code class="language-bash">[2026-06-08 16:53:05,349] INFO [GroupCoordinator 1]: Preparing to rebalance group consumer-app in state PreparingRebalance with old generation 1 (__consumer_offsets-0) (reason: Removing member consumer-consumer-app-1-3792090b-be55-4e19-bdbd-a3c66b968168 on LeaveGroup; client reason: consumer poll timeout has expired.)
...
[2026-06-08 16:53:05,354] INFO [GroupCoordinator 1]: Member MemberMetadata(memberId=consumer-consumer-app-1-3792090b-be55-4e19-bdbd-a3c66b968168, groupInstanceId=None, clientId=consumer-consumer-app-1, clientHost=/172.17.0.1, sessionTimeoutMs=45000, rebalanceTimeoutMs=50, supportedProtocols=List(range, cooperative-sticky)) has left group consumer-app through explicit `LeaveGroup`; client reason: consumer poll timeout has expired...</code></pre>
<p>We&#8217;ll also confirm the consumer group&#8217;s state using the <em>kafka-consumer-groups</em> command:</p>
<pre><code class="language-bash">sh-4.4$ /usr/bin/kafka-consumer-groups --bootstrap-server localhost:9092 --describe --group consumer-app --state
Consumer group 'consumer-app' has no active members.
GROUP                     COORDINATOR (ID)          ASSIGNMENT-STRATEGY  STATE           #MEMBERS
consumer-app              8db993586dab:9092  (1)                         Empty           0</code></pre>
<p>Based on the above logs and status, we can see that <strong>the consumer was removed due to a client-specific error: <em>consumer poll timeout has expired</em>.</strong></p>
<p>Kafka has multiple ways to check if the consumer is healthy and running. This includes a background heartbeat check, which should run before the session timeout.</p>
<p>Additionally,<strong> Kafka expects the consumer to poll once within the specified <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://kafka.apache.org/39/configuration/consumer-configs/#consumerconfigs_max.poll.interval.ms">max poll interval time period</a>.</strong> If the consumer does not poll for any reason, <strong>it will be considered dead, and the partition will be reassigned to another consumer</strong>.</p>
<p>By further debugging and re-verifying the application logs, we observe that the record processing was clearly taking more time, and <strong>the consumer could not call the poll method within the specified <em>MAX_POLL_INTERVAL_MS_CONFIG</em> time period</strong>. Consequently, the consumer was removed by the Kafka group coordinator.</p>
<p>So, any further attempt to commit an offset failed with the error. It makes sense as the consumer is no longer the partition owner, and a commit can corrupt the offset state.</p>
<p>We&#8217;ll explore a few practical ways to prevent the error.</p>
<h2 id="bd-approaches-to-avoid-the-problem" data-id="approaches-to-avoid-the-problem">5. Approaches to Avoid the Problem</h2>
<div class="bd-anchor" id="approaches-to-avoid-the-problem"></div>
<p>With the consumer polling understanding, we&#8217;ll try to solve it by analyzing the consumer code and configuration.</p>
<p><strong>We may consider enabling auto-commit config</strong>, but that brings a possibility of data loss and irregular commit timing. Moreover, this approach does not ensure at-least-once processing.</p>
<p>Instead, we&#8217;ll explore some relevant manual commit solutions.</p>
<h3 id="bd-1-tuning-the-poll-duration-and-batch-size" data-id="1-tuning-the-poll-duration-and-batch-size">5.1. Tuning the Poll Duration and Batch Size</h3>
<div class="bd-anchor" id="1-tuning-the-poll-duration-and-batch-size"></div>
<p>By debugging the code, we found that the actual processing time was significantly longer than the specified maximum poll interval.</p>
<p>We&#8217;ll increase the maximum poll interval config:</p>
<pre><code class="language-java">consumerProperties.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, 1);
consumerProperties.put(ConsumerConfig.MAX_POLL_INTERVAL_MS_CONFIG, 300);</code></pre>
<p>We can also fine-tune the batch size according to our batch processing timing and throughput requirements.</p>
<p>Let&#8217;s test the updated consumer and confirm that no <em>CommitFailedException</em> is thrown.</p>
<p>We should note that it’s <strong>good to set the poll interval to about 3 times the batch processing time</strong> to ensure a safe buffer.</p>
<p>While changing the poll interval works, we may still face the issue in more complex setups, such as processing involving database transactions or network calls.</p>
<h3 id="bd-2-async-processing" data-id="2-async-processing">5.2. Async Processing</h3>
<div class="bd-anchor" id="2-async-processing"></div>
<p>If we&#8217;re processing records in the same thread that performs I/O operations, the above solution reduces throughput and increases latency.</p>
<p>In a production environment, <strong>we can&#8217;t guarantee a predictable database execution time, so configuring just the polling duration might be insufficient</strong>.</p>
<p>We can consider asynchronous processing of the records, so the consumer thread blocks for much shorter periods.</p>
<p>In Kafka, this can be done by processing the records asynchronously with virtual threads.</p>
<p>First, we&#8217;ll include a worker thread pool and a per-partition offset tracking map in the consumer service:</p>
<pre><code class="language-java">public class KafkaConsumerService {
    private final KafkaConsumer&lt;String, String&gt; consumer;
    private final AtomicBoolean running = new AtomicBoolean(true);
    private final ExecutorService workers;
    private final Map&lt;TopicPartition, AtomicLong&gt; committableOffsets = new ConcurrentHashMap&lt;&gt;();
    public KafkaConsumerService(Properties consumerProps, String topic) {
        this.consumer = new KafkaConsumer&lt;&gt;(consumerProps);
        consumer.subscribe(List.of(topic));
        workers = Executors.newVirtualThreadPerTaskExecutor();
    }
}</code></pre>
<p>Next, we&#8217;ll update the <em>consume</em> method with async processing using the worker threads:</p>
<pre><code class="language-java">public void consume() {
    try {
        while (running.get()) {
            ConsumerRecords&lt;String, String&gt; records = consumer.poll(Duration.ofMillis(100));
            if (records.isEmpty()) {
                continue;
            }
            List&lt;CompletableFuture&lt;Void&gt;&gt; futures = processAsync(records);
            try {
                CompletableFuture.allOf(futures.toArray(CompletableFuture[]::new))
                  .orTimeout(700L, TimeUnit.MILLISECONDS)
                  .join();
            } catch (CompletionException ex) {
                log.error("Batch processing timed out or failed", ex);
            }
            commitOffsets();
        }
    } catch (WakeupException ex) {
        if (running.get()) {
            log.error("Error in the Kafka Consumer with exception", ex);
            throw ex;
        }
    } finally {
        commitOffsets();
        consumer.close();
    }
}</code></pre>
<p>We&#8217;ll implement the <em>processAsync</em> method to asynchronously process the records:</p>
<pre><code class="language-java">private List&lt;CompletableFuture&lt;Void&gt;&gt; processAsync(ConsumerRecords&lt;String, String&gt; records) {
    return StreamSupport.stream(records.spliterator(), false)
      .map(record -&gt; CompletableFuture.runAsync(() -&gt; simulateDBUpdate(record), workers)
        .whenComplete((ignored, ex) -&gt; {
            if (ex == null) {
                markComplete(record);
            } else {
                log.error("Failed offset and send to DLQ {} {} {}", 
                  record.offset(), record.key(), ex.getMessage());
            }
        })
        .exceptionally(ex -&gt; null))
      .toList();
}</code></pre>
<p>In the code above, the consumer waits for the workers to complete processing, then commits the processed offset. If any record fails during processing, we log the failure message and can send it to a <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/kafka-spring-dead-letter-queue">Dead Letter Queue (DLQ)</a> topic.</p>
<p>Next, let&#8217;s implement the <em>markComplete</em> method to update the processed record offset into the<i> committableOffsets</i> map:</p>
<pre><code class="language-java">private void markComplete(ConsumerRecord&lt;String, String&gt; record) {
    TopicPartition tp = new TopicPartition(record.topic(), record.partition());
    committableOffsets.computeIfAbsent(tp, k -&gt; new AtomicLong(-1L))
      .accumulateAndGet(record.offset() + 1L, Math::max);
}</code></pre>
<p>Finally, we&#8217;ll implement the <em>commitOffsets</em> method to commit the processed offset:</p>
<pre><code class="language-java">private void commitOffsets() {
    Map&lt;TopicPartition, OffsetAndMetadata&gt; toCommit = new HashMap&lt;&gt;();
    committableOffsets.forEach((tp, atomicOffset) -&gt; {
        long val = atomicOffset.get();
        if (val != -1L) {
            toCommit.put(tp, new OffsetAndMetadata(val));
        }
    });
    if (toCommit.isEmpty()) {
        return;
    }
    consumer.commitSync(toCommit);
    toCommit.forEach((tp, meta) -&gt; {
        AtomicLong ref = committableOffsets.get(tp);
        if (ref != null) {
            ref.compareAndSet(meta.offset(), -1L);
        }
    });
}</code></pre>
<p>In the method above, we build the committed offset snapshot map for all the processed records and commit it. We also atomically mark the committed offset as completed.</p>
<p>Now, let&#8217;s configure the consumer with an increased batch size and max poll interval in the test class:</p>
<pre><code class="language-java">consumerProperties.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, 30);
consumerProperties.put(ConsumerConfig.MAX_POLL_INTERVAL_MS_CONFIG, 1000);</code></pre>
<p>Finally, we&#8217;ll implement a test case by producing and consuming multiple messages and verifying the offset:</p>
<pre><code class="language-java">@Test
void givenProducerMessagesAreSent_whenConsumerIsRunning_thenConsumerOffsetIsCommitted() {
    KafkaConsumerService kafkaConsumerService = new KafkaConsumerService(getConsumerConfig(), "test-topic");
    Thread th = new Thread(kafkaConsumerService::consume);
    th.start();
    try (KafkaProducer&lt;String, String&gt; producer = new KafkaProducer&lt;&gt;(getProducerConfig())) {
        for (int num = 0; num &lt; 100; num++) {
            producer.send(new ProducerRecord&lt;&gt;("test-topic", "x1" + num, "test" + num));
        }
        producer.flush();
    }
    Awaitility.await()
      .atMost(30, TimeUnit.SECONDS)
      .pollInterval(2, TimeUnit.SECONDS)
      .untilAsserted(() -&gt; {
          TopicPartition topicPartition = new TopicPartition("test-topic", 0);
          Map&lt;TopicPartition, OffsetAndMetadata&gt; committedOffsets;
          try (AdminClient adminClient = AdminClient.create(getAdminProps())) {
              ListConsumerGroupOffsetsResult result = adminClient.listConsumerGroupOffsets("consumer-app");
              committedOffsets = result.partitionsToOffsetAndMetadata()
                .get();
          }
          assertNotNull(committedOffsets);
          assertNotNull(committedOffsets.get(topicPartition));
          assertEquals(100L, committedOffsets.get(topicPartition)
            .offset());
      });
    kafkaConsumerService.shutdown();
}</code></pre>
<p>By running the test above, we have successfully verified that the committed offset matches the total number of consumed messages.</p>
<h3 id="bd-3-implementing-a-consumerrebalancelistener" data-id="3-implementing-a-consumerrebalancelistener">5.3. Implementing a <em>ConsumerRebalanceListener</em></h3>
<div class="bd-anchor" id="3-implementing-a-consumerrebalancelistener"></div>
<p>We should note that <strong>it&#8217;s always good practice to implement <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-kafka-reset-consumer-offset#1-implement-the-consumer-rebalance-listener"><em>ConsumerRebalanceListener</em></a> in the consumer to commit any in-flight offset and clean any data</strong>.</p>
<p>Let&#8217;s implement the <em>ConsumerRebalanceListener</em> in the <em>KafkaConsumer</em> object in the <em>KafkaConsumerService</em> class:</p>
<pre><code class="language-java">consumer.subscribe(List.of(topic), new ConsumerRebalanceListener() {
    @Override
    public void onPartitionsRevoked(Collection&lt;TopicPartition&gt; partitions) {
        try {
            commitOffsets(partitions);
        } catch (Exception ex) {
            log.error("Commit failed during rebalance", ex);
        } finally {
            partitions.forEach(committableOffsets::remove);
        }
    }
    @Override
    public void onPartitionsAssigned(Collection&lt;TopicPartition&gt; partitions) {
        partitions.forEach(committableOffsets::remove);
    }
});</code></pre>
<p>We&#8217;ll also need to implement the <em>commitOffsets</em> method to commit offsets for the specific partitions:</p>
<pre><code class="language-java">private void commitOffsets(Collection&lt;TopicPartition&gt; partitions) {
    Map&lt;TopicPartition, OffsetAndMetadata&gt; toCommit = new HashMap&lt;&gt;();
    partitions.forEach(tp -&gt; {
        AtomicLong ref = committableOffsets.get(tp);
        if (ref != null) {
            long val = ref.get();
            if (val != -1L) {
                toCommit.put(tp, new OffsetAndMetadata(val));
            }
        }
    });
    if (toCommit.isEmpty()) {
        return;
    }
    consumer.commitSync(toCommit);
}</code></pre>
<p>In the code above, we&#8217;re committing the offset by creating a snapshot map from the specific partition&#8217;s offset.</p>
<p>Now, let&#8217;s run the previous section&#8217;s test method to verify the offset for the updated consumer implementation:</p>
<pre><code class="language-bash">13:01:21.571 [virtual-158] INFO  c.b.k.c.f.async.KafkaConsumerService - Simulating a db call - record key x199 value test99
</code></pre>
<p>From the test above, we confirm that all messages were processed and committed.</p>
<h3 id="bd-4-best-practices" data-id="4-best-practices">5.4. Best Practices</h3>
<div class="bd-anchor" id="4-best-practices"></div>
<p>While we cannot prevent exceptions entirely, we can protect the system from any reprocessing duplication, data loss, or silent message drops.</p>
<p>We should implement idempotency on both the producer and the consumer, validate database constraints, and ensure side-effect-free reprocessing. Additionally, we should send the failed messages to a retry or DLQ topic.</p>
<h2 id="bd-conclusion" data-id="conclusion">6. Conclusion</h2>
<div class="bd-anchor" id="conclusion"></div>
<p>In this article, we learned about one of the common offset commit-related exceptions in the Consumer application. We explored why the resulting <em>CommitFailedException</em> happens and how to prevent it. We also implemented a preventive solution by tuning the maximum poll interval and switching to asynchronous processing logic.</p>
<p>As always, the example code can be found <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eugenp/tutorials/tree/master/apache-kafka-4">over on GitHub</a>.</p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-kafka-commitfailedexception">Understanding and Avoiding CommitFailedException in Kafka</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/958534139/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/958534139/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/958534139/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f11%2fData-Featured-Image-05-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/958534139/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/958534139/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/958534139/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-kafka-commitfailedexception#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-kafka-commitfailedexception/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/~/958534139/0/baeldung/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2024/11/Data-Featured-Image-05-150x150.jpg</webfeeds:featuredImage></item>
<item>
<feedburner:origLink>https://www.baeldung.com/hibernate-query-regexp</feedburner:origLink>
		<title>Regular Expression Support in HQL</title>
		<link>https://feeds.feedblitz.com/~/958534142/0/baeldung</link>
					<comments>https://feeds.feedblitz.com/~/958534142/0/baeldung#respond</comments>
		
		<dc:creator><![CDATA[Kai Yuan]]></dc:creator>
		<pubDate>Sat, 27 Jun 2026 18:48:47 +0000</pubDate>
				<category><![CDATA[JPA]]></category>
		<category><![CDATA[Hibernate]]></category>
		<category><![CDATA[HQL]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/hibernate-query-regexp</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;" /><p>Learn how to use HQL to query a string column against a regular expression.</p>
The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/958534142/0/baeldung">Regular Expression Support in HQL</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/958534142/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/958534142/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/958534142/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/958534142/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/958534142/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/hibernate-query-regexp#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/hibernate-query-regexp/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>Hibernate 7.2 introduces first-class regular expression support in HQL via the <em>like regexp</em> operator. Before this addition, matching a string column against a non-trivial pattern meant writing a native query. This new operator delegates regex matching to the database engine, avoiding native queries and post-filtering in Java.</p>
<p>In this tutorial, we&#8217;ll walk through a small <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-boot-start">Spring Boot</a> example that demonstrates <em>like regexp</em>. We&#8217;ll look under the hood to understand how Hibernate translates it to SQL and what happens when the underlying database doesn&#8217;t support regex.</p>
<h2 id="bd-preparing-a-simple-spring-boot-example" data-id="preparing-a-simple-spring-boot-example">2. Preparing a Simple Spring Boot Example</h2>
<div class="bd-anchor" id="preparing-a-simple-spring-boot-example"></div>
<p>Before we can demonstrate the <em>like regexp</em> operator, we need a tiny project to run queries against. We&#8217;ll set up the dependencies, an entity, some seed data, the application properties, and a Spring Data repository. For simplicity, we&#8217;ll not show all the code here.</p>
<h3 id="bd-1-dependencies" data-id="1-dependencies">2.1. Dependencies</h3>
<div class="bd-anchor" id="1-dependencies"></div>
<p>Our project uses Spring Boot 3.3 as its parent. Spring Boot 3.3&#8217;s BOM pins Hibernate to 6.5, so we override the version and a couple of its transitive dependencies that Hibernate 7 requires newer versions of:</p>
<pre><code class="language-xml">&lt;dependency&gt;
    &lt;groupId&gt;org.hibernate.orm&lt;/groupId&gt;
    &lt;artifactId&gt;hibernate-core&lt;/artifactId&gt;
    &lt;version&gt;7.4.2.Final&lt;/version&gt;
&lt;/dependency&gt;
&lt;dependency&gt;
    &lt;groupId&gt;org.jboss.logging&lt;/groupId&gt;
    &lt;artifactId&gt;jboss-logging&lt;/artifactId&gt;
    &lt;version&gt;3.6.1.Final&lt;/version&gt;
&lt;/dependency&gt;
&lt;dependency&gt;
    &lt;groupId&gt;jakarta.persistence&lt;/groupId&gt;
    &lt;artifactId&gt;jakarta.persistence-api&lt;/artifactId&gt;
    &lt;version&gt;3.2.0&lt;/version&gt;
&lt;/dependency&gt;
</code></pre>
<p>Beyond that, we pull in the usual <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/the-persistence-layer-with-spring-data-jpa"><em>spring-boot-starter-data-jpa</em></a> and the H2 driver, as we&#8217;ll use the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-boot-h2-database">H2 in-memory database</a> to keep the demo self-contained.</p>
<h3 id="bd-2-the-entity" data-id="2-the-entity">2.2. The Entity</h3>
<div class="bd-anchor" id="2-the-entity"></div>
<p>We&#8217;ll keep things minimal with a single-column entity:</p>
<pre><code class="language-java">@Entity
public class Message {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String content;
    // constructors, getters ...
}
</code></pre>
<h3 id="bd-3-seed-data" data-id="3-seed-data">2.3. Seed Data</h3>
<div class="bd-anchor" id="3-seed-data"></div>
<p>We load test data through Spring&#8217;s <em>data.sql</em> mechanism. The values are short phrases designed to exercise regex patterns:</p>
<pre><code class="language-sql">INSERT INTO message (content) VALUES ('quick brown fox jumps over');
INSERT INTO message (content) VALUES ('hello world from spring boot');
INSERT INTO message (content) VALUES ('Order 1234 shipped on Monday');
INSERT INTO message (content) VALUES ('Please call 555 today');
INSERT INTO message (content) VALUES ('ERROR connection refused remotely');
INSERT INTO message (content) VALUES ('warning low disk space detected');
</code></pre>
<h3 id="bd-4-application-properties" data-id="4-application-properties">2.4. Application Properties</h3>
<div class="bd-anchor" id="4-application-properties"></div>
<p>The test profile (<em>application-test.yaml</em>) wires up H2 and asks Spring to run <em>data.sql</em> after Hibernate creates the schema:</p>
<pre><code>spring:
  datasource:
    url: jdbc:h2:mem:testdb
    ...
  jpa:
    database-platform: org.hibernate.dialect.H2Dialect
    hibernate:
      ddl-auto: create-drop
    defer-datasource-initialization: true
    show-sql: true
    ...
  sql:
    init:
      mode: always
</code></pre>
<p><strong>The <em>defer-datasource-initialization: true</em> setting ensures that <em>data.sql</em> runs after the schema is created. </strong>It&#8217;s also worth mentioning that we enabled <em>show-sql</em> option to log translated SQL. This helps us understand how the <em>like regexp</em> operator works. We&#8217;ll discuss it later.</p>
<h3 id="bd-5-the-repository" data-id="5-the-repository">2.5. The Repository</h3>
<div class="bd-anchor" id="5-the-repository"></div>
<p>The repository is the part we&#8217;ll actually be exercising. It declares a pair of HQL queries using the new <em>like regexp</em>/<em>not like regexp</em> operators:</p>
<pre><code class="language-java">public interface MessageRepository extends JpaRepository&lt;Message, Long&gt; {
    @Query("select m from Message m where m.content like regexp :pattern")
    List&lt;Message&gt; findByContentMatchingRegex(@Param("pattern") String pattern);
    @Query("select m from Message m where m.content not like regexp :pattern")
    List&lt;Message&gt; findByContentNotMatchingRegex(@Param("pattern") String pattern);
}
</code></pre>
<p>As always, we&#8217;ll use unit tests to demonstrate how each operator behaves.</p>
<h2 id="bd-demonstration" data-id="demonstration">3. Demonstration</h2>
<div class="bd-anchor" id="demonstration"></div>
<p>In our defined repository methods, the <em>pattern</em> parameter accepts a full regex pattern. To find every message that contains at least one digit, we use .*<em>\d+.*</em>:</p>
<pre><code class="language-java">@Test
void givenContainsDigitRegex_whenLikeRegexp_thenReturnsMatching() {
    List&lt;Message&gt; results = repository.findByContentMatchingRegex(".*\\d+.*");
    assertThat(results)
      .extracting(Message::getContent)
      .containsExactlyInAnyOrder(
        "Order 1234 shipped on Monday",
        "Please call 555 today");
}
</code></pre>
<p>Similarly, if we pass the same regex to the <em>findByContentNotMatchingRegex()</em> method, we&#8217;ll get all rows that don&#8217;t contain any digits.</p>
<pre><code class="language-java">@Test
void givenContainsDigitRegex_whenNotLikeRegexp_thenReturnsNonMatching() {
    List&lt;Message&gt; results = repository.findByContentNotMatchingRegex(".*\\d+.*");
    assertThat(results)
      .extracting(Message::getContent)
      .containsExactlyInAnyOrder(
        "quick brown fox jumps over",
        "hello world from spring boot",
        "ERROR connection refused remotely",
        "warning low disk space detected");
}
</code></pre>
<p>If we run these two tests, they will pass. The same pattern parameter drives both variants, so we don&#8217;t need a separate regex for each side of the predicate. Next, let&#8217;s understand how Hibernate&#8217;s like regexp operator works under the hood.</p>
<h2 id="bd-under-the-hood" data-id="under-the-hood">5. Under The Hood</h2>
<div class="bd-anchor" id="under-the-hood"></div>
<p>Having seen the operator in action, let&#8217;s look at how Hibernate actually implements it and where the portability boundaries lie.</p>
<h3 id="bd-1-how-like-regexp-is-translated" data-id="1-how-like-regexp-is-translated">5.1. How <em>like regexp</em> Is Translated</h3>
<div class="bd-anchor" id="1-how-like-regexp-is-translated"></div>
<p>When Hibernate parses <em>m.content like regexp :pattern</em>, it doesn&#8217;t translate to a <em>LIKE</em> statement at all. Instead, <strong>it routes the predicate through a Semantic Query Model (SQM) function that each dialect registers under the name <em>regexp_like</em></strong>. The default <em>H2Dialect</em> maps <em>regexp_like(x, y)</em> to H2&#8217;s <em>regexp_like(x, y)</em> SQL function, so if we check the log output of the example test above, we can see the translated SQL:</p>
<pre><code>select m1_0.id, m1_0.content
from message m1_0
where regexp_like(m1_0.content, ?)
</code></pre>
<p>In other words, <em>like regexp</em> is syntactic sugar at the HQL level, while the heavy lifting happens in the database. Other dialects bind the same SQM function to whatever native construct they support, such as <em>REGEXP_LIKE</em> in Oracle, the <em>~</em> operator in PostgreSQL, <em>REGEXP</em> in MySQL, and so on.</p>
<p>Some dialects, for example, SQL Server prior to 2025, have no native regular expression function at all. In that case, the dialect simply doesn&#8217;t register <em>regexp_like</em>, and<strong> any HQL using <em>like regexp</em> fails at SQL execution time because Hibernate emits a call to a function the database doesn&#8217;t know</strong>.</p>
<h3 id="bd-2-the-portability-caveat" data-id="2-the-portability-caveat">5.2. The Portability Caveat</h3>
<div class="bd-anchor" id="2-the-portability-caveat"></div>
<p>Because each dialect delegates to the database&#8217;s native regex engine, the <em>flavor</em> of regex we can use is whatever the underlying database supports. Let&#8217;s see a few examples:</p>
<ul>
<li>H2 uses <em>java.util.regex</em> (Perl-compatible, <em>Matcher.find()</em> semantics — no implicit anchoring).</li>
<li>PostgreSQL uses POSIX regular expressions, which differ in subtle ways. For example, it has no <em>\d</em> shorthand without the embedded <em>(?x)</em> flag.</li>
<li>Oracle uses POSIX with a few extensions and does not support <em>\d</em> either. We must write <em>[[:digit:]]</em> or <em>[0-9]</em> instead.</li>
<li>MySQL (8.x) uses ICU regex, again with its own quirks.</li>
</ul>
<p><strong>So while the HQL stays portable, the patterns we feed into <em>like regexp</em> generally don&#8217;t</strong>. For example, a regex tested against H2 may fail or behave differently when the application is pointed at Oracle. If our application targets multiple databases, we should stick to a conservative regex subset.</p>
<h2 id="bd-conclusion" data-id="conclusion">6. Conclusion</h2>
<div class="bd-anchor" id="conclusion"></div>
<p>Hibernate 7.2&#8217;s <em>like regexp</em> operator lets us express regex predicates directly in HQL and have them pushed down to the database without resorting to native queries. The HQL surface is portable across dialects, but the actual regex flavor depends on the underlying database engine.</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/blob/master/persistence-modules/hibernate7">over on GitHub</a>.</p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/hibernate-query-regexp">Regular Expression Support in HQL</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/958534142/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/958534142/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/958534142/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/958534142/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/958534142/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/958534142/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/hibernate-query-regexp#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/hibernate-query-regexp/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/~/958534142/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/spring-bean-background-init</feedburner:origLink>
		<title>Bean Background Initialization in Spring Framework</title>
		<link>https://feeds.feedblitz.com/~/958532825/0/baeldung</link>
					<comments>https://feeds.feedblitz.com/~/958532825/0/baeldung#respond</comments>
		
		<dc:creator><![CDATA[Umara Mushtaq]]></dc:creator>
		<pubDate>Sat, 27 Jun 2026 18:42:09 +0000</pubDate>
				<category><![CDATA[Spring]]></category>
		<category><![CDATA[Spring 7]]></category>
		<category><![CDATA[Spring Boot 4]]></category>
		<category><![CDATA[Spring Core Basics]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/spring-bean-background-init</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 initialize Spring beans in the background using <em>@Bean(bootstrap = BACKGROUND)</em> to reduce startup latency while preserving dependency safety and lifecycle consistency.</p>
The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/958532825/0/baeldung">Bean Background Initialization in Spring Framework</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/958532825/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/958532825/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/958532825/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/958532825/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/958532825/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-bean-background-init#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-bean-background-init/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-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/spring-bean">Bean</a> background initialization in the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-tutorial">Spring Framework</a> provides a way to initialize selected beans asynchronously during application context startup. <strong>Instead of blocking the startup process on every bean initialization, Spring initializes selected beans in the background and continues processing the remaining application context</strong>. This improves application startup time and reduces perceived latency in applications with expensive initialization logic.</p>
<p>In this tutorial, we&#8217;ll explore how Bean background initialization works using the Spring native <em>bootstrap</em> attribute. We&#8217;ll demonstrate how to configure it correctly, how Spring manages lifecycle consistency during asynchronous initialization, and how this approach improves startup performance in real-world applications that rely on heavy resources such as caches, external services, or computation-heavy components.</p>
<h2 id="bd-why-background-initialization-matters-in-spring" data-id="why-background-initialization-matters-in-spring">2. Why Background Initialization Matters in Spring</h2>
<div class="bd-anchor" id="why-background-initialization-matters-in-spring"></div>
<p>Spring initializes <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-bean-scopes">singleton beans</a> during the <em><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-application-context">ApplicationContext</a></em> refresh phase in a strictly synchronous manner. During startup, the container manages the complete bean lifecycle and processes each bean in sequence:</p>
<ol>
<li>bean instantiation</li>
<li>dependency injection</li>
<li>initialization callback execution</li>
<li>marking the bean as ready for use</li>
</ol>
<p><strong>This approach provides predictable and deterministic startup behavior by ensuring that each bean reaches a fully initialized state before the context refresh proceeds</strong>. However, it introduces performance constraints when beans execute expensive operations during initialization.</p>
<h3 id="bd-1-synchronous-bean-initialization-model" data-id="1-synchronous-bean-initialization-model">2.1. Synchronous Bean Initialization Model</h3>
<div class="bd-anchor" id="1-synchronous-bean-initialization-model"></div>
<p><strong>Expensive initialization increases the duration of the <em>ApplicationContext</em> refresh phase by forcing the container to complete the lifecycle of each bean before moving forward</strong>. It reduces throughput in the bean creation pipeline and increases overall startup latency. As a result, even non-critical initialization logic delays the application from becoming available.</p>
<h3 id="bd-2-single-threaded-initialization-bottleneck" data-id="2-single-threaded-initialization-bottleneck">2.2. Single-Threaded Initialization Bottleneck</h3>
<div class="bd-anchor" id="2-single-threaded-initialization-bottleneck"></div>
<p><strong>The default startup model executes all bean initialization on the same thread that drives the context refresh process</strong>. Consequently, Spring executes independent initialization tasks sequentially and prevents them from running concurrently. This limits startup throughput and prevents Spring from taking advantage of available parallelism during application bootstrap.</p>
<h2 id="bd-background-initialization-mechanism" data-id="background-initialization-mechanism">3. Background Initialization Mechanism</h2>
<div class="bd-anchor" id="background-initialization-mechanism"></div>
<p>Spring introduces background initialization using the <em>bootstrap</em> attribute to reduce startup blocking caused by expensive singleton bean creation.</p>
<p>During application context refresh, Spring applies a modified execution strategy to selected beans:</p>
<ul>
<li><strong>Async delegation</strong>: Spring delegates the initialization phase of selected beans to a container-managed executor (the <em>bootstrap executor</em>), enabling execution to occur on a separate thread without blocking the main refresh process.</li>
<li><strong>Dependency coordination:</strong> When another bean depends on a background-initialized bean through a non-lazy dependency, Spring suspends dependency resolution for that bean and waits for initialization to complete before injecting the bean.</li>
<li><strong>Lifecycle preservation</strong>: Spring preserves the standard lifecycle order.</li>
</ul>
<p>This design ensures safe background execution while decoupling heavy initialization from the main startup thread, enabling earlier application readiness without affecting bean consistency.</p>
<h2 id="bd-example-setup" data-id="example-setup">4. Example Setup</h2>
<div class="bd-anchor" id="example-setup"></div>
<p>This section introduces a product preload class that simulates an expensive startup workload in Spring applications. This can be cache warming, dataset loading, or external resource preparation, which typically increases application startup time.</p>
<p>Let&#8217;s define the class to demonstrate this behavior:</p>
<pre><code class="language-java">public class ProductCatalogInitializer {
    public ProductCatalogInitializer() {
        loadProducts();
    }
    private void loadProducts() {
        System.out.println("Starting product preload");
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        System.out.println("Product preload completed");
    }
    public String getStatus() {
        return "Catalog ready";
    }
}</code></pre>
<p>The <em>loadProducts()</em> method represents a long-running initialization task executed during bean creation. Spring runs this logic on the main startup thread, which blocks the <em>ApplicationContext</em> refresh until completion.</p>
<p>This component acts as the baseline for demonstrating how Spring shifts such initialization work to background execution using the bootstrap mechanism in the next section.</p>
<h2 id="bd-background-initialization-using-bootstrap-mode" data-id="background-initialization-using-bootstrap-mode">5. Background Initialization Using Bootstrap Mode</h2>
<div class="bd-anchor" id="background-initialization-using-bootstrap-mode"></div>
<p>Spring applies background initialization at the bean definition level using the <em>bootstrap</em> attribute. Selected beans then get initialized concurrently without blocking the main startup thread.</p>
<h3 id="bd-1-enabling-background-initialization" data-id="1-enabling-background-initialization">5.1. Enabling Background Initialization</h3>
<div class="bd-anchor" id="1-enabling-background-initialization"></div>
<p>At this point, let&#8217;s create a configuration that marks <em>ProductCatalogInitializer</em> as a Spring-managed bean and enables background initialization using <em>BACKGROUND</em> bootstrap mode:</p>
<pre><code class="language-java">@Configuration
public class AppConfig {
    @Bean(bootstrap = Bean.Bootstrap.BACKGROUND)
    public ProductCatalogInitializer productCatalogInitializer() {
        return new ProductCatalogInitializer();
    }
}</code></pre>
<p>Spring detects the <em>BACKGROUND</em> bootstrap mode and offloads bean initialization to a container-managed <em>bootstrapExecutor</em>, allowing it to run on a separate thread instead of blocking the main application context refresh. Hence, this mechanism relies on a configured bootstrap executor. Without it, Spring can&#8217;t execute background initialization, and the affected beans fall back to normal synchronous initialization during startup.</p>
<h3 id="bd-2-bean-injection" data-id="2-bean-injection">5.2. Bean Injection</h3>
<div class="bd-anchor" id="2-bean-injection"></div>
<p>A background-initialized bean can be injected into other Spring-managed components in the same way as any standard singleton bean, since background initialization doesn&#8217;t change the core dependency injection model.</p>
<p>In most cases, direct injection remains sufficient because Spring coordinates the lifecycle and ensures that the bean becomes fully available within the application context startup phase.<strong> However, in scenarios where access may occur before the initialization process has completed, Spring provides <em>ObjectProvider</em> as an optional mechanism for deferred retrieval of the bean at runtime.</strong>
<br>
To that end, let&#8217;s create a service that demonstrates the injection of a background-initialized bean:</p>
<pre><code class="language-java">@Service
public class ProductService {
    private final ObjectProvider&lt;ProductCatalogInitializer&gt;
            initializerProvider;
    public ProductService(
            ObjectProvider&lt;ProductCatalogInitializer&gt; initializerProvider) {
                this.initializerProvider = initializerProvider;
    }
    public void printStatus() {
        ProductCatalogInitializer initializer = initializerProvider.getObject();
        System.out.println("Bean status: " + initializer.getStatus());
    }
}</code></pre>
<p>Now, the dependency isn&#8217;t resolved during bean creation. Instead, Spring retains the reference and defers resolution until <em>getObject()</em> is invoked. At that point, the actual bean instance is retrieved, so access occurs only after background initialization has completed, while the injection phase remains independent of startup timing.</p>
<h3 id="bd-3-application-startup-behaviorwith-background-initialization" data-id="3-application-startup-behaviorwith-background-initialization">5.3. Application Startup Behavior with Background Initialization</h3>
<div class="bd-anchor" id="3-application-startup-behaviorwith-background-initialization"></div>
<p>With <em>BACKGROUND</em> bootstrap mode enabled, Spring offloads selected bean initialization to a background thread, allowing the application context refresh to proceed without waiting for these beans. As a result, startup becomes non-blocking for the selected initialization workload, while execution continues in parallel.</p>
<p>Despite this, non-lazy background-initialized beans remain part of the Spring container lifecycle, and their completion is coordinated within the overall startup process.</p>
<h3 id="bd-4-performance-impact" data-id="4-performance-impact">5.4. Performance Impact</h3>
<div class="bd-anchor" id="4-performance-impact"></div>
<p>This behavior directly reduces startup latency. Spring moves heavy bean initialization off the main refresh thread. As a result, the application becomes operational earlier while background tasks continue executing in parallel. Beans that load large caches, establish connection pools, or run schema validation benefit the most.</p>
<h2 id="bd-conclusion" data-id="conclusion">6. Conclusion</h2>
<div class="bd-anchor" id="conclusion"></div>
<p>In this article, we explored background initialization in the Spring Framework using the <em>BACKGROUND</em> bootstrap mode, which enables selected beans to initialize asynchronously during application context refresh.</p>
<p><strong>The mechanism decouples expensive bean initialization from the main startup thread while preserving the Spring lifecycle semantics.</strong> Rather than launching uncontrolled threads, Spring keeps full awareness of bean dependencies. If a non-lazy bean depends on one that is still initializing in the background, Spring ensures it waits, preventing access to incomplete or half-built objects. In addition, using <em>ObjectProvider</em> helps delay bean retrieval until it is actually needed, adding an extra layer of safety for early-access scenarios.</p>
<p><strong>This approach improves application startup responsiveness in systems with heavy initialization workloads by enabling parallel execution during context refresh.</strong> It maintains dependency safety and lifecycle consistency while reducing blocking on the main thread. Background initialization is best suited for non-critical beans such as cache warmers, external integrations, and data preloading components.</p>
<p>The code backing this article is available <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eugenp/tutorials/tree/master/spring-core-5">over on Github.</a></p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-bean-background-init">Bean Background Initialization in Spring Framework</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/958532825/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/958532825/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/958532825/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/958532825/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/958532825/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/958532825/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-bean-background-init#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-bean-background-init/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/~/958532825/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-jcasbin-authorization</feedburner:origLink>
		<title>Authorization using jCasbin</title>
		<link>https://feeds.feedblitz.com/~/958532828/0/baeldung</link>
					<comments>https://feeds.feedblitz.com/~/958532828/0/baeldung#respond</comments>
		
		<dc:creator><![CDATA[Graham Cox]]></dc:creator>
		<pubDate>Sat, 27 Jun 2026 18:36:54 +0000</pubDate>
				<category><![CDATA[Security]]></category>
		<category><![CDATA[Authentication]]></category>
		<category><![CDATA[Authorization]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/java-jcasbin-authorization</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/11/Security-Featured-Image-06-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 jCasbin to define an access control model for Java applications, making use of standard approaches such as ACLs and RBAC.</p>
The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/958532828/0/baeldung">Authorization using jCasbin</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/958532828/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/958532828/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f11%2fSecurity-Featured-Image-06-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/958532828/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/958532828/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/958532828/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-jcasbin-authorization#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-jcasbin-authorization/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/Security-Featured-Image-06-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/Security-Featured-Image-06-1024x536.jpg 1024w, https://www.baeldung.com/wp-content/uploads/2024/11/Security-Featured-Image-06-300x157.jpg 300w, https://www.baeldung.com/wp-content/uploads/2024/11/Security-Featured-Image-06-768x402.jpg 768w, https://www.baeldung.com/wp-content/uploads/2024/11/Security-Featured-Image-06-100x52.jpg 100w, https://www.baeldung.com/wp-content/uploads/2024/11/Security-Featured-Image-06-600x314.jpg 600w, https://www.baeldung.com/wp-content/uploads/2024/11/Security-Featured-Image-06.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>In this tutorial, we’ll take a look at <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/apache/casbin-jcasbin">jCasbin</a> &#8211; the Java official port of the popular Apache <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://casbin.apache.org/">Casbin</a> authorization library. We’ll see what it is, how to use it and what we can do with it.</p>
<h2 id="bd-what-is-jcasbin" data-id="what-is-jcasbin"><strong>2. What is jCasbin</strong></h2>
<div class="bd-anchor" id="what-is-jcasbin"></div>
<p><strong>jCasbin is the Java version of the powerful Casbin access control library. This allows us to easily answer questions about access to resources within our applications.</strong></p>
<p>jCasbin, and the entire suite of Casbin libraries, work using a configuration model that allows us to describe the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-access-control-models">access control model</a> we want to use &#8211; such as <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://en.wikipedia.org/wiki/Access-control_list">ACLs</a> or <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/cs/role-vs-permission-based-access-control">RBAC</a> &#8211; and a separate set of policy data applied to that configuration model.</p>
<p>Typically, this works by defining:</p>
<ul>
<li>Objects that we want to control access to.</li>
<li>Subjects who want to access these objects.</li>
<li>Actions that the objects want to perform.</li>
</ul>
<a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/wp-content/uploads/2026/06/jcasbin-access-control.png"><img loading="lazy" decoding="async" class="alignnone wp-image-252658" src="https://www.baeldung.com/wp-content/uploads/2026/06/jcasbin-access-control-1024x382.png" alt="" width="536" height="200" /></a>
<p>However, jCasbin is flexible enough that we can use any structure we want, as long as we can model it correctly in our configuration and policy data.</p>
<h2 id="bd-dependencies" data-id="dependencies"><strong>3. Dependencies</strong></h2>
<div class="bd-anchor" id="dependencies"></div>
<p><strong>Before using jCasbin, we need to include the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/org.casbin/jcasbin">latest version</a> in our build, which is <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/org.casbin/jcasbin/1.99.0">1.99.0</a> at the time of writing.</strong></p>
<p>If we’re using Maven, we can include this dependency in our pom.xml file:</p>
<pre><code class="language-xml">&lt;dependency&gt;
    &lt;groupId&gt;org.casbin&lt;/groupId&gt;
    &lt;artifactId&gt;jcasbin&lt;/artifactId&gt;
    &lt;version&gt;1.99.0&lt;/version&gt;
&lt;/dependency&gt;</code></pre>
<p>At this point, we’re ready to start using it in our application.</p>
<h2 id="bd-creating-an-enforcer" data-id="creating-an-enforcer"><strong>4. Creating an Enforcer</strong></h2>
<div class="bd-anchor" id="creating-an-enforcer"></div>
<p>Once we have jCasbin in our application, we&#8217;re ready to use it. <strong>The central class for this is the <em>Enforcer</em>. Constructing this requires two data sources: our configuration model and our policy data.</strong> The easiest way to manage this is through passing in filenames:</p>
<pre><code class="language-java">Enforcer enforcer = new Enforcer("path/to/model.conf", "path/to/policy.csv");</code></pre>
<p>However, having our policy data be a local file can be difficult. <strong>As such, jCasbin allows the use of <em>Adapter</em>s here instead</strong>. These provide a means to load and manipulate the policy data from any data source we like. We also have a set of <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://casbin.apache.org/docs/adapters/">standard adapters</a> that can be used for our application. These include familiar technologies such as <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/apache/casbin-jcasbin-jdbc-adapter">JDBC</a>, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/jcasbin/hibernate-adapter">Hibernate</a>, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/jcasbin/jcasbin-mongo-adapter">MongoDB</a> and many others:</p>
<pre><code class="language-java">Adapter jdbcAdapter = new JDBCAdapter(dataSource);
Enforcer enforcer = new Enforcer("path/to/model.conf", jdbcAdapter);</code></pre>
<p>Exactly how these adapters work depends on the adapter in question and is out of scope of this article.</p>
<p>When specifying policy data as a filename, this internally uses the <em>FileAdapter </em>instead. We can also use this to load files available from other sources by providing an<em> InputStream</em>:</p>
<pre><code class="language-">FileAdapter fileAdapter = new FileAdapter(getClass().getResourceAsStream("/com/baeldung/jcasbin/policy.csv"));
Enforcer enforcer = new Enforcer("path/to/model.conf", fileAdapter);
</code></pre>
<p><strong>In exactly the same way, we can provide our model data using a <em>Model </em>instance instead.</strong> This allows us to load our model data from alternative sources:</p>
<pre><code class="language-java">String content = new String(getClass().getClassLoader().getResourceAsStream("com/baeldung/jcasbin/model.conf").readAllBytes());
Model model = new Model();
model.loadModelFromText(content);
Enforcer enforcer = new Enforcer(model, "path/to/policy.csv");
</code></pre>
<p>Once we&#8217;ve created our <em>Enforcer</em>, we&#8217;re ready to start using it to check permissions.</p>
<h2 id="bd-enforcing-permissions" data-id="enforcing-permissions"><strong>5. Enforcing Permissions</strong></h2>
<div class="bd-anchor" id="enforcing-permissions"></div>
<p><strong>Now that we&#8217;ve got our <em>Enforcer</em>, we can start checking permissions. We do this using the <em>enforce()</em> method.</strong> This typically involves passing in our subject, object, and action to check:</p>
<pre><code class="language-java">if (enforcer.enforce("alice", "data1", "read")) {
    // permit alice to read data1
} else {
    // deny the request
}</code></pre>
<p>Note that the <em>enforce()</em> method actually takes an arbitrary set of objects, depending on how our configuration model is defined.</p>
<p>The exact way permission checking works then depends on both the structure of our configuration model and our policy data.</p>
<h3 id="bd-1-access-control-lists" data-id="1-access-control-lists"><strong>5.1. Access Control Lists</strong></h3>
<div class="bd-anchor" id="1-access-control-lists"></div>
<p><strong>One of the easiest ways to configure jCasbin is through Access Control Lists (ACLs).</strong> A typical configuration model might look like this:</p>
<pre><code class="language-plaintext"># Request definition
[request_definition]
r = sub, obj, act
# Policy definition
[policy_definition]
p = sub, obj, act
# Matchers
[matchers]
m = r.sub == p.sub &amp;&amp; r.obj == p.obj &amp;&amp; r.act == p.act
# Policy effect
[policy_effect]
e = some(where (p.eft == allow))</code></pre>
<p>Here, the <em>request_definition </em>section tells us what the shape of the data passed into <em>enforcer.enforce()</em> should be. It gives us names for the parameters we pass in &#8211; here, &#8220;sub&#8221;, &#8220;obj&#8221; and &#8220;act&#8221; are used as shorthand for &#8220;subject&#8221;, &#8220;object&#8221; and &#8220;action&#8221;.</p>
<p>The <em>policy_definition </em>section then uses the exact same names to map onto the policy data. If not specified, there&#8217;s also an implicit value called &#8220;eft&#8221; &#8211; short for &#8220;effect&#8221; &#8211; which defaults to &#8220;allow&#8221;.</p>
<p>The <em>matchers</em> section then explains how we can match policy rows to requests. In this case, we&#8217;re simply saying that every field must have exactly the same value.</p>
<p>Finally, the <em>policy_effect</em> section tells us how to determine the overall effect of our policy. The action is only allowed if this returns a positive match.</p>
<p><strong>In this mode, our policy data is a straight list of subjects, objects and allowed actions &#8211; as defined by our <em>policy_definition:</em></strong></p>
<pre><code class="language-plaintext">p, alice, data1, read
p, bob, data2, write</code></pre>
<p>This indicates that the subject &#8220;alice&#8221; can perform the action &#8220;read&#8221; on the object &#8220;data1&#8221;, whereas the subject &#8220;bob&#8221; can perform the action &#8220;write&#8221; on the object &#8220;data2&#8221;.</p>
<pre><code class="language-java">assertTrue(enforcer.enforce("alice", "data1", "read"));
assertTrue(enforcer.enforce("bob", "data2", "write"));</code></pre>
<p>Any other checks are rejected because they don&#8217;t match our configuration.</p>
<h3 id="bd-2-super-users" data-id="2-super-users"><strong>5.2. Super Users</strong></h3>
<div class="bd-anchor" id="2-super-users"></div>
<p><strong>Sometimes we might want super users &#8211; users who can do anything regardless of what the policy data specifies.</strong></p>
<p>We express this in our configuration model by adjusting how the <em>matchers </em>section is defined:</p>
<pre><code class="language-plaintext">[matchers]
m = r.sub == p.sub &amp;&amp; r.obj == p.obj &amp;&amp; r.act == p.act || r.sub == "root"
</code></pre>
<p>Here, in addition to the same matching as before, we also match when the subject has the exact value &#8220;root&#8221;, regardless of what the rest of the policy expression says. <strong>Using this, a subject of &#8220;root&#8221; will always pass every check regardless:</strong></p>
<pre><code class="language-java">assertTrue(enforcer.enforce("root", "data2", "write"));</code></pre>
<p>This introduces some risks into our permission checks, so we need to use it carefully.</p>
<h3 id="bd-3-role-based-access-control" data-id="3-role-based-access-control"><strong>5.3. Role-Based Access Control</strong></h3>
<div class="bd-anchor" id="3-role-based-access-control"></div>
<p><strong>Role-Based Access Control (RBAC) introduces an additional layer of indirection into our configuration model. In this case, we assign roles to users, and can define permissions based on roles.</strong></p>
<p>We start by adding a <em>role_definition </em>section:</p>
<pre><code class="language-plaintext">[role_definition]
g = _, _</code></pre>
<p>This defines the <em>g()</em> function that we can use to define role membership.</p>
<p>We also need to update our <em>matchers</em> section to make use of this:</p>
<pre><code class="language-plaintext">[matchers]
m = g(r.sub, p.sub) &amp;&amp; r.obj == p.obj &amp;&amp; r.act == p.act</code></pre>
<p><strong>Instead of directly comparing the policy subjects with the request, this says they must match via the role definitions.</strong></p>
<p>We can now make use of this within our policy data:</p>
<pre><code class="language-plaintext">p, alice, data1, read
p, data2_admin, data2, read
p, data2_admin, data2, write
g, bob, data2_admin</code></pre>
<p>All of our rows that start with <em>p</em> define how permissions are assigned, either directly to users or else to roles. Here we&#8217;re assigning one permission directly to the subject &#8220;alice&#8221;, and two permissions to the role &#8220;data2_admin&#8221;.</p>
<p>The rows starting with <em>g </em>&#8211; from our <em>role_definition </em>section above &#8211; define how role membership works. Here, we&#8217;re defining that the subject &#8220;bob&#8221; is assigned the role &#8220;data2_admin&#8221;<em>. </em>This then means that this subject implicitly has all of the permissions assigned to this role as well:</p>
<pre><code class="language-java">assertTrue(enforcer.enforce("bob", "data2", "write"));</code></pre>
<p>We can also nest roles. That is, we can assign one role to another, generating an entire hierarchy of permissions:</p>
<pre><code class="language-plaintext">g, superuser, data2_admin
g, carol, superuser
</code></pre>
<p>Here, the role &#8220;data2_admin&#8221; has been assigned to the role &#8220;superuser&#8221;, and the role &#8220;superuser&#8221; has been assigned to the subject &#8220;carol&#8221;. This means that this subject has all the permissions all the way up:</p>
<pre><code class="language-java">assertTrue(enforcer.enforce("carol", "data2", "read"));</code></pre>
<p>We never gave &#8220;carol&#8221; permission on &#8220;data2&#8221; &#8211; either directly or indirectly. Instead, she inherited it through her role &#8220;superuser&#8221;.</p>
<h2 id="bd-management-api" data-id="management-api"><strong>6. Management API</strong></h2>
<div class="bd-anchor" id="management-api"></div>
<p><strong>In addition to checking permissions, we also have management APIs for working with our setup.</strong></p>
<p>We do this from the same <em>Enforcer </em>instance that we&#8217;ve already been using.</p>
<h3 id="bd-1-querying-subjects-objects-and-actions" data-id="1-querying-subjects-objects-and-actions"><strong>6.1. Querying Subjects, Objects and Actions</strong></h3>
<div class="bd-anchor" id="1-querying-subjects-objects-and-actions"></div>
<p><strong>The easiest thing we can do is query all the subjects and objects in our policy data.</strong> We do this using the <em>getAllSubjects()</em>, <em>getAllObjects()</em> and <em>getAllActions()</em> methods:</p>
<pre><code class="language-java">List&lt;String&gt; subjects = enforcer.getAllSubjects();
List&lt;String&gt; objects = enforcer.getAllObjects();
List&lt;String&gt; actions = enforcer.getAllActions();</code></pre>
<p>We can also query the actions that any given subject can perform on any given object:</p>
<pre><code class="language-java">Set&lt;String&gt; subjects = enforcer.getPermittedActions("alice", "data1");</code></pre>
<p>This works correctly for both ACL and RBAC setups, as well as for hierarchical roles.</p>
<h3 id="bd-2-querying-rbac-roles" data-id="2-querying-rbac-roles"><strong>6.2. Querying RBAC Roles</strong></h3>
<div class="bd-anchor" id="2-querying-rbac-roles"></div>
<p><strong>With an RBAC setup, we can also query exactly which roles are assigned to which subjects:</strong></p>
<pre><code class="language-java">List&lt;String&gt; roles = enforcer.getRolesForUser("carol");
// superuser</code></pre>
<p>This returns only the roles directly assigned, not those included in the configured hierarchy.</p>
<p>However, we can also query which roles are assigned to other roles in the same way:</p>
<pre><code class="language-java">List&lt;String&gt; roles = enforcer.getRolesForUser("superuser");
// data2_admin</code></pre>
<p>This means we can traverse the hierarchy if needed.</p>
<h3 id="bd-3-managing-permissions-and-roles" data-id="3-managing-permissions-and-roles"><strong>6.3. Managing Permissions and Roles</strong></h3>
<div class="bd-anchor" id="3-managing-permissions-and-roles"></div>
<p><strong>We also have a set of methods we can use to directly manage the permissions and roles that subjects have.</strong></p>
<p>We can directly add and remove permissions between a subject and an object:</p>
<pre><code class="language-java">enforcer.addPermissionForUser("alice", "data2", "read");
enforcer.deletePermissionForUser("alice", "data2", "read");</code></pre>
<p>These apply directly once called, and permission checks take effect straight away.</p>
<p><strong>When we&#8217;re working in RBAC mode, we can additionally add and remove roles from subjects:</strong></p>
<pre><code class="language-java">enforcer.addRoleForUser("alice", "superuser");
enforcer.deleteRoleForUser("alice", "superuser");</code></pre>
<p>We apply all of these changes in memory. We need to use the <em>savePolicy()</em> method to write them back to our backing store:</p>
<pre><code class="language-java">enforcer.savePolicy();</code></pre>
<p>However, we can only do this if we&#8217;re using certain adapters. It also depends on how the data was loaded. For example, we can&#8217;t write back if we loaded from an <em>InputStream</em>.</p>
<h2 id="bd-summary" data-id="summary"><strong>7. Summary</strong></h2>
<div class="bd-anchor" id="summary"></div>
<p>In this article, we took a very quick look at jCasbin. There’s a lot more that we can do with this. Next time you need to manage access controls for your applications, why not give it a try?</p>
<p>As always, all the code from this article is available over on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eugenp/tutorials/tree/master/libraries-7">GitHub</a>.</p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-jcasbin-authorization">Authorization using jCasbin</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/958532828/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/958532828/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/958532828/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f11%2fSecurity-Featured-Image-06-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/958532828/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/958532828/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/958532828/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-jcasbin-authorization#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-jcasbin-authorization/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/~/958532828/0/baeldung/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2024/11/Security-Featured-Image-06-150x150.jpg</webfeeds:featuredImage></item>
<item>
<feedburner:origLink>https://www.baeldung.com/hibernate-embedded-table</feedburner:origLink>
		<title>Guide to @EmbeddedTable in Hibernate</title>
		<link>https://feeds.feedblitz.com/~/958532831/0/baeldung</link>
					<comments>https://feeds.feedblitz.com/~/958532831/0/baeldung#respond</comments>
		
		<dc:creator><![CDATA[Abderrahim Azhrioun]]></dc:creator>
		<pubDate>Sat, 27 Jun 2026 18:33:15 +0000</pubDate>
				<category><![CDATA[JPA]]></category>
		<category><![CDATA[Hibernate]]></category>
		<category><![CDATA[JPA Annotations]]></category>
		<category><![CDATA[JPA Entities]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/hibernate-embedded-table</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2018/10/Social-Javaee-5-k-featured-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 the new <em>@EmbeddedTable</em> annotation of Hibernate to map embeddable objects to secondary tables more cleanly.</p>
The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/958532831/0/baeldung">Guide to @EmbeddedTable in Hibernate</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/958532831/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/958532831/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2018%2f10%2fSocial-Javaee-5-k-featured-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/958532831/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/958532831/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/958532831/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/hibernate-embedded-table#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/hibernate-embedded-table/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/2018/10/Social-Javaee-5-k-featured-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/2018/10/Social-Javaee-5-k-featured-1024x536.jpg 1024w, https://www.baeldung.com/wp-content/uploads/2018/10/Social-Javaee-5-k-featured-300x157.jpg 300w, https://www.baeldung.com/wp-content/uploads/2018/10/Social-Javaee-5-k-featured-768x402.jpg 768w, https://www.baeldung.com/wp-content/uploads/2018/10/Social-Javaee-5-k-featured-100x52.jpg 100w, https://www.baeldung.com/wp-content/uploads/2018/10/Social-Javaee-5-k-featured.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>The <em>@EmbeddedTable</em> annotation is a new Hibernate feature that simplifies the logic of <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/jpa-mapping-single-entity-to-multiple-tables">mapping an embedded object to a secondary table</a>.</p>
<p>In this short tutorial, we&#8217;ll shed light on the annotation and delve into its usage. First, we&#8217;ll start with some insight into the motivation behind <em>@EmbeddedTable</em>. Then, we’ll demonstrate how to use it in practice.</p>
<h2 id="bd-the-problem-with-traditional-mapping" data-id="the-problem-with-traditional-mapping">2. The Problem With Traditional Mapping</h2>
<div class="bd-anchor" id="the-problem-with-traditional-mapping"></div>
<p>Before this new annotation, JPA provided multiple annotations to map a single entity into multiple tables. However, this traditional approach requires <em><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/jpa-mapping-single-entity-to-multiple-tables#single-entity">@SecondaryTable</a>, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/jpa-embedded-embeddable#embedded">@Embedded</a>, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/jpa-embedded-embeddable#embeddable">@Embeddable</a></em>, and multiple <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/jpa-attributeoverride"><em>@AttributeOverride</em></a> annotations to override each column individually. <strong>This can quickly become verbose as the number of columns grows</strong>.</p>
<p>Let&#8217;s say we have a single <em>Person</em> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/jpa-entities">entity</a> that stores personal and address information. Here, <strong>we assume that we want to store the address details in a separate secondary table</strong>.</p>
<p>First, let&#8217;s create the <em>Address</em> object we want to embed inside the <em>Person</em> entity:</p>
<pre><code class="language-java">@Embeddable
public class Address {
	
    private String street;
    private String city;
    private String code;
    // setters
    
}</code></pre>
<p>Now, we create the <em>Person</em> entity:</p>
<pre><code class="language-java">@Entity
@SecondaryTable(name = "person_address")
public class Person {
    
    @Id
    private int id;
    
    @Column(name= "first_name")
    private String firstName;
    
    @Column(name = "last_name")
    private String lastName;
    
    @Embedded 
    private Address address;
    // setters
}
</code></pre>
<p>That&#8217;s not all, <strong>as mapping the <em>Address</em> fields to a secondary table requires the <em>@AttributeOverride</em> annotation for each field</strong>:</p>
<pre><code class="language-java">@Embedded 
@AttributeOverride(name = "street", column = @Column(table = "person_address")) 
@AttributeOverride(name = "city", column = @Column(table = "person_address"))
@AttributeOverride(name = "code", column = @Column(table = "person_address"))
private Address address;</code></pre>
<p>This approach quickly becomes cumbersome and error-prone since missing a single override can silently map a column to the wrong table. So, this is where <em>@EmbeddedTable</em> comes into play.</p>
<h2 id="bd-using-embeddedtable" data-id="using-embeddedtable">3. Using <em>@EmbeddedTable</em></h2>
<div class="bd-anchor" id="using-embeddedtable"></div>
<p>Starting with Hibernate 7.2, we can implement the same mapping we did in the previous section far more concisely with <em>@EmbeddedTable</em>:</p>
<pre><code class="language-java">@EmbeddedTable(value = "person_address")
private Address address;</code></pre>
<p>In a nutshell, <strong>Hibernate automatically maps every field of the embedded object</strong> <em>Address</em> to the specified secondary table <em>person_address</em> without overriding each one individually.</p>
<p><strong>This is particularly useful when dealing with large embeddable objects containing several fields</strong>.</p>
<p>So, let&#8217;s add a test case to confirm that everything works:</p>
<pre><code class="language-java">@Test
void whenUsingEmbeddedTableThenMapIntoTwoSeparateTables() {
    Address address = new Address();
    address.setStreet("12 Av. Tamassint center");
    address.setCity("Tamassint");
    address.setCode("10000");
    Person person = new Person();
    person.setId(1);
    person.setFirstName("Azhrioun");
    person.setLastName("Abderrahim");
    person.setAddress(address);
    session.persist(person);
    session.flush();
    Object[] addressRow = (Object[]) session.createNativeQuery(
      "select street, city from person_address",
      Object[].class
    ).uniqueResult();
    assertEquals("12 Av. Tamassint center", addressRow[0]);
    assertEquals("Tamassint", addressRow[1]);
    Object[] personRow = (Object[]) session.createNativeQuery(
      "select first_name, last_name from person",
      Object[].class
    ).uniqueResult();
    assertEquals("Azhrioun", personRow[0]);
    assertEquals("Abderrahim", personRow[1]);
}</code></pre>
<p>As expected, the test case runs successfully.</p>
<h2 id="bd-the-limitations" data-id="the-limitations">4. The Limitations</h2>
<div class="bd-anchor" id="the-limitations"></div>
<p>Although convenient, there are a couple of points worth keeping in mind when using <em>@EmbeddedTable</em>.</p>
<p>First, it&#8217;s a Hibernate-specific annotation and not part of the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/jakarta-persistence-3-2">Jakarta Persistence specification</a>. <strong>If portability across JPA providers matters, the <em>@AttributeOverride</em> annotation is always the safer choice</strong>.</p>
<p>Additionally, <em>@EmbeddedTable</em> is marked <em>@Incubating</em>, which means the API is still evolving and not yet considered stable and mature.</p>
<p>Typically, <em>@EmbeddedTable</em> is intended for top-level embedded objects. So, using it on nested objects isn&#8217;t supported yet.</p>
<h2 id="bd-conclusion" data-id="conclusion">5. Conclusion</h2>
<div class="bd-anchor" id="conclusion"></div>
<p>In this short article, we explored the new Hibernate <em>@EmbeddedTable</em> annotation.</p>
<p>Along the way, we learned that it provides a cleaner alternative to multiple <em>@AttributeOverride</em> annotations when mapping embedded objects to secondary tables.</p>
<p>The full source is available <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/azhwani/tutorials/tree/master/persistence-modules/hibernate-annotations">over on GitHub</a>.</p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/hibernate-embedded-table">Guide to @EmbeddedTable in Hibernate</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/958532831/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/958532831/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/958532831/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2018%2f10%2fSocial-Javaee-5-k-featured-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/958532831/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/958532831/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/958532831/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/hibernate-embedded-table#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/hibernate-embedded-table/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/~/958532831/0/baeldung/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2018/10/Social-Javaee-5-k-featured-150x150.jpg</webfeeds:featuredImage></item>
<item>
<feedburner:origLink>https://www.baeldung.com/java-weekly-652</feedburner:origLink>
		<title>Java Weekly, Issue 652</title>
		<link>https://feeds.feedblitz.com/~/958459364/0/baeldung</link>
					<comments>https://feeds.feedblitz.com/~/958459364/0/baeldung#respond</comments>
		
		<dc:creator><![CDATA[baeldung]]></dc:creator>
		<pubDate>Fri, 26 Jun 2026 12:57:04 +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=204137</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>The agent harness seems to be the one thing in 2026. For really good reason.</p>
The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/958459364/0/baeldung">Java Weekly, Issue 652</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/958459364/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/958459364/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/958459364/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/958459364/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/958459364/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-652#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-652/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://lucumr.pocoo.org/2026/6/23/the-coming-loop/">&gt;&gt; The Coming Loop</a></strong> [<span style="color: #993300;">lucumr.pocoo.org</span>]</p>
<p>A frame for the next phase of AI-assisted development &#8211; an agent harness, which generates, runs, inspects, corrects, and repeat quickly enough that the system becomes genuinely useful instead of just impressive. Anecdotally I&#8217;ve been running one for a few months now <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f642.png" alt="🙂" class="wp-smiley" style="height: 1em; max-height: 1em;" /> A good 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://spring.io/blog/2026/06/23/spring-ai-self-correcting-structured-output" target="_blank" rel="noopener"><strong>Self-Correcting Structured Output in Spring AI 2.0</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://inside.java/2026/06/21/better-tools-immutable-data/" target="_blank" rel="noopener"><strong>Better Tools for Immutable Data</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.infoq.com/news/2026/06/block-450-jvm-monorepo-migration/" target="_blank" rel="noopener"><strong>Behind the Scenes: Block 450 JVM Repositories into Monorepo to Reduce Dependency Drift</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://foojay.io/today/jurassic-jdk-migrate-or-extinct/" target="_blank" rel="noopener"><strong>Jurassic JDK: Migrate or Extinct</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.frankel.ch/programming-languages-targets-platforms/" target="_blank" rel="noopener"><strong>On programming languages, targets, and platforms</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://blog.jetbrains.com/amper/2026/06/kotlin-toolchain-0-11/" target="_blank" rel="noopener"><strong>Kotlin Toolchain 0.11: The Next Step for Amper</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/pi4j-drivers-simplifying-sensor-and-hardware-integration-in-java/" target="_blank" rel="noopener"><strong>Pi4J Drivers: Simplifying Sensor and Hardware Integration 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://foojay.io/today/systematic-ai-coding-my-takeaways-from-the-eclipse-foundation-workshop-in-brussels/" target="_blank" rel="noopener"><strong>Systematic AI Coding: My Takeaways from the Eclipse Foundation Workshop in Brussels</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://advancedweb.hu/backups-with-restic-2-year-retrospective/" target="_blank" rel="noopener"><strong>Backups with Restic: 2-year retrospective</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://mnot.net/blog/2026/well_known_uris" target="_blank" rel="noopener"><strong>So You Want To Define a Well-Known URI</strong></a> [<span style="color: #800000;">mnot.net</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://blog.scottlogic.com/2026/06/18/working-effectively-with-claude-code.html" target="_blank" rel="noopener"><strong>Working Effectively with Claude Code</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/06/19/sustainable-acceleration-and-the-agentic-software-development-life-cycle.html" target="_blank" rel="noopener"><strong>Sustainable acceleration and the Agentic Software Development Life Cycle</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.com/how-netflix-simplified-batch-compute-with-kueue-87860682629c" target="_blank" rel="noopener"><strong>How Netflix Simplified Batch Compute with Kueue</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://netflixtechblog.com/toward-more-controllable-ai-video-editing-an-early-research-exploration-at-netflix-eb8160ed60a2" target="_blank" rel="noopener"><strong>Toward More Controllable AI Video Editing: An Early Research Exploration at Netflix</strong></a> [<span style="color: #800000;">netflixtechblog.com</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/quarkus-unpacked-insights-from-the-foojay-podcast/" target="_blank" rel="noopener"><strong>Quarkus Unpacked: Insights from the Foojay Podcast</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/25/podcast-060/" target="_blank" rel="noopener"><strong>Episode 60 &#8220;How JEPs Drive Java&#8217;s Evolution&#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/25/a-bootiful-podcast-francesco-ciulla" target="_blank" rel="noopener"><strong>A Bootiful Podcast: My friend Francesco Ciulla on developer advocacy and more</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://spring.io/blog/2026/06/25/spring-boot-3-5-16-available-now" target="_blank" rel="noopener"><strong>Spring Boot 3.5.16 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://spring.io/blog/2026/06/24/spring-data-2025-0-13-released" target="_blank" rel="noopener"><strong>Spring Data 2025.0.13 released</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://quarkus.io/blog/CVE-2026-50559/" target="_blank" rel="noopener"><strong>Emergency releases to fix CVE-2026-50559 in all supported streams</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://github.com/quarkusio/quarkus/releases/tag/3.36.3" target="_blank" rel="noopener"><strong>Quarkus 3.36.3</strong></a>, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/quarkusio/quarkus/releases/tag/3.33.2.1" target="_blank" rel="noopener"><strong>3.33.2.1</strong></a>, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/quarkusio/quarkus/releases/tag/3.27.4.1" target="_blank" rel="noopener"><strong>3.27.4.1</strong></a>, and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/quarkusio/quarkus/releases/tag/3.20.6.2" target="_blank" rel="noopener"><strong>3.20.6.2</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/eclipse-vertx/vert.x/releases/tag/5.1.3" target="_blank" rel="noopener"><strong>Vert.x 5.1.3</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/v8.19.17" target="_blank" rel="noopener"><strong>Elasticsearch 8.19.17</strong></a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/elastic/elasticsearch/releases/tag/v9.3.6" target="_blank" rel="noopener"><strong>9.3.6</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.16" target="_blank" rel="noopener"><strong>Zuul v3.6.16</strong></a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/Netflix/zuul/releases/tag/v3.6.15" target="_blank" rel="noopener"><strong>v3.6.15</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.1.2" target="_blank" rel="noopener"><strong>Micronaut Core 5.1.2</strong></a> [<span style="color: #800000;">github.com/micronaut-projects</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/helidon-io/helidon/releases/tag/3.2.18" target="_blank" rel="noopener"><strong>Helidon 3.2.18</strong></a> [<span style="color: #800000;">github.com/helidon-io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.wildfly.org/news/2026/06/19/WildFly-40-0-1-is-released/" target="_blank" rel="noopener"><strong>WildFly 40.0.1 is released!</strong></a> [<span style="color: #800000;">wildfly.org</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://yusufaytas.com/old-software-was-fast-because-it-had-no-choice">&gt;&gt; Old Software Was Fast Because It Had No Choice</a></strong> [<span style="color: #993300;">yusufaytas.com</span>]</p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-weekly-652">Java Weekly, Issue 652</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/958459364/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/958459364/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/958459364/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/958459364/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/958459364/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/958459364/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-652#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-652/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/~/958459364/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/spring-ai-agent-skills</feedburner:origLink>
		<title>A Guide to Agent Skills in Spring AI</title>
		<link>https://feeds.feedblitz.com/~/958380014/0/baeldung</link>
					<comments>https://feeds.feedblitz.com/~/958380014/0/baeldung#respond</comments>
		
		<dc:creator><![CDATA[Hardik Singh Behl]]></dc:creator>
		<pubDate>Wed, 24 Jun 2026 10:26:06 +0000</pubDate>
				<category><![CDATA[Spring AI]]></category>
		<category><![CDATA[LLM]]></category>
		<category><![CDATA[OpenAI]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/spring-ai-guide-agent</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Featured-Image-10-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 Agent Skills using Spring AI to locally define, package, and grant capabilities to an AI agent for lightweight tasks for which MCPs would be an overkill.</p>
The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/958380014/0/baeldung">A Guide to Agent Skills 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/958380014/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/958380014/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f11%2fSpring-Featured-Image-10-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/958380014/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/958380014/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/958380014/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-agent-skills#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-agent-skills/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/Spring-Featured-Image-10-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-Featured-Image-10-1024x536.jpg 1024w, https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Featured-Image-10-300x157.jpg 300w, https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Featured-Image-10-768x402.jpg 768w, https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Featured-Image-10-100x52.jpg 100w, https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Featured-Image-10-600x314.jpg 600w, https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Featured-Image-10.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>Modern web applications are increasingly integrating with <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/cs/large-language-models">Large Language Models (LLMs)</a> to build solutions that go beyond simple question answering. To create AI agents capable of handling complex user requests, we often connect them to multiple <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> servers that provide them with specific capabilities via tools.</p>
<p>However, creating and running MCP servers can be overkill for lightweight and local automation tasks where we just need to expose a simple capability to a single agent.</p>
<p><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://agentskills.io/home">Agent Skills</a> is a specification that provides a structured way to locally define, package, and expose these capabilities to an AI agent.</p>
<p>In this tutorial, we&#8217;ll explore the Agent Skills capability in Spring AI. We&#8217;ll configure a custom skill and integrate it with a simple chatbot to summarize articles.</p>
<h2 id="bd-what-are-agent-skills" data-id="what-are-agent-skills">2. What are Agent Skills?</h2>
<div class="bd-anchor" id="what-are-agent-skills"></div>
<p><strong>Agent Skills is an open specification for defining various capabilities that an AI agent can invoke</strong>. <strong>A skill is essentially a directory containing a <em>SKILL.md</em> file, which acts as its manifest, alongside any associated code</strong> like Python or Bash scripts, or additional resources that the skill relies on.</p>
<p>The <em>SKILL.md</em> file contains a frontmatter block with a name and description, followed by a set of natural language instructions that tell the agent how to use the skill.</p>
<p><strong>When the agent receives a user request, it reads the descriptions of all available skills and decides if any is relevant</strong>. If yes, it then loads the relevant files into context and follows the instructions inside the matching skill to fulfill the request. And if no skill matches the request, the agent simply responds using its general capabilities without invoking any skill.</p>
<p>We&#8217;ll see an agent invoking our custom skill practically in action in the upcoming sections.</p>
<h2 id="bd-setting-up-the-project" data-id="setting-up-the-project">3. Setting up the Project</h2>
<div class="bd-anchor" id="setting-up-the-project"></div>
<p>Before we dive into the implementation, we’ll need to include the necessary dependencies and configure our application correctly.</p>
<h3 id="bd-1-dependencies" data-id="1-dependencies">3.1. Dependencies</h3>
<div class="bd-anchor" id="1-dependencies"></div>
<p>Let&#8217;s start by adding the necessary dependencies to our project’s <em>pom.xml</em> file:</p>
<pre><code class="language-xml">&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&lt;/version&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.10.0&lt;/version&gt;
&lt;/dependency&gt;</code></pre>
<p>Here, we first import <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/org.springframework.ai/spring-ai-starter-model-openai">Spring AI’s OpenAI starter dependency</a>, which we’ll use to interact with a chat model. Support for agent skills is available in Spring AI 2 and later, so we need to make sure we&#8217;re using the correct version.</p>
<p>Next, we import the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/org.springaicommunity/spring-ai-agent-utils">agent-utils dependency</a> from the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~github.com/spring-ai-community">Spring AI community</a>, which enables us to add the agent skills capability to our chat models.</p>
<h3 id="bd-2-configuring-a-chat-model" data-id="2-configuring-a-chat-model">3.2. Configuring a Chat Model</h3>
<div class="bd-anchor" id="2-configuring-a-chat-model"></div>
<p>Next, let’s configure our <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://platform.openai.com/api-keys">OpenAI API key</a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://developers.openai.com/api/docs/models">chat model</a> in the <em>application.yaml</em> file:</p>
<pre><code class="language-yaml">spring:
  ai:
    openai:
      api-key: ${OPENAI_API_KEY}
      chat:
        options:
          model: gpt-5.5</code></pre>
<p>We use the <em>${}</em> property placeholder to load the value of our API Key from an <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-boot-properties-env-variables#use-environment-variable-in-applicationyml-file">environment variable</a>.</p>
<p>Additionally, we specify OpenAI&#8217;s <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://openai.com/index/introducing-gpt-5-5/">GPT 5.5</a> model using the <em>gpt-5.5</em> model ID. Alternatively, we can use a different chat model that supports the agent skills specification, as the specific AI model or provider is irrelevant for this demonstration.</p>
<p>With these two properties set, <strong>Spring AI automatically creates a bean of type <em>ChatModel</em>, allowing us to interact with the specified model</strong>.</p>
<h2 id="bd-defining-our-custom-skill" data-id="defining-our-custom-skill">4. Defining Our Custom Skill</h2>
<div class="bd-anchor" id="defining-our-custom-skill"></div>
<p>Now let&#8217;s define a custom agent skill that can fetch an article from a URL and summarize it.</p>
<p>Agent skills follow a specific directory structure, so let&#8217;s set that up. <strong>We&#8217;ll start by creating an <em>.openai/skills</em> directory in our project&#8217;s root directory. We can define multiple subdirectories inside this, each representing a distinct agent skill</strong>.</p>
<p>Next, <strong>we&#8217;ll create an <em>article-summarizer</em> subdirectory to represent our custom skill inside .openai/skills</strong> and define our main <em>SKILL.md</em> file inside it:</p>
<pre><code class="language-markdown">---
name: article-summarizer
description: Summarizes articles into concise digests. Useful when user asks to summarize or get key points from an article.
---
# Article Summarizer
## Instructions
When summarizing an article:
1. If given a URL: Run `uv run scripts/fetch_article.py &lt;url&gt;` to retrieve the content.
2. Once content is available, extract the main thesis, few key points, and conclusion.
3. Structure the output as a TL;DR, key points, and a bottom line.</code></pre>
<p>In the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://docs.github.com/en/contributing/writing-for-github-docs/using-yaml-frontmatter">frontmatter block</a>, we define the name and description of our skill. <strong>The description is especially important as the agent uses it to determine whether this skill is relevant for a given user request</strong>. Next, we define the instructions that tells the agent exactly what steps to follow when the skill is invoked, including which script to run and how to structure the final output.</p>
<p>Next, let&#8217;s create the <em>fetch_article.py</em> script inside a new <em>scripts</em> subdirectory that we reference in the instructions:</p>
<pre><code class="language-python">ARTICLE = """
... hardcoding sample article for demonstration
"""
print(ARTICLE)</code></pre>
<p>Here, for our demonstration, we simply print a <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-ai-mcp-elicitations">hardcoded article about MCP elicitations</a> instead of actually making a web request. <strong>The AI model will run this script and read the standard output to get this article&#8217;s content regardless of the URL in the request </strong>.</p>
<p>Also, <strong>it&#8217;s important to note that we can define our scripts in any language of our choice</strong>. We just need to make sure that we pre-install the required runtimes for our agent to execute the necessary commands.</p>
<h2 id="bd-creating-a-simple-chatbot" data-id="creating-a-simple-chatbot">5. Creating a Simple Chatbot</h2>
<div class="bd-anchor" id="creating-a-simple-chatbot"></div>
<p>With our configurations in place, let’s build a simple chatbot.</p>
<p>In Spring AI, <strong>the <em>ChatClient</em> class acts as the main entry point for interacting with our configured chat completion model</strong>. Let&#8217;s define its bean using the auto-configured <em>ChatModel</em> bean:</p>
<pre><code class="language-java">@Bean
ChatClient chatClient(ChatModel chatModel) {
    String skillsRootDirectory = ".openai/skills";
    return ChatClient
      .builder(chatModel)
      .defaultTools(
        SkillsTool.builder()
          .addSkillsDirectory(skillsRootDirectory)
          .build(),
        FileSystemTools.builder()
          .allowedDirectory(skillsRootDirectory)
          .build(),
        ShellTools.builder()
          .build()
      ).build();
}</code></pre>
<p>In our bean definition, <strong>we first register our custom skills directory using <em>SkillsTool</em>, pointing it to the <em>.openai/skills</em> directory</strong>.</p>
<p>Secondly, <strong>we register <em>FileSystemTools</em>, which gives the agent the ability to read and write any files on the local filesystem</strong>. To restrict the tool&#8217;s operations to the configured skills directory, we use the <em>allowedDirectory()</em> method.</p>
<p>Finally, <strong>we register <em>ShellTools</em>, which allows the agent to execute shell commands</strong>, enabling it to run the Python script we&#8217;ve defined.</p>
<p>However, it&#8217;s important to note that <em>ShellTools</em> executes our scripts directly on the local machine without <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/cs/sandboxing-fundamentals">sandboxing</a>. As such, <strong>we should carefully review the scripts we expose to our agent</strong> and consider <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/dockerizing-spring-boot-application">containerizing our application</a> to limit potential exposure.</p>
<p>Next, let’s inject the <em>ChatClient</em> bean in a controller class and expose a REST API:</p>
<pre><code class="language-java">@PostMapping("/chat")
ResponseEntity&lt;ChatbotResponse&gt; chat(@RequestBody ChatbotRequest chatbotRequest) {
    String answer = chatClient
      .prompt()
      .user(chatbotRequest.question)
      .call()
      .content();
    return ResponseEntity.ok(new ChatbotResponse(answer));
}
record ChatbotRequest(String question) {}
record ChatbotResponse(String answer) {}</code></pre>
<p>Here, we simply pass the user’s <em>question</em> to the <em>chatClient</em> instance and return the LLM&#8217;s response. We’ll use this API endpoint to interact with our chatbot in the upcoming section.</p>
<h2 id="bd-interacting-with-our-chatbot" data-id="interacting-with-our-chatbot">6. Interacting With Our Chatbot</h2>
<div class="bd-anchor" id="interacting-with-our-chatbot"></div>
<p>Now that we’ve built our implementation, let’s interact with our chatbot and test the agent skill capability.</p>
<p>We’ll use the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/httpie-http-client-command-line">HTTPie</a> CLI to invoke the chatbot’s API endpoint:</p>
<pre class="add-horizontal-scrollbar"><code class="language-bash">http POST :8080/chat question="Can you summarize the following article: https://www.baeldung.com/sample-non-existing-article"</code></pre>
<p>Here, we ask the chatbot to summarize a specific article by passing a URL in the question. <strong>We deliberately provide the URL of a non-existent article to verify that the chatbot summarizes the article we&#8217;ve hardcoded in our Python script</strong>.</p>
<p>Let’s see what we get as a response:</p>
<pre class="add-horizontal-scrollbar"><code class="language-json">{
  "answer": "## TL;DR\nThis article explains how to implement MCP Elicitations in Spring AI, allowing MCP servers to request additional user information dynamically during tool execution.
    \n\n## Key Points\n- MCP Elicitations solve the problem of missing user information during MCP tool execution.
    \n- The tutorial demonstrates building an MCP server using Spring AI.
    \n- The MCP server exposes a tool that fetches author details and conditionally requests additional information.
    \n- The `elicit()` method is used to pause execution and gather required details from the user.
    \n- The article also demonstrates configuring an MCP client and integrating it with an Anthropic Claude model
    \n- Spring AI automatically creates MCP clients and tool callback providers from configuration.
    \n- An `@McpElicitation` handler is used on the client side to respond to elicitation requests.
    \n- The tutorial concludes with a working chatbot example and log outputs showing the complete elicitation flow.
    \n\n## Bottom Line\nMCP Elicitations enable interactive AI applications where tools can dynamically collect additional context from users during execution,
    making MCP-based systems more flexible and user-aware."
}</code></pre>
<p>As we can see, <strong>the LLM summarizes our hardcoded article and the response is structured exactly as our skill&#8217;s instructions prescribed, with a TL;DR, a set of key points, and a bottom line</strong>.</p>
<p>Behind the scenes, the agent matched the user&#8217;s request to the <em>article-summarizer</em> skill based on its description, loaded the instructions into context using <em>FileSystemTools</em>, executed the <em>fetch_article.py</em> script via <em>ShellTools</em> to retrieve the article content, and then structured the response following the instructions in our <em>SKILL.md</em> file.</p>
<h2 id="bd-conclusion" data-id="conclusion">7. Conclusion</h2>
<div class="bd-anchor" id="conclusion"></div>
<p>In this article, we&#8217;ve explored the concept of Agent Skills using Spring AI.</p>
<p>We started by understanding what Agent Skills are and how they help us in exposing reusable capabilities to an AI agent.</p>
<p>Then, we defined a custom article summarizer skill and wired it into a chatbot. Finally, we interacted with our chatbot to confirm that it correctly discovers and invokes our skill.</p>
<p>As always, all the code examples used in this article are available <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eugenp/tutorials/tree/master/spring-ai-modules/spring-ai-agent-skills">over on GitHub</a>.</p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-ai-agent-skills">A Guide to Agent Skills 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/958380014/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/958380014/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/958380014/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f11%2fSpring-Featured-Image-10-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/958380014/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/958380014/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/958380014/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-agent-skills#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-agent-skills/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/~/958380014/0/baeldung/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Featured-Image-10-150x150.jpg</webfeeds:featuredImage></item>
<item>
<feedburner:origLink>https://www.baeldung.com/java-spring-dynamic-authorization-scopes</feedburner:origLink>
		<title>Dynamic Authorization Scopes in Spring Authorization Server</title>
		<link>https://feeds.feedblitz.com/~/958357049/0/baeldung</link>
					<comments>https://feeds.feedblitz.com/~/958357049/0/baeldung#respond</comments>
		
		<dc:creator><![CDATA[Philippe Sevestre]]></dc:creator>
		<pubDate>Wed, 24 Jun 2026 10:09:15 +0000</pubDate>
				<category><![CDATA[Spring Security]]></category>
		<category><![CDATA[Authorization]]></category>
		<category><![CDATA[OAuth Spring]]></category>
		<category><![CDATA[Spring Boot 4]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/java-spring-dynamic-authorization-scopes</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Featured-Image-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 we can modify the Spring Authorization Server's default configuration to support dynamic scopes.</p>
The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/958357049/0/baeldung">Dynamic Authorization Scopes in Spring Authorization Server</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/958357049/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/958357049/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f11%2fSpring-Featured-Image-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/958357049/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/958357049/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/958357049/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-spring-dynamic-authorization-scopes#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-spring-dynamic-authorization-scopes/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/Spring-Featured-Image-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/11/Spring-Featured-Image-11-1024x536.jpg 1024w, https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Featured-Image-11-300x157.jpg 300w, https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Featured-Image-11-768x402.jpg 768w, https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Featured-Image-11-100x52.jpg 100w, https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Featured-Image-11-600x314.jpg 600w, https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Featured-Image-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>In this tutorial, we&#8217;ll show how to extend the Spring Authorization Server to handle dynamic OAuth 2.0 scopes, which are required in advanced authentication/authorization use cases.</p>
<h2 id="bd-when-to-use-dynamic-scopes" data-id="when-to-use-dynamic-scopes">2. When to Use Dynamic Scopes?</h2>
<div class="bd-anchor" id="when-to-use-dynamic-scopes"></div>
<p>OAuth2-based applications use scopes to request access tokens with a defined set of privileges, like reading fields from a user&#8217;s profile, performing an automation task, and so on. Usually, the scopes are fixed and known in advance both by the authorization server and the client application. In some scenarios, however, this is not the case.</p>
<p><strong>For instance, suppose your client application needs an access token to execute a single operation, like a transfer request or updating sensitive information</strong>. One option is to implement a custom authorization scheme between the client and the resource server, but we&#8217;d be reinventing the wheel. From a security perspective, this is also not ideal, since it implies a bigger surface attack, as those protocols would need to be implemented across every resource server and client.</p>
<p>This is where dynamic scopes come into play. <strong>In a nutshell, a dynamic scope is one that a client can request, but it&#8217;s not known beforehand by the authorization server.</strong></p>
<p>Usually, a dynamic scope requires some sort of validation service to exist, so it can be validated. In practical terms, this requires a consent repository to store the requested scope, which will be used to validate it and, if the end user grants it, to update its status.</p>
<p><strong>When the client receives the access token, it will have the dynamic scope associated with it, allowing it to complete the desired operation.</strong></p>
<h2 id="bd-dynamic-scopes-in-spring-authorization-server" data-id="dynamic-scopes-in-spring-authorization-server">3. Dynamic Scopes in Spring Authorization Server</h2>
<div class="bd-anchor" id="dynamic-scopes-in-spring-authorization-server"></div>
<p>The Spring Authorization Server, true to its roots, has a modular architecture, <strong>which allows us to replace many of its components with custom ones</strong>. We&#8217;ve already explored this characteristic <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-multitenancy">to implement multitenancy support</a>, a feature not available by default. In our case, these are the aspects we need to modify:</p>
<ul>
<li>Scope validation logic</li>
<li>Consent Validation</li>
<li>Consent page: optional, but usually required</li>
</ul>
<p>Moreover, it&#8217;s a good idea to separate the Spring-specific code from the actual scope validation logic. We&#8217;ll create a <em>DynamicScopeService</em> for that purpose, leaving to a <em>@Configuration</em> class the task of creating an adapter between the expected method&#8217;s signature and the service.</p>
<p>Now, let&#8217;s move to the implementation.</p>
<h2 id="bd-project-setup" data-id="project-setup">4. Project Setup</h2>
<div class="bd-anchor" id="project-setup"></div>
<p>Dynamic Scopes don&#8217;t need any extra dependencies besides those needed by the Spring Authorization Server itself:</p>
<pre><code class="language-xml">&lt;dependency&gt;
    &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
    &lt;artifactId&gt;spring-boot-starter-security-oauth2-authorization-server&lt;/artifactId&gt;
    &lt;version&gt;4.1.0&lt;/version&gt;
&lt;/dependency&gt;
&lt;dependency&gt;
    &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
    &lt;artifactId&gt;spring-boot-starter-security-oauth2-authorization-server-test&lt;/artifactId&gt;
    &lt;version&gt;4.1.0&lt;/version&gt;
    &lt;scope&gt;test&lt;/scope&gt;
&lt;/dependency&gt;
</code></pre>
<p>The latest versions of these dependencies are available on Maven Central:</p>
<ul>
<li><em><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-security-oauth2-authorization-server">spring-boot-starter-security-oauth2-authorization-server</a></em></li>
<li><em><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-security-oauth2-authorization-server-test">spring-boot-starter-security-oauth2-authorization-server-test</a></em></li>
</ul>
<h2 id="bd-security-configuration" data-id="security-configuration">5. Security Configuration</h2>
<div class="bd-anchor" id="security-configuration"></div>
<p>The Spring Authorization Server needs two security filter chains to operate:</p>
<ul>
<li>one for handling OAuth/OIDC protocol endpoints</li>
<li>a &#8220;catch-all&#8221; chain that handles everything else, including user authentication</li>
</ul>
<p>As usual in Spring Security, the built-in ones are only created when there are no application-defined chains available. <strong>In our case, we need to tweak just the first one, but by doing so, the auto-configuration for the catch-all chain will be disabled, so we&#8217;ll need to provide both.</strong></p>
<h3 id="bd-1-authorization-server-chain" data-id="1-authorization-server-chain">5.1. Authorization Server Chain</h3>
<div class="bd-anchor" id="1-authorization-server-chain"></div>
<p>This is the modified chain where we&#8217;ll add our customization logic:</p>
<pre><code class="language-java">@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) {
    http.oauth2AuthorizationServer(authorizationServer -&gt; {
        http.securityMatcher(authorizationServer.getEndpointsMatcher());
        authorizationServer
          .oidc(withDefaults())
          .authorizationEndpoint(ap -&gt; {
              ap.consentPage("/consent");
              ap.authenticationProviders(providers -&gt; {
                  providers.stream()
                    .filter(OAuth2AuthorizationCodeRequestAuthenticationProvider.class::isInstance)
                    .map(p -&gt; (OAuth2AuthorizationCodeRequestAuthenticationProvider)p)
                    .findFirst()
                    .ifPresent(p -&gt; {
                        p.setAuthenticationValidator(dynamicScopesAuthenticationValidator());
                        p.setAuthorizationConsentRequired(dynamicScopesConsentValidator());
                    });
              });
          });
    });
    http.authorizeHttpRequests(authorize -&gt; authorize.anyRequest().authenticated());
    http.oauth2ResourceServer(resourceServer -&gt; resourceServer.jwt(withDefaults()));
    http.exceptionHandling(exceptions -&gt; exceptions.defaultAuthenticationEntryPointFor(
      new LoginUrlAuthenticationEntryPoint("/login"), createRequestMatcher()));
    return http.build();
}
</code></pre>
<p>The scope validation takes place at the <em>OAuth2AuthorizationCodeRequestAuthenticationProvider</em>, which uses two collaborating objects to perform its task:</p>
<ul>
<li>An <em>AuthenticationValidator</em> to validate the submitted parameters</li>
<li>A <em>Predicate</em> that decides whether to ask for consent</li>
</ul>
<p>Since the authorization server uses multiple <em>AuthenticationProvider</em>s in its implementation, we need to go through all of them until we find the proper one. All this happens in the <em>authorizationEndpointCustomizer</em>, which exposes the list of authentication providers.</p>
<p>The same customizer also allows us to set the <em>URI</em> for a custom consent page, which will be &#8220;/consent&#8221; in our case. We&#8217;ll come back to the consent page later in this tutorial.</p>
<h3 id="bd-2-catch-all-chain" data-id="2-catch-all-chain">5.2. &#8220;Catch-All&#8221; Chain</h3>
<div class="bd-anchor" id="2-catch-all-chain"></div>
<p>This chain, which has no <em>securityMatcher()</em> call in its DSL, will apply the following rules to incoming requests:</p>
<ul>
<li>Any request must be authenticated</li>
<li>When receiving a non-authenticated request, use form-based login with default settings</li>
</ul>
<pre><code class="language-java">@Bean
@Order(SecurityFilterProperties.BASIC_AUTH_ORDER)
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) {
    http.authorizeHttpRequests(authorize -&gt; {
        authorize.anyRequest().authenticated();
      })
      .formLogin(withDefaults());
    return http.build();
}
</code></pre>
<p>Here, we&#8217;re just replicating the default configuration. In a real-world scenario, we&#8217;d probably want to change some aspects of its properties. For instance, this is where we&#8217;d provide the <em>URI</em> for a custom login page, enable two-factor authentication, or add support for a federated identity provider.</p>
<h2 id="bd-scope-validation" data-id="scope-validation">6. Scope Validation</h2>
<div class="bd-anchor" id="scope-validation"></div>
<p>The <em>dynamicScopesAuthenticationValidator()</em> method creates an adapter that isolates the Spring Security aspects of the scope validation from the business logic.</p>
<p>In our tutorial, we&#8217;ll assume that the dynamic scope will be a string that starts with the &#8220;TX:&#8221; prefix, followed by a unique transaction identifier. When a client needs an access token with this scope, it must add it to the scope request parameter of an authentication request, as in this example:</p>
<pre><code class="language-shell">https://idp/oauth2/authorize?scope=openid TX:1234&amp;...other params omitted</code></pre>
<p>The authentication provider will perform the standard validation steps and, eventually, will call our adapter to perform the validation logic:</p>
<pre><code class="language-java">private Consumer&lt;OAuth2AuthorizationCodeRequestAuthenticationContext&gt; dynamicScopesAuthenticationValidator() {
    return ctx -&gt; {
        OAuth2AuthorizationCodeRequestAuthenticationToken auth = ctx.getAuthentication();
        var requestedScopes = new HashSet&lt;&gt;(auth.getScopes());
        if ( requestedScopes.isEmpty() ) {
            return;
        }
        var registeredClient = ctx.getRegisteredClient();
        var allowedScopes = registeredClient.getScopes();
        requestedScopes.removeIf(allowedScopes::contains);
        if (requestedScopes.isEmpty() ) {
            return;
        }
        // Now, let's validate the remaining scopes using the provided validation service
        try {
            if (!dynamicScopeService.validate(registeredClient.getId(), requestedScopes)) {
                throw new OAuth2AuthorizationCodeRequestAuthenticationException(
                  new OAuth2Error(OAuth2ErrorCodes.INVALID_SCOPE),
                  auth
                );
            }
        } catch (Exception ex) {
            throw new OAuth2AuthorizationCodeRequestAuthenticationException(
              new OAuth2Error(OAuth2ErrorCodes.SERVER_ERROR), 
              ex, 
              auth
            );
        }
    };
}
</code></pre>
<p>The adapter receives an <em>OAuth2AuthorizationCodeRequestAuthenticationContext</em> instance that contains information about the authorization request. <strong>Notice that, at this point, the end user is not yet known, so we can&#8217;t use it as part of this logic</strong>.</p>
<p>These are the steps performed by the adapter:</p>
<ul>
<li>Firstly, recover the requested scopes and client information from the context.</li>
<li>Secondly, remove any static scopes from the list.</li>
<li>If there are no dynamic scopes, return immediately</li>
<li>Otherwise, pass the client identifier and dynamic scopes to the validation service</li>
</ul>
<p>The validation service&#8217;s job is to return just a yes/no answer depending on whether the request should be authorized. Our implementation, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eugenp/tutorials/tree/master/spring-security-modules/spring-security-auth-server/src/main/java/com/baeldung/auth/server/dynamicscopes/components/impl/DynamicScopeServiceImpl.java">available online</a>, just checks for a valid pattern. A real-world one would perform more complex validation, such as querying a database and applying any rules required by the specific use case.</p>
<h2 id="bd-consent-validation" data-id="consent-validation">7. Consent Validation</h2>
<div class="bd-anchor" id="consent-validation"></div>
<p>The other tweak we&#8217;ve made to support dynamic scopes is to add custom logic to determine whether the authorization needs to ask for user consent before issuing an access token. <strong>This was done using the AuthenticationProvider&#8217;s <em>setAuthorizationConsentRequentid()</em> method, which accepts a <em>Predicate</em> argument.</strong></p>
<p>This <em>Predicate</em> should return <em>true</em> when an authorization request needs user consent, and <em>false</em> otherwise. As we&#8217;ve done in the scope validation case, we&#8217;ll use an adapter to extract the client&#8217;s identifier and requested scopes from the <em>OAuth2AuthorizationCodeRequestAuthenticationContext</em> instance passed to the <em>Predicate</em>. Once the basic checks are done, we invoke the validation service to get a final decision about the consent:</p>
<pre><code class="language-java">private Predicate&lt;OAuth2AuthorizationCodeRequestAuthenticationContext&gt; dynamicScopesConsentValidator() {
    return ctx -&gt; {
        var previousConsent = ctx.getAuthorizationConsent();
        if ( previousConsent == null ) {
            // First consent, so consent is required
            return true;
        }
        OAuth2AuthorizationCodeRequestAuthenticationToken auth = ctx.getAuthentication();
        var requestedScopes = new HashSet&lt;&gt;(auth.getScopes());
        if ( requestedScopes.isEmpty() ) {
            return false;
        }
        // Remove already consented scopes
        var alreadyConsented = new HashSet&lt;&gt;(previousConsent .getScopes());		
        requestedScopes.removeIf(alreadyConsented::contains);
        if (requestedScopes.isEmpty() ) {
            return false;
        }
        // Any remaining scopes are dynamic scopes or static ones with no previous consent
        return dynamicScopeService.isConsentNeeded(ctx.getRegisteredClient().getId(), requestedScopes);
    };
}
</code></pre>
<p>A key distinction of this validation is that it happens after a successful authentication. <strong>This means that the business logic can use any of the standard Spring Security methods to get information about the current user and use it as part of the decision logic</strong>.</p>
<h2 id="bd-consent-page" data-id="consent-page">8. Consent Page</h2>
<div class="bd-anchor" id="consent-page"></div>
<p>This page will be displayed to the user after a successful authentication, right before issuing an authorization code to a client application.</p>
<p><strong>From the user agent&#8217;s perspective, this page will be accessed through a redirect from the login page</strong>. The <em>Location</em> header will point to the configured <em>URI</em> along with the following query parameters:</p>
<ul>
<li><em>client_id</em>: Client application&#8217;s identifier</li>
<li><em>scope</em>: Requested scopes</li>
<li><em>state</em>: Server-side state associated with the current authorization request.</li>
</ul>
<p><strong>Please note that the state parameter in this case has no relation to the one with the same name that&#8217;s included in the initial authorization <em>URL</em> created by the client!</strong></p>
<p>Let&#8217;s implement a simple controller that takes these parameters, prepares a Model, and forwards the request to a view:</p>
<pre><code class="language-java">@Controller
public class ConsentController {
    private final RegisteredClientRepository registeredClientRepository;
    private final OAuth2AuthorizationConsentService authorizationConsentService;
    // ... constructor omitted
    @GetMapping("/consent")
    public String consent(Principal principal, Model model,
      @RequestParam(name = OAuth2ParameterNames.CLIENT_ID) String clientId,
      @RequestParam(name = OAuth2ParameterNames.SCOPE) String scope,
      @RequestParam(name = OAuth2ParameterNames.STATE) String state) {
        var client = registeredClientRepository.findByClientId(clientId);
        assert client != null;
        var currentConsent =  authorizationConsentService.findById(client.getId(), principal.getName());
        Set&lt;String&gt; authorizedScopes = currentConsent != null ? currentConsent.getScopes() : Set.of();
        // Remove already authorized scopes from the requested scopes and the special 'openid' scope.
        var neededScopes = Set.of(scope.split(" ")).stream()
          .filter(s -&gt; !authorizedScopes.contains(s) &amp;&amp; !OidcScopes.OPENID.equals(s))
          .toList();
        model.addAttribute("clientId", clientId);
        model.addAttribute(
          "clientName", 
          client.getClientName() != null ? client.getClientName() : client.getClientId()
        );
        model.addAttribute("scopes", neededScopes);
        model.addAttribute("state", state);
        model.addAttribute("authorizedScopes", authorizedScopes);
        return "consent";
   }
}
</code></pre>
<p>We&#8217;ve injected two services in this controller to look for additional information needed to build the view:</p>
<ul>
<li><em>registeredClientRepository</em>: Allow us to retrieve detailed information for the client, given its identifier</li>
<li><em>authorizationConsentService</em>: Returns an <em>OAuthAuthorizationConsent</em> containing all consents given to a client by a user</li>
</ul>
<p>We use those services to build a list of the newly required consents and store it in a Spring MVC <em>Model</em>.</p>
<p>Here, we could also query a business service to enrich the <em>Model</em> with details related to any requested dynamic scope. For instance, if our dynamic scope represents a transfer request, we could look up its details and include them in the model.</p>
<p><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eugenp/tutorials/tree/master/spring-security-modules/spring-security-auth-server/src/main/resources/templates/consent.html">The view itself</a> uses a Thymeleaf template to display the consent request and present to the user an option to accept or deny it.<strong> Note that the form&#8217;s submit target is, by default, protected with a CSRF token, so make sure to use the th:action=&#8221;<em>@{/path}&#8221;</em> syntax</strong>. Thanks to <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.thymeleaf.org/doc/tutorials/3.1/thymeleafspring.html#integration-with-requestdatavalueprocessor">Thymeleaf&#8217;s Spring integration</a>,  this will automatically include a hidden input field with the correct CSRF token value.</p>
<p>Last, but not least, note that the form&#8217;s action target should point to the server&#8217;s OAuth2 authorization endpoint. Later, if we decide to change this <em>URI</em> from the default value, we must make sure that this form points to the same place.</p>
<h2 id="bd-testing" data-id="testing">9. Testing</h2>
<div class="bd-anchor" id="testing"></div>
<p>Now, let&#8217;s write a test to verify our customized authorization server&#8217;s behavior, at least for the &#8220;happy path&#8221; case. The <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eugenp/tutorials/tree/master/spring-security-modules/spring-security-auth-server/src/test/java/com/baeldung/auth/server/dynamicscopes/DynamicScopesAuthServerUnitTest.java">full code</a> is a bit long, so here we&#8217;ll just discuss the main caveats when implementing it.</p>
<p>The first thing is that, to validate end-to-end calls, it&#8217;s better to use an environment that&#8217;s close to the real one. This is why we use <em>WebEnvironment.RANDOM_PORT</em>, <strong>which starts a full servlet stack bound to a free local port.</strong> The port&#8217;s actual value is injected into the test using the <em>@LocalPort</em> annotation, so we can use it to build all request <em>URLs</em>.</p>
<p>Secondly, we need to be able to keep session cookies between requests sent throughout the authentication/authorization process. This means we need to configure a <em>RestTestClient</em> with a factory request with this support enabled.</p>
<p>In our case, there&#8217;s another issue. We can instruct a <em>RestTestClient</em>  to either follow all redirects or none. However, once configured, it&#8217;s not trivial to modify this behavior. We could opt to use manual redirect handling only, but that would require additional coding. <strong>Instead, we&#8217;ve opted to define two clients: one that follows redirects, and another that doesn&#8217;t</strong>. Both clients share the same <em>CookieManager</em> instance, so we can use either one in the test without getting into session-related issues:</p>
<pre><code class="language-java">@BeforeEach
void setupRestClient() {
    CookieManager cookieManager = new CookieManager();
    var followRedirectsHttpClient = HttpClient.newBuilder()
      .followRedirects(HttpClient.Redirect.ALWAYS)
      .cookieHandler(cookieManager)
      .build();
    var followRedirectsRequestFactory = new JdkClientHttpRequestFactory(followRedirectsHttpClient);
    restTestClient = RestTestClient
      .bindToServer(followRedirectsRequestFactory)
      .baseUrl("http://localhost:" + port)
      .build();
    var noRedirectsHttpClient = HttpClient.newBuilder()
      .followRedirects(HttpClient.Redirect.NEVER)
      .cookieHandler(cookieManager)
      .build();
    var noRedirectsRequestFactory = new JdkClientHttpRequestFactory(noRedirectsHttpClient);
    noRedirecRestTestClient = RestTestClient
      .bindToServer(noRedirectsRequestFactory)
      .baseUrl("http://localhost:" + port)
      .build();
}
</code></pre>
<p>Our &#8220;happy-path&#8221; test covers the following:</p>
<ul>
<li>Endpoint discovery</li>
<li>Building an authorization request with a dynamic scope</li>
<li>Submitting user credentials to the login form</li>
<li>Submitting values in the consent form</li>
<li>Using the generated authentication code to retrieve an access token</li>
<li>Validate that the access token has the request dynamic scope associated with it</li>
</ul>
<p><strong>Those steps cover a complete authorization flow, producing an access token that we can use to send a request to a resource server.</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 tutorial, we&#8217;ve seen how we can modify the Spring Authorization Server&#8217;s default configuration to support dynamic scopes. This expands the range of use cases where we can use it, making it a suitable candidate for complex authorization-related scenarios.</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-security-modules/spring-security-auth-server">over on GitHub</a>.</p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-spring-dynamic-authorization-scopes">Dynamic Authorization Scopes in Spring Authorization Server</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/958357049/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/958357049/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/958357049/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f11%2fSpring-Featured-Image-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/958357049/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/958357049/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/958357049/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-spring-dynamic-authorization-scopes#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-spring-dynamic-authorization-scopes/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/~/958357049/0/baeldung/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Featured-Image-11-150x150.jpg</webfeeds:featuredImage></item>
<item>
<feedburner:origLink>https://www.baeldung.com/java-weekly-651</feedburner:origLink>
		<title>Java Weekly, Issue 651</title>
		<link>https://feeds.feedblitz.com/~/958199051/0/baeldung</link>
					<comments>https://feeds.feedblitz.com/~/958199051/0/baeldung#respond</comments>
		
		<dc:creator><![CDATA[baeldung]]></dc:creator>
		<pubDate>Fri, 19 Jun 2026 15:01:01 +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=204055</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>Spring AI 2.0 is finally out. Time to start updating the course :)</p>
The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/958199051/0/baeldung">Java Weekly, Issue 651</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/958199051/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/958199051/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/958199051/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/958199051/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/958199051/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-651#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-651/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/15/spring-ai-composable-tool-calling">&gt;&gt; Tool Calling in Spring AI 2.0: A Composable, Agentic Architecture</a></strong> [<span style="color: #993300;">spring.io</span>]</p>
<p>Spring AI 2.0 is out this week (see below) and turns tool calling into a first-class element, making agent loops easier to observe, compose, and extend. A strong read for anyone building agentic systems on Spring who needs more than a black-box function call.</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/06/15/java-microservices-fast-go-2026-benchmark/" target="_blank" rel="noopener"><strong>Can Java Microservices Be As Fast As Go? A 2026 Benchmark Update</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-quarkus-data/" target="_blank" rel="noopener"><strong>Introducing Quarkus Data: One Gateway for Data Access</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://blog.frankel.ch/bigdecimal-vs-double/" target="_blank" rel="noopener"><strong>double, BigDecimal, or Fixed-Point?</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://foojay.io/today/spring-quarkus-jooby-vertx-java-framework-comparison/" target="_blank" rel="noopener"><strong>Spring vs Quarkus vs Jooby vs Vert.x: Pick Your Next Java Framework</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/06/spring-boot-4-1/" target="_blank" rel="noopener"><strong>Spring Boot 4.1 Adds gRPC Auto-Configuration, SSRF Mitigation, and Kotlin 2.3 Support</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/news/2026/06/oracle-genai-policies/" target="_blank" rel="noopener"><strong>Oracle&#8217;s OpenJDK Bans Generative AI Contributions While Oracle&#8217;s GraalVM Allows Them</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://inside.java/2026/06/14/cline-migrate-java-oca/" target="_blank" rel="noopener"><strong>How Agentic Coding Can Help You Migrate Java Applications Faster</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/06/11/thesis-simplify-weak-reference-processing-zgc/" target="_blank" rel="noopener"><strong>Simplifying Weak Reference Processing in ZGC</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/observing-apache-netbeans-with-opentelemetry-and-dash0/" target="_blank" rel="noopener"><strong>Observing Apache NetBeans with OpenTelemetry and Dash0</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/ask-a-lille-dev-what-java-developers-really-think-about-quality-frameworks-communities-and-careers/" target="_blank" rel="noopener"><strong>Ask a Lille Dev: What Java Developers Really Think About Quality, Frameworks, Communities, and Careers</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.christianposta.com/difference-between-microservices-and-ai-agents/" target="_blank" rel="noopener"><strong>The Differences Between Microservices and AI Agents</strong></a> [<span style="color: #800000;">christianposta.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://martinfowler.com/articles/reliable-llm-bayer.html" target="_blank" rel="noopener"><strong>Building Reliable Agentic AI Systems</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://foojay.io/today/did-ai-just-break-software-security-for-ever/" target="_blank" rel="noopener"><strong>Did AI Just Break Software Security For Ever?</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.scottlogic.com/2026/06/16/ponytail-yagni-and-the-problem-with-prompt-benchmarks.html" target="_blank" rel="noopener"><strong>Ponytail? YAGNI! And the Problem with Prompt Benchmarks</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/06/16/What-a-good-transformation-looks-like.html" target="_blank" rel="noopener"><strong>What a Good Transformation Looks Like</strong></a> [<span style="color: #800000;">scottlogic.com</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-98/" target="_blank" rel="noopener"><strong>Foojay Podcast #98: The End of JNI Pain: How WebAssembly Is Quietly Replacing Native Libraries 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://www.infoq.com/presentations/java-aws-serverless/" target="_blank" rel="noopener"><strong>Practical Performance Tuning for Serverless Java on AWS</strong></a> [<span style="color: #800000;">infoq.com</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://spring.io/blog/2026/06/12/spring-ai-2-0-0-GA-available-now" target="_blank" rel="noopener"><strong>Spring AI 2.0 is here!</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/06/15/spring-tools-5-2-0-released" target="_blank" rel="noopener"><strong>Spring Tools 5.2.0 released</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/06/12/spring-ai-1-1-8-1-0-9-avaialble-now" target="_blank" rel="noopener"><strong>Spring AI 1.0.9, 1.1.8 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://in.relation.to/2026/06/16/orm-80-beta1/" target="_blank" rel="noopener"><strong>Hibernate 8.0.0.Beta1</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://github.com/quarkusio/quarkus/releases/tag/3.37.0" target="_blank" rel="noopener"><strong>Quarkus 3.37.0</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/micronaut-projects/micronaut-core/releases/tag/v5.1.1" target="_blank" rel="noopener"><strong>Micronaut Core 5.1.1</strong></a> [<span style="color: #800000;">github.com/micronaut-projects</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/helidon-io/helidon/releases/tag/4.5.0" target="_blank" rel="noopener"><strong>Helidon 4.5.0</strong></a> [<span style="color: #800000;">github.com/helidon-io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/apache/grails-core/releases/tag/v7.1.2" target="_blank" rel="noopener"><strong>Grails 7.1.2</strong></a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/apache/grails-core/releases/tag/v7.0.12" target="_blank" rel="noopener"><strong>7.0.12</strong></a> [<span style="color: #800000;">github.com/apache</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.14" target="_blank" rel="noopener"><strong>Zuul 3.6.14</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://www.wildfly.org/news/2026/06/11/A2A-Jakarta-1-0-0-CR1-is-released/" target="_blank" rel="noopener"><strong>A2A Jakarta 1.0.0.CR1 is released!</strong></a> [<span style="color: #800000;">wildfly.org</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>Paul Graham&#8217;s short essay is a crisp reminder that the most valuable ideas are often both broadly useful and genuinely surprising. It is a useful lens for technical work too: even a small amount of novelty can matter when it attaches to a big enough idea.</p>
<p><strong><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.paulgraham.com/sun.html">&gt;&gt; General and Surprising</a></strong> [<span style="color: #993300;">paulgraham.com</span>]</p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-weekly-651">Java Weekly, Issue 651</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/958199051/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/958199051/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/958199051/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/958199051/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/958199051/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/958199051/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-651#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-651/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/~/958199051/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/java-commonmark-render-markdown</feedburner:origLink>
		<title>Markdown Rendering Using commonmark-java</title>
		<link>https://feeds.feedblitz.com/~/958199054/0/baeldung</link>
					<comments>https://feeds.feedblitz.com/~/958199054/0/baeldung#respond</comments>
		
		<dc:creator><![CDATA[Olayemi Michael]]></dc:creator>
		<pubDate>Fri, 19 Jun 2026 14:47:26 +0000</pubDate>
				<category><![CDATA[Java Web]]></category>
		<category><![CDATA[HTML]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/java-commonmark-render-markdown</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-10-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 the CommonMark library to parse Markdown into HTML and convert HTML back into Markdown.</p>
The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/958199054/0/baeldung">Markdown Rendering Using commonmark-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/958199054/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/958199054/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f07%2fJava-Featured-10-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/958199054/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/958199054/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/958199054/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-commonmark-render-markdown#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-commonmark-render-markdown/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-10-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-10-1024x536.jpg 1024w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-10-300x157.jpg 300w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-10-768x402.jpg 768w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-10-100x52.jpg 100w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-10.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>Manipulating Markdown content is a common programming task. <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/commonmark/commonmark-java">CommonMark</a> is a Java library that simplifies working with Markdown documents.</p>
<p>In this tutorial, we&#8217;ll learn how to manipulate Markdown content using the library. We&#8217;ll see how to parse Markdown into HTML and convert HTML back into Markdown. Finally, we&#8217;ll explore how to customize nodes for advanced processing.</p>
<h2 id="bd-commonmark-java-library" data-id="commonmark-java-library">2. <em>commonmark-java</em> Library</h2>
<div class="bd-anchor" id="commonmark-java-library"></div>
<p>The <em>commonmark-java</em> library provides classes and interfaces for working with Markdown content based on the CommonMark specification. <strong>It allows us to parse Markdown into HTML and convert HTML back into Markdown</strong>. Additionally, it provides access to the Abstract Syntax Tree (AST), enabling further customization and processing.</p>
<p>To use the <em>common-java</em> library, let&#8217;s add the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/org.commonmark/commonmark"><em>commonmark</em></a> dependency to our <em>pom.xml</em>:</p>
<pre><code class="language-xml">&lt;dependency&gt;
    &lt;groupId&gt;org.commonmark&lt;/groupId&gt;
    &lt;artifactId&gt;commonmark&lt;/artifactId&gt;
    &lt;version&gt;0.28.0&lt;/version&gt;
&lt;/dependency&gt;</code></pre>
<p>The commark dependency provides classes such as <em>Parser</em>, <em>HtmlRenderer</em>, and <em>MarkdownRenderer </em>for Markdown processing and rendering.</p>
<p>Additionally, CommonMark provides extension dependencies for more advanced processing features. Examples include <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/org.commonmark/commonmark-ext-gfm-tables"><em>commonmark-ext-gfm-tables</em></a> for GitHub Flavored Markdown tables and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/org.commonmark/commonmark-ext-gfm-alerts"><em>commonmark-ext-gfm-alerts</em></a> for alert blocks.</p>
<h2 id="bd-parsing-and-rendering-markdown-to-html" data-id="parsing-and-rendering-markdown-to-html">3. Parsing and Rendering Markdown to HTML</h2>
<div class="bd-anchor" id="parsing-and-rendering-markdown-to-html"></div>
<p>Moving on, let&#8217;s see one of the most common uses of the library: parsing Markdown and rendering it as HTML.</p>
<p>First, let&#8217;s define a method named <em>markDownToHtml()</em>:</p>
<pre><code class="language-java">public static String markDownToHtml(String markdown) {
    Parser parser = Parser.builder().build();
    Node document = parser.parse(markdown);
    HtmlRenderer renderer = HtmlRenderer.builder().build();
    return renderer.render(document);
}</code></pre>
<p>Here, we create an instance of <em>Parser</em> to parse Markdown input into a document node. Next, <strong>we create an <em>HtmlRenderer</em> instance to render the parsed node as HTML</strong>.</p>
<p>A <em>Node</em> represents an element in the parsed Markdown document tree.</p>
<p>Then, let&#8217;s write a unit test to verify the result:</p>
<pre><code class="language-java">@Test
void givenMarkdownInput_whenConvertingToHtml_thenReturnRenderedHtml() {
    String html = markDownToHtml("Welcome to *Baeldung*");
    assertEquals("&lt;p&gt;Welcome to &lt;em&gt;Baeldung&lt;/em&gt;&lt;/p&gt;\n", html);
}</code></pre>
<p>In the code above, we pass a string containing Markdown syntax to <em>markDownToHtml()</em> method. Since <em>Baeldung</em> is wrapped in asterisks (<em>*</em>), the Markdown parser interprets it as emphasized text. Consequently, the renderer converts it to an HTML <em>&lt;em&gt;</em> element.</p>
<h2 id="bd-processing-parsed-nodes" data-id="processing-parsed-nodes">4. Processing Parsed Nodes</h2>
<div class="bd-anchor" id="processing-parsed-nodes"></div>
<p>Furthermore, we can use a <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-visitor-pattern">visitor</a> to further process nodes in the parsed document tree. The library allows us to extend the <em>AbstractVisitor</em> class to process a node.</p>
<p>Let&#8217;s create a visitor class that counts every word in a sentence:</p>
<pre><code class="language-java">class WordCountVisitor extends AbstractVisitor {
    int wordCount = 0;
    @Override
    public void visit(Text text) {
        wordCount += text.getLiteral().split("\\w+").length;
        visitChildren(text);
    }
}</code></pre>
<p>Next, let&#8217;s write a method that uses the visitor:</p>
<pre><code class="language-java">public static int processParsedNode(String markdown) {
    Parser parser = Parser.builder().build();
    Node node = parser.parse(markdown);
    WordCountVisitor visitor = new WordCountVisitor();
    node.accept(visitor);
    return visitor.wordCount;
}</code></pre>
<p>In the method above, we create a <em>WordCountVisitor</em> object and pass it to the parsed node for processing.</p>
<p>Let&#8217;s write a unit test to confirm the method:</p>
<pre><code class="language-java">@Test
void givenMarkdownInput_whenProcessingParsedNode_thenReturnWordCount() {
    int wordCount = processParsedNode("Welcome to *Baeldung*");
    assertEquals(3, wordCount);
}</code></pre>
<p>Here, we verify that the expected word count is equal to the actual word count.</p>
<h2 id="bd-rendering-html-to-markdown" data-id="rendering-html-to-markdown">5. Rendering HTML to Markdown</h2>
<div class="bd-anchor" id="rendering-html-to-markdown"></div>
<p>Furthermore, CommonMark also provides classes for rendering HTML-like document structures into Markdown format, making it a library for both HTML and Markdown processing.</p>
<p>Let&#8217;s see this in action by writing code that converts an HTML heading into a Markdown format:</p>
<pre><code class="language-java">public static String htmlToMarkDown(String htmlHeading) {
    Heading heading = new Heading();
    heading.setLevel(2);
    heading.appendChild(new Text(htmlHeading));
    Document document = new Document();
    document.appendChild(heading);
    MarkdownRenderer renderer = MarkdownRenderer.builder()
      .build();
    return renderer.render(document);
}</code></pre>
<p>In the code above, we create a <em>Heading</em> object and set the level to <em>2</em>, which represents an H2 heading. Next, we append a  <em>Text</em> object containing the heading content. <strong>Finally, we use the <em>MarkdownRenderer</em> builder to render the document as Markdown</strong>.</p>
<p>Next, let&#8217;s write a unit test to confirm the output:</p>
<pre><code class="language-java">@Test
void givenHeadingText_whenConvertingToMarkdown_thenReturnMarkdownHeading() {
    String markdown = htmlToMarkDown("Java Tutorial");
    assertEquals("## Java Tutorial\n", markdown);
}</code></pre>
<p>In the code above, we verify that the renderer correctly converts the document structure into a valid Markdown output.</p>
<h2 id="bd-customizing-html-rendering" data-id="customizing-html-rendering">6. Customizing HTML Rendering</h2>
<div class="bd-anchor" id="customizing-html-rendering"></div>
<p>Furthermore, CommonMark allows us to customize rendered HTML attributes using the <em>AttributeProvider</em> interface.</p>
<p>Let&#8217;s see this in action by implementing a class custom image attribute provider:</p>
<pre><code class="language-java">public class ImageAttributeProvider implements AttributeProvider {
    @Override
    public void setAttributes(Node node, String tagName, Map&lt;String, String&gt; attributes) {
        if (node instanceof Image) {
            attributes.put("class", "border");
        }
    }
}</code></pre>
<p>In the code above, we create a class named <em>ImageAttributeProvider</em> that implements the <em>AttributeProvider</em> interface. Inside the <em>setAttributes()</em> method, we check whether the current node is an instance of <em>Image</em>. If it is, we add a <em>class</em> attribute with the value &#8220;<em>border</em>&#8220;.</p>
<p>Next, let&#8217;s write a method that applies the custom attribute provider during HTML rendering:</p>
<pre><code class="language-java">public static String changingHtmlAttribute(String source) {
    Parser parser = Parser.builder()
      .build();
    Node node = parser.parse(source);
    HtmlRenderer renderer = HtmlRenderer.builder()
      .attributeProviderFactory(context -&gt; new ImageAttributeProvider())
      .build();
    return renderer.render(node);
}</code></pre>
<p>In the code above, we customize the <em>HtmlRenderer</em> with a custom attribute provider using the <em>attributeProviderFactory()</em> method. Using <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-8-lambda-expressions-tips">lambda expressions</a>, we invoke our provider for each node, allowing us to customize the generated HTML attributes. Every rendered image element receives a <em>class=&#8221;border&#8221;</em> attribute.</p>
<p>Finally, let&#8217;s write a unit test to ascertain the customized output:</p>
<pre><code class="language-java">@Test
void givenImageMarkdown_whenRenderingHtml_thenAddCustomClassAttribute() {
    String html = changingHtmlAttribute("![text](/url.png)");
    assertEquals("&lt;p&gt;&lt;img src=\"/url.png\" alt=\"text\" class=\"border\" /&gt;&lt;/p&gt;\n", html);
}</code></pre>
<p>In the test above, we verify that the custom attribute provider successfully adds the <em>border</em> CSS class to rendered image elements.</p>
<h2 id="bd-customizing-render-node" data-id="customizing-render-node">7. Customizing Render Node</h2>
<div class="bd-anchor" id="customizing-render-node"></div>
<p>Moreover, the library allows us to customize how specific nodes are rendered by implementing the <em>NodeRenderer</em> interface.</p>
<p>Let&#8217;s create a custom renderer for <em>IndentedCodeBlock</em> nodes:</p>
<pre><code class="language-java">public class IndentedCodeBlockNodeRenderer implements NodeRenderer {
    private final HtmlWriter html;
    public IndentedCodeBlockNodeRenderer(HtmlNodeRendererContext context) {
        this.html = context.getWriter();
    }
    @Override
    public Set&lt;Class&lt;? extends Node&gt;&gt; getNodeTypes() {
        return Set.of(IndentedCodeBlock.class);
    }
    @Override
    public void render(Node node) {
        IndentedCodeBlock codeBlock = (IndentedCodeBlock) node;
        html.line();
        html.tag("pre");
        html.text(codeBlock.getLiteral());
        html.tag("/pre");
        html.line();
    }
}</code></pre>
<p>In the code above, we implement the <em>NodeRenderer</em> interface and specify that the renderer handles <em>IndentedCodeBlock</em> nodes through the <em>getNodeTypes()</em> method.</p>
<p>Then, in the <em>render()</em> method, we manually generate the HTML output for the code block using <em>HtmlWriter</em>.</p>
<p>Next, let&#8217;s register the custom renderer with <em>HtmlRenderer</em>:</p>
<pre><code class="language-java">public static String customizingHtmlRendering(String source) {
    Parser parser = Parser.builder()
      .build();
    Node node = parser.parse(source);
    HtmlRenderer renderer = HtmlRenderer.builder()
      .nodeRendererFactory(IndentedCodeBlockNodeRenderer::new)
      .build();
    return renderer.render(node);
}</code></pre>
<p>Here, we customize the renderer using the <em>nodeRendererFactory()</em> method. We invoke the custom node using a <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-method-references">method reference</a>. This allows the renderer to delegate matching nodes to our custom implementation during HTML generation</p>
<p>Let&#8217;s write a unit test to verify the <em>customizingHtmlRendering()</em> method:</p>
<pre><code class="language-java">@Test
void givenIndentedCodeBlock_whenRenderingHtml_thenUseCustomNodeRenderer() {
    String html = customizingHtmlRendering("Example:\n\n    code");
    assertEquals("&lt;p&gt;Example:&lt;/p&gt;\n&lt;pre&gt;code\n&lt;/pre&gt;\n", html);
}</code></pre>
<p>Here, we verify that indented code blocks are rendered using our custom renderer implementation.</p>
<h2 id="bd-conclusion" data-id="conclusion">8. Conclusion</h2>
<div class="bd-anchor" id="conclusion"></div>
<p>In this article, we learned how to use the CommonMark library to parse Markdown into HTML and convert HTML back into Markdown. Additionally, we saw how to customize HTML attributes and node rendering for more advanced processing scenarios.</p>
<p>As always, the source code for the sample code is available <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eugenp/tutorials/tree/master/libraries-formatting">over on GitHub</a>.</p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-commonmark-render-markdown">Markdown Rendering Using commonmark-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/958199054/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/958199054/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/958199054/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f07%2fJava-Featured-10-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/958199054/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/958199054/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/958199054/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-commonmark-render-markdown#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-commonmark-render-markdown/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/~/958199054/0/baeldung/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-10-150x150.jpg</webfeeds:featuredImage></item>
<item>
<feedburner:origLink>https://www.baeldung.com/java-weekly-650</feedburner:origLink>
		<title>Java Weekly, Issue 650</title>
		<link>https://feeds.feedblitz.com/~/957980303/0/baeldung</link>
					<comments>https://feeds.feedblitz.com/~/957980303/0/baeldung#respond</comments>
		
		<dc:creator><![CDATA[baeldung]]></dc:creator>
		<pubDate>Fri, 12 Jun 2026 12:45:26 +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=204004</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>All about performance in Java this week.</p>
The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/957980303/0/baeldung">Java Weekly, Issue 650</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/957980303/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/957980303/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/957980303/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/957980303/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/957980303/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-650#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-650/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/06/09/jdk-26-performance-improvements">&gt;&gt; Performance Improvements in JDK 26</a></strong> [<span style="color: #993300;">inside.java</span>]</p>
<p>A tour of where JDK 26 makes Java faster in practice. It is especially handy because it groups the changes by where they show up in real apps: startup, allocation pressure, throughput, and carrier-thread scalability. Always great to see how quickly the JDK is evolving.</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/06/10/hat-tensors-computation" target="_blank" rel="noopener"><strong>Exploiting GPU Tensor Cores from Java using Babylon</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://blog.vanillajava.blog/2026/06/why-you-should-tun-code-before-your.html" target="_blank" rel="noopener"><strong>Why You Should Tune Code Before Your Garbage Collector</strong></a> [<span style="color: #800000;">vanillajava.blog</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://blog.vanillajava.blog/2026/06/testing-java-memory-management-with.html" target="_blank" rel="noopener"><strong>Testing Java Memory Management with Chronicle-FIX using AI</strong></a> [<span style="color: #800000;">vanillajava.blog</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://foojay.io/today/your-tls-stack-is-lying-about-zero-copy/" target="_blank" rel="noopener"><strong>Your TLS Stack Is Lying to You About Zero-Copy</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/spring-ai-agents-no-second-runtime/" target="_blank" rel="noopener"><strong>Why Spring Teams Don&#8217;t Need a Second Runtime for 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://foojay.io/today/spring-boot-migration-and-the-cra-when-good-enough-isnt/" target="_blank" rel="noopener"><strong>Spring Boot Migration and the CRA: When Good Enough Isn&#8217;t</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/05/jep538-target-jdk27" target="_blank" rel="noopener"><strong>JEP targeted to JDK 27: 538: PEM Encodings of Cryptographic Objects (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://blog.johanneslink.net/2026/06/09/the-jqwik-anti-ai-affair/" target="_blank" rel="noopener"><strong>The Jqwik Anti-AI Affair</strong></a> [<span style="color: #800000;">johanneslink.net</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://foojay.io/today/metal-default-a-new-build-cloud-and-a-new-format/" target="_blank" rel="noopener"><strong>Codename One: Metal Default, A New Build Cloud, And A New Format</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://dandreamsofcoding.com/2026/06/06/negotiating-vendor-contracts/" target="_blank" rel="noopener"><strong>Negotiating Vendor Contracts</strong></a> [<span style="color: #800000;">dandreamsofcoding.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://netflixtechblog.medium.com/a-human-augmenting-agentic-workflow-for-causal-inference-4623f0a9c5af" target="_blank" rel="noopener"><strong>A Human-Augmenting Agentic Workflow for Causal Inference</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://lucumr.pocoo.org/2026/6/10/gaslighting/" target="_blank" rel="noopener"><strong>Gaslighting Openness</strong></a> [<span style="color: #800000;">lucumr.pocoo.org</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.infoq.com/articles/adoption-curve-twenty/" target="_blank" rel="noopener"><strong>The Technology Adoption Curve, Twenty Years On</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.scottlogic.com/2026/06/09/rethinking-transformation-long-term-capability.html" target="_blank" rel="noopener"><strong>Rethinking &#8216;Transformation&#8217;: From projects to long-term capability</strong></a> [<span style="color: #800000;">scottlogic.com</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/06/07/java-next-language-features" target="_blank" rel="noopener"><strong>JavaNext Language Features</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/quarkus-insights-250-quarkus-data/" target="_blank" rel="noopener"><strong>Quarkus Insights #250: What&#8217;s New with Quarkus Data</strong></a> [<span style="color: #800000;">quarkus.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://spring.io/blog/2026/06/10/spring-batch-6-0-4-and-5-2-6-available-now" target="_blank" rel="noopener"><strong>Spring Batch 6.0.4 and 5.2.6 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://spring.io/blog/2026/06/10/spring-for-graphql-1-4-6-and-2-0-4-released" target="_blank" rel="noopener"><strong>Spring for GraphQL 1.4.6 and 2.0.4 released</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/06/10/spring-grpc-1-1-0-available-now" target="_blank" rel="noopener"><strong>Spring gRPC 1.1.0 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://spring.io/blog/2026/06/10/spring-vault-4-0-3-3-2-1-available" target="_blank" rel="noopener"><strong>Spring Vault 4.0.3 and 3.2.1 available</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/quarkusio/quarkus/releases/tag/3.36.2" target="_blank" rel="noopener"><strong>Quarkus 3.36.2</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/eclipse-vertx/vert.x/releases/tag/5.1.2" target="_blank" rel="noopener"><strong>Vert.x 5.1.2</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/micrometer-metrics/micrometer/releases/tag/v1.17.0" target="_blank" rel="noopener"><strong>Micrometer 1.17.0</strong></a> [<span style="color: #800000;">github.com/micrometer-metrics</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.11" target="_blank" rel="noopener"><strong>Zuul 3.6.11</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://inside.java/2026/06/08/java-vscode-extension-update" target="_blank" rel="noopener"><strong>Oracle Java Extension for Visual Studio Code Version 26.0.0 Is Now Available</strong></a> [<span style="color: #800000;">inside.java</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>My first course focused on building with AI is out, and the series is getting started:</p>
<p><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://ai.baeldung.com"><strong>&gt;&gt; Foundations of AI Coding</strong></a></p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-weekly-650">Java Weekly, Issue 650</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/957980303/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/957980303/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/957980303/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/957980303/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/957980303/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/957980303/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-650#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-650/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/~/957980303/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/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#comments</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[popular]]></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;" loading="lazy" /><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#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/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>&nbsp;
<div style="clear:left;"><a rel="NOFOLLOW" href="https://www.baeldung.com/java-codename-one-cross-platform#comments"><h3>Comments</h3></a><ul><li><a rel="NOFOLLOW" href="https://www.baeldung.com/java-codename-one-cross-platform#comment-15697">In reply to Steve Hannah.   Hey, Steve.   Thanks for the ...</a> <i>by Ulisses Lima</i><li><a rel="NOFOLLOW" href="https://www.baeldung.com/java-codename-one-cross-platform#comment-15696">Holy cow. Great comprehensive tutorial, Francesco! An ...</a> <i>by Steve Hannah</i></ul></div>&#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" loading="lazy" 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="auto, (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 loading="lazy" 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#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/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>&nbsp;
<div style="clear:left;"><a rel="NOFOLLOW" href="https://www.baeldung.com/java-codename-one-cross-platform#comments"><h3>Comments</h3></a><ul><li><a rel="NOFOLLOW" href="https://www.baeldung.com/java-codename-one-cross-platform#comment-15697">In reply to Steve Hannah.   Hey, Steve.   Thanks for the ...</a> <i>by Ulisses Lima</i><li><a rel="NOFOLLOW" href="https://www.baeldung.com/java-codename-one-cross-platform#comment-15696">Holy cow. Great comprehensive tutorial, Francesco! An ...</a> <i>by Steve Hannah</i></ul></div>&#160;</div>]]>
</content:encoded>
					
					<wfw:commentRss>https://feeds.feedblitz.com/~/957869417/0/baeldung/feed</wfw:commentRss>
			<slash:comments>2</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>
</channel></rss>

