<?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: Adarsh Kurumali</title>
    <description>The latest articles on DEV Community by Adarsh Kurumali (@elfinmaple).</description>
    <link>https://dev.to/elfinmaple</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%2F4138671%2F3ea28fa7-54d6-45f9-b15a-3e7949c90a0a.png</url>
      <title>DEV Community: Adarsh Kurumali</title>
      <link>https://dev.to/elfinmaple</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/elfinmaple"/>
    <language>en</language>
    <item>
      <title>I Built a Real-Time Product Recall Detection System in One Day with Java, Kafka, and Confluent Cloud</title>
      <dc:creator>Adarsh Kurumali</dc:creator>
      <pubDate>Wed, 23 Sep 2026 17:55:26 +0000</pubDate>
      <link>https://dev.to/elfinmaple/i-built-a-real-time-product-recall-detection-system-in-one-day-with-java-kafka-and-confluent-cloud-228o</link>
      <guid>https://dev.to/elfinmaple/i-built-a-real-time-product-recall-detection-system-in-one-day-with-java-kafka-and-confluent-cloud-228o</guid>
      <description>&lt;p&gt;&lt;em&gt;What a one-day project taught me about event-driven systems, matching events that arrive out of order, and the difference between a working prototype and a production-ready application.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Imagine buying a product today and learning tomorrow that its manufacturer has recalled a particular batch.&lt;/p&gt;

&lt;p&gt;The retailer now has a question to answer: &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Which customers purchased that exact batch?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It sounds like a database query. And for some systems, a database query may be an entirely reasonable solution.&lt;/p&gt;

&lt;p&gt;But I wanted to explore a different approach: &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;what if purchases and recall announcements were treated as events, and a service continuously connected them as they arrived?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That question became &lt;strong&gt;RecallRadar&lt;/strong&gt;, a real-time product recall detection prototype I built for Confluent AI Developer Day.&lt;/p&gt;

&lt;p&gt;Using &lt;strong&gt;Java, Confluent Cloud Kafka, Apache Flink SQL, and Spring Boot&lt;/strong&gt;, I went from an idea to a working application in one day. It publishes simulated purchase and recall events, identifies affected customers, generates safety alert events, and displays the results on a live dashboard.&lt;/p&gt;

&lt;p&gt;The interesting part wasn't simply getting the technologies to communicate. It was discovering what happens when two related events arrive at different times—and what an application needs to remember to connect them.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Faxt98ss7xm0guuzndydw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Faxt98ss7xm0guuzndydw.png" alt="RecallRadar Dashboard" width="800" height="387"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;RecallRadar's dashboard displaying alerts generated from simulated retail events.&lt;/p&gt;

&lt;p&gt;The problem: A recall can arrive after the purchase—or before it&lt;/p&gt;

&lt;p&gt;Consider two events:&lt;/p&gt;

&lt;p&gt;Purchase event&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "customerId": "CUST-104",
  "productId": "PRODUCT-42",
  "batchId": "BATCH-7"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Recall event&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "productId": "PRODUCT-42",
  "batchId": "BATCH-7",
  "reason": "Potential safety issue"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These are simplified examples of the event data, not a copy of the project's complete event schema.&lt;/p&gt;

&lt;p&gt;The customer is affected because the product and batch identifiers match. Matching only the product identifier would be insufficient: a recall may apply to one manufacturing batch rather than every unit of a product.&lt;/p&gt;

&lt;p&gt;Now consider the order in which these events might arrive:&lt;/p&gt;

&lt;p&gt;Scenario A — Purchase first: A customer buys the product. Days later, the manufacturer announces a recall.&lt;/p&gt;

&lt;p&gt;Scenario B — Recall first: A recall is already known, but a purchase event arrives afterward. This might represent a delayed transaction record or a purchase that requires investigation.&lt;/p&gt;

&lt;p&gt;Both scenarios matter. A service that checks only new recalls against previously seen purchases misses one direction of the problem.&lt;/p&gt;

&lt;p&gt;That leads to the central design lesson of this project:&lt;/p&gt;

&lt;p&gt;When related events can arrive in either order, processing the latest event is not enough. The system also needs access to relevant earlier information.&lt;/p&gt;

&lt;p&gt;The architecture I built&lt;/p&gt;

&lt;p&gt;RecallRadar uses four Kafka topics:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Topic&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;rr-purchases&lt;/td&gt;
&lt;td&gt;Simulated Retail Purchase Events&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;rr-recalls&lt;/td&gt;
&lt;td&gt;Product recall announcements&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;rr-alerts&lt;/td&gt;
&lt;td&gt;Customer-specific safety alert events&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;rr-recall-impact&lt;/td&gt;
&lt;td&gt;Flink-generated recall impact analytics&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two Java producers publish purchases and recalls to their respective topics.&lt;/p&gt;

