<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Code Wiz on Medium]]></title>
        <description><![CDATA[Stories by Code Wiz on Medium]]></description>
        <link>https://medium.com/@code.wizzard01?source=rss-3093c328bad8------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/0*QEgSE3RTO1VzGgi4</url>
            <title>Stories by Code Wiz on Medium</title>
            <link>https://medium.com/@code.wizzard01?source=rss-3093c328bad8------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sat, 14 Nov 2026 02:30:41 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@code.wizzard01/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[AI coding agents are only as useful as the context you give them.]]></title>
            <link>https://medium.com/@code.wizzard01/ai-coding-agents-are-only-as-useful-as-the-context-you-give-them-9a4840a7925c?source=rss-3093c328bad8------2</link>
            <guid isPermaLink="false">https://medium.com/p/9a4840a7925c</guid>
            <category><![CDATA[agentic-ai]]></category>
            <category><![CDATA[ai-coding-assistant]]></category>
            <category><![CDATA[ai-agent]]></category>
            <dc:creator><![CDATA[Code Wiz]]></dc:creator>
            <pubDate>Sun, 03 May 2026 10:04:41 GMT</pubDate>
            <atom:updated>2026-05-03T10:04:41.897Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*vfgCFzcGO1r0SO07vzSNQQ.png" /></figure><p>AI coding agents are only as useful as the context you give them. Feed too little and they generate generic code that ignores your architecture. Feed too much — a 500-line style guide, every ADR, every naming convention — and you burn through the context window before the real work begins, leaving the model slower and less accurate.</p><p>The real problem is not what to tell the agent. It is <em>when</em>. Your deployment checklist is irrelevant when you are writing a unit test. Your frontend component rules should not be loaded when you are tuning a SQL query. Static system prompts cannot make that distinction.</p><p>Agent Skills solve this with on-demand context loading: the agent reads only a short description for each skill at startup, then pulls the full instructions only when your task actually calls for them.</p><h3>What are Agent Skills?</h3><p>Agent Skills are a lightweight, open format for extending AI agent capabilities with specialized knowledge and workflows. The <a href="https://agentskills.io/">agentskills.io</a> specification is standardizing this so skills remain portable across different AI products.</p><p>At its core, a skill is a folder containing a SKILL.md file. This file has YAML frontmatter (name and description) followed by the instructions that tell the agent how to perform a specific task. Skills can also bundle scripts, reference materials, and templates.</p><pre>my-skill/<br>├── SKILL.md            # Required: metadata + instructions<br>├── scripts/            # Optional: executable code<br>├── references/         # Optional: documentation<br>├── assets/             # Optional: templates, resources<br>└── ...</pre><h3>Where skills live</h3><p>The directory depends on which AI tool you’re using:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/633/1*qMjVODXO9xgCyA1XEZspEw.png" /></figure><h3>Why not AGENTS.md or rules?</h3><p>AGENTS.md/Claude.md/Cursor rules all solve the same problem: telling the agent how to behave. The difference is <em>when</em> the context loads.</p><p><strong>AGENTS.md</strong> is read at the start of every session. Every token in that file counts against your context window for every task, whether you’re writing docs, debugging a race condition, or refactoring a database layer.</p><p><strong>Skills load on demand.</strong> The agent reads only the metadata (name and description) at startup. The full skill body is fetched only when your prompt matches the description. This means:</p><ul><li>A 500-line deployment checklist skill costs near-zero tokens until you ask about deployments.</li><li>You can maintain 50+ skills without bloating every conversation.</li><li>Skills are portable: the same .cursor/skills/ folder works in Cursor today and any future tool that adopts the agentskills.io spec.</li><li>Skills can bundle executable scripts and reference files that load incrementally during execution.</li></ul><p>The right mental model: AGENTS.md is a standing order, a skill is a specialist you call when you need them.</p><h3>How the agent discovers and uses a skill</h3><p>The agent works in three stages:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/810/1*owNYG7fdi-fcxE9HjgUcgQ.png" /></figure><p><strong>Stage 1 — Discovery:</strong> When the agent starts, it scans the skills directory and reads only the YAML frontmatter (name and description) from every SKILL.md. This is a tiny token cost even with dozens of skills.</p><p><strong>Stage 2 — Activation:</strong> When you send a prompt, the agent compares your request against the description of each skill. If it matches, the full SKILL.md body is loaded and injected into the LLM context alongside your prompt.</p><p><strong>Stage 3 — Execution:</strong> The LLM now has the skill instructions and follows them. If the skill references scripts or resource files, those are fetched or run on demand as the task progresses.</p><h3>Creating a custom skill</h3><p>First create the directory and file:</p><pre>mkdir -p .agent/skills/frontend-component<br>touch .agent/skills/frontend-component/SKILL.md</pre><p>The SKILL.md needs YAML frontmatter for discovery, followed by the instructions:</p><pre>---<br>name: frontend-component<br>description: Use this skill when building or modifying a UI component.<br>---<br># Frontend component skill example<br>1. Use our internal CSS Modules system. Do not use Tailwind CSS.<br>2. Ensure every interactive element includes an `aria-label`.<br>3. Read `tokens.json` in this directory to get the exact hex codes for our brand colors before styling.<br>4. After implementation, run `npm run lint:css` to verify compliance.<br></pre><p>The description field is what the agent pattern-matches against your prompt at discovery time. Make it specific. &quot;Use this skill when building or modifying a UI component&quot; is better than &quot;frontend skill&quot; because the agent needs enough context to trigger it correctly and to <em>not</em> trigger it when it&#39;s irrelevant.</p><h3>How skills are invoked</h3><p>Skills can be triggered in two ways: explicitly by the user or automatically by the agent.</p><h3>Manual invocation with /</h3><p>Most AI tools support a slash-command shortcut. Type / followed by the skill name and the agent immediately loads that skill&#39;s full body, regardless of whether your prompt text would have matched it automatically.</p><pre>/frontend-component Build the UserCard component<br>/test Write tests for the payment module<br>/seo-blog Add meta tags to the about page</pre><p>This is useful when you know exactly which workflow you want. The agent skips the pattern-matching step and jumps straight to execution. It is also a good safety net for skills with broad descriptions that might not trigger consistently.</p><h3>Automatic discovery</h3><p>When you write a natural-language prompt without a slash command, the agent compares your request against the description field of every skill in the directory. If the description is a strong semantic match, the skill activates automatically.</p><pre># User types:<br>&quot;I need to build a responsive navigation bar&quot;<br><br># Agent matches description:<br>&quot;Use this skill when building or modifying a UI component.&quot;<br># → SKILL.md body is loaded automatically</pre><p>The quality of the description determines how reliable automatic discovery is. A vague description like &quot;frontend skill&quot; will either fire too broadly or miss the right moments. A precise description scoped to a specific action prevents unintended activations.</p><p>Invocation modeWhen to use/skill-name promptYou know exactly which skill appliesNatural languageConversational workflows where the agent picks the right tool</p><h3>Example: an SEO skill for website development</h3><p>Here is a practical skill you would add when building or maintaining a content-heavy website. Save it at .github/skills/seo-blog/SKILL.md (or the equivalent path for your tool):</p><pre>---<br>name: seo-blog<br>description: Use this skill when adding, reviewing, or optimising SEO metadata for a web page or blog post, including meta tags, Open Graph tags, structured data, and sitemap entries.<br>---<br><br># SEO skill<br><br>## Checklist for every page<br>1. **Title tag** - 50-60 characters, primary keyword near the front.<br>2. **Meta description** - 120-158 characters, includes the keyword and a clear value statement.<br>3. **Open Graph tags** - og:title, og:description, og:image (1200×630 px), og:url.<br>4. **Twitter/X card** - twitter:card, twitter:title, twitter:description, twitter:image.<br>5. **Canonical URL** - set link rel=&quot;canonical&quot; on every page to prevent duplicate indexing.<br>6. **Structured data** - add JSON-LD Article schema for blog posts, WebPage for static pages.<br>7. **Sitemap** - verify the page is included in sitemap.xml with the correct lastmod date.<br>8. **Image alt text** - every img tag must have a descriptive alt attribute.<br>## Naming conventions<br>- Slug format: kebab-case, maximum 5 words, keyword first. Example: spring-boot-caching-guide.<br>- Keep slugs stable after publication; redirects cost crawl budget.<br>## Validation<br>After making changes, run:<br>  npm run build &amp;&amp; npx lighthouse http://localhost:3000/your-page --only-categories=seo<br>Target score: 95 or above. Fix any flagged issues before merging.<br></pre><p>Invoke this skill manually when writing a new post (/seo-blog Add metadata to the Java caching guide) or let it fire automatically whenever your prompt mentions SEO, meta tags, or Open Graph.</p><h3>Creating skills for each stage of SDLC</h3><p>Here are a few examples of skills you can create for each stage of SDLC:</p><h3>Brainstorming and definition (/spec)</h3><p>Instruct the agent to interview you before writing code. It clarifies edge cases, API boundaries, and performance constraints, then outputs a strict technical specification.</p><h3>Planning (/plan)</h3><p>The agent takes the specification and breaks it down into small, verifiable units, mapping out exact file changes.</p><h3>Coding (/frontend or /backend)</h3><p>Enforce use of specific internal component libraries and architectural rules. This prevents the LLM from generating generic, outdated solutions.</p><h3>Testing (/test)</h3><p>Instruct the agent to write unit tests, run the test suite via a shell command, and debug failures before finalizing the feature.</p><h3>Security risks</h3><p>Treat skills like third-party dependencies. Because Agent Skills give LLMs the ability to execute scripts and read files, a malicious .py script could exfiltrate environment variables or run destructive bash commands.</p><blockquote>⚠️ Always audit skills from external sources before adding them to your .agent/skills directory. Check network access settings if you run agents in a sandbox.</blockquote><h3>Skill repositories</h3><p>Instead of writing everything from scratch, there are many open source repositories that we can use to get started. I have used few from the below recently.</p><ul><li><a href="https://github.com/addyosmani/agent-skills"><strong>addyosmani/agent-skills</strong></a><strong>:</strong> Production-grade engineering skills enforcing the Test Pyramid and trunk-based development.</li><li><a href="https://github.com/anthropics/skills"><strong>anthropics/skills</strong></a><strong>:</strong> The official Anthropic repository demonstrating progressive disclosure with workflows for MCP servers and PDFs.</li><li><a href="https://github.com/VoltAgent/awesome-agent-skills"><strong>VoltAgent/awesome-agent-skills</strong></a><strong>:</strong> Community curation of custom agent skills and framework-agnostic tools.</li></ul><p>Start with the Anthropic examples to understand baseline capabilities, then layer in the engineering constraints from Addy Osmani’s repository.</p><p>🔗 <a href="https://codewiz.info/">Blog</a> 🔗 <a href="https://www.linkedin.com/in/code-wiz-740370302/">LinkedIn</a> 🔗 <a href="https://medium.com/@code.wizzard01">Medium</a> 🔗 <a href="https://github.com/CodeWizzard01">Github</a></p><p><em>Originally published at </em><a href="https://codewiz.info/blog/ai-agent-skills-guide/"><em>https://codewiz.info</em></a><em>.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=9a4840a7925c" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Distributed Locking with Redis and Spring Boot: Implementation Guide]]></title>
            <link>https://medium.com/@code.wizzard01/distributed-locking-with-redis-and-spring-boot-implementation-guide-bf7ca8440259?source=rss-3093c328bad8------2</link>
            <guid isPermaLink="false">https://medium.com/p/bf7ca8440259</guid>
            <category><![CDATA[redis]]></category>
            <category><![CDATA[distributed-systems]]></category>
            <category><![CDATA[java]]></category>
            <category><![CDATA[spring]]></category>
            <dc:creator><![CDATA[Code Wiz]]></dc:creator>
            <pubDate>Fri, 01 May 2026 14:45:16 GMT</pubDate>
            <atom:updated>2026-05-01T14:45:16.678Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*XmUff5_w66HJ2jKXJ4Ft_w.png" /></figure><p>In modern microservices architectures, we often run multiple instances of our applications to ensure high availability and scalability. While this is great for performance, it introduces a new set of challenges when it comes to managing shared resources.</p><p>One of the most common challenges is ensuring that a specific critical section of code is executed by only one instance at a time. This is where distributed locking comes into play.</p><h3>The problem: The double booking nightmare</h3><p>Imagine you are building a movie ticket booking application. You have a TicketService with a method bookSeat(String seatId, String userId).</p><p>If you have a single instance of your application, you might use Java’s synchronized keyword or a ReentrantLock to ensure that two users don&#39;t book the same seat simultaneously.</p><pre>// Works fine for a Single Instance<br>public synchronized void bookSeat(String seatId, String userId) {<br>    if (isSeatAvailable(seatId)) {<br>        reserverSeat(seatId, userId);<br>    }<br>}</pre><p>However, in a <strong>distributed environment</strong> with multiple instances of the service running (e.g., on Kubernetes), synchronized only works within a single JVM.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/527/1*zqsgMjh4SIaK9OPFBCHWhw.png" /></figure><p>Since Instance A and Instance B don’t share memory, their local locks are unaware of each other. User A and User B could both check availability at the exact same moment, see the seat as “Available”, and both proceed to book it. Result: <strong>Double Booking</strong>.</p><h3>The solution: Distributed locking with Redis</h3><p>To solve this, we need a lock that is external to our application instances — a shared source of truth. Redis is an excellent choice for this. It processes commands sequentially (single-threaded), ensuring atomic operations. It is extremely fast because it runs in-memory, and it supports key expiration, which is crucial for preventing deadlocks.</p><h3>How it works</h3><p>The algorithm requires acquiring a lock by trying to set a locking key in Redis (e.g., lock:seat:1A). If the key sets successfully, you have the lock and can proceed. If the key already exists, someone else has the lock, so you must wait or fail. When you finish, you release the lock by deleting the key.</p><h3>Key concepts</h3><p>Two core Redis features make this work. First, the SETNX (SET if Not eXists) command. This operation only sets the key if it does not already exist, which is exactly the behavior we need to acquire a lock atomically.</p><p>Second, we need expiration (TTL). What if the service crashes after acquiring the lock but before releasing it? The lock would stay forever, creating a deadlock. To prevent this, we set a Time-To-Live (TTL) on the lock key so the database automatically releases it if the application dies.</p><h3>Implementation in Spring Boot</h3><p>Here is how we implement a robust distributed lock using RedisTemplate.</p><h3>1. Dependencies</h3><p>Add the Redis starter to your pom.xml:</p><pre>&lt;dependency&gt;<br>    &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;<br>    &lt;artifactId&gt;spring-boot-starter-data-redis&lt;/artifactId&gt;<br>&lt;/dependency&gt;</pre><h3>2. RedisLock utility</h3><p>We’ll create a component to handle the locking logic.</p><pre>import org.springframework.data.redis.core.StringRedisTemplate;<br>import org.springframework.stereotype.Component;<br><br>import java.time.Duration;<br>import java.util.concurrent.TimeUnit;<br>@Component<br>public class RedisLock {<br>    private final StringRedisTemplate redisTemplate;<br>    public RedisLock(StringRedisTemplate redisTemplate) {<br>        this.redisTemplate = redisTemplate;<br>    }<br>    /**<br>     * Tries to acquire the lock.<br>     * @param key The unique key for the resource (e.g., &quot;lock:seat:123&quot;)<br>     * @param timeout The duration for which the lock should be held (TTL)<br>     * @return true if lock is acquired, false otherwise<br>     */<br>    public boolean acquireLock(String key, Duration timeout) {<br>        // SET key value NX EX timeout<br>        Boolean success = redisTemplate.opsForValue()<br>                .setIfAbsent(key, &quot;LOCKED&quot;, timeout);<br>        return Boolean.TRUE.equals(success);<br>    }<br>    /**<br>     * Releases the lock by deleting the key.<br>     * @param key The unique key to release<br>     */<br>    public void releaseLock(String key) {<br>        redisTemplate.delete(key);<br>    }<br>}</pre><blockquote><strong>Note</strong>: setIfAbsent corresponds to the atomic SET ... NX EX ... command in Redis. It ensures we set the key and the expiry in one atomic operation.</blockquote><h3>3. Ticket booking service</h3><p>Now, let’s use this lock in our TicketService.</p><pre>import org.springframework.stereotype.Service;<br>import java.time.Duration;<br><br>@Service<br>public class TicketService {<br>    private final RedisLock redisLock;<br>    private final BookingRepository bookingRepository;<br>    public TicketService(RedisLock redisLock, BookingRepository bookingRepository) {<br>        this.redisLock = redisLock;<br>        this.bookingRepository = bookingRepository;<br>    }<br>    public void bookSeat(String seatId, String userId) {<br>        String lockKey = &quot;lock:seat:&quot; + seatId;<br>        <br>        // Define how long we hold the lock. <br>        // Should be longer than the expected execution time.<br>        Duration lockTimeout = Duration.ofSeconds(10); <br>        try {<br>            // 1. Try to acquire the lock<br>            if (redisLock.acquireLock(lockKey, lockTimeout)) {<br>                <br>                // 2. Critical Section<br>                // Double check availability in DB just to be sure<br>                if (isSeatAvailable(seatId)) {<br>                    // Simulate processing (e.g., payment)<br>                    processPayment(); <br>                    <br>                    reserveSeatInDb(seatId, userId);<br>                    System.out.println(&quot;Seat &quot; + seatId + &quot; booked for &quot; + userId);<br>                } else {<br>                    throw new RuntimeException(&quot;Seat already booked!&quot;);<br>                }<br>                <br>            } else {<br>                // 3. Failed to acquire lock<br>                throw new RuntimeException(&quot;Seat is currently being booked by someone else. Please try again.&quot;);<br>            }<br>        } finally {<br>            // 4. Always release the lock!<br>            redisLock.releaseLock(lockKey);<br>        }<br>    }<br>    private boolean isSeatAvailable(String seatId) {<br>        // Logic to check DB<br>        return true;<br>    }<br>    private void reserveSeatInDb(String seatId, String userId) {<br>        // Logic to save booking<br>    }<br>    <br>    private void processPayment() {<br>         try {<br>            Thread.sleep(2000); // Simulate network call<br>        } catch (InterruptedException e) {<br>            Thread.currentThread().interrupt();<br>        }<br>    }<br>}</pre><h3>Improving the solution</h3><p>The implementation above functions well, but production systems have edge cases that quickly complicate things.</p><p>Consider accidental releases. What if Instance A acquires a lock but takes too long processing, causing the lock to expire? Instance B then acquires the lock. Finally, Instance A finishes its work and deletes the key. Instance A just deleted Instance B’s lock! The fix here is to store a unique UUID as the value of the lock key. When releasing, you check if the value matches your UUID before deleting. This requires a Lua script to remain atomic.</p><p>Then there is Redis failover. If the Redis master crashes after a lock is acquired but before it replicates to the replica, consistency goes out the window. You can mitigate this using the Redlock algorithm or by ensuring strong consistency in your Redis cluster, but it adds significant operational overhead.</p><h3>Production recommendation: Redisson</h3><p>For production applications, don’t reinvent the wheel. Use <strong>Redisson</strong>, a popular Redis client for Java that implements java.util.concurrent.locks.Lock backed by Redis. It handles:</p><ul><li>Automatic lease extension (Watchdog).</li><li>Correct atomic release logic.</li><li>Wait strategies (spin locks, pub/sub for release events).</li></ul><p>Here is how simple it is with Redisson:</p><pre>RLock lock = redissonClient.getLock(&quot;lock:seat:&quot; + seatId);<br>try {<br>    if (lock.tryLock(1, 10, TimeUnit.SECONDS)) {<br>        // Critical section<br>    }<br>} catch (InterruptedException e) {<br>    Thread.currentThread().interrupt();<br>} finally {<br>    if (lock.isHeldByCurrentThread()) {<br>        lock.unlock();<br>    }<br>}</pre><h3>Conclusion</h3><p>Distributed locking ensures data consistency in microservices, and Redis provides a fast, reliable foundation for implementing these locks using atomic operations and key expirations. However, building a custom lock requires careful handling of edge cases like accidental releases and failovers. Rather than writing this logic from scratch, use libraries like Redisson that handle lease management and failover automatically. By managing your critical sections correctly, you avoid double booking nightmares down the road.</p><p>🔗 <a href="https://codewiz.info/">Blog</a> 🔗 <a href="https://www.linkedin.com/in/code-wiz-740370302/">LinkedIn</a> 🔗 <a href="https://medium.com/@code.wizzard01">Medium</a> 🔗 <a href="https://github.com/CodeWizzard01">Github</a></p><p><em>Originally published at </em><a href="https://codewiz.info/blog/distributed-locking-redis-spring-boot/"><em>https://codewiz.info</em></a><em>.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=bf7ca8440259" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Flyway vs Liquibase: Why I prefer Flyway for Spring Boot projects]]></title>
            <link>https://medium.com/@code.wizzard01/flyway-vs-liquibase-why-i-prefer-flyway-for-spring-boot-projects-fdd4c9636f23?source=rss-3093c328bad8------2</link>
            <guid isPermaLink="false">https://medium.com/p/fdd4c9636f23</guid>
            <category><![CDATA[spring-boot]]></category>
            <category><![CDATA[liquibase]]></category>
            <category><![CDATA[spring]]></category>
            <category><![CDATA[flyway]]></category>
            <category><![CDATA[java]]></category>
            <dc:creator><![CDATA[Code Wiz]]></dc:creator>
            <pubDate>Wed, 29 Apr 2026 09:58:17 GMT</pubDate>
            <atom:updated>2026-04-29T09:58:17.026Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*e10Bv8pKH-lfUhnAUwXlfw.png" /></figure><p>Every Spring Boot project that touches a database hits this question sooner or later: Flyway or Liquibase?</p><p>I’ve used both across different teams and projects over the years. Both are battle-tested, both integrate with Spring Boot out of the box, and both get the job done. But I keep reaching for Flyway. Not because Liquibase is bad (it isn’t), but because Flyway’s philosophy aligns better with how I think about database changes.</p><p>Let me walk through the real differences, the stuff that actually matters day-to-day, and why I land where I do.</p><p>You can read the same blog <a href="https://codewiz.info/blog/flyway-vs-liquibase-database-migrations/">here</a> on my website also</p><h3>What both tools actually do</h3><p>Before comparing, let’s be clear about the shared goal. Both Flyway and Liquibase:</p><ul><li>Track which schema changes have been applied to a database</li><li>Run pending migrations automatically on application startup</li><li>Store migration history in a metadata table (flyway_schema_history for Flyway, DATABASECHANGELOG for Liquibase)</li><li>Integrate with Spring Boot via auto-configuration (just add the dependency and it works)</li><li>Support all major databases: PostgreSQL, MySQL, Oracle, SQL Server, H2, etc.</li></ul><p>The difference is in <em>how</em> they approach migrations.</p><h3>Flyway: SQL files, naming conventions, done</h3><p>Flyway’s approach is dead simple. You write SQL files, name them with a version prefix, and drop them in a folder. That’s it.</p><pre>src/main/resources/db/migration/<br>├── V1__create_users_table.sql<br>├── V2__add_email_column.sql<br>├── V3__create_orders_table.sql<br>└── V4__add_index_on_email.sql</pre><p>Each file contains plain SQL:</p><pre>-- V1__create_users_table.sql<br>CREATE TABLE users (<br>    id BIGSERIAL PRIMARY KEY,<br>    username VARCHAR(100) NOT NULL UNIQUE,<br>    created_at TIMESTAMP NOT NULL DEFAULT NOW()<br>);</pre><p>Flyway scans the folder, checks which versions have already been applied (via flyway_schema_history), and runs the new ones in order. The naming convention ( V{version}__{description}.sql) is the entire configuration.</p><p>Spring Boot setup? One dependency and zero configuration:</p><pre>&lt;dependency&gt;<br>    &lt;groupId&gt;org.flywaydb&lt;/groupId&gt;<br>    &lt;artifactId&gt;flyway-core&lt;/artifactId&gt;<br>&lt;/dependency&gt;</pre><p>Spring Boot auto-detects the db/migration folder and runs Flyway on startup. You don&#39;t even need to add a single property unless you want to customize something.</p><h3>Liquibase: Changelogs, changesets, and format choices</h3><p>Liquibase takes a different approach. Instead of raw SQL files, you define changes in a <strong>changelog</strong> file using XML, YAML, JSON, or SQL. Each change is wrapped in a <strong>changeset</strong> with an author and an ID.</p><p>Here’s the same users table creation in Liquibase XML:</p><pre>&lt;!-- db/changelog/db.changelog-master.xml --&gt;<br>&lt;databaseChangeLog xmlns=&quot;http://www.liquibase.org/xml/ns/dbchangelog&quot;<br>    xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;<br>    xsi:schemaLocation=&quot;http://www.liquibase.org/xml/ns/dbchangelog<br>        http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd&quot;&gt;<br><br>    &lt;changeSet id=&quot;1&quot; author=&quot;user1&quot;&gt;<br>        &lt;createTable tableName=&quot;users&quot;&gt;<br>            &lt;column name=&quot;id&quot; type=&quot;BIGSERIAL&quot; autoIncrement=&quot;true&quot;&gt;<br>                &lt;constraints primaryKey=&quot;true&quot; nullable=&quot;false&quot;/&gt;<br>            &lt;/column&gt;<br>            &lt;column name=&quot;username&quot; type=&quot;VARCHAR(100)&quot;&gt;<br>                &lt;constraints nullable=&quot;false&quot; unique=&quot;true&quot;/&gt;<br>            &lt;/column&gt;<br>            &lt;column name=&quot;created_at&quot; type=&quot;TIMESTAMP&quot; defaultValueComputed=&quot;NOW()&quot;&gt;<br>                &lt;constraints nullable=&quot;false&quot;/&gt;<br>            &lt;/column&gt;<br>        &lt;/createTable&gt;<br>    &lt;/changeSet&gt;<br><br>&lt;/databaseChangeLog&gt;</pre><p>Or in YAML:</p><pre>databaseChangeLog:<br>  - changeSet:<br>      id: 1<br>      author: user1<br>      changes:<br>        - createTable:<br>            tableName: users<br>            columns:<br>              - column:<br>                  name: id<br>                  type: BIGSERIAL<br>                  autoIncrement: true<br>                  constraints:<br>                    primaryKey: true<br>                    nullable: false<br>              - column:<br>                  name: username<br>                  type: VARCHAR(100)<br>                  constraints:<br>                    nullable: false<br>                    unique: true</pre><p>Spring Boot setup for Liquibase:</p><pre>&lt;dependency&gt;<br>    &lt;groupId&gt;org.liquibase&lt;/groupId&gt;<br>    &lt;artifactId&gt;liquibase-core&lt;/artifactId&gt;<br>&lt;/dependency&gt;</pre><blockquote>By default, Spring Boot looks for db/changelog/db.changelog-master.yaml (or .xml).</blockquote><h3>The real differences that matter</h3><p>Let me cut through the feature-matrix noise and focus on what actually impacts your daily workflow.</p><h3>1. Migration format: SQL vs abstraction layer</h3><p>This is the fundamental philosophical split.</p><p><strong>Flyway</strong> says: “You know SQL. Write SQL.”</p><p><strong>Liquibase</strong> says: “Describe what you want, and we’ll generate the right SQL for your database.”</p><p>Liquibase’s abstraction layer (createTable, addColumn, etc.) is genuinely useful if you deploy the same app against multiple database engines. If your app needs to run on both PostgreSQL and Oracle, writing createTable in XML is easier than maintaining two sets of SQL files.</p><p>But here’s the thing: most Spring Boot projects I’ve worked on target one database. We pick PostgreSQL (or MySQL, or whatever) and stick with it. In that world, the abstraction layer is overhead. I’d rather write the exact CREATE TABLE statement I want, with the exact PostgreSQL-specific features I need (BIGSERIAL, JSONB columns, partial indexes), than translate my intent through XML tags.</p><p>Flyway does support Java-based migrations for complex cases, and Liquibase lets you write raw SQL changesets too. But the defaults and conventions push you in opposite directions.</p><h3>2. Readability in code review</h3><p>This one matters more than people think.</p><p>When a teammate opens a PR with a new Flyway migration, I see SQL. I can immediately tell what’s changing. I can mentally run the statement and think about locking implications, index strategies, or data type choices.</p><pre>-- V12__add_status_to_orders.sql<br>ALTER TABLE orders ADD COLUMN status VARCHAR(20) NOT NULL DEFAULT &#39;PENDING&#39;;<br>CREATE INDEX idx_orders_status ON orders(status);</pre><p>That’s two lines. I know exactly what’s happening.</p><p>The Liquibase equivalent:</p><pre>&lt;changeSet id=&quot;12&quot; author=&quot;ratheesh&quot;&gt;<br>    &lt;addColumn tableName=&quot;orders&quot;&gt;<br>        &lt;column name=&quot;status&quot; type=&quot;VARCHAR(20)&quot; defaultValue=&quot;PENDING&quot;&gt;<br>            &lt;constraints nullable=&quot;false&quot;/&gt;<br>        &lt;/column&gt;<br>    &lt;/addColumn&gt;<br>    &lt;createIndex indexName=&quot;idx_orders_status&quot; tableName=&quot;orders&quot;&gt;<br>        &lt;column name=&quot;status&quot;/&gt;<br>    &lt;/createIndex&gt;<br>&lt;/changeSet&gt;</pre><p>It’s more verbose, and the XML ceremony makes it harder to scan quickly. In a busy PR with application code changes alongside migration changes, the Flyway version wins for me every time.</p><h3>3. Rollback support</h3><p>This is where Liquibase has a genuine edge, and I want to be honest about it.</p><p>Liquibase supports automatic rollback for many operations. If a createTable was applied, it knows to DROP TABLE on rollback. For operations where it can&#39;t auto-generate a rollback, you can define custom rollback SQL within the changeset.</p><pre>&lt;changeSet id=&quot;5&quot; author=&quot;ratheesh&quot;&gt;<br>    &lt;addColumn tableName=&quot;users&quot;&gt;<br>        &lt;column name=&quot;phone&quot; type=&quot;VARCHAR(20)&quot;/&gt;<br>    &lt;/addColumn&gt;<br>    &lt;rollback&gt;<br>        &lt;dropColumn tableName=&quot;users&quot; columnName=&quot;phone&quot;/&gt;<br>    &lt;/rollback&gt;<br>&lt;/changeSet&gt;</pre><p>Flyway’s community edition doesn’t support rollback at all. The paid “Teams” edition does, but honestly? I’ve never relied on automated rollback in production anyway.</p><p>Here’s my take: if a migration goes wrong in production, I don’t want to “undo” it automatically. I want to understand what happened, assess data impact, and write a <em>forward</em> migration to fix it. Rolling back a CREATE TABLE is fine, but rolling back an ALTER TABLE that just ran against 50 million rows? That needs human judgment, not an automated reverse script.</p><p>So while Liquibase’s rollback support is technically better, I don’t consider it a dealbreaker.</p><h3>4. Spring Boot integration</h3><p>Both tools have first-class Spring Boot support, but Flyway’s is slightly more frictionless.</p><p><strong>Flyway with Spring Boot:</strong></p><pre># application.properties - honestly, you might not need ANY of these<br>spring.flyway.enabled=true<br>spring.flyway.locations=classpath:db/migration<br># That&#39;s it. Both lines above are the defaults anyway.</pre><p><strong>Liquibase with Spring Boot:</strong></p><pre>spring.liquibase.enabled=true<br>spring.liquibase.change-log=classpath:db/changelog/db.changelog-master.yaml</pre><p>The difference is minimal, but Flyway’s convention of “just drop SQL files in db/migration&quot; means there is genuinely zero configuration needed. No master changelog file to maintain, no changeset IDs to manage.</p><h3>5. Learning curve</h3><p>I’ve onboarded junior developers onto both tools. With Flyway, the conversation goes:</p><p>“Write your SQL, name the file V{next_number}__description.sql, put it in db/migration. Done.&quot;</p><p>With Liquibase, there’s more to explain: changelog format choices (XML? YAML? SQL?), changeset structure, author/ID conventions, preconditions, contexts, labels. It’s more powerful, but newcomers have more to absorb.</p><h3>Where Liquibase genuinely wins</h3><p>I don’t want to pretend Flyway is better at everything. Liquibase has real strengths:</p><p><strong>Multi-database support.</strong> If you ship a product that customers deploy on their choice of database (PostgreSQL, MySQL, Oracle, SQL Server), Liquibase’s abstraction layer saves you from maintaining separate SQL scripts per database. Flyway can do this with location overrides, but it’s more manual.</p><p><strong>Preconditions.</strong> Liquibase can check if a table exists before trying to create it, or if a column is a certain type before altering it. This is useful for migrations that might run against databases in varying states (common in large enterprise environments).</p><pre>&lt;changeSet id=&quot;10&quot; author=&quot;ratheesh&quot;&gt;<br>    &lt;preConditions onFail=&quot;MARK_RAN&quot;&gt;<br>        &lt;not&gt;&lt;tableExists tableName=&quot;audit_log&quot;/&gt;&lt;/not&gt;<br>    &lt;/preConditions&gt;<br>    &lt;createTable tableName=&quot;audit_log&quot;&gt;<br>        &lt;!-- columns --&gt;<br>    &lt;/createTable&gt;<br>&lt;/changeSet&gt;</pre><p><strong>Contexts and labels.</strong> Need to apply certain migrations only in dev but not prod? Liquibase has built-in support for this with contexts. Flyway can achieve similar results, but it&#39;s not as clean.</p><p><strong>Diff and changelog generation.</strong> Liquibase can compare two database schemas and auto-generate a changelog of differences. This is helpful when reverse-engineering an existing database into version control for the first time.</p><h3>Where Flyway wins (and why I pick it)</h3><p><strong>Simplicity.</strong> There is almost nothing to learn. SQL files, naming convention, done. The cognitive overhead is close to zero.</p><p><strong>Transparency.</strong> What you see is what’s executed. No abstraction layer translating your intent into SQL behind the scenes. When something goes wrong, there’s no question about what SQL actually ran.</p><p><strong>Speed of authoring.</strong> Writing a SQL file is faster than wrapping the same logic in XML/YAML tags. Over hundreds of migrations across a project’s lifetime, this adds up.</p><p><strong>Clean Git history.</strong> Each migration is a standalone file. Git blame and log work exactly as you&#39;d expect. With Liquibase&#39;s single-changelog-file approach (or even with includes), the history is messier.</p><p><strong>Team convention.</strong> Since there’s only one way to do things in Flyway (SQL files with version prefixes), there are fewer debates. With Liquibase, teams argue about XML vs YAML, single changelog vs multiple files, when to use preconditions, etc.</p><h3>A side-by-side Spring Boot setup</h3><p>For anyone who wants to quickly try both, here’s the minimum setup for each.</p><p><strong>Flyway:</strong></p><pre>pom.xml:    flyway-core dependency<br>            (flyway-database-postgresql if using PG)<br>folder:     src/main/resources/db/migration/<br>file:       V1__init.sql<br>config:     nothing required</pre><p><strong>Liquibase:</strong></p><pre>pom.xml:    liquibase-core dependency<br>folder:     src/main/resources/db/changelog/<br>file:       db.changelog-master.yaml (+ included changelogs)<br>config:     nothing required (Spring Boot defaults work)</pre><h3>When to pick Liquibase instead</h3><p>Pick Liquibase when:</p><ul><li>Your application deploys against multiple database vendors and you want one set of migration definitions</li><li>You’re in a heavily regulated environment where rollback support, preconditions, and labels are required by policy</li><li>You’re working with customers who manage their own database and migrations need to be resilient to schema drift</li><li>You need to reverse-engineer an existing database into version-controlled changelogs</li></ul><h3>When to pick Flyway (most of the time)</h3><p>Pick Flyway when:</p><ul><li>You target a single database (which is the majority of Spring Boot apps)</li><li>You value simplicity and want the lowest possible learning curve</li><li>Your team is comfortable writing SQL directly</li><li>You want maximum transparency in code reviews</li><li>You don’t need automated rollback (forward-only migrations is your strategy)</li></ul><h3>My honest take</h3><p>Both Flyway and Liquibase are production-ready tools used by thousands of teams. You won’t regret picking either one.</p><p>But I keep choosing Flyway because it stays out of my way. I write SQL every day. I don’t need an abstraction layer to write ALTER TABLE. The naming convention enforces order without any configuration. And when I review a teammate&#39;s migration PR, I see exactly what will happen to the database. No translation needed.</p><p>Liquibase is the better choice when your problem space genuinely requires its advanced features (multi-database, preconditions, policy-driven rollbacks). But for the typical Spring Boot app talking to one PostgreSQL instance? Flyway is all you need.</p><p>Pick the tool that matches your team’s reality, not the one with the longest feature list.</p><blockquote>📚 Related: If you’re working with Spring Data JPA alongside your migrations, check out the <a href="https://codewiz.info/blog/spring-jpa-cheat-sheet">Spring JPA cheat sheet</a> and <a href="https://codewiz.info/blog/database-indexes-deep-dive">Database indexes deep dive</a>.</blockquote><p>🔗 <a href="https://codewiz.info/">Blog</a> 🔗 <a href="https://www.linkedin.com/in/code-wiz-740370302/">LinkedIn</a> 🔗 <a href="https://medium.com/@code.wizzard01">Medium</a> 🔗 <a href="https://github.com/CodeWizzard01">Github</a></p><p><em>Originally published at </em><a href="https://codewiz.info/blog/flyway-vs-liquibase-database-migrations/"><em>https://codewiz.info</em></a><em>.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=fdd4c9636f23" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Learn Spring AI Basics by building a Stock Advisor Agent]]></title>
            <link>https://medium.com/@code.wizzard01/spring-ai-is-the-most-popular-ai-framework-for-building-agents-in-jvm-ecosystem-3e6906b12987?source=rss-3093c328bad8------2</link>
            <guid isPermaLink="false">https://medium.com/p/3e6906b12987</guid>
            <category><![CDATA[ai-agent]]></category>
            <category><![CDATA[ai]]></category>
            <category><![CDATA[spring]]></category>
            <category><![CDATA[java]]></category>
            <category><![CDATA[spring-ai]]></category>
            <dc:creator><![CDATA[Code Wiz]]></dc:creator>
            <pubDate>Mon, 26 Jan 2026 00:06:25 GMT</pubDate>
            <atom:updated>2026-01-26T00:07:32.466Z</atom:updated>
            <content:encoded><![CDATA[<p><strong>Learn Spring AI Basics by building a Stock Advisor Agent</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*UJsRQqabhBXGyPTz9T2hgg.png" /></figure><p>Spring AI is the most popular AI framework for building agents in JVM ecosystem. It seamlessly integrates with the rest of the Spring ecosystem and makes it really easy to integrate with all major LLMs.</p><p>In this short tutorial, we will build a <strong>Stock Advisor Agent</strong> and learn key building blocks of Spring AI.</p><p>We’ll cover the below concepts:</p><ul><li><strong>Chat Client</strong>: The main interface for talking to LLMs.</li><li><strong>Tools (Function Calling)</strong>: Giving the AI ability to execute code.</li><li><strong>Memory</strong>: Letting the AI remember context across a conversation.</li><li><strong>Advisors</strong>: Intercepting requests to add logic (like memory or logging).</li><li><strong>Structured Output</strong>: Getting strict JSON objects back, not just text.</li></ul><p>You can also read the same blog on my website <a href="https://codewiz.info/blog/spring-ai-basics-stock-advisor-agent/">here</a></p><h3>Setting Up the Project</h3><p>We’ll start with a Spring Boot project. I’m using Java 25 here, but Java 17+ works fine. The key dependencies are spring-ai-starter-model-openai for the LLM interaction and spring-boot-starter-web for exposing our endpoints.</p><h3>Dependencies</h3><p>Here are the key dependencies in pom.xml:</p><pre>&lt;dependencies&gt;<br>    &lt;dependency&gt;<br>        &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;<br>        &lt;artifactId&gt;spring-boot-starter-web&lt;/artifactId&gt;<br>    &lt;/dependency&gt;<br>    &lt;dependency&gt;<br>        &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;<br>        &lt;artifactId&gt;spring-boot-starter-data-jdbc&lt;/artifactId&gt;<br>    &lt;/dependency&gt;<br>    &lt;dependency&gt;<br>        &lt;groupId&gt;org.postgresql&lt;/groupId&gt;<br>        &lt;artifactId&gt;postgresql&lt;/artifactId&gt;<br>        &lt;scope&gt;runtime&lt;/scope&gt;<br>    &lt;/dependency&gt;<br>    &lt;dependency&gt;<br>        &lt;groupId&gt;org.springframework.ai&lt;/groupId&gt;<br>        &lt;artifactId&gt;spring-ai-starter-model-openai&lt;/artifactId&gt;<br>    &lt;/dependency&gt;<br>&lt;/dependencies&gt;</pre><p>You can find the full pom.xml <a href="https://github.com/CodeWizzard01/spring-ai-stock-agent/blob/main/pom.xml">here</a>.</p><h3>Configuration</h3><p>Make sure you have your OPENAI_API_KEY set in your environment variables. Spring AI will pick this up automatically.</p><p>Here is the application.properties file:</p><p>Here we are using OpenAI model. You can also switch to any other model or even local LLMs by just changing the dependency and adding the configuration for the model.</p><p>You can find the full application.properties <a href="https://github.com/CodeWizzard01/spring-ai-stock-agent/blob/main/src/main/resources/application.properties">here</a>.</p><pre>spring.application.name=stockagent<br><br>spring.ai.openai.chat.api-key=${OPENAI_API_KEY}<br><br># PostgreSQL Database Configuration<br>spring.datasource.url=jdbc:postgresql://localhost:5432/stock-agent-db<br>spring.datasource.username=stock-agent-user<br>spring.datasource.password=secret<br>spring.datasource.driver-class-name=org.postgresql.Driver<br><br># Schema Initialization<br>spring.sql.init.mode=always<br>spring.sql.init.schema-locations=classpath:schema.sql<br><br># Stock API Configuration<br>stock.api.key=${STOCK_API_KEY}</pre><h3>The Chat Client: Talking to the Brain</h3><p>The heart of our agent is the ChatClient. Think of it as the fluent API wrapper that makes interacting with the AI model much smoother than raw HTTP calls.</p><p>It handles the heavy lifting of:</p><ul><li><strong>Prompt Management</strong>: structuring the messages sent to the AI.</li><li><strong>Advisors</strong>: Intercepting requests to add logic (like memory or logging).</li><li><strong>Model Switching</strong>: You can swap OpenAI for Claude or Gemini without code changes.</li></ul><h3>System vs. User Prompts</h3><p>In the configuration below, you’ll see two types of messages:</p><ol><li><strong>System Prompt (</strong><strong>defaultSystem)</strong>: This sets the &quot;Persona&quot; and &quot;Rules&quot;. It&#39;s like the job description for the AI. We tell it: &quot;You are a stock advisor,&quot; &quot;Use Markdown,&quot; and &quot;Always ask for confirmation.&quot; This context persists across every interaction.</li><li><strong>User Prompt</strong>: This is the specific input from the user (e.g., “Buy Apple stock”). It triggers the immediate action.</li></ol><p>Here is how we configure it in StockAdvisorAssistant.java:</p><pre>@Service<br>public class StockAdvisorAssistant {<br><br>    private final ChatClient chatClient;<br>    private static final String SYSTEM_MESSAGE = &quot;&quot;&quot;<br>            You are a polite stock advisor assistant who provides advice based on <br>            the latest stock price, company information and financial results.<br>            When you are asked to create a stock order, ask for a confirmation before creating it.<br>            In the confirmation message, include the stock symbol, quantity, price, and current market price.<br>            All your responses should be in markdown format.<br>            When you are returning a list of items like position, orders, list of stocks etc, return them in a table format.<br>            &quot;&quot;&quot;;<br><br>    public StockAdvisorAssistant(ChatClient.Builder chatClientBuilder) {<br>        this.chatClient = chatClientBuilder<br>                .defaultSystem(SYSTEM_MESSAGE)<br>                .build();<br>    }<br>    <br>    // ... methods ...<br>}</pre><p>Notice the system message. I found that being very specific about formatting (like “return them in a table format”) saves a ton of parsing headaches later. We inject the ChatClient.Builder which makes testing easier and allows Spring to pre-configure defaults.</p><h3>Giving the Agent Tools</h3><p>An AI that can’t access data is just a fancy text generator. Tools (also known as Function Calling in OpenAI land) is one of the approaches to bridge this gap.</p><p>In Spring AI, any Java method can be a tool. You just annotate it with @Tool.</p><p>Here’s our StockInformationService that fetches real data using API from <a href="https://site.financialmodelingprep.com/">FMP</a>:</p><pre>@Service<br>public class StockInformationService {<br><br>    private static final Logger log = LoggerFactory.getLogger(StockInformationService.class);<br>    // ... dependencies ...<br><br>    @Tool(description = &quot;Returns the stock price for the given stock symbol&quot;)<br>    public String getStockPrice(String stockSymbol) {<br>        log.info(&quot;Fetching stock price for stock symbols: {}&quot;, stockSymbol);<br>        return fetchData(&quot;/quote?symbol=&quot; + stockSymbol);<br>    }<br><br>    @Tool(description = &quot;Returns the company profile for the given stock symbol.&quot;)<br>    public String getCompanyProfile(String stockSymbol) {<br>        // implementation fetching from external API<br>        return fetchData(&quot;/profile?symbol=&quot; + stockSymbol);<br>    }<br>    <br>    // ... other tools ...<br>}</pre><p>The description in the @Tool annotation is critical. This is what the LLM sees to decide <em>when</em> and <em>how</em> to call this function. If you leave it vague, the model might hallucinate parameters or ignore the tool entirely.</p><p>We also have a StockOrderService to perform actions for storing and retrieving stock orders in the database:</p><pre>@Service<br>public class StockOrderService {<br>    <br>    private final StockOrderRepository stockOrderRepository;<br><br>    @Tool(description = &quot;Creates a new stock order. Input: StockOrder object with symbol, quantity, price, and orderType (BUY or SELL)&quot;)<br>    public StockOrder createOrder(StockOrder order) {<br>        // Saves to database using Spring Data JDBC<br>        return stockOrderRepository.save(order);<br>    }<br>}</pre><h3>Registering the Tools</h3><p>Now we need to tell the ChatClient about these tools. We do this in our configuration class:</p><pre>@Configuration<br>public class AssistantConfiguration {<br><br>    @Bean<br>    public ChatClient.Builder chatClientBuilder(org.springframework.ai.chat.model.ChatModel chatModel,<br>            ChatMemory chatMemory, StockInformationService stockInformationService, StockOrderService stockOrderService) {<br>        return ChatClient.builder(chatModel)<br>                .defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build())<br>                .defaultTools(stockInformationService, stockOrderService);<br>    }<br>}</pre><p>By calling .defaultTools(stockInformationService, stockOrderService), we make these beans available to every prompt sent by this client. The Spring AI handles the serialization of arguments and execution of the method automatically.</p><h3>Memory: Maintaining Context</h3><p>If you ask “What’s the price of Apple?” and then follow up with “Buy 10 shares”, a stateless LLM will ask “Buy 10 shares of what?”.</p><p>To fix this, we need conversational memory. Spring AI handles this via <strong>Advisors</strong>.</p><h3>What are Advisors?</h3><p>Advisors in Spring AI are like interceptors or “middleware” for your Chat Client. They sit between your application and the AI model, allowing you to modify the request or response.</p><p>Common uses for Advisors include:</p><ul><li><strong>Memory</strong>: Injecting past conversation history into the prompt (like we do here).</li><li><strong>RAG (Retrieval Augmented Generation)</strong>: Finding relevant documents and adding them to the prompt context.</li><li><strong>Logging/Observability</strong>: inspecting tokens and latency.</li></ul><p>In our case, we use MessageChatMemoryAdvisor to automatically attach the conversation history to every request.</p><p>In the configuration above, we added: .defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build())</p><p>We will configure a simple in-memory store that keeps the last 20 messages:</p><pre>@Bean<br>public ChatMemory chatMemory() {<br>    return MessageWindowChatMemory.builder()<br>            .maxMessages(20)<br>            .build();<br>}</pre><p>Now, when we call the chat client, we pass a conversationId to retrieve the correct history:</p><pre>public String chat(String userMessage, String conversationId) {<br>    return chatClient.prompt()<br>            .user(userMessage)<br>            .advisors(a -&gt; a.param(ChatMemory.CONVERSATION_ID, conversationId))<br>            .call()<br>            .content();<br>}</pre><p>This simple addition makes the interaction feel human. The agent remembers we were talking about Apple stock without us repeating the ticker symbol.</p><h3>Going Beyond In-Memory (Persistence)</h3><p>In this example, we used MessageWindowChatMemory, which lives in the application&#39;s RAM. If you restart the server, the conversation is lost.</p><p>For production apps, you usually want to persist this history. Spring AI supports several options to store chat history externally without changing your memory advisor logic:</p><ul><li><strong>JDBC Chat Memory</strong>: Stores conversation history in your relational database (PostgreSQL, MySQL).</li><li><strong>NoSQL Stores</strong>: Adapters available for Cassandra, MongoDB, etc.</li><li><strong>Vector Stores</strong>: You can even use vector databases to retrieve only the <em>relevant</em> past memories rather than the entire history (RAG-style memory).</li></ul><p>You would simply swap the ChatMemory bean with a persistent implementation, and the rest of your agent code remains exactly the same.</p><h3>Testing in Action</h3><p>Now our agent is ready. We can test it by calling the chat url from browser or using curl or httpie.</p><p>But I’ve built a simple HTML frontend (included in the project source) to test our agent. Here is how the conversation flows:</p><p>You can access the frontend at `http://localhost:8080/index.html`.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*6QWsWZhDusrkvRN9.png" /></figure><p><em>Figure 1: The agent welcomes us and lists its capabilities.</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*zcfLoaL1G1HIDn73.png" /></figure><p><em>Figure 2: We ask for the price of Apple (AAPL), and the agent calls the </em><em>getStockPrice tool.</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*DQqGidlUjh6FDD_F.png" /></figure><p><em>Figure 3: We ask to buy shares. Notice the agent asks for confirmation? That’s the system prompt in action.</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*SWpbIi3Z23qL-wCR.png" /></figure><p><em>Figure 4: Once confirmed, the agent calls </em><em>createOrder and returns the transaction details.</em></p><h3>Structured Output</h3><p>Sometimes you don’t want a chat response; you want data you can use in your code. For example, if I ask for “3 stock picks for a growth strategy”, I want a JSON array, not a paragraph of text.</p><p>Spring AI handles this beautifully with the .entity() method.</p><pre>public List&lt;StockPick&gt; getStockPicks(String strategy, int count) {<br>    return chatClient.prompt()<br>            .user(u -&gt; u.text(&quot;Suggest {count} stock picks for a {strategy} portfolio. Return symbol, companyName, and brief rationale.&quot;)<br>                    .param(&quot;count&quot;, count)<br>                    .param(&quot;strategy&quot;, strategy))<br>            .call()<br>            .entity(new ParameterizedTypeReference&lt;List&lt;StockPick&gt;&gt;() {});<br>}</pre><p>Under the hood, Spring AI injects strict format instructions into the prompt and parses the JSON response into your Java StockPick record. It&#39;s concise and type-safe.</p><h3>Testing Structured Output</h3><p>We can verify this endpoint using httpie:</p><pre>http :8080/picks strategy==growth count==5</pre><p><strong>Response:</strong></p><pre>[<br>    {<br>        &quot;symbol&quot;: &quot;AAPL&quot;,<br>        &quot;companyName&quot;: &quot;Apple Inc.&quot;,<br>        &quot;rationale&quot;: &quot;Strong ecosystem, expanding services revenue, and continued innovation in hardware and AI drive robust top‑line growth.&quot;<br>    },<br>    {<br>        &quot;symbol&quot;: &quot;AMZN&quot;,<br>        &quot;companyName&quot;: &quot;Amazon.com, Inc.&quot;,<br>        &quot;rationale&quot;: &quot;Dominant e‑commerce platform combined with high‑margin cloud services (AWS) and expanding advertising business fuel long‑term revenue acceleration.&quot;<br>    },<br>    {<br>        &quot;symbol&quot;: &quot;NVDA&quot;,<br>        &quot;companyName&quot;: &quot;NVIDIA Corporation&quot;,<br>        &quot;rationale&quot;: &quot;Leader in GPU technology powering AI, data centers, gaming, and autonomous vehicles, with rapidly growing market share in high‑growth segments.&quot;<br>    },<br>    {<br>        &quot;symbol&quot;: &quot;TSLA&quot;,<br>        &quot;companyName&quot;: &quot;Tesla, Inc.&quot;,<br>        &quot;rationale&quot;: &quot;Rapidly scaling electric‑vehicle production, expanding energy storage and solar businesses, and strong brand positioning in the transition to clean transportation.&quot;<br>    },<br>    {<br>        &quot;symbol&quot;: &quot;SHOP&quot;,<br>        &quot;companyName&quot;: &quot;Shopify Inc.&quot;,<br>        &quot;rationale&quot;: &quot;Provides a scalable commerce platform for SMBs, benefiting from the shift to online retail and a growing ecosystem of merchants worldwide.&quot;<br>    }<br>]</pre><h3>Conclusion</h3><p>The complete code for this project is available on my <a href="https://github.com/CodeWizzard01/spring-ai-stock-agent">Github repo</a>. It includes the full Docker Compose setup for PostgreSQL and the complete implementation of the tools.</p><p><em>Originally published at </em><a href="https://codewiz.info/blog/spring-ai-basics-stock-advisor-agent/"><em>http://codewiz.info</em></a><em>.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=3e6906b12987" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Top 10 Microservice Questions Asked in Interviews]]></title>
            <link>https://medium.com/@code.wizzard01/top-10-microservice-questions-asked-in-interviews-dee2a105c1e2?source=rss-3093c328bad8------2</link>
            <guid isPermaLink="false">https://medium.com/p/dee2a105c1e2</guid>
            <category><![CDATA[microservices]]></category>
            <category><![CDATA[microservice-architecture]]></category>
            <category><![CDATA[interview]]></category>
            <category><![CDATA[system-design-interview]]></category>
            <dc:creator><![CDATA[Code Wiz]]></dc:creator>
            <pubDate>Sun, 11 Jan 2026 07:44:41 GMT</pubDate>
            <atom:updated>2026-01-11T07:44:41.691Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Uq75Mf1EJsbbQBgcbWvhjA.png" /></figure><p>I’ve sat through hundreds of microservice interviews, both as a candidate and an interviewer. The questions that separate senior engineers from juniors aren’t about definitions-they’re about production experience or how well you can demonstrate your experience with distributed systems. Can you explain distributed transactions? Sure. But have you debugged a compensation transaction that failed halfway through?</p><p>Here are the top 10 microservice interview questions that actually matter. These aren’t theory questions-they’re designed to reveal your experience with distributed systems that handle real traffic, real failures, and real complexity.</p><h3>1. How do you handle distributed transactions and data consistency across microservices?</h3><p>This is where most candidates stumble. They know ACID transactions don’t work across services, but they can’t articulate the practical alternatives.</p><p><strong>What They’re Really Asking:</strong></p><p>Can you manage data consistency without a global transaction? In a monolith, you’d wrap everything in a single database transaction. In microservices, that approach fails because each service owns its own database. You need different strategies.</p><p><strong>The Answer:</strong></p><p>We can use the Saga pattern for long-running transactions that span multiple services. There are two approaches: orchestration and choreography.</p><p><strong>Orchestration Pattern:</strong> A central orchestrator coordinates the workflow. It’s easier to understand and debug, but creates a single point of coordination. We can use this when we need explicit control over the transaction flow.</p><p><strong>Java code example:</strong></p><pre>@Service<br>public class OrderOrchestrator {<br>    <br>    public void createOrder(OrderRequest request) {<br>        try {<br>            // Step 1: Reserve inventory<br>            InventoryResponse inventory = inventoryService.reserve(request.getItems());<br>            <br>            // Step 2: Process payment<br>            PaymentResponse payment = paymentService.process(request.getPayment());<br>            <br>            // Step 3: Create order<br>            Order order = orderService.create(request);<br>            <br>            // Step 4: Send confirmation<br>            notificationService.sendConfirmation(order);<br>            <br>        } catch (Exception e) {<br>            // Compensation logic<br>            compensate(request, e);<br>        }<br>    }<br>}</pre><p><strong>Choreography Pattern:</strong> Each service publishes events and reacts to events from others. No central coordinator. Better for scalability, but harder to debug. We can use this when services are loosely coupled and we want to avoid a coordination bottleneck.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*BM3jBFU2x36ngaWHsjcEdA.png" /></figure><p>The tradeoff is eventual consistency. You can’t guarantee immediate consistency across services. That’s okay for most business scenarios-users can tolerate a few seconds of delay for order confirmation.</p><p><strong>Production Gotchas:</strong></p><p>I’ve been bitten by compensation failures before. If your compensation logic itself fails, you’re in a partial state. Always make compensation operations idempotent. Also, ensure your saga steps are idempotent-network retries can cause duplicate executions.</p><h3>2. Explain your approach to inter-service communication. When would you use synchronous vs asynchronous patterns?</h3><p>This question reveals whether you understand the fundamental tradeoffs of distributed systems. Every communication pattern has consequences.</p><p><strong>What They’re Really Asking:</strong></p><p>Do you know when to use REST vs message queues? Can you explain why one approach might cascade failures while the other doesn’t?</p><p><strong>The Answer:</strong></p><p>We can choose synchronous communication (REST, gRPC) when we need immediate responses and can tolerate tight coupling. Asynchronous communication (Kafka, RabbitMQ) when we need resilience and can accept eventual consistency.</p><p><strong>Synchronous Communication (REST/gRPC):</strong></p><p>We can use REST for user-facing operations where response time matters. When a user clicks “Place Order,” they need to know immediately if it succeeded. But synchronous calls create dependencies-if the payment service is down, orders can’t be placed.</p><pre>@RestController<br>public class OrderController {<br>    <br>    @Autowired<br>    private PaymentServiceClient paymentClient;<br>    <br>    @PostMapping(&quot;/orders&quot;)<br>    public ResponseEntity&lt;Order&gt; createOrder(@RequestBody OrderRequest request) {<br>        // Synchronous call - blocks until response<br>        PaymentResponse payment = paymentClient.processPayment(request.getPayment());<br>        <br>        if (payment.isSuccessful()) {<br>            return ResponseEntity.ok(orderService.create(request));<br>        }<br>        throw new PaymentException(&quot;Payment failed&quot;);<br>    }<br>}</pre><p>The catch? Cascading failures. If the payment service is slow, your order service becomes slow too. That’s why I always add timeouts and circuit breakers to synchronous calls.</p><p><strong>Asynchronous Communication (Message Brokers):</strong></p><p>We can use message queues when we want loose coupling and better fault tolerance. The order service publishes an “OrderCreated” event. Payment and inventory services consume it independently. If payment service is down, the order still gets created-payment happens eventually.</p><pre>@Service<br>public class OrderService {<br>    <br>    @Autowired<br>    private KafkaTemplate&lt;String, OrderEvent&gt; kafkaTemplate;<br>    <br>    public Order createOrder(OrderRequest request) {<br>        Order order = orderRepository.save(new Order(request));<br>        <br>        // Fire and forget - doesn&#39;t wait for consumers<br>        kafkaTemplate.send(&quot;order-created&quot;, new OrderCreatedEvent(order));<br>        <br>        return order;<br>    }<br>}</pre><p>The tradeoff is eventual consistency. Users might see their order before payment is confirmed. That’s fine for most scenarios, but you need clear UX patterns.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Az0aCMuWptLTaKSJFpoOow.png" /></figure><p>I’ve seen teams use synchronous calls everywhere “because it’s simpler.” Then production hits, and a single slow service brings down the entire system. The fix? Move non-critical paths to async.</p><blockquote><em>📚 Related: Learn more about resilience patterns in </em><a href="https://codewiz.info/blog/resilient-microservices"><em>Top Resilience Patterns in Microservices</em></a><em>.</em></blockquote><h3>3. How do you implement service discovery in a dynamic microservices environment? How do you handle service unavailability?</h3><p>Service discovery is one of those infrastructure concerns that seems simple until you’re debugging why service A can’t find service B at 3 AM.</p><p><strong>What They’re Really Asking:</strong></p><p>Do you understand how services find each other when IPs change constantly? Can you handle the edge cases?</p><p><strong>The Answer:</strong></p><p>We can use client-side service discovery with a service registry like Consul or Eureka. Services register themselves on startup and deregister on shutdown. Clients query the registry to find available instances.</p><p><strong>Service Registry Pattern:</strong></p><p>Each service registers its network location (IP + port) when it starts. The registry maintains a list of healthy services. Clients query the registry periodically to get fresh service instances.</p><p><strong>Handling Service Unavailability:</strong></p><p>The registry marks services as unhealthy when health checks fail. But clients might have cached stale instances. I always implement retry logic with exponential backoff and failover to different instances.</p><p>For production, we can use client-side load balancing (Ribbon or Spring Cloud LoadBalancer) that automatically handles instance selection and retries. We should also configure timeouts shorter than the registry’s health check interval-if a service becomes unhealthy, clients stop calling it quickly.</p><p><strong>Production Gotchas:</strong></p><p>DNS caching is the silent killer. Java’s default DNS TTL is infinite, meaning clients cache IP addresses forever. If a service instance dies and a new one takes its IP, clients might keep hitting the old IP. I always set networkaddress.cache.ttl to 30 seconds in production.</p><p>Also, watch out for the thundering herd problem. When a service comes back online, all clients try to connect simultaneously. Implement staggered retry with jitter to spread the load.</p><p>In Kubernetes environments, we can use built-in service discovery. Kubernetes DNS automatically resolves service names to IPs, and endpoints are updated dynamically. Simpler than running a separate registry.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*bb7fhrnoTmxfxpU5wTG4eg.png" /></figure><blockquote><em>📚 Related: Explore more system design patterns in </em><a href="https://codewiz.info/blog/top-15-system-design-patterns"><em>Top 15 System Design Patterns</em></a><em>.</em></blockquote><h3>4. Describe your strategy for monitoring and observability in a distributed system. How do you trace a request across multiple services?</h3><p>Observability is what keeps you sane in production. Without it, debugging distributed systems is like searching for a needle in a haystack blindfolded.</p><p><strong>What They’re Really Asking:</strong></p><p>Can you debug a request that spans 10 services? Do you understand correlation IDs and distributed tracing?</p><p><strong>The Answer:</strong></p><p>We can implement the three pillars of observability: metrics, logs, and traces. Each serves a different purpose, and you need all three.</p><p><strong>Metrics (What’s happening now):</strong></p><p>We can use Prometheus for metrics collection. Key metrics to track: request rate, latency percentiles (p50, p95, p99), error rate, and business metrics like orders per second. Dashboards in Grafana provide real-time visibility.</p><p>The trick is choosing the right metrics. Too many metrics create noise. Too few and you miss critical issues. We can focus on SLOs-if we promise 200ms p95 latency, that’s what we monitor.</p><p><strong>Logs (What happened):</strong></p><p>Centralized logging with correlation IDs. Every request gets a unique correlation ID that propagates through all service calls. When something breaks, we can grep for that ID and see the entire request flow.</p><p>We can use structured logging (JSON format) to query logs effectively. Tools like ELK stack or Loki make searching millions of log lines manageable.</p><p><strong>Distributed Tracing (Why it’s slow):</strong></p><p>Tracing shows the exact path a request takes through services and where time is spent. We can use OpenTelemetry with Jaeger or Zipkin.</p><p>The key is trace context propagation. Each service adds itself to the trace and forwards the trace context to downstream services. At the end, we get a complete timeline showing that 80% of the request time was spent in the payment service database query.</p><p><strong>Production Gotchas:</strong></p><p>Sampling is critical. You can’t trace every request-it’s too expensive. We can sample 1% of requests normally, but 100% during incidents. Also, trace context propagation can break if you use async processing. Make sure to pass trace context through message headers.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*JApHup2a9eSuodkF22VV1g.png" /></figure><p>Alert fatigue is real. I only alert on metrics that require immediate action. Everything else goes to dashboards. If you get 50 alerts per hour, you’ll start ignoring them all.</p><h3>5. What is the Circuit Breaker pattern, and can you describe a production scenario where you implemented it?</h3><p>This question tests whether you understand failure modes in distributed systems. Circuit breakers prevent cascading failures-they’re essential in production.</p><p><strong>What They’re Really Asking:</strong></p><p>Have you experienced cascading failures? Do you understand when to fail fast vs retry?</p><p><strong>The Answer:</strong></p><p>A circuit breaker is like an electrical circuit breaker-it stops current flow when there’s too much load. In software, it stops calling a failing service to prevent cascading failures.</p><p>The circuit has three states: <strong>Closed</strong> (normal operation), <strong>Open</strong> (failing, reject requests immediately), and <strong>Half-Open</strong> (testing if service recovered).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Exw1xSREPh4a6rcrCB4ngQ.png" /></figure><p><strong>How It Works:</strong></p><p>We can configure a failure threshold-say, 50% failures in the last 100 requests. When threshold is breached, the circuit opens. All requests fail fast without calling the service. After a timeout period, the circuit moves to half-open and allows one test request. If it succeeds, the circuit closes. If it fails, it opens again.</p><p><strong>Sample Code:</strong></p><pre>@Service<br>public class PaymentServiceClient {<br>    <br>    @Autowired<br>    private RestClient restClient;<br>    <br>    @CircuitBreaker(name = &quot;payment-service&quot;, fallbackMethod = &quot;processPaymentFallback&quot;)<br>    @Retry(name = &quot;payment-service&quot;)<br>    public PaymentResponse processPayment(PaymentRequest request) {<br>        return restClient.post()<br>            .uri(&quot;http://payment-service/process&quot;)<br>            .contentType(MediaType.APPLICATION_JSON)<br>            .body(request)<br>            .retrieve()<br>            .body(PaymentResponse.class);<br>    }<br>    <br>    public PaymentResponse processPaymentFallback(PaymentRequest request, Exception e) {<br>        // Fallback: queue payment for later processing<br>        return new PaymentResponse()<br>            .status(&quot;QUEUED&quot;)<br>            .message(&quot;Payment queued due to service unavailability&quot;);<br>    }<br>}</pre><p><strong>Production Gotchas:</strong></p><p>Don’t set thresholds too aggressively. If you open the circuit after 5% failures, you’ll open it during normal blips. Also, half-open state is tricky-if multiple requests hit during half-open, you might close the circuit prematurely. Most libraries handle this, but it’s worth understanding.</p><p>I’ve also seen teams forget fallback logic. Opening the circuit is good, but if you have no fallback, you’re just failing in a different way. Always design fallback behavior.</p><blockquote><em>📚 Related: Learn more about implementing circuit breakers and other resilience patterns in </em><a href="https://codewiz.info/blog/resilient-microservices"><em>Top Resilience Patterns in Microservices</em></a><em>.</em></blockquote><h3>6. How do you handle API versioning and backward compatibility in microservices? What’s your deployment strategy?</h3><p>This question separates engineers who’ve shipped breaking changes in production from those who haven’t. Versioning seems straightforward until you realize you’ve got 50 services depending on service A’s API, and service A needs to change.</p><p><strong>What They’re Really Asking:</strong></p><p>Have you broken production by deploying an incompatible API change? Do you understand the different versioning strategies and when each makes sense?</p><p><strong>The Answer:</strong></p><p>Versioning refers to the practice of managing changes to APIs so that existing clients continue to work while new features or breaking changes are introduced. It’s essential for evolving microservices without disrupting running systems, especially when multiple teams or clients depend on your APIs. Basically it is about running multiple versions of the same API side by side.</p><p>There are several techniques for API versioning:</p><p><strong>1. URI Path Versioning:</strong><br> Include the version number in the API path (e.g.,/api/v1/orders). This makes it clear which version is being used and allows multiple versions to coexist.</p><p><strong>2. Header Versioning:</strong><br> Clients specify the desired API version in a custom HTTP header, such as Accept-Version: v2. This keeps URLs cleaner and can make versioning less visible to end-users.</p><p><strong>3. Content Negotiation:</strong><br> The API version is embedded in the Accept or Content-Type headers using MIME types (e.g., application/vnd.myservice.v2+json). This decouples versioning from URLs but can be harder to discover and debug.</p><p><strong>4. Query Parameter Versioning:</strong><br> Clients provide a version as a query parameter (e.g.,/orders?version=2). While easy to implement, this approach is less common for APIs serving external clients.</p><p>Each technique has trade-offs in terms of discoverability, cacheability, and tooling support. The choice largely depends on team preferences, consumer needs, and the nature of the API. Whichever technique is chosen, the key is to ensure backward compatibility so that existing clients are not broken by new releases.</p><p><strong>Backward Compatibility:</strong></p><p>The golden rule: add, don’t remove. When introducing v2, keep v1 running. Never remove fields-deprecate them instead. Clients migrate at their own pace.</p><p><strong>Deployment Strategy:</strong></p><p>Deploy v2 alongside v1, not replacing it. Run both versions simultaneously during migration. For internal services, use canary deployments. External APIs require explicit versioning since we don’t control client upgrade schedules.</p><p><strong>Production Gotchas:</strong></p><p>Always version breaking changes-never change response structures in place. Maintain at most two versions (current and previous). Watch out for transitive dependencies-if service A calls service B (v1) and service B upgrades to v2, service A might break.</p><blockquote><em>📚 Related: Learn more about implementing API versioning in Spring Boot in </em><a href="https://codewiz.info/blog/api-versioning-spring-boot"><em>A Developer’s Guide to API Versioning and Implementation in Spring Boot</em></a><em>.</em></blockquote><h3>7. How do you ensure security in a microservices architecture, especially for service-to-service communication?</h3><p>Security in microservices is harder than in monoliths because you’ve got network boundaries everywhere. A vulnerability in one service can expose the entire system if service-to-service communication isn’t secured properly.</p><p><strong>What They’re Really Asking:</strong></p><p>Do you understand the security implications of distributed systems? Have you implemented mTLS, service meshes, or other production-grade security patterns?</p><p><strong>The Answer:</strong></p><p>We can use mutual TLS (mTLS) for service-to-service communication. Each service presents a certificate, and both services verify each other’s identity. This prevents man-in-the-middle attacks and ensures only authorized services can communicate.</p><p>The challenge is certificate management. In Kubernetes, we can use cert-manager to automatically issue and rotate certificates from Let’s Encrypt or an internal CA. Without automation, certificate rotation becomes a nightmare. I’ve seen teams forget to rotate certificates and wake up to production outages when certificates expired.</p><p>For user-facing APIs, we can use OAuth 2.0 with JWT tokens. The API Gateway validates tokens and forwards them to services. Services trust the gateway and extract user context from the token. This pattern centralizes authentication while keeping services stateless.</p><p>API keys are fine for service-to-service calls in trusted environments, but they’re static secrets. Anyone with the key has access. I prefer certificates because they can be rotated without code changes and provide better audit trails.</p><p>The service mesh pattern is also used for security. Istio or Linkerd handle mTLS automatically, encrypting all service-to-service traffic without code changes. I’ve used Istio in production, and while the operational complexity is higher, the automatic security posture improvement is worth it for large deployments.</p><blockquote><em>📚 </em><strong><em>Related:</em></strong><em> Learn how to secure service-to-service calls in depth with JWT and Spring Security in </em><a href="https://codewiz.info/blog/jwt-authentication-spring6"><em>JWT Authentication with Spring 6 Security: Complete Guide</em></a><em>.</em></blockquote><h3>8. What is eventual consistency, and how do you handle scenarios where your service needs strongly consistent data from another microservice?</h3><p>This question tests whether you understand the fundamental tradeoffs of distributed systems. Eventual consistency isn’t a bug-it’s a feature you design for. But sometimes you need stronger guarantees.</p><p><strong>What They’re Really Asking:</strong></p><p>Do you understand CAP theorem in practice? Can you explain when eventual consistency is acceptable and when it isn’t? Have you built systems that need strong consistency across service boundaries?</p><p><strong>The Answer:</strong></p><p>Eventual consistency means that if I stop making updates, all services will eventually see the same data. There’s no guarantee of immediate consistency. If service A updates a customer’s balance, service B might not see that change for seconds or even minutes, depending on how you propagate updates.</p><p>Most business scenarios can tolerate eventual consistency. If I place an order, it’s fine if the inventory service updates a few seconds later. Users don’t need to see real-time inventory counts. If payment processing takes a moment, that’s acceptable. Eventual consistency gives you availability and partition tolerance, which are usually more important than immediate consistency.</p><p>But sometimes you need strong consistency. If I’m checking if a customer has sufficient balance before processing a payment, eventual consistency isn’t good enough. I need to know the current balance right now, not eventually.</p><p>The solution depends on where the data lives. If both services need strongly consistent data, we can use one of these patterns:</p><p><strong>Pattern 1: Move the data into one service.</strong> If order service needs customer balance, maybe the order service should own customer balances. But this breaks service boundaries and creates tight coupling. We should avoid this unless the data naturally belongs together.</p><p><strong>Pattern 2: Use distributed locks or transactions.</strong> We can use a distributed lock to ensure only one service updates customer balance at a time. This gives strong consistency but creates contention and potential deadlocks. The performance impact is significant, so we should only use this when absolutely necessary.</p><p><strong>Pattern 3: Use a transactional outbox.</strong> When the payment service updates a balance, it writes the update to its database and also writes an event to an outbox table in the same transaction. A separate process reads the outbox and publishes events. The outbox ensures the event is published exactly once, giving eventual consistency with strong delivery guarantees.</p><p><strong>Pattern 4: Query the source directly.</strong> If order service needs customer balance, it can call customer service synchronously and trust the response. This gives strong consistency for reads, but if customer service is down, order service can’t function. This is the tradeoff-you get consistency but lose availability.</p><p>In practice, we should design for eventual consistency by default. Most scenarios don’t need strong consistency, and the performance and availability benefits are huge. When we do need strong consistency, we can use synchronous calls with appropriate fallbacks. If customer service is down, we can queue the order and process it when customer service recovers.</p><p>The production gotcha is user experience. If your UI shows data that’s eventually consistent, users might see stale data and get confused. I always design UIs to handle this gracefully. Show “updating…” indicators, use optimistic updates, or explain that data might be slightly delayed. Don’t pretend everything is real-time when it isn’t.</p><blockquote><em>📚 Related: Learn about event sourcing patterns in </em><a href="https://codewiz.info/blog/event-sourcing-explained"><em>Event Sourcing Explained</em></a><em> and how to ensure data consistency between microservices using the Outbox Pattern in </em><a href="https://codewiz.info/blog/outbox-pattern-data-consistency"><em>The Outbox Pattern: Ensuring Data Consistency in Microservices</em></a><em>.</em></blockquote><h3>9. What is the role of an API Gateway in a microservices architecture, and how have you used it in production?</h3><p>API Gateways are one of those patterns that everyone implements differently. Some teams use them for everything, others avoid them entirely. The right answer depends on context, which is what interviewers are really probing for.</p><p><strong>What They’re Really Asking:</strong></p><p>Do you understand the single entry point pattern? Have you built systems with and without gateways? Can you explain the tradeoffs?</p><p><strong>The Answer:</strong></p><p>An API Gateway is a single entry point for all client requests. Instead of clients calling multiple services directly, they call the gateway, which routes requests to the appropriate services. The gateway handles cross-cutting concerns like authentication, rate limiting, routing, and response aggregation.</p><p>We can use API Gateways for external-facing APIs. When external clients need to access our system, they shouldn’t know about our internal service topology. The gateway provides a stable interface while we refactor services behind it. We can add, remove, or split services without breaking clients.</p><p>The gateway also centralizes authentication and authorization. Instead of implementing OAuth in every service, I do it once in the gateway. Services receive requests with user context already validated. This reduces code duplication and ensures consistent security policies.</p><p>Rate limiting is another common use case. I can throttle abusive clients at the gateway before they hit my services. This protects my infrastructure from traffic spikes or attacks. Without a gateway, I’d need to implement rate limiting in every service, which is inefficient and inconsistent.</p><p>Response aggregation is useful when clients need data from multiple services. Instead of making multiple round trips, the gateway can call multiple services and combine the responses. This reduces latency and simplifies client code. The tradeoff is tight coupling-if one service is slow, the entire aggregated response is slow.</p><p>In production, I’ve used both API Gateway-as-a-Service offerings like AWS API Gateway and self-hosted solutions like Kong or Zuul. The managed services are simpler but less flexible. Self-hosted gives me more control but requires more operational overhead.</p><p>The production gotcha is the gateway becoming a bottleneck. If all traffic flows through a single gateway, its failure brings down everything. I always run multiple gateway instances behind a load balancer. I also monitor gateway performance closely-if it becomes slow, everything becomes slow.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*r5FS-WtFv6we9MLJ_MmIxA.png" /></figure><p>Some teams avoid gateways entirely and let clients call services directly. This works if you have few clients or if clients are internal and can handle service discovery. But for external APIs with many clients, a gateway is essential. The complexity tradeoff is worth it for the operational benefits.</p><h3>10. Describe a time when you had to debug a production issue in a microservices environment. What was your approach?</h3><p>This is the most revealing question. Anyone can explain theory, but debugging production issues in distributed systems requires real experience. The answer reveals whether you’ve actually built and operated microservices at scale.</p><p><strong>What They’re Really Asking:</strong></p><p>Have you been on-call at 2 AM debugging a distributed system? Can you systematically debug issues across multiple services? Do you understand the tools and techniques?</p><p><strong>The Answer:</strong></p><p>Here’s a systematic framework for debugging production issues:</p><p><strong>1. Understand Scope (Metrics First):</strong></p><p>Check dashboards for error rates, latency percentiles, and affected services. Identify if the issue is isolated or widespread, and correlate with recent deployments or traffic spikes.</p><p><strong>2. Trace Request Flow (Correlation IDs &amp; Tracing):</strong></p><p>Extract correlation IDs from failed requests and trace the path across services. Distributed tracing shows where requests fail or slow down, identifying bottlenecks.</p><p><strong>3. Analyze Logs:</strong></p><p>Pull logs using correlation IDs for detailed errors and stack traces. Search for error patterns, connection failures, timeouts, and compare logs across services.</p><p><strong>4. Check Infrastructure:</strong></p><p>Verify service registry and health checks-services might be marked healthy but failing. Check resource utilization (CPU, memory, connection pools) and network connectivity.</p><p><strong>5. Narrow Down &amp; Fix:</strong></p><p>Review code paths for resource leaks, missing error handling, or logic errors. Deploy fix with monitoring enabled, verify metrics return to baseline, and document the incident.</p><p><strong>Common Strategies:</strong></p><p>Start broad, narrow down (metrics → logs → traces → code review). Compare before/after deployments. Check dependencies (downstream services, databases, message brokers). Review recent changes and correlate timing.</p><p><strong>Production Gotchas:</strong></p><p>Health checks that don’t verify actual functionality can mask issues. Without correlation IDs and distributed tracing, debugging becomes guesswork. Good observability is essential-metrics show what’s wrong, logs show why, traces show where.</p><h3>Conclusion</h3><p>These ten questions reveal production experience-understanding not just what these patterns are, but when to use them and what can go wrong.</p><p>For a comprehensive collection of microservice interview questions with detailed answers and code examples, check out our <a href="https://codewiz.info/interview/microservices-interview-questions">Microservices Interview Questions</a> page.</p><p><em>Originally published at </em><a href="https://codewiz.info/blog/top-10-microservice-interview-questions/"><em>https://codewiz.info</em></a><em>.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=dee2a105c1e2" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Top 5 Developer Conference YouTube Channels for Java Developers]]></title>
            <link>https://medium.com/@code.wizzard01/top-5-developer-conference-youtube-channels-for-java-developers-8a5ed42d8d61?source=rss-3093c328bad8------2</link>
            <guid isPermaLink="false">https://medium.com/p/8a5ed42d8d61</guid>
            <dc:creator><![CDATA[Code Wiz]]></dc:creator>
            <pubDate>Wed, 07 Jan 2026 02:16:39 GMT</pubDate>
            <atom:updated>2026-01-07T02:16:39.112Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*aLX0CBO20YOJQlC2DYNeCg.png" /></figure><p>I’ve learned more from watching conference talks on YouTube than from most online courses. There’s something about watching a framework maintainer explain <em>why</em> they made certain design decisions, or hearing a Netflix engineer describe how they handle 200 million users, that just doesn’t translate to written tutorials.</p><p>The problem? There are hundreds of conference channels out there, and most of them aren’t worth your time if you’re focused on Java and the JVM ecosystem. I’ve curated the five channels I keep coming back to-the ones where I actually finish the videos and walk away having learned something.</p><p>You can also read the same blog on <a href="https://codewiz.info/blog/top-5-developer-conference-youtube-channels-java/">my website here</a></p><h3>1. Devoxx (@DevoxxForever)</h3><p><strong>👥 Subscribers:</strong> 169K | <strong>📺 Videos:</strong> 4,302 | <strong>👀 Total Views:</strong> 20.1M</p><p><strong>Channel URL</strong>: <a href="https://www.youtube.com/@DevoxxForever">https://www.youtube.com/@DevoxxForever</a></p><p><strong>🎯 Why I recommend it:</strong> If you want one conference channel for Java, make it this one.</p><p>Devoxx Belgium is basically the JavaOne successor that we all needed. You’ll find talks from Brian Goetz explaining Project Valhalla, Venkat Subramaniam doing live coding sessions, and deep dives into JVM internals from the engineers who actually work on HotSpot. The production quality is solid, and they’re fast about uploading-usually within weeks of the conference.</p><p>One thing I appreciate: not only they have short informative sessions to get an idea about the new features, but they also have longer deep-dive sessions where engineers talk about specific problems they’ve solved. That’s where the real learning happens.</p><h3>2. Spring I/O (@SpringIOConference)</h3><p><strong>👥 Subscribers:</strong> 47.4K | <strong>📺 Videos:</strong> 393 | <strong>👀 Total Views:</strong> 2.9M</p><p><strong>Channel URL</strong>: <a href="https://www.youtube.com/@SpringIOConference">https://www.youtube.com/@SpringIOConference</a></p><p><strong>🎯 Why I recommend it:</strong> Straight from the Spring team-no middleman.</p><p>If you’re building anything with Spring Boot (and let’s be honest, most of us are), this channel is essential. These conferences bring in the actual Spring maintainers like Josh Long, Jürgen Höller, the Spring Security team, and they don’t just demo features. They explain the <em>why</em> behind design decisions.</p><p>I particularly like their Spring Security talks. The official docs are good, but watching someone walk through OAuth2 flows while explaining what can go wrong? That’s saved me hours of debugging.</p><h3>3. GOTO Conferences (@GOTO-)</h3><p><strong>👥 Subscribers:</strong> 1.06M | <strong>📺 Videos:</strong> 3,409 | <strong>👀 Total Views:</strong> 73.1M</p><p><strong>Channel URL</strong>: <a href="https://www.youtube.com/@GOTO-">https://www.youtube.com/@GOTO-</a></p><p><strong>🎯 Why I recommend it:</strong> Architecture and design talks that make you rethink how you build systems.</p><p>GOTO isn’t Java-specific, but that’s actually a strength. You’ll find Martin Fowler discussing refactoring, Sam Newman on microservices, and architects from companies like Spotify and Netflix explaining their systems. The Java content is excellent when it appears, but even the language-agnostic talks on architecture and distributed systems are directly applicable.</p><p>Fair warning: some talks are more “inspirational keynote” than “technical deep dive.” I tend to skip those. Filter by speaker or topic rather than watching everything.</p><h3>4. InfoQ (@InfoQ)</h3><p><strong>👥 Subscribers:</strong> 233K | <strong>📺 Videos:</strong> 1,781 | <strong>👀 Total Views:</strong> 28.0M</p><p><strong>Channel URL</strong>: <a href="https://www.youtube.com/@InfoQ">https://www.youtube.com/@InfoQ</a></p><p><strong>🎯 Why I recommend it:</strong> QCon talks plus interviews with people shaping the industry.</p><p>InfoQ curates content from QCon conferences worldwide, and they’ve got a good nose for what’s actually important versus what’s just hype. Their Java coverage includes interviews with OpenJDK contributors, JCP discussions, and practical talks on topics like observability and performance tuning.</p><p>What sets InfoQ apart is their podcast-style interviews. Sometimes you learn more from a 20-minute conversation with a framework author than from an hour-long conference talk. Their recent series on AI in software development has been surprisingly practical-not just “look at this cool demo” but “here’s what actually works in production.”</p><h3>5. AWS Events (@AWSEventsChannel)</h3><p><strong>👥 Subscribers:</strong> 167K | <strong>📺 Videos:</strong> 12,604 | <strong>👀 Total Views:</strong> 68.9M</p><p><strong>Channel URL</strong>: <a href="https://www.youtube.com/@AWSEventsChannel">https://www.youtube.com/@AWSEventsChannel</a></p><p><strong>🎯 Why I recommend it:</strong> If you’re deploying Java to AWS, this is required viewing.</p><p>Look, I know-it’s a vendor channel. But re:Invent sessions on Lambda cold starts with Java, Corretto performance tuning, and EKS deployment patterns are genuinely useful. They bring in customers who run serious Java workloads and have them explain what worked (and what didn’t).</p><p>The trick with AWS Events is knowing what to skip. Ignore the product announcements and marketing sessions. Search specifically for “Java,” “Spring Boot,” or “JVM” and you’ll find the sessions worth watching. Their talks on reducing Lambda cold start times for Java changed how I architect serverless applications.</p><h3>The Bottom Line</h3><p>You don’t need to watch everything. Pick one or two channels, find speakers whose style works for you, and make conference talks part of your regular learning routine. I usually watch one during lunch a few times a week-it adds up.</p><p>For more conference channels covering other languages and technologies, check out <a href="https://www.ytubeinfluencers.com/category/developerconferences/">Developer Conference Channels on YTubeInfluencers</a>.</p><p><em>Originally published at </em><a href="https://codewiz.info/blog/top-5-developer-conference-youtube-channels-java/"><em>https://codewiz.info</em></a><em>.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=8a5ed42d8d61" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[What’s New in JDK 25: Key Features and Enhancements]]></title>
            <link>https://medium.com/@code.wizzard01/whats-new-in-jdk-25-key-features-and-enhancements-25f841a5df0c?source=rss-3093c328bad8------2</link>
            <guid isPermaLink="false">https://medium.com/p/25f841a5df0c</guid>
            <category><![CDATA[java25]]></category>
            <category><![CDATA[java]]></category>
            <dc:creator><![CDATA[Code Wiz]]></dc:creator>
            <pubDate>Sun, 23 Nov 2025 03:20:19 GMT</pubDate>
            <atom:updated>2025-11-23T03:20:19.374Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*uAexfmsBKsgNwFphyi_qcA.png" /></figure><p>Java 25 released on September 16th, 2025 brings several significant features to the Java platform, including the finalization of Scoped Values, the introduction of Stable Values, and further refinements to Structured Concurrency. Let’s explore the key features included in the Java 25 release.</p><h3>1.1 Stable Values (Preview)</h3><p>Stable Values are variables that can be set only once during an application’s lifetime and stay unchanged after that. They provide a standardized way to lazily initialize constants, letting the JVM optimize them just like it does with final values.</p><p>This feature solves the long-standing problem of safe lazy initialization in Java. It offers better performance than final fields (which must be initialized at creation) and better safety than double-checked locking or other lazy initialization patterns.</p><p>You can learn more about Stable Values from this <a href="https://codewiz.info/blog/stable-values-java25">Stable Values Guide</a>.</p><p><strong>Example:</strong></p><pre>public class EmailService {<br>    private static final StableValue&lt;SMTPClient&gt; smtpClient = StableValue.of();<br>    <br>    public static void sendEmail(String to, String subject, String body) {<br>        SMTPClient client = smtpClient.orElseSet(() -&gt; <br>            new SMTPClient(loadConfiguration()));<br>        client.send(to, subject, body);<br>    }<br>}</pre><h3>1.2 Primitive Types in Pattern Matching (Third Preview)</h3><p>This feature extends pattern matching to work with primitive types in both instanceof and switch expressions. This enhancement simplifies code that needs to match primitive types, reducing boilerplate and improving readability.</p><p><strong>Example:</strong></p><pre>public void processValue(Object obj) {<br>    switch (obj) {<br>        case int i -&gt; System.out.println(&quot;Integer: &quot; + i);<br>        case long l -&gt; System.out.println(&quot;Long: &quot; + l);<br>        case String s -&gt; System.out.println(&quot;String: &quot; + s);<br>        default -&gt; System.out.println(&quot;Unknown: &quot; + obj);<br>    }<br>}</pre><blockquote><strong>Related:</strong> <a href="https://codewiz.info/blog/java25-new-features/java23-pattern-matching.md">Pattern Matching in Java</a> — Deep dive into pattern matching, sealed types, destructuring, and related features leading up to Java 25.</blockquote><h3>1.3 Module Import Declarations (Final)</h3><p>Module Import Declarations allow developers to import all the packages exported by a module with a single declaration. This simplifies working with modular libraries by significantly reducing import statement verbosity.</p><p><strong>Example:</strong></p><pre>import module java.base;<br><br>void main() throws IOException {<br>    var names = List.of(&quot;Alice&quot;, &quot;Bob&quot;, &quot;Charlie&quot;);<br>    Path filePath = Paths.get(&quot;test.txt&quot;);<br>    Files.write(filePath, names);<br>}</pre><h3>1.4 Compact Source Files and Instance Main Methods (Final)</h3><p>This feature simplifies Java program structure for beginners by eliminating the need for explicit class declarations and static main methods in simple programs. It lowers the barrier to entry for learning Java by allowing students to write their first programs without needing to understand large amounts of boilerplate code.</p><p><strong>Example:</strong></p><pre>void main() {<br>    System.out.println(&quot;Hello, World!&quot;);<br>}</pre><h3>1.5 Flexible Constructor Bodies (Final)</h3><p>Flexible Constructor Bodies allow statements to appear before an explicit constructor invocation (super(...) or this(...)). This enables cleaner initialization logic and reduces the need for repetitive code in constructors, giving developers more freedom in how they structure their object initialization.</p><p><strong>Example:</strong></p><pre>public UserProfile(String rawUsername, String rawEmail) {<br>    // Sanitize input before calling superclass constructor<br>    var sanitizedUsername = sanitize(rawUsername);<br>    var sanitizedEmail = sanitize(rawEmail);<br>    <br>    super(sanitizedUsername, sanitizedEmail);<br>}</pre><h3>2. Performance and Runtime Improvements</h3><h3>2.1 Compact Object Headers (Final)</h3><p>This feature reduces the size of object headers in the HotSpot JVM from between 96 and 128 bits down to 64 bits on 64-bit architectures. By reducing the per-object overhead, this change reduces heap size, improves deployment density, and increases data locality.</p><h3>2.2 Generational Shenandoah</h3><p>Generational Shenandoah introduces a generational mode for the Shenandoah garbage collector. This improves performance for applications with high allocation rates by separating young and old objects, allowing for more frequent collection of short-lived objects without scanning the entire heap.</p><h3>3. Library Enhancements</h3><h3>3.1 Scoped Values (Final)</h3><p>Scoped Values, now a finalized feature in Java 25, provide a safer and more structured way of sharing data within a thread and its child threads. They serve as a modern alternative to ThreadLocal.</p><p>Scoped values address the limitations of ThreadLocal (unconstrained mutability, unbounded lifetime) by providing a immutable, bounded-lifetime data sharing mechanism. It is particularly important for Virtual Threads where ThreadLocal can be expensive.</p><p>You can learn more about Scoped Values from this <a href="https://codewiz.info/blog/scoped-values-java">Scoped Values Guide</a>.</p><p><strong>Example:</strong></p><pre>public class UserContextDemo {<br>    public static final ScopedValue&lt;String&gt; userScopedVal = ScopedValue.newInstance();<br>    public static void main(String[] args) {<br>        ScopedValue.where(userScopedVal, &quot;Alice&quot;).run(() -&gt; {<br>            processUserRequest();<br>        });<br>    }<br>    <br>    static void processUserRequest() {<br>        System.out.println(&quot;Processing for user: &quot; + userScopedVal.get());<br>    }<br>}</pre><h3>3.2 Structured Concurrency (Fifth Preview)</h3><p>Structured Concurrency is an API that treats groups of related tasks running in different threads as a single unit of work. This simplifies concurrent programming by eliminating common risks like thread leaks and cancellation delays, while improving reliability and observability of concurrent code.</p><p>You can learn more about Structured Concurrency from this <a href="https://codewiz.info/blog/structured-concurrency-java25">Structured Concurrency Guide</a>.</p><p><strong>Example:</strong></p><pre>public void generateReport(String stockSymbol) throws InterruptedException {<br>    try (var scope = StructuredTaskScope.open()) {<br>        var stockTask = scope.fork(() -&gt; stockRepository.getStockHolding(stockSymbol));<br>        var marketPriceTask = scope.fork(() -&gt; stockAPIClient.getStockPrice(stockSymbol));<br>        <br>        scope.join();<br>        <br>        // Process results<br>        printReport(stockTask.get(), marketPriceTask.get());<br>    }<br>}</pre><h3>3.3 Vector API (Tenth Incubator)</h3><p>The Vector API allows developers to express vector computations that reliably compile to optimal vector hardware instructions. This dramatically improves performance for computationally intensive applications by leveraging SIMD (Single Instruction, Multiple Data) hardware capabilities.</p><h3>3.4 Key Derivation Function API (Final)</h3><p>The Key Derivation Function API provides a standard API for Key Derivation Functions (KDFs). This addition provides standardized, secure methods for deriving cryptographic keys, making it easier to implement secure key management in Java applications.</p><h3>4. Security Enhancements</h3><h3>4.1 PEM Encodings of Cryptographic Objects (Preview)</h3><p>This preview feature introduces a standard API for encoding and decoding cryptographic keys and certificates in the PEM format. This simplifies the handling of cryptographic artifacts, which are commonly exchanged in PEM format across different systems.</p><h3>5. Tooling and Observability Updates</h3><h3>5.1 JFR CPU-Time Profiling (Experimental)</h3><p>This experimental feature enhances JDK Flight Recorder (JFR) to capture more accurate CPU-time profiling information on Linux. It provides deeper insights into application performance by accurately measuring CPU consumption of individual methods, helping developers identify optimization targets more effectively.</p><h3>5.2 JFR Method Timing &amp; Tracing</h3><p>This update extends JFR with facilities for method timing and tracing via bytecode instrumentation. It allows recording complete and exact statistics for specific methods without source code modifications, moving beyond sample-based statistics for critical code paths.</p><h3>5.3 JFR Cooperative Sampling</h3><p>JFR Cooperative Sampling improves the stability of JFR when asynchronously sampling Java thread stacks by walking call stacks only at safepoints while minimizing safepoint bias. This enhances the reliability of profiling in production environments by preventing crashes that could occur with previous asynchronous sampling techniques.</p><h3>5.4 Ahead-of-Time Command-Line Ergonomics</h3><p>This feature simplifies the process of creating ahead-of-time (AOT) caches by streamlining the command-line options. It makes it easier for developers to adopt AOT compilation for faster startup times without dealing with complex multi-step processes.</p><h3>5.5 Ahead-of-Time Method Profiling</h3><p>Ahead-of-Time Method Profiling improves warmup time by making method-execution profiles from a previous run available immediately upon startup. This enables the JIT compiler to generate native code immediately, significantly reducing the warmup period for Java applications.</p><h3>Conclusion</h3><p>JDK 25 continues the trend of modernizing Java with features that improve developer productivity, performance, and security. The finalization of Scoped Values and the introduction of Stable Values are particularly exciting for building robust, high-performance applications.</p><p>To stay updated with the latest updates in Java and Spring, follow us on <a href="https://www.linkedin.com/in/code-wiz-740370302/"><strong>LinkedIn</strong></a> and <a href="https://medium.com/@code.wizzard01"><strong>Medium</strong></a>.</p><p><em>Originally published at </em><a href="https://codewiz.info/blog/java25-new-features/"><em>https://codewiz.info</em></a><em>.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=25f841a5df0c" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Spring Framework 7 and Spring Boot 4: Key Features for Java Developers]]></title>
            <link>https://medium.com/@code.wizzard01/spring-framework-7-and-spring-boot-4-key-features-for-java-developers-d58c941265be?source=rss-3093c328bad8------2</link>
            <guid isPermaLink="false">https://medium.com/p/d58c941265be</guid>
            <category><![CDATA[spring-boot]]></category>
            <category><![CDATA[spring]]></category>
            <category><![CDATA[java]]></category>
            <category><![CDATA[spring-framework]]></category>
            <category><![CDATA[microservices]]></category>
            <dc:creator><![CDATA[Code Wiz]]></dc:creator>
            <pubDate>Tue, 18 Nov 2025 12:25:25 GMT</pubDate>
            <atom:updated>2025-11-23T03:07:52.012Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/944/1*AXVt0RGqrONXILvh70EQJQ.png" /></figure><p>Spring Framework 7 and Spring Boot 4 introduced several new features and improvements. Let us explore some of the key features which are important for Java developers. Java 17 is still the baseline, but it’s recommended to use the latest LTS JDK (for example, Java 25) for better performance and security.</p><h3>API Versioning</h3><p>API versioning is the process of assigning unique version numbers to your API. As you introduce new features or make breaking changes, you can release a new version of your API while allowing existing clients to continue using the older version. This ensures that you don’t disrupt services for your users.</p><p>In earlier versions of Spring Boot, there was no built-in, opinionated way to handle API versioning. Developers had to implement versioning manually using Spring’s flexible request mapping capabilities.</p><p>With the introduction of Spring Framework 7, API versioning has been significantly streamlined through native support. The framework now provides an ApiVersionStrategy that centralizes the configuration and management of API versions. This eliminates the need for manual version resolution logic and reduces code duplication.</p><h4>Configuration</h4><p>You can configure API versioning by implementing WebMvcConfigurer and overriding the configureApiVersioning method:</p><pre>@Configuration<br>public class WebConfiguration implements WebMvcConfigurer {<br>@Override<br>    public void configureApiVersioning(ApiVersionConfigurer configurer) {<br>        configurer.useRequestHeader(&quot;API-Version&quot;);<br>    }<br>}</pre><p>This configuration tells Spring to extract the API version from the “API-Version” request header. You can configure other strategies as well: use configurer.useQueryParam(&quot;version&quot;) for query parameter-based versioning, configurer.usePathSegment() for path segment versioning, or configurer.useMediaTypeParameter() for media type-based versioning.</p><p>For Spring Boot applications, you can configure this using properties in application.properties:</p><pre>spring.mvc.apiversion.use.header=API-Version</pre><h4>Using the Version Attribute</h4><p>Once configured, you can specify the API version directly in your controller mappings using the new version attribute:</p><pre>@RestController<br>public class ProductController {<br>@GetMapping(path = &quot;/products/{id}&quot;, version = &quot;1&quot;)<br>    public Product getProductV1(@PathVariable String id) {<br>        return productService.getProduct(id);<br>    }<br>    @GetMapping(path = &quot;/products/{id}&quot;, version = &quot;2&quot;)<br>    public ProductV2 getProductV2(@PathVariable String id) {<br>        return productService.getProductV2(id);<br>    }<br>}</pre><blockquote><strong>Want more details?</strong><br>See <a href="https://codewiz.info/blog/api-versioning-spring-boot">A Developer’s Guide to API Versioning in Spring Boot</a> for in-depth strategies, legacy methods, and side-by-side code examples on API versioning in Spring Boot and Framework 7.</blockquote><h3>Resilience features</h3><p>Till now for resilience features, we had to use Spring Retry and Resilience4j. With Spring Framework 7, we have got a native support for retry and concurrency limit features. For others like circuit breaker, rate limiting, etc., we still need to use Resilience4j.</p><pre>@EnableResilientMethods<br>@Service<br>class PaymentService {<br>  @Retryable(maxAttempts = 4, delay = 100, multiplier = 2, maxDelay = 1000)<br>  @ConcurrencyLimit(3)<br>  public String charge(String id) { return &quot;ok&quot;; }<br>}</pre><p>First we need to enable the resilience features in our application by adding the @EnableResilientMethods annotation. Then we can annotate our methods with @Retryable and @ConcurrencyLimit annotations.</p><blockquote><strong>Recommended Reading<br></strong>For detailed examples of implementing retry and backoff using Spring Framework 7, see <a href="https://codewiz.info/blog/retry-pattern-spring">Implementing the Retry Pattern in Spring Boot</a>.<br>For an introduction to circuit breaker, bulkhead and other resilience patterns, check out <a href="https://codewiz.info/blog/resilient-microservices">Resilient Microservices: Patterns &amp; Diagrams</a>.</blockquote><h3>JMS Client</h3><p>Similar to JdbcClient and RestClient, we now have JmsClient to make JMS calls as an alternative to JmsTemplate.</p><pre>public class MyJmsClient {<br>  private final JmsClient jmsClient;<br>  public MyJmsClient(JmsClient jmsClient) {<br>    this.jmsClient = jmsClient;<br>  }<br>  public void sendMessage(String message) {<br>    jmsClient.destination(&quot;queue1&quot;).send(message);<br>  }<br>  public String receiveMessage() {<br>    return jmsClient.destination(&quot;queue2&quot;).receive();<br>  }<br>}</pre><h3>HTTP Service Client Enhancements</h3><p>Spring Framework 7 introduces the <strong>HTTP Service Registry</strong> to simplify managing multiple HTTP service clients. Instead of configuring each client individually, you can organize related services into groups and configure them collectively.</p><p>Define your HTTP service interface:</p><pre>public interface InventoryService {<br>    @GetExchange(&quot;http://product-service/products/{id}&quot;)<br>    Product getProduct(@PathVariable String id);<br>    @PostExchange(&quot;http://product-service/products&quot;)<br>    ProductResponse createProduct(@RequestBody ProductRequest request);<br>}</pre><p>Group and configure the service:</p><pre>@ImportHttpServices(group = &quot;inventory&quot;, types = {InventoryService.class})<br>@Configuration<br>public class InventoryClientConfig {<br>}</pre><p>This approach centralizes configuration, reduces boilerplate, and makes it easier to manage shared settings like base URLs and authentication headers across related services.</p><p>After this, you can inject the service into your controller:</p><pre>@RestController<br>public class InventoryController {<br>    private final InventoryService inventoryService;<br>    public InventoryController(InventoryService inventoryService) {<br>        this.inventoryService = inventoryService;<br>    }<br>    @GetMapping(&quot;/products/{id}&quot;)<br>    public Product getProduct(@PathVariable String id) {<br>        return inventoryService.getProduct(id);<br>    }<br>}</pre><h3>Null safety through JSpecify</h3><p>Spring Framework 7 adopts <strong>JSpecify annotations</strong> to enhance null-safety by explicitly declaring the nullability of APIs, fields, and type usages. This helps prevent NullPointerException through build-time checks and makes the potential absence of values explicit in your code.</p><pre>import org.jspecify.annotations.Nullable;<br>import org.jspecify.annotations.NonNull;<br><br>public class UserService {<br>    <br>    @NonNull<br>    public String getUserName(@NonNull String userId) {<br>        // userId is guaranteed to be non-null<br>        return userRepository.findById(userId).getName();<br>    }<br>    <br>    @Nullable<br>    public String findOptionalEmail(String userId) {<br>        // Return value may be null<br>        return userRepository.findEmailById(userId);<br>    }<br>}</pre><p>JSpecify annotations work with tools like NullAway and are supported by IDEs such as IntelliJ IDEA and Eclipse. For Kotlin developers, these annotations are automatically translated to Kotlin’s native null-safety features, ensuring seamless integration.</p><h3>Programmatic bean registration</h3><p>Traditionally, registering multiple beans or handling complex registration logic in a @Configuration class was discouraged: each @Bean method should register only one bean, and its return type should be as concrete as possible. These guidelines, while ensuring clarity, have sometimes limited flexibility—especially when advanced conditional or bulk registration is needed.</p><p>Spring Framework 7 now includes a new programmatic bean registration feature via the BeanRegistrar contract, empowering developers to implement more sophisticated and dynamic registration logic with ease.</p><h3>RestTestClient</h3><p>Spring Framework 7 introduces <strong>RestTestClient</strong>, a testing utility for client-side REST API testing. It provides a fluent API for sending HTTP requests and verifying responses, with built-in assertions and integration with AssertJ for flexible response validation.</p><pre>import org.springframework.test.web.client.RestTestClient;<br><br>@SpringBootTest<br>class UserControllerTest {<br>    <br>    @Autowired<br>    private WebApplicationContext context;<br>    <br>    @Test<br>    void shouldGetUser() {<br>        RestTestClient client = RestTestClient.bindToApplicationContext(context).build();<br>        <br>        client.get()<br>            .uri(&quot;/users/123&quot;)<br>            .exchange()<br>            .expectStatus().isOk()<br>            .expectBody(User.class)<br>            .value(user -&gt; assertThat(user.getName()).isEqualTo(&quot;John&quot;));<br>    }<br>}</pre><p>RestTestClient simplifies testing by allowing developers to focus on request and response handling without needing an actual server, making it ideal for integration testing of REST endpoints.</p><h3>Support for Jackson 3</h3><p>Spring Framework 7 introduces support for <strong>Jackson 3</strong>, the latest major release of the JSON library.</p><p>Jackson 3 is designed to coexist with Jackson 2, allowing for a gradual migration path. Spring Boot 4 uses Jackson 3 as the default, while Spring Framework 7 maintains compatibility with both versions.</p><h3>References</h3><ul><li>Spring 7 release notes: <a href="https://github.com/spring-projects/spring-framework/wiki/Spring-Framework-7.0-Release-Notes">Spring Framework 7.0 Release Notes</a></li></ul><p>To stay updated with the latest updates in Java and Spring follow us on <a href="https://www.linkedin.com/in/code-wiz-740370302/"><strong>linked in</strong></a> and <a href="https://medium.com/@code.wizzard01"><strong>medium</strong></a>.</p><p><em>Originally published at </em><a href="https://codewiz.info/blog/spring-framework-7-and-boot-4-features/"><em>https://codewiz.info</em></a><em>.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d58c941265be" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Java Completable Future Deep Dive : A Comprehensive Guide]]></title>
            <link>https://medium.com/@code.wizzard01/java-completable-future-deep-dive-a-comprehensive-guide-869acd8fe83d?source=rss-3093c328bad8------2</link>
            <guid isPermaLink="false">https://medium.com/p/869acd8fe83d</guid>
            <category><![CDATA[threads]]></category>
            <category><![CDATA[concurrency]]></category>
            <category><![CDATA[java]]></category>
            <dc:creator><![CDATA[Code Wiz]]></dc:creator>
            <pubDate>Sun, 09 Nov 2025 13:04:45 GMT</pubDate>
            <atom:updated>2025-11-09T13:04:45.502Z</atom:updated>
            <content:encoded><![CDATA[<h3>Java Completable Future Deep Dive : A Comprehensive Guide</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*cu0UdxeJPPLNy9i8lVZbwA.png" /></figure><p>CompletableFuture has been part of the Java toolbox since Java 8 and provides a powerful API for asynchronous programming on the JVM. It lets you compose asynchronous pipelines, control threading, and coordinate multiple tasks without directly dealing with low-level concurrency APIs. In this blog post, we will cover the core API features and then see how it fits alongside virtual threads and structured concurrency in modern Java.</p><h3>What is CompletableFuture?</h3><p>CompletableFuture is a class in the java.util.concurrent package which implements the Future and CompletionStage interfaces. It provides a rich API for asynchronous programming on the JVM. You can think of it as a container for a future result of an asynchronous operation.</p><h3>Creating Futures</h3><h3>1.runAsync</h3><p>runAsync is used to create a CompletableFuture that does not return a value.</p><pre>CompletableFuture&lt;Void&gt; fireAndForget = CompletableFuture.runAsync(() -&gt; {<br>    auditService.log(&quot;event&quot;);<br>});</pre><h3>2.supplyAsync</h3><p>supplyAsync is used to create a CompletableFuture that returns a value. It takes a Supplier&lt;T&gt; as input which returns the result of the task. It returns a CompletableFuture&lt;T&gt; where T is the type of the value returned by the Supplier.</p><p>To get the result of the CompletableFuture, we can call the get method on it, but this will block the current thread until the result is available.</p><pre>CompletableFuture&lt;String&gt; fetchData = CompletableFuture.supplyAsync(() -&gt; {<br>    return httpClient.fetch(&quot;https://api.example.com/data&quot;);<br>});<br>String data = fetchData.get(); // Blocking call<br>System.out.println(data);</pre><blockquote>Both runAsync and supplyAsync methods use the common ForkJoinPool by default. Pass a custom executor when you need different sizing.</blockquote><pre>ExecutorService executor = Executors.newFixedThreadPool(10);<br><br>CompletableFuture&lt;OrderDetails&gt; orderFuture =<br>        CompletableFuture.supplyAsync(() -&gt; orderClient.fetch(orderId), executor);</pre><h3>3.completedFuture</h3><p>completedFuture is useful when you already have a value and want to seed a pipeline without scheduling work. It surfaces in testing and in branching flows where one side has a constant result.</p><pre>CompletableFuture&lt;Customer&gt; cachedCustomer =<br>        CompletableFuture.completedFuture(customerCache.get(customerId));</pre><h3>Creating Pipelines</h3><p>Below are some commonly used methods for chaining mutliple CompletableFuture objects to create a pipeline.</p><ul><li>thenApply: transform the result synchronously. This method is used when we want to apply some transformation on the result of the task.</li><li>thenApplyAsync: transform the result asynchronously. This method is used when we want to apply some transformation on the result of the task asynchronously.</li><li>thenCompose: flatten nested futures (think async flatMap). This method is used when we want to chain multiple CompletableFuture and one task depends on the result of the previous task.</li><li>thenAccept / thenRun: consume results without returning a value.</li></ul><pre>CompletableFuture&lt;BigDecimal&gt; priceFuture = orderFuture<br>        .thenApply(OrderDetails::price) // Transform the result synchronously<br>        .thenCompose(price -&gt; exchangeClient.convert(price, &quot;USD&quot;)) // Chain multiple `CompletableFuture`s and one task depends on the result of the previous task<br>        .thenApply(converted -&gt; converted.multiply(new BigDecimal(&quot;0.85&quot;))) // Transform the result synchronously</pre><p>Use thenCompose whenever the chained method produces a CompletableFuture. In the above example, we are calling exchangeClient.convert(price, &quot;USD&quot;) which returns a CompletableFuture&lt;BigDecimal&gt;. So we are using thenCompose to chain the CompletableFuture objects.</p><p>Use thenApply when the chained method does not produce a CompletableFuture. In the above example, we are calling OrderDetails::price which returns a BigDecimal. So we are using thenApply to chain the CompletableFuture objects.</p><h3>Combining Multiple Tasks</h3><p>Below are some commonly used methods for combining multiple CompletableFuture objects.</p><ul><li>allOf: wait for all futures to complete.</li><li>anyOf: resolve when the first completes.</li><li>thenCombine: combine two futures’ results.</li></ul><pre>CompletableFuture&lt;User&gt; userFuture = userClient.fetch(userId);<br>CompletableFuture&lt;List&lt;Order&gt;&gt; ordersFuture = orderClient.fetchOrders(userId);<br><br>CompletableFuture&lt;UserOrders&gt; combined = userFuture.thenCombine(ordersFuture,<br>        (user, orders) -&gt; new UserOrders(user, orders));<br>CompletableFuture&lt;Void&gt; allDone = CompletableFuture.allOf(userFuture, ordersFuture, auditFuture);</pre><p>Collecting results from allOf requires unpacking the individual futures:</p><pre>CompletableFuture&lt;List&lt;ProductPrice&gt;&gt; pricesFuture = CompletableFuture<br>        .allOf(priceFutures.toArray(CompletableFuture[]::new))<br>        .thenApply(v -&gt; priceFutures.stream()<br>                .map(CompletableFuture::join)<br>                .toList());</pre><p>anyOf finishes when the first future wins. Its result is typed as Object, so cast as needed.</p><pre>CompletableFuture&lt;Object&gt; fastest =<br>        CompletableFuture.anyOf(searchFromCache(q), searchFromDb(q), searchFromApi(q));<br>String winningResult = (String) fastest.join();</pre><p>join() retrieves the completed result without forcing you to catch checked exceptions; it mirrors get() but wraps failures in an unchecked CompletionException.</p><h3>Exception Handling Strategies</h3><p>Below are some commonly used methods for handling exceptions in CompletableFuture.</p><ul><li>exceptionally: recover with a fallback value.</li><li>handle: inspect both success and failure.</li></ul><pre>CompletableFuture&lt;String&gt; profileFuture = profileClient.fetch(userId)<br>        .exceptionally(ex -&gt; {<br>            metrics.increment(&quot;profile.fallback&quot;);<br>            return &quot;{}&quot;; // degraded response<br>        });<br><br>CompletableFuture&lt;Order&gt; safeOrderFuture = orderClient.fetch(orderId)<br>        .handle((result, ex) -&gt; {<br>            if (ex != null) {<br>                log.error(&quot;Failed to fetch order {}&quot;, orderId, ex);<br>                throw new OrderUnavailableException(orderId, ex);<br>            }<br>            return result;<br>        });</pre><h3>Timeouts</h3><p>We can use orTimeout to set a timeout on the CompletableFuture and if task doesn&#39;t complete within the timeout, it will throw a TimeoutException.</p><p>If you don’t want to throw an exception, you can use completeOnTimeout to set a default value if the task doesn&#39;t complete within the timeout.</p><pre>CompletableFuture&lt;Product&gt; productFuture = productClient.fetchAsync(productId)<br>        .orTimeout(500, TimeUnit.MILLISECONDS)<br>        .exceptionally(ex -&gt; {<br>            throw new ProductTimeoutException(productId, ex);<br>        });<br><br>CompletableFuture&lt;Product&gt; defaulted = productClient.fetchAsync(productId)<br>        .completeOnTimeout(Product.defaultProduct(), 300, TimeUnit.MILLISECONDS);</pre><h3>Virtual Threads with CompletableFuture</h3><p>Java 21 introduced virtual threads, allowing use to run millions of threads in a normal commodity machine. CompletableFuture works seamlessly with virtual threads:</p><p>We just need to use Executors.newVirtualThreadPerTaskExecutor() to create a virtual thread executor and pass it to the supplyAsync method.</p><pre>try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {<br>    CompletableFuture&lt;String&gt; salesReport = CompletableFuture.supplyAsync(() -&gt; reportService.generate(&quot;sales&quot;), executor);<br>    CompletableFuture&lt;String&gt; inventoryReport = CompletableFuture.supplyAsync(() -&gt; reportService.generate(&quot;inventory&quot;), executor);<br>    CompletableFuture&lt;String&gt; revenueReport = CompletableFuture.supplyAsync(() -&gt; reportService.generate(&quot;revenue&quot;), executor);<br>    CompletableFuture.allOf(salesReport, inventoryReport, revenueReport).join();<br>    List&lt;String&gt; reports = List.of(<br>            salesReport.get(),<br>            inventoryReport.get(),<br>            revenueReport.get());<br>}</pre><h3>Structured Concurrency — A Cleaner Way to Handle Concurrency</h3><p>Structured concurrency, still a preview feature in Java 25 provides a better and cleaner way to handle concurrency. We can expect most of the use cases for which we use CompletableFuture to be replaced by StructuredTaskScope in the future.</p><blockquote><strong>Further Reading:</strong> Learn about the new <a href="https://codewiz.info/blog/structured-concurrency-java25">Structured Concurrency API in Java 25</a> for a modern and safer approach to managing concurrency, cancellation, and result aggregation.</blockquote><h3>When to use Completable Future now</h3><ul><li>Parallelising independent I/O-bound calls such as fetching data from multiple APIs.</li><li>Orchestrating CPU-bound work where results feed into each other.</li><li>Bridging callback-based APIs into a fluent, composable model.</li><li>Implementing timeouts, retries, and fallbacks around asynchronous operations.</li></ul><h3>Conclusion</h3><p>CompletableFuture is a powerful API for asynchronous programming on the JVM. It lets you compose asynchronous pipelines, control threading, and coordinate multiple tasks without dropping down to low-level concurrency APIs. It is a great tool to have in your toolbox when you need to write asynchronous code in Java.</p><p>To stay updated with the latest updates in Java and Spring, follow us on <a href="https://www.linkedin.com/in/code-wiz-740370302/"><strong>LinkedIn</strong></a> and <a href="https://medium.com/@code.wizzard01"><strong>Medium</strong></a>.</p><p><em>Originally published at </em><a href="https://codewiz.info/blog/java-completable-future-guide/"><em>https://codewiz.info</em></a><em>.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=869acd8fe83d" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Implementing the Retry Pattern in Spring Boot]]></title>
            <link>https://medium.com/@code.wizzard01/implementing-the-retry-pattern-in-spring-boot-b5c2f64f6dd7?source=rss-3093c328bad8------2</link>
            <guid isPermaLink="false">https://medium.com/p/b5c2f64f6dd7</guid>
            <category><![CDATA[microservices]]></category>
            <category><![CDATA[java]]></category>
            <category><![CDATA[spring]]></category>
            <category><![CDATA[resilience]]></category>
            <dc:creator><![CDATA[Code Wiz]]></dc:creator>
            <pubDate>Wed, 05 Nov 2025 11:02:59 GMT</pubDate>
            <atom:updated>2025-11-05T11:02:59.296Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/847/1*CCihOu2Fb_JHKnh_l_elkA.png" /></figure><p>Retry pattern is a resilience strategy that automatically re-attempts a failed operation (such as a network call) a specified number of times, often with delays in between attempts, to handle transient errors gracefully. Anyone who has worked with microservices of reasonable complexity knows that failures are inevitable. In a distributed system, where multiple services communicate over the network, failures can occur due to various reasons such as network issues, service downtime, or unexpected errors. These failures can lead to a poor user experience if not handled properly.</p><p>Many of these failures are transient in nature-they might be temporary network glitches, momentary service overloads, or brief connectivity issues that resolve themselves quickly. However, if these intermittent failures are not handled properly, they can lead to a cascading failure, where one service failure causes other dependent services to fail as well. This can result in a complete system outage, leading to significant downtime and loss of revenue.</p><p>There are multiple resilience patterns that can be used to handle such failures. In this blog post, we will explore the retry pattern in detail and implement it using Spring Boot.</p><blockquote><em>📚 For a broader overview of resilience patterns (including retry, circuit breaker, bulkhead, and more), check out our complementary guide:<br></em><a href="https://codewiz.info/blog/resilient-microservices"><em>Top Resilience Patterns in Microservices and implementing in Spring Boot</em></a></blockquote><h3>Understanding the Retry Pattern</h3><p>Before diving into the code, let’s understand how the retry pattern works:</p><p>The retry pattern involves automatically retrying a failed operation with the expectation that it might succeed on subsequent attempts. This is useful for handling transient failures such as network blips or temporary service unavailability.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/847/1*CCihOu2Fb_JHKnh_l_elkA.png" /></figure><p>In the above diagram, the client sends a request to Service A, which in turn calls Service B. If Service B fails, Service A will retry the call after a back off period. The back off period can be exponential, meaning it increases with each failure.</p><h3>Implementing the Retry Pattern in a Spring Boot Application</h3><p>Now that we understand the retry pattern, let’s see how to implement it in a Spring Boot application.</p><p>In this blog we will follow the new resilience features introduced in Spring Framework 7.0. For version prior to 7.0, you can refer to the <a href="https://codewiz.info/blog/resilient-microservices">previous blog</a> where we implemented the retry pattern using Spring Retry.</p><p>As of Spring Framework 7.0, the @Retryable annotation is part of the core framework, so you no longer need to add the spring-retry dependency.</p><h3>Enabling Resilient Methods</h3><p>To enable the processing of resilience annotations, you can declare @EnableResilientMethods on a corresponding @Configuration class.</p><pre>@Configuration<br>@EnableResilientMethods<br>public class AppConfig {<br>}</pre><p>Let us say we have a method that fetches weather data from a weather API. We can annotate this method with @Retryable to enable retry functionality.</p><pre>@Retryable(includes = IOException.class, maxAttempts = 3, delay = 100, jitter = 10,multiplier = 2)<br>public WeatherRecords.WeatherData getWeather(PlaceRecords.Location location, String travelDate){<br>    log.info(&quot;Getting weather data for location: {} and date: {}&quot;, location, travelDate);<br>    String uriString = UriComponentsBuilder.fromUriString(&quot;/forecast?lat=-{lat}&amp;lon={long}&amp;appid={apiKey}&amp;units=metric&quot;)<br>            .buildAndExpand(location.latitude(), location.longitude(), apiKey)<br>            .toUriString();<br>    var weatherResponse = weatherClient.get()<br>            .uri(uriString)<br>            .retrieve()<br>            .body(WeatherRecords.WeatherResponse.class);<br>    return weatherResponse.list().stream()<br>            .filter(weatherData -&gt; weatherData.dtTxt().startsWith(travelDate))<br>            .findFirst()<br>            .orElseThrow(()-&gt;new NoDataFoundException(&quot;No weather data found for the date&quot;,100));<br>}</pre><p>In this code:</p><ul><li>The @Retryable annotation specifies that the method should be retried if it fails.</li><li>includes specifies the exception types that should trigger a retry (in this case, IOException).</li><li>maxAttempts specifies the maximum number of attempts (including the initial attempt), set to 3.</li><li>delay specifies the initial delay in milliseconds before the first retry (100ms).</li><li>multiplier specifies the multiplier for exponential backoff (2, so delays will be 100ms, 200ms, 400ms, etc.).</li><li>jitter adds randomness to the delay to prevent thundering herd problems.</li></ul><p>This configuration will retry the method up to 3 times when an IOException occurs, with exponential backoff starting at 100ms and doubling with each retry.</p><h3>Programmatic Retry Support</h3><p>RetryTemplate provides a programmatic API for retrying arbitrary blocks of code. We can use this to retry any code block that is not annotated with @Retryable.</p><pre>var retryTemplate = new RetryTemplate();<br><br>retryTemplate.execute(<br>        () -&gt; jmsClient.destination(&quot;notifications&quot;).send(...));</pre><p>If you want to customize the retry logic, you can create a RetryPolicy and pass it to the RetryTemplate.</p><pre>var retryTemplate = new RetryTemplate(RetryPolicy.withMaxAttempts(5));<br>retryTemplate.execute(<br>        () -&gt; jmsClient.destination(&quot;notifications&quot;).send(...));</pre><p>To stay updated with the latest updates in Java and Spring follow us on <a href="https://www.linkedin.com/in/code-wiz-740370302/"><strong>linked in</strong></a> and <a href="https://medium.com/@code.wizzard01"><strong>medium</strong></a>.</p><h3>References</h3><ul><li><a href="https://docs.spring.io/spring-framework/reference/7.0/core/resilience.html">Spring Framework Resilience Documentation</a></li></ul><p><em>Originally published at </em><a href="https://codewiz.info/blog/retry-pattern-spring/"><em>https://codewiz.info</em></a><em>.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=b5c2f64f6dd7" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>