<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: IROSH PERERA</title>
    <description>The latest articles on DEV Community by IROSH PERERA (@irosh-perera).</description>
    <link>https://dev.to/irosh-perera</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4133792%2F6db09563-d929-43c9-a34b-c8ab02148a70.jpeg</url>
      <title>DEV Community: IROSH PERERA</title>
      <link>https://dev.to/irosh-perera</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/irosh-perera"/>
    <language>en</language>
    <item>
      <title>I Built a Spring Boot Starter to Handle Duplicate API Requests</title>
      <dc:creator>IROSH PERERA</dc:creator>
      <pubDate>Sun, 20 Sep 2026 07:24:07 +0000</pubDate>
      <link>https://dev.to/irosh-perera/i-built-a-spring-boot-starter-to-handle-duplicate-api-requests-2m4j</link>
      <guid>https://dev.to/irosh-perera/i-built-a-spring-boot-starter-to-handle-duplicate-api-requests-2m4j</guid>
      <description>&lt;p&gt;A client sends a request to create an order. The server processes it, but the connection drops before the response reaches the client.&lt;/p&gt;

&lt;p&gt;The client retries. How can the API recognise that retry and avoid creating another order?&lt;/p&gt;

&lt;p&gt;This is the problem my open-source &lt;strong&gt;Spring Boot Idempotency Starter&lt;/strong&gt; aims to address.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is idempotency?
&lt;/h2&gt;

&lt;p&gt;For an API operation, idempotency means that repeating the same logical request should not produce additional side effects.&lt;/p&gt;

&lt;p&gt;An idempotency key lets the client identify that logical request. It should reuse the same key when retrying and generate a new key for a new operation.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the starter provides
&lt;/h2&gt;

&lt;p&gt;The starter supports:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An &lt;code&gt;@Idempotent&lt;/code&gt; annotation for protected endpoints.&lt;/li&gt;
&lt;li&gt;An &lt;code&gt;Idempotency-Key&lt;/code&gt; request header.&lt;/li&gt;
&lt;li&gt;Request body hash validation.&lt;/li&gt;
&lt;li&gt;Caching and replaying completed responses.&lt;/li&gt;
&lt;li&gt;In-memory and Redis storage.&lt;/li&gt;
&lt;li&gt;Configurable record expiry and processing timeout.&lt;/li&gt;
&lt;li&gt;Spring Boot auto-configuration.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is how to get started.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Add the Maven dependency
&lt;/h2&gt;

&lt;p&gt;Requirements: Java 17+, Spring Boot 3.x, and Maven 3.8+.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;io.github.iroshperera&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;spring-boot-idempotency-starter&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;0.1.0&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  2. Annotate an endpoint
&lt;/h2&gt;

&lt;p&gt;Add &lt;code&gt;@Idempotent&lt;/code&gt; to the endpoint you want to protect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;lk.irosh.idempotency.annotation.Idempotent&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;@Idempotent&lt;/span&gt;
&lt;span class="nd"&gt;@PostMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/orders"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;OrderResponse&lt;/span&gt; &lt;span class="nf"&gt;createOrder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="nd"&gt;@RequestBody&lt;/span&gt; &lt;span class="nc"&gt;OrderRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;
&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;orderService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This snippet belongs inside your controller. &lt;code&gt;OrderRequest&lt;/code&gt;, &lt;code&gt;OrderResponse&lt;/code&gt;, and &lt;code&gt;orderService&lt;/code&gt; represent your application's own types and service.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Configure the starter
&lt;/h2&gt;

&lt;p&gt;For a local example, add the following to &lt;code&gt;application.yml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;idempotency&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;storage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;memory&lt;/span&gt;
  &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;header-name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Idempotency-Key&lt;/span&gt;
  &lt;span class="na"&gt;default-expiry&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;24h&lt;/span&gt;
  &lt;span class="na"&gt;processing-timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5m&lt;/span&gt;
  &lt;span class="na"&gt;cache-response&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;reject-different-request-hash&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This configuration requires a key, enables response caching, and rejects reuse of a key with a different request body.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Send a request with a key
&lt;/h2&gt;

&lt;p&gt;Assuming your endpoint is available at &lt;code&gt;/orders&lt;/code&gt; and accepts the example payload:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:8080/orders &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Idempotency-Key: order-request-001"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"item":"Laptop","quantity":1}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Retry the same request with the &lt;strong&gt;same key and body&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The documented behaviour is:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;Behaviour&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;First request&lt;/td&gt;
&lt;td&gt;Executes the controller and saves the response&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Retry after completion, with the same key and body&lt;/td&gt;
&lt;td&gt;Returns the saved response without executing the controller again&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Same key with a different body&lt;/td&gt;
&lt;td&gt;Returns &lt;code&gt;409 Conflict&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Missing key when a key is required&lt;/td&gt;
&lt;td&gt;Returns &lt;code&gt;400 Bad Request&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Use a fresh key when creating a genuinely new order.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choosing a storage option
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;In-memory storage&lt;/strong&gt; is a starting point for development, testing, and single-instance applications. Its records are local to the running application and do not survive a restart.&lt;/p&gt;