&lt;p&gt;A Java detection service consumes both streams, matches events using productId and batchId, and publishes customer-specific alerts to &lt;code&gt;rr-alerts&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;From there, the application has two paths:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Apache Flink SQL&lt;/strong&gt; continuously aggregates alerts and writes recall impact results to &lt;code&gt;rr-recall-impact&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Spring Boot consumes individual alert events and serves a web dashboard. In the current prototype, the dashboard calculates its displayed metrics independently; it does not read the Flink output topic.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffi02oo6b37pqokhiq9qz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffi02oo6b37pqokhiq9qz.png" alt="Cluster Lineage" width="800" height="301"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The event-processing pipeline as shown in Confluent Cloud.&lt;/p&gt;

&lt;p&gt;The separation is intentional from a learning perspective: one alert stream can serve different consumers with different needs. The dashboard needs individual alerts, while the analytics pipeline needs aggregate information about recall impact.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lesson 1: Kafka connects the components without making them one application
&lt;/h2&gt;

&lt;p&gt;My first step was creating a Kafka cluster and topics in Confluent Cloud, then configuring the Java producers and consumers to communicate with them.&lt;/p&gt;

&lt;p&gt;Rather than having the recall producer call the detection service directly, it publishes an event. The detection service consumes that event when it is available.&lt;/p&gt;

&lt;p&gt;That distinction matters. The producer's responsibility is to publish the fact that a recall was announced. It doesn't need to know how the dashboard works or how recall impact is calculated.&lt;/p&gt;

&lt;p&gt;Likewise, the dashboard doesn't need to communicate directly with either producer. It consumes the alert events generated by the detection service.&lt;/p&gt;

&lt;p&gt;Kafka is not automatically the right choice for every recall system. A conventional database and scheduled query may be simpler for a small application. Event streaming becomes interesting when multiple systems need to react to new information independently, or when continuous processing is part of the requirement.&lt;/p&gt;

&lt;p&gt;For RecallRadar, Kafka gave me a practical way to explore that design.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lesson 2: Event correlation is a state-management problem
&lt;/h2&gt;

&lt;p&gt;The detection service has to answer two questions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;When a recall arrives, which previously observed purchases match it?&lt;/li&gt;
&lt;li&gt;When a purchase arrives, does it match a recall that is already known?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In the prototype, the Java service maintains the information needed for this matching in memory.&lt;/p&gt;

&lt;p&gt;Here is a simplified sketch of the matching rule:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;record ProductBatch(String productId, String batchId) {}

