<?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 Tharindu Dulshan on Medium]]></title>
        <description><![CDATA[Stories by Tharindu Dulshan on Medium]]></description>
        <link>https://medium.com/@tharindudulshanfdo?source=rss-100b0df9f4e------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*aeQx9xdPAsAZkHgEEnfh2Q.png</url>
            <title>Stories by Tharindu Dulshan on Medium</title>
            <link>https://medium.com/@tharindudulshanfdo?source=rss-100b0df9f4e------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sat, 14 Nov 2026 02:35:42 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@tharindudulshanfdo/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[Getting Started with Micronaut: Building Lightweight Java Microservices]]></title>
            <link>https://medium.com/@tharindudulshanfdo/getting-started-with-micronaut-building-lightweight-java-microservices-77a305c4a421?source=rss-100b0df9f4e------2</link>
            <guid isPermaLink="false">https://medium.com/p/77a305c4a421</guid>
            <category><![CDATA[spring-boot]]></category>
            <category><![CDATA[java]]></category>
            <category><![CDATA[micronaut-framework]]></category>
            <category><![CDATA[micronaut]]></category>
            <category><![CDATA[spring]]></category>
            <dc:creator><![CDATA[Tharindu Dulshan]]></dc:creator>
            <pubDate>Sat, 07 Nov 2026 13:35:38 GMT</pubDate>
            <atom:updated>2026-11-07T13:35:38.809Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*cyR4LcJ55f-PueiSbKuaeQ.png" /></figure><h3>1. Introduction</h3><p>Micronaut is a modern, JVM-based framework designed for building lightweight, scalable, and cloud-native applications. It supports Java, Kotlin, and Groovy and provides features such as dependency injection, REST APIs, configuration management, HTTP clients, and microservice communication.</p><p>For developers familiar with Spring Boot, Micronaut offers a similar programming experience but takes a different approach to dependency injection and application initialization.</p><p>One of its main advantages is <strong>compile-time dependency injection</strong>, which reduces the need for runtime reflection and can improve application startup time and memory efficiency.</p><h3>2. What Makes Micronaut Different?</h3><p>Traditional dependency injection frameworks often discover and resolve dependencies at runtime.</p><p>Micronaut processes dependency injection metadata during compilation using annotation processors.</p><p>For example:</p><pre>import jakarta.inject.Singleton;</pre><pre>@Singleton<br>public class PaymentService {</pre><pre>    public String processPayment() {<br>        return &quot;Payment processed successfully&quot;;<br>    }<br>}</pre><p>The @Singleton annotation tells Micronaut to manage the lifecycle of this class and provide a single instance within the application context.</p><p>Micronaut generates the necessary bean metadata during compilation rather than relying on extensive runtime classpath scanning and reflection.</p><h3>3. Building a REST API with Micronaut</h3><p>Let’s build a simple Payment Management API.</p><p>First, create a Micronaut application using the official Micronaut Launch website:</p><p><a href="https://micronaut.io/launch/">https://micronaut.io/launch/</a></p><p>Choose Java as the programming language and Maven as the build tool.</p><p>For this example, we will implement an endpoint that processes payment requests.</p><h3>Step 1: Create a REST Controller</h3><p>In Micronaut, controllers are defined using the @Controller annotation.</p><pre>import io.micronaut.http.annotation.Controller;<br>import io.micronaut.http.annotation.Get;</pre><pre>@Controller(&quot;/payments&quot;)<br>public class PaymentController {</pre><pre>    @Get(&quot;/status&quot;)<br>    public String getPaymentStatus() {<br>        return &quot;Payment service is running&quot;;<br>    }<br>}</pre><p>When the application starts, Micronaut registers this controller and exposes the following endpoint:</p><pre>GET /payments/status</pre><p>Response:</p><pre>Payment service is running</pre><h3>Step 2: Implement Business Logic Using Dependency Injection</h3><p>Instead of putting business logic directly inside the controller, we can create a separate service class.</p><pre>import jakarta.inject.Singleton;</pre><pre>@Singleton<br>public class PaymentService {</pre><pre>    public String processPayment(double amount) {</pre><pre>        if (amount &lt;= 0) {<br>            throw new IllegalArgumentException(<br>                &quot;Invalid payment amount&quot;<br>            );<br>        }</pre><pre>        return &quot;Payment of &quot; + amount + &quot; processed successfully&quot;;<br>    }<br>}</pre><p>Now inject this service into the controller using constructor injection.</p><pre>import io.micronaut.http.annotation.Controller;<br>import io.micronaut.http.annotation.Get;<br>import io.micronaut.http.annotation.QueryValue;</pre><pre>@Controller(&quot;/payments&quot;)<br>public class PaymentController {</pre><pre>    private final PaymentService paymentService;</pre><pre>    public PaymentController(PaymentService paymentService) {<br>        this.paymentService = paymentService;<br>    }</pre><pre>    @Get(&quot;/process&quot;)<br>    public String processPayment(<br>            @QueryValue double amount) {</pre><pre>        return paymentService.processPayment(amount);<br>    }<br>}</pre><p>Example request:</p><pre>GET /payments/process?amount=1500</pre><p>Response:</p><pre>Payment of 1500.0 processed successfully</pre><p>Notice that we did not manually create the PaymentService instance using new PaymentService(). Micronaut automatically injects the dependency.</p><p>This is similar to constructor-based dependency injection in Spring Boot.</p><h3>4. Connecting to External Microservices</h3><p>In a microservices architecture, services frequently communicate with other services through HTTP APIs.</p><p>Micronaut provides a declarative HTTP client using the @Client annotation.</p><p>For example, imagine our Payment Service needs to communicate with an external Customer Service.</p><pre>import io.micronaut.http.annotation.Get;<br>import io.micronaut.http.client.annotation.Client;</pre><pre>@Client(&quot;http://localhost:8081&quot;)<br>public interface CustomerClient {</pre><pre>    @Get(&quot;/customers/{id}&quot;)<br>    String getCustomer(Long id);<br>}</pre><p>We can inject this client into another service:</p><pre>import jakarta.inject.Singleton;</pre><pre>@Singleton<br>public class CustomerService {</pre><pre>    private final CustomerClient customerClient;</pre><pre>    public CustomerService(CustomerClient customerClient) {<br>        this.customerClient = customerClient;<br>    }</pre><pre>    public String findCustomer(Long id) {<br>        return customerClient.getCustomer(id);<br>    }<br>}</pre><p>Micronaut generates the implementation of the declarative client.</p><p>This reduces the amount of boilerplate code required to make HTTP requests between microservices.</p><h3>5. Application Configuration</h3><p>Like Spring Boot, Micronaut supports externalized application configuration through application.yml.</p><p>For example:</p><pre>micronaut:<br>  application:<br>    name: payment-service</pre><pre>server:<br>  port: 8082</pre><p>This configuration sets the application name and HTTP server port.</p><p>You can also inject custom configuration values into your services rather than hardcoding them.</p><h3>6. Testing a Micronaut Application</h3><p>Testing is an important part of backend development.</p><p>Micronaut provides integration with JUnit 5 through Micronaut Test.</p><p>Example:</p><pre>import io.micronaut.test.extensions.junit5.annotation.MicronautTest;<br>import jakarta.inject.Inject;<br>import org.junit.jupiter.api.Test;</pre><pre>import static org.junit.jupiter.api.Assertions.*;</pre><pre>@MicronautTest<br>class PaymentServiceTest {</pre><pre>    @Inject<br>    PaymentService paymentService;</pre><pre>    @Test<br>    void shouldProcessPayment() {</pre><pre>        String result =<br>            paymentService.processPayment(1500);</pre><pre>        assertEquals(<br>            &quot;Payment of 1500.0 processed successfully&quot;,<br>            result<br>        );<br>    }<br>}</pre><p>The @MicronautTest annotation starts the application context, allowing dependencies to be injected during testing.</p><h3>7. Micronaut vs Spring Boot</h3><p>Both frameworks are excellent choices for building Java backend applications.</p><p>However, there are some architectural differences:</p><p>FeatureMicronautSpring BootDependency InjectionCompile-time metadataPrimarily runtime resolutionStartup PerformanceDesigned for fast startupDepends on application configurationMemory ConsumptionDesigned for lower overheadCan have higher runtime overheadREST API DevelopmentSupportedSupportedMicroservicesBuilt-in integrationsExtensive ecosystemTestingJUnit, SpockJUnit, Mockito</p><h3>8. Conclusion</h3><p>Micronaut is an interesting alternative to Spring Boot, particularly for developers building microservices and cloud-native applications.</p><p>Its compile-time dependency injection, declarative HTTP clients, configuration management, and testing support make it a framework worth exploring.</p><p>For developers already experienced with Java and Spring Boot, learning Micronaut should be relatively straightforward because many fundamental concepts remain familiar.</p><p>Rather than replacing Spring Boot in every scenario, Micronaut provides another option when startup performance, memory efficiency, and cloud-native deployment are important considerations.</p><p><strong>Useful resources:</strong></p><ul><li>Official website: <a href="https://micronaut.io/">https://micronaut.io/</a></li><li>Documentation: <a href="https://docs.micronaut.io/">https://docs.micronaut.io/</a></li><li>Tutorials: <a href="https://guides.micronaut.io/">https://guides.micronaut.io/</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=77a305c4a421" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Do You Still Need Resilience4j with Spring Framework 7?]]></title>
            <link>https://medium.com/@tharindudulshanfdo/do-you-still-need-resilience4j-with-spring-framework-7-ca65aca4440f?source=rss-100b0df9f4e------2</link>
            <guid isPermaLink="false">https://medium.com/p/ca65aca4440f</guid>
            <category><![CDATA[retry]]></category>
            <category><![CDATA[circuit-breaker]]></category>
            <category><![CDATA[java]]></category>
            <category><![CDATA[spring-boot]]></category>
            <category><![CDATA[resilience]]></category>
            <dc:creator><![CDATA[Tharindu Dulshan]]></dc:creator>
            <pubDate>Sat, 08 Aug 2026 07:51:55 GMT</pubDate>
            <atom:updated>2026-08-08T07:51:55.235Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*TBwugJxDsPS4EhQniyNgdQ.png" /></figure><p>For years, <strong>Resilience4j</strong> has been a popular choice for making Spring Boot applications more resilient. Example : Circuit Breakers, Rate Limiting, Retry, Bulkhead etc.</p><p>But with <strong>Spring Framework 7</strong>, Spring now provides more built-in resilience capabilities.</p><p>So, do we still need Resilience4j?</p><h3>Retry with Spring Framework 7</h3><p>For temporary failures, Spring can now handle retries directly.</p><pre>@Retryable(<br>    maxAttempts = 3,<br>    delay = 500<br>)<br>public PaymentResponse makePayment() {<br>    return paymentClient.call();<br>}</pre><p>If the service temporarily fails:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*RRkrZSdmfPvrN6FP60LyKg.png" /></figure><p>For simple retry scenarios, you may not need Resilience4j.</p><h3>Concurrency Limiting</h3><p>Spring Framework 7 also introduces concurrency limiting.</p><pre>@ConcurrencyLimit(50)<br>public PaymentResponse makePayment() {<br>    return paymentClient.call();<br>}</pre><p>This limits how many calls can execute simultaneously, helping protect your service or a downstream dependency during traffic spikes.</p><h3>Where Resilience4j Still Helps</h3><p>A major example is the <strong>Circuit Breaker</strong>.</p><pre>@CircuitBreaker(<br>    name = &quot;paymentService&quot;,<br>    fallbackMethod = &quot;fallback&quot;<br>)<br>public PaymentResponse makePayment() {<br>    return paymentClient.call();<br>}<br><br>public PaymentResponse fallback(Throwable ex) {<br>    return new PaymentResponse(&quot;Payment service unavailable&quot;);<br>}</pre><p>If the payment service keeps failing:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*c0LPHe7SBcUatQe2usb87A.png" /></figure><p>Retry and Circuit Breaker solve different problems:</p><p><strong>Retry:</strong> “It might be a temporary problem. Try again.”</p><p><strong>Circuit Breaker:</strong> “This service keeps failing. Stop calling it for a while.”</p><h3>What Should You Use?</h3><p>If you only need:</p><pre>Retry + Concurrency Control</pre><p>Spring Framework 7 may be enough.</p><p>If you need:</p><pre>Retry<br>+ Circuit Breaker<br>+ Rate Limiting<br>+ Bulkhead<br>+ Time Limiter</pre><p><strong>Resilience4j is still very useful.</strong></p><h3>Final Thoughts</h3><p>Spring Framework 7 doesn’t make Resilience4j obsolete.</p><p>It simply means we don’t need to add Resilience4j automatically to every Spring Boot application.</p><p>Use Spring’s built-in features for simpler resilience requirements. Use Resilience4j when you need more advanced patterns such as circuit breakers and rate limiting.</p><p>The real question isn’t:</p><p><strong>“Do I still need Resilience4j?”</strong></p><p>It’s:</p><p><strong>“What kind of failures does my application need to survive?”</strong></p><h3>References</h3><ul><li><a href="https://spring.io/blog/2025/11/13/spring-framework-7-0-general-availability">Spring Framework 7.0 General Availability</a></li><li><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><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=ca65aca4440f" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[HTTP QUERY Method: The Missing Piece Finally Added to HTTP]]></title>
            <link>https://medium.com/@tharindudulshanfdo/http-query-method-the-missing-piece-finally-added-to-http-35dd9743537c?source=rss-100b0df9f4e------2</link>
            <guid isPermaLink="false">https://medium.com/p/35dd9743537c</guid>
            <category><![CDATA[query]]></category>
            <category><![CDATA[query-methods]]></category>
            <category><![CDATA[https]]></category>
            <category><![CDATA[api-development]]></category>
            <category><![CDATA[rest-api]]></category>
            <dc:creator><![CDATA[Tharindu Dulshan]]></dc:creator>
            <pubDate>Fri, 03 Jul 2026 10:55:58 GMT</pubDate>
            <atom:updated>2026-07-03T10:55:58.941Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*4MS3JaES6W7Y6-8dmJj4bw.png" /></figure><p>For years, developers have faced a common dilemma when building REST APIs:</p><blockquote><strong><em>How do you send a complex, read-only query without misusing HTTP methods?</em></strong></blockquote><p>If you’ve ever created a /search endpoint using POST simply because your filters were too large for a URL, you&#39;re not alone.</p><p>After decades of discussion, the HTTP protocol finally has a standardized solution: <strong>the </strong><strong>QUERY method</strong>.</p><p>In <strong>June 2026</strong>, the Internet Engineering Task Force (IETF) published <strong>RFC 10008</strong>, officially introducing the HTTP QUERY method.</p><p>Let’s explore why this matters and how it changes API design.</p><h3>The Problem with GET</h3><p>GET is designed for retrieving resources. It is <strong>Safe, Idempotent, Cache-friendly</strong></p><p>A typical request looks like this:</p><pre>GET /products?category=laptops&amp;brand=Dell&amp;minPrice=1000&amp;maxPrice=2000</pre><p>This works well for simple filters.</p><p>But real-world APIs often require much more.</p><p>Imagine searching products with:</p><ul><li><em>Multiple category selections</em></li><li><em>Deeply nested logical filters</em></li><li><em>Precise date and time ranges</em></li><li><em>Advanced sorting options and pagination</em></li><li><em>Complex boolean conditions (</em><em>AND/</em><em>OR logic)</em></li></ul><p>Your URL quickly becomes unreadable.</p><pre>GET /products?category=laptops&amp;brand=Dell&amp;brand=Lenovo&amp;priceMin=1000&amp;priceMax=2000&amp;rating=4&amp;availability=true&amp;...</pre><p>Besides readability, long URLs can exceed limits imposed by browsers, proxies, and web servers.</p><h3>Why Not Just Use POST?</h3><p>Most APIs solve this problem using POST.</p><pre>POST /products/search<br>Content-Type: application/json</pre><pre>{<br>  &quot;category&quot;: &quot;laptops&quot;,<br>  &quot;brands&quot;: [&quot;Dell&quot;, &quot;Lenovo&quot;],<br>  &quot;price&quot;: {<br>    &quot;min&quot;: 1000,<br>    &quot;max&quot;: 2000<br>  }<br>}</pre><p>Although this works technically, it introduces semantic issues.</p><p>According to HTTP:</p><ul><li>POST is <strong>not</strong> guaranteed to be safe (it implies state modification).</li><li>POST is <strong>not</strong> inherently idempotent (repeating the request blindly isn&#39;t safely guaranteed by network layers).</li></ul><p>This means intermediaries, caches, and clients must assume the request <strong>could modify server state</strong>, even if your endpoint is only performing a search.</p><p>In other words, we’re using a write-oriented method for a read-only operation.</p><h3>Enter the HTTP QUERY Method</h3><p>The new QUERY method was created specifically for read-only requests that require a request body.</p><p>Example:</p><pre>QUERY /products HTTP/1.1<br>Host: api.example.com<br>Content-Type: application/json</pre><pre>{<br>  &quot;category&quot;: &quot;laptops&quot;,<br>  &quot;brands&quot;: [&quot;Dell&quot;, &quot;Lenovo&quot;],<br>  &quot;price&quot;: {<br>    &quot;min&quot;: 1000,<br>    &quot;max&quot;: 2000<br>  },<br>  &quot;sort&quot;: &quot;price&quot;<br>}</pre><p>The important part is the semantics.</p><p>A QUERY request:</p><ul><li><strong>Reads data only:</strong> It never changes the server state.</li><li><strong>Supports a request body:</strong> It allows complex JSON, XML, or SQL-like payloads.</li><li><strong>Is safe and idempotent:</strong> Intermediaries and clients can safely repeat the request if a network glitch occurs.</li></ul><p>This is exactly what developers have wanted for years.</p><h3>Why This Is a Big Deal</h3><p>The HTTP protocol has anchored the web for over 30 years. Throughout its evolution, API designers have consistently been forced to compromise, choosing between misusing GET (bloating the URL) or misusing POST (breaking caching and semantics).</p><p>By providing a standardized way to express complex, payload-backed reads, QUERY radically improves:</p><ul><li><strong>API Clarity:</strong> Endpoints instantly communicate their true intent.</li><li><strong>Security &amp; Privacy:</strong> Sensitive parameters stay contained safely inside the request body rather than leaking into server logs.</li><li><strong>Tooling Optimization:</strong> CDNs and caching proxies can now natively optimize caching strategies for heavy read payloads using content negotiation headers like Accept-Query.</li></ul><h3>Final Thoughts</h3><p>The introduction of the QUERY method may seem like a small addition to HTTP, but it addresses a problem developers have encountered for decades.</p><p>It allows APIs to express intent more accurately:</p><ul><li>GET for simple retrievals</li><li>QUERY for complex read-only requests</li><li>POST for creating or processing data</li></ul><p>It’s a small change with the potential to improve API design across the web.</p><p>As ecosystem support grows, don’t be surprised if QUERY becomes the preferred method for complex search endpoints that have traditionally relied on POST.</p><h3>References</h3><ul><li>RFC 10008 — <em>The HTTP QUERY Method</em> (June 2026)</li><li>HTTP Semantics (RFC 9110)</li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=35dd9743537c" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Stop Paying for Invoice Software. This Free Tool Runs Right in Your Browser.]]></title>
            <link>https://medium.com/@tharindudulshanfdo/stop-paying-for-invoice-software-this-free-tool-runs-right-in-your-browser-1437cdb81ea9?source=rss-100b0df9f4e------2</link>
            <guid isPermaLink="false">https://medium.com/p/1437cdb81ea9</guid>
            <category><![CDATA[invoicing-software]]></category>
            <category><![CDATA[invoice-app]]></category>
            <category><![CDATA[small-business]]></category>
            <category><![CDATA[free-invoice-generator]]></category>
            <category><![CDATA[invoice]]></category>
            <dc:creator><![CDATA[Tharindu Dulshan]]></dc:creator>
            <pubDate>Thu, 07 May 2026 09:23:54 GMT</pubDate>
            <atom:updated>2026-05-07T09:23:54.804Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*x1PiTSsY0sqNB6nyIwYDEQ.png" /></figure><p>No subscriptions. No accounts. No data sent to anyone. Just open it and start invoicing. Link : <a href="https://invoicegeny.com/">https://invoicegeny.com/</a></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*C5D-I014dcBT9R2IlcuP9A.png" /><figcaption>The Invoicegeny dashboard - all your invoicing activity at a glance.</figcaption></figure><p>If you run a small business, freelance, or do any kind of client work, invoicing is one of those necessary evils. You need professional-looking invoices, but the tools that create them either cost money every month, require you to create an account, or lock your data behind a login.</p><p>Invoicegeny is a free alternative that works differently. It runs entirely in your web browser. No sign-up, no subscription, no data ever leaving your device. Open it, set it up once, and start sending invoices in minutes.</p><h3>Your Data Stays on Your Device</h3><p>This is the biggest difference from every other invoicing tool.</p><p>Apps like FreshBooks, Wave, or QuickBooks store your business data on their servers. That means your client list, your pricing, your revenue, all of it lives somewhere you don’t fully control. If they change their pricing, get acquired, or shut down, you have a problem.</p><p>Invoicegeny stores everything in your browser’s local storage, the same place your browser saves your preferences and history. Nothing is sent to any server. Nothing is stored in the cloud. Your data is yours, period.</p><h3>Set It Up Once, Use It Forever</h3><p>The first thing you do is fill in your Seller Profile. This is your business info: name, logo, address, email, phone, tax ID, default tax rate, service charge, and bank account details. You fill this out once. Every invoice you create pulls from this profile automatically.</p><p>You can save multiple bank accounts and pick the right one per invoice with a single click. Currency is fully configurable — USD, EUR, GBP, LKR, and dozens more are supported out of the box.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*eI9rWp4o9N_zMxYJ7g4dOQ.png" /><figcaption>Set up your business profile once -it appears on every invoice automatically.</figcaption></figure><h3>Customers and Products - Managed, Not Re-Typed</h3><p>One of the most tedious parts of manual invoicing is re-entering the same customer details and prices over and over.</p><p>Invoicegeny has a proper customer list and product catalogue. When you create an invoice, you search for the customer by name, phone, or email, it finds them instantly. Same with products: search, click, and the item appears on the invoice with the right price already filled in.</p><p>Adding the same product twice? It just bumps the quantity. No duplicate lines. If a customer or product doesn’t exist yet, you can add them right from the invoice creation screen,no need to go to a separate page first.</p><h4>Create an Invoice in Under a Minute</h4><p>Once your profile, customers, and products are set up, creating an invoice is straightforward: pick a customer, search and add products, choose a payment method (Cash, Card, or Bank Transfer), set a due date, add any notes, and click “Save &amp; Download PDF”.</p><p>A professionally formatted PDF downloads to your computer instantly, with your logo, your business details, the itemised list, tax, service charge, and total. Ready to send to your client.</p><p>Invoice numbers are assigned automatically in sequence (INV-0001, INV-0002…) so you never have to think about it.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*tomircAyefqwgKagce2FIA.png" /><figcaption>The invoice creation form- search customers and products, fill in payment details, download PDF.</figcaption></figure><h3>The PDF Looks Professional</h3><p>A lot of free invoice tools produce PDFs that look like they were made in 2003. This one doesn’t.</p><p>The generated PDF includes your logo in the top left corner, your business name and contact details next to it, “INVOICE” prominently on the right with the invoice number and dates, a clean items table with alternating row shading, an itemised totals section showing subtotal, tax, service charge, and the final amount, payment details at the bottom, and a footer.</p><p>It’s the kind of invoice that makes you look established and professional, even if you’re a team of one.</p><h3>Track the Status of Every Invoice</h3><p>Your invoices list shows every invoice you’ve created with its current status: Draft, Sent, Paid, or Overdue. You can update the status with one click. It’s a simple but effective way to know at a glance what’s been paid and what you still need to chase.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ZWA4k2E5-LnPF0Wen4zqyA.png" /><figcaption>Every invoice tracked in one place, with colour-coded statuses for Sent, Paid, and Overdue.</figcaption></figure><h3>Who Is This For?</h3><p>This tool is a great fit if you are a freelancer (designer, developer, photographer, writer, consultant) who invoices clients directly, a sole trader or small business, someone who values privacy and doesn’t want their business data on third-party servers, someone tired of paying a monthly fee for a feature they only use occasionally, or operating in any country — multi-currency support is built in.</p><h3>Completely Free. Forever.</h3><p>There’s no free tier with limits. No premium plan. No credit card required. The tool has no server costs because it has no server. It’s free to use because there’s genuinely nothing to charge for.</p><p>Open the app, fill in your seller profile, add a couple of customers and products, create your first invoice, and download the PDF. The whole setup takes about five minutes. After that, invoicing takes under a minute per client.</p><p><strong>To try you can visit → </strong><a href="https://invoicegeny.com/"><strong>https://invoicegeny.com/</strong></a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=1437cdb81ea9" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Think Before You Catch: 3 Rules Every Developer Should Know]]></title>
            <link>https://medium.com/@tharindudulshanfdo/think-before-you-catch-3-rules-every-developer-should-know-522c9bb5178f?source=rss-100b0df9f4e------2</link>
            <guid isPermaLink="false">https://medium.com/p/522c9bb5178f</guid>
            <category><![CDATA[spring-boot]]></category>
            <category><![CDATA[java]]></category>
            <category><![CDATA[exception-handling]]></category>
            <category><![CDATA[backend-development]]></category>
            <category><![CDATA[retry]]></category>
            <dc:creator><![CDATA[Tharindu Dulshan]]></dc:creator>
            <pubDate>Sat, 04 Apr 2026 10:09:21 GMT</pubDate>
            <atom:updated>2026-04-04T10:09:21.520Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*2Q9ayvXg7BD5-KNEOMhX3A.png" /></figure><p>This article targets the beginner level java developers(Spring-Boot). If you’ve been writing Java code for a while, chances are you’ve fallen into this habit of wrapping everything up with try-catch blocks. As you keep going on you might have over used try-catch blocks. Excessive try-catch usage is often a sign of poor design rather than good error handling.</p><p>I’m not saying that try-catch blocks should be totally replaced but you should know when to use it and when not to.</p><p>In this article I will be covering 3 points that may help you,</p><ol><li><strong>Validate First</strong> — Let your request objects enforce rules using annotations instead of relying on exceptions for basic validation.</li><li><strong>Hierarchy</strong> <strong>of</strong> <strong>Custom Exceptions — </strong>Define meaningful exceptions that represent real business problems.</li><li><strong>Global Exception Handling with </strong><strong>@ControllerAdvice</strong> –Centralize error handling instead of repeating it in every method.</li></ol><p>At the end, I hope this may help you all to write better, redable and structured code.</p><h3>1. Validate First</h3><p>sometimes developers uses try-catch blocks to capture exceptions thrown because of invalid/bad inputs, Null inputs. For example you might have used a try catch to throw an exception when user enters an invalid email address.</p><pre>@RestController<br>@RequestMapping(&quot;/users&quot;)<br>public class UserController {<br><br>    @PostMapping(&quot;/register&quot;)<br>    public ResponseEntity&lt;String&gt; registerUser(@RequestBody UserRequest request) {<br>        try {<br>            if (!request.getEmail().contains(&quot;@&quot;)) {<br>                throw new RuntimeException(&quot;Invalid email format&quot;);<br>            }<br><br>            if (request.getName() == null) {<br>                throw new NullPointerException(&quot;Name cannot be null&quot;);<br>            }<br><br>            // imagine saving the user here<br>            return ResponseEntity.ok(&quot;User registered successfully&quot;);<br><br>        } catch (Exception e) {<br>            return ResponseEntity.badRequest().body(e.getMessage());<br>        }<br>    }<br>}</pre><p>instead of doing the above we can simply validate in the request object.</p><pre>import jakarta.validation.constraints.Email;<br>import jakarta.validation.constraints.NotBlank;<br><br>public class UserRequest {<br><br>    @NotBlank(message = &quot;Email is required&quot;)<br>    @Email(message = &quot;Invalid email format&quot;)<br>    private String email;<br><br>    // getters &amp; setters<br>}</pre><h3><strong>2. Hierarchy</strong> <strong>of</strong> <strong>Custom Exceptions</strong></h3><p>Another common mistake is catching generic exceptions like Exception and trying to figure out what went wrong after the failure.</p><pre>// generic exceptions will not tell you exactly what&#39;s wrong<br>try {<br>    userService.process(user);<br>} catch (Exception e) {<br>    logger.error(&quot;Something failed: &quot; + e.getMessage());<br>    return ResponseEntity.internalServerError().build();<br>}</pre><p>To fix this we can use pre written custom exceptions in java. Instead of catching everything, you can define exceptions that clearly describe the failure.</p><p><strong>Base Exception</strong></p><pre>public class AppException extends RuntimeException {<br><br>    private final HttpStatus status;<br>    private final String errorCode;<br><br>    public AppException(String message, HttpStatus status, String errorCode) {<br>        super(message);<br>        this.status = status;<br>        this.errorCode = errorCode;<br>    }<br><br>    public HttpStatus getStatus() {<br>        return status;<br>    }<br><br>    public String getErrorCode() {<br>        return errorCode;<br>    }<br>}</pre><p><strong>Specific Exceptions</strong></p><pre>public class ProductNotFoundException extends AppException {<br>    public ProductNotFoundException(Long productId) {<br>        super(&quot;Product not found with id: &quot; + productId,<br>              HttpStatus.NOT_FOUND,<br>              &quot;PRODUCT_NOT_FOUND&quot;);<br>    }<br>}<br><br>public class ProductOutOfStockException extends AppException {<br>    public ProductOutOfStockException(String productName) {<br>        super(&quot;Product out of stock: &quot; + productName,<br>              HttpStatus.CONFLICT,<br>              &quot;PRODUCT_OUT_OF_STOCK&quot;);<br>    }<br>}</pre><p><strong>Usage in Service Layer</strong></p><pre>public Product purchaseProduct(Long productId) {<br><br>    Product product = productRepository.findById(productId)<br>        .orElseThrow(() -&gt; new ProductNotFoundException(productId));<br><br>    if (product.getStock() &lt;= 0) {<br>        throw new ProductOutOfStockException(product.getName());<br>    }<br><br>    product.setStock(product.getStock() - 1);<br>    return productRepository.save(product);<br>}</pre><h3>3. Global Exception Handling with @ControllerAdvice</h3><p>As of now you have meaning full exceptions written the next thing to do is write a global exception handler to handle all exceptionjs at one place.</p><pre>import org.springframework.web.bind.annotation.ExceptionHandler;<br>import org.springframework.web.bind.annotation.RestControllerAdvice;<br><br>@RestControllerAdvice<br>public class GlobalExceptionHandler {<br><br>    @ExceptionHandler(AppException.class)<br>    public ResponseEntity&lt;ErrorResponse&gt; handleAppException(AppException ex) {<br>        ErrorResponse error = new ErrorResponse(<br>                ex.getErrorCode(),<br>                ex.getMessage()<br>        );<br>        return new ResponseEntity&lt;&gt;(error, ex.getStatus());<br>    }<br><br>    @ExceptionHandler(Exception.class)<br>    public ResponseEntity&lt;ErrorResponse&gt; handleGenericException(Exception ex) {<br>        ErrorResponse error = new ErrorResponse(<br>                &quot;INTERNAL_ERROR&quot;,<br>                &quot;Something went wrong&quot;<br>        );<br>        return new ResponseEntity&lt;&gt;(error, HttpStatus.INTERNAL_SERVER_ERROR);<br>    }<br>}</pre><p><strong>Error Response Model</strong></p><pre>public class ErrorResponse {<br>    private String code;<br>    private String message;<br><br>    public ErrorResponse(String code, String message) {<br>        this.code = code;<br>        this.message = message;<br>    }<br><br>    // getters &amp; setters<br>}</pre><h3>Bonus: Retrying Operations Safely</h3><p>Sometimes failures are <strong>transient. F</strong>or example, calling a payment gateway or an external API may fail temporarily. Instead of wrapping everything in try-catch and manually retrying, Spring provides a clean way to handle retries using <strong>Spring Retry</strong>.</p><ol><li><strong>Add Spring Retry Dependency</strong></li></ol><pre>&lt;dependency&gt;<br>    &lt;groupId&gt;org.springframework.retry&lt;/groupId&gt;<br>    &lt;artifactId&gt;spring-retry&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-aop&lt;/artifactId&gt;<br>&lt;/dependency&gt;</pre><p><strong>Enable retry in your application:</strong></p><pre>import org.springframework.retry.annotation.EnableRetry;<br>import org.springframework.boot.SpringApplication;<br>import org.springframework.boot.autoconfigure.SpringBootApplication;<br><br>@SpringBootApplication<br>@EnableRetry<br>public class DemoApplication {<br>    public static void main(String[] args) {<br>        SpringApplication.run(DemoApplication.class, args);<br>    }<br>}</pre><p><strong>2. Retry on Specific Exceptions</strong></p><pre>import org.springframework.retry.annotation.Backoff;<br>import org.springframework.retry.annotation.Retryable;<br>import org.springframework.stereotype.Service;<br><br>@Service<br>public class PaymentService {<br><br>    @Retryable(<br>        value = { NetworkException.class },  // Retry only on this exception<br>        maxAttempts = 3,                     // Try 3 times<br>        backoff = @Backoff(delay = 2000)     // Wait 2 seconds between retries<br>    )<br>    public void processPayment(Order order) {<br>        // Fake API Call<br>        if (Math.random() &lt; 0.7) {<br>            throw new NetworkException(&quot;Temporary network failure&quot;);<br>        }<br>        System.out.println(&quot;Payment processed successfully for order: &quot; + order.getId());<br>    }<br>}</pre><p>3. Optional: Handle Retry Exhaustion</p><p>You can catch the failure <strong>after all retries are exhausted</strong> using @Recover:</p><pre>import org.springframework.retry.annotation.Recover;<br><br>@Recover<br>public void recover(NetworkException e, Order order) {<br>    // Log and take alternate action<br>    System.out.println(&quot;Failed after retries: &quot; + e.getMessage() + &quot; for order &quot; + order.getId());<br>}</pre><p>Note : <em>Don’t retry </em><strong><em>all exceptions blindly</em></strong><em>. Only transient, recoverable errors should be retried, permanent failures like validation errors should fail fast.</em></p><h3>Summary</h3><p>Try-catch is not bad, over using and also misusing it is bad.</p><h3>References</h3><ul><li>Spring Boot Official Documentation — <a href="https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-validation"><strong>Validation</strong></a></li><li><a href="https://beanvalidation.org/"><strong>Jakarta Bean Validation</strong></a><strong> </strong>(JSR 380)</li><li>Spring Boot Exception Handling with <a href="https://spring.io/guides/gs/rest-service/#handling-errors"><strong>@ControllerAdvice</strong></a></li><li>Building a <a href="https://www.baeldung.com/exception-handling-best-practices-java"><strong>Custom Exception Hierarchy</strong></a><strong> </strong>in Java</li><li><a href="https://docs.spring.io/spring-retry/docs/current/reference/html/"><strong>Spring Retry </strong></a>— Declarative Retry for Spring Applications</li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=522c9bb5178f" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The Simplicity of Go: Why Developers Love It]]></title>
            <link>https://medium.com/@tharindudulshanfdo/the-simplicity-of-go-why-developers-love-it-ba99433e3c42?source=rss-100b0df9f4e------2</link>
            <guid isPermaLink="false">https://medium.com/p/ba99433e3c42</guid>
            <category><![CDATA[java]]></category>
            <category><![CDATA[go]]></category>
            <category><![CDATA[google]]></category>
            <category><![CDATA[golang]]></category>
            <category><![CDATA[programming]]></category>
            <dc:creator><![CDATA[Tharindu Dulshan]]></dc:creator>
            <pubDate>Thu, 07 Aug 2025 06:06:13 GMT</pubDate>
            <atom:updated>2025-08-07T06:06:13.503Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Rkku4q5lBjk4II3VoW-zDA.jpeg" /></figure><p>In the ever-evolving world of programming languages, simplicity often gets overshadowed by complexity and “powerful” features. However, simplicity is what makes software scalable, maintainable, and enjoyable to build. That’s where <strong>Go (or Golang)</strong> truly shines.</p><h3>👋 Why I Started Learning Go as a Java Developer</h3><p>After spending over 5 years building large-scale applications in <strong>Java</strong>, especially with <strong>Spring Boot</strong>, I found myself becoming increasingly aware of certain limitations:</p><ul><li><strong>Verbose boilerplate</strong> code, even with Lombok and annotations</li><li><strong>Heavy runtime requirements</strong> with the JVM</li><li><strong>Complex thread management</strong> for concurrency</li><li><strong>Slower startup and larger memory usage</strong>, especially for microservices</li></ul><p>While Java remains an incredibly powerful and mature language, I was looking for something more lightweight — something that could handle concurrency elegantly, compile into a single binary, and reduce development overhead for smaller services and tools.</p><p>That’s when I decided to give <strong>Go</strong> a try.</p><p>My first Go project was a small REST API(CRUD) that fetched data from an external service, processed it, and stored it in a database. To my surprise:</p><ul><li>The code was minimal and easy to read.</li><li>The concurrency model (goroutines and channels) felt natural.</li><li>The final binary was compact and deployable without Docker or JVM.</li></ul><h4><strong>1. Minimal Syntax, Maximum Clarity</strong></h4><p>Go’s syntax is clean, concise, and consistent. If you’ve worked with Java, Go might feel refreshing.</p><p>Compare a simple “Hello, World!” in Java:</p><pre>public class Main {<br>    public static void main(String[] args) {<br>        System.out.println(&quot;Hello, World!&quot;);<br>    }<br>}</pre><p>Now in Go:</p><pre>package main<br><br>import &quot;fmt&quot;<br><br>func main() {<br>    fmt.Println(&quot;Hello, World!&quot;)<br>}</pre><p>No boilerplate. No classes. Just main and fmt.</p><h4>2. One Way to Do Things</h4><p>Unlike languages that offer multiple ways to solve the same problem, Go deliberately provides <strong>just one right way</strong>.</p><ul><li>No inheritance (use composition)</li><li>No exceptions (explicit error handling)</li><li>No annotations, generics (until very recently), or reflection-heavy frameworks</li></ul><p>This makes reading other people’s code as easy as reading your own.</p><h4>3. Built-in Concurrency with Goroutines</h4><p>Concurrency in Java often involves managing threads, thread pools, and synchronization. Go simplifies it with <strong>goroutines</strong> and <strong>channels</strong>.</p><pre>go doSomething() // runs in the background</pre><p>And communication between goroutines:</p><pre>ch := make(chan string)<br><br>go func() {<br>    ch &lt;- &quot;Hello from goroutine&quot;<br>}()<br><br>msg := &lt;-ch<br>fmt.Println(msg)</pre><p>No thread management. No race conditions (if used well). Just elegant concurrency.</p><h4>4. Fast Compilation and Deployment</h4><p>Go compiles directly to machine code, producing a <strong>single binary</strong>. No JARs, WARs, or runtime dependencies.</p><pre>go build main.go<br>./main</pre><p>This makes it ideal for Docker containers, small services, or CLI tools where minimalism matters.</p><h4>5. Simpler Dependency Management</h4><p>Go Modules (go mod) make dependency management straightforward:</p><pre>go mod init myapp<br>go get github.com/gin-gonic/gin</pre><p>No XML files, no complex build systems — just go.mod and go.sum.</p><h4>6. Rich Standard Library</h4><p>The standard library covers everything from HTTP, JSON, and I/O to encryption and testing. You can build a web server with a few lines:</p><pre>package main<br><br>import (<br>    &quot;fmt&quot;<br>    &quot;net/http&quot;<br>)<br><br>func handler(w http.ResponseWriter, r *http.Request) {<br>    fmt.Fprintf(w, &quot;Welcome to Go!&quot;)<br>}<br><br>func main() {<br>    http.HandleFunc(&quot;/&quot;, handler)<br>    http.ListenAndServe(&quot;:8080&quot;, nil)<br>}</pre><h4>7. Explicit Error Handling</h4><p>Go favors <strong>explicit</strong> error handling over exceptions. While some find it verbose at first, it promotes clear and predictable error paths.</p><pre>val, err := doSomething()<br>if err != nil {<br>    return err<br>}</pre><p>Less magic, more control.</p><h3>Final Thoughts</h3><p>Go doesn’t try to be everything. It focuses on being simple, fast, and readable — the traits that many large languages slowly lose over time.</p><p>For me, as a Java developer, learning Go was refreshing. It reminded me of why I started programming in the first place: to build things quickly, efficiently, and enjoyably.</p><h3>🚀 Want to Try Go?</h3><ul><li>Install Go: <a href="https://golang.org/dl/">https://golang.org/dl/</a></li><li>Try it in browser: <a href="https://play.golang.org/">https://play.golang.org/</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=ba99433e3c42" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Builder Pattern in Java with Spring Boot — Clean Object Creation Made Easy]]></title>
            <link>https://medium.com/@tharindudulshanfdo/builder-pattern-in-java-with-spring-boot-clean-object-creation-made-easy-0f44f71a25fb?source=rss-100b0df9f4e------2</link>
            <guid isPermaLink="false">https://medium.com/p/0f44f71a25fb</guid>
            <category><![CDATA[java]]></category>
            <category><![CDATA[builder-pattern]]></category>
            <category><![CDATA[design-pattern-in-java]]></category>
            <category><![CDATA[lombok]]></category>
            <category><![CDATA[spring-boot]]></category>
            <dc:creator><![CDATA[Tharindu Dulshan]]></dc:creator>
            <pubDate>Sun, 15 Jun 2025 10:29:04 GMT</pubDate>
            <atom:updated>2025-06-15T10:29:04.364Z</atom:updated>
            <content:encoded><![CDATA[<h3>Builder Pattern in Java with Spring Boot — Clean Object Creation Made Easy</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*gEQvpmQ7Wyj5QtFKgKIlEg.png" /></figure><p>Complex objects with lots of fields, particularly optional ones, can easily become chaotic. The Builder Pattern is useful in this situation. It makes it possible to create objects that are neat, readable, and maintainable — especially in Java, where named parameters are not used.</p><p>In this blog I’ll be covering:</p><ul><li>What the Builder Pattern is</li><li>When to use it</li><li>How to implement it in Java</li><li>Bonus: How Lombok makes this even simpler in Spring Boot projects</li></ul><h3>What is the Builder Pattern?</h3><p>The <strong>Builder Pattern</strong> is a <strong>creational design pattern</strong> that lets you construct complex objects step by step. It separates the construction of an object from its representation, allowing the same construction process to create different representations.</p><p><strong>Example of a constructor problem:</strong></p><pre>public class User {<br>    private String firstName;<br>    private String lastName;<br>    private int age;<br>    private String phone;<br>    private String address;<br>}</pre><p>Now let’s see what happens when we have multiple constructors with different combinations of these fields?</p><pre>new User(&quot;Tharindu&quot;, &quot;Fernando&quot;);<br>new User(&quot;Tharindu&quot;, &quot;Fernando&quot;, 30);<br>new User(&quot;Tharindu&quot;, &quot;Fernando&quot;, 30, &quot;0771234567&quot;);</pre><ul><li>In the above case, Constructor overloads are everywhere — so the code is not clean! This will get ugly fast.</li></ul><h3>Solution: The Builder Pattern</h3><p>In Java we can write a <strong>Builder </strong>:</p><pre>User user = User.builder()<br>                .firstName(&quot;Tharindu&quot;)<br>                .lastName(&quot;Fernando&quot;)<br>                .age(30)<br>                .phone(&quot;0771234567&quot;)<br>                .address(&quot;Negombo,Sri Lanka&quot;)<br>                .build();</pre><p>Clean. Readable. No constructor chaos.</p><h3>Let’s see how to Implement Builder Pattern Manually</h3><ol><li><strong>POJO with Builder</strong></li></ol><pre>public class User {<br><br>    private String firstName;<br>    private String lastName;<br>    private int age;<br>    private String phone;<br>    private String address;<br><br>    private User(Builder builder) {<br>        this.firstName = builder.firstName;<br>        this.lastName = builder.lastName;<br>        this.age = builder.age;<br>        this.phone = builder.phone;<br>        this.address = builder.address;<br>    }<br><br>    public static Builder builder() {<br>        return new Builder();<br>    }<br><br>    public static class Builder {<br>        private String firstName;<br>        private String lastName;<br>        private int age;<br>        private String phone;<br>        private String address;<br><br>        public Builder firstName(String firstName) {<br>            this.firstName = firstName;<br>            return this;<br>        }<br><br>        public Builder lastName(String lastName) {<br>            this.lastName = lastName;<br>            return this;<br>        }<br><br>        public Builder age(int age) {<br>            this.age = age;<br>            return this;<br>        }<br><br>        public Builder phone(String phone) {<br>            this.phone = phone;<br>            return this;<br>        }<br><br>        public Builder address(String address) {<br>            this.address = address;<br>            return this;<br>        }<br><br>        public User build() {<br>            return new User(this);<br>        }<br>    }<br>}</pre><p>2. Usage in Spring Boot Service</p><pre>@Service<br>public class RegistrationService {<br>    public User registerUser() {<br>        return User.builder()<br>                .firstName(&quot;Tharindu&quot;)<br>                .lastName(&quot;Fernando&quot;)<br>                .age(25)<br>                .address(&quot;Colombo&quot;)<br>                .build();<br>    }<br>}</pre><h3><strong>⚡ Shortcut: Using Lombok’s </strong><strong>@Builder in Spring Boot</strong></h3><p>Spring Boot projects often use <strong>Project Lombok</strong> to reduce boilerplate.</p><pre>import lombok.Builder;<br>import lombok.Data;<br><br>@Data<br>@Builder<br>public class User {<br>    private String firstName;<br>    private String lastName;<br>    private int age;<br>    private String phone;<br>    private String address;<br>}</pre><p>And usage remains the same:</p><pre>User user = User.builder()<br>                .firstName(&quot;Mark&quot;)<br>                .lastName(&quot;Anthony&quot;)<br>                .age(32)<br>                .build();</pre><p>No need to write the builder class manually — clean and efficient.</p><h3><strong>Conclusion</strong></h3><p>The <strong>Builder Pattern</strong> is an essential design pattern in a Java developer’s toolkit, especially for Spring Boot applications where clean, maintainable code is key.</p><h4><strong>References</strong></h4><ul><li><a href="https://projectlombok.org/features/Builder">@Builder</a></li><li><a href="https://refactoring.guru/design-patterns/builder">Builder</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=0f44f71a25fb" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How Gzip saved our springboot server’s performance issue.]]></title>
            <link>https://medium.com/@tharindudulshanfdo/how-gzip-saved-our-springboot-servers-performance-issue-8edebf74f17f?source=rss-100b0df9f4e------2</link>
            <guid isPermaLink="false">https://medium.com/p/8edebf74f17f</guid>
            <category><![CDATA[performance-optimization]]></category>
            <category><![CDATA[java]]></category>
            <category><![CDATA[spring-boot]]></category>
            <category><![CDATA[gzip]]></category>
            <category><![CDATA[gzip-compression]]></category>
            <dc:creator><![CDATA[Tharindu Dulshan]]></dc:creator>
            <pubDate>Fri, 16 May 2025 07:03:18 GMT</pubDate>
            <atom:updated>2025-05-16T07:03:18.161Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/634/1*uNZkzWTR4BFvU0MNrCLsng.png" /></figure><h3>The Problem</h3><p>Recently, while working on a high-traffic Spring Boot application, I noticed some performance bottlenecks, particularly in response times for larger payloads. Users were experiencing slower page loads, especially during peak traffic. This became a critical issue as our application serves real-time data, and latency directly impacted user experience.</p><p>After analyzing the application, it became evident that our API responses were quite large — often exceeding several megabytes. This was especially true for endpoints returning lists of data or complex JSON objects. The challenge was clear: <strong>how could we reduce the response size without altering the actual data structure?</strong></p><h3>The Search for a Solution</h3><p>Initially, we tried optimizing database queries and refactoring some heavy methods, but the results were not significant enough. That’s when I came across <strong>Gzip compression</strong>, a technique to minimize payload size during data transfer. Gzip is widely supported by modern browsers and API clients, and Spring Boot offers built-in support for it.</p><h3>Implementing Gzip Compression in Spring Boot</h3><p>To enable Gzip compression in our Spring Boot application, I added the following configurations to application.properties:</p><pre>server.compression.enabled=true<br>server.compression.mime-types=application/json,application/xml,text/html,text/plain<br>server.compression.min-response-size=1024</pre><p><strong>Explanation:</strong></p><ul><li>server.compression.enabled: This enables Gzip compression.</li><li>server.compression.mime-types: Specifies the types of responses to compress (JSON, XML, HTML, Plain text).</li><li>server.compression.min-response-size: Sets the minimum response size (in bytes) to trigger compression.</li></ul><p>Within minutes of deploying these changes, I tested the API using <strong>Postman</strong> and saw immediate improvements. A typical JSON response that was previously around <strong>2 MB</strong> dropped to around <strong>500 KB</strong> — a <strong>75% reduction</strong> in size.</p><h3>Example of Unnecessary Payload vs. Compressed Payload</h3><p>To better understand the impact, let’s look at an example. Suppose we have an endpoint that responds with a large JSON object:</p><pre>{<br>  &quot;userId&quot;: 456,<br>  &quot;name&quot;: &quot;James Arthur&quot;,<br>  &quot;email&quot;: &quot;james.arthur@gmail.com&quot;,<br>  &quot;orders&quot;: [<br>    {<br>      &quot;orderId&quot;: 10,<br>      &quot;product&quot;: &quot;Tablet&quot;,<br>      &quot;price&quot;: 600,<br>      &quot;description&quot;: &quot;A high-resolution tablet with 10-inch display and 128GB storage.&quot;<br>    },<br>    {<br>      &quot;orderId&quot;: 11,<br>      &quot;product&quot;: &quot;Headphones&quot;,<br>      &quot;price&quot;: 150,<br>      &quot;description&quot;: &quot;Wireless noise-cancelling headphones with 30-hour battery life.&quot;<br>    },<br>    {<br>      &quot;orderId&quot;: 12,<br>      &quot;product&quot;: &quot;Smartwatch&quot;,<br>      &quot;price&quot;: 300,<br>      &quot;description&quot;: &quot;A smartwatch with fitness tracking and mobile connectivity.&quot;<br>    }<br>  ]<br>}<br>}</pre><p>Without Gzip compression, this payload may consume around <strong>2 KB</strong> of data. With Gzip enabled, it compresses down to <strong>500 bytes</strong> or less, demonstrating a <strong>75% reduction</strong>.</p><h4>Proof of Reduction:</h4><p>To verify, you can use the following curl commands:</p><p>Without Gzip:</p><pre>curl -H &quot;Accept-Encoding: identity&quot; -w &quot;<br>Size: %{size_download} bytes<br>&quot; -o /dev/null http://localhost:8080/api/test</pre><p>With Gzip:</p><pre>curl -H &quot;Accept-Encoding: gzip&quot; -w &quot;<br>Size: %{size_download} bytes<br>&quot; -o /dev/null http://localhost:8080/api/test</pre><p>You should observe a significant difference in the size_download value between the two requests. This is a clear indication of how Gzip optimizes network bandwidth and enhances response times.</p><h3>Verifying the Improvement</h3><p>To validate, I used the following curl command to inspect the headers:</p><pre>curl -H &quot;Accept-Encoding: gzip&quot; -I http://localhost:8080/api/test</pre><p>The response headers included:</p><pre>Content-Encoding: gzip</pre><p>Additionally, the browser’s Developer Tools → Network tab showed the reduced size of the compressed response.</p><h3>Applying Gzip Compression for Specific Endpoints</h3><p>While enabling Gzip globally is straightforward, there are cases where you might want to compress only specific endpoints. This is particularly useful if you want to avoid the CPU overhead for certain responses or exclude endpoints that handle already compressed data.</p><h4>Custom Filter for Endpoint-Based Gzip Compression</h4><p>We can create a <strong>Servlet Filter</strong> that selectively applies Gzip only for specific paths.</p><p><strong>GzipResponseFilter.java</strong>:</p><pre>import jakarta.servlet.Filter;<br>import jakarta.servlet.FilterChain;<br>import jakarta.servlet.FilterConfig;<br>import jakarta.servlet.ServletException;<br>import jakarta.servlet.ServletRequest;<br>import jakarta.servlet.ServletResponse;<br>import jakarta.servlet.http.HttpServletResponse;<br>import java.io.IOException;<br>import java.util.zip.GZIPOutputStream;<br><br>public class GzipResponseFilter implements Filter {<br><br>    @Override<br>    public void init(FilterConfig filterConfig) {}<br><br>    @Override<br>    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)<br>            throws IOException, ServletException {<br>        <br>        HttpServletResponse httpResponse = (HttpServletResponse) response;<br><br>        // Specify the endpoints where Gzip should be applied<br>        String requestURI = request.getServletContext().getContextPath();<br>        if (requestURI.startsWith(&quot;/api/user&quot;)) {<br>            httpResponse.addHeader(&quot;Content-Encoding&quot;, &quot;gzip&quot;);<br>            GZIPResponseWrapper gzipResponseWrapper = new GZIPResponseWrapper(httpResponse);<br>            chain.doFilter(request, gzipResponseWrapper);<br>            gzipResponseWrapper.close();<br>        } else {<br>            chain.doFilter(request, response);<br>        }<br>    }<br><br>    @Override<br>    public void destroy() {}<br>}</pre><p><strong>GZIPResponseWrapper.java</strong>:</p><pre>import jakarta.servlet.http.HttpServletResponse;<br>import jakarta.servlet.http.HttpServletResponseWrapper;<br>import java.io.IOException;<br>import java.io.OutputStream;<br>import java.util.zip.GZIPOutputStream;<br><br>public class GZIPResponseWrapper extends HttpServletResponseWrapper {<br><br>    private GZIPOutputStream gzipOutputStream;<br><br>    public GZIPResponseWrapper(HttpServletResponse response) throws IOException {<br>        super(response);<br>        this.gzipOutputStream = new GZIPOutputStream(response.getOutputStream());<br>    }<br><br>    @Override<br>    public OutputStream getOutputStream() throws IOException {<br>        return gzipOutputStream;<br>    }<br><br>    @Override<br>    public void flushBuffer() throws IOException {<br>        gzipOutputStream.flush();<br>    }<br><br>    @Override<br>    public void close() throws IOException {<br>        gzipOutputStream.close();<br>    }<br>}</pre><p><strong>WebConfig.java</strong>:</p><pre>import org.springframework.boot.web.servlet.FilterRegistrationBean;<br>import org.springframework.context.annotation.Bean;<br>import org.springframework.context.annotation.Configuration;<br><br>@Configuration<br>public class WebConfig {<br><br>    @Bean<br>    public FilterRegistrationBean&lt;GzipResponseFilter&gt; gzipFilter() {<br>        FilterRegistrationBean&lt;GzipResponseFilter&gt; registrationBean = new FilterRegistrationBean&lt;&gt;();<br>        registrationBean.setFilter(new GzipResponseFilter());<br>        registrationBean.addUrlPatterns(&quot;/api/user/*&quot;);<br>        return registrationBean;<br>    }<br>}</pre><h4>How It Works:</h4><ul><li>The filter intercepts HTTP responses for endpoints that start with /api/user/*.</li><li>Gzip compression is applied only to those specific URIs, reducing unnecessary overhead for other routes.</li><li>This approach allows granular control over what gets compressed.</li></ul><p>Now you have the flexibility to compress only what matters and avoid unnecessary CPU consumption.</p><h4>Real-World Impact</h4><p>Enabling Gzip compression not only improved response times but also reduced bandwidth usage, resulting in cost savings. Users reported smoother and faster load times, even on slower networks. This simple configuration turned out to be a game-changer for our application’s performance.</p><h4>Potential Drawbacks of Gzip Compression</h4><p>While Gzip offers significant benefits, there are some drawbacks to consider:</p><ol><li><strong>Increased CPU Usage:</strong><br>Compression is a CPU-intensive operation. For high-traffic applications, enabling Gzip can lead to increased CPU usage as each response needs to be compressed before being sent.</li><li><strong>Minimal Benefit for Already Compressed Files:</strong><br>File types like images (.jpg, .png, .gif) and compressed assets (.zip, .mp4) are already optimized, and applying Gzip provides almost no reduction in size while still consuming CPU.</li><li><strong>Latency for Small Responses:</strong><br>If the response payload is small (e.g., less than 1 KB), the overhead of compression and decompression can actually increase the response time slightly. This is why Spring Boot allows you to set a minimum size threshold.</li></ol><h3>Conclusion</h3><p>What started as a frustrating performance bottleneck turned into a learning opportunity. Gzip compression, with just a few lines of configuration, enhanced our Spring Boot application’s efficiency remarkably. If you’re facing similar issues with large payloads and sluggish response times, enabling Gzip might be the optimization you’re looking for.</p><h4><strong>References</strong></h4><p><a href="https://www.gzip.org/">The gzip home page</a></p><p><a href="https://www.baeldung.com/spring-resttemplate-compressing-requests">https://www.baeldung.com/spring-resttemplate-compressing-requests</a></p><p>Github : <a href="https://github.com/tharindu1998/gzip-springboot-example">https://github.com/tharindu1998/gzip-springboot-example</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=8edebf74f17f" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Resolving Deadlocks in Transactions in Spring Boot]]></title>
            <link>https://medium.com/@tharindudulshanfdo/resolving-deadlocks-in-transactions-in-spring-boot-32bd2d58f5e7?source=rss-100b0df9f4e------2</link>
            <guid isPermaLink="false">https://medium.com/p/32bd2d58f5e7</guid>
            <category><![CDATA[spring-boot]]></category>
            <category><![CDATA[deadlock-in-java]]></category>
            <category><![CDATA[transactions]]></category>
            <category><![CDATA[deadlock-prevention]]></category>
            <category><![CDATA[sql]]></category>
            <dc:creator><![CDATA[Tharindu Dulshan]]></dc:creator>
            <pubDate>Mon, 05 May 2025 04:59:14 GMT</pubDate>
            <atom:updated>2025-05-05T04:59:14.541Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*i7W7VkoCmjfWzbseZVE2UQ.png" /></figure><p>In transactional systems, deadlocks are a common concurrency issue, particularly when several threads or processes seek for the same resources. Deadlocks can cause major problems with application speed and consistency in Spring Boot applications that use relational databases and @Transactional annotations.</p><p>In this blog post, we will cover:</p><ul><li>What a deadlock is</li><li>How it occurs in Spring Boot transactional systems</li><li>Real code examples</li><li>How to detect and handle deadlocks</li><li>Best practices to prevent them</li></ul><h4>What is a Deadlock?</h4><p>A <strong>deadlock</strong> occurs when two or more transactions block each other by holding a lock and waiting for the other’s lock to be released.</p><p><strong>Example:</strong></p><ul><li>Transaction A locks <strong>Row 1</strong> and needs <strong>Row 2</strong></li><li>Transaction B locks <strong>Row 2</strong> and needs <strong>Row 1</strong></li><li>Neither can proceed, resulting in a deadlock</li></ul><p>Relational databases (like MySQL, PostgreSQL) will detect the deadlock and typically abort one of the transactions, throwing an exception.</p><h4>Deadlock in Spring Boot Example</h4><p>Let’s consider a simple scenario using Spring Data JPA and @Transactional.</p><p><strong>Model :</strong></p><pre>@Entity<br>public class Account {<br>    @Id<br>    @GeneratedValue(strategy = GenerationType.IDENTITY)<br>    private Long id;<br><br>    private String name;<br>    private String email;<br><br>    private BigDecimal balance;<br><br>    // getters and setters<br>}</pre><p><strong>Repository :</strong></p><pre>public interface AccountRepository extends JpaRepository&lt;Account, Long&gt; {}</pre><p><strong>Service with Transaction :</strong></p><pre>@Service<br>public class AccountService {<br><br>    @Autowired<br>    private AccountRepository accountRepository;<br><br>    //this method will cause deadlocks if two threads access the transfer method at the same time.<br>    //If both threads fetch and lock their first account row and then,<br>    //attempt to access the other, a deadlock is likely.<br>    @Transactional<br>    public void transfer(Long fromId, Long toId, BigDecimal amount) {<br>        Account from = accountRepository.findById(fromId).orElseThrow();<br>        Account to = accountRepository.findById(toId).orElseThrow();<br><br>        from.setBalance(from.getBalance().subtract(amount));<br>        to.setBalance(to.getBalance().add(amount));<br><br>        accountRepository.save(from);<br>        accountRepository.save(to);<br>    }</pre><ul><li>Thread A: transfers from Account 1 to 2</li><li>Thread B: transfers from Account 2 to 1</li></ul><h4>How to Detect Deadlocks</h4><ol><li><strong>Database Logs</strong></li></ol><p>Databases like MySQL and PostgreSQL will log deadlocks. For example, in MySQL, you can run:</p><pre>SHOW ENGINE INNODB STATUS;</pre><p><strong>2. Application Logs</strong></p><p>Spring will typically throw a DeadlockLoserDataAccessException or a database-specific exception. Make sure your logs include stack traces.</p><h4>How to Handle and Prevent Deadlocks</h4><ol><li><strong>Consistent Lock Ordering</strong></li></ol><p>Always access resources in the same order. In the above example, always load accounts in order of ID:</p><pre>    @Transactional<br>    public void transfer(Long fromId, Long toId, BigDecimal amount) {<br>        List&lt;Account&gt; accounts = accountRepository.findAllById(List.of(fromId, toId));<br>        Account from = fromId &lt; toId ? accounts.get(0) : accounts.get(1);<br>        Account to = fromId &lt; toId ? accounts.get(1) : accounts.get(0);<br><br>        accountRepository.save(from);<br>        accountRepository.save(to);<br>    }</pre><p><strong>2. Retry Logic on Deadlocks</strong></p><p>Use Spring’s @Transactional with retry logic using @Retryable from Spring Retry:</p><pre>    @Retryable(<br>            value = {TransientDataAccessException.class},<br>            maxAttempts = 3,<br>            backoff = @Backoff(delay = 200))<br>    @Transactional<br>    public void transfer2(Long fromId, Long toId, BigDecimal amount) {<br>        Account from = accountRepository.findById(fromId).orElseThrow();<br>        Account to = accountRepository.findById(toId).orElseThrow();<br><br>        from.setBalance(from.getBalance().subtract(amount));<br>        to.setBalance(to.getBalance().add(amount));<br><br>        accountRepository.save(from);<br>        accountRepository.save(to);<br>    }</pre><p>Don’t forget to enable Spring Retry:</p><pre>@EnableRetry<br>@SpringBootApplication<br>public class DeadlockexampleApplication {<br><br> public static void main(String[] args) {<br>  SpringApplication.run(DeadlockexampleApplication.class, args);<br> }<br><br>}</pre><ul><li>value and include are deprecated in latest versions so you can Define retry behavior via a RetryPolicy bean.</li></ul><pre>@Configuration<br>@EnableRetry<br>public class RetryConfig {<br><br>    @Bean<br>    public RetryPolicy retryPolicy() {<br>        SimpleRetryPolicy policy = new SimpleRetryPolicy();<br>        policy.setMaxAttempts(3);<br>        policy.setRetryableExceptions(Map.of(<br>            TransientDataAccessException.class, true<br>        ));<br>        return policy;<br>    }<br><br>    @Bean<br>    public RetryTemplate retryTemplate(RetryPolicy retryPolicy) {<br>        RetryTemplate template = new RetryTemplate();<br>        template.setRetryPolicy(retryPolicy);<br>        return template;<br>    }<br>}</pre><p>3. <strong>Locking Strategies</strong></p><p>Repository:</p><pre>@Repository<br>public interface AccountRepository extends JpaRepository&lt;Account, Long&gt; {<br>    @Lock(LockModeType.PESSIMISTIC_WRITE)<br>    @Query(&quot;SELECT a FROM Account a WHERE a.id = :id&quot;)<br>    Account findByIdForUpdate(@Param(&quot;id&quot;) Long id);<br><br>}</pre><p>Service:</p><pre>    @Transactional<br>    public void transfer(Long fromId, Long toId, BigDecimal amount) {<br>        Account from = accountRepository.findByIdForUpdate(fromId);<br>        Account to = accountRepository.findByIdForUpdate(toId);<br><br>        from.setBalance(from.getBalance().subtract(amount));<br>        to.setBalance(to.getBalance().add(amount));<br><br>        accountRepository.save(from);<br>        accountRepository.save(to);<br>    }</pre><p><strong>Important Notes:</strong></p><ul><li><strong>Pessimistic locking</strong> tells the database to acquire a <strong>row-level write lock</strong> as soon as the row is read — no other transaction can read/write it until the current one commits or rolls back.</li><li>This works well to <strong>prevent deadlocks</strong>, but can cause <strong>blocking and contention</strong> under high load. So use it <strong>carefully</strong>.</li><li>Always make sure you’re using it inside a @Transactional method — otherwise, the lock will be released immediately after the method exits.</li></ul><p><strong>4. Keep Transactions Short</strong></p><p>Avoid long-running transactions. Don’t perform unnecessary logic inside a transaction block.</p><h3>Conclusion</h3><p>Deadlocks are inevitable in highly concurrent systems, but with careful design, detection, and retries, you can manage them effectively in Spring Boot. Stick to consistent access patterns, use proper locking, and ensure your application is resilient to deadlocks through retries and monitoring.</p><h4>References</h4><p><a href="https://www.baeldung.com/spring-retry">https://www.baeldung.com/spring-retry</a></p><p><a href="https://www.baeldung.com/java-deadlock-livelock">https://www.baeldung.com/java-deadlock-livelock</a></p><p><a href="https://www.baeldung.com/transaction-configuration-with-jpa-and-spring">https://www.baeldung.com/transaction-configuration-with-jpa-and-spring</a></p><p><strong>GitHub : </strong><a href="https://github.com/tharindu1998/resolve-deadlock-example"><strong>https://github.com/tharindu1998/resolve-deadlock-example</strong></a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=32bd2d58f5e7" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Applying ACID Principles to Ensure Data Consistency and Reliability in Spring Boot Microservices…]]></title>
            <link>https://medium.com/@tharindudulshanfdo/applying-acid-principles-to-ensure-data-consistency-and-reliability-in-spring-boot-microservices-500fc0458415?source=rss-100b0df9f4e------2</link>
            <guid isPermaLink="false">https://medium.com/p/500fc0458415</guid>
            <category><![CDATA[acid]]></category>
            <category><![CDATA[spring-boot]]></category>
            <category><![CDATA[sql]]></category>
            <category><![CDATA[transactions]]></category>
            <category><![CDATA[acid-principles]]></category>
            <dc:creator><![CDATA[Tharindu Dulshan]]></dc:creator>
            <pubDate>Wed, 30 Apr 2025 09:05:57 GMT</pubDate>
            <atom:updated>2025-04-30T09:05:57.798Z</atom:updated>
            <content:encoded><![CDATA[<h3>Applying ACID Principles to Ensure Data Consistency and Reliability in Spring Boot Microservices with MySQL</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*jiOaRIu8SoIH_8mvFomqlA.jpeg" /></figure><p>In modern Microservices Architecture, <strong>data integrity</strong> and <strong>reliability</strong> are non-negotiable. Specially when you are dealing with SQL. This is where ACID(Atomicity, Consistency, Isolation and Durability) principles comes into play.</p><p>Although you may understand these principles, it is really important to leverage them to increase system performance.Let’s break it down in the context of a <strong>Spring Boot app</strong> with a relational database like PostgreSQL or MySQL.</p><h4>What Are ACID Properties?</h4><ol><li><strong>Atomicity -</strong> A transaction is all-or-nothing. Either all changes succeed or none do.</li></ol><ul><li><em>Use Case</em>: Booking a caregiver and updating availability must both succeed — or both fail.</li></ul><p>2. <strong>Consistency - </strong>Data must always be in a valid state before and after the transaction.</p><ul><li><em>Use Case</em>: A caregiver should never be double-booked.</li></ul><p><strong>3. Isolation - </strong>Transactions should not interfere with each other. Concurrency must not violate correctness.</p><ul><li><em>Use Case</em>: <em>Two clients booking the same caregiver at the same time shouldn’t both succeed.</em></li></ul><p><strong>4. Durability - </strong>Once a transaction is committed, it stays even after system failure.</p><ul><li><em>Use Case: A confirmed booking should persist even if the server crashes immediately after.</em></li></ul><h4><strong>Let’s look into some code level Applications,</strong></h4><h4><strong>1.Atomicity in SpringBoot.</strong></h4><p>Use Spring’s @Transactional annotation to wrap multiple operations into a single atomic unit.</p><pre>@Transactional<br>public void bookCaregiver(Long caregiverId, Long userId) {<br>    updateAvailability(caregiverId);<br>    createBooking(caregiverId, userId);<br>}</pre><blockquote>Optimization Tip: Avoid overusing @Transactional on read-heavy operations to reduce overhead.</blockquote><h4><strong>2. Ensuring Consistency</strong></h4><p>Use database <strong>constraints</strong> (foreign keys, check constraints) and <strong>validation</strong> at both application and DB layers.</p><ul><li><em>Use Spring Validator + Hibernate Validator (JSR-380).</em></li><li><em>Enforce DB-level constraints for additional safety.</em></li></ul><pre>@Column(nullable = false)<br>@NotNull<br>private LocalDate bookingDate;</pre><blockquote><strong>Optimization Tip</strong>: Push validation upstream — validate early (at API layer) to avoid costly DB rollbacks.</blockquote><h4><strong>3. Isolation for Concurrency Control</strong></h4><p>Spring Boot lets you define transaction isolation levels using @Transactional.</p><pre>@Transactional(isolation = Isolation.SERIALIZABLE)</pre><p>Isolation Levels:</p><ul><li><strong>READ_COMMITTED</strong> -Prevents Dirty reads (<em>Reading data from a transaction that hasn’t been committed yet.</em>)</li><li><strong>REPEATABLE_READ </strong>-Prevents Dirty Reads + Non-Repeatable Reads(<em>Ensures that </em><strong><em>if you read a row once</em></strong><em>, you’ll get </em><strong><em>the same data</em></strong><em> if you read it again within the same transaction, even if other transactions commit changes.)</em></li><li><strong>SERIALIZABLE</strong>- Prevents Dirty Reads + Non-Repeatable Reads + Phantom Reads(<em>new rows matching the query condition might appear if another transaction inserts them.</em>)</li></ul><blockquote><strong>Optimization Tip</strong>: Use REPEATABLE_READ or READ_COMMITTED unless absolute isolation is needed—<strong>SERIALIZABLE</strong> may hurt performance.</blockquote><h4>4. Durability through Logging and Replication</h4><p>Most relational databases (e.g., PostgreSQL, MySQL) ensure durability via <strong>WAL (Write-Ahead Logs)</strong> and disk flushing.</p><p>In Spring Boot, durability is mostly the job of the database and hardware — but you can support it:</p><ul><li><em>Use proper DB replication (e.g., master-slave setup).</em></li><li><em>Use persistent queues (Kafka, RabbitMQ) for critical flows before writing to DB.</em></li></ul><h4><strong>Bonus → Real-world Practices to Optimize for ACID in Spring Boot</strong></h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*74yILBuxqhl53a8QBD_vSA.png" /></figure><h4>Conclusion</h4><p>Understanding and leveraging <strong>ACID principles</strong> isn’t just about following theory — it’s about making practical, high-impact architectural decisions. In your Spring Boot applications:</p><ul><li>Use transactions wisely.</li><li>Choose the right isolation level.</li><li>Validate data early.</li><li>Embrace eventual consistency where possible.</li></ul><p>By doing this, you not only ensure <strong>data integrity</strong> but also improve <strong>scalability, maintainability</strong>, and <strong>system resilience</strong>.</p><p><strong>REFERENCES</strong></p><ul><li><a href="https://www.mongodb.com/resources/basics/databases/acid-transactions">ACID Properties in DBMS Explained</a></li><li><a href="https://www.databricks.com/glossary/acid-transactions">What are ACID Transactions?</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=500fc0458415" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>