<?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 Arnab Nandy on Medium]]></title>
        <description><![CDATA[Stories by Arnab Nandy on Medium]]></description>
        <link>https://medium.com/@arnabnandy2?source=rss-d337d1ffe986------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/0*Wq0GSE3FxGLQULqD.jpeg</url>
            <title>Stories by Arnab Nandy on Medium</title>
            <link>https://medium.com/@arnabnandy2?source=rss-d337d1ffe986------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Wed, 14 Oct 2026 12:42:56 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@arnabnandy2/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[Giving Java Exceptions a Stable Identity with BugDNA]]></title>
            <link>https://blog.stackademic.com/giving-java-exceptions-a-stable-identity-with-bugdna-61fdc35efda8?source=rss-d337d1ffe986------2</link>
            <guid isPermaLink="false">https://medium.com/p/61fdc35efda8</guid>
            <category><![CDATA[software-engineering]]></category>
            <category><![CDATA[observability]]></category>
            <category><![CDATA[spring-boot]]></category>
            <category><![CDATA[open-source]]></category>
            <category><![CDATA[java]]></category>
            <dc:creator><![CDATA[Arnab Nandy]]></dc:creator>
            <pubDate>Sun, 14 Jun 2026 18:44:08 GMT</pubDate>
            <atom:updated>2026-06-18T13:51:38.691Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Xsv_aPm4dxNaC-uWEoTL9Q.png" /><figcaption>BugDNA demystified</figcaption></figure><p><em>Turn recurring stack traces into deterministic fingerprints you can group, track, compare, and monitor.</em></p><p>The full article is available <a href="https://medium.com/@arnabnandy2/61fdc35efda8?source=friends_link&amp;sk=68a431526f8473832c24929caf199fed">here</a>.</p><p>Production logs are excellent at telling us that something failed. They are often less helpful at answering a more operational question:</p><p><strong>Have we seen this exact failure before?</strong></p><p>The same bug can appear thousands of times with different timestamps, request IDs, user IDs, exception messages, and line numbers. To a person reading the logs, those events may clearly represent one underlying problem. To a log pipeline, however, they are usually thousands of unrelated blocks of text.</p><p>I built <strong>BugDNA</strong> to give recurring Java failures a stable identity.</p><p>BugDNA converts an exception into a deterministic fingerprint, such as:</p><blockquote>BUGDNA-7A3F21C98B64E12</blockquote><p>Once a failure has an identity, it becomes much easier to count it, compare it between deployments, attach it to logs and metrics, detect bursts, and discuss it across engineering teams.</p><p>BugDNA is open source, released under the MIT License, and available as a core Java library, a Spring Boot starter, and a command-line log analyser.</p><p><strong>The problem with grouping failures by message</strong></p><p>A common first attempt at exception grouping is to use the exception class and message:</p><blockquote>NullPointerException: customer 481 has no billing profile<br>NullPointerException: customer 927 has no billing profile</blockquote><p>These messages are different, but the bug may be identical.</p><p>Line numbers create a similar problem. Adding a comment or moving a method can turn this:</p><blockquote>PaymentService.java:142</blockquote><p>into this:</p><blockquote>PaymentService.java:147</blockquote><p>The application behaviour did not necessarily change, but a line-based grouping key would say that it did.</p><p>BugDNA deliberately excludes volatile data from fingerprint identity:</p><ul><li>Exception messages</li><li>Line numbers</li><li>Timestamps</li><li>Request IDs</li><li>User identifiers</li></ul><p>Instead, it uses the deepest exception type and up to five normalised stack frames. A normalised frame contains the class and method name, but not the source line.</p><p>Conceptually, the canonical input looks like this:</p><blockquote>java.lang.NullPointerException<br>com.example.UserService#getUser<br>com.example.UserController#showUser</blockquote><p>BugDNA hashes that canonical representation with SHA-256 and exposes the first 16 uppercase hexadecimal characters with a `BUGDNA-` prefix.</p><p>This means failures caused by different input values can still receive the same ID, while a meaningful refactor, such as renaming `getUser` to `loadUser`, can produce a new one.</p><p>That distinction is intentional: the goal is not to pretend that fingerprints<br>never change. The goal is to remove accidental variation while preserving useful structural changes.</p><p><strong>Generating a fingerprint</strong></p><p>The core library targets Java 8 and has no framework dependency.</p><p>Add it with Maven:</p><pre>&lt;dependency&gt;<br>    &lt;groupId&gt;io.github.arnabnandy7&lt;/groupId&gt;<br>    &lt;artifactId&gt;bugdna&lt;/artifactId&gt;<br>    &lt;version&gt;1.0.1&lt;/version&gt;<br>&lt;/dependency&gt;</pre><p>Then generate a fingerprint from any `Throwable`:</p><pre>try {<br>    userRepository.findById(userId);<br>} catch (RuntimeException failure) {<br>    Fingerprint fingerprint = BugDna.generate(failure);<br><br>    System.out.println(fingerprint.getId());<br>    System.out.println(fingerprint.getRootCause());<br>    System.out.println(fingerprint.getSignature());<br>}</pre><p>Example output:</p><blockquote>BUGDNA-7A3F21C98B64E120<br>java.lang.NullPointerException<br>UserService#getUser</blockquote><p>A fingerprint contains more than an ID. It also exposes:</p><ul><li>The deepest root-cause exception</li><li>The simple and fully qualified origin</li><li>The normalised frames used for grouping</li><li>The outer-to-inner cause chain</li><li>A simplified failure chain</li><li>A stability confidence score</li><li>An impact-based priority</li><li>A broad failure category</li><li>An operational root-cause family</li></ul><p>For a compact, log-friendly explanation:</p><pre>System.out.println(fingerprint.explain());</pre><blockquote>BUGDNA-7A3F21C98B64E120</blockquote><blockquote>Root Cause:<br>NullPointerException</blockquote><blockquote>Origin:<br>UserService#getUser</blockquote><blockquote>Confidence:<br>98%</blockquote><blockquote>Failure Chain:<br>UserController -&gt; UserService</blockquote><p><strong>From individual exceptions to operational signals</strong></p><p>A stable ID is useful on its own, but it becomes much more valuable when failures are aggregated.</p><p>BugDNA includes a thread-safe, in-memory `FailureTracker`:</p><pre>FailureTracker tracker = new FailureTracker();<br><br>tracker.capture(firstFailure);<br>tracker.capture(firstFailure);<br>tracker.capture(secondFailure);<br><br>System.out.println(tracker.topFailureReport());</pre><p>Example:</p><blockquote>Top 10 Failure Signatures<br>BUGDNA-001<br>Count: 2<br>BUGDNA-002<br>Count: 1</blockquote><p>The tracker can provide structured aggregates for dashboards or application code, not just text reports. It also records a bounded timeline of recent occurrences. That timeline enables burst detection, which can answer questions such as:</p><p><strong><em>Did this failure occur occasionally throughout the day, or did it suddenly reach 300 occurrences per minute?</em></strong></p><p>Different fingerprints can also be grouped into an operational family. A connection refusal, socket timeout, and exhausted connection pool may have different exact stack signatures but still point to the same larger incident:</p><blockquote>DATABASE_CONNECTIVITY</blockquote><p>Importantly, the message text may be used as heuristic evidence for the family classification, but it is never included in the fingerprint hash. Exact identity and broader operational classification remain separate concerns.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*H8yPWZf14ywLXq9MGdECBQ.png" /></figure><p><strong>Adding impact context</strong></p><p>A stack trace alone cannot tell you how urgent a failure is.</p><p>One exception affecting a background cleanup task is not necessarily equivalent to the same exception affecting 500 customers. For that reason, BugDNA only assigns a priority when the application supplies impact context:</p><pre>FailureContext context = FailureContext.of(<br>        125,   // occurrences<br>        18,    // affected users<br>        false  // fatal<br>);<br><br>Fingerprint fingerprint = BugDna.generate(failure, context);<br>System.out.println(fingerprint.getPriority());</pre><p>This example produces `HIGH`.</p><p>Without impact data, the priority is `UNKNOWN`. That is more honest than inferring business severity from an exception class.</p><p><strong>Automatic Spring Boot capture</strong></p><p>For Spring Boot applications, BugDNA provides a starter targeting Java 17 and Spring Boot 4.x:</p><pre>&lt;dependency&gt;<br>    &lt;groupId&gt;io.github.arnabnandy7&lt;/groupId&gt;<br>    &lt;artifactId&gt;bugdna-spring-boot-starter&lt;/artifactId&gt;<br>    &lt;version&gt;1.0.1&lt;/version&gt;<br>&lt;/dependency&gt;</pre><p>The starter can be enabled explicitly:</p><pre>@EnableBugDna<br>@SpringBootApplication<br>class Application {<br>}</pre><p>Unhandled Spring MVC and WebFlux exceptions are then fingerprinted, logged, and recorded automatically:</p><blockquote>ERROR [BUGDNA-7A3F21C98B64E120] Unhandled exception fingerprinted by bugdna</blockquote><p>BugDNA does not replace Spring’s normal error handling. The MVC integration returns control to Spring’s exception resolution flow, and the WebFlux integration re-emits the same error. It acts as a diagnostic infrastructure rather than an HTTP error response framework.</p><p>The starter also integrates with:</p><ul><li>SLF4J MDC</li><li>Spring Boot Actuator</li><li>Micrometer</li><li>Prometheus-compatible metrics</li></ul><p>MDC fields can include the fingerprint ID, confidence, category, family, and priority. Micrometer exposes total and unique failure gauges, while the optional Actuator endpoint returns recent fingerprint snapshots.</p><p>Background jobs, message listeners, and manually created threads can use the Spring-managed service directly:</p><pre>Fingerprint fingerprint = bugDna.fingerprint(failure);</pre><p><strong>Comparing deployments</strong></p><p>One of the most practical uses of stable failure identities is regression analysis.</p><p>Given fingerprints observed in versions `1.2.0` and `1.3.0`, BugDNA can classify them as:</p><ul><li><strong>New</strong>: present only in the newer deployment</li><li><strong>Resolved</strong>: present only in the older deployment</li><li><strong>Recurring</strong>: present in both deployments</li></ul><pre>DeploymentComparison comparison = RegressionDetector.compare(<br>        new DeploymentSnapshot(&quot;1.2.0&quot;, previousFingerprints),<br>        new DeploymentSnapshot(&quot;1.3.0&quot;, currentFingerprints)<br>);<br><br>System.out.println(comparison.report());</pre><blockquote>Version 1.2.0 -&gt; Version 1.3.0</blockquote><blockquote>New fingerprints: 4<br>Resolved fingerprints: 12<br>Recurring fingerprints: 8</blockquote><p>Occurrence counts do not change these classifications. This comparison answers whether a signature exists in each deployment, while the tracker and metrics answer how often it occurred.</p><p>When a refactor changes an exact fingerprint, BugDNA can compare two fingerprints for similarity and to explain structural differences such as a changed method, origin class, root cause, or normalised call path.</p><p><strong>Analysing existing logs from the command line</strong></p><p>BugDNA also includes a small CLI for logs that already contain `BUGDNA-*` IDs:</p><pre>bugdna analyze app.log</pre><blockquote>Unique Failures: 2</blockquote><blockquote>BUGDNA-001 : 42<br>BUGDNA-002 : 7</blockquote><p>Two log files can be compared directly:</p><pre>bugdna compare 1.2.0 app-v1.log 1.3.0 app-v2.log</pre><p>The CLI intentionally does not attempt to reconstruct fingerprints from arbitrary stack-trace text. It counts IDs that were generated at runtime, preserving the same identity model from application to analysis.</p><p><strong>Design boundaries</strong></p><p>BugDNA is intentionally small, but its boundaries matter.</p><p>The core tracker and Spring repository are in-memory. Counts and recent snapshots reset when the process restarts. The recent timeline is bounded, while the aggregate state retains one entry per unique fingerprint until it is cleared or the process ends.</p><p>That makes BugDNA useful as a lightweight application component, but it is not a replacement for a persistent observability infrastructure. Applications that need long-term history should export fingerprint IDs and aggregates to their existing logging, metrics, tracing, or storage platform.</p><p>Failure categories and families are heuristic operational metadata. They can help group-related symptoms, but they should not replace domain-specific exception handling or incident diagnosis.</p><p>Finally, fingerprints retain class and method names. Actuator endpoints and logs should be protected according to the same security and privacy standards as other application diagnostics.</p><p><strong>Why stable failure identity matters</strong></p><p>A handle lets a team say:</p><ul><li>“This deployment introduced four new failure signatures.”</li><li>“This exact bug occurred 1,003 times.”</li><li>“Three different signatures belong to the same database connectivity incident.”</li><li>“The failure moved from one method to another after the refactor.”</li><li>“The current spike is a burst, not normal background noise.”</li></ul><p>BugDNA does not remove the need for stack traces. It gives those stack traces a consistent identity so they can participate in the rest of an engineering system.</p><p>The project is available on <a href="https://github.com/arnabnandy7/bugdna">GitHub</a>, with the core and Spring Boot<br>artifacts published through Maven Central.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=61fdc35efda8" width="1" height="1" alt=""><hr><p><a href="https://blog.stackademic.com/giving-java-exceptions-a-stable-identity-with-bugdna-61fdc35efda8">Giving Java Exceptions a Stable Identity with BugDNA</a> was originally published in <a href="https://blog.stackademic.com">Stackademic</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>