&lt;p&gt;For shared records across application instances, the starter also provides &lt;strong&gt;Redis storage&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;idempotency&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;storage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis&lt;/span&gt;

&lt;span class="na"&gt;spring&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;redis&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;localhost&lt;/span&gt;
      &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;6379&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Merge these settings into your configuration and make sure Redis is running.&lt;/p&gt;

&lt;h2&gt;
  
  
  Things to consider before production use
&lt;/h2&gt;

&lt;p&gt;Idempotency records have a limited lifetime. Once a record expires, you should not assume that an old key will still prevent duplicate processing.&lt;/p&gt;

&lt;p&gt;Shared storage is also only one part of a distributed solution. Concurrent requests, failures during processing, and the relationship between business transactions and stored responses need careful testing.&lt;/p&gt;

&lt;p&gt;For critical operations, keep appropriate database constraints and business-level safeguards in place. An idempotency layer should not be treated as a blanket guarantee of exactly-once execution.&lt;/p&gt;

&lt;h2&gt;
  
  
  What’s next?
&lt;/h2&gt;

&lt;p&gt;The project roadmap includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;JDBC and PostgreSQL storage.&lt;/li&gt;
&lt;li&gt;MongoDB storage.&lt;/li&gt;
&lt;li&gt;Distributed locking improvements.&lt;/li&gt;
&lt;li&gt;Metrics and monitoring.&lt;/li&gt;
&lt;li&gt;Additional integration tests.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it and share your feedback
&lt;/h2&gt;

&lt;p&gt;The project is open source under the &lt;strong&gt;MIT License&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://github.com/IroshPerera/spring-boot-idempotency-starter" rel="noopener noreferrer"&gt;View Spring Boot Idempotency Starter on GitHub&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Feedback, bug reports, and contributions are welcome—especially around concurrency and failure scenarios.&lt;/p&gt;

&lt;p&gt;How do you handle duplicate requests in your Spring Boot applications?&lt;/p&gt;

</description>
      <category>java</category>
      <category>springboot</category>
      <category>opensource</category>
      <category>backend</category>
    </item>
    <item>
      <title>I Built a Java Library to Transliterate Sinhala Text into Latin Characters</title>
      <dc:creator>IROSH PERERA</dc:creator>
      <pubDate>Sun, 20 Sep 2026 07:16:07 +0000</pubDate>
      <link>https://dev.to/irosh-perera/built-a-java-library-to-transliterate-sinhala-text-into-latin-characters-439j</link>
      <guid>https://dev.to/irosh-perera/built-a-java-library-to-transliterate-sinhala-text-into-latin-characters-439j</guid>
      <description>&lt;p&gt;I’m sharing &lt;strong&gt;Sinhala Transliterator&lt;/strong&gt;, an open-source Java library I built to convert Sinhala script into Latin characters using predefined mapping rules.&lt;/p&gt;

&lt;p&gt;If you’re building a Java application that works with Sinhala text, this library provides a simple way to add transliteration.&lt;/p&gt;

&lt;h2&gt;
  
  
  What does it do?
&lt;/h2&gt;

&lt;p&gt;Transliteration represents text in another writing system. It does not translate the meaning into English.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;සිංහල → siṁhala&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The library includes mappings for Sinhala vowels, consonants, and diacritics.&lt;/p&gt;

&lt;h2&gt;
  
  
  Add it to your project
&lt;/h2&gt;

&lt;p&gt;Add this dependency to your Maven &lt;code&gt;pom.xml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;io.github.iroshperera&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;sinhala-transliterator&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;1.1.0&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Try it in Java
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.webmotech.transliterator.SinhalaToLatinTransliterator&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Main&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"සිංහල"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
                &lt;span class="nc"&gt;SinhalaToLatinTransliterator&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;transliterate&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Expected output, as shown in the project README:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;siṁhala
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Where could you use it?
&lt;/h2&gt;

&lt;p&gt;Potential uses include displaying Latin-script versions alongside Sinhala text and experimenting with Sinhala text-processing tools.&lt;/p&gt;

&lt;p&gt;Because the conversion uses predefined rules, test it with your application's actual text samples to check whether the output matches your needs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Feedback and contributions
&lt;/h2&gt;

&lt;p&gt;The project is available under the &lt;strong&gt;Apache License 2.0&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I’d welcome feedback on transliteration accuracy, edge cases, and documentation. If you find an unexpected result, please open a GitHub issue with the input, actual output, and expected output.&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://github.com/IroshPerera/sinhala-transliterator" rel="noopener noreferrer"&gt;Explore Sinhala Transliterator on GitHub&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Have you worked with Sinhala text in Java? What challenges did you encounter?&lt;/p&gt;

</description>
      <category>java</category>
      <category>opensource</category>
      <category>maven</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