static boolean matches(
        String purchaseProductId,
        String purchaseBatchId,
        String recallProductId,
        String recallBatchId) {

    return purchaseProductId.equals(recallProductId)
            &amp;amp;&amp;amp; purchaseBatchId.equals(recallBatchId);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Illustrative Java code showing the matching rule—not a verbatim excerpt from the repository.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The matching condition itself is simple. The harder question is where the application gets the purchase and recall records to compare.&lt;/p&gt;

&lt;p&gt;If a recall arrives today, the relevant purchase may have been processed yesterday. If the service has forgotten that purchase, the matching function cannot help.&lt;/p&gt;

&lt;p&gt;This is why event correlation is also a state-management problem.&lt;/p&gt;

&lt;p&gt;For my one-day prototype, in-memory state was a practical way to get the matching logic working and test both event orders. It also creates an important limitation: restarting the service can lose that state.&lt;/p&gt;

&lt;p&gt;A production implementation would need a deliberate strategy for durable state, recovery, retention, and replay. It would also need to prevent duplicate customer alerts if an event is processed again.&lt;/p&gt;

&lt;p&gt;The takeaway: Writing a correct comparison function is only one part of stream processing. You must also decide what information the system needs to remember, for how long, and how it recovers that information after a failure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lesson 3: An alert event can be useful beyond the dashboard
&lt;/h2&gt;

&lt;p&gt;Once the detection service identifies a match, it publishes a customer-specific safety alert to &lt;code&gt;rr-alerts&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That alert can be consumed by the dashboard, but it can also become the input to a separate analytics pipeline.&lt;/p&gt;

&lt;p&gt;I used &lt;strong&gt;Apache Flink SQL&lt;/strong&gt; in &lt;strong&gt;Confluent Cloud&lt;/strong&gt; to continuously calculate recall impact statistics, including the number of distinct affected customers and the total number of alert events.&lt;/p&gt;

&lt;p&gt;Those results were written to &lt;code&gt;rr-recall-impact&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This is a useful distinction:&lt;/p&gt;

&lt;p&gt;An individual alert answers: &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Which customer may be affected by this recall?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;An aggregate answers: &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“How many customers and alert events are associated with this recall?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;They are different questions, even though they originate from the same stream.&lt;/p&gt;

&lt;p&gt;Flink SQL let me explore continuous aggregation without building a separate Java service solely to maintain those analytics.&lt;/p&gt;

&lt;p&gt;One subtle point: total alert events and distinct affected customers are not necessarily the same number. If a customer has multiple matching purchases or receives more than one alert, the event count can be higher than the distinct-customer count.&lt;/p&gt;

&lt;p&gt;That is why metric definitions matter as much as the query producing them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson 4: Managed infrastructure saves setup time, not debugging time&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Confluent Cloud helped me move quickly. I could create Kafka topics, inspect messages, register a JSON Schema for alerts, and work with Flink SQL through its cloud interface rather than setting up every component locally.&lt;/p&gt;

&lt;p&gt;That made a one-day prototype achievable for me.&lt;/p&gt;

&lt;p&gt;It did not mean every step worked on the first attempt.&lt;/p&gt;

&lt;p&gt;During development, I encountered a Kafka error along the lines of:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;Topic rr-purchases not present in metadata after 60000 ms&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That message is a useful reminder that application code is only one part of a distributed system. Topic availability, client configuration, credentials, connectivity, and permissions all need to line up before a producer can successfully publish.&lt;/p&gt;

&lt;p&gt;I worked through the configuration and verified successful event publishing before moving on to the detection logic.&lt;/p&gt;

&lt;p&gt;The experience changed how I think about debugging: when an event-driven application appears to do nothing, start by checking where the event stopped moving.&lt;/p&gt;

&lt;p&gt;Can the producer publish? Is the event visible in the topic? Is the consumer receiving it? Did the detection logic find a match? Was an alert published? Did the dashboard consume it?&lt;/p&gt;

&lt;p&gt;Following the event through the pipeline is much more useful than treating the entire application as one black box.&lt;/p&gt;

&lt;p&gt;What the finished prototype demonstrates—and what it doesn't&lt;/p&gt;

&lt;p&gt;By the end of the day, RecallRadar could process simulated purchase and recall events, generate matching customer alerts, maintain recall impact analytics through Flink SQL, and display live alert information in a Spring Boot dashboard.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://drive.google.com/file/d/1Qx3god6o4qW3Jyo1g9Rvu2MTkO-0xJFt/view?usp=drive_link" rel="noopener noreferrer"&gt;Watch the Live Demo to RecallRadar&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Demo: publishing events and observing the resulting alerts in the running application.&lt;/p&gt;

&lt;p&gt;But “working” and “production-ready” are not synonyms.&lt;/p&gt;

&lt;p&gt;RecallRadar currently uses simulated transactions and fictional recalls. Its detection service maintains matching state in memory. It generates alert events but does not deliver real emails or SMS messages to customers. Its dashboard consumes the alert stream independently of the Flink-generated analytics topic.&lt;/p&gt;

&lt;p&gt;Before using a system like this for actual consumer safety decisions, I would want to address durable state, recovery after restarts, duplicate-alert prevention, verified recall data, auditability, and reliable customer notification delivery.&lt;/p&gt;

&lt;p&gt;Those are not small finishing touches. They are part of the core design of a dependable system.&lt;/p&gt;

&lt;p&gt;The most useful thing I learned&lt;/p&gt;

&lt;p&gt;I started this project thinking about Kafka topics, Java consumers, and a live dashboard.&lt;/p&gt;

&lt;p&gt;I finished it thinking much more about time and memory.&lt;/p&gt;

&lt;p&gt;A purchase and a recall can be related even when they arrive hours or days apart. To recognize that relationship, the application must retain—or be able to recover—the right information.&lt;/p&gt;

&lt;p&gt;That insight extends beyond product recalls. Similar questions appear in payment reconciliation, fraud detection, order tracking, inventory management, and other systems where an event only becomes meaningful when connected to something that happened earlier.&lt;/p&gt;

&lt;p&gt;The technology stack made RecallRadar possible, but this was the lesson I found most valuable:&lt;/p&gt;

&lt;p&gt;In an event-driven system, deciding what to remember can be just as important as deciding what to process next.&lt;/p&gt;

&lt;p&gt;If you're learning Kafka or stream processing, try building a small application with two related event types. Then test what happens when you reverse their arrival order, restart the consumer, or process the same event twice. Those experiments reveal design questions that a happy-path demo can easily hide.&lt;/p&gt;

&lt;p&gt;Explore RecallRadar 👇&lt;/p&gt;

&lt;p&gt;The project source code and setup instructions are available on GitHub:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/3lfinmapl3/recallradar" rel="noopener noreferrer"&gt;RecallRadar — GitHub repository&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This was a one-day hackathon prototype, and I've shut down its Confluent Cloud resources after submission (Of course 😂). The repository remains available for anyone who wants to explore the implementation.&lt;/p&gt;

&lt;p&gt;I'd be interested to hear how others would approach durable event correlation and duplicate-alert prevention in a production version of this system.&lt;/p&gt;

</description>
      <category>kafka</category>
      <category>confluent</category>
      <category>java</category>
      <category>cloud</category>
    </item>
  </channel>
</rss>
