<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged java</title>
<link>https://coderlegion.com/tag/java</link>
<description>Powered by Question2Answer</description>
<item>
<title>Reliable Event-Driven Architecture in Spring Boot: Outbox, Inbox, Retries, and Idempotency</title>
<link>https://coderlegion.com/32907/reliable-event-driven-architecture-in-spring-boot-outbox-inbox-retries-and-idempotency</link>
<description>&lt;p&gt;&lt;em&gt;Why publishing an event is only the beginning of building a reliable distributed system.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Building an event-driven application with Spring Boot is relatively straightforward.&lt;/p&gt;
&lt;p&gt;You create a Kafka producer, publish an event, implement a consumer, and process the message.&lt;/p&gt;
&lt;p&gt;Everything works beautifully during development.&lt;/p&gt;
&lt;p&gt;Then production introduces the situations your happy-path tests never covered.&lt;/p&gt;
&lt;p&gt;What happens when the database transaction commits, but Kafka is unavailable? What happens when the consumer processes an event successfully but crashes before acknowledging it? What happens when two application instances attempt to process the same message?&lt;/p&gt;
&lt;p&gt;These aren't unusual edge cases. They're fundamental challenges of distributed systems.&lt;/p&gt;
&lt;p&gt;And solving them requires more than adding a retry annotation.&lt;/p&gt;
&lt;p&gt;Let's explore the reliability boundaries behind a production-ready event-driven architecture.&lt;/p&gt;
&lt;h3 id=&quot;1-the-dual-write-problem&quot;&gt;1. The dual-write problem&lt;/h3&gt;
&lt;p&gt;Imagine a payment service that must perform two operations:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Save a payment in PostgreSQL.&lt;/li&gt;
&lt;li&gt;Publish a &lt;code&gt;PaymentCompleted&lt;/code&gt; event to Kafka.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;A straightforward implementation might look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Transactional
public void completePayment(Payment payment) {
    paymentRepository.save(payment);
    kafkaTemplate.send(
        &quot;payment.completed&quot;,
        new PaymentCompleted(payment.getId())
    );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The code looks reasonable, but there's a serious problem.&lt;/p&gt;
&lt;p&gt;The database transaction and Kafka publication do not share a single atomic transaction.&lt;/p&gt;
&lt;p&gt;Several outcomes are possible:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The database commits, but the event is never published.&lt;/li&gt;
&lt;li&gt;Kafka accepts the event, but the database transaction rolls back.&lt;/li&gt;
&lt;li&gt;Publication times out, leaving the application uncertain whether Kafka received the event.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Adding retries doesn't eliminate the fundamental inconsistency.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The problem is not whether Kafka is reliable. The problem is coordinating two independent systems.&lt;/strong&gt;&lt;/p&gt;
&lt;h4 id=&quot;the-transactional-outbox&quot;&gt;The transactional outbox&lt;/h4&gt;
&lt;p&gt;The transactional outbox pattern changes the transaction boundary.&lt;/p&gt;
&lt;p&gt;Instead of publishing directly to Kafka, the application writes an outbox record within the same database transaction as the business operation.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Payment Service
      |
      v
Database Transaction
      |
      +-- Save Payment
      |
      +-- Save Outbox Event
      |
      v
    Commit
      |
      v
Outbox Publisher
      |
      v
    Kafka
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Either the payment and outbox event are committed together, or neither is committed.&lt;/p&gt;
&lt;p&gt;A separate publisher retrieves pending outbox records and delivers them to Kafka.&lt;/p&gt;
&lt;p&gt;This solves the atomicity problem between the business state and the durable intent to publish.&lt;/p&gt;
&lt;p&gt;It does not guarantee that an event will be published exactly once.&lt;/p&gt;
&lt;p&gt;That distinction matters.&lt;/p&gt;
&lt;h3 id=&quot;2-why-the-outbox-still-needs-idempotency&quot;&gt;2. Why the outbox still needs idempotency&lt;/h3&gt;
&lt;p&gt;Consider this sequence:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The outbox publisher sends an event to Kafka.&lt;/li&gt;
&lt;li&gt;Kafka successfully accepts it.&lt;/li&gt;
&lt;li&gt;The application crashes before marking the outbox record as published.&lt;/li&gt;
&lt;li&gt;The publisher restarts and sends the event again.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Kafka may now contain two copies of the same logical event.&lt;/p&gt;
&lt;p&gt;This is a normal consequence of at-least-once delivery.&lt;/p&gt;
&lt;p&gt;The correct response is not to assume duplicates can always be prevented.&lt;/p&gt;
&lt;p&gt;Instead, design consumers that can safely tolerate them.&lt;/p&gt;
&lt;h3 id=&quot;3-the-transactional-inbox&quot;&gt;3. The transactional inbox&lt;/h3&gt;
&lt;p&gt;An inbox provides a durable record of incoming events that have already been processed.&lt;/p&gt;
&lt;p&gt;Each event should carry a stable identifier, such as an event ID generated when the logical event is created.&lt;/p&gt;
&lt;p&gt;When the consumer receives a message, it checks whether that event has already been processed.&lt;/p&gt;
&lt;p&gt;A simplified model looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Kafka Event
     |
     v
Consumer
     |
     v
Database Transaction
     |
     +-- Claim Event ID in Inbox
     |
     +-- Apply Business Changes
     |
     +-- Mark Inbox Completed
     |
     v
   Commit
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The important part is the transaction boundary.&lt;/p&gt;
&lt;p&gt;The inbox record and the business changes must participate in the same transaction when using the same transactional database.&lt;/p&gt;
&lt;p&gt;Otherwise, a consumer can record an event as completed even though its business operation failed.&lt;/p&gt;
&lt;h4 id=&quot;a-subtle-but-important-case&quot;&gt;A subtle but important case&lt;/h4&gt;
&lt;p&gt;Suppose a consumer receives &lt;code&gt;PaymentCompleted&lt;/code&gt;, updates an invoice, and produces &lt;code&gt;InvoicePaid&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Now the reliability boundary becomes:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Inbox + Business State + Outbox&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;All three should be committed atomically.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Incoming Event
      |
      v
Database Transaction
      |
      +-- Inbox Claim
      |
      +-- Update Invoice
      |
      +-- Create Outbox Event
      |
      v
    Commit
      |
      v
Outbox Publisher
      |
      v
Outgoing Event
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is one of the most useful patterns for building dependable event-driven workflows.&lt;/p&gt;
&lt;p&gt;The inbox protects the incoming side. The outbox protects the outgoing side.&lt;/p&gt;
&lt;p&gt;Neither eliminates the need for careful business-level idempotency, especially when external side effects are involved.&lt;/p&gt;
&lt;h3 id=&quot;4-retries-are-a-lifecycle-not-just-an-annotation&quot;&gt;4. Retries are a lifecycle, not just an annotation&lt;/h3&gt;
&lt;p&gt;Retries are often introduced with a simple configuration:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Retryable(
    retryFor = TransientException.class,
    maxAttempts = 3
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For short-lived operations, that can be perfectly reasonable.&lt;/p&gt;
&lt;p&gt;But what happens when a dependency is unavailable for 30 minutes? What happens when the application restarts? What happens when an event requires operator intervention?&lt;/p&gt;
&lt;p&gt;An in-memory retry loop cannot reliably manage every long-running failure scenario.&lt;/p&gt;
&lt;p&gt;For durable asynchronous processing, retries should be modeled as persistent state.&lt;/p&gt;
&lt;p&gt;A typical lifecycle might look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;PENDING
   |
   v
PROCESSING
   |
   +---- Success ----&amp;gt; COMPLETED
   |
   +---- Retryable failure
   |           |
   |           v
   |      RETRY_SCHEDULED
   |           |
   |           v
   |        PENDING
   |
   +---- Terminal failure
               |
               v
             FAILED
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Each attempt can record useful information:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Attempt count&lt;/li&gt;
&lt;li&gt;Next eligible execution time&lt;/li&gt;
&lt;li&gt;Last failure category&lt;/li&gt;
&lt;li&gt;Processing status&lt;/li&gt;
&lt;li&gt;Relevant timestamps&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Exponential backoff with jitter helps avoid repeatedly overwhelming an unhealthy dependency.&lt;/p&gt;
&lt;p&gt;But retry policies should distinguish transient failures from permanent ones.&lt;/p&gt;
&lt;p&gt;Retrying an invalid business request a thousand times won't make it valid.&lt;/p&gt;
&lt;h3 id=&quot;5-multi-instance-processing-changes-everything&quot;&gt;5. Multi-instance processing changes everything&lt;/h3&gt;
&lt;p&gt;A background publisher may work perfectly when there's only one application instance.&lt;/p&gt;
&lt;p&gt;Then the service is scaled to three pods.&lt;/p&gt;
&lt;p&gt;All three publishers query the same pending records.&lt;/p&gt;
&lt;p&gt;Without coordination, multiple instances may attempt to publish or process the same work concurrently.&lt;/p&gt;
&lt;p&gt;One common PostgreSQL technique is:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;SELECT id
FROM outbox_event
WHERE status = 'PENDING'
ORDER BY created_at
FOR UPDATE SKIP LOCKED
LIMIT 100;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;SKIP LOCKED&lt;/code&gt; allows concurrent workers to skip rows already locked by another transaction.&lt;/p&gt;
&lt;p&gt;This can improve coordination between workers, but it's not a complete delivery protocol.&lt;/p&gt;
&lt;p&gt;The design must still consider:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;How work is claimed and how long claims remain valid&lt;/li&gt;
&lt;li&gt;Transaction duration and lock ownership&lt;/li&gt;
&lt;li&gt;Recovery after a worker crashes&lt;/li&gt;
&lt;li&gt;Retry scheduling and fairness&lt;/li&gt;
&lt;li&gt;Ordering requirements&lt;/li&gt;
&lt;li&gt;Duplicate delivery&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For example, holding database locks across a slow network call can increase contention and create unnecessarily long transactions.&lt;/p&gt;
&lt;p&gt;A robust implementation may use short claim transactions, explicit processing ownership, and recovery mechanisms rather than keeping database transactions open while publishing.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Concurrency control and delivery guarantees are related, but they are not interchangeable.&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;6-exactly-once-is-not-an-end-to-end-promise&quot;&gt;6. Exactly-once is not an end-to-end promise&lt;/h3&gt;
&lt;p&gt;It's tempting to describe an event-driven system as exactly-once.&lt;/p&gt;
&lt;p&gt;But exactly-once semantics depend on the boundaries being discussed.&lt;/p&gt;
&lt;p&gt;Kafka transactions can provide guarantees for supported Kafka operations. A PostgreSQL transaction can atomically update records within PostgreSQL.&lt;/p&gt;
&lt;p&gt;Neither automatically makes a database update, Kafka publication, HTTP request, and external payment operation one globally atomic action.&lt;/p&gt;
&lt;p&gt;A more useful engineering goal is:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;At-least-once delivery with effectively-once business outcomes wherever possible.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;That typically requires durable event identities, transactional inbox processing, idempotent business operations, and explicit handling of external side effects.&lt;/p&gt;
&lt;p&gt;For payment systems, this distinction is particularly important.&lt;/p&gt;
&lt;p&gt;Processing the same event twice must not mean charging a customer twice.&lt;/p&gt;
&lt;h3 id=&quot;7-observability-is-part-of-reliability&quot;&gt;7. Observability is part of reliability&lt;/h3&gt;
&lt;p&gt;A system that retries failures silently can appear healthy while accumulating thousands of unprocessed events.&lt;/p&gt;
&lt;p&gt;Operational visibility should answer questions such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;How many events are pending?&lt;/li&gt;
&lt;li&gt;How old is the oldest unpublished event?&lt;/li&gt;
&lt;li&gt;How many retries are scheduled?&lt;/li&gt;
&lt;li&gt;Which events are permanently failing?&lt;/li&gt;
&lt;li&gt;Are workers making progress?&lt;/li&gt;
&lt;li&gt;Are duplicates being detected?&lt;/li&gt;
&lt;li&gt;Is event processing latency increasing?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Metrics, structured logs, tracing, and operator-facing failure information are not optional extras for a production system.&lt;/p&gt;
&lt;p&gt;They help distinguish a temporary delay from a workflow that has stopped making progress.&lt;/p&gt;
&lt;p&gt;A reliable system should make failure visible and recovery understandable.&lt;/p&gt;
&lt;h3 id=&quot;8-putting-these-patterns-together&quot;&gt;8. Putting these patterns together&lt;/h3&gt;
&lt;p&gt;A practical architecture combines several responsibilities:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Business Operation
       |
       v
Transactional Outbox
       |
       v
Durable Publisher
       |
       v
Message Broker
       |
       v
Transactional Inbox
       |
       v
Business Processing
       |
       +-- State Changes
       |
       +-- Optional New Outbox Events
       |
       v
Commit
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Around that flow, we need retry policies, concurrency coordination, idempotency, recovery, and observability.&lt;/p&gt;
&lt;p&gt;Each component solves a different failure mode.&lt;/p&gt;
&lt;p&gt;The outbox prevents losing the intent to publish when business data commits.&lt;/p&gt;
&lt;p&gt;The inbox protects consumers from repeated processing.&lt;/p&gt;
&lt;p&gt;Retries help transient failures recover.&lt;/p&gt;
&lt;p&gt;Concurrency controls coordinate multiple workers.&lt;/p&gt;
&lt;p&gt;Observability makes the system operable.&lt;/p&gt;
&lt;p&gt;None of these patterns should be treated as magic.&lt;/p&gt;
&lt;p&gt;Their value comes from understanding exactly which failure boundary each one protects.&lt;/p&gt;
&lt;h3 id=&quot;9-why-i-built-nerv-event&quot;&gt;9. Why I built NERV Event&lt;/h3&gt;
&lt;p&gt;After working with event-driven enterprise applications, I wanted these reliability concerns to be reusable rather than repeatedly implemented in individual services.&lt;/p&gt;
&lt;p&gt;That led to &lt;strong&gt;NERV Event&lt;/strong&gt;, an open-source Java and Spring Boot library focused on patterns such as transactional outbox, inbox processing, retries, and idempotency.&lt;/p&gt;
&lt;p&gt;The goal isn't to hide distributed-systems complexity behind annotations.&lt;/p&gt;
&lt;p&gt;It's to provide reusable infrastructure while keeping the reliability model explicit.&lt;/p&gt;
&lt;p&gt;GitHub: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/czetsuyatech/nerv-event&quot;&gt;https://github.com/czetsuyatech/nerv-event&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The library is one possible implementation of these ideas. The architectural principles are useful regardless of which framework or library you choose.&lt;/p&gt;
&lt;h3 id=&quot;final-thoughts&quot;&gt;Final thoughts&lt;/h3&gt;
&lt;p&gt;Publishing an event is easy.&lt;/p&gt;
&lt;p&gt;Building a system that behaves predictably when databases, brokers, application instances, and downstream dependencies fail is much harder.&lt;/p&gt;
&lt;p&gt;Reliable event-driven architecture begins with a simple question:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What happens if the application crashes at this exact point?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Ask that question at every transaction boundary, every message acknowledgment, and every external call.&lt;/p&gt;
&lt;p&gt;The answers will reveal where durability, idempotency, retries, and recovery are actually needed.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I'd be interested to hear how other teams approach this:&lt;/strong&gt; Do you use a transactional inbox alongside your outbox, or do you handle consumer idempotency directly in your business tables?&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Originally published on &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.czetsuyatech.com&quot;&gt;CzetsuyaTech&lt;/a&gt;. Explore more articles on Java, Spring Boot, distributed systems, and software architecture there.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/32907/reliable-event-driven-architecture-in-spring-boot-outbox-inbox-retries-and-idempotency</guid>
<pubDate>Thu, 29 Oct 2026 00:07:44 +0000</pubDate>
</item>
<item>
<title>Oracle Plans Post-Quantum TLS for Java 8 and 11 in Late 2027, and Wants Testers Now</title>
<link>https://coderlegion.com/32755/oracle-plans-post-quantum-tls-for-java-8-and-11-in-late-2027-and-wants-testers-now</link>
<description>&lt;p&gt;Security teams are starting to ask a question platform teams don't like: what's our plan for post-quantum encryption? For shops still running Java 8 or 11, the answer has mostly been a shrug.&lt;/p&gt;
&lt;p&gt;At Oracle AI World in Las Vegas this week, I got a firmer one. I sat down with Georges Saab, senior vice president of Oracle's Java Platform Group; Donald Smith, vice president of product management for Java Platform; and Bernard Traversat, vice president of software development for the Java Platform Group. I asked about the schedule for hybrid post-quantum key exchange in TLS 1.3.&lt;/p&gt;
&lt;p&gt;The feature is generally available in JDK 27. It arrived in JDK 25 with the October critical patch update. I laid out what comes next as I understood it: Java 21 and 17 in the first half of 2027, Java 11 and 8 in the second half. The team confirmed that was right.&lt;/p&gt;
&lt;p&gt;Those are half-year windows, not release dates. And there is a condition attached.&lt;/p&gt;
&lt;p&gt;Customers have been asking Oracle for post-quantum support as fast as possible. Now that it ships, the team wants them to use it. Many reply that they'd rather have it backported first. Oracle's answer is to test what already exists.&lt;/p&gt;
&lt;p&gt;Smith said the key to stability is &quot;just getting people using what we have, even though it's only in the latest.&quot;&lt;/p&gt;
&lt;p&gt;There is a chicken-and-egg problem. The specification was only finalized in February, so many of the endpoints a Java client talks to don't support the hybrid exchange yet. Smith called the backport itself the hard part. For the next two to three months, the team is focused on stability so the backport starts from solid ground.&lt;/p&gt;
&lt;p&gt;Feedback comes from two places. One is customers. The other is a group in OpenJDK that works with major third-party library projects, including Apache and Eclipse, and with the OpenSSL community.&lt;/p&gt;
&lt;p&gt;Performance is the other worry. Java 8 shipped years before TLS 1.3 was final, and the team had to backport it. That taught them the backport has to be stable and fast on old hardware. A team tracks performance benchmarks on every release, including the new algorithms, he said. He gave no numbers.&lt;/p&gt;
&lt;p&gt;If you run Java 8 or 11, waiting for the backport and then starting your testing is the slow path. Point a JDK 25 or 27 client at your post-quantum-capable endpoints, such as a gateway or proxy, and report what breaks. Oracle says it needs that feedback.&lt;/p&gt;
&lt;p&gt;Backports aren't the only way out. Saab said he now regularly meets teams where most workloads are on Java 21.&lt;/p&gt;
&lt;p&gt;He described the upgrade ladder this way. Going from 8 to 11 is the hardest step. Each step after that gets easier. &quot;21 to 25 super easy,&quot; he said. The platform has been changed so that code survives upgrades better than it used to.&lt;/p&gt;
&lt;p&gt;When teams do get stuck, the cause is usually outside the JDK. One is third-party libraries that nobody maintains. The other is the oldest problem: &quot;when people used APIs that were not part of the public API,&quot; Saab said.&lt;/p&gt;
&lt;p&gt;For finding those problems, Traversat pointed to Oracle's Java Management Service. It analyzes the APIs an application uses and reports deprecated APIs, changed behavior, and JVM argument changes between versions, such as 11 to 17 or 25. After that, he said, you still run the testing you use to certify the application.&lt;/p&gt;
&lt;p&gt;I also asked how a team should verify code that an AI agent has refactored. Smith was direct: &quot;I don't have a swift answer for that one.&quot; Saab said migration tooling is being built by other people. His team has helped groups inside Oracle migrate, to learn what the JDK could do better. Would the JDK team build migration tools itself? &quot;I'm not saying we will never do that,&quot; he said.&lt;/p&gt;
&lt;p&gt;So if you plan to use agents on a legacy Java codebase, the tools will come from outside the JDK. Verification still falls to your test suite.&lt;/p&gt;
&lt;p&gt;The other date on your calendar is the patch schedule. Oracle announced in the spring that it is moving to monthly security updates across the company, not just for Java. Smith said the Java team will phase this in. &quot;Next year we'll probably do 10 security updates and then it'll be every month thereafter,&quot; he said.&lt;/p&gt;
&lt;p&gt;AI is part of the reason. &quot;It's clear that AI has increased the rate at which you find vulnerabilities,&quot; Smith said. He added that it has also improved the team's ability to fix them.&lt;/p&gt;
&lt;p&gt;He worried at first that system administrators would push back. They haven't. &quot;The feedback is like nobody is surprised by this,&quot; he said. Some organizations will still have to adapt. A team on a quarterly change window is the obvious example.&lt;/p&gt;
&lt;p&gt;Two items have no dates: non-hybrid key exchange and Merkle certificates. Smith said the work follows the IETF specifications and he wasn't sure enterprises could plan around it. His advice was to follow OpenJDK development. The issue trackers are open, and code already exists.&lt;/p&gt;
&lt;p&gt;That caution is deliberate. &quot;Never get ahead of the spec,&quot; Smith said. He pointed to the original &lt;code&gt;java.util.Date&lt;/code&gt; as the example. An early implementation gained traction, it wasn't the best design, and the proper replacement took 10 to 15 years to arrive. Traversat added that anything shipped in the platform has to be supported for years, so Oracle moves conservatively.&lt;/p&gt;
&lt;p&gt;For Java teams, the planning date is the second half of 2027 for Java 8 and 11. The work that decides whether that date holds starts now, and Oracle is asking for your help.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/32755/oracle-plans-post-quantum-tls-for-java-8-and-11-in-late-2027-and-wants-testers-now</guid>
<pubDate>Tue, 27 Oct 2026 23:35:19 +0000</pubDate>
</item>
<item>
<title>Polar Bear Cache: Local Caching Without Repeated Remote Calls</title>
<link>https://coderlegion.com/32489/polar-bear-cache-local-caching-without-repeated-remote-calls</link>
<description>&lt;p&gt;&lt;strong&gt;Shared storage provides the source of truth. Local caches provide speed. Events help keep cache state in sync.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Project:&lt;/strong&gt; Polar Bear Cache — &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/babyblue94520/polar-bear-cache&quot;&gt;GitHub repository&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;In my previous discussions about microservices, I explored how shared storage and event-driven synchronization can work together to manage distributed session state.&lt;/p&gt;
&lt;p&gt;This time, I want to focus on another part of the problem: caching frequently accessed data across multiple service instances.&lt;/p&gt;
&lt;h3 id=&quot;the-problem-faster-reads-distributed-cache-invalidation&quot;&gt;The Problem: Faster Reads, Distributed Cache Invalidation&lt;/h3&gt;
&lt;p&gt;In a microservices architecture, multiple service instances often need the same data, especially data that is read frequently but updated relatively infrequently.&lt;/p&gt;
&lt;p&gt;A distributed cache can reduce database load, but accessing it still involves network round trips and potentially serialization and deserialization overhead.&lt;/p&gt;
&lt;p&gt;So, what if each service instance could keep frequently accessed data in its own local cache, while receiving notifications when that data changes elsewhere?&lt;/p&gt;
&lt;p&gt;That is the idea behind &lt;strong&gt;Polar Bear Cache&lt;/strong&gt;, an open-source Java caching library designed to work with Spring's familiar cache annotations.&lt;/p&gt;
&lt;h3 id=&quot;the-design-local-cache-event-driven-invalidation&quot;&gt;The Design: Local Cache + Event-Driven Invalidation&lt;/h3&gt;
&lt;p&gt;Each service instance maintains its own local cache. When data changes, an event service can notify other instances that specific cache entries need to be invalidated.&lt;/p&gt;
&lt;p&gt;The key principle is simple:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Broadcast invalidation events, not the entire cached data.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Polar Bear Cache integrates with Spring's cache annotations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;@Cacheable&lt;/code&gt; — Returns a cached value when available, or executes the underlying method to load it when the value is missing.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@CachePut&lt;/code&gt; — Updates the local cache and, when event-service integration is configured, notifies other instances to invalidate the corresponding entry.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@CacheEvict&lt;/code&gt; — Evicts cached data locally and, when event-service integration is configured, notifies other instances to invalidate their corresponding entries.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For example, when a product is updated:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The application updates the source of truth and its local cache.&lt;/li&gt;
&lt;li&gt;It publishes a cache-invalidation event through the event service.&lt;/li&gt;
&lt;li&gt;Other service instances receive the event and evict the corresponding local entry.&lt;/li&gt;
&lt;li&gt;Their next read loads the data again from the source of truth.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This allows repeated reads to stay local in the common case, while cross-instance communication is used to coordinate invalidation when data changes.&lt;/p&gt;
&lt;h3 id=&quot;when-does-this-approach-make-sense-&quot;&gt;When Does This Approach Make Sense?&lt;/h3&gt;
&lt;p&gt;This design is particularly suitable for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Read-heavy workloads with relatively infrequent updates.&lt;/li&gt;
&lt;li&gt;Applications running multiple service instances, each with its own local cache.&lt;/li&gt;
&lt;li&gt;Use cases where brief delays in cache invalidation are acceptable.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There is an important trade-off: event-driven invalidation is asynchronous. Between a data update and the time other instances process the invalidation event, stale data may still be served.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Polar Bear Cache does not guarantee strong consistency across service instances.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Depending on the application, you may also need to consider event-delivery reliability, duplicate events, cache expiration, and recovery strategies when a service instance misses an event.&lt;/p&gt;
&lt;p&gt;The goal is not to eliminate every consistency trade-off, but to make local caching practical in distributed applications without requiring every read to go through a remote cache.&lt;/p&gt;
&lt;h3 id=&quot;why-polar-bear-&quot;&gt;Why “Polar Bear”?&lt;/h3&gt;
&lt;p&gt;The name comes from a somewhat optimistic engineering idea:&lt;/p&gt;
&lt;p&gt;Local caching&lt;br&gt;
→ Fewer remote cache requests and network round trips&lt;br&gt;
→ Potentially more throughput from the same resources&lt;br&gt;
→ Potentially fewer servers needed for a given workload&lt;br&gt;
→ Lower energy consumption and greenhouse gas emissions&lt;br&gt;
→ Maybe we can help save polar bears. 🐻❄️🧊&lt;/p&gt;
&lt;p&gt;Of course, the actual impact depends on workload characteristics, infrastructure, and deployment choices. Performance improvements do not automatically translate into lower energy consumption, but I like the idea that better resource efficiency could benefit both applications and the environment.&lt;/p&gt;
&lt;h3 id=&quot;i-d-love-to-hear-your-thoughts&quot;&gt;I'd Love to Hear Your Thoughts&lt;/h3&gt;
&lt;p&gt;I'm interested in how other engineers approach local caching in distributed systems.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;How do you handle cache invalidation across service instances?&lt;/li&gt;
&lt;li&gt;Do you prefer event-driven invalidation, short TTLs, versioned keys, or a combination?&lt;/li&gt;
&lt;li&gt;What failure scenarios would you consider essential to address in this design?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you're interested in the implementation, feedback and technical discussion are very welcome.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Less remote access, more ice.&lt;/em&gt; 🐻❄️🧊&lt;/p&gt;
&lt;h2 id=&quot;java-spring-microservices-caching-distributedsystems-opensource&quot;&gt;Java #Spring #Microservices #Caching #DistributedSystems #OpenSource&lt;/h2&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/32489/polar-bear-cache-local-caching-without-repeated-remote-calls</guid>
<pubDate>Mon, 26 Oct 2026 10:07:01 +0000</pubDate>
</item>
<item>
<title>Sync Session: Rethinking Session Management in Microservices</title>
<link>https://coderlegion.com/32486/sync-session-rethinking-session-management-in-microservices</link>
<description>&lt;h2 id=&quot;sync-session-rethinking-session-management-in-microservices&quot;&gt;Sync Session: Rethinking Session Management in Microservices&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Project:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/babyblue94520/sync-session&quot;&gt;Sync Session on GitHub&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Shared storage provides the source of truth. Local caches provide performance. Events synchronize session state across service instances.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Since I started working with microservices in 2016, I have continuously explored and refined different approaches to session management.&lt;/p&gt;
&lt;p&gt;One question has remained particularly important to me:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How can every service independently verify a user's session without turning authentication into a bottleneck?&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;the-challenge-security-vs-performance&quot;&gt;The Challenge: Security vs. Performance&lt;/h3&gt;
&lt;p&gt;In a microservices architecture, services frequently communicate with one another. From a zero-trust perspective, a service should not blindly trust a &lt;code&gt;userId&lt;/code&gt; simply because another service included it in a request.&lt;/p&gt;
&lt;p&gt;Instead, each service should validate the session and enforce the appropriate authorization rules.&lt;/p&gt;
&lt;p&gt;However, querying a shared database every time a service needs to validate a session can increase latency and database load, especially when requests pass through multiple services.&lt;/p&gt;
&lt;p&gt;This creates a practical engineering challenge: how do we preserve session validity and revocation capabilities while reducing the cost of repeated remote access?&lt;/p&gt;
&lt;p&gt;That question led me to develop &lt;strong&gt;Sync Session&lt;/strong&gt;, an open-source session management library for Spring Boot.&lt;/p&gt;
&lt;h3 id=&quot;the-design&quot;&gt;The Design&lt;/h3&gt;
&lt;p&gt;The design combines four main components:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;RDBMS as shared storage:&lt;/strong&gt; The persistent source of truth for session state.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Local cache:&lt;/strong&gt; Each service instance maintains a local copy of session data to reduce repeated database queries.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Event service:&lt;/strong&gt; Session changes trigger events that allow other service instances to invalidate their corresponding local cache entries.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Session ID propagation:&lt;/strong&gt; The session ID can be passed through a cookie or HTTP header, depending on the application's request flow.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The session itself is intentionally kept small. Rather than embedding a large amount of user data and permissions into the session, the application uses a protected session ID to retrieve the associated authenticated identity.&lt;/p&gt;
&lt;p&gt;User information and authorization data remain under the responsibility of the relevant systems, where they can be updated independently.&lt;/p&gt;
&lt;p&gt;This separation helps avoid relying on outdated user information stored in a session and supports centralized session revocation.&lt;/p&gt;
&lt;h3 id=&quot;what-does-this-approach-support-&quot;&gt;What Does This Approach Support?&lt;/h3&gt;
&lt;p&gt;The project aims to address several practical requirements:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Distributed session management across multiple service instances.&lt;/li&gt;
&lt;li&gt;Sliding session expiration, allowing the expiration time to be extended according to the configured policy.&lt;/li&gt;
&lt;li&gt;Administrative session revocation, enabling an administrator to force a user to log out.&lt;/li&gt;
&lt;li&gt;Synchronization of session invalidation across service instances.&lt;/li&gt;
&lt;li&gt;Reduced database access through local caching.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The key idea is that services can validate sessions locally in the common case, while shared storage and event-driven invalidation help coordinate changes.&lt;/p&gt;
&lt;h3 id=&quot;performance-reference&quot;&gt;Performance Reference&lt;/h3&gt;
&lt;p&gt;In my MySQL load tests, I measured the following results under a workload involving 100 threads and 1 million sessions:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Operation &lt;/th&gt;&lt;th&gt;Measured throughput&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Session creation &lt;/td&gt;&lt;td&gt;~15,533 operations/sec&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Local cache access &lt;/td&gt;&lt;td&gt;~20.01 million operations/sec&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Direct database queries &lt;/td&gt;&lt;td&gt;~69,278 operations/sec&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;All 1 million session creation operations completed successfully in this test, with no reported failures.&lt;/p&gt;
&lt;p&gt;Under these particular test conditions, local cache access achieved approximately &lt;strong&gt;289 times the throughput of direct database queries&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;These numbers are reference measurements from my test environment, not universal performance guarantees. The results depend on hardware, database configuration, query patterns, cache implementation, concurrency, and the exact benchmark methodology.&lt;/p&gt;
&lt;p&gt;The comparison also measures different access paths, so it should not be interpreted as an end-to-end authentication benchmark.&lt;/p&gt;
&lt;h3 id=&quot;consistency-and-security-trade-offs&quot;&gt;Consistency and Security Trade-offs&lt;/h3&gt;
&lt;p&gt;Local caching introduces an important limitation: event-driven invalidation is asynchronous.&lt;/p&gt;
&lt;p&gt;When a session is revoked, another service instance may still have a cached copy until it processes the invalidation event. Event delivery failures or temporary network partitions can extend this window.&lt;/p&gt;
&lt;p&gt;For security-sensitive systems, this behavior needs to be considered explicitly. Depending on the threat model, additional safeguards may be necessary, such as bounded cache lifetimes, reliable event delivery, recovery mechanisms, or synchronous validation for particularly sensitive operations.&lt;/p&gt;
&lt;p&gt;Likewise, validating a session is not the same as authorizing every operation. Services must still enforce their own authorization requirements, and internal service communication must be authenticated appropriately.&lt;/p&gt;
&lt;p&gt;The goal is not to claim that local caching removes every security or consistency challenge. It is to explore a practical balance between independent session validation, efficient reads, and distributed session invalidation.&lt;/p&gt;
&lt;h3 id=&quot;a-design-principle-worth-discussing&quot;&gt;A Design Principle Worth Discussing&lt;/h3&gt;
&lt;p&gt;The principle behind Sync Session can be summarized as:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Shared storage for correctness. Local cache for performance. Events for synchronization.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This is not the only valid architecture, and it may not be appropriate for every workload. I am sharing it because session management is a recurring challenge in distributed systems, and I believe the trade-offs deserve discussion beyond the implementation of any single library.&lt;/p&gt;
&lt;p&gt;I'd especially like to hear from engineers who have built authentication infrastructure at scale:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;How do you handle session revocation across multiple service instances?&lt;/li&gt;
&lt;li&gt;Would you accept a short invalidation delay, or require immediate revocation for every request?&lt;/li&gt;
&lt;li&gt;How do you balance database load, cache consistency, and security requirements?&lt;/li&gt;
&lt;li&gt;What failure scenarios would you consider essential to address in this design?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I'm happy to discuss the implementation, benchmark methodology, and architectural trade-offs.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/32486/sync-session-rethinking-session-management-in-microservices</guid>
<pubDate>Mon, 26 Oct 2026 09:17:47 +0000</pubDate>
</item>
<item>
<title>LangGraph4j Annotations: Organize Graph Behavior with Action Catalogs</title>
<link>https://coderlegion.com/32159/langgraph4j-annotations-organize-graph-behavior-with-action-catalogs</link>
<description>&lt;h3 id=&quot;from-graph-lambdas-to-action-catalogs&quot;&gt;From graph lambdas to action catalogs&lt;/h3&gt;
&lt;p&gt;When building a workflow with &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/langgraph4j/langgraph4j&quot;&gt;LangGraph4j&lt;/a&gt;, I usually start with a few nodes and straightforward transitions. As the workflow grows, the graph definition can become crowded with action implementations, configuration access, and routing logic.&lt;/p&gt;
&lt;p&gt;Moving those actions into separate classes helps. The new annotations provide a convenient way to organize them: &lt;strong&gt;ordinary Java methods become node actions and edge conditions&lt;/strong&gt;, grouped into an &lt;strong&gt;Action Catalog&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Available from &lt;strong&gt;LangGraph4j 1.9.6&lt;/strong&gt;, &lt;code&gt;@NodeAction&lt;/code&gt; and &lt;code&gt;@EdgeCondition&lt;/code&gt; belong to &lt;code&gt;langgraph4j-core&lt;/code&gt;, in the &lt;code&gt;org.bsc.langgraph4j.annotation&lt;/code&gt; package. This guide follows the &lt;a rel=&quot;nofollow&quot; href=&quot;https://langgraph4j.github.io/langgraph4j/1.9/core/annotations/&quot;&gt;official annotations documentation&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;what-an-action-catalog-contains&quot;&gt;What an Action Catalog contains&lt;/h3&gt;
&lt;p&gt;An Action Catalog is a Java class implementing the &lt;code&gt;ActionCatalog&lt;/code&gt; marker interface. Its public annotated methods describe the available behavior:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;@NodeAction&lt;/code&gt; identifies work that produces updates to graph state.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@EdgeCondition&lt;/code&gt; identifies a routing decision, optionally accompanied by state updates.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;ActionCatalogRepository.of(catalog)&lt;/code&gt; discovers those methods, including inherited public methods, and adapts them into actions that LangGraph4j can execute.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The catalog owns behavior; the &lt;code&gt;StateGraph&lt;/code&gt; definition owns the connections.&lt;/strong&gt; Registering a catalog does not automatically create a workflow. We still choose which actions become nodes, where execution starts, and how each routing result maps to a destination.&lt;/p&gt;
&lt;p&gt;Both annotations support two optional attributes:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Attribute &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;&lt;code&gt;name&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Lookup name in the repository. An empty or blank value uses the Java method name.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;description&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Descriptive metadata; it does not control execution or routing.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;An explicit name is useful when the catalog should expose a stable action name even if its Java method is renamed. Give overloaded annotated methods distinct names. Keep names unique within the node actions and within the edge conditions; these are separate lookup maps.&lt;/p&gt;
&lt;h3 id=&quot;an-example&quot;&gt;An example&lt;/h3&gt;
&lt;p&gt;Let's build a small document workflow: prepare some text, decide whether review is requested, and optionally mark it as reviewed. We intentionally keep it simple so no LLM or external service is needed than we can focus on how the catalog connects to the graph.&lt;/p&gt;
&lt;h4 id=&quot;define-the-catalog&quot;&gt;Define the catalog&lt;/h4&gt;
&lt;p&gt;We create a new &lt;code&gt;DocumentCatalog&lt;/code&gt; class assuming it uses the standard &lt;code&gt;AgentState&lt;/code&gt;, so there is no custom state class to introduce first.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public final class DocumentCatalog implements ActionCatalog {
    // Trims the supplied text and records the executing graph node.
    @NodeAction(name = &quot;prepare&quot;, description = &quot;Prepare the document text&quot;)
    public Map&amp;lt;String, Object&amp;gt; prepareDocument(
            AgentState state, RunnableConfig config) {
        // Missing text is allowed in this example and becomes an empty document.
        String text = state.&amp;lt;String&amp;gt;value(&quot;text&quot;).orElse(&quot;&quot;);

        // Return updates for the engine to merge before it evaluates routing.
        return Map.of(&quot;text&quot;, text.strip(), &quot;preparedBy&quot;, config.nodeId());
    }

    // Selects a review route and records the decision without mutating state.
    @EdgeCondition(description = &quot;Review only when requested&quot;)
    public Command route(AgentState state) {
        // Omitted needsReview means this example can finish after preparation.
        boolean needsReview = state.&amp;lt;Boolean&amp;gt;value(&quot;needsReview&quot;).orElse(false);
        String route = needsReview ? &quot;reviewRequired&quot; : &quot;complete&quot;;

        // These are routing keys; the graph maps them to node IDs or END.
        return new Command(route, Map.of(&quot;reviewDecision&quot;, route));
    }

    // Marks the document as reviewed; this example performs no human interaction.
    @NodeAction
    public Map&amp;lt;String, Object&amp;gt; review(AgentState state) {
        return Map.of(&quot;reviewed&quot;, true);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There are some details to notice here.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;@NodeAction&lt;/code&gt; annotated methods returns a &lt;strong&gt;partial state update&lt;/strong&gt; as required by LangGraph4j node actions classic.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;@EdgeCondition&lt;/code&gt; annotated methods return a &lt;code&gt;Command&lt;/code&gt; as required by  LangGraph4j classic edge conditions. &lt;/li&gt;
&lt;li&gt;The method &lt;code&gt;prepareDocument&lt;/code&gt; is registered as &lt;strong&gt;&lt;code&gt;prepare&lt;/code&gt;&lt;/strong&gt; because it has an explicit annotation name. The other methods use their Java names: &lt;code&gt;route&lt;/code&gt; and &lt;code&gt;review&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;route&lt;/code&gt; returns a &lt;code&gt;Command&lt;/code&gt; that lets the condition record &lt;code&gt;reviewDecision&lt;/code&gt; as well as select the next route.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;review&lt;/code&gt; action is deliberately a local marker. A real Human-in-the-Loop review would also need the appropriate interruption, persistence, and resume behavior.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;resolve-the-catalog-and-connect-the-graph&quot;&gt;Resolve the catalog and connect the graph&lt;/h4&gt;
&lt;p&gt;Let's build the &lt;code&gt;DocumentWorkflow&lt;/code&gt; class alongside the catalog:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public final class DocumentWorkflow {
    // Builds the workflow and prints the final state for an input requiring review.
     public static void main(String[] args) throws Exception {
        // Register before resolving names; actions execute on this catalog instance.
        final var repository = ActionCatalogRepository.of(new DocumentCatalog());

        // Define the topology explicitly, mapping each possible routing result.
        final var graph = new StateGraph&amp;lt;&amp;gt;(AgentState::new)
                .add(repository.node(&quot;prepare&quot;))
                .add(repository.node(&quot;review&quot;))
                .addEdge(START, &quot;prepare&quot;)
                .add(repository.edge(&quot;prepare&quot;, &quot;route&quot;,
                        EdgeMappings.builder()
                                .to( &quot;review&quot;, &quot;reviewRequired&quot; )
                                .toEND( &quot;complete&quot; )
                                .build()))
                .addEdge(&quot;review&quot;, END)
                .compile(); // Resolve action factories before executing the graph.

        // A fresh invocation requests review and supplies the text to prepare.
        graph.stream(
                    GraphInput.args(Map.of(
                            &quot;text&quot;, &quot;  A document to review.  &quot;,
                            &quot;needsReview&quot;, true)),
                    RunnableConfig.empty())
            .reduce( (a,b) -&amp;gt; b )
            .thenAccept( System.out::println );

    }

}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There are some details to notice here.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;repository.node(&quot;prepare&quot;)&lt;/code&gt; and &lt;code&gt;repository.node(&quot;review&quot;)&lt;/code&gt; supplies a complete graph node whose ID and action lookup name are the same. &lt;/li&gt;
&lt;li&gt;Similarly, &lt;code&gt;repository.edge(...)&lt;/code&gt; supplies a complete conditional edge. Both work with the &lt;code&gt;StateGraph.add(...)&lt;/code&gt; methods.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This gives us a small example to make in evidence how graph definition change using annotations.&lt;/p&gt;
&lt;h3 id=&quot;reuse-an-action-under-another-node-id&quot;&gt;Reuse an action under another node ID&lt;/h3&gt;
&lt;p&gt;Sometimes, a graph reuses an action while assigning a specific name to the role it plays within a particular workflow. The &lt;code&gt;repository.nodeAction(...)&lt;/code&gt; method is provided specifically for this purpose; it retrieves the action factory without assigning a node ID.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// Alternative preparation node: graph identity differs from the catalog lookup name.
var workflow = new StateGraph&amp;lt;&amp;gt;(AgentState::new)
        .addNode(&quot;prepareIncomingDocument&quot;, repository.nodeAction(&quot;prepare&quot;))
        .addEdge(START, &quot;prepareIncomingDocument&quot;)
        .addEdge(&quot;prepareIncomingDocument&quot;, END);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This fragment replaces the earlier definition and uses the same repository. After compilation, &lt;code&gt;prepareIncomingDocument&lt;/code&gt; node would contain &lt;code&gt;prepare&lt;/code&gt; action bound with &lt;code&gt;prepareDocument&lt;/code&gt; method in action catalog.&lt;/p&gt;
&lt;p&gt;That distinction is useful when an action appears in several workflows, or more than once within a graph. Catalog names identify reusable behavior; graph node IDs identify its position in a particular workflow.&lt;/p&gt;
&lt;p&gt;The &lt;a rel=&quot;nofollow&quot; href=&quot;https://langgraph4j.github.io/langgraph4j/1.9/core/annotations/&quot;&gt;official guide&lt;/a&gt; also discusses a singular &lt;code&gt;edgeAction(...)&lt;/code&gt; accessor with details on its usage&lt;/p&gt;
&lt;h3 id=&quot;other-supported-method-shapes&quot;&gt;Other supported method shapes&lt;/h3&gt;
&lt;p&gt;The example uses synchronous methods, but catalogs also support asynchronous work and node construction during compilation:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Method role &lt;/th&gt;&lt;th&gt;Supported return types&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Runtime node action &lt;/td&gt;&lt;td&gt;&lt;code&gt;Map&amp;lt;String, Object&amp;gt;&lt;/code&gt; or &lt;code&gt;CompletableFuture&amp;lt;Map&amp;lt;String, Object&amp;gt;&amp;gt;&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Runtime edge condition &lt;/td&gt;&lt;td&gt;&lt;code&gt;String&lt;/code&gt;, &lt;code&gt;Command&lt;/code&gt;, or a &lt;code&gt;CompletableFuture&lt;/code&gt; of either&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Compile-time node method &lt;/td&gt;&lt;td&gt;&lt;code&gt;NodeActionWithConfig&amp;lt;State&amp;gt;&lt;/code&gt; or &lt;code&gt;AsyncNodeActionWithConfig&amp;lt;State&amp;gt;&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Runtime methods can receive your &lt;code&gt;AgentState&lt;/code&gt; subclass, &lt;code&gt;RunnableConfig&lt;/code&gt;, both in either order, or neither. Include only what the method needs. A returned future must complete before its updates or routing result can be applied; returning a future does not itself schedule background work.&lt;/p&gt;
&lt;p&gt;A &lt;strong&gt;compile-time&lt;/strong&gt; node method instead takes &lt;strong&gt;one &lt;code&gt;CompileConfig&lt;/code&gt; argument&lt;/strong&gt;. It runs when the graph creates its action during compilation and returns the action to execute later. This is useful when action construction depends on compilation settings. Edge conditions do not support &lt;strong&gt;compile-time&lt;/strong&gt; configuration. &lt;/p&gt;
&lt;p&gt;See the &lt;a rel=&quot;nofollow&quot; href=&quot;https://langgraph4j.github.io/langgraph4j/1.9/core/annotations/#node-action-signatures&quot;&gt;signature examples&lt;/a&gt; for the complete forms.&lt;/p&gt;
&lt;h3 id=&quot;organize-catalogs-around-related-behavior&quot;&gt;Organize catalogs around related behavior&lt;/h3&gt;
&lt;p&gt;For a small workflow, a single catalog keeps related actions and conditions together. As the application grows, I would split catalogs by responsibility: document preparation in one, review policy in another, and delivery in a third. Each can have its own repository, with their actions connected in the same explicit graph definition.&lt;/p&gt;
&lt;p&gt;The repository uses the &lt;strong&gt;catalog instance you supply&lt;/strong&gt;. That means constructor injection works naturally: create the catalog with its service clients, repositories, or model dependencies before registering it. The annotations do not require a dependency injection framework.&lt;/p&gt;
&lt;p&gt;Keep per-run values in graph state or execution configuration. A catalog shared across concurrent runs also shares its fields and dependencies, so mutable collaborators need suitable concurrency handling. The stateless catalog in this example avoids that concern.&lt;/p&gt;
&lt;p&gt;Catalog methods can be called directly in ordinary Java tests.&lt;/p&gt;
&lt;h3 id=&quot;common-mistakes-to-catch-early&quot;&gt;Common mistakes to catch early&lt;/h3&gt;
&lt;p&gt;Use &lt;strong&gt;public annotated methods&lt;/strong&gt;, with one graph annotation per method. Repository discovery will not pick up private methods. Unsupported runtime parameter types are rejected during repository creation, while unsupported results fail when the action executes; compile-time node results are checked during compilation.&lt;/p&gt;
&lt;p&gt;Looking up an unknown action through &lt;code&gt;node&lt;/code&gt;, &lt;code&gt;nodeAction&lt;/code&gt;, or &lt;code&gt;edge&lt;/code&gt; throws &lt;code&gt;IllegalArgumentException&lt;/code&gt;. Explicit duplicate names are also rejected; make method-derived names unique rather than relying on overload discovery order.&lt;/p&gt;
&lt;p&gt;Exceptions thrown by runtime methods, and exceptional future completions, propagate through graph execution. Annotations provide adaptation, so application error handling still belongs in the actions or the surrounding execution code. The &lt;a rel=&quot;nofollow&quot; href=&quot;https://langgraph4j.github.io/langgraph4j/1.9/core/annotations/#validation-and-failures&quot;&gt;validation reference&lt;/a&gt; covers these failure cases.&lt;/p&gt;
&lt;p&gt;For an existing graph, a practical migration is to move a related group of actions into a catalog, register it, and replace their inline implementations with repository lookups. The edges and routing mappings can remain explicit throughout that refactoring.&lt;/p&gt;
&lt;h3 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;The approach of organizing actions into catalogs helps maintain a clear structure as your workflow grows. It encourages modularity, reusability, and easier testing, while keeping the graph definitions explicit and manageable. &lt;br&gt;
In the future action catalogs could feed advanced workflow designer tools, enabling more declarative automation and orchestration capabilities.&lt;br&gt;
I'm interested in how this approach fits larger workflows. Try it grouping a set of related actions in &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/langgraph4j/langgraph4j&quot;&gt;LangGraph4j&lt;/a&gt;, share your feedback, and... happy AI coding! 👋&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/32159/langgraph4j-annotations-organize-graph-behavior-with-action-catalogs</guid>
<pubDate>Fri, 23 Oct 2026 08:50:30 +0000</pubDate>
</item>
<item>
<title>Why Does a Backend API Become Slow, and How Can We Fix It?</title>
<link>https://coderlegion.com/31915/why-does-a-backend-api-become-slow-and-how-can-we-fix-it</link>
<description>&lt;p&gt;As a backend developer, I spend a lot of time thinking about what happens behind the scenes when someone uses an application. A user clicks a button, submits a form, or requests some information, and the backend has to process that request and return a response.&lt;/p&gt;
&lt;p&gt;Everything looks simple when an application is small. But as the number of users and the amount of data increase, things can change quickly. An API that used to respond almost instantly may suddenly take several seconds to return the same information.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;So, what actually makes a backend API slow, and how can we fix it?&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;the-problem-an-api-that-takes-too-long-to-respond&quot;&gt;The Problem: An API That Takes Too Long to Respond&lt;/h3&gt;
&lt;p&gt;Imagine you're building an e-commerce application using Java and Spring Boot. The application has an API that returns a list of products.&lt;/p&gt;
&lt;p&gt;At first, the API works perfectly. It fetches product information from the database and returns the result to the frontend.&lt;/p&gt;
&lt;p&gt;But after the application grows, users begin to complain that the product page takes too long to load.&lt;/p&gt;
&lt;p&gt;There could be several reasons behind this problem:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The database is executing inefficient queries.&lt;/li&gt;
&lt;li&gt;The API is retrieving more data than the frontend needs.&lt;/li&gt;
&lt;li&gt;The application is making unnecessary database calls.&lt;/li&gt;
&lt;li&gt;Multiple operations are being performed when they are not required.&lt;/li&gt;
&lt;li&gt;The server is struggling to handle too many requests at once.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The first mistake would be to start changing the code without understanding the actual cause.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Before optimizing an API, we need to identify where the time is being spent.&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;step-1-measure-the-api-performance&quot;&gt;Step 1: Measure the API Performance&lt;/h3&gt;
&lt;p&gt;The first thing I would do is measure the response time.&lt;/p&gt;
&lt;p&gt;Instead of guessing, I would check application logs, database query execution times, and performance metrics to understand whether the delay comes from the database, application logic, or another part of the system.&lt;/p&gt;
&lt;p&gt;For example, if an API takes 1,500 milliseconds to respond, I would investigate how much time is spent executing the database query and how much is spent processing the result.&lt;/p&gt;
&lt;p&gt;This gives us a starting point.&lt;/p&gt;
&lt;p&gt;In a Spring Boot application, tools such as Spring Boot Actuator and Micrometer can help expose useful application metrics. Database query logging and profiling can also help identify expensive operations.&lt;/p&gt;
&lt;p&gt;The goal is simple: find the bottleneck before trying to fix it.&lt;/p&gt;
&lt;h3 id=&quot;step-2-improve-database-queries&quot;&gt;Step 2: Improve Database Queries&lt;/h3&gt;
&lt;p&gt;One common problem is retrieving unnecessary data.&lt;/p&gt;
&lt;p&gt;Suppose the frontend only needs a product's name, price, and image URL. There is no reason to fetch every column from the database if the application doesn't need them.&lt;/p&gt;
&lt;p&gt;A basic SQL query might look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;SELECT *
FROM products;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A more focused query would be:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;SELECT name, price, image_url
FROM products;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This can reduce the amount of data transferred and processed, particularly when the table contains many columns or large fields.&lt;/p&gt;
&lt;p&gt;Indexes can also improve performance when queries frequently filter or search by specific columns. However, indexes should be chosen based on actual query patterns because unnecessary indexes increase storage use and can slow down writes.&lt;/p&gt;
&lt;p&gt;Small database improvements can make a noticeable difference when an API is called frequently.&lt;/p&gt;
&lt;h3 id=&quot;step-3-avoid-unnecessary-database-calls&quot;&gt;Step 3: Avoid Unnecessary Database Calls&lt;/h3&gt;
&lt;p&gt;Another issue I would investigate is whether the application is making too many database queries.&lt;/p&gt;
&lt;p&gt;For example, imagine an API returns 100 products. If the application executes one query to fetch the products and then another query for each product's category, it could make 101 queries instead of a smaller number of well-designed queries.&lt;/p&gt;
&lt;p&gt;This is a common example of the N+1 query problem.&lt;/p&gt;
&lt;p&gt;With Java and Spring Data JPA, the solution might involve a carefully designed fetch strategy, a projection, or a query that retrieves the required information together.&lt;/p&gt;
&lt;p&gt;The right approach depends on the relationships between the entities and the data the endpoint actually needs.&lt;/p&gt;
&lt;p&gt;I would also avoid blindly using eager loading everywhere, because loading too much related data can create a different performance problem.&lt;/p&gt;
&lt;h3 id=&quot;step-4-return-only-the-data-the-client-needs&quot;&gt;Step 4: Return Only the Data the Client Needs&lt;/h3&gt;
&lt;p&gt;A backend API should have a clear responsibility: provide the information the client needs to complete a task.&lt;/p&gt;
&lt;p&gt;Returning thousands of records in a single response is rarely a good choice when the user only needs to view a small portion of them.&lt;/p&gt;
&lt;p&gt;Pagination can help.&lt;/p&gt;
&lt;p&gt;For example, a Spring Data repository can support paginated queries:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;Page&amp;lt;Product&amp;gt; findAll(Pageable pageable);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A service or controller can then request a specific page rather than loading every product at once.&lt;/p&gt;
&lt;p&gt;For larger datasets, cursor-based pagination may be more suitable than offset-based pagination, depending on the use case.&lt;/p&gt;
&lt;p&gt;The important thing is to make sure the API remains efficient as the dataset grows.&lt;/p&gt;
&lt;h3 id=&quot;step-5-use-caching-when-it-makes-sense&quot;&gt;Step 5: Use Caching When It Makes Sense&lt;/h3&gt;
&lt;p&gt;Sometimes the same information is requested repeatedly even though it rarely changes.&lt;/p&gt;
&lt;p&gt;Product categories, public configuration, and other relatively stable data may be good candidates for caching.&lt;/p&gt;
&lt;p&gt;Spring provides caching support through annotations such as &lt;code&gt;@Cacheable&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Cacheable(&quot;productCategories&quot;)
public List&amp;lt;Category&amp;gt; getCategories() {
    return categoryRepository.findAll();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After the first successful lookup, subsequent calls can reuse the cached result, depending on the configured cache provider and cache rules.&lt;/p&gt;
&lt;p&gt;However, caching is not a solution for every slow API. We need to consider expiration, invalidation, memory usage, and whether users might receive outdated information.&lt;/p&gt;
&lt;p&gt;For frequently changing or sensitive data, caching requires additional care.&lt;/p&gt;
&lt;h3 id=&quot;step-6-test-the-changes&quot;&gt;Step 6: Test the Changes&lt;/h3&gt;
&lt;p&gt;After making improvements, I would test the API again using the same conditions as before.&lt;/p&gt;
&lt;p&gt;I would compare:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Average response time.&lt;/li&gt;
&lt;li&gt;Higher-percentile latency, such as p95.&lt;/li&gt;
&lt;li&gt;Database query count and execution time.&lt;/li&gt;
&lt;li&gt;Error rates.&lt;/li&gt;
&lt;li&gt;Resource usage under concurrent requests.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A change that makes one request faster but causes errors under heavy traffic is not a successful optimization.&lt;/p&gt;
&lt;p&gt;Performance testing tools can help simulate multiple users and reveal problems that may not appear during ordinary development.&lt;/p&gt;
&lt;p&gt;I would also keep automated tests in place to ensure that performance improvements don't accidentally change the API's expected behavior.&lt;/p&gt;
&lt;h3 id=&quot;what-i-learned-from-this-approach&quot;&gt;What I Learned From This Approach&lt;/h3&gt;
&lt;p&gt;The biggest lesson is that backend performance is not just about writing shorter code or choosing a faster programming language.&lt;/p&gt;
&lt;p&gt;A well-designed API depends on several things working together: efficient database queries, sensible data retrieval, appropriate caching, reliable application logic, and good monitoring.&lt;/p&gt;
&lt;p&gt;Java and Spring Boot provide useful tools for building APIs, but the framework cannot automatically make every design decision efficient.&lt;/p&gt;
&lt;p&gt;As developers, we still need to understand what the application is doing and why a particular operation is taking too long.&lt;/p&gt;
&lt;p&gt;I would rather spend time identifying the actual bottleneck than introduce unnecessary complexity in the name of optimization.&lt;/p&gt;
&lt;h3 id=&quot;final-thoughts-1&quot;&gt;Final Thoughts&lt;/h3&gt;
&lt;p&gt;Building a backend API that works is one challenge. Keeping it reliable and responsive as the application grows is another.&lt;/p&gt;
&lt;p&gt;When an API becomes slow, I think the best approach is to measure first, identify the problem, make a focused change, and test the result.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Good backend development is not only about making an application work today. It's about making sure it continues to work well as more users depend on it.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I'm interested in how other Java developers approach API performance. When you notice a slow endpoint in your application, what's the first thing you investigate: database queries, application logic, or something else?&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/31915/why-does-a-backend-api-become-slow-and-how-can-we-fix-it</guid>
<pubDate>Wed, 21 Oct 2026 03:36:54 +0000</pubDate>
</item>
<item>
<title>The Order Was Saved. The Kafka Event Might Never Arrive.</title>
<link>https://coderlegion.com/30973/the-order-was-saved-the-kafka-event-might-never-arrive</link>
<description>&lt;p&gt;A customer clicks &lt;strong&gt;Place order&lt;/strong&gt;. Your API returns success. The order row is in the database. But the service that processes orders never sees an event.&lt;/p&gt;
&lt;p&gt;This is not a hypothetical edge case invented by Kafka. It is a failure window in a common Spring Boot pattern: save to a database, then publish to a broker as a second operation. I found the pattern while reviewing my own event-driven order processing capstone. &lt;strong&gt;This is a code review of a learning project, not a claim that a production outage occurred.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The lesson applies well beyond orders. If your API writes data and then emits an event for fraud checks, notifications, inventory, or analytics, a successful HTTP response can tell less of the story than you think.&lt;/p&gt;
&lt;h3 id=&quot;the-four-lines-that-made-me-stop&quot;&gt;The four lines that made me stop&lt;/h3&gt;
&lt;p&gt;My order service saves a new order, then calls a producer:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;CustomerOrder order = orderRepository.save(new CustomerOrder(...));
producer.publish(new OrderEvent(order.getId(), ...));
return toResponse(order);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The producer uses Spring Kafka like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public void publish(OrderEvent event) {
    kafkaTemplate.send(&quot;order-events&quot;, String.valueOf(event.orderId()), event);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That send returns a future. In this implementation, the future is ignored. The method can move on to the HTTP response without observing whether Kafka acknowledged the record. More fundamentally, the database write and Kafka publish are two separate operations. A process crash can occur between them even if you wait for the send result.&lt;/p&gt;
&lt;p&gt;The code is perfectly useful for demonstrating an event-driven flow. It is not enough by itself to promise that every saved order will produce a durable event.&lt;/p&gt;
&lt;h3 id=&quot;where-the-gap-opens&quot;&gt;Where the gap opens&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=14888194715637270969&quot; alt=&quot;Diagram: database commit, asynchronous Kafka send, and the failure window before event confirmation&quot;&gt;&lt;/p&gt;
&lt;p&gt;There are at least two ways to lose the intended handoff:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Crash after the database commit, before the send.&lt;/strong&gt; The order exists, but no event was requested.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Asynchronous send failure after the method proceeds.&lt;/strong&gt; The producer future completes with an error, but this code does not inspect it.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The reverse uncertainty also matters: Kafka may accept an event while the client never receives the HTTP response. A retry from the client can create another order unless the API has a separate request-idempotency rule.&lt;/p&gt;
&lt;p&gt;I already had retries and a dead-letter topic on the &lt;strong&gt;consumer&lt;/strong&gt; side of the capstone. Those help when an event reaches Kafka and processing fails. They cannot recover an event that was never published. This distinction is easy to miss when a demo looks healthy end to end.&lt;/p&gt;
&lt;h3 id=&quot;first-choose-what-a-successful-response-means&quot;&gt;First, choose what a successful response means&lt;/h3&gt;
&lt;p&gt;An API contract should say what has actually happened. For an asynchronous workflow, a successful create response may mean &lt;strong&gt;the order and the intent to process it have been stored&lt;/strong&gt;. It should not imply that downstream processing is complete.&lt;/p&gt;
&lt;p&gt;A practical state model might be:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;CREATED → PROCESSING → APPROVED or REJECTED
                       ↘ NEEDS_ATTENTION
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A GET endpoint lets the caller check the current state. An order stuck in CREATED for too long should be visible to monitoring and operators, not quietly forgotten.&lt;/p&gt;
&lt;h3 id=&quot;the-transactional-outbox-fix&quot;&gt;The transactional outbox fix&lt;/h3&gt;
&lt;p&gt;A transactional outbox makes the event intent part of the same database transaction as the order. The API writes two rows: the business record and an outbox record containing an event ID, aggregate ID, event type, and payload. Either both rows commit or neither does.&lt;/p&gt;
&lt;p&gt;A separate relay publishes committed outbox rows to Kafka. That relay can poll the table or use change-data capture. Debezium's outbox event router is one established implementation of the latter approach.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=7037414946828488191&quot; alt=&quot;Diagram: order and outbox rows committed together, then relayed to Kafka with downstream deduplication&quot;&gt;&lt;/p&gt;
&lt;p&gt;The core application code becomes conceptually:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@Transactional
public OrderResponse createOrder(CreateOrderRequest request) {
    CustomerOrder order = orderRepository.save(toEntity(request));
    outboxRepository.save(OutboxEvent.orderCreated(order));
    return toResponse(order);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is &lt;strong&gt;illustrative replacement code&lt;/strong&gt;, not a feature already implemented in my capstone. The important part is the transaction boundary around both inserts. The relay is responsible for eventual delivery after commit.&lt;/p&gt;
&lt;p&gt;The outbox does not turn the whole system into exactly-once delivery. A relay may publish an event and crash before recording that it published it. It will publish again after restart. Consumers therefore need to handle duplicates, usually by recording a stable event ID and making the business effect idempotent.&lt;/p&gt;
&lt;h3 id=&quot;two-ids-solve-two-different-retry-problems&quot;&gt;Two IDs solve two different retry problems&lt;/h3&gt;
&lt;p&gt;It helps to keep these IDs distinct:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Request ID:&lt;/strong&gt; supplied or assigned at the API edge so a repeated Place order request does not create a second order.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Event ID:&lt;/strong&gt; stored with the outbox record so repeated event delivery does not apply the same downstream action twice.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Kafka producer idempotence is useful for producer retries, but it does not make a database transaction and a broker publish atomic. Nor does it replace consumer-side deduplication across relay restarts. Each layer protects a different boundary.&lt;/p&gt;
&lt;h3 id=&quot;the-test-i-would-add-before-calling-this-production-ready&quot;&gt;The test I would add before calling this production-ready&lt;/h3&gt;
&lt;p&gt;I would make failure injection part of the acceptance test:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create an order while the broker is unavailable.&lt;/li&gt;
&lt;li&gt;Confirm that the order and its outbox event are committed together.&lt;/li&gt;
&lt;li&gt;Restore the broker and confirm that the relay publishes the event.&lt;/li&gt;
&lt;li&gt;Force the relay to retry, then confirm the consumer applies the event only once.&lt;/li&gt;
&lt;li&gt;Check that the order eventually leaves CREATED, or raises an alert if it does not.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In the current direct-send design, a broker failure or crash can leave an order without a durable event intent. The test should make that gap visible. In the outbox design, the same test checks the recovery path rather than just the happy path.&lt;/p&gt;
&lt;h3 id=&quot;what-i-would-monitor&quot;&gt;What I would monitor&lt;/h3&gt;
&lt;p&gt;A reliable event-driven API needs signals beyond HTTP success rate:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Age of the oldest unpublished outbox row&lt;/li&gt;
&lt;li&gt;Number of publish failures and relay retries&lt;/li&gt;
&lt;li&gt;Consumer lag and dead-letter count&lt;/li&gt;
&lt;li&gt;Duplicate event count&lt;/li&gt;
&lt;li&gt;Orders stuck in CREATED or PROCESSING&lt;/li&gt;
&lt;li&gt;Time from order creation to final state&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These metrics turn “the dashboard never updated” into an observable state with an owner and a recovery path.&lt;/p&gt;
&lt;h3 id=&quot;the-takeaway&quot;&gt;The takeaway&lt;/h3&gt;
&lt;p&gt;My capstone taught me a useful production lesson: &lt;strong&gt;an event-driven workflow is only as reliable as the boundary between its database and its broker.&lt;/strong&gt; Consumer retries are valuable, but they start too late to repair a missing publish. Save the business record and the event intent together, deliver at least once, deduplicate downstream, and monitor the gap between creation and completion.&lt;/p&gt;
&lt;p&gt;If you have built a database-to-Kafka flow, where do you persist the intent to publish? I would be interested to hear how your team tests that failure window.&lt;/p&gt;
&lt;h4 id=&quot;references&quot;&gt;References&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.spring.io/spring-kafka/reference/kafka/sending-messages.html&quot;&gt;Spring Kafka: Sending Messages&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://debezium.io/documentation/reference/stable/transformations/outbox-event-router.html&quot;&gt;Debezium: Outbox Event Router&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://kafka.apache.org/documentation/#semantics&quot;&gt;Apache Kafka: Design and Delivery Semantics&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/30973/the-order-was-saved-the-kafka-event-might-never-arrive</guid>
<pubDate>Tue, 13 Oct 2026 17:20:19 +0000</pubDate>
</item>
<item>
<title>Most Developers Are Building AI Agents Wrong: MCP Is the Missing Contract</title>
<link>https://coderlegion.com/30689/most-developers-are-building-ai-agents-wrong-mcp-is-the-missing-contract</link>
<description>&lt;p&gt;AI agents are easy to demo and difficult to trust.&lt;/p&gt;
&lt;p&gt;A developer can connect a language model to a few tools in an afternoon. The first demo looks impressive: the agent reads a request, calls an API, checks a database, and returns an answer.&lt;/p&gt;
&lt;p&gt;Then production arrives.&lt;/p&gt;
&lt;p&gt;The agent calls the wrong tool. It sends an incomplete payload. It retries a payment operation. It exposes information from another tenant. Nobody can explain why it made a decision.&lt;/p&gt;
&lt;p&gt;The problem is not always the language model. The deeper problem is that many teams are building agents without a clear contract between the model, the tools, the data, and the people responsible for the outcome.&lt;/p&gt;
&lt;p&gt;That is where the Model Context Protocol, or MCP, becomes important.&lt;/p&gt;
&lt;h3 id=&quot;an-ai-agent-is-more-than-a-chatbot&quot;&gt;An AI agent is more than a chatbot&lt;/h3&gt;
&lt;p&gt;A useful enterprise agent usually has five parts:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A model that reasons about the request&lt;/li&gt;
&lt;li&gt;Instructions that define its role and limits&lt;/li&gt;
&lt;li&gt;Tools that allow it to read or change systems&lt;/li&gt;
&lt;li&gt;Memory or context that helps it understand the task&lt;/li&gt;
&lt;li&gt;Guardrails that control what it is allowed to do&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;A chatbot mainly generates text. An agent can take actions.&lt;/p&gt;
&lt;p&gt;That difference changes the engineering problem completely. If an agent only produces an explanation, an inaccurate response may be corrected by a human. If an agent can approve a refund, change a production configuration, or submit a healthcare request, an inaccurate response becomes an operational incident.&lt;/p&gt;
&lt;p&gt;The agent must therefore be treated like a distributed system with an unreliable reasoning component.&lt;/p&gt;
&lt;h3 id=&quot;the-tool-boundary-is-the-real-architecture&quot;&gt;The tool boundary is the real architecture&lt;/h3&gt;
&lt;p&gt;Many early agent implementations expose tools with a name, description, and input schema. That is not enough for production.&lt;/p&gt;
&lt;p&gt;A safe tool contract must answer several questions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Which user or tenant is the agent allowed to access?&lt;/li&gt;
&lt;li&gt;Which values are valid?&lt;/li&gt;
&lt;li&gt;Does the operation require approval?&lt;/li&gt;
&lt;li&gt;Is the operation idempotent?&lt;/li&gt;
&lt;li&gt;What happens if the network times out after success?&lt;/li&gt;
&lt;li&gt;How is the action audited?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A tool description is not a security policy. It is only the beginning of a contract.&lt;/p&gt;
&lt;h3 id=&quot;why-mcp-matters&quot;&gt;Why MCP matters&lt;/h3&gt;
&lt;p&gt;MCP provides a standard way for AI applications to discover and use external tools and resources. Instead of building a different integration for every model and every application, teams can expose capabilities through a consistent protocol.&lt;/p&gt;
&lt;p&gt;That standardization is valuable, but it does not automatically make an integration safe.&lt;/p&gt;
&lt;p&gt;MCP should be treated as an interoperability layer, not as permission to give an agent unrestricted access to a system.&lt;/p&gt;
&lt;p&gt;A production MCP tool should define:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A clear business purpose&lt;/li&gt;
&lt;li&gt;Read or write classification&lt;/li&gt;
&lt;li&gt;Authentication and authorization requirements&lt;/li&gt;
&lt;li&gt;Tenant and data-scope restrictions&lt;/li&gt;
&lt;li&gt;Input validation&lt;/li&gt;
&lt;li&gt;Idempotency behavior&lt;/li&gt;
&lt;li&gt;Timeout and retry rules&lt;/li&gt;
&lt;li&gt;Rate limits&lt;/li&gt;
&lt;li&gt;Audit events&lt;/li&gt;
&lt;li&gt;Human approval requirements&lt;/li&gt;
&lt;li&gt;A safe failure response&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The most important design principle is simple:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;The model may request an action, but the platform must decide whether that action is allowed.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;a-safer-agent-flow&quot;&gt;A safer agent flow&lt;/h3&gt;
&lt;p&gt;A reliable enterprise agent should follow a controlled sequence:&lt;/p&gt;
&lt;p&gt;User request -&amp;gt; identity verification -&amp;gt; permitted context -&amp;gt; model plan -&amp;gt; policy evaluation -&amp;gt; human approval when required -&amp;gt; MCP tool execution -&amp;gt; result validation -&amp;gt; audit event -&amp;gt; user response&lt;/p&gt;
&lt;p&gt;The model should not directly own the complete workflow. It should operate inside one.&lt;/p&gt;
&lt;p&gt;For example, an agent may propose: Cancel this payment.&lt;/p&gt;
&lt;p&gt;The policy layer should check whether the user is authenticated, whether the payment belongs to the user organization, whether it is still cancellable, whether the amount exceeds an approval threshold, whether the request was already processed, and whether the downstream tool is healthy.&lt;/p&gt;
&lt;p&gt;Only after these checks should the action reach the payment service.&lt;/p&gt;
&lt;h3 id=&quot;mcp-and-spring-boot&quot;&gt;MCP and Spring Boot&lt;/h3&gt;
&lt;p&gt;A practical Java implementation can place an agent gateway in front of domain services.&lt;/p&gt;
&lt;p&gt;The gateway can provide:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Spring Security authentication&lt;/li&gt;
&lt;li&gt;OAuth2 or JWT validation&lt;/li&gt;
&lt;li&gt;Tool-level authorization&lt;/li&gt;
&lt;li&gt;Request and response schemas&lt;/li&gt;
&lt;li&gt;Correlation IDs&lt;/li&gt;
&lt;li&gt;Rate limiting&lt;/li&gt;
&lt;li&gt;OpenTelemetry traces&lt;/li&gt;
&lt;li&gt;Approval workflow integration&lt;/li&gt;
&lt;li&gt;Central audit logging&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The domain service should still enforce its own business rules. Never assume that because a request came from an AI gateway, it is trustworthy.&lt;/p&gt;
&lt;p&gt;A safe execution model is: validate the user, evaluate policy, validate the tool input, enforce idempotency, call the domain service, validate the result, and record an audit event.&lt;/p&gt;
&lt;p&gt;The model can select a tool. It should not bypass the same controls that apply to a human-operated API.&lt;/p&gt;
&lt;h3 id=&quot;read-tools-and-write-tools-are-not-equal&quot;&gt;Read tools and write tools are not equal&lt;/h3&gt;
&lt;p&gt;Read-only tools may retrieve an invoice, search a knowledge base, or check an order status. Write tools may create a payment, update a customer, delete a document, or deploy software.&lt;/p&gt;
&lt;p&gt;These categories should have different controls. A read tool may require normal user authorization. A high-impact write tool may require explicit confirmation, a second approver, a transaction limit, a dry-run mode, or a compensating action.&lt;/p&gt;
&lt;p&gt;Do not hide a write operation behind a friendly name such as manage account. Use names that reveal the action and its risk: create refund, disable user, or deploy release.&lt;/p&gt;
&lt;p&gt;Clear names help models, developers, reviewers, and security teams understand what is happening.&lt;/p&gt;
&lt;h3 id=&quot;the-biggest-mcp-security-risks&quot;&gt;The biggest MCP security risks&lt;/h3&gt;
&lt;p&gt;MCP-based systems introduce familiar security risks in a new form.&lt;/p&gt;
&lt;h4 id=&quot;excessive-permissions&quot;&gt;Excessive permissions&lt;/h4&gt;
&lt;p&gt;An agent receives access to more data and tools than it needs. If the model or server is compromised, the blast radius becomes large.&lt;/p&gt;
&lt;h4 id=&quot;prompt-injection&quot;&gt;Prompt injection&lt;/h4&gt;
&lt;p&gt;Untrusted content inside a document, webpage, or ticket attempts to influence the agent instructions. Retrieved content must be treated as data, not as a higher-priority instruction.&lt;/p&gt;
&lt;h4 id=&quot;confused-deputy-problems&quot;&gt;Confused deputy problems&lt;/h4&gt;
&lt;p&gt;The agent uses its own powerful credentials to perform an action on behalf of a user who does not have that permission.&lt;/p&gt;
&lt;h4 id=&quot;tool-poisoning&quot;&gt;Tool poisoning&lt;/h4&gt;
&lt;p&gt;A malicious or poorly maintained tool provides misleading descriptions, hidden side effects, or unsafe defaults.&lt;/p&gt;
&lt;h4 id=&quot;data-leakage&quot;&gt;Data leakage&lt;/h4&gt;
&lt;p&gt;The agent combines information from different users, tenants, or conversations and includes it in a response.&lt;/p&gt;
&lt;p&gt;The solution is not one magic prompt. It is layered security: least privilege, isolation, validation, monitoring, and clear ownership.&lt;/p&gt;
&lt;h3 id=&quot;observability-is-not-optional&quot;&gt;Observability is not optional&lt;/h3&gt;
&lt;p&gt;Traditional application monitoring asks what request failed, which service was slow, and what status code was returned.&lt;/p&gt;
&lt;p&gt;Agent observability needs additional questions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;What was the original user intent?&lt;/li&gt;
&lt;li&gt;What context was retrieved?&lt;/li&gt;
&lt;li&gt;Which tools were considered?&lt;/li&gt;
&lt;li&gt;Which tool was selected?&lt;/li&gt;
&lt;li&gt;What policy decision was made?&lt;/li&gt;
&lt;li&gt;How many retries occurred?&lt;/li&gt;
&lt;li&gt;Was human approval requested?&lt;/li&gt;
&lt;li&gt;What data was returned to the user?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Every agent action should have a correlation ID that follows the request across the model gateway, MCP server, domain service, database, and audit system.&lt;/p&gt;
&lt;p&gt;Without this trail, debugging becomes guesswork.&lt;/p&gt;
&lt;h3 id=&quot;start-with-narrow-agents&quot;&gt;Start with narrow agents&lt;/h3&gt;
&lt;p&gt;The best first production agent is rarely a general-purpose assistant with access to the entire company.&lt;/p&gt;
&lt;p&gt;Start with one bounded workflow:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Search internal documentation&lt;/li&gt;
&lt;li&gt;Summarize support tickets&lt;/li&gt;
&lt;li&gt;Validate an invoice&lt;/li&gt;
&lt;li&gt;Classify incoming documents&lt;/li&gt;
&lt;li&gt;Explain a failed transaction&lt;/li&gt;
&lt;li&gt;Draft, but do not send, a customer response&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Measure accuracy, latency, cost, refusal quality, tool-selection errors, and security violations. Then expand carefully.&lt;/p&gt;
&lt;p&gt;A narrow agent with clear boundaries can create more business value than a general agent that appears intelligent but cannot be trusted.&lt;/p&gt;
&lt;h3 id=&quot;the-future-of-agents-is-controlled-autonomy&quot;&gt;The future of agents is controlled autonomy&lt;/h3&gt;
&lt;p&gt;AI agents will become more capable, but capability alone is not the goal.&lt;/p&gt;
&lt;p&gt;The goal is controlled autonomy: systems that can complete useful work while remaining observable, explainable, reversible, and accountable.&lt;/p&gt;
&lt;p&gt;MCP can help create a common interface between agents and the software world. But the protocol is only one layer. The real production architecture still needs identity, policy, security, resilience, testing, and human judgment.&lt;/p&gt;
&lt;p&gt;The strongest agent teams will not ask only, What can the model do? They will also ask, What should the model be allowed to do, under which conditions, and how can we prove what happened?&lt;/p&gt;
&lt;p&gt;That is the difference between an impressive demo and an enterprise system.&lt;/p&gt;
&lt;h3 id=&quot;final-checklist&quot;&gt;Final checklist&lt;/h3&gt;
&lt;p&gt;Before exposing an MCP tool to an AI agent, verify:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Is the tool purpose narrow and explicit?&lt;/li&gt;
&lt;li&gt;Is access scoped to the authenticated user and tenant?&lt;/li&gt;
&lt;li&gt;Are inputs validated independently of the model?&lt;/li&gt;
&lt;li&gt;Are write actions idempotent?&lt;/li&gt;
&lt;li&gt;Are high-risk actions reversible or approval-gated?&lt;/li&gt;
&lt;li&gt;Are prompts and retrieved documents treated as untrusted data?&lt;/li&gt;
&lt;li&gt;Are tool calls, policy decisions, and outputs audited?&lt;/li&gt;
&lt;li&gt;Can the team trace a failed decision?&lt;/li&gt;
&lt;li&gt;Is there a rate limit and emergency disable mechanism?&lt;/li&gt;
&lt;li&gt;Does the system fail safely when the model is wrong?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If the answer to several of these questions is no, the agent is not ready for production.&lt;/p&gt;
&lt;p&gt;It is ready for a better architecture.&lt;/p&gt;
&lt;h3 id=&quot;a-production-agent-architecture&quot;&gt;A production agent architecture&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;flowchart LR
    U[User request] --&amp;gt; G[Agent gateway]
    G --&amp;gt; P[Policy and identity checks]
    P --&amp;gt; M[Model plan]
    M --&amp;gt; C[MCP server]
    C --&amp;gt; D[Domain service]
    D --&amp;gt; A[Audit and observability]
    A --&amp;gt; R[Validated response]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The model proposes the next action, but the gateway, policy layer, MCP server, and domain service each enforce their own boundary. This layered design prevents one incorrect model decision from becoming an uncontrolled production change.&lt;/p&gt;
&lt;h3 id=&quot;reference&quot;&gt;Reference&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://modelcontextprotocol.io/&quot;&gt;Model Context Protocol official documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/30689/most-developers-are-building-ai-agents-wrong-mcp-is-the-missing-contract</guid>
<pubDate>Sun, 11 Oct 2026 07:48:38 +0000</pubDate>
</item>
<item>
<title>From Writing Web Novels to Shipping Software — Nocklock’s Developer Journey</title>
<link>https://coderlegion.com/30297/from-writing-web-novels-to-shipping-software-nocklocks-developer-journey</link>
<description>&lt;h2 id=&quot;from-writing-web-novels-to-shipping-software-nocklock-s-developer-journey&quot;&gt;From Writing Web Novels to Shipping Software — Nocklock’s Developer Journey&lt;/h2&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;em&gt;“I think AI is most useful when it extends my thinking rather than replacing it.”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Nocklock didn't originally become a developer because programming was a lifelong childhood dream.&lt;/p&gt;
&lt;p&gt;It started for a much more practical reason: making a living.&lt;/p&gt;
&lt;p&gt;A few years ago, before AI became deeply integrated into everyday software development, Nocklock was looking for a practical way into the industry. In Korea, Java and Spring were widely used in enterprise and public-sector development, making them a natural choice.&lt;/p&gt;
&lt;p&gt;But programming eventually became interesting for a reason that had little to do with career practicality.&lt;/p&gt;
&lt;p&gt;Before becoming a developer, Nocklock had commercially published two web novels.&lt;/p&gt;
&lt;p&gt;That experience would unexpectedly influence the way he thinks about software.&lt;/p&gt;
&lt;p&gt;Writing fiction means creating a world, defining its rules, and allowing characters to interact within those rules.&lt;/p&gt;
&lt;p&gt;Software isn't so different.&lt;/p&gt;
&lt;p&gt;Programming languages have rules. Networks have rules. Data has structure. Business logic creates constraints. Developers build systems and user flows inside those constraints.&lt;/p&gt;
&lt;p&gt;The connection between writing and programming gave Nocklock a different way of thinking about development.&lt;/p&gt;
&lt;p&gt;Instead of seeing software only as code, he began seeing it as a system of possibilities.&lt;/p&gt;
&lt;p&gt;And that perspective continues to influence how he builds today.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;programming-started-as-a-practical-choice&quot;&gt;Programming Started as a Practical Choice&lt;/h3&gt;
&lt;p&gt;Nocklock's initial decision to learn programming was pragmatic.&lt;/p&gt;
&lt;p&gt;Java and Spring offered a realistic path into professional software development, particularly within Korea's enterprise and public-sector technology landscape.&lt;/p&gt;
&lt;p&gt;But choosing a technology for practical reasons doesn't necessarily mean remaining interested for practical reasons.&lt;/p&gt;
&lt;p&gt;As Nocklock gained experience, he discovered that programming gave him something he had already experienced through writing:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;the ability to create systems governed by rules.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;When writing a novel, he might ask:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;What if I were the character?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;What if the situation were different?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;What would happen if the rules changed?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;He now brings similar questions into software development.&lt;/p&gt;
&lt;p&gt;When building something, he tries to imagine different situations and perspectives.&lt;/p&gt;
&lt;p&gt;What if he were the user?&lt;/p&gt;
&lt;p&gt;What if the user knew nothing about the subject?&lt;/p&gt;
&lt;p&gt;What if the environment were different?&lt;/p&gt;
&lt;p&gt;What if an assumption turned out to be wrong?&lt;/p&gt;
&lt;p&gt;That habit of questioning has become part of how he approaches development.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;learning-by-shipping&quot;&gt;Learning by Shipping&lt;/h3&gt;
&lt;p&gt;One of Nocklock's strongest beliefs is reflected directly in his profile:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;learning by shipping.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;He discovered why this approach worked for him while studying for Korea's Engineer Information Processing qualification.&lt;/p&gt;
&lt;p&gt;Much of the preparation involved studying and memorizing theory.&lt;/p&gt;
&lt;p&gt;But he noticed something interesting.&lt;/p&gt;
&lt;p&gt;He could forget information he had memorized for an exam.&lt;/p&gt;
&lt;p&gt;Yet he could still remember errors he had encountered while working, why they happened, and how he eventually solved them.&lt;/p&gt;
&lt;p&gt;The difference was experience.&lt;/p&gt;
&lt;p&gt;Knowledge connected to a real problem stayed with him.&lt;/p&gt;
&lt;p&gt;That realization changed how he approached learning.&lt;/p&gt;
&lt;p&gt;When he encountered an AI development competition, he began building rather than waiting until he had learned everything he thought he needed to know.&lt;/p&gt;
&lt;p&gt;He learned what was necessary while developing the project.&lt;/p&gt;
&lt;p&gt;He made mistakes.&lt;/p&gt;
&lt;p&gt;He investigated them.&lt;/p&gt;
&lt;p&gt;He fixed them.&lt;/p&gt;
&lt;p&gt;And eventually, he shipped.&lt;/p&gt;
&lt;p&gt;That's what &lt;strong&gt;learning by shipping&lt;/strong&gt; means to him.&lt;/p&gt;
&lt;p&gt;It doesn't mean abandoning study.&lt;/p&gt;
&lt;p&gt;It means turning knowledge into experience.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;security-lens-when-the-mvp-became-real&quot;&gt;Security Lens: When the MVP Became Real&lt;/h3&gt;
&lt;p&gt;One of the clearest examples of this philosophy was &lt;strong&gt;Security Lens&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The original idea sounded relatively straightforward.&lt;/p&gt;
&lt;p&gt;A user would upload an image, OCR would extract information, the system would identify potential privacy risks, and the user would receive useful results.&lt;/p&gt;
&lt;p&gt;But building the real product exposed a much messier reality.&lt;/p&gt;
&lt;p&gt;Real files and images didn't always behave as expected.&lt;/p&gt;
&lt;p&gt;OCR accuracy wasn't always reliable.&lt;/p&gt;
&lt;p&gt;Unexpected problems kept appearing.&lt;/p&gt;
&lt;p&gt;And once the core functionality finally worked, Nocklock realized something important:&lt;/p&gt;
&lt;p&gt;The main feature working didn't mean the product was finished.&lt;/p&gt;
&lt;p&gt;It meant the next phase had started.&lt;/p&gt;
&lt;p&gt;There were still improvements to make, problems to investigate, and a deadline approaching.&lt;/p&gt;
&lt;p&gt;That experience changed his understanding of what it means to ship software.&lt;/p&gt;
&lt;p&gt;Building the main feature is only one milestone.&lt;/p&gt;
&lt;p&gt;Shipping means confronting the difference between:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;the product you imagined&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;and&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;the product that actually exists.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-readme-became-a-product-review&quot;&gt;The README Became a Product Review&lt;/h3&gt;
&lt;p&gt;Nocklock initially thought writing the README would simply mean documenting what he had already built.&lt;/p&gt;
&lt;p&gt;Instead, documentation exposed problems in the product itself.&lt;/p&gt;
&lt;p&gt;During development, he had often moved quickly from one problem to the next.&lt;/p&gt;
&lt;p&gt;Get something working.&lt;/p&gt;
&lt;p&gt;Move forward.&lt;/p&gt;
&lt;p&gt;Solve the next issue.&lt;/p&gt;
&lt;p&gt;Keep building.&lt;/p&gt;
&lt;p&gt;But when he tried to explain the project clearly, he started noticing gaps.&lt;/p&gt;
&lt;p&gt;Sometimes he would describe a feature and suddenly question whether the feature was necessary at all.&lt;/p&gt;
&lt;p&gt;That changed the purpose of the README.&lt;/p&gt;
&lt;p&gt;It was no longer just documentation.&lt;/p&gt;
&lt;p&gt;It became a way of reviewing the product.&lt;/p&gt;
&lt;p&gt;Writing forced him to examine:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The project structure&lt;/li&gt;
&lt;li&gt;Individual features&lt;/li&gt;
&lt;li&gt;Development decisions&lt;/li&gt;
&lt;li&gt;The reasoning behind those decisions&lt;/li&gt;
&lt;li&gt;Whether certain features were actually necessary&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In that sense, documentation became another form of development.&lt;/p&gt;
&lt;p&gt;Sometimes explaining what you built reveals whether you should have built it in the first place.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;a-new-laptop-revealed-hidden-complexity&quot;&gt;A New Laptop Revealed Hidden Complexity&lt;/h3&gt;
&lt;p&gt;Another unexpected lesson came from something much more ordinary:&lt;/p&gt;
&lt;p&gt;setting up a new laptop.&lt;/p&gt;
&lt;p&gt;Moving from an established development environment to a clean machine exposed how much invisible state had accumulated over time.&lt;/p&gt;
&lt;p&gt;JDK versions.&lt;/p&gt;
&lt;p&gt;PATH settings.&lt;/p&gt;
&lt;p&gt;Git configuration.&lt;/p&gt;
&lt;p&gt;IDE settings.&lt;/p&gt;
&lt;p&gt;Build synchronization.&lt;/p&gt;
&lt;p&gt;Environment variables.&lt;/p&gt;
&lt;p&gt;Small setup decisions that had gradually become invisible.&lt;/p&gt;
&lt;p&gt;On the old machine, those things simply worked.&lt;/p&gt;
&lt;p&gt;On the new one, they didn't.&lt;/p&gt;
&lt;p&gt;Suddenly, when something behaved unexpectedly, Nocklock had to ask:&lt;/p&gt;
&lt;p&gt;Is this a problem with my code?&lt;/p&gt;
&lt;p&gt;Is it the build state?&lt;/p&gt;
&lt;p&gt;The JDK?&lt;/p&gt;
&lt;p&gt;The IDE?&lt;/p&gt;
&lt;p&gt;The environment configuration?&lt;/p&gt;
&lt;p&gt;That experience led to an important realization:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The development environment is part of the system too.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A clean machine can reveal what you genuinely understand and what your previous environment had quietly been doing for you.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;debugging-doesn-t-stop-at-the-code&quot;&gt;Debugging Doesn't Stop at the Code&lt;/h3&gt;
&lt;p&gt;Deployment reinforced the same lesson.&lt;/p&gt;
&lt;p&gt;Local development can feel comfortable.&lt;/p&gt;
&lt;p&gt;Then the application gets deployed.&lt;/p&gt;
&lt;p&gt;Suddenly, ports, environment variables, runtime differences, external services, configuration and hidden assumptions all become part of the problem.&lt;/p&gt;
&lt;p&gt;Nocklock experienced this while deploying Security Lens to Railway.&lt;/p&gt;
&lt;p&gt;It also helped him understand why tools such as Docker became so important.&lt;/p&gt;
&lt;p&gt;Docker doesn't eliminate every deployment problem.&lt;/p&gt;
&lt;p&gt;But making the runtime environment reproducible can remove a significant category of differences between machines.&lt;/p&gt;
&lt;p&gt;As a result, his approach to debugging has expanded.&lt;/p&gt;
&lt;p&gt;He doesn't look only at the code anymore.&lt;/p&gt;
&lt;p&gt;He also considers:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Configuration&lt;/li&gt;
&lt;li&gt;Runtime&lt;/li&gt;
&lt;li&gt;Environment&lt;/li&gt;
&lt;li&gt;External dependencies&lt;/li&gt;
&lt;li&gt;Deployment conditions&lt;/li&gt;
&lt;li&gt;Assumptions connecting those pieces&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Sometimes the bug isn't in the code.&lt;/p&gt;
&lt;p&gt;Sometimes the code is behaving exactly as written inside an environment you misunderstood.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;why-java-and-spring-still-matter&quot;&gt;Why Java and Spring Still Matter&lt;/h3&gt;
&lt;p&gt;Nocklock originally chose Java and Spring because they were practical.&lt;/p&gt;
&lt;p&gt;After working with them professionally, however, he developed a different appreciation for the ecosystem.&lt;/p&gt;
&lt;p&gt;What stands out to him is its maturity and structure.&lt;/p&gt;
&lt;p&gt;Backend services can quickly become complicated.&lt;/p&gt;
&lt;p&gt;They may involve:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Business logic&lt;/li&gt;
&lt;li&gt;Databases&lt;/li&gt;
&lt;li&gt;APIs&lt;/li&gt;
&lt;li&gt;Security&lt;/li&gt;
&lt;li&gt;External integrations&lt;/li&gt;
&lt;li&gt;Multiple requirements&lt;/li&gt;
&lt;li&gt;Different environments&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In that situation, conventions and proven patterns can become extremely valuable.&lt;/p&gt;
&lt;p&gt;Spring provides abstractions that help developers build quickly while still leaving a deeper ecosystem underneath those abstractions to understand.&lt;/p&gt;
&lt;p&gt;But Nocklock doesn't believe Java or Spring should automatically be used for every problem.&lt;/p&gt;
&lt;p&gt;The technology should fit the problem.&lt;/p&gt;
&lt;p&gt;That idea connects directly to one of his broader principles:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;follow the problem, not a fixed stack.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;ai-should-extend-thinking-not-replace-it&quot;&gt;AI Should Extend Thinking, Not Replace It&lt;/h3&gt;
&lt;p&gt;Nocklock's approach to AI is deliberately different from simply asking an AI model to write everything.&lt;/p&gt;
&lt;p&gt;His first step isn't:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;“Write this for me.”&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Instead, he tries to explain what he's curious about, why he's curious about it, and how he thinks the pieces might connect.&lt;/p&gt;
&lt;p&gt;Then he asks AI to help explain the subject.&lt;/p&gt;
&lt;p&gt;Only after understanding the direction does he use AI for implementation, code review, identifying mistakes or suggesting improvements.&lt;/p&gt;
&lt;p&gt;That distinction matters to him.&lt;/p&gt;
&lt;p&gt;AI can provide an enormous amount of knowledge.&lt;/p&gt;
&lt;p&gt;Its ability to interact with tools and take action is also growing quickly.&lt;/p&gt;
&lt;p&gt;But Nocklock wants the important questions, judgment and decisions to remain his responsibility.&lt;/p&gt;
&lt;p&gt;The ideal relationship isn't human versus AI.&lt;/p&gt;
&lt;p&gt;It's human thinking enhanced by AI.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;AI becomes a multiplier for understanding rather than a substitute for understanding.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;following-the-problem-instead-of-the-stack&quot;&gt;Following the Problem Instead of the Stack&lt;/h3&gt;
&lt;p&gt;Nocklock doesn't want technology to be the starting point of a project.&lt;/p&gt;
&lt;p&gt;He starts with the problem.&lt;/p&gt;
&lt;p&gt;What is being built?&lt;/p&gt;
&lt;p&gt;How quickly does it need to be built?&lt;/p&gt;
&lt;p&gt;What constraints exist?&lt;/p&gt;
&lt;p&gt;What can realistically be maintained?&lt;/p&gt;
&lt;p&gt;What can be verified?&lt;/p&gt;
&lt;p&gt;Sometimes Java and Spring are the right choice because they are familiar and fit the requirements.&lt;/p&gt;
&lt;p&gt;Sometimes another technology is better.&lt;/p&gt;
&lt;p&gt;AI has made experimenting with unfamiliar technologies easier, but that doesn't mean every new technology deserves to be added to a project.&lt;/p&gt;
&lt;p&gt;In fact, sometimes following the problem means deliberately choosing something familiar and boring.&lt;/p&gt;
&lt;p&gt;That's an important distinction.&lt;/p&gt;
&lt;p&gt;Being flexible doesn't mean constantly chasing new technology.&lt;/p&gt;
&lt;p&gt;It means choosing technology based on what the problem actually requires.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-cost-of-moving-too-fast&quot;&gt;The Cost of Moving Too Fast&lt;/h3&gt;
&lt;p&gt;One mistake Nocklock continues to notice in himself is focusing heavily on implementation and assuming the structure can always be cleaned up later.&lt;/p&gt;
&lt;p&gt;Security Lens made this particularly obvious.&lt;/p&gt;
&lt;p&gt;Because of the competition deadline, he often solved the next problem in front of him and moved on.&lt;/p&gt;
&lt;p&gt;That approach helped maintain momentum.&lt;/p&gt;
&lt;p&gt;But later, while documenting the project, he could see where the structure had become messy.&lt;/p&gt;
&lt;p&gt;He could also see that some features probably didn't need to exist.&lt;/p&gt;
&lt;p&gt;The lesson wasn't that moving quickly was wrong.&lt;/p&gt;
&lt;p&gt;It was that speed doesn't remove the cost of decisions.&lt;/p&gt;
&lt;p&gt;It simply delays when you see that cost.&lt;/p&gt;
&lt;p&gt;Today, Nocklock tries to create small moments during development where he can step outside the code and ask:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Does what I'm building still make sense?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;That pause can prevent a lot of future cleanup.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-he-s-exploring-next&quot;&gt;What He's Exploring Next&lt;/h3&gt;
&lt;p&gt;Nocklock's interests are increasingly moving toward the intersection of:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Backend development.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;AI.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Security.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Real products.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Security Lens began as a tool for detecting privacy risks in files before they are uploaded.&lt;/p&gt;
&lt;p&gt;Working on it opened up broader questions about digital content.&lt;/p&gt;
&lt;p&gt;How do we know whether an image or piece of content is authentic?&lt;/p&gt;
&lt;p&gt;Where did it come from?&lt;/p&gt;
&lt;p&gt;Was it generated or modified by AI?&lt;/p&gt;
&lt;p&gt;Can AI-generated media be used to imitate another person?&lt;/p&gt;
&lt;p&gt;These questions have made areas such as content provenance and identity risk particularly interesting to him.&lt;/p&gt;
&lt;p&gt;At the same time, he remains interested in building smaller experimental products.&lt;/p&gt;
&lt;p&gt;The goal is to test an idea quickly, receive real feedback and then decide whether the idea deserves further investment.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;build-something-smaller&quot;&gt;Build Something Smaller&lt;/h3&gt;
&lt;p&gt;When asked what advice he would give other developers, Nocklock's answer is straightforward:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Build something smaller than the thing you originally wanted to build.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;It's easy to remain in preparation mode.&lt;/p&gt;
&lt;p&gt;There is always another:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Course&lt;/li&gt;
&lt;li&gt;Framework&lt;/li&gt;
&lt;li&gt;Tutorial&lt;/li&gt;
&lt;li&gt;Concept&lt;/li&gt;
&lt;li&gt;Technology&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;that could be learned first.&lt;/p&gt;
&lt;p&gt;But developers don't need to understand everything before starting.&lt;/p&gt;
&lt;p&gt;Build something small enough to finish.&lt;/p&gt;
&lt;p&gt;Let it break.&lt;/p&gt;
&lt;p&gt;Deploy it.&lt;/p&gt;
&lt;p&gt;Let another person use it.&lt;/p&gt;
&lt;p&gt;Then examine what goes wrong.&lt;/p&gt;
&lt;p&gt;Those problems will often tell you what you genuinely need to learn next.&lt;/p&gt;
&lt;p&gt;For Nocklock, one real error that requires investigation can teach more than memorizing ten definitions.&lt;/p&gt;
&lt;p&gt;Experience creates context.&lt;/p&gt;
&lt;p&gt;Context makes knowledge stick.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;becoming-more-than-a-framework-developer&quot;&gt;Becoming More Than a Framework Developer&lt;/h3&gt;
&lt;p&gt;Nocklock doesn't want to become a developer defined by a single framework.&lt;/p&gt;
&lt;p&gt;Java and Spring are an important foundation, but his longer-term goal is broader.&lt;/p&gt;
&lt;p&gt;He wants to become someone who can move comfortably between:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Engineering&lt;/li&gt;
&lt;li&gt;AI&lt;/li&gt;
&lt;li&gt;Product thinking&lt;/li&gt;
&lt;li&gt;Deployment&lt;/li&gt;
&lt;li&gt;Problem solving&lt;/li&gt;
&lt;li&gt;Communication&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;He wants to understand a problem, choose appropriate tools, build the system, deploy it, explain it clearly and improve it based on what actually happens.&lt;/p&gt;
&lt;p&gt;In other words:&lt;/p&gt;
&lt;p&gt;Not simply someone who writes code.&lt;/p&gt;
&lt;p&gt;Someone who can take an idea and keep working on it until it becomes something another person can actually use.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;from-writing-stories-to-building-systems&quot;&gt;From Writing Stories to Building Systems&lt;/h3&gt;
&lt;p&gt;There is an interesting connection running through Nocklock's entire journey.&lt;/p&gt;
&lt;p&gt;Before software, there was storytelling.&lt;/p&gt;
&lt;p&gt;He created fictional worlds, defined their rules and imagined what could happen inside them.&lt;/p&gt;
&lt;p&gt;Then came programming.&lt;/p&gt;
&lt;p&gt;Different syntax.&lt;/p&gt;
&lt;p&gt;Different constraints.&lt;/p&gt;
&lt;p&gt;Different tools.&lt;/p&gt;
&lt;p&gt;But a surprisingly similar creative process.&lt;/p&gt;
&lt;p&gt;Software also involves defining rules, creating systems and imagining what happens when different pieces interact.&lt;/p&gt;
&lt;p&gt;Today, AI adds another layer to that process.&lt;/p&gt;
&lt;p&gt;It can help him explore unfamiliar concepts, test ideas and move faster.&lt;/p&gt;
&lt;p&gt;But he still wants to be the person asking the questions.&lt;/p&gt;
&lt;p&gt;The person deciding what matters.&lt;/p&gt;
&lt;p&gt;The person choosing what to build.&lt;/p&gt;
&lt;p&gt;And ultimately, the person responsible for shipping it.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;knowing-what-not-to-build&quot;&gt;Knowing What Not to Build&lt;/h3&gt;
&lt;p&gt;When Nocklock first started programming, he thought becoming a better developer mostly meant knowing more.&lt;/p&gt;
&lt;p&gt;More syntax.&lt;/p&gt;
&lt;p&gt;More frameworks.&lt;/p&gt;
&lt;p&gt;More technologies.&lt;/p&gt;
&lt;p&gt;His perspective has changed.&lt;/p&gt;
&lt;p&gt;Technical knowledge still matters.&lt;/p&gt;
&lt;p&gt;But software development is also about understanding the problem, questioning assumptions, debugging beyond the code, communicating decisions, working within constraints and actually shipping something people can use.&lt;/p&gt;
&lt;p&gt;He has also learned that knowing &lt;strong&gt;what not to build&lt;/strong&gt; can be just as important as knowing how to build something.&lt;/p&gt;
&lt;p&gt;Code remains at the center of the work.&lt;/p&gt;
&lt;p&gt;But it isn't the whole job anymore.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-developer-journey-continues&quot;&gt;The Developer Journey Continues&lt;/h3&gt;
&lt;p&gt;Nocklock's journey began with a practical career decision.&lt;/p&gt;
&lt;p&gt;It developed through Java and Spring.&lt;/p&gt;
&lt;p&gt;It was shaped by an earlier life as a published web novelist.&lt;/p&gt;
&lt;p&gt;It accelerated through learning by shipping.&lt;/p&gt;
&lt;p&gt;Security Lens taught him the difference between building an MVP and actually shipping a product.&lt;/p&gt;
&lt;p&gt;Documentation taught him to question his own decisions.&lt;/p&gt;
&lt;p&gt;Deployment taught him that the environment is part of the system.&lt;/p&gt;
&lt;p&gt;AI taught him another way to learn and work.&lt;/p&gt;
&lt;p&gt;And his current interests are taking him toward the intersection of backend engineering, AI, security and real-world products.&lt;/p&gt;
&lt;p&gt;His philosophy can be summed up simply:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Start with the problem.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Build something small.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Let reality test it.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Learn from what breaks.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Ship it.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Then do it again.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;about-nocklock&quot;&gt;About Nocklock&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Nocklock&lt;/strong&gt; is a developer building useful things with Java, Spring and AI.&lt;/p&gt;
&lt;p&gt;His approach centers on learning by shipping, documenting what works and following the problem rather than committing to a fixed technology stack.&lt;/p&gt;
&lt;p&gt;His current interests include backend development, AI, security, content provenance and experimental product development.&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://coderlegion.com/user/Nocklock&quot;&gt;View Nocklock's profile on CoderLegion&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://nocklock.tistory.com/&quot;&gt;Visit Nocklock's website&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;coderlegion-developer-stories&quot;&gt;CoderLegion Developer Stories&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Real developers. Real journeys. Real experiences.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Developer Stories at CoderLegion gives developers a place to share how they think, what they build, the problems they encounter and what they learn along the way.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Interview &amp;amp; Story by Mehadi Hasan&lt;/strong&gt;&lt;/p&gt;
</description>
<category>Dev Stories</category>
<guid isPermaLink="true">https://coderlegion.com/30297/from-writing-web-novels-to-shipping-software-nocklocks-developer-journey</guid>
<pubDate>Thu, 08 Oct 2026 13:10:12 +0000</pubDate>
</item>
<item>
<title>A New Laptop Exposed the Hidden State in My Development Environment</title>
<link>https://coderlegion.com/29593/a-new-laptop-exposed-the-hidden-state-in-my-development-environment</link>
<description>&lt;p&gt;I recently moved my development setup to a new &lt;strong&gt;LG gram Pro 17&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;At first, I was mostly distracted by the laptop itself.&lt;/p&gt;
&lt;p&gt;It's a 17-inch machine, but it doesn't really feel like one when you pick it up.&lt;/p&gt;
&lt;p&gt;The screen is huge compared to what I was used to, and the weight was honestly the first thing that surprised me.&lt;/p&gt;
&lt;p&gt;Then I opened it and tried to get back to work.&lt;/p&gt;
&lt;p&gt;That was when the more interesting part started.&lt;/p&gt;
&lt;p&gt;My old development environment had disappeared.&lt;/p&gt;
&lt;p&gt;Not my code.&lt;/p&gt;
&lt;p&gt;My &lt;strong&gt;environment&lt;/strong&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;On my previous machine, Java was already installed.&lt;/p&gt;
&lt;p&gt;Git worked.&lt;/p&gt;
&lt;p&gt;IntelliJ knew my projects.&lt;/p&gt;
&lt;p&gt;Dependencies were already downloaded.&lt;/p&gt;
&lt;p&gt;Environment variables existed.&lt;/p&gt;
&lt;p&gt;Some configuration files had probably been sitting there for months without me thinking about them.&lt;/p&gt;
&lt;p&gt;I could open a project and start coding.&lt;/p&gt;
&lt;p&gt;It felt simple.&lt;/p&gt;
&lt;p&gt;A new laptop made me realize that it was only simple because I had already solved all of those problems sometime in the past.&lt;/p&gt;
&lt;h3 id=&quot;a-fresh-machine-removes-the-invisible-parts&quot;&gt;A fresh machine removes the invisible parts&lt;/h3&gt;
&lt;p&gt;I started rebuilding everything.&lt;/p&gt;
&lt;p&gt;Windows updates.&lt;/p&gt;
&lt;p&gt;JDK 17.&lt;/p&gt;
&lt;p&gt;Git.&lt;/p&gt;
&lt;p&gt;IntelliJ.&lt;/p&gt;
&lt;p&gt;Project SDK.&lt;/p&gt;
&lt;p&gt;Then I cloned one of my existing Spring Boot projects.&lt;/p&gt;
&lt;p&gt;None of this was new to me.&lt;/p&gt;
&lt;p&gt;I've installed Java before.&lt;/p&gt;
&lt;p&gt;I've configured IntelliJ before.&lt;/p&gt;
&lt;p&gt;I've run Spring Boot projects more times than I can count.&lt;/p&gt;
&lt;p&gt;But doing it all again from zero made something visible that I normally don't notice.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A development machine accumulates state.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Over time, that state becomes invisible.&lt;/p&gt;
&lt;p&gt;You stop remembering which environment variable you added.&lt;/p&gt;
&lt;p&gt;You forget which local file exists only on your machine.&lt;/p&gt;
&lt;p&gt;You don't think about which JDK your IDE is pointing to because it has been correct for months.&lt;/p&gt;
&lt;p&gt;You just work.&lt;/p&gt;
&lt;p&gt;Until you move to another machine.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;installing-the-tools-wasn-t-the-real-test&quot;&gt;Installing the tools wasn't the real test&lt;/h3&gt;
&lt;p&gt;At some point I had Java installed.&lt;/p&gt;
&lt;p&gt;Git was working.&lt;/p&gt;
&lt;p&gt;IntelliJ was open.&lt;/p&gt;
&lt;p&gt;Technically, I had a development environment.&lt;/p&gt;
&lt;p&gt;But I didn't really consider the setup finished.&lt;/p&gt;
&lt;p&gt;I had one condition:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Can one of my existing Spring Boot projects actually run here?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So I opened an old project.&lt;/p&gt;
&lt;p&gt;The IDE indexed it.&lt;/p&gt;
&lt;p&gt;Dependencies were resolved.&lt;/p&gt;
&lt;p&gt;The project built.&lt;/p&gt;
&lt;p&gt;Then I ran the application.&lt;/p&gt;
&lt;p&gt;The server started.&lt;/p&gt;
&lt;p&gt;That was the moment the new machine felt ready.&lt;/p&gt;
&lt;p&gt;Not when IntelliJ opened.&lt;/p&gt;
&lt;p&gt;Not when &lt;code&gt;java -version&lt;/code&gt; worked.&lt;/p&gt;
&lt;p&gt;Not when Git cloned the repository.&lt;/p&gt;
&lt;p&gt;When the application actually ran.&lt;/p&gt;
&lt;p&gt;Because a real application depends on much more than the editor where you write the code.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;-works-on-my-machine-has-another-side&quot;&gt;&quot;Works on my machine&quot; has another side&lt;/h3&gt;
&lt;p&gt;We usually talk about &quot;works on my machine&quot; when deployment breaks.&lt;/p&gt;
&lt;p&gt;But moving to a new laptop made me realize there is a smaller version of the same problem inside development itself.&lt;/p&gt;
&lt;p&gt;If a project only works because my old machine has the right combination of hidden configuration, cached dependencies, local files, and environment variables...&lt;/p&gt;
&lt;p&gt;how reproducible is my development environment really?&lt;/p&gt;
&lt;p&gt;I'm not saying every local setup needs to be fully automated.&lt;/p&gt;
&lt;p&gt;Mine isn't.&lt;/p&gt;
&lt;p&gt;But I do think I should be able to answer a basic question:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;What does this project actually need in order to run somewhere else?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;A new laptop is surprisingly good at testing that.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;this-felt-familiar&quot;&gt;This felt familiar&lt;/h3&gt;
&lt;p&gt;I ran into almost the same problem while building and deploying small Spring Boot MVPs.&lt;/p&gt;
&lt;p&gt;The main feature worked.&lt;/p&gt;
&lt;p&gt;The API responded.&lt;/p&gt;
&lt;p&gt;The UI behaved correctly.&lt;/p&gt;
&lt;p&gt;On localhost, everything looked fine.&lt;/p&gt;
&lt;p&gt;Then deployment started exposing assumptions.&lt;/p&gt;
&lt;p&gt;Ports.&lt;/p&gt;
&lt;p&gt;Environment variables.&lt;/p&gt;
&lt;p&gt;Build configuration.&lt;/p&gt;
&lt;p&gt;External services.&lt;/p&gt;
&lt;p&gt;Things that my local machine had quietly been handling for me.&lt;/p&gt;
&lt;p&gt;That's when I started thinking of &lt;strong&gt;building&lt;/strong&gt; and &lt;strong&gt;shipping&lt;/strong&gt; as two different skills.&lt;/p&gt;
&lt;p&gt;I eventually turned the checklist I was using into a small guide called &lt;strong&gt;Ship Your Spring Boot MVP&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;It focuses on the things I kept checking before calling a project ready to leave localhost:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;clean builds&lt;/li&gt;
&lt;li&gt;configuration&lt;/li&gt;
&lt;li&gt;environment variables&lt;/li&gt;
&lt;li&gt;deployment checks&lt;/li&gt;
&lt;li&gt;evidence that the application actually works outside the development machine&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If that's a problem you're working through too:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://nocklock.lemonsqueezy.com/checkout/buy/587f273f-2eb7-4e55-9ad7-cfdcba6acc99&quot;&gt;Ship Your Spring Boot MVP&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I didn't make it because deploying Spring Boot is mysterious.&lt;/p&gt;
&lt;p&gt;I made it because I kept realizing how easy it is to confuse &lt;strong&gt;&quot;the feature works&quot;&lt;/strong&gt; with &lt;strong&gt;&quot;the product is ready to ship.&quot;&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-laptop-itself-still-needs-more-testing&quot;&gt;The laptop itself still needs more testing&lt;/h3&gt;
&lt;p&gt;As for the gram Pro 17, I'm still not ready to call this a full developer review.&lt;/p&gt;
&lt;p&gt;The machine I'm using has a &lt;strong&gt;Core Ultra 7 and 32GB of RAM&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;So far, I really like the screen size and the weight.&lt;/p&gt;
&lt;p&gt;But what I actually care about comes next.&lt;/p&gt;
&lt;p&gt;I want to spend real development time with:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;IntelliJ&lt;/li&gt;
&lt;li&gt;Spring Boot&lt;/li&gt;
&lt;li&gt;Docker&lt;/li&gt;
&lt;li&gt;a database&lt;/li&gt;
&lt;li&gt;browser tabs&lt;/li&gt;
&lt;li&gt;documentation&lt;/li&gt;
&lt;li&gt;all the other things I somehow leave open at the same time&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I want to see the thermals.&lt;/p&gt;
&lt;p&gt;I want to see the battery life.&lt;/p&gt;
&lt;p&gt;And I want to know whether carrying a 17-inch laptop every day still feels like a good idea after the novelty wears off.&lt;/p&gt;
&lt;p&gt;So the hardware review can wait.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;For now, the more interesting thing wasn't the new laptop.&lt;/p&gt;
&lt;p&gt;It was seeing my old development environment without all of its accumulated history.&lt;/p&gt;
&lt;p&gt;The laptop was new.&lt;/p&gt;
&lt;p&gt;The code wasn't.&lt;/p&gt;
&lt;p&gt;But starting from an empty machine made me see how much invisible context had been sitting between the two.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/29593/a-new-laptop-exposed-the-hidden-state-in-my-development-environment</guid>
<pubDate>Sat, 03 Oct 2026 05:41:23 +0000</pubDate>
</item>
<item>
<title>How Java Developers Build Scalable Applications and Backend Systems</title>
<link>https://coderlegion.com/29480/how-java-developers-build-scalable-applications-and-backend-systems</link>
<description>&lt;p&gt;As a Java developer, I enjoy building applications that solve real problems, handling complex logic, and exploring how software works behind the scenes. Java is a powerful programming language that helps developers create everything from small applications to large backend systems used by businesses every day.&lt;/p&gt;
&lt;p&gt;But how does the work of a Java developer actually happen? It involves much more than writing code. It requires understanding problems, designing solutions, managing data, testing functionality, and making sure applications work reliably as they grow.&lt;/p&gt;
&lt;h3 id=&quot;understanding-the-problem-before-writing-code&quot;&gt;Understanding the Problem Before Writing Code&lt;/h3&gt;
&lt;p&gt;Every application starts with a problem or an idea. Before writing Java code, I need to understand what the application should do, who will use it, and what features are required.&lt;/p&gt;
&lt;p&gt;For example, if I am building an online shopping application, I need to think about user registration, product management, shopping carts, payments, and order tracking.&lt;/p&gt;
&lt;p&gt;Breaking a large problem into smaller tasks makes development easier and helps create a clear plan for implementation.&lt;/p&gt;
&lt;h3 id=&quot;building-the-backend-with-java&quot;&gt;Building the Backend with Java&lt;/h3&gt;
&lt;p&gt;The backend is the part of an application that handles business logic, processes requests, communicates with databases, and returns responses to users.&lt;/p&gt;
&lt;p&gt;Java is useful for backend development because it supports object-oriented programming, strong type checking, and a large ecosystem of development tools and libraries.&lt;/p&gt;
&lt;p&gt;For example, when a user logs into an application, the backend receives the login request, validates the submitted information, checks the stored user data, and returns an appropriate response.&lt;/p&gt;
&lt;p&gt;Frameworks such as Spring Boot help Java developers build REST APIs, organize application logic, manage dependencies, and connect different parts of a system.&lt;/p&gt;
&lt;h3 id=&quot;connecting-applications-to-databases&quot;&gt;Connecting Applications to Databases&lt;/h3&gt;
&lt;p&gt;Most applications need to store and retrieve information. This is where databases become important.&lt;/p&gt;
&lt;p&gt;A Java application might use MySQL or PostgreSQL to store user accounts, product information, transactions, or other business data.&lt;/p&gt;
&lt;p&gt;Using tools such as Spring Data JPA and Hibernate, developers can work with database records through Java objects while still understanding the SQL queries and database operations involved.&lt;/p&gt;
&lt;p&gt;For example, when a customer places an order, the application may validate the order, save its details, update inventory, and return a confirmation.&lt;/p&gt;
&lt;p&gt;Handling these operations correctly is important because a small database error can affect the entire application.&lt;/p&gt;
&lt;h3 id=&quot;designing-scalable-backend-systems&quot;&gt;Designing Scalable Backend Systems&lt;/h3&gt;
&lt;p&gt;Scalability means an application can handle increasing workloads as the number of users or requests grows.&lt;/p&gt;
&lt;p&gt;Imagine a small application that works perfectly when 100 people use it. What happens when thousands of users start accessing it simultaneously?&lt;/p&gt;
&lt;p&gt;Developers need to consider database performance, efficient algorithms, caching, connection management, and application architecture.&lt;/p&gt;
&lt;p&gt;Depending on the requirements, a Java application might use Redis for caching, message queues for background processing, or multiple application instances to handle incoming requests.&lt;/p&gt;
&lt;p&gt;Scalability does not always mean using microservices or adding more servers. Sometimes, improving a database query or removing an unnecessary operation is enough to make a significant difference.&lt;/p&gt;
&lt;h3 id=&quot;solving-complex-problems-through-debugging&quot;&gt;Solving Complex Problems Through Debugging&lt;/h3&gt;
&lt;p&gt;One of the most important parts of software development is debugging.&lt;/p&gt;
&lt;p&gt;An application may fail because of incorrect business logic, unexpected input, database connection problems, or communication failures between services.&lt;/p&gt;
&lt;p&gt;Instead of fixing errors randomly, I try to understand what caused the problem, reproduce it, examine logs, and test possible solutions.&lt;/p&gt;
&lt;p&gt;Writing unit tests and integration tests also helps identify problems early and reduces the risk of breaking existing functionality when new features are added.&lt;/p&gt;
&lt;p&gt;Every bug provides an opportunity to understand the system more deeply and improve the quality of the code.&lt;/p&gt;
&lt;h3 id=&quot;learning-and-exploring-software-architecture&quot;&gt;Learning and Exploring Software Architecture&lt;/h3&gt;
&lt;p&gt;Technology continues to evolve, so learning is an important part of being a Java developer.&lt;/p&gt;
&lt;p&gt;I enjoy exploring software architecture, design patterns, REST APIs, performance optimization, and tools that make applications easier to maintain.&lt;/p&gt;
&lt;p&gt;For example, separating an application into controller, service, and repository layers can help organize responsibilities and make the code easier to test.&lt;/p&gt;
&lt;p&gt;As projects become more complex, understanding how different components communicate becomes just as important as knowing the programming language itself.&lt;/p&gt;
&lt;h3 id=&quot;what-makes-development-meaningful-&quot;&gt;What Makes Development Meaningful?&lt;/h3&gt;
&lt;p&gt;For me, software development is about turning ideas into working solutions. Writing code is only one part of the process. Understanding the problem, making thoughtful technical decisions, testing the result, and improving the system are equally important.&lt;/p&gt;
&lt;p&gt;I believe that becoming a better Java developer requires consistent practice, curiosity, and a willingness to learn from mistakes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;My goal is to keep improving my Java development skills, build reliable and scalable backend applications, and explore better ways to solve real-world problems through software.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Every project brings a new challenge, and every challenge offers an opportunity to learn something valuable.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/29480/how-java-developers-build-scalable-applications-and-backend-systems</guid>
<pubDate>Fri, 02 Oct 2026 04:00:39 +0000</pubDate>
</item>
<item>
<title>eSIMQR Travel eSIM Store</title>
<link>https://coderlegion.com/29325/esimqr-travel-esim-store</link>
<description>&lt;h3 id=&quot;a-self-hosted-esim-commerce-stack-for-operators-and-agencies&quot;&gt;A self-hosted eSIM commerce stack for operators and agencies&lt;/h3&gt;
&lt;p&gt;Travel eSIM businesses often stitch together a storefront, supplier APIs, payment gateways, reseller spreadsheets and a separate admin panel. eSIMQR packages those workflows into one self-hosted platform that teams can deploy and customize.&lt;/p&gt;
&lt;p&gt;The customer-facing storefront supports multilingual destination and plan discovery, checkout, payment and QR-code delivery. Behind it, operators can manage supplier catalogues, pricing rules, orders, refunds and reporting from one administration workspace. Agencies can also provide resellers with partner pricing, prepaid wallets and their own portal instead of handling every transaction manually.&lt;/p&gt;
&lt;h3 id=&quot;what-is-included&quot;&gt;What is included&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Travel eSIM storefront with QR-code delivery&lt;/li&gt;
&lt;li&gt;Reseller portal with partner pricing and prepaid wallets&lt;/li&gt;
&lt;li&gt;Admin tools for catalogues, orders, pricing and refunds&lt;/li&gt;
&lt;li&gt;Supplier plugins for Airalo, eSIM Access, eSIM Go and YonoSIM&lt;/li&gt;
&lt;li&gt;Stripe, PayPal, Paddle and Airwallex payment plugins&lt;/li&gt;
&lt;li&gt;Java 21, Spring Boot and Docker deployment files&lt;/li&gt;
&lt;li&gt;Editable source code and test integrations&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The project is intended for teams that want to own their customer experience and infrastructure rather than depend entirely on a hosted white-label service. The codebase can be adapted for a direct-to-consumer shop, an agency reseller programme or an internal eSIM sales operation.&lt;/p&gt;
&lt;p&gt;View the full package, screenshots and technical details on &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.codester.com/items/71626/esimqr-java-esim-store-and-reseller-platform&quot;&gt;Codester&lt;/a&gt;.&lt;/p&gt;
</description>
<category>Launches</category>
<guid isPermaLink="true">https://coderlegion.com/29325/esimqr-travel-esim-store</guid>
<pubDate>Thu, 01 Oct 2026 01:33:25 +0000</pubDate>
</item>
<item>
<title>I Built the MVP. Then I Realized Shipping Was a Different Skill.</title>
<link>https://coderlegion.com/28489/i-built-the-mvp-then-i-realized-shipping-was-a-different-skill</link>
<description>&lt;h2 id=&quot;i-built-the-mvp-then-i-realized-shipping-was-a-different-skill-&quot;&gt;I Built the MVP. Then I Realized Shipping Was a Different Skill.&lt;/h2&gt;
&lt;p&gt;A few days ago, I wrote about something I had learned the hard way:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Building the MVP first and writing the README later made me realize how much I had assumed.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I thought that was mostly a documentation problem.&lt;/p&gt;
&lt;p&gt;It wasn't.&lt;/p&gt;
&lt;p&gt;The deeper problem was that &lt;strong&gt;building something and shipping something are two different skills.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-mvp-was-working&quot;&gt;The MVP was working&lt;/h3&gt;
&lt;p&gt;I recently built &lt;strong&gt;Security Lens&lt;/strong&gt;, a Spring Boot-based privacy risk analyzer for images and documents.&lt;/p&gt;
&lt;p&gt;The basic flow was working:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Image Upload
      ↓
OCR
      ↓
Personal Information Detection
      ↓
EXIF Metadata Analysis
      ↓
Risk Scoring
      ↓
LLM-assisted Analysis
      ↓
Risk Explanation
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It could detect things such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;phone numbers&lt;/li&gt;
&lt;li&gt;email addresses&lt;/li&gt;
&lt;li&gt;EXIF GPS information&lt;/li&gt;
&lt;li&gt;capture timestamps&lt;/li&gt;
&lt;li&gt;device metadata&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;and turn the results into a simple privacy risk score.&lt;/p&gt;
&lt;p&gt;At that point, I had something that worked.&lt;/p&gt;
&lt;p&gt;I thought I was almost done.&lt;/p&gt;
&lt;p&gt;I wasn't.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;-it-works-was-only-the-beginning&quot;&gt;&quot;It works&quot; was only the beginning&lt;/h3&gt;
&lt;p&gt;Once I tried to actually ship the project, a different set of questions appeared.&lt;/p&gt;
&lt;p&gt;Not:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Does this method work?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;But:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Which exact version is deployed?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;&lt;p&gt;What happens if an environment variable is missing?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;&lt;p&gt;Did I actually verify the production configuration?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;&lt;p&gt;What if the database or external integration behaves differently from localhost?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;&lt;p&gt;What evidence do I have that the deployed application is healthy?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;&lt;p&gt;Can I roll back?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Those questions were not really about writing more code.&lt;/p&gt;
&lt;p&gt;They were about &lt;strong&gt;operating the thing I had already written.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;deployment-exposed-assumptions&quot;&gt;Deployment exposed assumptions&lt;/h3&gt;
&lt;p&gt;Localhost is forgiving.&lt;/p&gt;
&lt;p&gt;Your machine already has:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;your environment variables&lt;/li&gt;
&lt;li&gt;your tools&lt;/li&gt;
&lt;li&gt;your configuration&lt;/li&gt;
&lt;li&gt;your ports&lt;/li&gt;
&lt;li&gt;your dependencies&lt;/li&gt;
&lt;li&gt;your development habits&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Production does not care about any of that.&lt;/p&gt;
&lt;p&gt;It only gets what you actually configured.&lt;/p&gt;
&lt;p&gt;While working through deployment, I ran into issues around ports, environment setup, AI integration, and the difference between an implementation that works locally and one that can actually run as a product.&lt;/p&gt;
&lt;p&gt;That changed how I looked at &quot;done.&quot;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;ai-made-this-even-more-interesting&quot;&gt;AI made this even more interesting&lt;/h3&gt;
&lt;p&gt;I use AI tools heavily while building.&lt;/p&gt;
&lt;p&gt;They are extremely useful.&lt;/p&gt;
&lt;p&gt;But I also noticed a dangerous pattern.&lt;/p&gt;
&lt;p&gt;It is very easy to ask:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&quot;Is this production-ready?&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;and get an answer that sounds convincing.&lt;/p&gt;
&lt;p&gt;The problem is that confidence is not evidence.&lt;/p&gt;
&lt;p&gt;So I started using a different rule:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;If the evidence is missing, the result is NOT VERIFIED.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Instead of asking AI to make the release decision for me, I ask it to help inspect:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;configuration&lt;/li&gt;
&lt;li&gt;logs&lt;/li&gt;
&lt;li&gt;build output&lt;/li&gt;
&lt;li&gt;deployment settings&lt;/li&gt;
&lt;li&gt;security assumptions&lt;/li&gt;
&lt;li&gt;missing evidence&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The AI can be a reviewer.&lt;/p&gt;
&lt;p&gt;It should not silently become the owner of the release decision.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;that-became-a-checklist&quot;&gt;That became a checklist&lt;/h3&gt;
&lt;p&gt;While repeatedly asking myself the same deployment questions, I started writing them down.&lt;/p&gt;
&lt;p&gt;Eventually, those notes became a small public project:&lt;/p&gt;
&lt;p&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/nocklock-h/spring-boot-release-checklist&quot;&gt;Spring Boot Release Checklist&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;It focuses on things like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;clean builds&lt;/li&gt;
&lt;li&gt;expected tests&lt;/li&gt;
&lt;li&gt;configuration and secrets&lt;/li&gt;
&lt;li&gt;database connectivity&lt;/li&gt;
&lt;li&gt;external integrations&lt;/li&gt;
&lt;li&gt;production smoke tests&lt;/li&gt;
&lt;li&gt;rollback points&lt;/li&gt;
&lt;li&gt;evidence-first AI review&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The main idea is simple:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;A successful deployment is not the same as a verified release.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id=&quot;then-i-noticed-something-else&quot;&gt;Then I noticed something else&lt;/h3&gt;
&lt;p&gt;The checklist helped me catch blockers.&lt;/p&gt;
&lt;p&gt;But I still wanted a better way to answer:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;What did I actually verify?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;&lt;p&gt;Where is the evidence?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;&lt;p&gt;What still remains uncertain?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;&lt;p&gt;Should I ship this version?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That eventually became a more structured release workflow with interactive checks, evidence fields, AI review prompts, and ship / no-ship decisions.&lt;/p&gt;
&lt;p&gt;I ended up turning it into a 58-page deployment runbook.&lt;/p&gt;
&lt;p&gt;That was another new experience for me.&lt;/p&gt;
&lt;p&gt;For the first time, I was not only building software.&lt;/p&gt;
&lt;p&gt;I was taking something I had learned while building software and turning it into something another developer could actually use.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;shipping-forced-me-to-learn-things-coding-alone-did-not&quot;&gt;Shipping forced me to learn things coding alone did not&lt;/h3&gt;
&lt;p&gt;The interesting part was not setting up a product page.&lt;/p&gt;
&lt;p&gt;It was everything around it.&lt;/p&gt;
&lt;p&gt;I had to think about:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;what the product actually solves&lt;/li&gt;
&lt;li&gt;how to explain it in one sentence&lt;/li&gt;
&lt;li&gt;what should remain free&lt;/li&gt;
&lt;li&gt;what someone would reasonably pay for&lt;/li&gt;
&lt;li&gt;how a buyer receives the files&lt;/li&gt;
&lt;li&gt;how the download flow works&lt;/li&gt;
&lt;li&gt;what happens after payment&lt;/li&gt;
&lt;li&gt;how the public GitHub repository connects to the full version&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;None of these problems required a complicated algorithm.&lt;/p&gt;
&lt;p&gt;But they still required engineering decisions.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;my-definition-of-done-is-changing&quot;&gt;My definition of &quot;done&quot; is changing&lt;/h3&gt;
&lt;p&gt;Before:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Feature works
→ done
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Feature works
→ build succeeds
→ configuration is verified
→ deploy
→ production smoke test
→ evidence recorded
→ rollback understood
→ documentation updated
→ then maybe done
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It is slower.&lt;/p&gt;
&lt;p&gt;But it feels much closer to building something real.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;one-more-thing-i-learned&quot;&gt;One more thing I learned&lt;/h3&gt;
&lt;p&gt;I used to think &quot;shipping&quot; was mostly the final step after development.&lt;/p&gt;
&lt;p&gt;Now I think it changes how you develop from the beginning.&lt;/p&gt;
&lt;p&gt;When you know you will actually deploy something, you think differently about:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;configuration&lt;/li&gt;
&lt;li&gt;secrets&lt;/li&gt;
&lt;li&gt;observability&lt;/li&gt;
&lt;li&gt;failure cases&lt;/li&gt;
&lt;li&gt;reproducibility&lt;/li&gt;
&lt;li&gt;documentation&lt;/li&gt;
&lt;li&gt;users&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The project stops being just code.&lt;/p&gt;
&lt;p&gt;It becomes a system someone else has to trust.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-s-next&quot;&gt;What's next&lt;/h3&gt;
&lt;p&gt;I am continuing to improve Security Lens and experimenting with the same workflow on other small products.&lt;/p&gt;
&lt;p&gt;The Security Lens source code remains private, but I created a public technical case study here:&lt;/p&gt;
&lt;p&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/nocklock-h/security-lens-case-study&quot;&gt;Security Lens — Technical Case Study&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;And the lightweight Spring Boot release workflow is available here:&lt;/p&gt;
&lt;p&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/nocklock-h/spring-boot-release-checklist&quot;&gt;Spring Boot Release Checklist&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;For me, the biggest lesson so far is simple:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Coding makes the product possible. Shipping makes it real.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Still learning.&lt;br&gt;
Still shipping.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/28489/i-built-the-mvp-then-i-realized-shipping-was-a-different-skill</guid>
<pubDate>Thu, 24 Sep 2026 13:44:30 +0000</pubDate>
</item>
<item>
<title>I Built the MVP First. Then I Wrote the README.</title>
<link>https://coderlegion.com/28488/i-built-the-mvp-first-then-i-wrote-the-readme</link>
<description>&lt;p&gt;I did this in the wrong order.&lt;/p&gt;
&lt;p&gt;At least, that was my first thought.&lt;/p&gt;
&lt;p&gt;I was building a small project that checks images and documents for privacy risks. The backend came first, then the API, then a frontend that finally made it feel like an actual service.&lt;/p&gt;
&lt;p&gt;Somewhere along the way, I realized something.&lt;/p&gt;
&lt;p&gt;I had built quite a lot before properly organizing the requirements and documentation.&lt;/p&gt;
&lt;p&gt;Normally, I would expect the order to look more like this:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;requirements → design → implementation → testing → documentation&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Mine looked closer to this:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;build → break something → fix it → build more → test it → finally organize everything&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Not exactly textbook development.&lt;/p&gt;
&lt;p&gt;But writing the documentation afterwards turned out to be more useful than I expected.&lt;/p&gt;
&lt;h3 id=&quot;the-readme-became-a-way-to-inspect-the-project&quot;&gt;The README became a way to inspect the project&lt;/h3&gt;
&lt;p&gt;At first, I thought I was just writing down what I had already built.&lt;/p&gt;
&lt;p&gt;Instead, I kept finding things that were unclear.&lt;/p&gt;
&lt;p&gt;What does this API actually guarantee?&lt;/p&gt;
&lt;p&gt;What configuration is required before running the project?&lt;/p&gt;
&lt;p&gt;What happens when an external dependency is unavailable?&lt;/p&gt;
&lt;p&gt;Which parts have actually been tested?&lt;/p&gt;
&lt;p&gt;Would someone other than me know how to start this project?&lt;/p&gt;
&lt;p&gt;These are simple questions.&lt;/p&gt;
&lt;p&gt;They become much harder to ignore when you have to explain the project in plain English.&lt;/p&gt;
&lt;p&gt;And that was probably the most useful part.&lt;/p&gt;
&lt;p&gt;Documentation stopped being something I needed to finish after development.&lt;/p&gt;
&lt;p&gt;It became another form of testing.&lt;/p&gt;
&lt;h3 id=&quot;ai-made-building-faster-but-that-created-a-different-problem&quot;&gt;AI made building faster, but that created a different problem&lt;/h3&gt;
&lt;p&gt;I also used AI quite heavily while building.&lt;/p&gt;
&lt;p&gt;ChatGPT, Claude, and coding tools made it much easier to move from an idea to working code.&lt;/p&gt;
&lt;p&gt;That part was great.&lt;/p&gt;
&lt;p&gt;But faster implementation also meant I could move past decisions without really stopping to organize them.&lt;/p&gt;
&lt;p&gt;A configuration works, so I move on.&lt;/p&gt;
&lt;p&gt;An endpoint returns the right response, so I move on.&lt;/p&gt;
&lt;p&gt;Docker starts successfully, so I move on.&lt;/p&gt;
&lt;p&gt;Then deployment comes.&lt;/p&gt;
&lt;p&gt;Suddenly all the small things I skipped become visible again.&lt;/p&gt;
&lt;p&gt;Environment variables.&lt;/p&gt;
&lt;p&gt;Ports.&lt;/p&gt;
&lt;p&gt;Database configuration.&lt;/p&gt;
&lt;p&gt;Build commands.&lt;/p&gt;
&lt;p&gt;Health checks.&lt;/p&gt;
&lt;p&gt;Logs.&lt;/p&gt;
&lt;p&gt;Security settings.&lt;/p&gt;
&lt;p&gt;Things that looked like small details on localhost started feeling much more important once I tried to get the application outside my own machine.&lt;/p&gt;
&lt;p&gt;That was the point where I started thinking less about:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;“Does this code work?”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;and more about:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;“Is this actually ready to leave localhost?”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;There is a bigger difference between those two questions than I expected.&lt;/p&gt;
&lt;h3 id=&quot;a-working-mvp-is-not-the-same-as-a-shippable-mvp&quot;&gt;A working MVP is not the same as a shippable MVP&lt;/h3&gt;
&lt;p&gt;I used to think of an MVP mainly in terms of features.&lt;/p&gt;
&lt;p&gt;If the core function works, the MVP works.&lt;/p&gt;
&lt;p&gt;I still think that is partly true.&lt;/p&gt;
&lt;p&gt;But there is another stage after that.&lt;/p&gt;
&lt;p&gt;The application has to survive being run somewhere other than your laptop.&lt;/p&gt;
&lt;p&gt;Someone needs to know how to configure it.&lt;/p&gt;
&lt;p&gt;You need to know when it has failed.&lt;/p&gt;
&lt;p&gt;Secrets should not be sitting in the repository.&lt;/p&gt;
&lt;p&gt;The build should be reproducible.&lt;/p&gt;
&lt;p&gt;The production environment should not depend on something you happened to configure manually three days ago and already forgot about.&lt;/p&gt;
&lt;p&gt;None of these things are particularly exciting.&lt;/p&gt;
&lt;p&gt;They are also exactly the things I kept running into.&lt;/p&gt;
&lt;p&gt;So I started writing them down.&lt;/p&gt;
&lt;p&gt;Not as another tutorial.&lt;/p&gt;
&lt;p&gt;Just as a checklist I wanted for myself the next time I reached this stage.&lt;/p&gt;
&lt;h3 id=&quot;that-checklist-became-its-own-project&quot;&gt;That checklist became its own project&lt;/h3&gt;
&lt;p&gt;Eventually those notes turned into a small release workflow.&lt;/p&gt;
&lt;p&gt;I made a lightweight version public on GitHub:&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/nocklock-h/spring-boot-release-checklist&quot;&gt;Spring Boot Release Checklist&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;It is a practical pre-release checklist for Spring Boot MVPs moving from localhost to production.&lt;/p&gt;
&lt;p&gt;The idea is simple:&lt;/p&gt;
&lt;p&gt;before calling an MVP ready, go through the things that are easy to miss when most of your attention has been on making the feature work.&lt;/p&gt;
&lt;p&gt;I later expanded the same idea into a more structured 58-page deployment runbook, but the GitHub checklist remains the lightweight public version.&lt;/p&gt;
&lt;p&gt;I did not originally plan to make a product out of it.&lt;/p&gt;
&lt;p&gt;It came from repeatedly asking myself the same question while building:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;“What am I forgetting before I ship this?”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;i-still-don-t-think-building-first-is-the-correct-order&quot;&gt;I still don't think building first is the correct order&lt;/h3&gt;
&lt;p&gt;I would not turn this into advice like:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;“Forget requirements. Just start coding.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That would be the wrong lesson.&lt;/p&gt;
&lt;p&gt;If anything, doing it backwards showed me why the earlier steps exist.&lt;/p&gt;
&lt;p&gt;But there was something valuable about documenting a project after I had actually struggled with it.&lt;/p&gt;
&lt;p&gt;The checklist was no longer theoretical.&lt;/p&gt;
&lt;p&gt;Every item reminded me of something I had needed, forgotten, broken, or had to go back and check.&lt;/p&gt;
&lt;p&gt;Maybe next time my process will be a little cleaner.&lt;/p&gt;
&lt;p&gt;Probably not perfectly clean.&lt;/p&gt;
&lt;p&gt;And that's fine.&lt;/p&gt;
&lt;p&gt;I'm still learning by making things.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;I keep more notes about development, AI tools, deployment, and the things that break along the way on the &lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://nocklock.tistory.com/&quot;&gt;Nocklock blog&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;You can also see the projects I'm building on &lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/nocklock-h&quot;&gt;GitHub&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/28488/i-built-the-mvp-first-then-i-wrote-the-readme</guid>
<pubDate>Thu, 24 Sep 2026 13:42:28 +0000</pubDate>
</item>
<item>
<title>Spring Boot 4 + Spring AI + Langfuse: Tracing LLM Calls and Tool Calling</title>
<link>https://coderlegion.com/28396/spring-boot-4-spring-ai-langfuse-tracing-llm-calls-and-tool-calling</link>
<description>&lt;h2 id=&quot;part-2-spring-ai-langfuse-tracing-streaming-with-observationfilter&quot;&gt;Part 2: &lt;a rel=&quot;nofollow&quot; href=&quot;https://dev.to/lbobylev/spring-ai-langfuse-tracing-streaming-with-observationfilter-h3l&quot;&gt;Spring AI + Langfuse: Tracing Streaming with ObservationFilter&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In this post, we will look at how to connect Spring AI with Langfuse and get complete tracing for LLM calls, tool calling, prompts, and responses.&lt;/p&gt;
&lt;p&gt;Below is a demo project built with Spring Boot and Spring AI: an HTTP request invokes an LLM, &lt;code&gt;WeatherTools&lt;/code&gt; is used when needed to fetch weather data from the internet, and traces, prompts, and responses are sent to Langfuse through OpenTelemetry and &lt;code&gt;CallAdvisor&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://langfuse.com/&quot;&gt;Langfuse&lt;/a&gt; is a platform for monitoring and evaluating AI applications. The experience is very similar to LangSmith: you can trace LLM calls, inspect inputs and outputs, track latency and cost, and compare prompts and models. Langfuse also has an open-source version and can be self-hosted, which makes it a good choice for teams that need more control over their data and infrastructure.&lt;/p&gt;
&lt;p&gt;The main dependencies in &lt;code&gt;build.gradle&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-gradle&quot;&gt;dependencyManagement {
    imports {
        mavenBom 'org.springframework.ai:spring-ai-bom:2.0.0'
    }
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web:4.1.0'
    implementation 'org.springframework.boot:spring-boot-starter-opentelemetry:4.1.0'
    implementation 'org.springframework.ai:spring-ai-starter-model-openai:2.0.0'

    compileOnly 'io.opentelemetry:opentelemetry-exporter-otlp:1.62.0'

    testImplementation 'org.springframework.boot:spring-boot-starter-test:4.1.0'
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;application-configuration&quot;&gt;Application Configuration&lt;/h3&gt;
&lt;p&gt;In &lt;code&gt;application.properties&lt;/code&gt;, configure OpenAI, Spring AI observability, and the Langfuse OTLP endpoint:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-properties&quot;&gt;spring.ai.openai.api-key=${OPENAI_API_KEY}
spring.ai.openai.chat.options.model=gpt-5
spring.ai.tools.observations.include-content=true
spring.ai.chat.client.observations.log-prompt=true
spring.ai.chat.client.observations.log-completion=true
spring.ai.chat.observations.log-prompt=true
spring.ai.chat.observations.log-completion=true

management.otlp.metrics.export.enabled=false
management.tracing.sampling.probability=1.0
management.tracing.export.otlp.enabled=${LANGFUSE_TRACING_ENABLED:false}
management.opentelemetry.tracing.export.otlp.endpoint=${LANGFUSE_OTLP_ENDPOINT:http://localhost:3000/api/public/otel}
management.opentelemetry.tracing.export.otlp.transport=http

langfuse.public-key=${LANGFUSE_PUBLIC_KEY:}
langfuse.secret-key=${LANGFUSE_SECRET_KEY:}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Since Langfuse is used for tracing, it is better to disable OTLP metric export. Otherwise, the application may try to send metrics to the same endpoint and get a &lt;code&gt;ConnectException&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-properties&quot;&gt;management.otlp.metrics.export.enabled=false
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;controller&quot;&gt;Controller&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;LangfusePromptTracingAdvisor&lt;/code&gt; adds the prompt and response to the Langfuse trace.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@RestController
class HelloAiController {

    private final ChatClient chatClient;
    private final WeatherTools weatherTools;

    HelloAiController(
            ChatClient.Builder chatClientBuilder,
            WeatherTools weatherTools,
            LangfusePromptTracingAdvisor langfusePromptTracingAdvisor
    ) {
        this.chatClient = chatClientBuilder
                .defaultAdvisors(langfusePromptTracingAdvisor)
                .build();

        this.weatherTools = weatherTools;
    }

    @GetMapping(&quot;/ai&quot;)
    String ai(
            @RequestParam(defaultValue = &quot;Say hello in one short sentence&quot;)
            String message
    ) {
        return this.chatClient.prompt()
                .user(message)
                .tools(this.weatherTools)
                .call()
                .content();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;tools&quot;&gt;Tools&lt;/h3&gt;
&lt;p&gt;The tool uses Open-Meteo to find the coordinates of a city and retrieve the current weather for those coordinates.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Component
class WeatherTools {

    private final RestClient restClient;

    WeatherTools(RestClient.Builder restClientBuilder) {
        this.restClient = restClientBuilder.build();
    }

    @Tool(description = &quot;Get current weather for a city using live internet weather data&quot;)
    String getCurrentWeather(
            @ToolParam(description = &quot;City name, for example Berlin or New York&quot;)
            String city
    ) {
        GeocodingResponse geocoding = this.restClient.get()
                .uri(
                        &quot;https://geocoding-api.open-meteo.com/v1/search&quot;
                                + &quot;?name={city}&amp;amp;count=1&amp;amp;language=en&amp;amp;format=json&quot;,
                        city
                )
                .retrieve()
                .body(GeocodingResponse.class);

        if (geocoding == null
                || geocoding.results() == null
                || geocoding.results().isEmpty()) {
            return &quot;Weather is unavailable: city not found: &quot; + city;
        }

        Location location = geocoding.results().getFirst();

        ForecastResponse forecast = this.restClient.get()
                .uri(
                        &quot;https://api.open-meteo.com/v1/forecast&quot;
                                + &quot;?latitude={latitude}&quot;
                                + &quot;&amp;amp;longitude={longitude}&quot;
                                + &quot;&amp;amp;current=temperature_2m,relative_humidity_2m,&quot;
                                + &quot;wind_speed_10m,weather_code&quot;,
                        location.latitude(),
                        location.longitude()
                )
                .retrieve()
                .body(ForecastResponse.class);

        if (forecast == null || forecast.current() == null) {
            return &quot;Weather is unavailable for &quot; + location.name();
        }

        CurrentWeather current = forecast.current();

        return &quot;Current weather in %s, %s: %.1f C, humidity %d%%, &quot;
                + &quot;wind %.1f km/h, weather code %d.&quot;
                .formatted(
                        location.name(),
                        location.country(),
                        current.temperature2m(),
                        current.relativeHumidity2m(),
                        current.windSpeed10m(),
                        current.weatherCode()
                );
    }

    private record GeocodingResponse(List&amp;lt;Location&amp;gt; results) {
    }

    private record Location(
            String name,
            String country,
            double latitude,
            double longitude
    ) {
    }

    private record ForecastResponse(CurrentWeather current) {
    }

    private record CurrentWeather(
            @tools.jackson.annotation.JsonProperty(&quot;temperature_2m&quot;)
            double temperature2m(),

            @tools.jackson.annotation.JsonProperty(&quot;relative_humidity_2m&quot;)
            int relativeHumidity2m(),

            @tools.jackson.annotation.JsonProperty(&quot;wind_speed_10m&quot;)
            double windSpeed10m(),

            @tools.jackson.annotation.JsonProperty(&quot;weather_code&quot;)
            int weatherCode()
    ) {
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;sending-traces-to-langfuse-through-opentelemetry&quot;&gt;Sending Traces to Langfuse Through OpenTelemetry&lt;/h3&gt;
&lt;p&gt;Spring Boot sends traces through the &lt;code&gt;spring-boot-starter-opentelemetry&lt;/code&gt; dependency. Langfuse requires Basic Authentication using the public and secret keys. The required environment variables are:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;export LANGFUSE_TRACING_ENABLED=true
export LANGFUSE_PUBLIC_KEY=pk-lf-...
export LANGFUSE_SECRET_KEY=sk-lf-...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A separate configuration adds the required HTTP headers:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Configuration
class LangfuseTracingConfig {

    @Bean
    OtlpHttpSpanExporterBuilderCustomizer langfuseOtlpHeaders(
            @Value(&quot;${langfuse.public-key:}&quot;) String publicKey,
            @Value(&quot;${langfuse.secret-key:}&quot;) String secretKey
    ) {
        return builder -&amp;gt; {
            if (!StringUtils.hasText(publicKey)
                    || !StringUtils.hasText(secretKey)) {
                return;
            }

            String token = Base64.getEncoder()
                    .encodeToString(
                            (publicKey + &quot;:&quot; + secretKey)
                                    .getBytes(StandardCharsets.UTF_8)
                    );

            builder.addHeader(&quot;Authorization&quot;, &quot;Basic &quot; + token);
            builder.addHeader(&quot;x-langfuse-ingestion-version&quot;, &quot;4&quot;);
        };
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;adding-prompts-to-langfuse&quot;&gt;Adding Prompts to Langfuse&lt;/h3&gt;
&lt;p&gt;Spring AI does not put the prompt and completion into the Langfuse &lt;code&gt;input&lt;/code&gt; and &lt;code&gt;output&lt;/code&gt; fields in the required format. To solve this, we use the standard Spring AI &lt;code&gt;CallAdvisor&lt;/code&gt; mechanism.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Component
class LangfusePromptTracingAdvisor implements CallAdvisor {

    private final ObjectMapper objectMapper = new ObjectMapper();

    @Override
    public ChatClientResponse adviseCall(
            ChatClientRequest chatClientRequest,
            CallAdvisorChain callAdvisorChain
    ) {
        var span = Span.current();

        String input = serializePrompt(chatClientRequest);

        span.setAttribute(&quot;langfuse.observation.type&quot;, &quot;generation&quot;);
        span.setAttribute(&quot;input.value&quot;, input);
        span.setAttribute(&quot;langfuse.observation.input&quot;, input);

        ChatClientResponse chatClientResponse =
                callAdvisorChain.nextCall(chatClientRequest);

        String output =
                serializeResponse(chatClientResponse.chatResponse());

        span.setAttribute(&quot;output.value&quot;, output);
        span.setAttribute(&quot;langfuse.observation.output&quot;, output);

        return chatClientResponse;
    }

    @Override
    public String getName() {
        return &quot;Langfuse Prompt Tracing Advisor&quot;;
    }

    @Override
    public int getOrder() {
        return ToolCallingAdvisor.DEFAULT_ORDER + 100;
    }

    private String serializePrompt(ChatClientRequest request) {
        var messages = request.prompt()
                .getInstructions()
                .stream()
                .map(message -&amp;gt; Map.of(
                        &quot;role&quot;, message.getMessageType().getValue(),
                        &quot;content&quot;, ofNullable(message.getText()).orElse(&quot;&quot;)))
                .toList();
        return toJson(messages);
    }

    private String serializeResponse(ChatResponse response) {
        return ofNullable(response).map(ChatResponse::getResult)
                .map(Generation::getOutput)
                .map(output -&amp;gt; toJson(

                        Map.of(&quot;role&quot;, output.getMessageType().getValue(),
                                &quot;content&quot;, ofNullable(output.getText()).orElse(&quot;&quot;),
                                &quot;hasToolCalls&quot;, response.hasToolCalls())

                ))
                .orElse(&quot;&quot;);
    }

    private String toJson(Object value) {
        try {
            return this.objectMapper.writeValueAsString(value);
        }
        catch (JsonProcessingException ex) {
            return String.valueOf(value);
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;According to the Spring AI documentation, &lt;code&gt;ToolCallingAdvisor&lt;/code&gt; manages the tool-calling loop itself, while &lt;code&gt;getOrder()&lt;/code&gt; determines the advisor's position in the chain. Setting it to &lt;code&gt;ToolCallingAdvisor.DEFAULT_ORDER + 100&lt;/code&gt; places our advisor inside the tool-calling loop, so it runs on every iteration and can observe the original request, the model response that decides to call a tool, the following request containing the tool result, and the final model response.&lt;/p&gt;
&lt;h3 id=&quot;what-you-will-see-in-langfuse&quot;&gt;What You Will See in Langfuse&lt;/h3&gt;
&lt;p&gt;Example request to the application:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;curl &quot;http://localhost:8080/ai?message=What%20is%20the%20weather%20in%20Berlin?&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Langfuse should display a trace with spans.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/ij8eavci8ks23o5mnpx7.png&quot; alt=&quot;Tool calling&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/asyt0mux9xnbx6xf3hgz.png&quot; alt=&quot;Prompt tracing&quot;&gt;&lt;/p&gt;
&lt;p&gt;Generation spans will contain attributes such as:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;langfuse.observation.type=generation
langfuse.observation.input=...
langfuse.observation.output=...
input.value=...
output.value=...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When the following option is enabled, Spring AI exports tool call arguments and results:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-properties&quot;&gt;spring.ai.tools.observations.include-content=true
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;production-considerations&quot;&gt;Production Considerations&lt;/h3&gt;
&lt;p&gt;Prompts, completions, and tool results may contain sensitive data. Traces are also typically stored longer than the application's runtime memory. For production, it is worth adding content truncation, sanitization, secret removal, and PII filtering.&lt;/p&gt;
&lt;p&gt;Combining Spring AI Advisors with Langfuse through OTLP provides transparent tracing of LLM calls, prompts, responses, tool calls, and intermediate steps.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/28396/spring-boot-4-spring-ai-langfuse-tracing-llm-calls-and-tool-calling</guid>
<pubDate>Wed, 23 Sep 2026 21:53:21 +0000</pubDate>
</item>
<item>
<title>Android CI/CD for Beginners — Part 1: Introduction</title>
<link>https://coderlegion.com/28201/android-ci-cd-for-beginners-part-1-introduction</link>
<description>&lt;p&gt;Stop building everything manually. Learn how modern Android teams automate their development workflow&lt;/p&gt;
&lt;p&gt;This series is designed to provide a complete understanding of CI/CD for Android development. In Part 1, we’ll introduce the fundamentals of CI/CD, explore what it is, why it matters, and how it’s used in Android app development.&lt;/p&gt;
&lt;p&gt;CI/CD (Continuous Integration and Continuous Delivery/Deployment) is a set of practices that automate repetitive tasks in the software development process, such as building, testing, and releasing applications.&lt;/p&gt;
&lt;p&gt;Instead of manually performing these tasks every time you make a change, a CI/CD pipeline executes them automatically whenever predefined events occur, such as pushing code to a Git repository.&lt;/p&gt;
&lt;p&gt;How can CI/CD automate tasks in Android development?&lt;br&gt;
CI/CD automates many of the repetitive tasks involved in the Android development lifecycle. Instead of running these steps manually every time you make a change, they can be executed automatically whenever code is pushed to your repository.&lt;/p&gt;
&lt;p&gt;With a CI/CD pipeline, your Android app can be automatically:&lt;/p&gt;
&lt;p&gt;Built using Gradle.&lt;br&gt;
Analyzed for code quality with Android Lint.&lt;br&gt;
Tested by running unit and instrumentation tests.&lt;br&gt;
Packaged into a signed or unsigned APK or Android App Bundle (AAB).&lt;br&gt;
Distributed to testers or prepared for release to the Google Play Store.&lt;br&gt;
You might be wondering: Why do we need to automate these tasks?&lt;/p&gt;
&lt;p&gt;In real-world Android projects, multiple developers often work on the same codebase. Every new feature, bug fix, or improvement is usually submitted as a pull request (PR).&lt;/p&gt;
&lt;p&gt;Before those changes are merged, the team needs to ensure they meet the project’s quality standards and don’t introduce bugs or break existing functionality.&lt;/p&gt;
&lt;p&gt;Manually building the app, running lint checks, executing tests, and verifying every pull request can quickly become repetitive and time-consuming, especially as the project grows.&lt;/p&gt;
&lt;p&gt;This is where CI/CD becomes valuable. Whenever a pull request is opened or updated, a CI pipeline can automatically build the app, run lint checks, execute unit and instrumentation tests, and report whether all checks have passed.&lt;/p&gt;
&lt;p&gt;If any step fails, developers are notified immediately, allowing issues to be fixed before the changes are merged.&lt;/p&gt;
&lt;p&gt;Once all the automated checks succeed, the CD pipeline can take over by generating an APK or App Bundle and distributing it to testers or preparing it for release.&lt;/p&gt;
&lt;p&gt;This eliminates many manual steps, speeds up the development process, and helps maintain a stable and reliable codebase.&lt;/p&gt;
&lt;p&gt;Common CI/CD Terminology&lt;br&gt;
Before we start building our first CI/CD pipeline, it’s important to understand some common terms you’ll encounter throughout this series. These concepts are not exclusive to Android development, they’re used across many CI/CD platforms. We’ll explore how these ideas come together in practice in the upcoming sections.&lt;/p&gt;
&lt;p&gt;Pipeline&lt;br&gt;
A pipeline is the complete automated process that runs after a specific event, such as pushing code to a Git repository or opening a pull request. A pipeline can build your app, run tests, generate an APK, and even distribute it to testers — all without manual intervention.&lt;/p&gt;
&lt;p&gt;Think of a pipeline as a sequence of automated tasks executed in a predefined order.&lt;/p&gt;
&lt;p&gt;Workflow&lt;br&gt;
In GitHub Actions, a pipeline is defined using a workflow. A workflow is a YAML (.yml or .yaml) file stored inside the .github/workflows directory of your project.&lt;/p&gt;
&lt;p&gt;It describes what should happen, when it should happen, and how it should be executed.&lt;/p&gt;
&lt;p&gt;A repository can contain multiple workflows, each responsible for different tasks. For example, one workflow may run tests for every pull request, while another creates a release build whenever a new version is published.&lt;/p&gt;
&lt;p&gt;Event (Trigger)&lt;br&gt;
A workflow doesn’t run on its own — it needs an event, also known as a trigger.&lt;/p&gt;
&lt;p&gt;Common triggers include:&lt;/p&gt;
&lt;p&gt;Pushing code to a branch (push)&lt;br&gt;
Opening or updating a pull request (pull_request)&lt;br&gt;
Manually starting a workflow (workflow_dispatch)&lt;br&gt;
Creating a release (release)&lt;br&gt;
Whenever one of these events occurs, GitHub Actions starts the corresponding workflow.&lt;/p&gt;
&lt;p&gt;Runner&lt;br&gt;
A runner is the machine responsible for executing your workflow.&lt;/p&gt;
&lt;p&gt;GitHub provides hosted runners running Linux, Windows, and macOS. For Android projects, Linux runners are commonly used because they provide a fast and cost-effective environment for building Android applications.&lt;/p&gt;
&lt;p&gt;You can also configure your own self-hosted runner if you need custom hardware or software.&lt;/p&gt;
&lt;p&gt;Job&lt;br&gt;
A job is a collection of related tasks executed on the same runner.&lt;/p&gt;
&lt;p&gt;For example, one job might build the Android app, while another runs unit tests. Multiple jobs can run sequentially or in parallel, depending on how the workflow is configured.&lt;/p&gt;
&lt;p&gt;Step&lt;br&gt;
A step is a single task inside a job.&lt;/p&gt;
&lt;p&gt;Examples of steps include:&lt;/p&gt;
&lt;p&gt;Checking out the repository&lt;br&gt;
Setting up Java&lt;br&gt;
Installing Android dependencies&lt;br&gt;
Running Gradle commands&lt;br&gt;
Uploading the generated APK&lt;br&gt;
A job is simply a sequence of steps executed one after another.&lt;/p&gt;
&lt;p&gt;Action&lt;br&gt;
An Action is a reusable piece of automation that performs a specific task.&lt;/p&gt;
&lt;p&gt;Instead of writing everything from scratch, you can use actions created by GitHub or the community.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;p&gt;actions/checkout downloads your repository onto the runner.&lt;br&gt;
actions/setup-java installs and configures Java.&lt;br&gt;
actions/upload-artifact uploads build outputs such as APKs or test reports.&lt;br&gt;
Actions make workflows shorter, cleaner, and easier to maintain.&lt;/p&gt;
&lt;p&gt;Artifact&lt;br&gt;
An artifact is a file generated during a workflow that you want to keep after the workflow finishes.&lt;/p&gt;
&lt;p&gt;In Android projects, common artifacts include:&lt;/p&gt;
&lt;p&gt;APK files&lt;br&gt;
Android App Bundles (AAB)&lt;br&gt;
Lint reports&lt;br&gt;
Unit test reports&lt;br&gt;
Code coverage reports&lt;br&gt;
Artifacts can be downloaded later for testing, debugging, or distribution.&lt;/p&gt;
&lt;p&gt;GitHub Actions&lt;br&gt;
Throughout this series, we’ll use GitHub Actions as our CI/CD platform.&lt;/p&gt;
&lt;p&gt;GitHub Actions is GitHub’s built-in automation service that allows you to create workflows using YAML files. It automatically responds to events in your repository, runs your workflows on runners, and reports the results all directly within GitHub.&lt;/p&gt;
&lt;p&gt;Now that you’re familiar with these core concepts, we’ll start creating our first Android CI workflow in the next part of this series.&lt;/p&gt;
&lt;p&gt;That all it for now…&lt;br&gt;
Lets stop here for now as this is already too much if your just starting out&lt;/p&gt;
&lt;p&gt;Now that you understand the purpose of CI/CD and the terminology behind it, it’s time to build your first pipeline.&lt;/p&gt;
&lt;p&gt;In Part 2, we’ll set up GitHub Actions, create our first workflow, and automatically build an Android application whenever code is pushed to GitHub.&lt;/p&gt;
&lt;p&gt;👋Bye!!!&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/28201/android-ci-cd-for-beginners-part-1-introduction</guid>
<pubDate>Tue, 22 Sep 2026 17:12:07 +0000</pubDate>
</item>
<item>
<title>Faker Doesn't Know Your Entities Are Related, So I Built Something That Does</title>
<link>https://coderlegion.com/28147/faker-doesnt-know-your-entities-are-related-so-i-built-something-that-does</link>
<description>&lt;h2 id=&quot;faker-doesn-t-know-your-entities-are-related-so-i-built-something-that-does&quot;&gt;Faker Doesn't Know Your Entities Are Related, So I Built Something That Does&lt;/h2&gt;
&lt;p&gt;You've added a second entity to the schema, wired up a &lt;code&gt;@ManyToOne&lt;/code&gt;, and gone back to your seed script to generate fifty more rows. Ninety seconds later, the app refuses to start: unique constraint violation, somewhere inside a loop you wrote three weeks ago at 11pm. You fix it. You restart. A different field breaks a different constraint.&lt;/p&gt;
&lt;p&gt;This is the exact moment every Spring Boot developer eventually meets the real limit of tools like Faker. They're brilliant at generating a name, an email, an address. They have no idea the &lt;code&gt;Payment&lt;/code&gt; sitting in front of them needs a &lt;code&gt;Counterparty&lt;/code&gt; to already exist.&lt;/p&gt;
&lt;p&gt;So you do what everyone does: hand-write the wiring. Create parents first. Hold onto their generated IDs. Wire them into children. Hope you didn't just violate a &lt;code&gt;@NotNull&lt;/code&gt; somewhere in the process. It works, for a while. Then the schema changes, and the script quietly stops matching reality until the next 3am debugging session finds out the hard way.&lt;/p&gt;
&lt;p&gt;I hit this enough times that I stopped patching the script and looked at the actual problem: the information needed to seed this correctly already exists. It's sitting right there in the entity, in the annotations you already wrote. &lt;code&gt;@ManyToOne&lt;/code&gt;, &lt;code&gt;@NotNull&lt;/code&gt;, &lt;code&gt;@Column(unique = true)&lt;/code&gt;, JPA already knows the shape of your data. Nothing should need to be told that twice.&lt;/p&gt;
&lt;p&gt;That became &lt;strong&gt;SynthForge&lt;/strong&gt;.&lt;/p&gt;
&lt;h3 id=&quot;the-core-idea&quot;&gt;The core idea&lt;/h3&gt;
&lt;p&gt;Instead of writing a script that generates data, you annotate the entity:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Entity @Seed(count = 50)
public class Counterparty { /* fields only */ }

@Entity @Seed(count = 200)
public class Payment {
    @ManyToOne(optional = false) private Counterparty counterparty;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Start the app in a dev profile. Both tables populate, correctly ordered, on every restart. No seed method. No calling code, anywhere. The entity is the seed script.&lt;/p&gt;
&lt;h3 id=&quot;what-s-actually-happening-underneath&quot;&gt;What's actually happening underneath&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Entity scanning.&lt;/strong&gt; SynthForge reads JPA-managed attributes through the &lt;code&gt;jakarta.persistence.metamodel.Metamodel&lt;/code&gt; API, not raw reflection. That distinction matters more than it sounds: only real persistent fields ever get touched, nothing that merely resembles one.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Relationship ordering.&lt;/strong&gt; From the owning-side &lt;code&gt;@ManyToOne&lt;/code&gt;/&lt;code&gt;@OneToOne&lt;/code&gt; relationships, it builds a dependency graph and topologically sorts it. Parent rows exist before a child is ever generated to reference them. This is precisely where hand-written scripts fail first: the moment a second level of nesting shows up, manual ID-tracking becomes its own source of bugs, the thing you're debugging instead of your actual feature.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Constraint-aware generation.&lt;/strong&gt; &lt;code&gt;@NotNull&lt;/code&gt;, &lt;code&gt;@Size&lt;/code&gt;, &lt;code&gt;@Email&lt;/code&gt;, and a set of field-name heuristics (&lt;code&gt;email&lt;/code&gt;, &lt;code&gt;iban&lt;/code&gt;, &lt;code&gt;amount&lt;/code&gt;, &lt;code&gt;country&lt;/code&gt;) drive realistic values through Datafaker. Hit a &lt;code&gt;@Column(unique = true)&lt;/code&gt; field, and instead of a constraint violation forty rows in, you get a bounded retry loop.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Idempotent restarts.&lt;/strong&gt; A table that already has rows gets skipped. Restart against a persistent database as many times as you want; nothing duplicates.&lt;/p&gt;
&lt;h3 id=&quot;-isn-t-this-just-instancio-&quot;&gt;&quot;Isn't this just Instancio?&quot;&lt;/h3&gt;
&lt;p&gt;Fair question, and worth answering directly rather than dodging it. Instancio, and its &lt;code&gt;instancio-jpa&lt;/code&gt; extension, gives you an API to call from inside a test: build a graph, persist it, for that one test. It's genuinely good at that job.&lt;/p&gt;
&lt;p&gt;SynthForge isn't called from anywhere. Annotate the entity, start the app in an enabled profile, and the database is already populated before your code runs a single line. Different tool, because it solves a different moment: application startup, not a single test method.&lt;/p&gt;
&lt;h3 id=&quot;where-it-stands&quot;&gt;Where it stands&lt;/h3&gt;
&lt;p&gt;Live on Maven Central:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;io.github.thembatman0&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;synthforge-spring&amp;lt;/artifactId&amp;gt;
    &amp;lt;version&amp;gt;0.1.0&amp;lt;/version&amp;gt;
&amp;lt;/dependency&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Repo: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/ThembaTman0/synthforge&quot;&gt;github.com/ThembaTman0/synthforge&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;It won't fix your architecture and it won't make decisions for you. But the next time you add a foreign key at 11pm, there's a decent chance it means you get to go to bed instead.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/28147/faker-doesnt-know-your-entities-are-related-so-i-built-something-that-does</guid>
<pubDate>Tue, 22 Sep 2026 11:22:33 +0000</pubDate>
</item>
<item>
<title>Why your Java 21 Virtual Threads might freeze under load (and how to spot pinning)</title>
<link>https://coderlegion.com/28108/why-your-java-21-virtual-threads-might-freeze-under-load-and-how-to-spot-pinning</link>
<description>&lt;p&gt;A lot of teams migrated to Java 21 and flipped spring.threads.virtual.enabled=true, expecting instant scaling without the reactive boilerplate.&lt;/p&gt;
&lt;p&gt;And for basic I/O, it works great. But once high concurrency hits, some services start seeing unexplained HTTP timeouts and latency spikes, even though CPU and memory metrics look totally calm.&lt;/p&gt;
&lt;p&gt;The culprit is usually Carrier Thread Pinning.&lt;/p&gt;
&lt;p&gt;Project Loom mounts virtual threads onto underlying OS platform threads (Carrier Threads, managed by an internal ForkJoinPool). When a virtual thread hits a blocking network call, Loom unmounts it so the carrier can pick up other work.&lt;/p&gt;
&lt;p&gt;Where it breaks: If a virtual thread enters a synchronized block or method and blocks inside on I/O, the JVM cannot unmount it. The virtual thread stays &quot;pinned&quot; to its carrier thread. If your server has 8 cores, 8 pinned threads waiting on slow external APIs will freeze your entire carrier pool—even if you have 50,000 other virtual threads ready to run.&lt;/p&gt;
&lt;p&gt;Quick mitigation:&lt;/p&gt;
&lt;p&gt;Replace synchronized with ReentrantLock around any blocking I/O (j.u.c primitives support clean unmounting).&lt;/p&gt;
&lt;p&gt;Test locally with -Djdk.tracePinnedThreads=short to dump offending methods to stdout.&lt;/p&gt;
&lt;p&gt;Keep an eye on third-party JDBC drivers and legacy SDKs that still use monitor locks on socket reads.&lt;/p&gt;
&lt;p&gt;I wrote an open-source diagnostics tool and Spring Boot Actuator endpoint to track pinning ratios and pool saturation in real time (LoomDoctor), and put together an interactive simulator if you want to test how carrier starvation happens under load: &lt;a rel=&quot;nofollow&quot; href=&quot;https://frodygr.github.io/playground/#loomdoctor&quot;&gt;https://frodygr.github.io/playground/#loomdoctor&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Have you hit carrier pinning in your Java 21 deployments yet? How did your team catch it?&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/28108/why-your-java-21-virtual-threads-might-freeze-under-load-and-how-to-spot-pinning</guid>
<pubDate>Tue, 22 Sep 2026 07:31:36 +0000</pubDate>
</item>
<item>
<title>Spring Boot Authentication Boilerplate: From JWT Basics to Production-Ready Auth</title>
<link>https://coderlegion.com/27856/spring-boot-authentication-boilerplate-from-jwt-basics-to-production-ready-auth</link>
<description>&lt;p&gt;Starting a new Spring Boot project usually feels great.&lt;/p&gt;
&lt;p&gt;You create the project, add a few dependencies, write your first controller...&lt;/p&gt;
&lt;p&gt;Then authentication enters the chat.&lt;/p&gt;
&lt;p&gt;Suddenly you're configuring:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Spring Security&lt;/li&gt;
&lt;li&gt;JWT access tokens&lt;/li&gt;
&lt;li&gt;refresh tokens&lt;/li&gt;
&lt;li&gt;roles and permissions&lt;/li&gt;
&lt;li&gt;password hashing&lt;/li&gt;
&lt;li&gt;exception handling&lt;/li&gt;
&lt;li&gt;database migrations&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Three hours later, your original feature still has exactly &lt;strong&gt;zero lines of code&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Congratulations. You built authentication again.&lt;/p&gt;
&lt;h3 id=&quot;the-auth-code-isn-t-the-interesting-part&quot;&gt;The auth code isn't the interesting part&lt;/h3&gt;
&lt;p&gt;Most applications need roughly the same authentication foundation.&lt;/p&gt;
&lt;p&gt;Register a user.&lt;/p&gt;
&lt;p&gt;Login.&lt;/p&gt;
&lt;p&gt;Return an access token.&lt;/p&gt;
&lt;p&gt;Refresh it safely.&lt;/p&gt;
&lt;p&gt;Protect endpoints.&lt;/p&gt;
&lt;p&gt;Check roles.&lt;/p&gt;
&lt;p&gt;Logout.&lt;/p&gt;
&lt;p&gt;Maybe add passkeys, OAuth, account recovery, or multiple sessions later.&lt;/p&gt;
&lt;p&gt;None of this is particularly unique to your SaaS, internal tool, API, or side project.&lt;/p&gt;
&lt;p&gt;But we keep rebuilding it.&lt;/p&gt;
&lt;p&gt;I was doing the same thing with my Spring Boot projects, so I eventually turned that repeated setup into &lt;strong&gt;AuthKit&lt;/strong&gt;.&lt;/p&gt;
&lt;h3 id=&quot;authkit-lite&quot;&gt;AuthKit Lite&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;AuthKit Lite&lt;/strong&gt; is the free and open-source version.&lt;/p&gt;
&lt;p&gt;It gives you a working Spring Boot authentication foundation with:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;JWT access tokens&lt;/li&gt;
&lt;li&gt;rotating refresh tokens&lt;/li&gt;
&lt;li&gt;Spring Security&lt;/li&gt;
&lt;li&gt;role-based authorization&lt;/li&gt;
&lt;li&gt;BCrypt passwords&lt;/li&gt;
&lt;li&gt;optional WebAuthn passkeys&lt;/li&gt;
&lt;li&gt;Flyway migrations&lt;/li&gt;
&lt;li&gt;H2 for instant local testing&lt;/li&gt;
&lt;li&gt;MySQL support&lt;/li&gt;
&lt;li&gt;integration tests&lt;/li&gt;
&lt;li&gt;a browser-based API testing page&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The goal is simple:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Clone → run → test the APIs → start building your application.&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git clone https://github.com/buildbasekit/AuthKit-Lite.git
cd AuthKit-Lite
./mvnw spring-boot:run
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then open:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;http://localhost:8080/api-test
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;No Docker ceremony.&lt;/p&gt;
&lt;p&gt;No spending your evening creating JWT filters for the 17th time.&lt;/p&gt;
&lt;p&gt;No pretending you'll &quot;clean up the auth code later.&quot;&lt;/p&gt;
&lt;p&gt;You can find AuthKit Lite here:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://buildbasekit.com/boilerplates/authkit-lite/&quot;&gt;https://buildbasekit.com/boilerplates/authkit-lite/&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;when-lite-isn-t-enough&quot;&gt;When Lite isn't enough&lt;/h3&gt;
&lt;p&gt;Basic authentication is enough for many projects.&lt;/p&gt;
&lt;p&gt;But production applications often grow into things like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;email verification&lt;/li&gt;
&lt;li&gt;password recovery&lt;/li&gt;
&lt;li&gt;multiple user sessions&lt;/li&gt;
&lt;li&gt;session revocation&lt;/li&gt;
&lt;li&gt;refresh-token replay protection&lt;/li&gt;
&lt;li&gt;OAuth2 / OIDC&lt;/li&gt;
&lt;li&gt;account linking&lt;/li&gt;
&lt;li&gt;magic links&lt;/li&gt;
&lt;li&gt;passkeys&lt;/li&gt;
&lt;li&gt;account lifecycle handling&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That is where &lt;strong&gt;AuthKit Pro&lt;/strong&gt; comes in.&lt;/p&gt;
&lt;p&gt;It takes the same idea further: provide the authentication foundation while keeping the application-specific business logic in your application.&lt;/p&gt;
&lt;p&gt;The important part for me was avoiding a giant framework that tries to own the entire application.&lt;/p&gt;
&lt;p&gt;Auth should be infrastructure.&lt;/p&gt;
&lt;p&gt;Not your new hobby.&lt;/p&gt;
&lt;p&gt;AuthKit Pro is here:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://buildbasekit.com/boilerplates/authkit-pro&quot;&gt;https://buildbasekit.com/boilerplates/authkit-pro&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;lite-or-pro-&quot;&gt;Lite or Pro?&lt;/h3&gt;
&lt;p&gt;The easiest way to think about it:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;AuthKit Lite&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Use it when you need a clean Spring Boot authentication starting point or want to understand and extend the implementation yourself.&lt;/p&gt;
&lt;p&gt;It's free and open source.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;AuthKit Pro&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Use it when the project needs more complete authentication flows and production-focused session and account lifecycle features.&lt;/p&gt;
&lt;p&gt;Both are source-code foundations.&lt;/p&gt;
&lt;p&gt;You still own the application.&lt;/p&gt;
&lt;p&gt;You can change the architecture.&lt;/p&gt;
&lt;p&gt;You can remove features.&lt;/p&gt;
&lt;p&gt;You can add your own authorization rules.&lt;/p&gt;
&lt;p&gt;And your business logic doesn't have to live inside somebody else's authentication service.&lt;/p&gt;
&lt;h3 id=&quot;why-a-boilerplate-&quot;&gt;Why a boilerplate?&lt;/h3&gt;
&lt;p&gt;I'm not trying to eliminate authentication decisions.&lt;/p&gt;
&lt;p&gt;You still need to decide things like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;token lifetimes&lt;/li&gt;
&lt;li&gt;authorization rules&lt;/li&gt;
&lt;li&gt;production secrets&lt;/li&gt;
&lt;li&gt;rate limiting&lt;/li&gt;
&lt;li&gt;monitoring&lt;/li&gt;
&lt;li&gt;application-specific permissions&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A boilerplate just means you don't have to start those decisions from an empty &lt;code&gt;SecurityConfig.java&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;That's the part I wanted to solve.&lt;/p&gt;
&lt;p&gt;Because there are much more interesting bugs waiting for us in the actual product.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;AuthKit Lite — Free &amp;amp; Open Source&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://buildbasekit.com/boilerplates/authkit-lite/&quot;&gt;https://buildbasekit.com/boilerplates/authkit-lite/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;AuthKit Pro — Production-Focused&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://buildbasekit.com/boilerplates/authkit-pro&quot;&gt;https://buildbasekit.com/boilerplates/authkit-pro&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Built with Spring Boot and Spring Security.&lt;/p&gt;
&lt;p&gt;If you're building Spring Boot APIs, I'd also be interested to know:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Do you rebuild authentication for every project, maintain your own starter, or use an external identity provider?&lt;/strong&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/27856/spring-boot-authentication-boilerplate-from-jwt-basics-to-production-ready-auth</guid>
<pubDate>Sun, 20 Sep 2026 16:50:00 +0000</pubDate>
</item>
<item>
<title>LangGraph4j 1.9.0: What's new and the roadmap</title>
<link>https://coderlegion.com/27744/langgraph4j-1-9-0-whats-new-and-the-roadmap</link>
<description>&lt;h3 id=&quot;langgraph4j-1-9-0-is-out-&quot;&gt;LangGraph4j 1.9.0 is out!&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/langgraph4j/langgraph4j&quot;&gt;LangGraph4j&lt;/a&gt; 1.9.0 is out!&lt;/strong&gt; With the &lt;code&gt;1.8.x&lt;/code&gt; release stream now in LTS, development moves forward on &lt;code&gt;1.9&lt;/code&gt; with improvements to streaming, persistence, and the infrastructure for building agents in Java.&lt;/p&gt;
&lt;p&gt;This release is an important step toward workflows that are easier to follow while they run and easier to inspect after they finish. A node can now report progress output before returning its result, checkpoint savers have a richer execution lifecycle, and Studio gets an UI refresh.&lt;/p&gt;
&lt;p&gt;Let's look at what changes for an application built on &lt;code&gt;1.8.x&lt;/code&gt;, with particular attention to &lt;strong&gt;custom output from nodes&lt;/strong&gt; and &lt;strong&gt;checkpoint management&lt;/strong&gt;. The &lt;a rel=&quot;nofollow&quot; href=&quot;https://langgraph4j.github.io/langgraph4j/1.9/whats-new-v1.9&quot;&gt;official migration guide&lt;/a&gt; provides details API reference for the changes discussed here.&lt;/p&gt;
&lt;h3 id=&quot;emit-custom-output-while-a-node-is-running&quot;&gt;Emit custom output while a node is running&lt;/h3&gt;
&lt;p&gt;Imagine a node that retrieves documents, processes them, and prepares a response. The caller may want to show progress throughout that work. Waiting for the final node output gives the user minimal information about what is happening in the meantime.&lt;/p&gt;
&lt;p&gt;In &lt;code&gt;1.9&lt;/code&gt;, a node action can emit its own typed &lt;code&gt;NodeOutput&lt;/code&gt; values through the active graph execution stream (&lt;code&gt;graph.stream(...)&lt;/code&gt;). They arrive alongside the usual START, node, and END outputs, before the node has necessarily finished.&lt;/p&gt;
&lt;p&gt;The mechanism is straightforward:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Define a subclass of &lt;code&gt;NodeOutput&lt;/code&gt; carrying the information you want to expose.&lt;/li&gt;
&lt;li&gt;Obtain a dispatcher from the node's &lt;code&gt;RunnableConfig&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Dispatch custom outputs during execution.&lt;/li&gt;
&lt;li&gt;Handle those types when consuming the graph stream.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Here is a small example.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public final class ProgressOutput extends NodeOutput&amp;lt;AgentState&amp;gt; {
    private final String message;

    public ProgressOutput(String node, AgentState state, String message) {
        super(node, state);
        this.message = message;
    }

    public String message() {
        return message;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Inside the node, the dispatcher publishes a message while the returned map still provides the normal state update:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;AsyncNodeActionWithConfig&amp;lt;AgentState&amp;gt; process = (state, config) -&amp;gt; {
    var dispatcher = config.&amp;lt;AgentState, ProgressOutput&amp;gt;customDispatcher();

    dispatcher.dispatchAsync(
        new ProgressOutput(config.nodeId(), state, &quot;Processing documents&quot;));

    // Perform the node's work here.

    return completedFuture(Map.of(&quot;result&quot;, &quot;Documents processed&quot;));
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once this action is registered in a compiled graph, the caller can recognize its progress messages:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;graph.stream(GraphInput.noArgs(), RunnableConfig.empty()).forEachAsync( output -&amp;gt; {
        if (output instanceof ProgressOutput progress) {
            System.out.println(progress.message());
        } else {
            System.out.println(&quot;Graph step: &quot; + output.node());
        }
    });
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This gives us a useful channel for progress notifications, intermediate results, and application-specific events. &lt;strong&gt;Dispatching a custom output does not update graph state.&lt;/strong&gt; If a value must influence routing, be available to later nodes, or become part of a persisted result, include it in the node's returned state update.&lt;/p&gt;
&lt;p&gt;There are two dispatch options. &lt;code&gt;dispatchSync(...)&lt;/code&gt; waits until the stream accepts the value and can throw &lt;code&gt;InterruptedException&lt;/code&gt;; &lt;code&gt;dispatchAsync(...)&lt;/code&gt; submits without waiting. The dispatcher is available during node execution in an active graph stream, including the supported hook context. Keep its use within that lifecycle: accessing it outside has an unpredicable result.&lt;/p&gt;
&lt;p&gt;Custom events are intended for &lt;code&gt;graph.stream(...)&lt;/code&gt; consumers; they do not change the result returned by &lt;code&gt;graph.invoke(...)&lt;/code&gt;. See the &lt;a rel=&quot;nofollow&quot; href=&quot;https://langgraph4j.github.io/langgraph4j/1.9/core/emit-custom-output/&quot;&gt;custom-output tutorial&lt;/a&gt; for the complete contract and a &lt;strong&gt;hook-based&lt;/strong&gt; example.&lt;/p&gt;
&lt;h4 id=&quot;the-streaming-engine-behind-it&quot;&gt;The streaming engine behind it&lt;/h4&gt;
&lt;p&gt;This capability comes with a refactoring of the internal streaming engine around &lt;code&gt;AsyncGeneratorFlow&lt;/code&gt; from &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/bsorrentino/java-async-generator&quot;&gt;&lt;code&gt;async-generator 5.0&lt;/code&gt;&lt;/a&gt; project.&lt;/p&gt;
&lt;p&gt;Ordinary iteration over &lt;code&gt;stream()&lt;/code&gt; remains source-compatible. Applications extending directly streaming generators, supplying a &lt;code&gt;BlockingQueue&lt;/code&gt;, or depending on &lt;code&gt;AsyncGenerator.WithResult&lt;/code&gt; need migration work. &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; 👀&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;This refactoring is a groundwork for Reactor Flow support in a future LangGraph4j &lt;code&gt;2.0&lt;/code&gt;; that support is a future direction.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;checkpoint-savers-a-richer-execution-lifecycle&quot;&gt;Checkpoint Savers: A richer execution lifecycle&lt;/h3&gt;
&lt;p&gt;Checkpoint persistence is central to long-running agents and Human-in-the-Loop workflows. In &lt;code&gt;1.9&lt;/code&gt;, the changes cover what happens when execution finishes, how released runs can be retrieved, how subgraph savers participate, and how interruptions and failures are recorded. These are the &lt;a rel=&quot;nofollow&quot; href=&quot;https://langgraph4j.github.io/langgraph4j/1.9/whats-new-v1.9&quot;&gt;checkpoint changes&lt;/a&gt; I would review first when upgrading an existing application.&lt;/p&gt;
&lt;h4 id=&quot;completed-runs-release-their-thread-by-default&quot;&gt;Completed runs release their thread by default&lt;/h4&gt;
&lt;p&gt;With a checkpoint saver configured, &lt;strong&gt;a graph that completes normally now releases its active thread automatically&lt;/strong&gt;. Depending on the saver, release archives or tags its checkpoints and removes the active checkpoint set.&lt;/p&gt;
&lt;p&gt;This matters if your application completes a graph, updates the same active thread's state, and executes it again. To retain the previous lifecycle, explicitly disable automatic release when compiling:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;var compileConfig = CompileConfig.builder()
    .checkpointSaver(saver)
    .releaseThread(false) // backward compatible option
    .build();

var graph = workflow.compile(compileConfig);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;An interruption or an exception does not automatically release the thread.&lt;/strong&gt; Interrupted runs remain available for Human-in-the-Loop continuation, and failed runs remain available for the application's recovery strategy. &lt;code&gt;graph.updateState(...)&lt;/code&gt; continues to work in those situations as before.&lt;/p&gt;
&lt;p&gt;The practical upgrade check is to examine what your code does &lt;em&gt;after successful completion&lt;/em&gt;. Any inspection, replay, resume, or manual-release logic that assumes an active checkpoint set needs attention.&lt;/p&gt;
&lt;h4 id=&quot;experimental-versioning-for-released-runs&quot;&gt;Experimental versioning for released runs&lt;/h4&gt;
&lt;p&gt;Checkpoint tags gain optional version information. &lt;code&gt;BaseCheckpointSaver.Tag&lt;/code&gt; exposes &lt;code&gt;threadId()&lt;/code&gt;, &lt;code&gt;version()&lt;/code&gt; as an &lt;code&gt;Optional&amp;lt;Integer&amp;gt;&lt;/code&gt;, &lt;code&gt;checkpoints()&lt;/code&gt;, and &lt;code&gt;lastCheckpoint()&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Two lookup methods make released runs accessible:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;Optional&amp;lt;Tag&amp;gt; tag(RunnableConfig config, Integer version) throws Exception;

Optional&amp;lt;Tag&amp;gt; lastTag(RunnableConfig config) throws Exception;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;version&lt;/code&gt; argument to &lt;code&gt;tag(...)&lt;/code&gt; is nullable. &lt;code&gt;lastTag(...)&lt;/code&gt; retrieves the latest tag regardless of whether it is versioned.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;GraphResult.asLastCheckpointStateData()&lt;/code&gt; now obtains its data through &lt;code&gt;Tag.lastCheckpoint()&lt;/code&gt;, preserving its observable result. This is useful continuity for callers retrieving the final checkpoint state while the underlying release model evolves.&lt;/p&gt;
&lt;h4 id=&quot;parent-and-subgraph-savers-work-together&quot;&gt;Parent and Subgraph savers work together&lt;/h4&gt;
&lt;p&gt;Subgraphs increasingly carry real agent behavior, so their persistence lifecycle must follow the parent execution.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;BaseCheckpointSaver&lt;/code&gt; now supports &lt;code&gt;putSubGraphSaver(...)&lt;/code&gt; to register a subgraph saver and &lt;code&gt;listSubGraphSaver(...)&lt;/code&gt; to retrieve registrations associated with a parent configuration. &lt;code&gt;SubCompiledGraphNodeAction&lt;/code&gt; registers its saver with the parent; &lt;code&gt;AbstractCheckpointSaver.release(...)&lt;/code&gt; then cascades release to the registered subgraph threads.&lt;/p&gt;
&lt;p&gt;If you implement a custom saver, &lt;code&gt;AbstractCheckpointSaver&lt;/code&gt; provides a reference implementation of the registration mechanism. Review that lifecycle when adapting your implementation, especially if your agents contain nested compiled graphs.&lt;/p&gt;
&lt;h4 id=&quot;interruption-and-error-metadata&quot;&gt;Interruption and Error Metadata&lt;/h4&gt;
&lt;p&gt;The saver contract also exposes explicit lifecycle hooks:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;void registerInterruption(
    RunnableConfig config,
    InterruptionMetadata interruptionMetadata) throws Exception;

Tag releaseCheckpointsOnError(
    RunnableConfig config,
    Throwable error) throws Exception;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These allow a saver to retain interruption information and distinguish an error release from normal completion. &lt;code&gt;InterruptionMetadata&lt;/code&gt; now also exposes the interruption reason. That gives later analysis more context about why an execution stopped.&lt;/p&gt;
&lt;p&gt;The error-release hook does not change the default described above: an exception leaves the thread available, and the caller chooses its recovery or release strategy.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; 👀&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;To avoid leaving lots of unfinished records in the checkpoint persistence layer, we recommend calling &lt;code&gt;saver.releaseOnError(RunnableConfig, Throwable)&lt;/code&gt; within exception handling logic. We are currently considering automating this  in a future release by having the graph execution engine invoke the method whenever an error occurs.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The in-memory, file-system, Redis, Postgres, Oracle, MySQL, CockroachDB, DynamoDB, and Hazelcast savers have been aligned with this contract through default implementations. The amount of information retained remains a concern of the specific saver implementation.&lt;/p&gt;
&lt;h4 id=&quot;sqlite-and-postgresql-v2-savers&quot;&gt;SQLite and PostgreSQL V2 savers&lt;/h4&gt;
&lt;p&gt;Both relational integrations add implementations backed by versioned SQL resources:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Integration &lt;/th&gt;&lt;th&gt;New implementation &lt;/th&gt;&lt;th&gt;Existing implementation&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;SQLite &lt;/td&gt;&lt;td&gt;&lt;code&gt;SQLiteSaverV2&lt;/code&gt; &lt;/td&gt;&lt;td&gt;&lt;code&gt;SQLiteSaver&lt;/code&gt;, for the V1 schema&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;PostgreSQL &lt;/td&gt;&lt;td&gt;&lt;code&gt;PostgresSaverV2&lt;/code&gt; &lt;/td&gt;&lt;td&gt;&lt;code&gt;PostgresSaver&lt;/code&gt;, for the V1 schema&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The V2 implementations align with the updated release, error, and interruption contract. The original classes remain available for their V1 schemas. Treat adoption of a V2 saver as a persistence change to review against your existing database.&lt;/p&gt;
&lt;h3 id=&quot;other-changes-worth-knowing-before-upgrading&quot;&gt;Other changes worth knowing before upgrading&lt;/h3&gt;
&lt;h4 id=&quot;explicit-graph-inputs&quot;&gt;Explicit Graph Inputs&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;GraphInput&lt;/code&gt; becomes the preferred API for expressing whether an execution is starting or resuming:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;graph.stream(GraphInput.args(Map.of(&quot;input&quot;, &quot;Hello&quot;)), config);
graph.stream(GraphInput.resume(), config);
graph.stream(GraphInput.resume(Map.of(&quot;approval&quot;, &quot;APPROVED&quot;)), config);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Use &lt;code&gt;GraphInput.noArgs()&lt;/code&gt; for a new execution without arguments. The older map-based stream and invoke overloads remain available in &lt;code&gt;1.9&lt;/code&gt;, but are deprecated for removal. Moving away from a null map makes the intended operation explicit.&lt;/p&gt;
&lt;h4 id=&quot;diagnostics-and-state-serialization&quot;&gt;Diagnostics and State Serialization&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;RunnableConfig.nodePath()&lt;/code&gt; supplies the current node path for regular nodes and subgraphs, replacing the older partial &lt;code&gt;graphPath()&lt;/code&gt; usage. A node can also intentionally interrupt execution by raising &lt;code&gt;GraphInterruptException&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;State cloning remains enabled by default. &lt;code&gt;RunnableConfig.builder().disableCloneState()&lt;/code&gt; opts out, which can help during early development with objects that are not yet serializable. Exposed state may then reflect later mutations, so consider how consumers retain or modify it.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;StateSerializer.declareTransientAttributes(...)&lt;/code&gt; excludes named attributes from serialized data and restores them from the same serializer's in-memory transient storage. Those values do not survive a restart, a different process, or a new serializer instance. Keep anything needed for durable resume in persisted state. &lt;code&gt;GsonStateSerializer&lt;/code&gt; is deprecated for removal; move toward Jackson-based serialization.&lt;/p&gt;
&lt;h4 id=&quot;agent-infrastructure&quot;&gt;Agent infrastructure&lt;/h4&gt;
&lt;p&gt;The experimental core skills API introduces &lt;code&gt;SkillSource&lt;/code&gt;, &lt;code&gt;SkillPath&lt;/code&gt;, and &lt;code&gt;SkillParser&lt;/code&gt;. &lt;/p&gt;
&lt;p&gt;Spring AI builds on this with &lt;code&gt;SubAgent&lt;/code&gt;, &lt;code&gt;CustomSubAgent&lt;/code&gt;, &lt;code&gt;SkilledReactSubAgent&lt;/code&gt;, and &lt;code&gt;SkillResource&lt;/code&gt;, making compiled agents reusable as tools. I covered the pattern in &lt;a rel=&quot;nofollow&quot; href=&quot;https://bsorrentino.github.io/bsorrentino/ai/2026/04/28/LangGraph4j-SubAgent.html&quot;&gt;Skill-Based Sub-Agents with LangGraph4j and Spring AI&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;langgraph4j-studio-gets-a-visual-refresh&quot;&gt;LangGraph4j Studio gets a visual refresh&lt;/h3&gt;
&lt;h4 id=&quot;studio-1-8&quot;&gt;Studio 1.8&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=10733277276186610415&quot; alt=&quot;LangGraph4j Studio 1.8&quot;&gt;&lt;/p&gt;
&lt;h4 id=&quot;studio-1-9&quot;&gt;Studio 1.9&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=17852363960302728394&quot; alt=&quot;LangGraph4j Studio 1.9&quot;&gt;&lt;/p&gt;
&lt;p&gt;In the new Studio the active node receives a blue highlight and a loading indicator, making execution easier to follow at a glance.&lt;/p&gt;
&lt;h3 id=&quot;langgraph4j-dsl&quot;&gt;LangGraph4j DSL&lt;/h3&gt;
&lt;p&gt;The release also adds &lt;code&gt;langgraph4j-dsl&lt;/code&gt;, whose &lt;code&gt;JsonDslGenerator&lt;/code&gt; exports graphs, including parallel nodes and nested subgraphs, as JSON:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;String json = compiledGraph.reduce(new JsonDslGenerator&amp;lt;&amp;gt;());
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This representation has been used for the Studio refactoring toward &lt;a rel=&quot;nofollow&quot; href=&quot;https://reactflow.dev&quot;&gt;React Flow&lt;/a&gt; library.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-s-next-for-langgraph4j&quot;&gt;What's Next for LangGraph4j&lt;/h3&gt;
&lt;p&gt;The next phase of development will focus on three areas. These are roadmap priorities, with scope and delivery to evolve as implementation progresses.&lt;/p&gt;
&lt;h4 id=&quot;persist-more-of-the-execution-context&quot;&gt;Persist more of the execution context&lt;/h4&gt;
&lt;p&gt;I want to improve graph execution-context persistence so that it supports monitoring and post-processing analysis more effectively. The checkpoint lifecycle work in &lt;code&gt;1.9&lt;/code&gt; is a step in that direction: understanding completed, interrupted, and failed runs is essential when agentic workflows move into production.&lt;/p&gt;
&lt;p&gt;The aim is to make retained execution information more useful for understanding behavior and for subsequent analysis and processing.&lt;/p&gt;
&lt;h4 id=&quot;redesign-parallel-node-execution&quot;&gt;Redesign parallel node execution&lt;/h4&gt;
&lt;p&gt;Another priority is to redesign the parallel-node implementation, removing current limitations and improving efficiency. As workflows grow, parallel branches need to become easier to compose and manage.&lt;/p&gt;
&lt;p&gt;This is upcoming work. Applications using &lt;code&gt;1.9&lt;/code&gt; should continue to account for the currently documented parallel-execution limitations.&lt;/p&gt;
&lt;h4 id=&quot;improve-built-in-agents-for-spring-ai-and-langchain4j&quot;&gt;Improve built-in Agents for Spring AI and LangChain4j&lt;/h4&gt;
&lt;p&gt;Finally, I want to enhance the built-in agent infrastructure for both &lt;strong&gt;Spring AI&lt;/strong&gt; and &lt;strong&gt;LangChain4j&lt;/strong&gt;. The skills and sub-agent work demonstrates how much can be built on top of graph execution; the goal is to make those building blocks more useful and easier to compose across both integrations.&lt;/p&gt;
&lt;h3 id=&quot;try-the-release&quot;&gt;Try the Release&lt;/h3&gt;
&lt;p&gt;Update the LangGraph4j BOM to &lt;code&gt;1.9.0&lt;/code&gt; to keep module versions aligned:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;&amp;lt;dependencyManagement&amp;gt;
  &amp;lt;dependencies&amp;gt;
    &amp;lt;dependency&amp;gt;
      &amp;lt;groupId&amp;gt;org.bsc.langgraph4j&amp;lt;/groupId&amp;gt;
      &amp;lt;artifactId&amp;gt;langgraph4j-bom&amp;lt;/artifactId&amp;gt;
      &amp;lt;version&amp;gt;1.9.0&amp;lt;/version&amp;gt;
      &amp;lt;type&amp;gt;pom&amp;lt;/type&amp;gt;
      &amp;lt;scope&amp;gt;import&amp;lt;/scope&amp;gt;
    &amp;lt;/dependency&amp;gt;
  &amp;lt;/dependencies&amp;gt;
&amp;lt;/dependencyManagement&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;conclusions&quot;&gt;Conclusions&lt;/h3&gt;
&lt;p&gt;For an existing &lt;code&gt;1.8.x&lt;/code&gt; application, start with the &lt;a rel=&quot;nofollow&quot; href=&quot;https://langgraph4j.github.io/langgraph4j/1.9/whats-new-v1.9&quot;&gt;migration guide&lt;/a&gt;, review thread release and stored checkpoint compatibility, then exercise interruption, resume, and completion paths with your chosen saver.&lt;/p&gt;
&lt;p&gt;I'm interested in feedback from real workflows, especially around custom progress output and checkpoint management. Check out &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/langgraph4j/langgraph4j&quot;&gt;LangGraph4j&lt;/a&gt;, try the release, and let me know what would help your next agentic application. If you find the project useful, leave a star ⭐️ and... happy AI coding! 👋&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/27744/langgraph4j-1-9-0-whats-new-and-the-roadmap</guid>
<pubDate>Sat, 19 Sep 2026 13:05:32 +0000</pubDate>
</item>
<item>
<title>Write a java program to count the letters, spaces, numbers and other characters of an input string.</title>
<link>https://coderlegion.com/26155/write-java-program-to-count-the-letters-spaces-numbers-and-other-characters-input-string</link>
<description>&lt;ol&gt;
&lt;li&gt;First take the input form the user as a string&lt;/li&gt;
&lt;li&gt;copy that string into a character array&lt;/li&gt;
&lt;li&gt;write logic for checking each character of character array is a letter/number/space/other-character&lt;/li&gt;
&lt;li&gt;print all the updated values of variables&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Note :- There is pre-built fuctions to copy all characters of string into a character array which is : &lt;br&gt;
char name_of_character_array[ ] = name_of_string.toCharArray and There are also pre-bilut functions are there to check weather character is letter/number/space in an array which are : Character.isLetter(ch[i]) , Character.isDigit(ch[i]) , Character.isSpaceChar(ch[i]) respectively&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/26155/write-java-program-to-count-the-letters-spaces-numbers-and-other-characters-input-string</guid>
<pubDate>Thu, 03 Sep 2026 12:40:15 +0000</pubDate>
</item>
<item>
<title>768. Max Chunks To Make Sorted II</title>
<link>https://coderlegion.com/24400/768-max-chunks-to-make-sorted-ii</link>
<description>&lt;p&gt;&lt;strong&gt;Problem&lt;/strong&gt;&lt;br&gt;
You are given an integer array arr.&lt;/p&gt;
&lt;p&gt;We split arr into some number of chunks (i.e., partitions), and individually sort each chunk. After concatenating them, the result should equal the sorted array.&lt;/p&gt;
&lt;p&gt;Return the largest number of chunks we can make to sort the array.&lt;/p&gt;
&lt;p&gt;Example 1:&lt;/p&gt;
&lt;p&gt;Input: arr = [5,4,3,2,1]&lt;br&gt;
Output: 1&lt;br&gt;
Explanation:&lt;br&gt;
Splitting into two or more chunks will not return the required result.&lt;br&gt;
For example, splitting into [5, 4], [3, 2, 1] will result in [4, 5, 1, 2, 3], which isn't sorted.&lt;br&gt;
Example 2:&lt;/p&gt;
&lt;p&gt;Input: arr = [2,1,3,4,4]&lt;br&gt;
Output: 4&lt;br&gt;
Explanation:&lt;br&gt;
We can split into two chunks, such as [2, 1], [3, 4, 4].&lt;br&gt;
However, splitting into [2, 1], [3], [4], [4] is the highest number of chunks possible.&lt;/p&gt;
&lt;p&gt;Constraints:&lt;/p&gt;
&lt;p&gt;1 &amp;lt;= arr.length &amp;lt;= 2000&lt;br&gt;
0 &amp;lt;= arr[i] &amp;lt;= 108&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Discussion&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This can be solved by traversing the array twice. The first time we put the elements in a min-heap/priority queue. This ensures that we have a list of the elements sorted in ascending order. Then we create 3 integer variables. One stores the sum of the sorted elements in the heap and is named sum. The second stores the total of the elements traversed in the array and is named total. The third stores the maximum number of chunks and is named answer.&lt;/p&gt;
&lt;p&gt;We then traverse the array for the second time. We pull the topmost element from the heap and add it to sum. We add the element at the current index in the array and add to total. If the values are equal, we increment answer. Once we have traversed the array, we return the value of answer.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Solution&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Java&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Solution {
    public int maxChunksToSorted(int[] arr) {
     PriorityQueue&amp;lt;Integer&amp;gt; pq=new PriorityQueue&amp;lt;&amp;gt;();
     int total=0;
     int sum=0;
     int answer=0;
     for(int i=0;i&amp;lt;arr.length;i++)pq.offer(arr[i]);
     for(int i=0;i&amp;lt;arr.length;i++){
      sum+=pq.poll();
      total+=arr[i];
      if(sum==total)answer++;  
     }
     return answer;
    }
}
&lt;/code&gt;&lt;/pre&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/24400/768-max-chunks-to-make-sorted-ii</guid>
<pubDate>Mon, 10 Aug 2026 17:51:40 +0000</pubDate>
</item>
<item>
<title>Cannot make a static reference to the non static method</title>
<link>https://coderlegion.com/21744/cannot-make-a-static-reference-to-the-non-static-method</link>
<description>&lt;p&gt;
&lt;strong&gt;&lt;span style=&quot;color: #ff04fc;&quot;&gt;&quot;Cannot make a static reference to the non-static method&quot;&lt;/span&gt; is an error message that may appear in Java when attempting to call a non-static method from a static context. In this article, we will explore the meaning of the error message, its causes, and how to resolve it.
&lt;/strong&gt;
&lt;/p&gt;&lt;hr&gt;
&lt;div id=&quot;toc-div&quot;&gt;
&lt;h3&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;&lt;strong&gt; Table of Contents:&lt;a id=&quot;toc-toogle&quot; class=&quot;open&quot;&gt;&lt;/a&gt;&lt;/strong&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;div id=&quot;toc-list&quot;&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#difference-between-static-and-non-static-methods&quot;&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;Difference between static and non-static methods&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#why-the-error&quot;&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;Why the error?&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#how-to-fix-the-error&quot;&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;How to fix the error&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#assigning-a-non-static-variable-to-a-static-variable&quot;&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;Assigning a Non-Static Variable to a Static Variable&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#conclusion&quot;&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;Conclusion&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#references&quot;&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;References&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;Difference between static and non-static methods&lt;/span&gt;&lt;a id=&quot;difference-between-static-and-non-static-methods&quot; class=&quot;custom_anchor&quot;&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;span style=&quot;color: #ff04fc;&quot;&gt;Static methods&lt;/span&gt;, also known as class methods, are methods that are associated with a class rather than an instance of the class. These methods can be called directly on the class, without the need to create an instance of the class first. They do not have access to the non-static members (instance variables or methods) of the class, but they can access the static members. A common use case for static methods is utility methods, such as mathematical operations or string manipulation.&lt;/p&gt;
&lt;p&gt;On the other hand, &lt;span style=&quot;color: #ff04fc;&quot;&gt;non-static methods&lt;/span&gt;, also known as instance methods, are methods that are associated with an instance of a class. These methods can only be called on an instance of the class, and they have access to both the static and non-static members of the class. A common use case for non-static methods is methods that perform operations on the instance variables of the class, such as getting or setting their values.&lt;/p&gt;
&lt;p&gt;To define a static method in Java, the keyword &lt;span style=&quot;color: #ff04fc;&quot;&gt;&lt;code&gt;static&lt;/code&gt;&lt;/span&gt; is added before the return type of the method. For example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;
public static int addTwoNumbers(int a, int b) {
    return a + b;
}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On the other hand, non-static method does not have the keyword &lt;span style=&quot;color: #ff04fc;&quot;&gt;&lt;code&gt;static&lt;/code&gt;&lt;/span&gt; before the return type of the method. For example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;
public int addTwoNumbers(int a, int b) {
    return a + b;
}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;div class=&quot;div-purple&quot;&gt;
&lt;span class=&quot;alert-header&quot;&gt;Note&lt;/span&gt;
&lt;span class=&quot;alert-body&quot;&gt;the main difference between static and non-static methods in Java is that static methods are associated with the class and can be called directly on the class, while non-static methods are associated with an instance of the class and can only be called on an instance of the class.&lt;/span&gt;
&lt;/div&gt;
&lt;h3&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;Why the error?&lt;/span&gt;&lt;a id=&quot;why-the-error&quot; class=&quot;custom_anchor&quot;&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The error message &lt;span style=&quot;color: #ff04fc;&quot;&gt;&quot;Cannot make a static reference to the non-static method&quot;&lt;/span&gt; occurs when a non-static method is called directly from a static context, such as a static method or the main method. This is because a non-static method belongs to an instance of a class, and a static context does not have access to an instance.&lt;/p&gt;
&lt;p&gt;Here is an example of code that will produce the error message:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;
class MyClass {
    public void nonStaticMethod() {
        System.out.println(&quot;This is a non-static method.&quot;);
    }

    public static void main(String[] args) {
        nonStaticMethod(); // Error: Cannot make a static reference to the non-static method nonStaticMethod()
    }
}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the example above, the &lt;span style=&quot;color: #ff04fc;&quot;&gt;&lt;code&gt;nonStaticMethod()&lt;/code&gt;&lt;/span&gt; is being called directly from the main method, which is a static context. To fix this error, you can either make the &lt;span style=&quot;color: #ff04fc;&quot;&gt;&lt;code&gt;nonStaticMethod()&lt;/code&gt;&lt;/span&gt; static or create an instance of the class and call the &lt;span style=&quot;color: #ff04fc;&quot;&gt;&lt;code&gt;nonStaticMethod()&lt;/code&gt;&lt;/span&gt; on that instance.&lt;/p&gt;
&lt;h3&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;How to fix the error&lt;/span&gt;&lt;a id=&quot;how-to-fix-the-error&quot; class=&quot;custom_anchor&quot;&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Here is an example of how to fix the error by making the &lt;span style=&quot;color: #ff04fc;&quot;&gt;&lt;code&gt;nonStaticMethod()&lt;/code&gt;&lt;/span&gt; static:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;
class MyClass {
    public static void nonStaticMethod() {
        System.out.println(&quot;This is a non-static method.&quot;);
    }

    public static void main(String[] args) {
        nonStaticMethod(); // This will now work
    }
}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And here is an example of how to fix the error by creating an instance of the class:&lt;/p&gt;
&lt;p&gt;It is important to note that the error message &lt;span style=&quot;color: #ff04fc;&quot;&gt;&quot;Cannot make a static reference to the non-static method&quot;&lt;/span&gt; is not limited to the main method. It can also occur when a non-static method is called directly from a static method or a static block of code.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;
class MyClass {
    public void nonStaticMethod() {
        System.out.println(&quot;This is a non-static method.&quot;);
    }

    public static void main(String[] args) {
        MyClass myObj = new MyClass();
        myObj.nonStaticMethod(); // This will now work
    }
}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;Assigning a Non-Static Variable to a Static Variable&lt;/span&gt;&lt;a id=&quot;assigning-a-non-static-variable-to-a-static-variable&quot; class=&quot;custom_anchor&quot;&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;It is not possible to assign a non-static variable to a static variable because they have different contexts, and the reference would not match. However, since static properties can be accessed directly without creating an object of the class, you can create an object of the class and use it to access the non-static variables. Then, you can use this reference to assign the variable to static fields or use it in static methods.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;
public class Main {
    // create a static variable
    static String staticVariable = &quot;Static Variable&quot;;
    //create none static variable
    String nonStaticVariable = &quot;Non Static Variable&quot;;

    public static void main(String args[]) {
        // Create an object of the class
        Main main_obj = new Main();

        // Assign the non-static variable to the static variable
        main_obj.staticVariable = main_obj.nonStaticVariable;

        // print the static variable
        System.out.println(main_obj.staticVariable);
        // output:
        //Non Static Variable
    }
}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The code creates a static variable called &lt;span style=&quot;color: #ff04fc;&quot;&gt;&lt;code&gt;staticVariable&lt;/code&gt;&lt;/span&gt; and a non-static variable called &lt;span style=&quot;color: #ff04fc;&quot;&gt;&lt;code&gt;nonStaticVariable&lt;/code&gt;&lt;/span&gt;. In the main method, an object of the class is created, and the value of the non-static variable is assigned to the static variable. The static variable is then printed, which should output &lt;span style=&quot;color: #ff04fc;&quot;&gt;&lt;code&gt;Non Static Variable&lt;/code&gt;&lt;/span&gt;.&lt;/p&gt;
&lt;h3&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;Conclusion&lt;/span&gt;&lt;a class=&quot;custom_anchor&quot;&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;In conclusion, &lt;span style=&quot;color: #ff04fc;&quot;&gt;&quot;Cannot make a static reference to the non-static method&quot;&lt;/span&gt; is an error message that occurs when a non-static method is called directly from a static context. To fix this error, you can either make the non-static method static or create an instance of the class and call the non-static method on that instance. Understanding the difference between static and non-static methods and the scope of each will help avoid this error in your code.&lt;/p&gt;
&lt;h3&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;References&lt;/span&gt;&lt;a class=&quot;custom_anchor&quot;&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;&lt;li&gt;Oracle Java Tutorials - Class Members: https://docs.oracle.com/en/java/javase/14/docs/specs/jls/se14/html/jls-8.html#jls-8.3&lt;/li&gt;&lt;li&gt;Oracle Java Tutorials - Creating and Using Methods: https://docs.oracle.com/en/java/javase/14/docs/specs/jls/se14/html/jls-8.html#jls-8.4&lt;/li&gt;&lt;li&gt;Oracle Java Tutorials - Understanding Class Members: https://docs.oracle.com/en/java/javase/14/docs/specs/jls/se14/html/jls-8.html#jls-8.3&lt;/li&gt;&lt;li&gt;Oracle Java Tutorials - Understanding Instance and Class Variables: https://docs.oracle.com/en/java/javase/14/docs/specs/jls/se14/html/jls-8.html#jls-8.3.1&lt;/li&gt;&lt;/ul&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/21744/cannot-make-a-static-reference-to-the-non-static-method</guid>
<pubDate>Thu, 02 Jul 2026 05:34:59 +0000</pubDate>
</item>
<item>
<title>AI Agents Are Now Handling Java Migrations - Here's What That Means for You</title>
<link>https://coderlegion.com/21682/ai-agents-are-now-handling-java-migrations-heres-what-that-means-for-you</link>
<description>&lt;p&gt;Legacy software migrations used to be the kind of project that kept CTOs up at night. But AI agents are starting to take on work that once required months and expensive specialists - and it's worth understanding what's actually happening.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The Real Cost of Living With Old Code&lt;/strong&gt;&lt;br&gt;
If you've ever worked near a software team managing a legacy Java codebase, you know the tension. The application works - sort of. But it runs on an older framework version that the vendor no longer fully supports. Security patches are slow to arrive. New developers struggle to onboard because the patterns are outdated. And every time someone suggests migrating to a modern framework version, the room goes quiet.&lt;/p&gt;
&lt;p&gt;The hesitation is understandable. A typical enterprise Java migration - say, moving from an older version of Spring Boot to a current one, or shifting from a legacy Jakarta EE setup - involves hundreds or thousands of files. Dependency trees have to be untangled. APIs that changed between versions need to be mapped and rewritten. Tests have to be updated. It's painstaking, detail-oriented work that traditionally required experienced engineers who knew both the old and new frameworks deeply.&lt;/p&gt;
&lt;p&gt;For small businesses or lean product teams, this kind of project often just gets deferred indefinitely. The cost in time and money is too high relative to other priorities. So the technical debt compounds, quietly making every future feature harder to build.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What AI Agents Actually Do Differently&lt;/strong&gt;&lt;br&gt;
There's an important distinction worth drawing here between an AI chatbot and an AI agent. When you use a chatbot to help with code, you're having a conversation - you paste in a snippet, it suggests a fix, you apply it manually and move on. Useful, but still mostly you doing the work.&lt;/p&gt;
&lt;p&gt;An AI agent is different. It can take a goal, break it into subtasks, execute those subtasks across multiple files and systems, check its own work, and iterate - often without needing you to hold its hand at every step. Applied to a codebase migration, that means an agent can scan the entire project, identify deprecated API calls, apply the correct replacements according to the new framework's documentation, update configuration files, and run tests to verify nothing broke - all in sequence.&lt;/p&gt;
&lt;p&gt;What makes this newly viable is a combination of factors: better reasoning in large language models, improved tool-use capabilities that let agents interact with file systems and code execution environments, and growing investment in benchmarking frameworks that measure how well agents actually perform on real migration tasks. Researchers and engineering teams are now building structured evaluations - essentially standardized test suites - so we can measure whether an agent truly migrated code correctly, not just whether it looks plausible on the surface.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Real Example - Step by Step&lt;/strong&gt;&lt;br&gt;
Let's say you're a product manager at a mid-sized company. Your engineering team maintains a Spring Boot 2.x application that needs to move to Spring Boot 3.x to maintain security compliance. There are roughly 400 Java files involved. Your lead developer estimates it's a 6-to-8-week project done manually, and the team is already stretched.&lt;/p&gt;
&lt;p&gt;Here's roughly how an AI agent-assisted migration plays out in practice:&lt;/p&gt;
&lt;p&gt;Step 1 - Discovery. The agent is pointed at the repository. It scans every file, catalogs all dependencies listed in the build file (Maven or Gradle), and flags every API call or annotation that changed between versions. This produces a migration map.&lt;/p&gt;
&lt;p&gt;Step 2 - Planning. The agent ranks changes by complexity - straightforward dependency version bumps versus more involved refactors where the underlying API signature changed significantly. It creates a work queue.&lt;/p&gt;
&lt;p&gt;Step 3 - Execution. Starting with the lower-risk changes, the agent rewrites code file by file. It handles things like namespace changes (a significant one in Spring Boot 3.x was the move from javax to jakarta packages), updated annotation patterns, and deprecated method replacements.&lt;/p&gt;
&lt;p&gt;Step 4 - Verification. The agent runs the existing test suite after each batch of changes. Where tests fail, it examines the failure, determines whether the issue is in the migration logic or in the test itself, and attempts a fix.&lt;/p&gt;
&lt;p&gt;Step 5 - Handoff. The agent produces a summary report: what was changed, what it couldn't resolve automatically, and what needs human review. Your engineer reviews the flagged items - a much smaller task than doing the whole migration from scratch.&lt;/p&gt;
&lt;p&gt;What took weeks manually might realistically take a long weekend of compute time, with your engineer spending a day reviewing edge cases rather than doing the bulk migration themselves.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How to Apply This Today&lt;/strong&gt;&lt;br&gt;
You don't need to be a developer to start thinking about this strategically. Here's what you can do right now:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;If you're evaluating vendors or tools:&lt;/strong&gt; Look for solutions that can show you benchmark results on standardized migration tasks - not just demos on clean toy examples. The quality gap between agents is significant, and real evaluation data matters.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Start small. Even a modest pilot -&lt;/strong&gt; migrating one module or one service - gives your team confidence in the process and surfaces the edge cases before you commit to something larger.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Key Takeaways&lt;/strong&gt;&lt;br&gt;
Legacy migration projects are a major source of deferred technical debt, especially for smaller teams&lt;br&gt;
AI agents differ from chatbots by executing multi-step tasks autonomously across entire codebases&lt;br&gt;
Benchmarking frameworks are emerging to measure agent migration quality on real, standardized tasks&lt;br&gt;
A human engineer's role shifts from doing the migration to reviewing the agent's output - a much faster process&lt;br&gt;
Even non-technical stakeholders can drive this conversation by asking the right questions of their engineering teams&lt;br&gt;
What's your experience with this? Drop a comment below - I read every one.&lt;/p&gt;
&lt;p&gt;Sources referenced: ScarfBench: Benchmarking AI Agents for Enterprise Java Framework Migration - Hugging Face Blog&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/21682/ai-agents-are-now-handling-java-migrations-heres-what-that-means-for-you</guid>
<pubDate>Wed, 01 Jul 2026 10:07:51 +0000</pubDate>
</item>
<item>
<title>Counting Characters in a String using Java/ Poor Deleted</title>
<link>https://coderlegion.com/21006/counting-characters-in-a-string-using-java-poor-deleted</link>
<description>&lt;h2&gt;Introduction:&lt;/h2&gt;
&lt;p&gt; In this article, we will discuss how to write a Java program to count the letters, spaces, numbers, and other characters in an input string. We will explore different methods of solving this problem and provide tips and guidelines for writing efficient and accurate code.&lt;/p&gt;
&lt;h2&gt;Table of Contents:&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#cont_1&quot;&gt;What is the problem?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#cont_2&quot;&gt;Why does this problem occur?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#cont_3&quot;&gt;How to resolve:&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#cont3_1&quot;&gt;Solution 1: Using a for loop and if-else statements.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#cont3_2&quot;&gt;Solution 2: Using an enhanced for loop. &lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#cont3_3&quot;&gt;Solution 3: Using a HashMap.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#cont3_4&quot;&gt;Solution 4: Using regular expressions&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#cont_4&quot;&gt;Conclusion.&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;div id=&quot;cont_1&quot;&gt;
    &lt;h2&gt;1. What is the problem?&lt;/h2&gt;
    The problem is to write a Java program that takes an input string and counts the number of letters, spaces, numbers, and other characters in the string.
&lt;/div&gt;
&lt;div id=&quot;cont_2&quot;&gt;&lt;h2&gt;2. Why does this problem occur?&lt;/h2&gt;
&lt;p&gt;This problem occurs because it is often necessary to analyze and understand the structure of a given text or string. This can be useful in various applications such as text processing, natural language processing, and data analysis.&lt;/p&gt;

&lt;h2&gt;3. How to resolve:&lt;/h2&gt;&lt;/div&gt;
&lt;h3 id=&quot;cont3_1&quot;&gt; - Solution 1:Using a for loop and if-else statements:&lt;/h3&gt;
&lt;p&gt;The first solution to this problem is to use a for loop to iterate through each character in the input string and use if-else statements to check if the character is a letter, space, number, or other character.&lt;/p&gt;
&lt;p&gt;One way to count the number of letters, spaces, numbers and other characters in a string is by using a for loop to iterate over each character in the string. For each character, we can check its ASCII value and increment the count of the corresponding category. For example, if the ASCII value of a character is between 65 and 90 or 97 and 122, we can increment the count of letters. Similarly, if the ASCII value is 32, we can increment the count of spaces. If the ASCII value is between 48 and 57, we can increment the count of numbers. And for all other characters, we can increment the count of other characters.&lt;/p&gt;

    
&lt;pre&gt;
&lt;code&gt;
public static void countCharacters(String input) {
    int letter = 0, space = 0, num = 0, other = 0;
    for (int i = 0; i &amp;lt; input.length(); i++) {
        char ch = input.charAt(i);
        if (Character.isLetter(ch)) {
            letter++;
        } else if (Character.isSpaceChar(ch)) {
            space++;
        } else if (Character.isDigit(ch)) {
            num++;
        } else {
            other++;
        }
    }
    System.out.println(&quot;Letters: &quot; + letter);
    System.out.println(&quot;Spaces: &quot; + space);
    System.out.println(&quot;Numbers: &quot; + num);
    System.out.println(&quot;Other: &quot; + other);
}
&lt;/code&gt;
&lt;/pre&gt;


&lt;p&gt; In this solution, we first initialize four variables to store the count of letters, spaces, numbers, and other characters. Then, we use a for loop to iterate over each character in the input string. For each character, we use the Character class's isLetter(), isSpaceChar(), and isDigit() methods to check if the character is a letter, space, or digit, respectively. If the character is a letter, we increment the count of letters. If the character is a space, we increment the count of spaces. If the character is a digit, we increment the count of numbers. And for all other characters, we increment the count of other characters. Finally, we print out the count of each category.

This solution is a simple and easy to understand, but it has a time complexity of O(n), where n is the length of the input string, as we need to iterate over each character in the string..&lt;/p&gt;

&lt;h3&gt;Solution 2: Using an enhanced for loop&lt;/h3&gt;
&lt;p&gt;One way to solve the problem of counting the number of letters, spaces, numbers, and other characters in a given input string is by using an enhanced for loop. An enhanced for loop is a simpler version of a traditional for loop that is used to iterate through the elements of an array or collection.&lt;/p&gt;

    
&lt;pre&gt;
&lt;code&gt;
public static void countCharacters(String input) {
    int letter = 0, space = 0, num = 0, other = 0;
    for (char ch : input.toCharArray()) {
        if (Character.isLetter(ch)) {
            letter++;
        } else if (Character.isSpaceChar(ch)) {
            space++;
        } else if (Character.isDigit(ch)) {
            num++;
        } else {
            other++;
        }
    }
    System.out.println(&quot;Letters: &quot; + letter);
    System.out.println(&quot;Spaces: &quot; + space);
    System.out.println(&quot;Numbers: &quot; + num);
    System.out.println(&quot;Other: &quot; + other);
}
&lt;/code&gt;
&lt;/pre&gt;


&lt;p&gt;This solution is similar to solution 1, but instead of using a traditional for loop, it uses an enhanced for loop to iter&lt;/p&gt;


&lt;h3 id=&quot;cont3_3&quot;&gt; - Solution 3: Using a HashMap &lt;/h3&gt;
&lt;p&gt;Another way to count the number of occurrences of each character in a string is to use a HashMap. A HashMap is a data structure that stores key-value pairs, where the key is the character and the value is the number of occurrences of that character.&lt;/p&gt;
&lt;p&gt;To implement this solution, we will first create an empty HashMap and then iterate through the string, adding each character to the HashMap and incrementing the count of that character if it already exists. Once the iteration is complete, we can then iterate through the HashMap and print out the character and its count.&lt;/p&gt;
&lt;p&gt;Here is an example implementation of this solution:&lt;/p&gt;

&lt;pre&gt;
&lt;code&gt;
        import java.util.HashMap;
    
    public class CharacterCount {
        public static void main(String[] args) {
            String input = &quot;This is a sample string&quot;;
            HashMap charCount = new HashMap&amp;lt;&amp;gt;();
    
            for (int i = 0; i &amp;lt; input.length(); i++) {
                char c = input.charAt(i);
                if (charCount.containsKey(c)) {
                    charCount.put(c, charCount.get(c) + 1);
                } else {
                    charCount.put(c, 1);
                }
            }
    
            for (char c : charCount.keySet()) {
                System.out.println(c + &quot;: &quot; + charCount.get(c));
            }
        }
    }
&lt;/code&gt;
&lt;/pre&gt;

&lt;p&gt;This solution has the advantage of being more efficient than the previous solution, as it only needs to iterate through the string once, whereas the previous solution needed to iterate through the string twice. Additionally, the HashMap allows for easy retrieval of the count for a specific character, which can be useful in certain situations.&lt;/p&gt;

&lt;h3 id=&quot;cont3_4&quot;&gt; - Solution 3: Using Regular Expression &lt;/h3&gt;
&lt;p&gt;Here is a sample Java program that uses regular expressions to count the number of letters, spaces, numbers, and other characters in an input string:&lt;/p&gt;

   
&lt;pre&gt;
&lt;code&gt;
     import java.util.regex.Matcher;
    import java.util.regex.Pattern;
    import java.util.Scanner;
    
    public class CharacterCounter {
        public static void main(String[] args) {
            Scanner sc = new Scanner(System.in);
            System.out.print(&quot;Enter a string: &quot;);
            String input = sc.nextLine();
    
            int letterCount = 0;
            int spaceCount = 0;
            int numCount = 0;
            int otherCount = 0;
    
            // Define regular expressions for letters, spaces, numbers, and other characters
            String letterRegex = &quot;[a-zA-Z]&quot;;
            String spaceRegex = &quot;\\s&quot;;
            String numRegex = &quot;\\d&quot;;
            String otherRegex = &quot;[^a-zA-Z\\s\\d]&quot;;
    
            // Create pattern objects
            Pattern letterPattern = Pattern.compile(letterRegex);
            Pattern spacePattern = Pattern.compile(spaceRegex);
            Pattern numPattern = Pattern.compile(numRegex);
            Pattern otherPattern = Pattern.compile(otherRegex);
    
            // Create matcher objects
            Matcher letterMatcher = letterPattern.matcher(input);
            Matcher spaceMatcher = spacePattern.matcher(input);
            Matcher numMatcher = numPattern.matcher(input);
            Matcher otherMatcher = otherPattern.matcher(input);
    
            // Count the number of matches for each regular expression
            while (letterMatcher.find()) {
                letterCount++;
            }
            while (spaceMatcher.find()) {
                spaceCount++;
            }
            while (numMatcher.find()) {
                numCount++;
            }
            while (otherMatcher.find()) {
                otherCount++;
            }
    
            // Print the results
            System.out.println(&quot;Letters: &quot; + letterCount);
            System.out.println(&quot;Spaces: &quot; + spaceCount);
            System.out.println(&quot;Numbers: &quot; + numCount);
            System.out.println(&quot;Other: &quot; + otherCount);
        }
    }
&lt;/code&gt;
&lt;/pre&gt;
&lt;p&gt;This program prompts the user to enter a string and uses four regular expressions to match letters, spaces, numbers, and other characters in the input string. The program uses the 'Matcher' class to find all occurrences of each regular expression in the input string, and uses a while loop to count the number of matches for each regular expression. Finally, the program prints the results to the console.&lt;/p&gt;

&lt;p&gt;It's worth mentioning that there are other ways like using String.replaceAll() to count the characters and loops as well , but using regular expression is more efficient and flexible.&lt;/p&gt;

&lt;h2 id=&quot;cont_4&quot;&gt;4. Conclusion:&lt;/h2&gt;
&lt;p&gt;In conclusion, writing a program to count the letters, spaces, numbers and other characters of an input string in Java is a common task that can be achieved through various methods. By understanding the problem, its causes and the available solutions, it is possible to write an efficient and accurate program. The solutions discussed in this article include using a for loop, HashMap. Each of these solutions has its own advantages and disadvantages and can be used depending on the specific requirements of the task. Ultimately, by understanding and practicing these solutions, programmers can improve their skills and become more proficient in Java programming.&lt;/p&gt;


</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/21006/counting-characters-in-a-string-using-java-poor-deleted</guid>
<pubDate>Sat, 20 Jun 2026 01:33:03 +0000</pubDate>
</item>
<item>
<title>CodeRef - Smart Java Analyzer with ML Engine</title>
<link>https://coderlegion.com/20028/coderef-smart-java-analyzer-with-ml-engine</link>
<description>&lt;p&gt;How a Single IntelliJ Plugin Cut Our Code Review Rework by 60% — A 6-Month Honest Review&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/czkp7gn62vpkuuqdxjuz.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;p&gt;Developer's IntelliJ IDEA workspace with CodeRef's analysis report open in the bottom panel, showing zero critical issues on a Spring Boot service class after automatic refactoring.&lt;/p&gt;
&lt;p&gt;I'm a backend engineer on a team of eight. We build microservices in Spring Boot, and like most Java teams, we use SonarQube in our CI pipeline to enforce code quality gates. It's a solid tool and we rely on it.&lt;/p&gt;
&lt;p&gt;But there was always a gap in our workflow — the feedback only arrived &lt;em&gt;after&lt;/em&gt; pushing code. I'd get a SonarQube report 12 minutes later telling me I introduced a cognitive complexity violation on line 47. By then I've already moved on to the next ticket. The quality gate was working, but the feedback loop was slow.&lt;/p&gt;
&lt;p&gt;Six months ago, a colleague dropped a link to &lt;strong&gt;CodeRef&lt;/strong&gt; in our team Slack channel. &quot;Try this, it catches stuff before you even commit.&quot; I installed it expecting another linter that I'd disable within a week.&lt;/p&gt;
&lt;p&gt;I haven't disabled it. Here's why.&lt;/p&gt;
&lt;p&gt;Week 1: The Instant Feedback Loop Changes Everything&lt;/p&gt;
&lt;p&gt;The first thing that hit me was &lt;strong&gt;speed&lt;/strong&gt;. I opened a service class I'd been working on, and within seconds the Report tab at the bottom of my IDE lit up with findings — not after a CI pipeline, not after a PR review, but &lt;em&gt;right there while I was still writing the method&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4kp48f1w7a0ft5tib4vt.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;p&gt;CodeRef analysis appearing within seconds of opening a Java file, with the Report tab showing three findings: a cognitive complexity violation, a missing @Transactional annotation, and field injection flagged for constructor migration.&lt;/p&gt;
&lt;p&gt;That first day, it caught three things in a class I was about to push:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;A &lt;code&gt;@Transactional&lt;/code&gt; annotation on a private method&lt;/strong&gt; — Spring proxies don't intercept private methods, so the annotation was doing absolutely nothing. This had been in production for two sprints. Traditional static analysis tools don't typically flag this because it's a framework-specific pattern that requires understanding Spring's proxy mechanism.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;An &lt;code&gt;Optional.get()&lt;/code&gt; without an &lt;code&gt;isPresent()&lt;/code&gt; check&lt;/strong&gt; — I knew better, but I was moving fast and missed it. Classic.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Field injection on three &lt;code&gt;@Autowired&lt;/code&gt; fields&lt;/strong&gt; — not a bug, but CodeRef flagged it with a clear explanation of why constructor injection is preferred for testability.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;What made this powerful was the &lt;strong&gt;timing&lt;/strong&gt;. Having analysis run inside the IDE meant I fixed all three before committing. No CI round-trip. No PR comment. No context-switching back to code I wrote an hour ago. By the time our CI pipeline ran its quality gate, the code was already clean.&lt;/p&gt;
&lt;h3 id=&quot;week-3-the-auto-fixers-saved-me-hours&quot;&gt;Week 3: The Auto-Fixers Saved Me Hours&lt;/h3&gt;
&lt;p&gt;I'll be honest — I almost ignored the &quot;Refactored Code&quot; tab for the first two weeks. I assumed it would be naive find-and-replace suggestions.&lt;/p&gt;
&lt;p&gt;Then I had a 45-line method that CodeRef flagged for cognitive complexity (S3776). Out of curiosity, I clicked the tab. It had &lt;strong&gt;extracted two nested blocks into well-named private methods&lt;/strong&gt;, preserved the logic perfectly, and presented the result as a clean diff.&lt;/p&gt;
&lt;p&gt;I copied it over. It compiled. Tests passed. What would have been a 15-minute manual refactor took 30 seconds.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/747rfldj0j1am862d1tk.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;p&gt;CodeRef's Refactored Code tab showing a side-by-side diff where a 45-line method with nested conditionals has been split into three focused methods, with the extracted methods named after their business logic.&lt;/p&gt;
&lt;p&gt;Since then, the fixers I use constantly:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Try-with-resources conversion&lt;/strong&gt; — I have a codebase with legacy &lt;code&gt;try-finally&lt;/code&gt; blocks everywhere. CodeRef converts them one click at a time. I've cleaned up about 30 so far during regular feature work, no dedicated refactoring sprint needed.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Constructor injection migration&lt;/strong&gt; — We decided as a team to move away from &lt;code&gt;@Autowired&lt;/code&gt; field injection. Instead of a bulk find-and-replace that would break things, I let CodeRef migrate each class as I touch it. It adds the &lt;code&gt;final&lt;/code&gt; field, creates the constructor parameter, and removes the annotation.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;String concatenation in loops&lt;/strong&gt; — It found three StringBuilder opportunities in our batch processing code. The performance improvement was measurable in our metrics dashboard.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The key insight: &lt;strong&gt;the fixes aren't suggestions, they're working code.&lt;/strong&gt; I review the diff, apply it, and move on. The plugin does the mechanical refactoring so I can focus on the logic.&lt;/p&gt;
&lt;p&gt;Month 2: Test Generation Accelerated Our Coverage Push&lt;/p&gt;
&lt;p&gt;Our team had a quarterly goal to get test coverage from 54% to 75%. Everyone was dreading the &quot;write tests for existing code&quot; phase.&lt;/p&gt;
&lt;p&gt;CodeRef's test generation changed the math on that effort entirely.&lt;/p&gt;
&lt;p&gt;For a &lt;code&gt;@RestController&lt;/code&gt; with five endpoints, it generated a complete &lt;code&gt;@WebMvcTest&lt;/code&gt; class with &lt;code&gt;MockMvc&lt;/code&gt; setup, mock dependencies, and test methods for each mapping — including exception cases. Was it perfect? No. I adjusted assertions and added edge cases specific to our business logic. But the &lt;strong&gt;scaffolding was correct and the boilerplate was done&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/67xiayafvgw9ci5kto4j.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;p&gt;CodeRef's Test Cases tab showing a generated @WebMvcTest class for an OrderController, with MockMvc injection, mocked OrderService, and test methods for GET /orders, POST /orders, and GET /orders/{id} including 404 handling.&lt;/p&gt;
&lt;p&gt;For &lt;code&gt;@Service&lt;/code&gt; classes, it set up &lt;code&gt;MockitoExtension&lt;/code&gt; with the right &lt;code&gt;@Mock&lt;/code&gt; and &lt;code&gt;@InjectMocks&lt;/code&gt; fields by reading the constructor parameters. For our &lt;code&gt;@Repository&lt;/code&gt; classes, it generated &lt;code&gt;@DataJpaTest&lt;/code&gt; with &lt;code&gt;TestEntityManager&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;What used to take 20–30 minutes per class (setting up the test class, figuring out which mocks to wire, writing the first few test methods) now takes 5 minutes of review and customization.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;We hit 78% coverage three weeks ahead of schedule.&lt;/strong&gt; I'm not going to attribute that entirely to CodeRef — the team put in real work on the complex test scenarios. But eliminating the boilerplate setup meant we spent our time on the tests that actually matter.&lt;/p&gt;
&lt;h3 id=&quot;month-3-the-ml-engine-started-earning-its-keep&quot;&gt;Month 3: The ML Engine Started Earning Its Keep&lt;/h3&gt;
&lt;p&gt;For the first two months, I occasionally dismissed findings that weren't relevant to our codebase. A &lt;code&gt;TODO&lt;/code&gt; comment warning on a ticket-tracked TODO. A magic number flag on a well-known HTTP status code. The usual noise.&lt;/p&gt;
&lt;p&gt;Around week 10, I noticed something: &lt;strong&gt;those warnings stopped appearing.&lt;/strong&gt; CodeRef's ML engine had been quietly learning from my dismissals and started suppressing similar false positives.&lt;/p&gt;
&lt;p&gt;I checked the ML insights panel — it had suppressed 23 findings that week that matched patterns I'd previously dismissed. Every single suppression was correct.&lt;/p&gt;
&lt;p&gt;This is the feature that turned CodeRef from &quot;good tool&quot; to &quot;tool I'd fight to keep.&quot; Every other linter I've used has a static configuration — you either suppress a rule globally or you deal with the noise. CodeRef &lt;strong&gt;learns what matters to you&lt;/strong&gt; and adjusts. The signal-to-noise ratio gets better every week.&lt;/p&gt;
&lt;p&gt;The severity re-ranking was a subtler benefit. Our team cares deeply about resource leaks (we had a production incident caused by an unclosed database connection), so I always prioritized those fixes. After a few weeks, CodeRef started bumping resource-related findings to Critical automatically. It understood our priorities without me writing a config file.&lt;/p&gt;
&lt;p&gt;Month 5: Spring-Specific Rules Caught Two Production Bugs Before They Shipped&lt;/p&gt;
&lt;p&gt;This is the story I tell when people ask if CodeRef is worth the Pro license.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Bug #1: Self-invocation bypassing @Transactional&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;We had a service method that called another method in the same class, both annotated with &lt;code&gt;@Transactional&lt;/code&gt;. The inner call wasn't going through the Spring proxy, so it was running without a transaction boundary. In our test environment with small datasets, this was invisible. In production with concurrent writes, it would have caused data inconsistency.&lt;/p&gt;
&lt;p&gt;CodeRef flagged it as S5962 (Spring proxy self-invocation bypass) with a clear explanation of &lt;em&gt;why&lt;/em&gt; the proxy doesn't intercept internal calls. I refactored it to use a separate service class. Total time to fix: 10 minutes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Bug #2: @ConfigurationProperties without @Validated&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A configuration class was binding external properties without validation. One of the properties was a connection timeout that defaulted to zero when not set. In our staging environment, the property was always present. In a new deployment environment that was being provisioned, it wasn't — and zero meant &quot;no timeout,&quot; which meant threads hanging indefinitely.&lt;/p&gt;
&lt;p&gt;CodeRef flagged the missing &lt;code&gt;@Validated&lt;/code&gt; annotation (S5975). I added it along with &lt;code&gt;@NotNull&lt;/code&gt; and &lt;code&gt;@Positive&lt;/code&gt; constraints. The new environment launched without issues.&lt;/p&gt;
&lt;p&gt;Neither of these would have been caught by PMD or SpotBugs alone. They require understanding Spring's proxy mechanism and configuration binding behavior. &lt;strong&gt;This is what framework-aware analysis means in practice.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Month 6: Project-Wide Analysis for Sprint Planning&lt;/p&gt;
&lt;p&gt;We recently started using CodeRef's project-wide scan before sprint planning. The scan runs across the entire Maven project and produces an aggregate report with per-file severity distribution.&lt;/p&gt;
&lt;p&gt;What makes this useful alongside our CI quality gates is the developer experience:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Runs locally in the IDE&lt;/strong&gt; — no context-switching to a browser dashboard, results in about 90 seconds for our 200-file project&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ML-enhanced results&lt;/strong&gt; — the bug risk score highlights files that are structurally complex and frequently modified, so we know where to focus review effort&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Actionable next steps&lt;/strong&gt; — every finding has an auto-fixer or test generation strategy attached, so the scan feeds directly into hands-on-keyboard work&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We've started allocating 10% of each sprint to &quot;CodeRef hygiene&quot; — picking the highest-risk files from the scan and applying auto-fixes. It's not glamorous work, but the trend lines on our defect rate speak for themselves.&lt;/p&gt;
&lt;p&gt;CodeRef project-wide analysis showing a file list sorted by bug risk score, with the top three files highlighted in red, severity distribution chart showing a 40% reduction in Critical findings over the past three sprints, and per-file issue counts with auto-fixable percentages.&lt;/p&gt;
&lt;p&gt;The Numbers After 6 Months&lt;/p&gt;
&lt;p&gt;I tracked some metrics because I knew people would ask:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jgqurh17c5pb43wkbuek.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;p&gt;Table showing 6-month impact metrics: code review rework dropped from ~12 to ~5 per sprint (-60%), time to detect issues from 47 minutes to under 10 seconds, test coverage from 54% to 81%, production bugs from 2-3 per quarter to 0, manual refactoring time from ~6 hours to ~1.5 hours per sprint, and issues caught before CI from ~0% to ~85%&lt;/p&gt;
&lt;p&gt;The ROI calculation was straightforward enough that our engineering manager approved Pro licenses for the entire team without a formal business case.&lt;/p&gt;
&lt;p&gt;What I Wish Were Better&lt;/p&gt;
&lt;p&gt;It's not a perfect tool, and I'd rather give an honest review than a sales pitch:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Large files (800+ lines) take a noticeable pause&lt;/strong&gt; — the three-engine parallel analysis is fast, but on a god class it can take 5–8 seconds. Not a dealbreaker, but noticeable.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The ML engine needs 50 interactions to activate&lt;/strong&gt; — for the first few weeks, you're getting raw unfiltered results. I wish there were a way to bootstrap it with team-level patterns from day one.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No Kotlin support yet&lt;/strong&gt; — we have a few Kotlin modules and those don't get analyzed. The roadmap mentions it, so I'm hopeful.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Test generation is scaffolding, not magic&lt;/strong&gt; — the generated tests are structurally correct and save significant time, but you still need to write the meaningful assertions. That's probably the right tradeoff, but set your expectations accordingly.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Who Should Consider This&lt;/p&gt;
&lt;p&gt;If you're on a Java team that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Uses Spring Boot, JPA, or Apache Camel&lt;/li&gt;
&lt;li&gt;Wants code quality feedback &lt;em&gt;at write-time&lt;/em&gt; to complement your CI quality gates&lt;/li&gt;
&lt;li&gt;Has a test coverage goal and needs to eliminate boilerplate&lt;/li&gt;
&lt;li&gt;Wants framework-aware analysis that understands Spring proxies, JPA lifecycle, and Camel routing&lt;/li&gt;
&lt;li&gt;Wants a tool that gets smarter over time instead of requiring more configuration&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Then CodeRef is worth a serious evaluation. It works well on its own, and it works even better as an early-feedback layer alongside your existing CI pipeline. Install the free tier, run it on your most problematic service class, and see what it finds. That's what convinced me.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t6lk7fcg35vfp2pptkb5.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;p&gt;Diagram showing CodeRef in the developer workflow: code is analyzed instantly in the IDE at write-time, issues are fixed before commit, and the CI pipeline quality gate sees cleaner code with fewer failures.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Six months in, CodeRef has become an essential part of my daily workflow. The earlier I catch an issue, the cheaper it is to fix — and catching it while I'm still in the method is about as early as it gets. If you've tried CodeRef, I'd love to hear how it's working for your team.&lt;/em&gt;&lt;/p&gt;
&lt;h2 id=&quot;java-springboot-codequality-developerproductivity-intellijidea-softwareengineering-devtools-codereview-testautomation-jetbrains&quot;&gt;Java #SpringBoot #CodeQuality #DeveloperProductivity #IntelliJIDEA #SoftwareEngineering #DevTools #CodeReview #TestAutomation #JetBrains&lt;/h2&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/20028/coderef-smart-java-analyzer-with-ml-engine</guid>
<pubDate>Tue, 09 Jun 2026 08:37:00 +0000</pubDate>
</item>
<item>
<title>What slowed your progress more: bad code or slow feedback?</title>
<link>https://coderlegion.com/19982/what-slowed-your-progress-more-bad-code-or-slow-feedback</link>
<description>&lt;p&gt;Think about the last time you were stuck on a frustrating frontend problem. You were trying to get a complex CSS grid to align properly, or debugging a tricky JavaScript interaction.&lt;/p&gt;
&lt;p&gt;Did you struggle because your logic was inherently flawed, or did you struggle because every time you made a tiny adjustment, it took five seconds for your online code editor or local environment to refresh the view?&lt;/p&gt;
&lt;p&gt;I strongly believe that a slow feedback loop is the silent killer of developer productivity. When we have to wait to see the results of our code, we lose our train of thought. We stop experimenting. We get frustrated.&lt;/p&gt;
&lt;p&gt;I’ve been building NitroIDE specifically to act as an instant frontend playground to eliminate this waiting period, but it made me wonder about the broader community's experience.&lt;/p&gt;
&lt;p&gt;When you reflect on your own workflow, how much time do you think you lose simply waiting for your tools to render your changes? Do you feel like your current setup encourages rapid prototyping, or does it make you hesitant to experiment? Let's discuss.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/19982/what-slowed-your-progress-more-bad-code-or-slow-feedback</guid>
<pubDate>Mon, 08 Jun 2026 15:58:59 +0000</pubDate>
</item>
<item>
<title>Asking the user for input until they give a valid response java</title>
<link>https://coderlegion.com/19581/asking-the-user-for-input-until-they-give-a-valid-response-java</link>
<description>&lt;div&gt;&lt;p&gt;
As a Java developer, you will often find yourself in situations where you need to ask the user for input and validate it before moving forward in your program. Whether it's asking for a number within a certain range, a valid email address, or any other type of input, it's essential to ensure that the user's input is valid before proceeding.In this article, we will explore &lt;span style=&quot;color: #FF04FC;&quot;&gt;how to repeatedly ask the user for input until they give a valid response using Java&lt;/span&gt;. We will look at examples of how to accomplish this task using while loops, the &lt;span style=&quot;color: #FF04FC;&quot;&gt;Scanner&lt;/span&gt; class, and regular expressions. Whether you're a beginner or an experienced Java developer, this article will provide you with the tools you need to ensure that your program only receives valid input from the user. So let's dive in and learn how to ask the user for input until they give a valid response in Java!
&lt;/p&gt;&lt;/div&gt;&lt;hr&gt;
&lt;div&gt;
&lt;h3&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;&lt;strong&gt; Table of Contents:&lt;a class=&quot;open&quot;&gt;&lt;/a&gt;&lt;/strong&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;div&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#the-scanner-class&quot;&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;The Scanner class&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#examples-of-input-validation&quot;&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;Examples of input validation&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#a-simple-guessing-game&quot;&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;A simple guessing game&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#conclusion&quot;&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;Conclusion&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#references&quot;&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;References&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div&gt;&lt;h3&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;The Scanner class&lt;/span&gt;&lt;a id=&quot;the-scanner-class&quot; class=&quot;custom_anchor&quot;&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;The &lt;span style=&quot;color: #FF04FC;&quot;&gt;Scanner&lt;/span&gt; class in Java is used to read input from various sources, such as the keyboard, files, and network sockets. Some of the useful methods it contains are:&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;color: #FF04FC;&quot;&gt;&lt;code&gt;nextLine()&lt;/code&gt;&lt;/span&gt;: Reads the next line of input as a String. Example usage:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;Scanner scanner = new Scanner(System.in);
System.out.println(&quot;Enter your name:&quot;);
String name = scanner.nextLine();
System.out.println(&quot;Hello, &quot; + name + &quot;!&quot;);
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;span style=&quot;color: #FF04FC;&quot;&gt;&lt;code&gt;nextInt()&lt;/code&gt;&lt;/span&gt;: Reads the next token of input as an int. Example usage&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;Scanner scanner = new Scanner(System.in);
System.out.println(&quot;Enter an integer:&quot;);
int num = scanner.nextInt();
System.out.println(&quot;You entered: &quot; + num);
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;span style=&quot;color: #FF04FC;&quot;&gt;&lt;code&gt;nextDouble()&lt;/code&gt;&lt;/span&gt;: Reads the next token of input as a double. Example usage:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;Scanner scanner = new Scanner(System.in);
System.out.println(&quot;Enter a decimal number:&quot;);
double num = scanner.nextDouble();
System.out.println(&quot;You entered: &quot; + num);
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;span style=&quot;color: #FF04FC;&quot;&gt;&lt;code&gt;nextBoolean()&lt;/code&gt;&lt;/span&gt;: Reads the next token of input as a boolean. Example usage:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;Scanner scanner = new Scanner(System.in);
System.out.println(&quot;Are you a student? (true/false)&quot;);
boolean isStudent = scanner.nextBoolean();
System.out.println(&quot;You are a student: &quot; + isStudent);
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;span style=&quot;color: #FF04FC;&quot;&gt;&lt;code&gt;hasNext()&lt;/code&gt;&lt;/span&gt;: Returns true if the scanner has another token in its input. Example usage:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;Scanner scanner = new Scanner(System.in);
while (scanner.hasNext()) {
    System.out.println(scanner.next());
}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;span style=&quot;color: #FF04FC;&quot;&gt;&lt;code&gt;hasNextLine()&lt;/code&gt;&lt;/span&gt;: Returns true if the scanner has another line of input. Example usage:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;Scanner scanner = new Scanner(System.in);
while (scanner.hasNextLine()) {
    System.out.println(scanner.nextLine());
}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;span style=&quot;color: #FF04FC;&quot;&gt;&lt;code&gt;next()&lt;/code&gt;&lt;/span&gt;: Reads the next token of input as a String. Example usage:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;Scanner scanner = new Scanner(&quot;Hello, world!&quot;);
System.out.println(scanner.next()); // Output: &quot;Hello,&quot;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;span style=&quot;color: #FF04FC;&quot;&gt;&lt;code&gt;useDelimiter(String pattern)&lt;/code&gt;&lt;/span&gt;: Specifies a delimiter to be used by the scanner to separate input tokens. Example usage:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;Scanner scanner = new Scanner(&quot;Hello, world!&quot;);
scanner.useDelimiter(&quot; &quot;);
System.out.println(scanner.next()); // Output: &quot;Hello,&quot;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;span style=&quot;color: #FF04FC;&quot;&gt;&lt;code&gt;close()&lt;/code&gt;&lt;/span&gt;: Closes the scanner and releases any resources associated with it. Example usage:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;Scanner scanner = new Scanner(System.in);
// Do some scanning here...
scanner.close();
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;div class=&quot;div-purple&quot;&gt;
&lt;span class=&quot;alert-header&quot;&gt;Note &lt;/span&gt;
&lt;span class=&quot;alert-body&quot;&gt;
It is important to note that when a &lt;span style=&quot;color: #FF04FC;&quot;&gt;Scanner&lt;/span&gt; is closed, it can no longer be used, and attempting to do so will result in an &lt;span style=&quot;color: #FF04FC;&quot;&gt;&lt;code&gt;IllegalStateException&lt;/code&gt;&lt;/span&gt;.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;h3&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;Examples of input validation&lt;/span&gt;&lt;a id=&quot;examples-of-input-validation&quot; class=&quot;custom_anchor&quot;&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;In Java, you can use a &lt;span style=&quot;color: #FF04FC;&quot;&gt;&lt;code&gt;while&lt;/code&gt;&lt;/span&gt; loop to repeatedly ask the user for input until they give a valid response. You can use the Scanner class to read input from the user and check if it is valid using an if statement. Here is an example of how you might ask the user for an integer input and check if it is within a certain range:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);

        int input;
        while (true) {
            System.out.println(&quot;Enter an integer between 1 and 10:&quot;);
            input = scanner.nextInt();
            if (input &amp;gt;= 1 &amp;amp;&amp;amp; input &lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Another example of asking user for input and validating it until a valid input is given is for a string input, such as an email address:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;import java.util.Scanner;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        String email;
        final String EMAIL_REGEX = &quot;^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Z|a-z]{2,}$&quot;;
        
        while (true) {
            System.out.println(&quot;Enter an email address:&quot;);
            email = scanner.nextLine();
            Pattern pattern = Pattern.compile(EMAIL_REGEX);
            Matcher matcher = pattern.matcher(email);
            if(matcher.matches()){
                break;
            }
            System.out.println(&quot;Invalid email. Please try again.&quot;);
        }
        System.out.println(&quot;Thank you for entering a valid email.&quot;);
    }
}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Note in the above example we used a regular expression to validate the email address, which is a powerful tool for validating different inputs.&lt;/p&gt;&lt;/div&gt;&lt;div&gt;&lt;h3&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;A simple guessing game&lt;/span&gt;&lt;a id=&quot;a-simple-guessing-game&quot; class=&quot;custom_anchor&quot;&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;In this section, we are going to create a simple guessing game to illustrate a practical approach of using input validation.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;import java.util.Scanner;

public class GuessingGame {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int secretNumber = (int) (Math.random() * 100) + 1; // Generates a random number between 1 and 100
        int guess;

        System.out.println(&quot;I'm thinking of a number between 1 and 100. Can you guess what it is?&quot;);
        do {
            while (!scanner.hasNextInt()) {
                System.out.println(&quot;Invalid input. Please enter a number:&quot;);
                scanner.next();
            }
            guess = scanner.nextInt();
            if (guess &amp;lt; secretNumber) {
                System.out.println(&quot;Too low. Try again:&quot;);
            } else if (guess &amp;gt; secretNumber) {
                System.out.println(&quot;Too high. Try again:&quot;);
            }
        } while (guess != secretNumber);

        System.out.println(&quot;Congratulations! You guessed the number!&quot;);
        scanner.close();
    }
}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;In this example, the program generates a random number between &lt;span style=&quot;color: #FF04FC;&quot;&gt;&lt;code&gt;1&lt;/code&gt;&lt;/span&gt; and &lt;span style=&quot;color: #FF04FC;&quot;&gt;&lt;code&gt;100&lt;/code&gt;&lt;/span&gt;, and then prompts the user to guess what the number is. The program uses a &lt;span style=&quot;color: #FF04FC;&quot;&gt;do-while&lt;/span&gt; loop to repeatedly ask the user for input until they guess the correct number. The &lt;span style=&quot;color: #FF04FC;&quot;&gt;&lt;code&gt;scanner.hasNextInt()&lt;/code&gt;&lt;/span&gt; method is used to check if the user's input is a valid integer, and if not, the program prompts the user to enter a valid number. Once the user enters the correct number, the loop exits, and the program prints a message congratulating the user for guessing the number.
&lt;div class=&quot;div-green&quot;&gt;
&lt;span class=&quot;alert-header&quot;&gt;Note &lt;/span&gt;
&lt;span class=&quot;alert-body&quot;&gt;
It is important to close the scanner object after you have finished using it to release the associated resources and avoid memory leaks&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;h3&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;Conclusion&lt;/span&gt;&lt;a class=&quot;custom_anchor&quot;&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;In conclusion, asking the user for input and validating it is an essential task for any Java developer. By using while loops, the Scanner class, and regular expressions, you can &lt;span style=&quot;color: #FF04FC;&quot;&gt;repeatedly ask the user for input until they give a valid response&lt;/span&gt;. The examples provided in this article demonstrate how to accomplish this task for both integer and string input, but the principles can be applied to any type of input. It's important to note that validation of user input is a crucial step in ensuring the integrity of your program and protecting it from potential errors or malicious attacks. By following the techniques discussed in this article, you'll be well on your way to creating robust and secure Java programs that only accept valid input from the user.&lt;/p&gt;&lt;/div&gt;&lt;div&gt;&lt;h3&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;References&lt;/span&gt;&lt;a class=&quot;custom_anchor&quot;&gt;&lt;/a&gt;&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;The Java Scanner class documentation: https://docs.oracle.com/en/java/javase/14/docs/api/java.base/java/util/Scanner.html&lt;/li&gt;&lt;li&gt;Oracle's Java tutorial on regular expressions: https://docs.oracle.com/en/java/javase/14/docs/api/java.base/java/util/regex/Pattern.html&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/19581/asking-the-user-for-input-until-they-give-a-valid-response-java</guid>
<pubDate>Wed, 03 Jun 2026 12:26:01 +0000</pubDate>
</item>
<item>
<title>How to Fix Gradle Error: Unsupported class file major version (React Native &amp; Expo)</title>
<link>https://coderlegion.com/19548/how-to-fix-gradle-error-unsupported-class-file-major-version-react-native-expo</link>
<description>&lt;p&gt;If your React Native or Expo Android compilation suddenly crashes with a massive terminal stack trace pointing to an &quot;unsupported class file major version&quot; or claims a class file has the wrong version (e.g., &lt;code&gt;wrong version 65.0, should be 61.0&lt;/code&gt;), your environment is caught in a silent version mismatch.&lt;/p&gt;
&lt;p&gt;This error almost always triggers right after you upgrade your Expo SDK, bump your React Native core version, or update Android Studio. &lt;/p&gt;
&lt;p&gt;Here is exactly why this happens, how to decode the cryptic internal version numbers, and how to get your build compiling cleanly.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;why-did-this-happen-suddenly-&quot;&gt;Why Did This Happen Suddenly?&lt;/h3&gt;
&lt;p&gt;When React Native or Expo upgrades their framework architecture, they also upgrade the required version of the &lt;strong&gt;Android Gradle Plugin (AGP)&lt;/strong&gt; inside your project. &lt;/p&gt;
&lt;p&gt;Each version of AGP requires a strict minimum version of the Java Development Kit (JDK) to run the compilation pipeline. If your system's global terminal runtime environment or your IDE is still pointing to an older Java runtime, the build engine throws an immediate exception during the semantic analysis phase.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-version-decoder-cheat-sheet&quot;&gt;The Version Decoder Cheat Sheet&lt;/h3&gt;
&lt;p&gt;Java’s compiler doesn't map version numbers logically to their public marketing names. When the Gradle stack trace throws a decimal number at you, use this index to figure out what version your code is demanding:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Class file major version 66.0&lt;/strong&gt; = Requires Java 22&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Class file major version 65.0&lt;/strong&gt; = Requires Java 21 (Common in newer 2025/2026 native modules)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Class file major version 61.0&lt;/strong&gt; = Requires Java 17 (The baseline standard for modern React Native)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Class file major version 55.0&lt;/strong&gt; = Requires Java 11 (Legacy)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If your terminal says &lt;code&gt;wrong version 65.0, should be 61.0&lt;/code&gt;, your build script encountered a dependency compiled for JDK 21, but your system is executing the build toolchain using JDK 17.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;step-1-find-the-exact-path-to-your-target-jdk&quot;&gt;Step 1: Find the Exact Path to Your Target JDK&lt;/h3&gt;
&lt;p&gt;Before making adjustments, you need to locate where the correct JDK version is actually installed on your machine. &lt;/p&gt;
&lt;h4 id=&quot;on-macos&quot;&gt;On macOS&lt;/h4&gt;
&lt;p&gt;If you use Homebrew or standard installers, your JDK runtimes are stored under the virtual machine path. You can list all installed environments by running:&lt;br&gt;
&lt;code&gt;/usr/libexec/java_home -V&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Copy the path to the required version. It will look similar to this:&lt;br&gt;
&lt;code&gt;/Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home&lt;/code&gt;&lt;/p&gt;
&lt;h4 id=&quot;on-windows&quot;&gt;On Windows&lt;/h4&gt;
&lt;p&gt;By default, standard Java installers or Android Studio bundle their JDK environments inside program directories. Check these common locations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;C:\Program Files\Java\jdk-17\&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;C:\Program Files\Android\Android Studio\jbr\&lt;/code&gt; (Android Studio's embedded Java runtime)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;step-2-apply-the-explicit-project-override-recommended-&quot;&gt;Step 2: Apply the Explicit Project Override (Recommended)&lt;/h3&gt;
&lt;p&gt;Instead of fighting global system environment variables that might break older projects on your machine, you can force this specific React Native project to use the correct runtime directory.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Navigate to your project's native android folder: &lt;code&gt;cd android&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Open the &lt;code&gt;gradle.properties&lt;/code&gt; file.&lt;/li&gt;
&lt;li&gt;Add the following property at the bottom, supplying the absolute path you copied in Step 1:&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;org.gradle.java.home=/Insert/Your/Actual/Target/JDK/Path/Here&lt;/p&gt;
&lt;p&gt;&lt;em&gt;(Note: Windows users must escape backslashes in this file, format it like: &lt;code&gt;C:\\Program Files\\Java\\jdk-17&lt;/code&gt;)&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;step-3-align-android-studio-with-your-terminal&quot;&gt;Step 3: Align Android Studio with Your Terminal&lt;/h3&gt;
&lt;p&gt;A massive source of frustration is when the project builds fine in the terminal via CLI, but crashes the second you open it in Android Studio (or vice versa). You must align the IDE's build path:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Open your project folder inside &lt;strong&gt;Android Studio&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Navigate to &lt;strong&gt;Settings&lt;/strong&gt; (or &lt;strong&gt;Preferences&lt;/strong&gt; on macOS) &amp;gt; &lt;strong&gt;Build, Execution, Deployment&lt;/strong&gt; &amp;gt; &lt;strong&gt;Build Tools&lt;/strong&gt; &amp;gt; &lt;strong&gt;Gradle&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Locate the &lt;strong&gt;Gradle JDK&lt;/strong&gt; dropdown menu.&lt;/li&gt;
&lt;li&gt;Change it from the default system runtime to match the exact version your project requires (e.g., JDK 17 or JDK 21).&lt;/li&gt;
&lt;li&gt;Click Apply and hit &lt;strong&gt;Sync Project with Gradle Files&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h3 id=&quot;step-4-clear-the-aggressive-build-cache&quot;&gt;Step 4: Clear the Aggressive Build Cache&lt;/h3&gt;
&lt;p&gt;Gradle caches compilation footprints deeply. If you change your Java version without clearing the cache, Gradle will try to read the old, mismatched class files and throw the same error again. &lt;/p&gt;
&lt;p&gt;Wipe the execution deck completely by running these commands in your root directory:&lt;/p&gt;
&lt;h2 id=&quot;clean-the-native-android-directories&quot;&gt;Clean the native android directories&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cd android
./gradlew clean
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;return-to-root-and-clear-bundler-caches&quot;&gt;Return to root and clear bundler caches&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cd ..
npx react-native start --clear
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once the development server reboots and paths match up seamlessly across your terminal, properties file, and IDE, run your run command (npx react-native run-android or npx expo run:android) and the version mismatch error will be gone.&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/19548/how-to-fix-gradle-error-unsupported-class-file-major-version-react-native-expo</guid>
<pubDate>Wed, 03 Jun 2026 08:49:58 +0000</pubDate>
</item>
<item>
<title>Fixing Gradle TLS handshake_failure on Maven Central (Android Builds)</title>
<link>https://coderlegion.com/19512/fixing-gradle-tls-handshakefailure-on-maven-central-android-builds</link>
<description>&lt;p&gt;If your Android native build is constantly failing with a TLS handshake error when trying to download dependencies from Maven Central, the root cause might be an invisible environment variable polluting your Java settings. &lt;/p&gt;
&lt;p&gt;The most frustrating part of this error is that your local internet connection is usually completely fine. &lt;/p&gt;
&lt;h3 id=&quot;the-quick-diagnosis&quot;&gt;The Quick Diagnosis&lt;/h3&gt;
&lt;p&gt;Open your terminal and test the connection directly to the Maven repository using curl:&lt;/p&gt;
&lt;p&gt;curl -I &lt;a rel=&quot;nofollow&quot; href=&quot;https://repo1.maven.org/maven2/&quot;&gt;https://repo1.maven.org/maven2/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;If curl connects successfully and returns an HTTP 200, but your Gradle build still throws a &lt;code&gt;Received fatal alert: handshake_failure&lt;/code&gt;, your network is not the problem. Gradle itself is failing to establish the secure line.&lt;/p&gt;
&lt;h3 id=&quot;the-root-cause&quot;&gt;The Root Cause&lt;/h3&gt;
&lt;p&gt;An environment variable called &lt;code&gt;GRADLE_OPTS&lt;/code&gt; is likely injecting broken SSL or JSSE flags into your build process. Specifically, flags like &lt;code&gt;-Djavax.net.ssl.trustStoreType=WINDOWS-ROOT&lt;/code&gt; force Gradle to look at system-specific certificate stores that conflict with standard Java behavior, breaking the handshake completely.&lt;/p&gt;
&lt;h3 id=&quot;the-fix&quot;&gt;The Fix&lt;/h3&gt;
&lt;p&gt;To resolve this permanently, you need to wipe out the polluted configuration so Gradle can fall back to its clean JDK defaults.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Open your system &lt;strong&gt;Environment Variables&lt;/strong&gt; settings.&lt;/li&gt;
&lt;li&gt;Check both the &lt;strong&gt;User variables&lt;/strong&gt; and the &lt;strong&gt;System/Machine variables&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Look for &lt;code&gt;GRADLE_OPTS&lt;/code&gt;. &lt;/li&gt;
&lt;li&gt;Delete the variable entirely from both locations.&lt;/li&gt;
&lt;li&gt;Close your IDE, restart your terminal, and run your build again.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Once those injected flags are gone, Gradle will use standard JDK defaults, and your dependency downloads will start succeeding immediately. &lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;I've been spending a lot of my spare time building out a diagnostic tool over at &lt;a rel=&quot;nofollow&quot; href=&quot;https://fix-my-error.vercel.app&quot;&gt;FixMyError&lt;/a&gt; to map out environment bugs like this automatically. I just put a live, free sandbox preview on the homepage if you want to see how the engine breaks down terminal stack traces before you ever make an account.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/19512/fixing-gradle-tls-handshakefailure-on-maven-central-android-builds</guid>
<pubDate>Tue, 02 Jun 2026 15:56:09 +0000</pubDate>
</item>
<item>
<title>The Easiest Way to Integrate pawaPay in Your Java Spring Boot App</title>
<link>https://coderlegion.com/19406/the-easiest-way-to-integrate-pawapay-in-your-java-spring-boot-app</link>
<description>&lt;p&gt;Building a high-throughput mobile money gateway is often romanticized as a simple REST API integration. The reality, however, is a brutal landscape of undocumented MNO network drops, complex cryptographic signature requirements, and terrifying double-spend vulnerabilities. When development teams attempt to hand-roll these integrations using standard Java HTTP clients like OkHttp or RestTemplate, they inadvertently construct a fragile house of cards. They pull highly sensitive environment secrets directly into the JVM heap to manually compute &lt;code&gt;Signature-Input&lt;/code&gt; headers, exposing their financial infrastructure to memory scraping and reverse-engineering. Furthermore, relying on blocking I/O to wait for unpredictable African telecom networks guarantees that thread pools will eventually exhaust, triggering cascading outages across the entire application architecture.&lt;/p&gt;
&lt;p&gt;This is the integration illusion. You think you are building a simple payment gateway, but you are actually attempting to build a secure, fault-tolerant, state-managing cryptographic vault from scratch.&lt;/p&gt;
&lt;p&gt;We recognized that engineering teams do not have months to waste debugging manual boilerplate. You need speed, concurrency, and ironclad security from day one. To solve this, we engineered the pawaPay Java SDK—a completely new paradigm for mobile money integration that shifts the heavy lifting away from your application logic and into a compiled, memory-safe environment. By adopting this architecture, you can execute non-blocking, asynchronous mobile money deposits and payouts in under five minutes.&lt;/p&gt;
&lt;h5 id=&quot;step-one-securing-the-artifact&quot;&gt;Step One: Securing the Artifact&lt;/h5&gt;
&lt;p&gt;The foundation of a secure integration begins with your dependency management. We distribute the official package directly through Maven Central, ensuring you receive cryptographically verified, unaltered binaries. You can view the live artifact registry at &lt;a rel=&quot;nofollow&quot; href=&quot;https://central.sonatype.com/artifact/com.katorymnd/pawapay-java-sdk&quot;&gt;https://central.sonatype.com/artifact/com.katorymnd/pawapay-java-sdk&lt;/a&gt; to verify the latest release versions.&lt;/p&gt;
&lt;p&gt;To bring the native power of the SDK into your project, simply declare it within your &lt;code&gt;pom.xml&lt;/code&gt; configuration file.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;com.katorymnd&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;pawapay-java-sdk&amp;lt;/artifactId&amp;gt;
    &amp;lt;version&amp;gt;LATEST_VERSION&amp;lt;/version&amp;gt;
&amp;lt;/dependency&amp;gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once synced, you are no longer just importing a library; you are embedding a highly optimized Java Native Interface (JNI) vault engineered in Rust. This core will handle all payload serialization and cryptographic signing entirely outside of the standard Java memory space, neutralizing standard de-compilation threats immediately.&lt;/p&gt;
&lt;h5 id=&quot;step-two-zero-trust-configuration&quot;&gt;Step Two: Zero-Trust Configuration&lt;/h5&gt;
&lt;p&gt;We engineer under the assumption that application state can be volatile, which is why we enforce a strict, zero-trust initialization process. There are no fallback secrets, and there are no dummy variables allowed. Your master API tokens and your specific Katorymnd license key must be injected via strict environment variables. If these are missing or malformed, the SDK aborts the boot sequence instantly to protect your system from executing unauthenticated requests.&lt;/p&gt;
&lt;p&gt;To wire this into a Spring Boot application, you construct a dedicated &lt;code&gt;@Configuration&lt;/code&gt; class. This class uses our &lt;code&gt;Config.Builder&lt;/code&gt; to construct the environment parameters and boot the &lt;code&gt;ApiClient&lt;/code&gt; securely.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;import com.katorymnd.pawapay.sdk.api.ApiClient;
import com.katorymnd.pawapay.sdk.config.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class PawaPayGatewayConfig {

    @Bean
    public ApiClient pawaPayClient() {
        String apiKey = System.getenv(&quot;PAWAPAY_API_TOKEN&quot;);
        String licenseKey = System.getenv(&quot;KATORYMND_PAWAPAY_SDK_LICENSE_KEY&quot;);

        Config config = new Config.Builder()
            .apiKey(apiKey)
            .environment(&quot;production&quot;)
            .timeout(30000)
            .build();

        return new ApiClient(config, licenseKey, true, &quot;v2&quot;);
    }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By defining the &lt;code&gt;ApiClient&lt;/code&gt; as a Spring Bean, you ensure the native vault is loaded exactly once during the application lifecycle. The vault establishes the secure connection pool, locks in the hardware anchoring, and remains resident in memory, ready to process thousands of concurrent requests without the overhead of re-initialization.&lt;/p&gt;
&lt;h5 id=&quot;step-three-the-reactive-endpoint&quot;&gt;Step Three: The Reactive Endpoint&lt;/h5&gt;
&lt;p&gt;The true architectural elegance of this integration reveals itself at the routing layer. Because the SDK was built specifically for modern, high-throughput environments, every core method returns a &lt;code&gt;CompletableFuture&lt;/code&gt;. This maps natively to Project Reactor, allowing you to build completely non-blocking Spring WebFlux endpoints.&lt;/p&gt;
&lt;p&gt;When a payload arrives, your application threads do not wait. The request is handed to the native core, and the thread is immediately liberated back to the pool to handle other users.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;import com.katorymnd.pawapay.sdk.api.ApiClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;

import java.util.Map;

import static org.springframework.web.reactive.function.server.RequestPredicates.POST;
import static org.springframework.web.reactive.function.server.RouterFunctions.route;

@Configuration
public class DepositController {

    @Bean
    public RouterFunction&amp;lt;ServerResponse&amp;gt; handleDeposit(ApiClient pawaPayClient) {
        return route(POST(&quot;/api/v2/payments/deposit&quot;), request -&amp;gt; 
            request.bodyToMono(Map.class)
                .flatMap(payload -&amp;gt; Mono.fromFuture(
                    pawaPayClient.initiateDepositV2(
                        payload.get(&quot;transactionId&quot;).toString(),
                        payload.get(&quot;amount&quot;).toString(),
                        payload.get(&quot;currency&quot;).toString(),
                        payload.get(&quot;phone&quot;).toString(),
                        payload.get(&quot;provider&quot;).toString(),
                        &quot;Spring WebFlux Integration&quot;,
                        null, null, null
                    )
                ))
                .flatMap(response -&amp;gt; ServerResponse.ok().bodyValue(response))
                .onErrorResume(error -&amp;gt; ServerResponse.badRequest().bodyValue(Map.of(&quot;error&quot;, error.getMessage())))
        );
    }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In just three steps, you have bypassed weeks of manual REST implementation. You possess an endpoint that can absorb massive MNO latency spikes without crashing your server, backed by a cryptographic engine that mathematically guarantees your payload integrity.&lt;/p&gt;
&lt;h5 id=&quot;validating-the-architecture&quot;&gt;Validating the Architecture&lt;/h5&gt;
&lt;p&gt;Before writing a single line of your own code, you can visually verify this exact execution flow. We have exposed our internal, Javalin-based disposable execution environment to the public. By visiting the live playground at &lt;a rel=&quot;nofollow&quot; href=&quot;https://katorymnd.dev/pawapay-demo/java/&quot;&gt;https://katorymnd.dev/pawapay-demo/java/&lt;/a&gt;, you can input your sandbox tokens and watch the native core process deposits, handle automated config syncs, and securely fetch MNO availability in real-time. It provides a transparent, terminal-level view of the exact security validations happening beneath the surface of the SDK.&lt;/p&gt;
&lt;h5 id=&quot;transitioning-to-enterprise-production&quot;&gt;Transitioning to Enterprise Production&lt;/h5&gt;
&lt;p&gt;While the artifact is freely available on Maven Central for integration testing, deploying this architecture into a live financial environment requires absolute operational certainty. For businesses processing real capital, we offer the Premium License infrastructure detailed at &lt;a rel=&quot;nofollow&quot; href=&quot;https://katorymnd.com/pawapay-payment-sdk/java&quot;&gt;https://katorymnd.com/pawapay-payment-sdk/java&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Securing a premium license unlocks critical production features, including custom domain locking to prevent your API keys from being used outside of authorized IP addresses, emergency technical support SLAs, and architectural consultation for your specific database synchronization strategy. It is the definitive path for teams that refuse to compromise on gateway resilience.&lt;/p&gt;
&lt;p&gt;Stop wrestling with clunky HTTP clients and vulnerable cryptographic libraries. Cut the boilerplate, implement the native SDK, and secure your mobile money infrastructure today.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/19406/the-easiest-way-to-integrate-pawapay-in-your-java-spring-boot-app</guid>
<pubDate>Mon, 01 Jun 2026 10:08:14 +0000</pubDate>
</item>
<item>
<title>Are developers underestimating the browser?</title>
<link>https://coderlegion.com/19357/are-developers-underestimating-the-browser</link>
<description>&lt;p&gt;Think about your current frontend workflow. We constantly upgrade our libraries, our bundlers, and our deployment pipelines. We swap out transpilers to save fractions of a second.&lt;/p&gt;
&lt;p&gt;But when was the last time we stopped to appreciate the sheer engineering magnitude of the browser itself?&lt;/p&gt;
&lt;p&gt;The modern browser is a heavily optimized, JIT-compiled, hardware-accelerated powerhouse. Yet, when we reach for a web IDE or a live code editor to quickly test a concept, the platforms we use often feel sluggish. They are wrapped in layers of unnecessary abstraction.&lt;/p&gt;
&lt;p&gt;I’ve been building NitroIDE to try and solve this by creating an online IDE that acts as a direct interface to the browser's native power, rather than fighting it. But I want to know what the community thinks.&lt;/p&gt;
&lt;p&gt;Why do we accept heavy, bloated development environments when the underlying engine (the browser) is incredibly fast? Have we lost sight of building lightweight, efficient tools in favor of adding more complexity? Thoughts?&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/19357/are-developers-underestimating-the-browser</guid>
<pubDate>Sun, 31 May 2026 15:34:40 +0000</pubDate>
</item>
<item>
<title>Public key retrieval is not allowed / Needs improvement</title>
<link>https://coderlegion.com/18848/public-key-retrieval-is-not-allowed-needs-improvement</link>
<description>&lt;p&gt;&lt;strong&gt;Today, in this article we will discuss the error message &quot;Public key retrieval is not allowed&quot;. This error &lt;span style=&quot;color: rgb(255, 4, 252);&quot;&gt;message is indicating that the system is configured to disallow the retrieval of a public key&lt;/span&gt;. This is likely a security measure &lt;span style=&quot;color: rgb(255, 4, 252);&quot;&gt;to prevent unauthorized access&lt;/span&gt; to sensitive information. It is &lt;span style=&quot;color: rgb(255, 4, 252);&quot;&gt;recommended to check with the administrator&lt;/span&gt; or technical support for further assistance.&lt;/strong&gt;&lt;/p&gt;
&lt;div&gt;
&lt;h4&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;&lt;strong&gt;Table of Contents: &lt;a class=&quot;open&quot;&gt;&lt;/a&gt;&lt;/strong&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;div&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;#what&quot; rel=&quot;nofollow&quot;&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;&lt;span data-sheets-value=&quot;{&quot; data-sheets-userformat=&quot;{&quot;&gt;What does Error &quot;Public key retrieval is not allowed&quot; mean?&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#why&quot; rel=&quot;nofollow&quot;&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;Why is the Error Generated?&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#sol&quot; rel=&quot;nofollow&quot;&gt;How to Solve the Error?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#con&quot; rel=&quot;nofollow&quot;&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;The Conclusion&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;&lt;a href=&quot;#con&quot; rel=&quot;nofollow&quot;&gt;The References&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;section&gt;&lt;/section&gt;
&lt;section&gt;
&lt;h2&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;&lt;strong&gt;What does error &quot;Public key retrieval is not allowed&quot; mean?&lt;/strong&gt;&lt;/span&gt;&lt;strong&gt; &lt;a id=&quot;TypeError-str-object&quot; style=&quot;color: blue;&quot; href=&quot;#what&quot; rel=&quot;nofollow&quot;&gt;#&lt;/a&gt;&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;The error message &quot;Public key retrieval is not allowed&quot; usually &lt;strong&gt;occurs when an application or service is trying to access a public key&lt;/strong&gt; from the server it is trying to connect to, but the operation is &lt;strong&gt;not allowed due to security or access control measures&lt;/strong&gt; that prevent the retrieval of the public key.&lt;/p&gt;
&lt;p&gt;In cryptography, public keys are used for encryption and digital signatures, while private keys are used for decryption and signing. When &lt;strong&gt;you attempt to connect to a server&lt;/strong&gt;, the system must first retrieve the server's public key &lt;strong&gt;to establish a secure connection&lt;/strong&gt;. If the &lt;strong&gt;server's public key cannot be retrieved&lt;/strong&gt;, the system may &lt;strong&gt;not be able to establish a secure connection&lt;/strong&gt;, and the error message &lt;strong&gt;&quot;Public key retrieval is not allowed&quot; will be displayed&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The cause of this error can vary. This &lt;strong&gt;may happen be due to a firewall, network restriction&lt;/strong&gt;, or a policy set by the system administrator. To resolve this issue, you may &lt;strong&gt;need to contact your system administrator&lt;/strong&gt; or network administrator to request access to the public key &lt;strong&gt;or check the firewall&lt;/strong&gt; or network configurations.&lt;/p&gt;
&lt;/section&gt;
&lt;section&gt;
&lt;h2&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;&lt;strong&gt;Why is the Error Generated? &lt;/strong&gt;&lt;/span&gt;&lt;strong&gt;&lt;a id=&quot;reasons-that-will-cause&quot; style=&quot;color: blue;&quot; href=&quot;#why&quot; rel=&quot;nofollow&quot;&gt;#&lt;/a&gt;&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;The &quot;Public key retrieval is not allowed&quot; error message can occur due to various causes. Some of the most common causes are:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Firewall restriction:&lt;/strong&gt; Your system may not be able to retrieve the server's public key if the connection is being blocked by a firewall. The firewall may be configured to block connections to the server or to block the retrieval of the public key.&lt;br&gt;For example; you are trying to connect to a server over a secure connection using SSL/TLS, but the firewall is blocking the connection.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Network restriction:&lt;/strong&gt; The system may not be able to retrieve the server's public key if there are restrictions in place on the network that prevent the retrieval. This could be due to a policy set by the network administrator or due to a misconfiguration of the network.&lt;br&gt;Say, you try to retrieve the public key of a server that is located in a different country, but the connection is blocked by the network administrator due to security policies. Then you will receives the error message &quot;Public key retrieval is not allowed.&quot;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Access control measures:&lt;/strong&gt; The server may have security measures in place that prevent unauthorized access to the public key. This could be due to a policy set by the system administrator or due to security software installed on the server e.g. you are trying to connect to a server that has security measures in place to prevent unauthorized access to the public key.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public class MySQLConnectionExample {

  public static void main(String[] args) {
    try {
      // Load the MySQL Connector/J driver
      Class.forName(&quot;com.mysql.cj.jdbc.Driver&quot;);

      // Establish a connection to the MySQL server
      Connection conn = DriverManager.getConnection(
        &quot;jdbc:mysql://localhost:3306/test_db?useSSL=true&quot;,
        &quot;username&quot;,
        &quot;password&quot;
      );

      System.out.println(&quot;Connected to the MySQL server successfully!&quot;);

      // Close the connection
      conn.close();
    } catch (SQLException | ClassNotFoundException e) {
      System.out.println(&quot;An error occurred while connecting to the MySQL server: &quot; + e.getMessage());
    }
  }
}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this example, we first load the MySQL Connector/J driver using the &lt;code&gt;Class.forName&lt;/code&gt; method. Then, we use the &lt;code&gt;DriverManager.getConnection&lt;/code&gt; method to establish a connection to the MySQL server. The connection URL includes the &lt;code&gt;useSSL=true&lt;/code&gt; parameter, which indicates that we want to use SSL/TLS to secure the connection. We also specify the username and password to use when connecting to the server.&lt;br&gt;Here when the client is unable to retrieve the public key an error occurs, an exception will be thrown and an error message will be displayed.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;IP/domain restrictions:&lt;/strong&gt; The server may be configured to only allow public key retrieval from specific IP addresses or domains. If you are not on the approved list, it may not be able to retrieve the public key.&lt;br&gt;An example is that you are trying to connect to a server that is configured to only allow public key retrieval from specific IP addresses or domains and your system is not on the approved list, so the will receive the error.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Incorrect server configuration:&lt;/strong&gt; The server may be misconfigured, which could prevent the retrieval of the public key. This could be due to an error in the server's settings or due to an incorrect configuration of the server's software. For example, you try to connect to a server that is misconfigured, which prevents the retrieval of the public key.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Certificate issues:&lt;/strong&gt; The server may have an expired or revoked certificate, which could prevent the retrieval of the public key. In this case, the system may not be able to establish a secure connection with the server. For example, you will try to connect to a server that has an expired or revoked certificate, which prevents the retrieval of the public key.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt; &lt;/p&gt;
&lt;div class=&quot;div-purple&quot;&gt;&lt;span class=&quot;alert-header&quot;&gt;Note&lt;/span&gt; &lt;span class=&quot;alert-body&quot;&gt;It is important to understand the cause of the &quot;Public key retrieval is not allowed&quot; error in order to resolve it effectively. Depending on the cause, you may need to make apply the appropiate solution.&lt;/span&gt;&lt;/div&gt;
&lt;/section&gt;
&lt;section&gt;&lt;a id=&quot;sol&quot; class=&quot;custom_anchor&quot;&gt;&lt;/a&gt;&lt;/section&gt;
&lt;section&gt;
&lt;h2&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;&lt;strong&gt;How to Solve the Error? &lt;/strong&gt;&lt;/span&gt;&lt;strong&gt;&lt;a id=&quot;the-solution&quot; style=&quot;color: blue;&quot; href=&quot;#sol&quot; rel=&quot;nofollow&quot;&gt;#&lt;/a&gt;&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;The possible solutions for the causes of the &quot;Public key retrieval is not allowed&quot; error message are as follows:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Firewall restriction:&lt;/strong&gt; To resolve this issue, you can check the firewall configuration and ensure that the connection to the server is not being blocked. You may also need to open specific ports or add an exception to the firewall to allow the connection.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Network restriction:&lt;/strong&gt; To resolve this issue, you may need to contact the network administrator and request that the restriction be lifted or that the necessary changes be made to the network configuration.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Access control measures:&lt;/strong&gt; To resolve this issue, you may need to request access to the public key from the system administrator or the security software provider. The system administrator may need to change the security measures in place to allow you to retrieve the public key. Like, the error in previous coding example occurs due to &lt;code&gt;useSSL=true&lt;/code&gt; parameter. Therefore, we change it to &lt;code&gt;useSSL=false&lt;/code&gt; as done below.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public class MySQLConnectionExample {

  public static void main(String[] args) {
    try {
      // Load the MySQL Connector/J driver
      Class.forName(&quot;com.mysql.cj.jdbc.Driver&quot;);

      // Establish a connection to the MySQL server
      Connection conn = DriverManager.getConnection(
        &quot;jdbc:mysql://localhost:3306/test_db?useSSL=false&quot;,
        &quot;username&quot;,
        &quot;password&quot;
      );

      System.out.println(&quot;Connected to the MySQL server successfully!&quot;);

      // Close the connection
      conn.close();
    } catch (SQLException | ClassNotFoundException e) {
      System.out.println(&quot;An error occurred while connecting to the MySQL server: &quot; + e.getMessage());
    }
  }
}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you change the value of &lt;code&gt;useSSL&lt;/code&gt; in the connection string from &lt;code&gt;true&lt;/code&gt; to &lt;code&gt;false&lt;/code&gt;, then the connection will no longer attempt to use SSL encryption. This change can resolve the &quot;Public key retrieval is not allowed&quot; error if the issue was caused by a problem with the server's SSL certificate.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;IP/domain restrictions:&lt;/strong&gt; To resolve this issue, one may need to check the server configuration and ensure that your IP address or domain is on the approved list. If the IP address or domain is not on the approved list, you may need to request that it be added.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Incorrect server configuration:&lt;/strong&gt; To resolve this issue, you may need to check the server configuration and make any necessary changes to ensure that the server is configured correctly. You may also need to check the server software and ensure that it is installed and configured correctly.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Certificate issues:&lt;/strong&gt; To resolve this issue, you may need to check the server certificate and ensure that it is valid and up-to-date. If the certificate is expired or revoked, you may need to request a new certificate from a trusted certificate authority.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
&lt;section&gt;&lt;/section&gt;
&lt;section&gt;&lt;a id=&quot;con&quot; class=&quot;custom_anchor&quot;&gt;&lt;/a&gt;
&lt;h2&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;&lt;strong&gt;The Conclusion&lt;/strong&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;In conclusion, the &quot;Public key retrieval is not allowed&quot; &lt;strong&gt;error message indicates that the your are unable to retrieve the server's public key&lt;/strong&gt;, which is &lt;strong&gt;required to establish a secure connection&lt;/strong&gt; using SSL/TLS. This error can be &lt;strong&gt;caused by various factors&lt;/strong&gt;, including firewall restrictions, network restrictions, access control measures, IP/domain restrictions, incorrect server configuration, and certificate issues. To resolve the issue you may need to &lt;strong&gt;check for the cause&lt;/strong&gt; of the error in your specific situation and &lt;strong&gt;apply the suitable solution&lt;/strong&gt;.&lt;/p&gt;
&lt;/section&gt;
&lt;section&gt;&lt;a id=&quot;ref&quot; class=&quot;custom_anchor&quot;&gt;&lt;/a&gt;
&lt;h2&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;&lt;strong&gt;The References&lt;/strong&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;Some resources for reference or further information are given below:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;MySQL Connector/J documentation:&lt;br&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://dev.mysql.com/doc/connector-j/8.0/en/connector-j-reference-using-ssl.html&quot; target=&quot;_new&quot;&gt;https://dev.mysql.com/doc/connector-j/8.0/en/connector-j-reference-using-ssl.html&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Java API documentation:&lt;br&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.oracle.com/en/java/javase/14/docs/api/java.sql/java/sql/Driver.html&quot; target=&quot;_new&quot;&gt;https://docs.oracle.com/en/java/javase/14/docs/api/java.sql/java/sql/Driver.html&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;MySQL official forums:&lt;br&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://forums.mysql.com/&quot; target=&quot;_new&quot;&gt;https://forums.mysql.com/&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/18848/public-key-retrieval-is-not-allowed-needs-improvement</guid>
<pubDate>Mon, 25 May 2026 04:13:39 +0000</pubDate>
</item>
<item>
<title>IBM Bob Turned a 30-Day Java Upgrade Into a 3-Day Sprint</title>
<link>https://coderlegion.com/18337/ibm-bob-turned-a-30-day-java-upgrade-into-a-3-day-sprint</link>
<description>&lt;p&gt;Java modernization is one of those tasks that sits in the backlog for years. Everyone knows it needs to happen. Nobody wants to be the team that breaks production by doing it.&lt;/p&gt;
&lt;p&gt;Blue Pearl, a South African IT consultancy, was in exactly that position. Their flagship product, Blue App, is a talent-matching platform used by roughly 26,000 consultants and freelancers, as well as their enterprise clients. The application was built in Java 11 back in 2020, and by the time the team got serious about modernizing it, the codebase had accumulated years of technical debt.&lt;/p&gt;
&lt;p&gt;The list was long: outdated API calls, more than 50 outdated dependencies, known CVEs, no automated test coverage, and an infrastructure design that no longer matched what the platform actually needed. A traditional modernization effort would have required 30-plus person-days of dedicated engineering work — and carried a serious risk of regression on a platform that nearly 30,000 users depended on.&lt;/p&gt;
&lt;p&gt;They used IBM Bob instead. The project took three days.&lt;/p&gt;
&lt;h3 id=&quot;what-the-team-actually-did&quot;&gt;What the Team Actually Did&lt;/h3&gt;
&lt;p&gt;Before writing a single line of new code, the Blue Pearl team spent about three hours building context. They fed Bob architecture diagrams, AWS workflow documentation, endpoint definitions, inter-service dependencies, and data flow maps. They also included input from actual platform users about what they needed from the application.&lt;/p&gt;
&lt;p&gt;That upfront work mattered. Bob used it to produce a full dependency map of the codebase, surface deprecated API calls, highlight outdated libraries, and flag integration points that had never been formally documented.&lt;/p&gt;
&lt;p&gt;From there, Bob guided the team through the full transformation:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Migration from Java 11 to Java 21 LTS&lt;/li&gt;
&lt;li&gt;Adoption of modern Java features, including records, sealed classes, pattern matching, and virtual threads&lt;/li&gt;
&lt;li&gt;Spring Boot and Hibernate updates to Java 21-compatible releases&lt;/li&gt;
&lt;li&gt;Namespace migration from javax to jakarta&lt;/li&gt;
&lt;li&gt;Restructured services with cleaner interfaces and reduced coupling&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Security was part of the same pass, not a separate effort. Bob helped strengthen authentication and authorization logic, standardize input validation, and map sensitive data flows. It also flagged the lack of observability as an architectural risk and recommended adding structured JSON logging, distributed tracing with Micrometer and OpenTelemetry, and health check endpoints across service boundaries.&lt;/p&gt;
&lt;h3 id=&quot;the-results&quot;&gt;The Results&lt;/h3&gt;
&lt;p&gt;When the dust settled, Blue Pearl had a platform that passed scans from Semgrep, Snyk, and SonarQube on the first attempt. Post-deployment results:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Zero CVE-bearing dependencies&lt;/li&gt;
&lt;li&gt;92% regression test coverage, up from zero&lt;/li&gt;
&lt;li&gt;Zero production incidents after deployment&lt;/li&gt;
&lt;li&gt;100% functional equivalence with the legacy system&lt;/li&gt;
&lt;li&gt;Estimated 15% JVM performance improvement&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;No customer complaints. No rollbacks. The team immediately moved on to UI improvements and new features rather than cleaning up backend issues.&lt;/p&gt;
&lt;h3 id=&quot;how-bob-handled-the-heavy-lifting&quot;&gt;How Bob Handled the Heavy Lifting&lt;/h3&gt;
&lt;p&gt;One thing worth understanding is how Bob approaches this task technically. It doesn't rely on a single model. Bob dynamically routes each task to a suitable model based on accuracy, performance, and cost, drawing from a mix of frontier models, including Anthropic Claude, Mistral, and IBM Granite, as well as specialized fine-tuned models for code reasoning and security analysis.&lt;/p&gt;
&lt;p&gt;Simpler completions go to lighter models. More complex analysis and transformation tasks go to more capable ones. The goal is better output at lower cost — and the developer doesn't have to manage any of that routing manually.&lt;/p&gt;
&lt;h3 id=&quot;the-pattern-that-made-it-work&quot;&gt;The Pattern That Made It Work&lt;/h3&gt;
&lt;p&gt;Bob didn't replace engineering judgment on this project. The pattern that worked was: context first, Bob second, senior developer validation third. Every recommendation Bob made was reviewed by a senior developer before implementation. Testing and internal UAT were followed before anything went to production.&lt;/p&gt;
&lt;p&gt;That's an important detail for anyone evaluating AI-assisted modernization. Bob accelerated the discovery, planning, and transformation phases. But experienced engineers stayed accountable for what shipped.&lt;/p&gt;
&lt;h3 id=&quot;why-this-matters-for-your-backlog&quot;&gt;Why This Matters for Your Backlog&lt;/h3&gt;
&lt;p&gt;Most development teams have at least one version of this problem sitting somewhere. A legacy Java app. An undocumented codebase. A modernization project that's been &quot;almost ready to start&quot; for two years.&lt;/p&gt;
&lt;p&gt;The Blue Pearl project is a useful proof point — not because the numbers are impressive, but because the approach is repeatable. Context in, Bob analyzes and recommends, and the senior engineer validates. The dependency mapping that would have taken multiple sprints was done in hours. The automated test baseline that didn't exist now provides a regression safety net for every future change.&lt;/p&gt;
&lt;p&gt;That shifts where engineering effort actually goes. Less time on code archaeology and regression anxiety. More time on product work that users notice.&lt;/p&gt;
&lt;p&gt;IBM Bob is now available at &lt;a rel=&quot;nofollow&quot; href=&quot;https://bob.ibm.com&quot;&gt;bob.ibm.com&lt;/a&gt;, with a 30-day free trial for teams who want to see what it can do with their own codebase.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/18337/ibm-bob-turned-a-30-day-java-upgrade-into-a-3-day-sprint</guid>
<pubDate>Tue, 19 May 2026 15:44:59 +0000</pubDate>
</item>
<item>
<title>How many constructors can a class have? (Java)</title>
<link>https://coderlegion.com/18196/how-many-constructors-can-a-class-have-java</link>
<description>&lt;p&gt;&lt;strong&gt;A class in Java is a blueprint for creating objects that defines the properties and behaviors of the objects. When a new object is created from a class, it is called an instance of that class. Java provides a special method called a &lt;span style=&quot;color: rgb(185, 106, 217);&quot;&gt;constructor&lt;/span&gt; to initialize the values of the object's attributes when it is created. A &lt;span style=&quot;color: rgb(185, 106, 217);&quot;&gt;constructor&lt;/span&gt; is called automatically when a new object is created, and it is used to set the initial values of the object's attributes.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;In this article, we will discuss &lt;span style=&quot;color: rgb(185, 106, 217);&quot;&gt;how many constructors a class can have&lt;/span&gt; in Java and reasons why we need multiple constructors.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;div id=&quot;tc-div&quot;&gt;
&lt;h4&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;&lt;strong&gt;Table of Contents &lt;a id=&quot;tc-toogle&quot; class=&quot;open&quot;&gt;&lt;/a&gt;&lt;/strong&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;div id=&quot;tc-list&quot;&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#hom&quot;&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;How many constructors can a class have?&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#why&quot;&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;Why do we need multiple constructors?&lt;/span&gt;&lt;/a&gt;
&lt;/li&gt;&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#con&quot;&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;Conclusion&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#ref&quot;&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;Reference&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;&lt;/div&gt;&lt;/div&gt;
&lt;section&gt;
&lt;a id=&quot;hom&quot; class=&quot;custom_anchor&quot;&gt;&lt;/a&gt; &lt;h2&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;&lt;strong&gt;1. How many constructors can a class have?&lt;/strong&gt;&lt;/span&gt; &lt;a id=&quot;how_many&quot; rel=&quot;nofollow&quot; href=&quot;#hom&quot; style=&quot;color: blue&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt; In Java, a class can have multiple constructors, with each constructor having a different number or type of parameters. But what is limit for the number of constructors a class can have? The simple answer is there's no limit. &lt;/p&gt;
&lt;p&gt;The Java documentation does not impose any specific limit on the number of constructors a class can have. It states that &quot;a constructor is a special type of method that is used to initialize objects,&quot; and there is no mention of any specific limit on the number of constructors a class can have.&lt;/p&gt;
&lt;div class=&quot;div-faq&quot;&gt;
&lt;span class=&quot;faq-header&quot;&gt;FAQ&lt;/span&gt;
&lt;span class=&quot;faq-body&quot;&gt;
Q: Is it true that due to technical limit (not according to Java docs), Java class can only have a maximum of 65535 constructor?&lt;br&gt;
A: No, it is not true. The Java documentation does not impose any specific limit on the number of constructors a class can have. Thus, it is possible to have an unlimited number of constructors in a Java class.
&lt;/span&gt;
&lt;/div&gt;
&lt;br&gt;
&lt;p&gt;The specific limit on the number of constructors a class can have,  which many people thought that exist, is actually the limit of the size of a method.&lt;/p&gt;
&lt;div class=&quot;div-purple&quot;&gt;
&lt;span class=&quot;alert-header&quot;&gt;Note&lt;/span&gt;
&lt;span class=&quot;alert-body&quot;&gt;The Java Virtual Machine (JVM) has a limit on the size of a method, including constructors, which is 65535 bytes.&lt;/span&gt;
&lt;/div&gt;
&lt;p&gt;However, this limit is imposed on the compiled code, not on the number of constructors a class can have. It is unlikely that a single constructor would reach this limit, and even if it did, it could be refactored into smaller methods to avoid the limit.&lt;/p&gt;
&lt;p&gt;In conclusion, a class in Java can have multiple constructors with different parameters. The Java documentation does not impose any limit on the number of constructors a class can have, and the JVM limit on the size of a method is unlikely to affect the number of constructors a class can have.&lt;/p&gt;
&lt;/section&gt;
&lt;section&gt;
&lt;a id=&quot;why&quot; class=&quot;custom_anchor&quot;&gt;&lt;/a&gt; &lt;h2&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;&lt;strong&gt;2. Why do we need multiple constructor?&lt;/strong&gt;&lt;/span&gt; &lt;a id=&quot;why_need_multiple_constructor&quot; rel=&quot;nofollow&quot; href=&quot;#why&quot; style=&quot;color: blue&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The purpose of having multiple constructors is to provide the programmer with the ability to initialize an object in different ways, depending on the requirements of the program. &lt;/p&gt;
&lt;p&gt;Suppose you have a class &lt;b&gt;Person&lt;/b&gt; and you want to create an object of this class to represent a person's information. The person's information can be their &lt;i&gt;name&lt;/i&gt;, &lt;i&gt;age&lt;/i&gt;, and &lt;i&gt;address&lt;/i&gt;. However, in some cases, you might only have the person's &lt;i&gt;name&lt;/i&gt; and &lt;i&gt;age&lt;/i&gt;. In this scenario, you need two constructors: one that takes two parameters (name and age) and another that takes three parameters (name, age, and address).&lt;/p&gt;

&lt;p&gt;To declare multiple constructors in a class, you simply provide several constructors with different parameter lists. Each constructor must have a unique set of parameters. When you create a new object of the class, Java will call the constructor that matches the number and type of parameters that you provide.
&lt;/p&gt;


&lt;p&gt;The ability to have multiple constructors provides greater flexibility when creating objects and makes it easier to write clean and maintainable code. However, it's important to consider the complexity that comes with having too many constructors.&lt;/p&gt;
&lt;/section&gt;
&lt;section&gt;
&lt;a class=&quot;custom_anchor&quot;&gt;&lt;/a&gt; &lt;h2&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;&lt;strong&gt;3. Conclusion&lt;/strong&gt;&lt;/span&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#con&quot; style=&quot;color: blue&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In this article, we have discussed about how many constructors a class can have in Java and the reasons of having multiple constructors in a class. If you have any questions or comments, feel free to leave them below. Have a nice day!&lt;/p&gt;
&lt;/section&gt;
&lt;section&gt;
&lt;a class=&quot;custom_anchor&quot;&gt;&lt;/a&gt; &lt;h2&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;&lt;strong&gt;4. Reference&lt;/strong&gt;&lt;/span&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#ref&quot; style=&quot;color: blue&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;For further information, please visit:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Constructor in Java:&lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.oracle.com/javase/8/docs/api/java/lang/reflect/Constructor.html&quot;&gt; https://docs.oracle.com/javase/8/docs/api/java/lang/reflect/Constructor.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Java documentation has no specific limit of number of construtors: &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.oracle.com/en/java/javase/14/docs/api/java.base/java/lang/Class.html&quot;&gt; https://docs.oracle.com/en/java/javase/14/docs/api/java.base/java/lang/Class.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;JVM has a limit on the size of a method: &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.oracle.com/en/java/javase/14/docs/specs/jvms/se14/html/jvms-4.html#jvms-4.10&quot;&gt; https://docs.oracle.com/en/java/javase/14/docs/specs/jvms/se14/html/jvms-4.html#jvms-4.10&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/18196/how-many-constructors-can-a-class-have-java</guid>
<pubDate>Mon, 18 May 2026 06:21:29 +0000</pubDate>
</item>
<item>
<title>Actual and formal argument lists differ in length</title>
<link>https://coderlegion.com/17789/actual-and-formal-argument-lists-differ-in-length</link>
<description>&lt;p&gt;&lt;strong&gt;
    Today, we're going to discuss a &lt;em&gt;compilation error&lt;/em&gt; in Java: &lt;span style=&quot;color: rgb(185, 106, 217);&quot;&gt;&lt;em&gt;&quot;Actual and formal argument lists differ in length&quot;&lt;/em&gt;&lt;/span&gt;. This error is produced when the number of arguments passed in doesn't match the number of parameters defined in the method or constructor. In this study blog, we'll explore what's &lt;span style=&quot;color: rgb(185, 106, 217);&quot;&gt;&lt;em&gt;&quot;Actual and formal argument lists differ in length&quot;&lt;/em&gt;&lt;/span&gt;, why it happens and how to resolve it.
  &lt;/strong&gt;&lt;/p&gt;
&lt;div&gt;
  &lt;h4&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;&lt;strong&gt; Table of Contents: &lt;a class=&quot;open&quot;&gt;&lt;/a&gt;&lt;/strong&gt;&lt;/span&gt;&lt;/h4&gt;
  &lt;div&gt;
    &lt;ol&gt;
      &lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#_Toc123774610&quot;&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;What is &quot;Actual and formal argument lists differ in length&quot;&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#_Toc123774611&quot;&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;Causes&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#_Toc123774612&quot;&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;Solution&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#_Toc123774613&quot;&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;The Conclusion&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#_Toc123774614&quot;&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;The Reference&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;section&gt;
  &lt;h2&gt;&lt;a class=&quot;custom_anchor&quot; name=&quot;_Toc123774610&quot; id=&quot;_Toc123774610&quot;&gt;&lt;/a&gt;&lt;strong&gt;&lt;span style=&quot;font-family: arial, helvetica, sans-serif;&quot;&gt;What is &quot;Actual and formal argument lists differ in length&quot;&lt;/span&gt; &lt;a style=&quot;color: blue;&quot; rel=&quot;nofollow&quot; href=&quot;#_Toc123774610&quot;&gt;#&lt;/a&gt;&lt;/strong&gt;&lt;/h2&gt;
  &lt;p&gt;&lt;span style=&quot;font-family: arial, helvetica, sans-serif;&quot;&gt;
    First, let's understand what is &lt;em&gt;&lt;strong&gt;actual argument&lt;/strong&gt;&lt;/em&gt; and &lt;em&gt;&lt;strong&gt;formal argument&lt;/strong&gt;&lt;/em&gt;.
  &lt;/span&gt;&lt;/p&gt;
  &lt;ol&gt;
    &lt;li&gt;&lt;em&gt;&lt;strong&gt;Formal argument: &lt;/strong&gt;&lt;/em&gt; The arguments we &lt;strong&gt;define&lt;/strong&gt; for a method or constructor.&lt;/li&gt;
    &lt;li&gt;&lt;em&gt;&lt;strong&gt;Actual argument: &lt;/strong&gt;&lt;/em&gt; The arguments we &lt;strong&gt;pass into&lt;/strong&gt; a method or constructor.&lt;/li&gt;
  &lt;/ol&gt;
  &lt;p&gt;&lt;span style=&quot;font-family: arial, helvetica, sans-serif;&quot;&gt;
    Let's see an example:
  &lt;/span&gt;&lt;/p&gt;
  &lt;pre&gt;&lt;code&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;
  public static void main(String[] args) {
        int num_1 = 10;
        int num_2 = 20;
        int sum = addition(num_1, num_2);
        }
    
     public static int addition(int a, int b){
        return a + b;
    } 
  &lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
  &lt;p&gt;&lt;span style=&quot;font-family: arial, helvetica, sans-serif;&quot;&gt;
      From the above example, &lt;em style=&quot;background-color: rgb(206, 212, 217); color: rgb(0, 0, 0);&quot;&gt;a&lt;/em&gt; and &lt;em style=&quot;background-color: rgb(206, 212, 217); color: rgb(0, 0, 0);&quot;&gt;b&lt;/em&gt; is the &lt;strong&gt;formal arguments&lt;/strong&gt; while &lt;em style=&quot;background-color: rgb(206, 212, 217); color: rgb(0, 0, 0);&quot;&gt;num_1&lt;/em&gt; and &lt;em style=&quot;background-color: rgb(206, 212, 217); color: rgb(0, 0, 0);&quot;&gt;num_2&lt;/em&gt; is the &lt;strong&gt;actual arguments&lt;/strong&gt; that we pass into the method &lt;em style=&quot;background-color: rgb(206, 212, 217); color: rgb(0, 0, 0);&quot;&gt;addition&lt;/em&gt;.
  &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: arial, helvetica, sans-serif;&quot;&gt;
      Now, we've understood the difference between &lt;em&gt;&lt;strong&gt;actual argument&lt;/strong&gt;&lt;/em&gt; and &lt;em&gt;&lt;strong&gt;formal argument&lt;/strong&gt;&lt;/em&gt;. Let's see another example:
  &lt;/span&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;
        public static void main(String[] args) {
        int num_1 = 10;
        int num_2 = 20;
        int sum = addition(num_1);
        }
    
     public static int addition(int a, int b){
        return a + b;
    }
  &lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;span style=&quot;font-family: arial, helvetica, sans-serif;&quot;&gt;
      The compilation error occurs:
  &lt;/span&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;
  method addition in class NewMain cannot be applied to given types;
  required: int,int
  found:    int
  reason: actual and formal argument lists differ in length
  &lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;span style=&quot;font-family: arial, helvetica, sans-serif;&quot;&gt;
  Here, we pass in only one &lt;em&gt;actual argument&lt;/em&gt;, &lt;em style=&quot;background-color: rgb(206, 212, 217); color: rgb(0, 0, 0);&quot;&gt;num_1&lt;/em&gt; but the method, &lt;em style=&quot;background-color: rgb(206, 212, 217); color: rgb(0, 0, 0);&quot;&gt;addition&lt;/em&gt; actually defines two &lt;em&gt;formal arguments&lt;/em&gt;, &lt;em style=&quot;background-color: rgb(206, 212, 217); color: rgb(0, 0, 0);&quot;&gt;a&lt;/em&gt; and &lt;em style=&quot;background-color: rgb(206, 212, 217); color: rgb(0, 0, 0);&quot;&gt;b&lt;/em&gt; to work. Let's see another example:
  &lt;/span&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;
        public static void main(String[] args) {
        int num_1 = 10;
        int num_2 = 20;
        int num_3 = 30;
        int sum = addition(num_1, num_2, num_3);
        }
    
     public static int addition(int a, int b){
        return a + b;
    }
  &lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;span style=&quot;font-family: arial, helvetica, sans-serif;&quot;&gt;
      The compilation error also occurs:
  &lt;/span&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;
  method addition in class NewMain cannot be applied to given types;
  required: int,int
  found:    int,int,int
  reason: actual and formal argument lists differ in length
  &lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;span style=&quot;font-family: arial, helvetica, sans-serif;&quot;&gt;
  Here, we pass in three &lt;em&gt;actual arguments&lt;/em&gt;, &lt;em style=&quot;background-color: rgb(206, 212, 217); color: rgb(0, 0, 0);&quot;&gt;num_1&lt;/em&gt;, &lt;em style=&quot;background-color: rgb(206, 212, 217); color: rgb(0, 0, 0);&quot;&gt;num_2&lt;/em&gt;, and &lt;em style=&quot;background-color: rgb(206, 212, 217); color: rgb(0, 0, 0);&quot;&gt;num_3&lt;/em&gt; while the method, &lt;em style=&quot;background-color: rgb(206, 212, 217); color: rgb(0, 0, 0);&quot;&gt;addition&lt;/em&gt; only need two &lt;em&gt;formal arguments&lt;/em&gt; to work.
  &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: arial, helvetica, sans-serif;&quot;&gt;
 So far we've discovered this problem in the method invocation. Let's see some examples of constructor:
  &lt;/span&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;
public class Student {
    private String first_Name;
    private String last_Name;
    
    public Student(String first_Name, String last_Name){
        this.first_Name = first_Name;
        this.last_Name = last_Name;
    }
}
  &lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;span style=&quot;font-family: arial, helvetica, sans-serif;&quot;&gt;
      Here, we declare a class, &lt;em style=&quot;background-color: rgb(206, 212, 217); color: rgb(0, 0, 0);&quot;&gt;Student&lt;/em&gt;, where we need &lt;em style=&quot;background-color: rgb(206, 212, 217); color: rgb(0, 0, 0);&quot;&gt;first_Name&lt;/em&gt; and &lt;em style=&quot;background-color: rgb(206, 212, 217); color: rgb(0, 0, 0);&quot;&gt;last_Name&lt;/em&gt; arguments in the constructor to create an instance of &lt;em style=&quot;background-color: rgb(206, 212, 217); color: rgb(0, 0, 0);&quot;&gt;Student&lt;/em&gt;.
  &lt;/span&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;
Student student = new Student(&quot;Alex&quot;);
  &lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;span style=&quot;font-family: arial, helvetica, sans-serif;&quot;&gt;
      Here is the compilation error:
  &lt;/span&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;
constructor Student in class Student cannot be applied to given types;
required: String,String
found:    String
reason: actual and formal argument lists differ in length
  &lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;span style=&quot;font-family: arial, helvetica, sans-serif;&quot;&gt;
      Let's move on to the next section to see why it occurs.
  &lt;/span&gt;&lt;/p&gt;
&lt;/section&gt;
&lt;section&gt;
  &lt;h2&gt;&lt;a class=&quot;custom_anchor&quot; name=&quot;_Toc123774611&quot; id=&quot;_Toc123774611&quot;&gt;&lt;/a&gt;&lt;strong&gt;&lt;span style=&quot;font-family: arial, helvetica, sans-serif;&quot;&gt;Causes&lt;/span&gt; &lt;a style=&quot;color: blue;&quot; rel=&quot;nofollow&quot; href=&quot;#_Toc123774611&quot;&gt;#&lt;/a&gt;&lt;/strong&gt;&lt;/h2&gt;
  &lt;p&gt;&lt;span style=&quot;font-family: arial, helvetica, sans-serif;&quot;&gt;From the above examples, we can see that this compilation error occurs when the number of &lt;em&gt;&lt;strong&gt;actual arguments&lt;/strong&gt;&lt;/em&gt; we pass into the method or constructor doesn't match the number of &lt;em&gt;&lt;strong&gt;formal arguments&lt;/strong&gt;&lt;/em&gt; we define for the method or constructor. As long as there's mismatch between the number of &lt;em&gt;&lt;strong&gt;actual arguments&lt;/strong&gt;&lt;/em&gt; and the number of &lt;em&gt;&lt;strong&gt;formal arguments&lt;/strong&gt;&lt;/em&gt;, this error will always be produced. Don't fret; it can be simply solved, let's move on to the next section:&lt;/span&gt;&lt;/p&gt;
&lt;/section&gt;
&lt;section&gt;
  &lt;h2&gt;&lt;a class=&quot;custom_anchor&quot; name=&quot;_Toc123774612&quot; id=&quot;_Toc123774612&quot;&gt;&lt;/a&gt;&lt;strong&gt;&lt;span style=&quot;font-family: arial, helvetica, sans-serif;&quot;&gt;Solution&lt;/span&gt; &lt;a style=&quot;color: blue;&quot; rel=&quot;nofollow&quot; href=&quot;#_Toc123774612&quot;&gt;#&lt;/a&gt;&lt;/strong&gt;&lt;/h2&gt;
  &lt;p&gt;&lt;span style=&quot;font-family: arial, helvetica, sans-serif;&quot;&gt;
    You may guess the solution. Yes, you just need to remember the main concept while defining and using the methods / constructors:
      &lt;/span&gt;&lt;/p&gt;
    &lt;div style=&quot;margin-top: 30px; margin-bottom: 30px;&quot;&gt;
      &lt;em style=&quot;background-color: rgb(206, 212, 217); color: rgb(0, 0, 0); padding: 15px;&quot;&gt;The actual and formal argument lists should always be &lt;strong&gt;same&lt;/strong&gt; in length&lt;/em&gt;
    &lt;/div&gt;
  &lt;p&gt;&lt;span style=&quot;font-family: arial, helvetica, sans-serif;&quot;&gt;
    What means? Before using the methods or constructors, you should check the number of formal arguments defined for a method or constructor. When you want to invoke a method or initiate an instance of object using constructor, make sure the number of actual arguments passed in is &lt;strong&gt;same&lt;/strong&gt; as the number of formal arguments.
      &lt;/span&gt;&lt;/p&gt;
  &lt;pre&gt;&lt;code&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;
public static void main(String[] args) {
        int num_1 = 10;
        int num_2 = 20;
        int num_3 = 30;
        int sum = addition(num_1, num_2, num_3);
        System.out.println(sum);
        }
    
     public static int addition(int a, int b, int c){
        return a + b + c;
    }
  &lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;span style=&quot;font-family: arial, helvetica, sans-serif;&quot;&gt;
      This is the output:
  &lt;/span&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;
60
  &lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;span style=&quot;font-family: arial, helvetica, sans-serif;&quot;&gt;
      We can either increase / decrease the number of formal arguments defined or the number of actual arguments passed in. Just to make sure both are having the same length.
  &lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;margin-top: 15px&quot; class=&quot;div-green&quot;&gt;
&lt;span class=&quot;alert-header&quot;&gt;Tip&lt;/span&gt;
&lt;span class=&quot;alert-body&quot;&gt;For a Class, if you didn't override its constructor, the constructor has no any &lt;em&gt;formal arguments&lt;/em&gt;&lt;/span&gt;
&lt;/div&gt;
 &lt;pre&gt;&lt;code&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;
public class Student {
    private String first_Name;
    private String last_Name;

    public String getFirst_Name() {
        return first_Name;
    }

    public void setFirst_Name(String first_Name) {
        this.first_Name = first_Name;
    }

    public String getLast_Name() {
        return last_Name;
    }

    public void setLast_Name(String last_Name) {
        this.last_Name = last_Name;
    }
}
  &lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;span style=&quot;font-family: arial, helvetica, sans-serif;&quot;&gt;
      I define a class, &lt;em style=&quot;background-color: rgb(206, 212, 217); color: rgb(0, 0, 0);&quot;&gt;Student&lt;/em&gt; without overriding its &lt;em&gt;&lt;strong&gt;default constructor&lt;/strong&gt;&lt;/em&gt;.
  &lt;/span&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;
Student student = new Student();
  &lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;span style=&quot;font-family: arial, helvetica, sans-serif;&quot;&gt;
      The compilation error will not occur and we can assign value to the variable, &lt;em style=&quot;background-color: rgb(206, 212, 217); color: rgb(0, 0, 0);&quot;&gt;student&lt;/em&gt; using &lt;em&gt;setter&lt;/em&gt; method in the class.
  &lt;/span&gt;&lt;/p&gt;
&lt;/section&gt;
&lt;section&gt;
  &lt;h2&gt;&lt;a class=&quot;custom_anchor&quot; name=&quot;_Toc123774613&quot; id=&quot;_Toc123774613&quot;&gt;&lt;/a&gt;&lt;strong&gt;&lt;span style=&quot;font-family: arial, helvetica, sans-serif;&quot;&gt;The Conclusion&lt;/span&gt; &lt;a style=&quot;color: blue;&quot; rel=&quot;nofollow&quot; href=&quot;#_Toc123774613&quot;&gt;#&lt;/a&gt;&lt;/strong&gt;&lt;/h2&gt;
  &lt;p&gt;&lt;span style=&quot;font-family: arial, helvetica, sans-serif;&quot;&gt;
      Thanks for sticking with this blog until the end. In this lesson, we've learned about what's &lt;span style=&quot;color: rgb(185, 106, 217);&quot;&gt;&lt;em&gt;&quot;Actual and formal argument lists differ in length&quot;&lt;/em&gt;&lt;/span&gt; in Java. Besides, we've known what is the causes of this compilation error and how to solve it simply. Just to remind again, we should always make sure the &lt;em&gt;&lt;strong&gt;formal arguments&lt;/strong&gt;&lt;/em&gt; and &lt;em&gt;&lt;strong&gt;actual arguments&lt;/strong&gt;&lt;/em&gt; have the &lt;em&gt;&lt;strong&gt;same&lt;/strong&gt;&lt;/em&gt; length. Otherwise, this error will be thrown during compilation.
    &lt;/span&gt;&lt;/p&gt;
&lt;/section&gt;
&lt;section&gt;
  &lt;h2&gt;&lt;a class=&quot;custom_anchor&quot; name=&quot;_Toc123774614&quot; id=&quot;_Toc123774614&quot;&gt;&lt;/a&gt;&lt;strong&gt;&lt;span style=&quot;font-family: arial, helvetica, sans-serif;&quot;&gt;The Reference&lt;/span&gt; &lt;a style=&quot;color: blue;&quot; rel=&quot;nofollow&quot; href=&quot;#_Toc123774614&quot;&gt;#&lt;/a&gt;&lt;/strong&gt;&lt;/h2&gt;
  &lt;p&gt;&lt;span style=&quot;font-family: arial, helvetica, sans-serif;&quot;&gt;
      You may go through this reference for more information:
    &lt;/span&gt;&lt;/p&gt;
  &lt;ol&gt;
    &lt;li&gt;&lt;em&gt;&lt;span style=&quot;font-family: arial, helvetica, sans-serif;&quot;&gt;Constructors: &lt;/span&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.oracle.com/javase/tutorial/java/javaOO/constructors.html&quot; target=&quot;_blank&quot;&gt;https://docs.oracle.com/javase/tutorial/java/javaOO/constructors.html&lt;/a&gt;&lt;/em&gt;&lt;/li&gt;
  &lt;/ol&gt;
&lt;/section&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/17789/actual-and-formal-argument-lists-differ-in-length</guid>
<pubDate>Thu, 14 May 2026 05:55:50 +0000</pubDate>
</item>
<item>
<title>The Ecosystem Conductor: Orchestrating Market Orders with Kafka, RabbitMQ and Spring Boot</title>
<link>https://coderlegion.com/17660/the-ecosystem-conductor-orchestrating-market-orders-with-kafka-rabbitmq-and-spring-boot</link>
<description>&lt;p&gt;Hey folks!&lt;/p&gt;
&lt;p&gt;We've reached one of the most important posts in the &lt;strong&gt;My Broker B3&lt;/strong&gt; series. Up until now we built each piece of the ecosystem separately — the price synchronizer, the matching engine, the financial custody, the asset catalog. Now it's time to build the service that connects all of it: the &lt;strong&gt;Broker Order API&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;This is the conductor of the ecosystem. When a user wants to buy or sell a stock, this is where everything begins — and where the results come back to.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;what-is-the-order-api-&quot;&gt;What is the Order API?&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;trading-broker-order&lt;/code&gt; is the entry point for the broker's transactional flow. It has a clear responsibility: &lt;strong&gt;orchestrate the complete lifecycle of an order&lt;/strong&gt;, from the user's intent to the final execution confirmation.&lt;/p&gt;
&lt;p&gt;To do this, it communicates with four other ecosystem services:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Asset API&lt;/strong&gt; (REST/Feign) — validates whether the ticker exists and is active&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Wallet API&lt;/strong&gt; (REST/Feign) — validates whether the user has sufficient balance&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;B3 Matching Engine&lt;/strong&gt; (RabbitMQ) — sends the order for execution&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Broker Wallet&lt;/strong&gt; (Kafka) — notifies the ecosystem about each status change&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&quot;-the-complete-order-flow&quot;&gt;️ The Complete Order Flow&lt;/h2&gt;
&lt;p&gt;Before diving into the code, here's the end-to-end flow:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;[User]
    │
    │  POST /api/v1/orders
    │  { userId, ticker, quantity, price, side: &quot;BUY&quot; }
    ▼
[trading-broker-order]
    │
    ├─ 1. Validate ticker  → GET trading-asset/assets/{ticker} (Feign/REST)
    │
    ├─ 2. Validate balance → GET trading-wallet/summary (Feign/REST)
    │
    ├─ 3. Persist order → MySQL (status: PENDING)
    │
    ├─ 4. Send to B3 → RabbitMQ (mq-broker-to-b3)
    │
    ├─ 5. Notify ecosystem → Kafka (order-events-v1, status: PENDING)
    │
    └─ Returns created order → HTTP 201

         ─ ─ ─ (B3 processes the order) ─ ─ ─

[b3-matching-engine]
    │
    │  RabbitMQ (mq-b3-to-broker)
    │  { orderId, status: FILLED/REJECTED, executedPrice }
    ▼
[trading-broker-order]
    │
    ├─ 6. Update status → MySQL (FILLED or REJECTED)
    │
    └─ 7. Notify ecosystem → Kafka (order-events-v1, final status)
              │
              ├─ trading-broker-wallet consumes → blocks/settles/refunds balance
              └─ other future consumers
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Two distinct communication channels, two Kafka publish moments, and the wallet reacting to each state change. Let's see how each part was implemented.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;-tech-stack&quot;&gt;️ Tech Stack&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;Technology &lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Usage&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Java 21&lt;/strong&gt; + &lt;strong&gt;Spring Boot 3.3.5&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Service core&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;MySQL&lt;/strong&gt; + &lt;strong&gt;Flyway&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Order persistence&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Spring Kafka&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Lifecycle event publishing&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Spring AMQP&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Integration with Matching Engine via RabbitMQ&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Spring Cloud OpenFeign&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;REST calls to Asset API and Wallet API&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;SpringDoc OpenAPI&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Swagger UI documentation&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id=&quot;-implementation-pillars&quot;&gt;️ Implementation Pillars&lt;/h2&gt;
&lt;h3 id=&quot;1-the-domain-model-order&quot;&gt;1. The Domain Model — Order&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;Order&lt;/code&gt; entity represents the state of an order at any point in its lifecycle:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Entity
@Table(name = &quot;orders&quot;)
public class Order {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String userId;
    private String ticker;

    @Column(nullable = false, precision = 19, scale = 4)
    private BigDecimal quantity;

    @Column(nullable = false, precision = 19, scale = 4)
    private BigDecimal price;

    // Null while PENDING — only set after B3 feedback
    @Column(precision = 19, scale = 4)
    private BigDecimal executedPrice;

    @Enumerated(EnumType.STRING)
    private OrderSide side;   // BUY | SELL

    @Enumerated(EnumType.STRING)
    private OrderStatus status; // PENDING | FILLED | REJECTED | CANCELLED

    @CreationTimestamp
    private LocalDateTime createdAt;

    @UpdateTimestamp
    private LocalDateTime updatedAt;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;An important design detail: &lt;code&gt;executedPrice&lt;/code&gt; is intentionally nullable. When the order is created, we don't yet know the execution price — that only comes as a response from the Matching Engine. Forcing &lt;code&gt;nullable = false&lt;/code&gt; here would be a domain modeling error.&lt;/p&gt;
&lt;p&gt;The entity also carries the &lt;code&gt;mapToEvent()&lt;/code&gt; method that serializes the current state into a Kafka event:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public OrderEventDTO mapToEvent() {
    return OrderEventDTO.builder()
            .orderId(this.getId())
            .userId(this.getUserId())
            .ticker(this.getTicker())
            .quantity(this.getQuantity())
            .price(this.getPrice())
            .executedPrice(this.getExecutedPrice())
            .side(this.getSide().name())
            .status(this.getStatus().name())
            .eventTimestamp(LocalDateTime.now())
            .build();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;2-ticker-validation-asset-feign-client&quot;&gt;2. Ticker Validation — Asset Feign Client&lt;/h3&gt;
&lt;p&gt;Before validating balance or persisting anything, we need to ensure the requested ticker actually exists and is available for trading. A user submitting an order for &lt;code&gt;XPTO999&lt;/code&gt; should get a clear error immediately — not a silent rejection from B3 two hops later.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@FeignClient(name = &quot;trading-broker-asset&quot;, url = &quot;${app.services.asset-url}&quot;)
public interface AssetClient {

    @GetMapping(&quot;/api/v1/assets/{ticker}&quot;)
    AssetDTO getByTicker(@PathVariable(&quot;ticker&quot;) String ticker);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The validation in &lt;code&gt;OrderService&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;private void validateTicker(String ticker) {
    try {
        var asset = assetClient.getByTicker(ticker.toUpperCase());
        if (!&quot;ACTIVE&quot;.equalsIgnoreCase(asset.status())) {
            throw new RuntimeException(&quot;Asset is not available for trading: &quot; + ticker);
        }
        log.info(&quot;Ticker {} validated — status: {}&quot;, ticker, asset.status());
    } catch (FeignException.NotFound e) {
        throw new RuntimeException(&quot;Asset not found or inactive: &quot; + ticker);
    } catch (FeignException e) {
        log.error(&quot;Asset service unavailable during ticker validation: {}&quot;, e.getMessage());
        throw new RuntimeException(&quot;Asset service unavailable. Please try again later.&quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Why catch &lt;code&gt;FeignException&lt;/code&gt; separately from &lt;code&gt;FeignException.NotFound&lt;/code&gt;?&lt;/strong&gt; A 404 from the asset service means the ticker doesn't exist — a business error that should fail fast. Any other &lt;code&gt;FeignException&lt;/code&gt; (connection refused, timeout, 5xx) means the asset service is unavailable — a different kind of failure that warrants a different message to the user.&lt;/p&gt;
&lt;h3 id=&quot;3-balance-validation-wallet-feign-client&quot;&gt;3. Balance Validation — Wallet Feign Client&lt;/h3&gt;
&lt;p&gt;For buy orders (&lt;code&gt;BUY&lt;/code&gt;), we need to ensure the user has available balance. We use Spring Cloud OpenFeign to query the Wallet API declaratively:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@FeignClient(name = &quot;trading-broker-wallet&quot;, url = &quot;${app.services.wallet-url}&quot;)
public interface WalletClient {

    @GetMapping(&quot;/api/v1/wallet/{userId}/summary&quot;)
    WalletSummaryDTO getWalletSummary(@PathVariable String userId);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The validation happens after ticker validation:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;private void validateBalance(OrderRequestDTO request) {
    BigDecimal totalOrderValue = request.getPrice().multiply(request.getQuantity());
    var walletSummary = walletClient.getWalletSummary(request.getUserId());

    if (walletSummary.getAvailableBalance().compareTo(totalOrderValue) &amp;lt; 0) {
        throw new RuntimeException(&quot;Insufficient balance. Available: &quot;
                + walletSummary.getAvailableBalance()
                + &quot; | Required: &quot; + totalOrderValue);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Why Feign and not RestTemplate?&lt;/strong&gt; OpenFeign eliminates HTTP boilerplate — the interface is sufficient. The URL is injected via &lt;code&gt;application.yaml&lt;/code&gt;, making environment changes straightforward without recompiling.&lt;/p&gt;
&lt;h3 id=&quot;4-the-creation-flow-orderservice&quot;&gt;4. The Creation Flow — OrderService&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;placeOrder()&lt;/code&gt; is the heart of the service. It orchestrates five steps in sequence within a transaction:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Transactional
public Order placeOrder(OrderRequestDTO request) {
    log.info(&quot;Processing new {} order for user: {}&quot;, request.getSide(), request.getUserId());

    // 1. Validate ticker exists and is ACTIVE in asset catalog
    validateTicker(request.getTicker());

    // 2. Validate balance for BUY orders via Wallet API
    if (request.getSide() == OrderSide.BUY) {
        validateBalance(request);
    }

    // 3. Persist order with PENDING status
    Order order = Order.builder()
            .userId(request.getUserId())
            .ticker(request.getTicker().toUpperCase())
            .quantity(request.getQuantity())
            .price(request.getPrice())
            .side(request.getSide())
            .status(OrderStatus.PENDING)
            .build();

    order = orderRepository.save(order);

    // 4. Send to B3 via RabbitMQ
    b3MessageProducer.sendToB3(mapToB3Message(order));

    // 5. Publish PENDING event to Kafka for wallet and other consumers
    orderEventProducer.sendOrderEvent(order.mapToEvent());

    return order;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Why validate ticker before balance?&lt;/strong&gt; It's cheaper to fail fast on a non-existent ticker (one network call) than to validate balance first and then discover the ticker is invalid. Order of validations matters for UX and for minimizing unnecessary load on downstream services.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Design decision:&lt;/strong&gt; The Kafka event is published with &lt;code&gt;PENDING&lt;/code&gt; status right after creation. This allows &lt;code&gt;trading-broker-wallet&lt;/code&gt; to immediately block the balance, without waiting for B3's result. This is the essence of &lt;strong&gt;eventual consistency&lt;/strong&gt; — each service reacts to the event in its own time.&lt;/p&gt;
&lt;h3 id=&quot;5-b3-integration-rabbitmq&quot;&gt;5. B3 Integration — RabbitMQ&lt;/h3&gt;
&lt;p&gt;Communication with the Matching Engine uses RabbitMQ for a specific reason: &lt;strong&gt;delivery guarantee&lt;/strong&gt;. Even if the Matching Engine is temporarily unavailable, the message stays in the queue and is processed when it comes back.&lt;/p&gt;
&lt;h4 id=&quot;infrastructure-configuration&quot;&gt;Infrastructure configuration&lt;/h4&gt;
&lt;p&gt;All RabbitMQ infrastructure is declared as Spring beans — ensuring queues, exchanges and bindings exist before any message flows:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Bean
public DirectExchange brokerExchange() {
    return new DirectExchange(exchangeName);
}

@Bean
public Queue orderOutQueue() {
    return QueueBuilder.durable(queueIn)
            .withArgument(&quot;x-dead-letter-exchange&quot;, &quot;dlx.broker.orders&quot;)
            .withArgument(&quot;x-dead-letter-routing-key&quot;, &quot;dlq-broker-to-b3&quot;)
            .build();
}

@Bean
public Binding orderOutBinding() {
    return BindingBuilder.bind(orderOutQueue())
            .to(brokerExchange())
            .with(routingKey);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The dead letter &lt;code&gt;withArgument&lt;/code&gt; ensures messages that fail are redirected to the DLQ instead of simply being discarded.&lt;/p&gt;
&lt;h4 id=&quot;producer-sending-to-b3&quot;&gt;Producer — sending to B3&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public void sendToB3(B3OrderRequestDTO message) {
    log.info(&quot;Sending order {} to B3 via exchange: {} | routing-key: {}&quot;,
            message.getOrderId(), exchange, routingKey);
    rabbitTemplate.convertAndSend(exchange, routingKey, message);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Important decision:&lt;/strong&gt; the message is sent to the &lt;strong&gt;exchange&lt;/strong&gt; with the &lt;strong&gt;routing key&lt;/strong&gt;, not directly to the queue. This respects the RabbitMQ routing model and allows adding new consumers without changing the producer.&lt;/p&gt;
&lt;h4 id=&quot;consumer-receiving-b3-feedback&quot;&gt;Consumer — receiving B3 feedback&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@RabbitListener(queues = &quot;${app.rabbitmq.queue-out}&quot;)
public void receiveFeedback(B3OrderResponseDTO response) {
    log.info(&quot;Feedback received from B3 for order: {} | Status: {}&quot;,
            response.getOrderId(), response.getStatus());
    try {
        orderStatusService.updateOrderStatus(response);
    } catch (Exception e) {
        log.error(&quot;Failed to update status for order {}: {}&quot;,
                response.getOrderId(), e.getMessage(), e);
        throw e; // RabbitMQ applies retry/DLQ policy
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;throw e&lt;/code&gt; is intentional: rethrowing the exception allows RabbitMQ to apply its retry policy instead of silently discarding the message.&lt;/p&gt;
&lt;h3 id=&quot;6-status-update-orderstatusservice&quot;&gt;6. Status Update — OrderStatusService&lt;/h3&gt;
&lt;p&gt;When B3 feedback arrives, &lt;code&gt;OrderStatusService&lt;/code&gt; updates the order state and publishes the second Kafka event:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Transactional
public void updateOrderStatus(B3OrderResponseDTO response) {
    Order order = orderRepository.findById(Long.valueOf(response.getOrderId()))
            .orElseThrow(() -&amp;gt; new RuntimeException(&quot;Order not found: &quot; + response.getOrderId()));

    order.setStatus(OrderStatus.valueOf(response.getStatus()));
    order.setExecutedPrice(response.getExecutedPrice());

    orderRepository.save(order);

    // Notify ecosystem with final status — wallet settles or refunds
    orderEventProducer.sendOrderEvent(order.mapToEvent());
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This second Kafka event closes the loop: &lt;code&gt;trading-broker-wallet&lt;/code&gt; consumes it and executes the settlement (&lt;code&gt;FILLED&lt;/code&gt;) or refund (&lt;code&gt;REJECTED&lt;/code&gt;) of the balance blocked in the previous step.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;rest-api&quot;&gt;REST API&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;Method &lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Endpoint &lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Description&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;POST &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;/api/v1/orders&lt;/code&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Place a new buy or sell order&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;GET &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;/api/v1/orders/{id}&lt;/code&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Get order details by ID&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;GET &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;/api/v1/orders/user/{userId}&lt;/code&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;List all orders for a user&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt; &lt;strong&gt;Swagger UI&lt;/strong&gt;: &lt;code&gt;http://localhost:8088/swagger-ui.html&lt;/code&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;-validating-the-execution&quot;&gt;✅ Validating the Execution&lt;/h2&gt;
&lt;p&gt;With the application running locally:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;✅ Flyway validated the &lt;code&gt;orders&lt;/code&gt; table schema&lt;/li&gt;
&lt;li&gt;✅ Tomcat started on port &lt;code&gt;8088&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;✅ RabbitMQ connected with exchange, queues and DLQ declared&lt;/li&gt;
&lt;li&gt;✅ Swagger UI with 3 documented endpoints&lt;/li&gt;
&lt;li&gt;✅ AssetClient validating ticker before any persistence&lt;/li&gt;
&lt;li&gt;✅ WalletClient validating balance for BUY orders&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&quot;the-complete-ecosystem&quot;&gt;The Complete Ecosystem&lt;/h2&gt;
&lt;p&gt;With &lt;code&gt;trading-broker-order&lt;/code&gt; ready, we have the first end-to-end flow working:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;[User] → [Order API] → [Asset API]  (ticker validation)
                    → [Wallet API]  (balance validation)
                    → [B3 Matching Engine] (execution)
                    → [Kafka] → [Wallet API] (custody)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;From the user's click to the updated wallet balance, through ticker and balance validation, RabbitMQ routing, price matching against Redis, and eventual consistency via Kafka.&lt;/p&gt;
&lt;p&gt;Got any questions about the design decisions or the inter-service orchestration? Drop them in the comments!&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;about-the-series&quot;&gt;About the Series&lt;/h2&gt;
&lt;p&gt;⬅️ Previous Post: &lt;a rel=&quot;nofollow&quot; href=&quot;#&quot;&gt;Financial Custody: Managing Balance and Portfolio with Eventual Consistency&lt;/a&gt;&lt;/p&gt;
&lt;p&gt; Series Index: &lt;a rel=&quot;nofollow&quot; href=&quot;https://dev.to/rvneto/series-roadmap-building-a-stock-brokerage-simulator-with-microservices-kgh&quot;&gt;Series Roadmap&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Links:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.linkedin.com/in/roberto-de-vargas/&quot;&gt;LinkedIn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/rvneto/trading-broker-order&quot;&gt;GitHub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/17660/the-ecosystem-conductor-orchestrating-market-orders-with-kafka-rabbitmq-and-spring-boot</guid>
<pubDate>Wed, 13 May 2026 00:55:31 +0000</pubDate>
</item>
<item>
<title>Financial Custody: Managing Balance and Portfolio with Eventual Consistency</title>
<link>https://coderlegion.com/17641/financial-custody-managing-balance-and-portfolio-with-eventual-consistency</link>
<description>&lt;p&gt;Hey folks!&lt;/p&gt;
&lt;p&gt;Continuing the &lt;strong&gt;My Broker B3&lt;/strong&gt; series, we've reached one of the most business-logic-rich services in the ecosystem: the &lt;strong&gt;Broker Wallet API&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;In previous posts we built the B3 infrastructure (market price sync and matching engine). Now we enter the financial core of the broker. This service is the guardian of the investor's money and assets — it needs to be correct above all else.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;what-is-the-wallet-api-&quot;&gt;What is the Wallet API?&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;trading-broker-wallet&lt;/code&gt; is the &lt;strong&gt;financial custody service&lt;/strong&gt; of the ecosystem. Its responsibility is to react to order lifecycle events and ensure the user's money and assets are managed correctly at each step.&lt;/p&gt;
&lt;p&gt;It doesn't receive direct buy commands — it &lt;strong&gt;listens to Kafka events&lt;/strong&gt; and acts according to each order's status:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;Kafka: order-events-v1
         │
    ┌────┴────────────────────────┐
    │                             │
 PENDING                       FILLED / REJECTED
    │                             │
 reserveBalance()          settleOrder() / refundBalance()
    │                             │
 Blocks balance            Settles or refunds
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2 id=&quot;why-is-the-wallet-so-critical-&quot;&gt;Why is the Wallet so Critical?&lt;/h2&gt;
&lt;p&gt;Imagine the following scenario without proper custody:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;User has R$ 1,000 available&lt;/li&gt;
&lt;li&gt;Places a buy order for R$ 800&lt;/li&gt;
&lt;li&gt;Before the order is processed, places &lt;strong&gt;another&lt;/strong&gt; order for R$ 500&lt;/li&gt;
&lt;li&gt;Both orders are executed → user ends up with &lt;strong&gt;negative&lt;/strong&gt; balance&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The wallet solves this with the concept of &lt;strong&gt;blocked balance&lt;/strong&gt;: the moment an order enters as &lt;code&gt;PENDING&lt;/code&gt;, the value is immediately reserved. The available balance is always &lt;code&gt;balance - blockedBalance&lt;/code&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;-tech-stack-1&quot;&gt;️ Tech Stack&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;Technology &lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Usage&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Java 21&lt;/strong&gt; + &lt;strong&gt;Spring Boot 3.5.11&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Service core&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;MySQL&lt;/strong&gt; + &lt;strong&gt;Flyway&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Operational persistence and versioning&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Spring Kafka&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Order event consumption&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Spring Data Redis&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Real-time prices for portfolio valuation&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;SpringDoc OpenAPI&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Swagger UI documentation&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id=&quot;-the-data-model&quot;&gt;️ The Data Model&lt;/h2&gt;
&lt;p&gt;Before diving into the logic, it's important to understand the three entities that support the service:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;Account
├── userId (UNIQUE)
├── balance          ← total balance (includes blocked amount)
├── blockedBalance   ← amount reserved for PENDING orders
└── currency

Account (1) ──▶ (N) Position
                     ├── ticker
                     ├── quantity
                     └── averagePrice

WalletTransaction (audit log)
├── orderId
├── userId
├── transactionType  ← RESERVE | SETTLEMENT | REFUND
└── amount
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;blockedBalance&lt;/code&gt; is the central piece of custody. The &lt;code&gt;balance&lt;/code&gt; never goes below zero — only &lt;code&gt;blockedBalance&lt;/code&gt; goes up and down as orders progress through their lifecycle.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;the-order-lifecycle-in-the-wallet&quot;&gt;The Order Lifecycle in the Wallet&lt;/h2&gt;
&lt;h3 id=&quot;step-1-pending-reserving-the-balance&quot;&gt;Step 1 — PENDING: Reserving the Balance&lt;/h3&gt;
&lt;p&gt;When an order is created, it arrives as &lt;code&gt;PENDING&lt;/code&gt;. The wallet must &lt;strong&gt;immediately block&lt;/strong&gt; the maximum amount that could be debited (quantity × limit price):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Transactional
public void reserveBalance(OrderEventDTO event) {
    BigDecimal amountToReserve = event.getPrice().multiply(event.getQuantity());

    Account account = accountRepository.findByUserId(event.getUserId())
            .orElseThrow(() -&amp;gt; new RuntimeException(&quot;Account not found for user: &quot; + event.getUserId()));

    // Available = balance - blockedBalance
    BigDecimal availableBalance = account.getAvailableBalance();

    if (availableBalance.compareTo(amountToReserve) &amp;lt; 0) {
        throw new RuntimeException(&quot;Insufficient balance to reserve funds.&quot;);
    }

    // Only blockedBalance increases — total balance stays intact
    account.setBlockedBalance(account.getBlockedBalance().add(amountToReserve));
    accountRepository.save(account);

    // Audit record
    walletTransactionRepository.save(WalletTransaction.builder()
            .userId(event.getUserId())
            .orderId(event.getOrderId())
            .transactionType(TransactionType.RESERVE)
            .amount(amountToReserve)
            .build());
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Important point:&lt;/strong&gt; the total &lt;code&gt;balance&lt;/code&gt; doesn't change at this step. Only &lt;code&gt;blockedBalance&lt;/code&gt; increases. The money is still in the account — it's just reserved.&lt;/p&gt;
&lt;h3 id=&quot;step-2a-filled-settling-the-order&quot;&gt;Step 2a — FILLED: Settling the Order&lt;/h3&gt;
&lt;p&gt;The order was executed by the Matching Engine. Now the money actually leaves. But here's a subtlety: &lt;strong&gt;the executed price may differ from the reserved price&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Example: user placed a buy order with a limit of R$ 30.00, but the market executed at R$ 29.50. The reserved amount was R$ 300 (10 shares × R$ 30), but the actual executed amount was R$ 295 (10 × R$ 29.50). The R$ 5.00 difference automatically becomes available again.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Transactional
public void settleOrder(OrderEventDTO event) {
    BigDecimal totalReserved = event.getPrice().multiply(event.getQuantity());
    BigDecimal totalExecuted = event.getExecutedPrice().multiply(event.getQuantity());

    Account account = accountRepository.findByUserId(event.getUserId())
            .orElseThrow(() -&amp;gt; new RuntimeException(&quot;Account not found&quot;));

    // Release the initial block
    BigDecimal newBlockedBalance = account.getBlockedBalance().subtract(totalReserved);
    account.setBlockedBalance(newBlockedBalance.compareTo(BigDecimal.ZERO) &amp;lt; 0
            ? BigDecimal.ZERO
            : newBlockedBalance);

    // Debit the actual executed amount from total balance
    account.setBalance(account.getBalance().subtract(totalExecuted));
    accountRepository.save(account);

    // Update asset position
    updatePosition(event, account);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;step-2b-rejected-refunding-the-balance&quot;&gt;Step 2b — REJECTED: Refunding the Balance&lt;/h3&gt;
&lt;p&gt;The order was rejected (price outside market range, ticker not found in Redis, etc.). The blocked money becomes available again:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Transactional
public void refundBalance(OrderEventDTO event) {
    BigDecimal amountToRefund = event.getPrice().multiply(event.getQuantity());

    Account account = accountRepository.findByUserId(event.getUserId())
            .orElseThrow(() -&amp;gt; new RuntimeException(&quot;Account not found for refund&quot;));

    // Only subtract from blockedBalance — total balance was never touched
    if (account.getBlockedBalance().compareTo(amountToRefund) &amp;gt;= 0) {
        account.setBlockedBalance(account.getBlockedBalance().subtract(amountToRefund));
    } else {
        log.warn(&quot;Refund amount exceeds blocked balance for order {}&quot;, event.getOrderId());
        account.setBlockedBalance(BigDecimal.ZERO);
    }

    accountRepository.save(account);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2 id=&quot;weighted-average-price-calculation&quot;&gt;Weighted Average Price Calculation&lt;/h2&gt;
&lt;p&gt;When a purchase is settled, we need to update the user's position in that asset. The weighted average price ensures that purchases made at different times are correctly reflected:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;New Average Price = (Current Cost + New Cost) / (Current Qty + New Qty)
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;private void updatePosition(OrderEventDTO event, Account account) {
    Position position = positionRepository
            .findByAccountIdAndTicker(account.getId(), event.getTicker())
            .orElse(Position.builder()
                    .account(account)
                    .ticker(event.getTicker())
                    .quantity(BigDecimal.ZERO)
                    .averagePrice(BigDecimal.ZERO)
                    .build());

    boolean isSell = &quot;SELL&quot;.equalsIgnoreCase(event.getSide());

    if (isSell) {
        BigDecimal newQuantity = position.getQuantity().subtract(event.getQuantity());
        if (newQuantity.compareTo(BigDecimal.ZERO) &amp;lt;= 0) {
            positionRepository.delete(position);
            return;
        }
        position.setQuantity(newQuantity);
        // Average price stays the same on sell
    } else {
        // BUY: recalculate weighted average price
        BigDecimal currentCost = position.getAveragePrice().multiply(position.getQuantity());
        BigDecimal newCost = event.getExecutedPrice().multiply(event.getQuantity());
        BigDecimal totalQuantity = position.getQuantity().add(event.getQuantity());
        BigDecimal newAveragePrice = currentCost.add(newCost)
                .divide(totalQuantity, 4, RoundingMode.HALF_UP);

        position.setQuantity(totalQuantity);
        position.setAveragePrice(newAveragePrice);
    }

    positionRepository.save(position);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Practical example:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Purchase 1: 10 PETR4 shares at R$ 30.00 → average price = R$ 30.00&lt;/li&gt;
&lt;li&gt;Purchase 2: 5 PETR4 shares at R$ 33.00 → average price = (300 + 165) / 15 = &lt;strong&gt;R$ 31.00&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&quot;critical-bugs-fixed&quot;&gt;Critical Bugs Fixed&lt;/h2&gt;
&lt;p&gt;During the code review, I identified issues that would cause silent failures in production:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. Guaranteed NPE on first deposit&lt;/strong&gt;&lt;br&gt;
New account created without initializing &lt;code&gt;blockedBalance&lt;/code&gt;. The first call to &lt;code&gt;getAvailableBalance()&lt;/code&gt; would throw &lt;code&gt;NullPointerException&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// ❌ Before
Account.builder()
    .balance(BigDecimal.ZERO)
    // blockedBalance missing → NPE

// ✅ After
Account.builder()
    .balance(BigDecimal.ZERO)
    .blockedBalance(BigDecimal.ZERO)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;2. Withdraw validated total balance, not available balance&lt;/strong&gt;&lt;br&gt;
Allowed withdrawing money that was blocked in pending orders:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// ❌ Before — allows withdrawing reserved funds
if (account.getBalance().compareTo(amount) &amp;lt; 0)

// ✅ After — validates only what's available
if (account.getAvailableBalance().compareTo(amount) &amp;lt; 0)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;3. SELL increased position instead of decreasing it&lt;/strong&gt;&lt;br&gt;
The &lt;code&gt;updatePosition()&lt;/code&gt; method always added quantity, completely ignoring the &lt;code&gt;side&lt;/code&gt; field. Selling 10 shares would add 10 to the position.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4. Kafka consumer silently swallowed exceptions&lt;/strong&gt;&lt;br&gt;
Errors were logged but the offset was committed — the message was discarded without retry. Now the consumer rethrows the exception so Kafka can apply its retry policy correctly.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;idempotency&quot;&gt;Idempotency&lt;/h2&gt;
&lt;p&gt;To prevent duplicates in case of Kafka reprocessing, we added a database constraint:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;-- V4 migration
ALTER TABLE wallet_transactions
    ADD CONSTRAINT uq_order_transaction UNIQUE (order_id, transaction_type);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If the same &lt;code&gt;PENDING&lt;/code&gt; event arrives twice, the second insert will fail with a constraint violation — preventing the balance from being blocked twice for the same order.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;rest-api-1&quot;&gt;REST API&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;Method &lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Endpoint &lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Description&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;POST &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;/api/v1/wallet/{userId}/deposit&lt;/code&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Deposit funds&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;POST &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;/api/v1/wallet/{userId}/withdraw&lt;/code&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Withdraw available funds&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;GET &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;/api/v1/wallet/{userId}/summary&lt;/code&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Summary: balance + positions + total equity&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;GET &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;/api/v1/wallet/{userId}/positions&lt;/code&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;List asset positions&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;GET &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;/api/v1/wallet/{userId}/transactions&lt;/code&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Transaction history&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt; &lt;strong&gt;Swagger UI&lt;/strong&gt;: &lt;code&gt;http://localhost:8085/swagger-ui.html&lt;/code&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;-validating-the-execution-1&quot;&gt;✅ Validating the Execution&lt;/h2&gt;
&lt;p&gt;With the application running locally:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;✅ Flyway applied all 4 migrations successfully&lt;/li&gt;
&lt;li&gt;✅ Hibernate validated the schema&lt;/li&gt;
&lt;li&gt;✅ Kafka consumer connected and subscribed to &lt;code&gt;order-events-v1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;✅ Consumer group &lt;code&gt;trading-broker-wallet&lt;/code&gt; synced&lt;/li&gt;
&lt;li&gt;✅ Swagger UI with 5 documented endpoints at &lt;code&gt;http://localhost:8085/swagger-ui.html&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&quot;what-s-next-&quot;&gt;What's Next?&lt;/h2&gt;
&lt;p&gt;With the Wallet ready to react to order events, the next step is building the &lt;strong&gt;trading-broker-order&lt;/strong&gt; — the orchestrator that will manage the complete order lifecycle:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Receive buy/sell intent via REST&lt;/li&gt;
&lt;li&gt;Validate the ticker against &lt;code&gt;trading-broker-asset&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Save the order as &lt;code&gt;PENDING&lt;/code&gt; and publish to Kafka&lt;/li&gt;
&lt;li&gt;Send the order to the Matching Engine via RabbitMQ&lt;/li&gt;
&lt;li&gt;Consume the result and update the final status&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Once that service is ready, we'll have the first complete end-to-end flow: from the user's click all the way to the updated wallet balance.&lt;/p&gt;
&lt;p&gt;Got any questions about the custody logic or the PENDING → FILLED → REJECTED cycle? Drop them in the comments!&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;about-the-series-1&quot;&gt;About the Series&lt;/h2&gt;
&lt;p&gt;⬅️ Previous Post: &lt;a rel=&quot;nofollow&quot; href=&quot;#&quot;&gt;The Heart of B3: Building the Matching Engine with RabbitMQ and Redis&lt;/a&gt;&lt;/p&gt;
&lt;p&gt; Series Index: &lt;a rel=&quot;nofollow&quot; href=&quot;https://dev.to/rvneto/series-roadmap-building-a-stock-brokerage-simulator-with-microservices-kgh&quot;&gt;Series Roadmap&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Links:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.linkedin.com/in/roberto-de-vargas/&quot;&gt;LinkedIn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/rvneto/trading-broker-wallet&quot;&gt;GitHub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/17641/financial-custody-managing-balance-and-portfolio-with-eventual-consistency</guid>
<pubDate>Tue, 12 May 2026 20:43:57 +0000</pubDate>
</item>
<item>
<title>Spring Boot Large File Upload: Limits, Streaming, and Production Best Practices</title>
<link>https://coderlegion.com/17532/spring-boot-large-file-upload-limits-streaming-and-production-best-practices</link>
<description>&lt;p&gt;File uploads look simple until someone uploads a 2GB video.&lt;/p&gt;
&lt;p&gt;Then suddenly your backend starts behaving differently:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;memory usage spikes&lt;/li&gt;
&lt;li&gt;requests take forever&lt;/li&gt;
&lt;li&gt;uploads fail halfway&lt;/li&gt;
&lt;li&gt;server threads stay blocked&lt;/li&gt;
&lt;li&gt;users retry the same upload again&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A small file upload API and a production-ready large file upload API are not the same thing.&lt;/p&gt;
&lt;p&gt;If you're building file upload functionality in Spring Boot, simply increasing the max upload limit is not enough.&lt;/p&gt;
&lt;p&gt;Let's look at how to handle large file uploads properly.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;why-large-file-uploads-become-a-backend-problem&quot;&gt;Why large file uploads become a backend problem&lt;/h2&gt;
&lt;p&gt;For small files, basic multipart upload works fine.&lt;/p&gt;
&lt;p&gt;For larger files, several issues appear quickly:&lt;/p&gt;
&lt;h3 id=&quot;1-memory-pressure&quot;&gt;1. Memory pressure&lt;/h3&gt;
&lt;p&gt;A common mistake is loading the uploaded file entirely into memory.&lt;/p&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;byte[] bytes = file.getBytes();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Looks harmless.&lt;/p&gt;
&lt;p&gt;But if users upload 500MB or 2GB files, this becomes dangerous fast.&lt;/p&gt;
&lt;p&gt;That single line can destroy your application's memory stability.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;2-long-request-lifecycle&quot;&gt;2. Long request lifecycle&lt;/h3&gt;
&lt;p&gt;Large uploads naturally take longer.&lt;/p&gt;
&lt;p&gt;That means:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;open HTTP connections for longer periods&lt;/li&gt;
&lt;li&gt;higher timeout risk&lt;/li&gt;
&lt;li&gt;blocked request threads&lt;/li&gt;
&lt;li&gt;poor user experience if retries happen&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;3-storage-bottlenecks&quot;&gt;3. Storage bottlenecks&lt;/h3&gt;
&lt;p&gt;Saving directly to local disk may work in development.&lt;/p&gt;
&lt;p&gt;In production:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;multiple instances may not share storage&lt;/li&gt;
&lt;li&gt;containers may lose uploaded files&lt;/li&gt;
&lt;li&gt;disk space becomes a hidden failure point&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;4-broken-validation-strategy&quot;&gt;4. Broken validation strategy&lt;/h3&gt;
&lt;p&gt;If validation happens too late, you waste resources processing invalid uploads.&lt;/p&gt;
&lt;p&gt;Examples:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;unsupported file types&lt;/li&gt;
&lt;li&gt;oversized payloads&lt;/li&gt;
&lt;li&gt;corrupted uploads&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Reject bad uploads early.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;how-large-file-uploads-should-work&quot;&gt;How large file uploads should work&lt;/h2&gt;
&lt;p&gt;A cleaner production flow looks like this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Client sends multipart upload request&lt;/li&gt;
&lt;li&gt;Server validates request constraints&lt;/li&gt;
&lt;li&gt;File is streamed instead of fully loaded&lt;/li&gt;
&lt;li&gt;Storage layer handles persistence&lt;/li&gt;
&lt;li&gt;API returns upload metadata&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Simple architecture.&lt;/p&gt;
&lt;p&gt;Big difference in reliability.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;configure-upload-limits-first&quot;&gt;Configure upload limits first&lt;/h2&gt;
&lt;p&gt;Start by defining upload constraints.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-properties&quot;&gt;spring.servlet.multipart.max-file-size=1GB
spring.servlet.multipart.max-request-size=1GB
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This protects your application from uncontrolled uploads.&lt;/p&gt;
&lt;p&gt;Without limits:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;accidental abuse becomes easy&lt;/li&gt;
&lt;li&gt;memory pressure becomes unpredictable&lt;/li&gt;
&lt;li&gt;infrastructure costs can spike&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Limits are not optional.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;the-biggest-mistake-loading-entire-files-into-memory&quot;&gt;The biggest mistake: loading entire files into memory&lt;/h2&gt;
&lt;p&gt;This pattern is dangerous:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;byte[] bytes = file.getBytes();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Also risky:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;String content = new String(file.getBytes());
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Why?&lt;/p&gt;
&lt;p&gt;Because the whole file gets loaded into memory before processing.&lt;/p&gt;
&lt;p&gt;For large uploads, this is exactly what you want to avoid.&lt;/p&gt;
&lt;p&gt;Instead, stream the content.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;better-approach-stream-the-upload&quot;&gt;Better approach: stream the upload&lt;/h2&gt;
&lt;p&gt;Use InputStream instead of loading everything at once.&lt;/p&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@PostMapping(&quot;/upload&quot;)
public ResponseEntity&amp;lt;String&amp;gt; upload(
        @RequestParam(&quot;file&quot;) MultipartFile file
) throws IOException {

    if (file.isEmpty()) {
        return ResponseEntity.badRequest().body(&quot;File is empty&quot;);
    }

    Path destination = Paths.get(&quot;/uploads/&quot; + file.getOriginalFilename());

    try (InputStream inputStream = file.getInputStream()) {
        Files.copy(inputStream, destination);
    }

    return ResponseEntity.ok(&quot;Upload successful&quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Why this is better:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;lower memory usage&lt;/li&gt;
&lt;li&gt;safer under larger payloads&lt;/li&gt;
&lt;li&gt;simpler production behavior&lt;/li&gt;
&lt;li&gt;predictable resource consumption&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&quot;separate-upload-logic-from-storage-logic&quot;&gt;Separate upload logic from storage logic&lt;/h2&gt;
&lt;p&gt;Do not put everything inside the controller.&lt;/p&gt;
&lt;p&gt;Bad:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@PostMapping(&quot;/upload&quot;)
public ResponseEntity&amp;lt;?&amp;gt; upload(MultipartFile file) {
    // validation
    // storage logic
    // business rules
    // response mapping
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Cleaner structure:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;controller handles request&lt;/li&gt;
&lt;li&gt;service handles workflow&lt;/li&gt;
&lt;li&gt;storage layer handles persistence&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@RestController
@RequiredArgsConstructor
public class FileUploadController {

    private final FileUploadService fileUploadService;

    @PostMapping(&quot;/upload&quot;)
    public ResponseEntity&amp;lt;String&amp;gt; upload(
            @RequestParam MultipartFile file
    ) {
        fileUploadService.upload(file);
        return ResponseEntity.ok(&quot;Uploaded&quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Service:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Service
@RequiredArgsConstructor
public class FileUploadService {

    private final StorageService storageService;

    public void upload(MultipartFile file) {
        storageService.store(file);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This becomes much easier to maintain later.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;storage-strategy-matters&quot;&gt;Storage strategy matters&lt;/h2&gt;
&lt;p&gt;Local storage is okay for prototypes.&lt;/p&gt;
&lt;p&gt;Production systems often need better options:&lt;/p&gt;
&lt;h3 id=&quot;local-disk&quot;&gt;Local disk&lt;/h3&gt;
&lt;p&gt;Good for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;prototypes&lt;/li&gt;
&lt;li&gt;internal tools&lt;/li&gt;
&lt;li&gt;single-instance deployments&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Bad for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;autoscaling environments&lt;/li&gt;
&lt;li&gt;Docker/Kubernetes deployments&lt;/li&gt;
&lt;li&gt;distributed systems&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;cloud-object-storage&quot;&gt;Cloud object storage&lt;/h3&gt;
&lt;p&gt;Examples:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;AWS S3&lt;/li&gt;
&lt;li&gt;Cloudflare R2&lt;/li&gt;
&lt;li&gt;MinIO&lt;/li&gt;
&lt;li&gt;Google Cloud Storage&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Better for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;scalability&lt;/li&gt;
&lt;li&gt;durability&lt;/li&gt;
&lt;li&gt;shared access across instances&lt;/li&gt;
&lt;li&gt;predictable infrastructure behavior&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&quot;validate-before-expensive-processing&quot;&gt;Validate before expensive processing&lt;/h2&gt;
&lt;p&gt;Check uploads early.&lt;/p&gt;
&lt;p&gt;Minimum checks:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;if (file.isEmpty()) {
    throw new IllegalArgumentException(&quot;Empty file&quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Size:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;if (file.getSize() &amp;gt; MAX_UPLOAD_SIZE) {
    throw new IllegalArgumentException(&quot;File too large&quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Content type:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;if (!allowedTypes.contains(file.getContentType())) {
    throw new IllegalArgumentException(&quot;Unsupported file type&quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Do this before storage work starts.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;common-production-mistakes&quot;&gt;Common production mistakes&lt;/h2&gt;
&lt;h3 id=&quot;loading-everything-into-memory&quot;&gt;Loading everything into memory&lt;/h3&gt;
&lt;p&gt;Classic performance killer.&lt;/p&gt;
&lt;h3 id=&quot;no-upload-size-limits&quot;&gt;No upload size limits&lt;/h3&gt;
&lt;p&gt;This creates operational risk immediately.&lt;/p&gt;
&lt;h3 id=&quot;using-local-storage-inside-containers&quot;&gt;Using local storage inside containers&lt;/h3&gt;
&lt;p&gt;Looks fine in dev.&lt;/p&gt;
&lt;p&gt;Breaks badly in production.&lt;/p&gt;
&lt;h3 id=&quot;no-timeout-awareness&quot;&gt;No timeout awareness&lt;/h3&gt;
&lt;p&gt;Large uploads take time.&lt;/p&gt;
&lt;p&gt;Your infrastructure must account for that.&lt;/p&gt;
&lt;h3 id=&quot;mixing-upload-and-business-logic&quot;&gt;Mixing upload and business logic&lt;/h3&gt;
&lt;p&gt;Creates messy controllers and painful maintenance.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;production-checklist&quot;&gt;Production checklist&lt;/h2&gt;
&lt;p&gt;Before shipping large upload support:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;upload size limits configured&lt;/li&gt;
&lt;li&gt;streaming instead of full buffering&lt;/li&gt;
&lt;li&gt;validation before processing&lt;/li&gt;
&lt;li&gt;storage abstraction in place&lt;/li&gt;
&lt;li&gt;timeout settings reviewed&lt;/li&gt;
&lt;li&gt;failure handling implemented&lt;/li&gt;
&lt;li&gt;infrastructure storage strategy chosen&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If this list is incomplete, the implementation is not production-ready.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;final-thoughts-2&quot;&gt;Final thoughts&lt;/h2&gt;
&lt;p&gt;Large file uploads are not complicated.&lt;/p&gt;
&lt;p&gt;But careless implementations become expensive fast.&lt;/p&gt;
&lt;p&gt;The winning approach is simple:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;set hard limits&lt;/li&gt;
&lt;li&gt;stream uploads&lt;/li&gt;
&lt;li&gt;validate early&lt;/li&gt;
&lt;li&gt;separate responsibilities&lt;/li&gt;
&lt;li&gt;choose proper storage&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That gets you a stable upload API.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Built a similar production-ready upload architecture recently while working on Spring Boot backend tooling at &lt;a rel=&quot;nofollow&quot; href=&quot;https://buildbasekit.com/&quot;&gt;BuildBaseKit&lt;/a&gt;.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/17532/spring-boot-large-file-upload-limits-streaming-and-production-best-practices</guid>
<pubDate>Tue, 12 May 2026 07:14:52 +0000</pubDate>
</item>
<item>
<title>LLM Observability tool</title>
<link>https://coderlegion.com/17391/llm-observability-tool</link>
<description>&lt;p&gt;I just created an open source LLM observability named &quot;TraceLM&quot; that can track the following activities:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;token cost&lt;/li&gt;
&lt;li&gt;token length&lt;/li&gt;
&lt;li&gt;latency information etc&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The following tool can be found here:&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/unnivm/tracelm-java-agent&quot;&gt;https://github.com/unnivm/tracelm-java-agent&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This tools offers a very cool dashboard to track the above information. The user is required to login to access this dashboard.&lt;/p&gt;
&lt;p&gt;This tool currently supports only Java based applications as it is basically working based on instrumentation technique. But, this tool will be extended ti support other programming languages as well.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kteoibfghsc0qzzw1o3u.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;p&gt;This project is under development and am going to add more features to to so that the idea is to make it an enterprise ready product.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/17391/llm-observability-tool</guid>
<pubDate>Mon, 11 May 2026 06:50:41 +0000</pubDate>
</item>
<item>
<title>The Heart of B3: Building the Matching Engine with RabbitMQ, Redis and Spring Boot</title>
<link>https://coderlegion.com/17341/the-heart-of-b3-building-the-matching-engine-with-rabbitmq-redis-and-spring-boot</link>
<description>&lt;p&gt;Hello everyone!&lt;/p&gt;
&lt;p&gt;Continuing the &lt;strong&gt;My Broker B3&lt;/strong&gt; series, we've reached one of the most anticipated components of the ecosystem: the &lt;strong&gt;B3 Matching Engine API&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;In the previous post, we built &lt;code&gt;b3-market-sync-api&lt;/code&gt; which synchronizes real prices from brapi.dev and stores them in Redis. Now it's time to use that data — this is the service that simulates the stock exchange itself, deciding whether a buy or sell order will be &lt;strong&gt;executed or rejected&lt;/strong&gt; based on the prices we just put in cache.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;-what-is-the-matching-engine-&quot;&gt;️ What is the Matching Engine?&lt;/h2&gt;
&lt;p&gt;In the real world, when you place a buy order through your broker, it gets sent to B3, which checks whether there's a counterpart willing to trade at that price. This process is called &lt;strong&gt;matching&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;In our simulator, the &lt;code&gt;b3-matching-engine-api&lt;/code&gt; reproduces this logic in a simplified way:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Intake&lt;/strong&gt;: Receives the order from the broker via RabbitMQ (queue &lt;code&gt;mq-broker-to-b3&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Price&lt;/strong&gt;: Fetches the current asset price from Redis (&lt;code&gt;market:price:{TICKER}&lt;/code&gt;) — data injected by the Market Sync we built previously&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Decision&lt;/strong&gt;: Applies the matching rule&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Persistence&lt;/strong&gt;: Records the result in PostgreSQL&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Notification&lt;/strong&gt;: Returns the result to the broker via RabbitMQ (queue &lt;code&gt;mq-b3-to-broker&lt;/code&gt;)&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;[Broker] ──RabbitMQ──▶ [mq-broker-to-b3] ──▶ [Matching Engine]
                                                      │
                                             Redis: market:price:{TICKER}
                                             (fed by Market Sync ⬆️)
                                                      │
                                         ┌────────────┴────────────┐
                                      FILLED                   REJECTED
                                         │                         │
                                   PostgreSQL                 PostgreSQL
                                         │                         │
                               [mq-b3-to-broker] ◀────────────────┘
                                         │
                                      [Broker]
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2 id=&quot;mvp-focus&quot;&gt;MVP Focus&lt;/h2&gt;
&lt;p&gt;Before diving into the code, same disclaimer as previous posts: &lt;strong&gt;we're building the foundation&lt;/strong&gt;. The goal is to have an end-to-end flow working with enough robustness to validate the POC.&lt;/p&gt;
&lt;p&gt;In this phase, I prioritized:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;End-to-end flow working correctly&lt;/li&gt;
&lt;li&gt;Proper failure handling (no orders silently disappearing)&lt;/li&gt;
&lt;li&gt;Dead Letter Queue for messages that fail during processing&lt;/li&gt;
&lt;li&gt;REST API for querying execution history&lt;/li&gt;
&lt;li&gt;Swagger documentation&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&quot;-tech-stack-2&quot;&gt;️ Tech Stack&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;Technology &lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Usage&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Java 21&lt;/strong&gt; + &lt;strong&gt;Spring Boot 3.5&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Service core&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Spring RabbitMQ&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Order intake and result notification&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Spring Data Redis&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Real-time price lookup&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Spring Data JPA&lt;/strong&gt; + &lt;strong&gt;PostgreSQL&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Execution persistence&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Flyway&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Database schema versioning&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;SpringDoc OpenAPI&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Swagger UI documentation&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id=&quot;-implementation-pillars-1&quot;&gt;️ Implementation Pillars&lt;/h2&gt;
&lt;h3 id=&quot;1-rabbitmq-configuration&quot;&gt;1. RabbitMQ Configuration&lt;/h3&gt;
&lt;p&gt;A critical lesson learned here: &lt;strong&gt;never rely on queues already existing in the broker&lt;/strong&gt;. If the application starts before RabbitMQ has the queues created, the consumer fails on startup.&lt;/p&gt;
&lt;p&gt;The solution is to declare all infrastructure beans directly in Spring — it ensures queues, exchanges and bindings exist before any message is processed:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Bean
public DirectExchange exchange() {
    return new DirectExchange(exchangeName);
}

@Bean
public Queue queueIn() {
    return QueueBuilder.durable(queueIn)
            .withArgument(&quot;x-dead-letter-exchange&quot;, dlxName)
            .withArgument(&quot;x-dead-letter-routing-key&quot;, dlqName)
            .build();
}

@Bean
public Binding bindingQueueIn(Queue queueIn, DirectExchange exchange) {
    return BindingBuilder.bind(queueIn).to(exchange).with(routingKey);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note the &lt;code&gt;withArgument&lt;/code&gt; calls — they configure the &lt;strong&gt;Dead Letter Queue&lt;/strong&gt; directly in the main queue declaration. Any message that fails processing is automatically redirected.&lt;/p&gt;
&lt;h3 id=&quot;2-the-matching-logic&quot;&gt;2. The Matching Logic&lt;/h3&gt;
&lt;p&gt;The heart of the service. The rule is straightforward:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;BUY&lt;/strong&gt;: If the price the user accepts to pay &amp;gt;= market price → &lt;code&gt;FILLED&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SELL&lt;/strong&gt;: If the price the user wants to receive &amp;lt;= market price → &lt;code&gt;FILLED&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Otherwise → &lt;code&gt;REJECTED&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;private static boolean isCanExecute(OrderEventDTO order, BigDecimal marketPrice) {
    if (SideStatus.BUY.name().equalsIgnoreCase(order.getSide())) {
        return order.getPrice().compareTo(marketPrice) &amp;gt;= 0;
    } else if (SideStatus.SELL.name().equalsIgnoreCase(order.getSide())) {
        return order.getPrice().compareTo(marketPrice) &amp;lt;= 0;
    }
    return false;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;3-failure-handling-no-silent-order-loss&quot;&gt;3. Failure Handling — No Silent Order Loss&lt;/h3&gt;
&lt;p&gt;A silent bug I identified: when the ticker wasn't in Redis, the method simply returned without doing anything. The broker would wait for a response that never came.&lt;/p&gt;
&lt;p&gt;The fix was simple but critical — any failure must notify the broker:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;if (marketDataOpt.isEmpty()) {
    log.warn(&quot;Price for ticker {} not found in Redis. Rejecting order {}&quot;,
             order.getTicker(), order.getOrderId());
    OrderResponseEvent response = new OrderResponseEvent(
        order.getOrderId(), ExecutionStatus.REJECTED.name(), BigDecimal.ZERO);
    orderProducer.sendToBroker(response);
    return;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;&lt;p&gt; This is exactly why we built &lt;code&gt;b3-market-sync-api&lt;/code&gt; first. If Redis doesn't have the price, the order is immediately rejected — correct and predictable behavior.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;4-transactional-guarantee&quot;&gt;4. Transactional Guarantee&lt;/h3&gt;
&lt;p&gt;Another critical point: persisting to PostgreSQL and publishing to RabbitMQ are two distinct operations. If the database saves but the publish fails, the execution is recorded but the broker is never notified — silent inconsistency.&lt;/p&gt;
&lt;p&gt;The solution was adding &lt;code&gt;@Transactional&lt;/code&gt; to the method that orchestrates both operations:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Transactional
private void saveAndNotify(OrderEventDTO order, BigDecimal price, ExecutionStatus status) {
    // 1. Persists in PostgreSQL
    OrderExecution execution = OrderExecution.builder()
            .orderId(order.getOrderId())
            .ticker(order.getTicker())
            .side(SideStatus.valueOf(order.getSide()))
            .quantity(order.getQuantity())
            .executedPrice(price)
            .status(status)
            .build();
    repository.save(execution);

    // 2. Notifies the Broker
    OrderResponseEvent response = new OrderResponseEvent(
        order.getOrderId(), status.name(), price);
    orderProducer.sendToBroker(response);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;5-dead-letter-queue&quot;&gt;5. Dead Letter Queue&lt;/h3&gt;
&lt;p&gt;For messages that fail in the consumer (unexpected processing error), we configured a full DLQ setup:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Bean
public DirectExchange deadLetterExchange() {
    return new DirectExchange(dlxName);
}

@Bean
public Queue deadLetterQueue() {
    return QueueBuilder.durable(dlqName).build();
}

@Bean
public Binding bindingDlq(Queue deadLetterQueue, DirectExchange deadLetterExchange) {
    return BindingBuilder.bind(deadLetterQueue)
            .to(deadLetterExchange)
            .with(dlqName);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the consumer, we rethrow the exception so RabbitMQ knows the message failed and routes it to the DLQ:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@RabbitListener(queues = &quot;${app.rabbitmq.queue-in}&quot;)
public void receiveOrder(OrderEventDTO event) {
    log.info(&quot;New order received: ID {} | Ticker {} | Side {}&quot;,
            event.getOrderId(), event.getTicker(), event.getSide());
    try {
        matchingService.process(event);
    } catch (Exception e) {
        log.error(&quot;Failed to process order {}: {}&quot;, event.getOrderId(), e.getMessage(), e);
        throw e; // RabbitMQ routes to DLQ
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;6-rest-api-swagger&quot;&gt;6. REST API + Swagger&lt;/h3&gt;
&lt;p&gt;Since the service exposes query endpoints, I configured &lt;strong&gt;Swagger UI&lt;/strong&gt; from the start:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Bean
public OpenAPI customOpenAPI() {
    return new OpenAPI()
            .info(new Info()
                    .title(&quot;B3 Matching Engine API&quot;)
                    .version(&quot;1.0.0&quot;)
                    .description(&quot;Simulates the B3 stock exchange matching engine...&quot;));
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Available endpoints:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;Method &lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Endpoint &lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Description&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;GET &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;/api/v1/executions&lt;/code&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;List all executions&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;GET &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;/api/v1/executions/order/{orderId}&lt;/code&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Find by order ID&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;GET &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;/api/v1/executions/ticker/{ticker}&lt;/code&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Find by ticker&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;GET &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;/api/v1/executions/status/{status}&lt;/code&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Find by status&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id=&quot;-validating-the-execution-2&quot;&gt;✅ Validating the Execution&lt;/h2&gt;
&lt;p&gt;With the application running locally, all components started correctly:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;✅ Flyway applied the &lt;code&gt;order_executions&lt;/code&gt; table migration&lt;/li&gt;
&lt;li&gt;✅ Hibernate validated the schema&lt;/li&gt;
&lt;li&gt;✅ RabbitMQ connected with queues and DLQ declared&lt;/li&gt;
&lt;li&gt;✅ Tomcat running on port &lt;code&gt;8091&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;✅ Swagger UI accessible at &lt;code&gt;http://localhost:8091/swagger-ui.html&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&quot;what-s-next--1&quot;&gt;What's Next?&lt;/h2&gt;
&lt;p&gt;With &lt;code&gt;b3-market-sync-api&lt;/code&gt; feeding Redis and &lt;code&gt;b3-matching-engine-api&lt;/code&gt; ready to process orders, the entire B3 side is operational.&lt;/p&gt;
&lt;p&gt;The next step is building the &lt;strong&gt;broker-order-api&lt;/strong&gt; — the orchestrator on the broker side that will manage the complete order lifecycle:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Receive the buy/sell intent from the user&lt;/li&gt;
&lt;li&gt;Validate the ticker via REST against &lt;code&gt;broker-asset-api&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Save the order as &lt;code&gt;PENDING&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Publish the event to Kafka&lt;/li&gt;
&lt;li&gt;Send the order to the Matching Engine via RabbitMQ&lt;/li&gt;
&lt;li&gt;Consume the feedback and update the final status&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Once that service is ready, we'll have the first complete end-to-end flow running in the ecosystem.&lt;/p&gt;
&lt;p&gt;Got any questions about the matching logic or RabbitMQ DLQ configuration? Drop them in the comments!&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;about-the-series-2&quot;&gt;About the Series&lt;/h2&gt;
&lt;p&gt;⬅️ Previous Post: &lt;a rel=&quot;nofollow&quot; href=&quot;#&quot;&gt;Syncing the Real Market: Consuming Brapi and Feeding Redis with Spring Boot&lt;/a&gt;&lt;/p&gt;
&lt;p&gt; Series Index: &lt;a rel=&quot;nofollow&quot; href=&quot;https://dev.to/rvneto/series-roadmap-building-a-stock-brokerage-simulator-with-microservices-kgh&quot;&gt;Series Roadmap&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Links:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.linkedin.com/in/roberto-de-vargas/&quot;&gt;LinkedIn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/rvneto/trading-b3-matching-engine&quot;&gt;GitHub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/17341/the-heart-of-b3-building-the-matching-engine-with-rabbitmq-redis-and-spring-boot</guid>
<pubDate>Sun, 10 May 2026 21:29:07 +0000</pubDate>
</item>
<item>
<title>Reason and Solutions for Java uses unchecked or unsafe operations:</title>
<link>https://coderlegion.com/17270/reason-and-solutions-for-java-uses-unchecked-or-unsafe-operations</link>
<description>&lt;p style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;As a developer working with Java, you have surely met this warning before: &quot;uses unchecked or unsafe operations&quot;. In this blog, we'll discover what is &quot;uses unchecked or unsafe operations&quot; and how to resolve it.&lt;/p&gt;
&lt;h1&gt;What is Java warning: uses unchecked or unsafe operations&lt;/h1&gt;
&lt;p style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;
 &quot;Uses unchecked or unsafe operations&quot; is a compilation warning that occurs when Java compiler is compiling our codes but it finds some codes which are lack of error checking or having unsafe operation. However, it's just an warning rather than error. It means that your codes will still run successfully and display the desired output.
&lt;/p&gt;
&lt;h2&gt;Examples of Java uses unchecked or unsafe operations&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;
  public static void main(String[] args) {
        ArrayList students = new ArrayList();
        students.add(&quot;Alex&quot;);
        students.add(&quot;Cindy&quot;);
        students.add(&quot;Benjamin&quot;);
        for(int i = 0; i &amp;lt; students.size(); i++){
            System.out.println(students.get(i));
        }
        }
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;This is the output: &lt;/p&gt;
&lt;p&gt; &lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;&lt;br&gt;
Note: C:\Users\MC\Documents\NetBeansProjects\LabViva\src\labviva\NewMain.java uses unchecked or unsafe operations.&lt;br&gt;
Note: Recompile with -Xlint:unchecked for details.&lt;br&gt;
compile-single:&lt;br&gt;
run-single:&lt;br&gt;
Alex&lt;br&gt;
Cindy&lt;br&gt;
Benjamin&lt;br&gt;
  &lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br&gt;
&lt;p style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;Look, we've got the desired output, as we print each student in the &lt;em&gt;ArrayList&lt;/em&gt;, &lt;em style=&quot;background-color: rgb(206, 212, 217); color: rgb(0, 0, 0);&quot;&gt;students&lt;/em&gt;. However, you may also notice the warning: &lt;em style=&quot;background-color: rgba(255, 118, 111, 1); color: rgb(0, 0, 0);&quot;&gt;uses unchecked or unsafe operations. Note: Recompile with -Xlint:unchecked for details.&lt;/em&gt;&lt;/p&gt;
&lt;h1&gt;Causes of Java warning: uses unchecked or unsafe operations&lt;/h1&gt;
&lt;p style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;Basically, it occurs when we use &lt;em&gt;raw types&lt;/em&gt; of &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.oracle.com/javase/8/docs/api/java/util/Collection.html&quot; target=&quot;_blank&quot;&gt;collections&lt;/a&gt; rather than &lt;em&gt;paramaterized types&lt;/em&gt;, Which is also known as &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.oracle.com/javase/tutorial/java/generics/why.html&quot; target=&quot;_blank&quot;&gt;generics&lt;/a&gt; of collections. This is due to the reason that specifying the raw types of collections in hazardous as it might produce unexpected error during runtime, such as &lt;em&gt;&lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.oracle.com/javase/7/docs/api/java/lang/ClassCastException.html&quot;&gt;ClassCastException&lt;/a&gt;&lt;/strong&gt;&lt;/em&gt;. For example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;
public static void main(String[] args) {
       ArrayList numbers = new ArrayList();
       numbers.add(1);
       numbers.add(2);
       numbers.add(&quot;Cindy&quot;);
       for(int i = 0; i &amp;lt; numbers.size() - 1; i++){
           System.out.println((int)numbers.get(i) + (int)numbers.get(i + 1));
       }
       }
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;This is the output: &lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;
Note: C:\Users\MC\Documents\NetBeansProjects\LabViva\src\labviva\NewMain.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
compile-single:
run-single:
3
Exception in thread &quot;main&quot; java.lang.ClassCastException: class java.lang.String cannot be cast to class java.lang.Integer (java.lang.String and java.lang.Integer are in module java.base of loader 'bootstrap')
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;Here, we're trying to cast the value, &lt;em style=&quot;background-color: rgb(206, 212, 217); color: rgb(0, 0, 0);&quot;&gt;&quot;Cindy&quot;&lt;/em&gt; to an integer value for addition opearation, which is invalid and hazardous. This error happens because we're using the raw ArrayList! We accidentally insert a &lt;em&gt;String&lt;/em&gt; value into the &lt;em&gt;ArrayList&lt;/em&gt; of &lt;em&gt;Integer&lt;/em&gt; type. &lt;/p&gt;
&lt;p style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;So now, how can we differentiate the &lt;em&gt;raw&lt;/em&gt; collections and &lt;em&gt;generic&lt;/em&gt; collections?&lt;/p&gt;
&lt;h3&gt;Examples:&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;
ArrayList students = new ArrayList();
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;This is the raw types of &lt;em&gt;ArrayList&lt;/em&gt; without any type specifier.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;
ArrayList students = new ArrayList();
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;The &lt;em&gt;ArrayList&lt;/em&gt;, &lt;em style=&quot;background-color: rgb(206, 212, 217); color: rgb(0, 0, 0);&quot;&gt;students&lt;/em&gt; has the type specifier, which is &lt;em style=&quot;background-color: rgb(206, 212, 217); color: rgb(0, 0, 0);&quot;&gt;&lt;/em&gt;, but it doesn't include it at the end of the constructor.&lt;/p&gt;
&lt;p style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;Both of the examples above will render the compilation error: &quot;Uses unchecked or unsafe operations&quot; because the Java compiler refers to the &lt;em&gt;ArrayList&lt;/em&gt; raw type rather than &lt;em&gt;generic types&lt;/em&gt;.&lt;/p&gt;
&lt;p style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;This is the generic collections:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;
ArrayList students = new ArrayList();
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;or like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;
ArrayList students = new ArrayList&amp;lt;&amp;gt;();
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;div style=&quot;margin-top: 15px&quot; class=&quot;div-green&quot;&gt;
&lt;span class=&quot;alert-header&quot;&gt;Tip&lt;/span&gt;
&lt;span class=&quot;alert-body&quot;&gt;After specifying the generic types, ArrayList at the front, we can substitute the parameterized type of construtor with &lt;em&gt;empty diamond&lt;/em&gt;, &amp;lt;&amp;gt; as the compiler may recognize the generic data type and do the &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.oracle.com/javase/7/docs/technotes/guides/language/type-inference-generic-instance-creation.html&quot; target=&quot;_blank&quot;&gt;type inference&lt;/a&gt; automatically.&lt;/span&gt;
&lt;/div&gt;
&lt;h1&gt;Solution to Java warning: uses unchecked or unsafe operations&lt;/h1&gt;
&lt;p style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;The solution is pretty simple and straightforward. You should always use &lt;em&gt;&lt;strong&gt;generic&lt;/strong&gt;&lt;/em&gt; collections rather than &lt;em&gt;&lt;strong&gt;raw collections! It's always the best to specify the data type of the collections so that the Java compiler can check across the collections to make sure it stores a list of values with same data type to avoid any unintentional error.&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;
 public static void main(String[] args) {
        ArrayList students = new ArrayList&amp;lt;&amp;gt;();
        students.add(&quot;Alex&quot;);
        students.add(&quot;Cindy&quot;);
        students.add(&quot;Benjamin&quot;);
        for(int i = 0; i &amp;lt; students.size(); i++){
            System.out.println(students.get(i));
        }
        }
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;This is the output: &lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;
Alex
Cindy
Benjamin
BUILD SUCCESSFUL (total time: 1 second)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;You may notice that the compile warning: &quot;Uses unchecked or unsafe operations&quot; has gone.&lt;/p&gt;
&lt;p style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;After specifying the generic type, if you add a value with data type different from that generic type, you would get the compile error.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;
    public static void main(String[] args) {
        ArrayList students = new ArrayList&amp;lt;&amp;gt;();
        students.add(&quot;Alex&quot;);
        students.add(&quot;Cindy&quot;);
        students.add(1);
        for(int i = 0; i &amp;lt; students.size(); i++){
            System.out.println(students.get(i));
        }
        }
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;This is the compile error: &lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;
incompatible types: int cannot be converted to String
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;This is because the value, &lt;em style=&quot;background-color: rgb(206, 212, 217); color: rgb(0, 0, 0);&quot;&gt;1&lt;/em&gt; has &lt;em style=&quot;background-color: rgb(206, 212, 217); color: rgb(0, 0, 0);&quot;&gt;Integer&lt;/em&gt; data type, which is incompatible to the generic type, &lt;em style=&quot;background-color: rgb(206, 212, 217); color: rgb(0, 0, 0);&quot;&gt;&lt;/em&gt; of the collection.&lt;/p&gt;
&lt;div style=&quot;margin-top: 15px&quot; class=&quot;div-green&quot;&gt;
&lt;span class=&quot;alert-header&quot;&gt;Tip&lt;/span&gt;
&lt;span class=&quot;alert-body&quot;&gt;If you want to know why and where the compile error happens, you can recompile your projects using the &lt;em style=&quot;background-color: rgb(206, 212, 217); color: rgb(0, 0, 0);&quot;&gt;-Xlint:unchecked&lt;/em&gt; command.&lt;/span&gt;
&lt;/div&gt;
&lt;p style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;For Apache Netbeans, go to &lt;em style=&quot;background-color: rgb(206, 212, 217); color: rgb(0, 0, 0);&quot;&gt;Your Project&lt;/em&gt; -&amp;gt; &lt;em style=&quot;background-color: rgb(206, 212, 217); color: rgb(0, 0, 0);&quot;&gt;Properties&lt;/em&gt; -&amp;gt; &lt;em style=&quot;background-color: rgb(206, 212, 217); color: rgb(0, 0, 0);&quot;&gt;Compiling&lt;/em&gt; -&amp;gt; &lt;em style=&quot;background-color: rgb(206, 212, 217); color: rgb(0, 0, 0);&quot;&gt;Additional Compilation Options&lt;/em&gt;, and paste &lt;em style=&quot;background-color: rgb(206, 212, 217); color: rgb(0, 0, 0);&quot;&gt;-Xlint:unchecked&lt;/em&gt;. When you run the codes:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;
    public static void main(String[] args) {
        ArrayList students = new ArrayList();
        students.add(&quot;Alex&quot;);
        students.add(&quot;Cindy&quot;);
        students.add(&quot;Benjamin&quot;);
        for(int i = 0; i &amp;lt; students.size(); i++){
            System.out.println(students.get(i));
        }
        }
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;This is the output: &lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;
C:\Users\MC\Documents\NetBeansProjects\LabViva\src\labviva\NewMain.java:21: warning: [unchecked] unchecked call to add(E) as a member of the raw type ArrayList
        students.add(&quot;Alex&quot;);
  where E is a type-variable:
    E extends Object declared in class ArrayList
C:\Users\MC\Documents\NetBeansProjects\LabViva\src\labviva\NewMain.java:22: warning: [unchecked] unchecked call to add(E) as a member of the raw type ArrayList
        students.add(&quot;Cindy&quot;);
  where E is a type-variable:
    E extends Object declared in class ArrayList
C:\Users\MC\Documents\NetBeansProjects\LabViva\src\labviva\NewMain.java:23: warning: [unchecked] unchecked call to add(E) as a member of the raw type ArrayList
        students.add(&quot;Benjamin&quot;);
  where E is a type-variable:
    E extends Object declared in class ArrayList
3 warnings
compile-single:
run-single:
Alex
Cindy
Benjamin
BUILD SUCCESSFUL (total time: 1 second)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;Then, you can notice what of your codes produce this compile warnings.&lt;/p&gt;
&lt;div class=&quot;div-purple&quot;&gt;
&lt;span class=&quot;alert-header&quot;&gt;Note&lt;/span&gt;
&lt;span class=&quot;alert-body&quot;&gt;You can also use &lt;em style=&quot;background-color: rgba(255, 118, 111, 1); color: rgb(0, 0, 0);&quot;&gt;@SuppressWarnings(&quot;unchecked&quot;)&lt;/em&gt; to hide this warning. However, it's inadvisable because we should always use generic collections rather than raw collections. Hiding this warning will cause you to not realise when you're using raw collections.&lt;/span&gt;
&lt;/div&gt;
&lt;pre&gt;&lt;code&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;
    @SuppressWarnings(&quot;unchecked&quot;)
    public static void main(String[] args) {
        ArrayList students = new ArrayList();
        students.add(&quot;Alex&quot;);
        students.add(&quot;Cindy&quot;);
        students.add(&quot;Benjamin&quot;);
        for(int i = 0; i &amp;lt; students.size(); i++){
            System.out.println(students.get(i));
        }
        }
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;This is the output: &lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;
Alex
Cindy
Benjamin
BUILD SUCCESSFUL (total time: 1 second)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;The compilation warning: &quot;uses unchecked or unsafe operations&quot; is now hidden rather than solved.&lt;/p&gt;
&lt;p style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;That's all about Java uses unchecked or unsafe operations and the way to solve it. Hope it helps, Thanks! :)&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/17270/reason-and-solutions-for-java-uses-unchecked-or-unsafe-operations</guid>
<pubDate>Sun, 10 May 2026 11:22:40 +0000</pubDate>
</item>
<item>
<title>Syncing the Real Market: Consuming Brapi and Feeding Redis with Spring Boot</title>
<link>https://coderlegion.com/17273/syncing-the-real-market-consuming-brapi-and-feeding-redis-with-spring-boot</link>
<description>&lt;p&gt;Hello everyone!&lt;/p&gt;
&lt;p&gt;Continuing the &lt;strong&gt;My Broker B3&lt;/strong&gt; series, today we'll talk about an essential component on the B3 side of our simulator: the &lt;strong&gt;B3 Market Sync API&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Before building the engine that decides whether an order gets executed or rejected, we need to ensure it has access to real market prices. That's exactly what &lt;code&gt;b3-market-sync-api&lt;/code&gt; does: &lt;strong&gt;fetches real asset prices from brapi.dev and stores them in Redis&lt;/strong&gt;, ready for real-time consumption.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;-what-is-the-market-sync-&quot;&gt;️ What is the Market Sync?&lt;/h2&gt;
&lt;p&gt;This microservice has a single, well-defined responsibility: to be the &lt;strong&gt;market data producer&lt;/strong&gt; of the ecosystem. It operates completely asynchronously and independently — no other service needs to call it directly.&lt;/p&gt;
&lt;p&gt;The flow is simple and elegant:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;[brapi.dev] ◀── Feign Client ── [MarketSyncScheduler]
                                         │
                              market:price:{TICKER}
                                         │
                                      [Redis]
                                         │
                              [B3 Matching Engine] (next post)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2 id=&quot;mvp-focus-1&quot;&gt;MVP Focus&lt;/h2&gt;
&lt;p&gt;The goal here is clear: &lt;strong&gt;have real prices available in Redis with low latency&lt;/strong&gt;. The Matching Engine we'll build in the next post won't query a database or make HTTP calls to get prices — it goes straight to Redis. This ensures the matching decision happens in microseconds.&lt;/p&gt;
&lt;p&gt;In this phase, I prioritized:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Scheduled sync with market hours guard&lt;/li&gt;
&lt;li&gt;Redis cache with TTL to prevent stale data&lt;/li&gt;
&lt;li&gt;External API rate limit protection&lt;/li&gt;
&lt;li&gt;Critical bug fixes found during code review&lt;/li&gt;
&lt;li&gt;REST API for querying cached prices&lt;/li&gt;
&lt;li&gt;Swagger documentation&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&quot;-tech-stack-3&quot;&gt;️ Tech Stack&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;Technology &lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Usage&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Java 21&lt;/strong&gt; + &lt;strong&gt;Spring Boot 3.5.11&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Service core&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Spring Cloud OpenFeign&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Declarative HTTP client for brapi.dev&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Spring Data Redis&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;High-performance cache&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Spring Scheduling&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Periodic synchronization&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Jackson JSR310&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Java 8 Date/Time serialization support&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;SpringDoc OpenAPI&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Swagger UI documentation&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id=&quot;-implementation-pillars-2&quot;&gt;️ Implementation Pillars&lt;/h2&gt;
&lt;h3 id=&quot;1-feign-client-consuming-brapi&quot;&gt;1. Feign Client — Consuming Brapi&lt;/h3&gt;
&lt;p&gt;Spring Cloud OpenFeign lets you declare an HTTP client as a simple interface, with no boilerplate code:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@FeignClient(name = &quot;brapiClient&quot;, url = &quot;${app.brapi.url}&quot;)
public interface BrapiClient {

    @GetMapping(&quot;/quote/{ticker}&quot;)
    BrapiResponseDTO getQuote(
        @PathVariable String ticker,
        @RequestParam String token
    );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The URL and token are injected via &lt;code&gt;application.yaml&lt;/code&gt;, keeping the code clean and environment-configurable.&lt;/p&gt;
&lt;h3 id=&quot;2-scheduler-with-market-hours-guard&quot;&gt;2. Scheduler with Market Hours Guard&lt;/h3&gt;
&lt;p&gt;The heart of the service is a scheduled job that fires every 30 minutes. But before any API call, it checks whether the market is open:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Scheduled(fixedRateString = &quot;${app.sync.interval:1800000}&quot;)
public void sync() {
    if (!isMarketOpen()) {
        log.info(&quot;Sync aborted: market is closed (outside trading hours or weekend).&quot;);
        return;
    }
    // ... fetch and store prices
}

private boolean isMarketOpen() {
    ZonedDateTime now = ZonedDateTime.now(ZoneId.of(&quot;America/Sao_Paulo&quot;));
    DayOfWeek day = now.getDayOfWeek();
    int hour = now.getHour();

    if (day == DayOfWeek.SATURDAY || day == DayOfWeek.SUNDAY) {
        return false;
    }

    // B3 trading hours: 10:00 to 18:00 (Sao Paulo time)
    return hour &amp;gt;= 10 &amp;amp;&amp;amp; hour &amp;lt; 18;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Important detail:&lt;/strong&gt; we use &lt;code&gt;ZonedDateTime&lt;/code&gt; with &lt;code&gt;America/Sao_Paulo&lt;/code&gt; explicitly. Inside a Docker container, the JVM may default to UTC — which would make the market hours guard behave incorrectly. We also pass &lt;code&gt;-Duser.timezone=America/Sao_Paulo&lt;/code&gt; in the Dockerfile &lt;code&gt;ENTRYPOINT&lt;/code&gt; to ensure the JVM honors the correct timezone:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-dockerfile&quot;&gt;ENTRYPOINT [&quot;java&quot;, &quot;-Duser.timezone=America/Sao_Paulo&quot;, &quot;-jar&quot;, &quot;app.jar&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;3-redis-cache-with-correct-ttl&quot;&gt;3. Redis Cache with Correct TTL&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;MarketDataService&lt;/code&gt; stores each quote in Redis with a standardized key and 5-minute TTL:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;private static final Duration CACHE_TTL = Duration.ofMinutes(5);

public void saveToCache(BrapiResultDTO quote) {
    String key = CACHE_KEY_PREFIX + quote.getTicker();
    // TTL of 5 minutes — if sync stops, stale prices expire quickly
    // preventing the Matching Engine from using outdated data
    redisTemplate.opsForValue().set(key, quote, CACHE_TTL);
    log.info(&quot;Ticker {} updated in cache: R$ {}&quot;, quote.getTicker(), quote.getRegularMarketPrice());
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The TTL is fundamental: if the service goes down, prices expire in 5 minutes. The Matching Engine then fails to find the price in Redis and rejects orders — correct and predictable behavior, much better than using hours-old prices.&lt;/p&gt;
&lt;h3 id=&quot;4-epoch-timestamp-deserialization&quot;&gt;4. Epoch Timestamp Deserialization&lt;/h3&gt;
&lt;p&gt;A subtle bug I found: brapi.dev returns the &lt;code&gt;regularMarketTime&lt;/code&gt; field as a &lt;strong&gt;Unix epoch&lt;/strong&gt; (integer), but Jackson was trying to deserialize it as &lt;code&gt;LocalDateTime&lt;/code&gt; (ISO string). This causes silent deserialization failure.&lt;/p&gt;
&lt;p&gt;The solution was a custom deserializer:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public class EpochToLocalDateTimeDeserializer extends StdDeserializer&amp;lt;LocalDateTime&amp;gt; {

    private static final ZoneId SAO_PAULO = ZoneId.of(&quot;America/Sao_Paulo&quot;);

    @Override
    public LocalDateTime deserialize(JsonParser parser, DeserializationContext context)
            throws IOException {
        long epoch = parser.getLongValue();
        return LocalDateTime.ofInstant(Instant.ofEpochSecond(epoch), SAO_PAULO);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And annotating it on the DTO:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@JsonDeserialize(using = EpochToLocalDateTimeDeserializer.class)
private LocalDateTime regularMarketTime;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;5-rate-limit-protection&quot;&gt;5. Rate Limit Protection&lt;/h3&gt;
&lt;p&gt;Since we use the brapi.dev free plan, we added a delay between calls to avoid throttling:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;for (String ticker : brapiProperties.getTickers()) {
    try {
        BrapiResponseDTO response = brapiClient.getQuote(ticker.trim(), brapiProperties.getToken());
        if (nonNull(response.getResults()) &amp;amp;&amp;amp; !response.getResults().isEmpty()) {
            marketDataService.saveToCache(response.getResults().getFirst());
        }
        // Small delay between calls to avoid hitting rate limit on free plan
        Thread.sleep(200);
    } catch (Exception e) {
        log.error(&quot;Failed to sync ticker {}: {}&quot;, ticker, e.getMessage());
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The per-ticker &lt;code&gt;try/catch&lt;/code&gt; is essential — if one asset fails, the loop continues for the rest.&lt;/p&gt;
&lt;h3 id=&quot;6-rest-api-swagger-1&quot;&gt;6. REST API + Swagger&lt;/h3&gt;
&lt;p&gt;Although the Matching Engine consumes prices directly from Redis, we added REST endpoints to make debugging and observability easier:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;Method &lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Endpoint &lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Description&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;GET &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;/api/v1/quotes&lt;/code&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;List prices for all configured tickers&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;GET &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;/api/v1/quotes/{ticker}&lt;/code&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Get price for a specific ticker&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Documented via Swagger UI at &lt;code&gt;http://localhost:8096/swagger-ui.html&lt;/code&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;security-token-out-of-code&quot;&gt;Security — Token Out of Code&lt;/h2&gt;
&lt;p&gt;A critical issue I fixed: the brapi.dev token was hardcoded as a default value in &lt;code&gt;application.yaml&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;# ❌ Before — token exposed in public repository
token: ${BRAPI_TOKEN:mFk9YRLijAtxsP5y1XxxxX}

# ✅ After — no fallback, variable is required
token: ${BRAPI_TOKEN}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Never expose credentials as default values in public repositories. The application now fails on startup if the variable is not provided — correct behavior.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;-validating-the-execution-3&quot;&gt;✅ Validating the Execution&lt;/h2&gt;
&lt;p&gt;With the application running locally:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;✅ Tomcat started on port &lt;code&gt;8096&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;✅ Application initialized in under 3 seconds&lt;/li&gt;
&lt;li&gt;✅ Scheduler fired and logged: &lt;code&gt;Sync aborted: market is closed (outside trading hours or weekend)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;✅ Swagger UI accessible at &lt;code&gt;http://localhost:8096/swagger-ui.html&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&quot;what-s-next--2&quot;&gt;What's Next?&lt;/h2&gt;
&lt;p&gt;With &lt;code&gt;b3-market-sync-api&lt;/code&gt; up and feeding Redis with real prices, we have the foundation the next service needs to work. In the next post we'll build the &lt;strong&gt;B3 Matching Engine&lt;/strong&gt; — the engine that will consume these prices and decide whether the broker's orders are executed or rejected.&lt;/p&gt;
&lt;p&gt;Got any questions about the Redis integration or the market hours guard? Drop them in the comments!&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;about-the-series-3&quot;&gt;About the Series&lt;/h2&gt;
&lt;p&gt;⬅️ Previous Post: &lt;a rel=&quot;nofollow&quot; href=&quot;https://dev.to/rvneto/from-stream-to-database-processing-market-data-with-spring-boot-redis-and-flyway-35m9&quot;&gt;From Stream to Database: Processing Market Data with Spring Boot, Redis and Flyway&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;➡️ Next Post: The Heart of B3: Building the Matching Engine with RabbitMQ and Redis &lt;em&gt;(coming soon)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt; Series Index: &lt;a rel=&quot;nofollow&quot; href=&quot;https://dev.to/rvneto/series-roadmap-building-a-stock-brokerage-simulator-with-microservices-kgh&quot;&gt;Series Roadmap&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Links:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.linkedin.com/in/roberto-de-vargas/&quot;&gt;LinkedIn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/rvneto/trading-b3-market-sync&quot;&gt;GitHub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/17273/syncing-the-real-market-consuming-brapi-and-feeding-redis-with-spring-boot</guid>
<pubDate>Sun, 10 May 2026 11:03:34 +0000</pubDate>
</item>
<item>
<title>From Stream to Database: Processing Market Data with Spring Boot, Redis, and Flyway</title>
<link>https://coderlegion.com/17111/from-stream-to-database-processing-market-data-with-spring-boot-redis-and-flyway</link>
<description>&lt;p&gt;Hey folks!&lt;/p&gt;
&lt;p&gt;Continuing the &lt;strong&gt;My Broker B3&lt;/strong&gt; series, today we build &lt;code&gt;trading-broker-asset&lt;/code&gt; — the Java service that &lt;strong&gt;consumes the quotes published to Kafka by trading-broker-market-data&lt;/strong&gt; and transforms them into structured data in MySQL, with high-performance caching in Redis.&lt;/p&gt;
&lt;p&gt;If the Python service is the collector, this one is the processor.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;-this-service-s-role-in-the-ecosystem&quot;&gt;️ This Service's Role in the Ecosystem&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;[trading-broker-market-data]
         │
         │ Kafka: trading-assets-market-data-v1
         ▼
[trading-broker-asset]
         │
    ┌────┴────┐
    ▼         ▼
  MySQL     Redis
(assets)  market:price:{TICKER}
              │
    [b3-matching-engine]   (reads from here)
    [trading-broker-order] (validates ticker from here)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Two critical consumers depend on the data this service maintains: the Matching Engine (for price decisions) and the Order API (to validate whether a ticker exists and is active).&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;mvp-focus-2&quot;&gt;MVP Focus&lt;/h2&gt;
&lt;p&gt;In this phase, I prioritized:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Robust Kafka consumer with error handling&lt;/li&gt;
&lt;li&gt;Asset upsert: creates on first event, updates on subsequent ones&lt;/li&gt;
&lt;li&gt;Redis cache with 10-minute TTL&lt;/li&gt;
&lt;li&gt;REST API for catalog queries&lt;/li&gt;
&lt;li&gt;Status filter: only &lt;code&gt;ACTIVE&lt;/code&gt; assets are returned&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&quot;-tech-stack-4&quot;&gt;️ Tech Stack&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;Technology &lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Usage&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Java 21&lt;/strong&gt; + &lt;strong&gt;Spring Boot 3.5.11&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Service core&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Spring Kafka&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Quote event consumption&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;MySQL&lt;/strong&gt; + &lt;strong&gt;Flyway&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Versioned asset catalog&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Spring Data Redis&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Real-time price cache&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;SpringDoc OpenAPI&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Swagger UI documentation&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id=&quot;-implementation-pillars-3&quot;&gt;️ Implementation Pillars&lt;/h2&gt;
&lt;h3 id=&quot;1-the-database-schema-flyway-&quot;&gt;1. The Database Schema (Flyway)&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;CREATE TABLE assets (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    ticker VARCHAR(20) NOT NULL UNIQUE,
    name VARCHAR(200),
    current_price DECIMAL(19, 4),
    last_update DATETIME,
    status ENUM('ACTIVE', 'INACTIVE') NOT NULL DEFAULT 'ACTIVE',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Why Flyway?&lt;/strong&gt; Schema versioning ensures that any environment (local, CI, production) has exactly the same database structure, without surprises.&lt;/p&gt;
&lt;h3 id=&quot;2-the-kafka-consumer&quot;&gt;2. The Kafka Consumer&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@KafkaListener(
        topics = &quot;trading-assets-market-data-v1&quot;,
        groupId = &quot;trading-broker-asset&quot;
)
public void consume(AssetMarketDataDTO dto) {
    log.info(&quot;Market data received for ticker: {}&quot;, dto.getTicker());
    try {
        assetService.updateAsset(dto);
    } catch (Exception e) {
        log.error(&quot;Failed to process market data for ticker {}: {}&quot;,
                dto.getTicker(), e.getMessage(), e);
        // Rethrow so Kafka can apply retry policy instead of silently discarding
        throw e;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Why rethrow the exception?&lt;/strong&gt; If processing fails silently, the offset is committed and the message is discarded — the asset is never updated. Rethrowing lets Kafka apply its retry policy.&lt;/p&gt;
&lt;h3 id=&quot;3-the-asset-upsert&quot;&gt;3. The Asset Upsert&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Transactional
public void updateAsset(AssetMarketDataDTO dto) {
    Asset asset = assetRepository.findByTicker(dto.getTicker())
            .orElse(Asset.builder()
                    .ticker(dto.getTicker())
                    .status(AssetStatus.ACTIVE)
                    .build());

    asset.setName(dto.getName());
    asset.setCurrentPrice(dto.getPrice());
    // Uses real market timestamp from event — not LocalDateTime.now()
    asset.setLastUpdate(dto.getUpdatedAt() != null ? dto.getUpdatedAt() : LocalDateTime.now());

    assetRepository.save(asset);

    // Update Redis cache after persisting
    marketPriceCacheService.updatePrice(dto.getTicker(), dto.getPrice());
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Important decision:&lt;/strong&gt; we use the real &lt;code&gt;updatedAt&lt;/code&gt; from the Kafka event, not &lt;code&gt;LocalDateTime.now()&lt;/code&gt;. This ensures &lt;code&gt;lastUpdate&lt;/code&gt; in the database reflects when the price was generated by the market, not when it was processed by the service.&lt;/p&gt;
&lt;h3 id=&quot;4-redis-cache-with-ttl&quot;&gt;4. Redis Cache with TTL&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;private static final Duration CACHE_TTL = Duration.ofMinutes(10);

public void updatePrice(String ticker, BigDecimal price) {
    String key = &quot;market:price:&quot; + ticker;
    // TTL of 10 minutes — prevents stale prices for inactive or delisted assets
    redisTemplate.opsForValue().set(key, price.toString(), CACHE_TTL);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Why TTL?&lt;/strong&gt; If an asset is deactivated or delisted, its price will expire from Redis in 10 minutes. Without TTL, the Matching Engine could use prices for assets that no longer exist.&lt;/p&gt;
&lt;h3 id=&quot;5-status-filter-in-queries&quot;&gt;5. Status Filter in Queries&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public List&amp;lt;AssetDTO&amp;gt; findAllActive() {
    // Query directly by status — not findAll() + filter in memory
    return assetRepository.findAllByStatus(AssetStatus.ACTIVE).stream()
            .map(AssetDTO::fromEntity)
            .toList();
}

public Optional&amp;lt;AssetDTO&amp;gt; findByTicker(String ticker) {
    // Only returns ACTIVE assets — inactive returns 404
    return assetRepository.findByTickerAndStatus(ticker.toUpperCase(), AssetStatus.ACTIVE)
            .map(AssetDTO::fromEntity);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When &lt;code&gt;trading-broker-order&lt;/code&gt; validates a ticker before creating an order, it calls &lt;code&gt;GET /api/v1/assets/{ticker}&lt;/code&gt;. If the asset is inactive, it gets a 404 and the order is rejected. Correct and predictable.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;rest-api-2&quot;&gt;REST API&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;Method &lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Endpoint &lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Description&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;GET &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;/api/v1/assets&lt;/code&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;List all active assets&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;GET &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;/api/v1/assets/{ticker}&lt;/code&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Find asset by ticker (404 if inactive)&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt; &lt;strong&gt;Swagger UI&lt;/strong&gt;: &lt;code&gt;http://localhost:8083/swagger-ui.html&lt;/code&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;-validating-the-execution-4&quot;&gt;✅ Validating the Execution&lt;/h2&gt;
&lt;p&gt;With the application running and &lt;code&gt;trading-broker-market-data&lt;/code&gt; publishing to Kafka:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;✅ Consumer connected to the &lt;code&gt;trading-assets-market-data-v1&lt;/code&gt; topic&lt;/li&gt;
&lt;li&gt;✅ Assets being created/updated in MySQL in real time&lt;/li&gt;
&lt;li&gt;✅ Redis being updated with TTL after each event&lt;/li&gt;
&lt;li&gt;✅ Logs showing &lt;code&gt;Updating ticker PETR4 with price R$ 47.37&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&quot;what-s-next--3&quot;&gt;What's Next?&lt;/h2&gt;
&lt;p&gt;With the asset catalog working, the next step is &lt;code&gt;b3-market-sync-api&lt;/code&gt; — which synchronizes prices directly to the B3's Redis — followed by &lt;code&gt;b3-matching-engine-api&lt;/code&gt;, which uses those prices to execute orders.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;about-the-series-4&quot;&gt;About the Series&lt;/h2&gt;
&lt;p&gt;⬅️ Previous Post: &lt;a rel=&quot;nofollow&quot; href=&quot;https://dev.to/rvneto/tooling-tips-visualizing-your-data-in-mongodb-and-kafka-1k1p&quot;&gt;Tooling Tips: MongoDB and Kafka&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;➡️ Next Post: Syncing the Real Market: Consuming Brapi and Feeding Redis with Spring Boot&lt;/p&gt;
&lt;p&gt; Series Index: &lt;a rel=&quot;nofollow&quot; href=&quot;https://dev.to/rvneto/series-roadmap-building-a-stock-brokerage-simulator-with-microservices-kgh&quot;&gt;Series Roadmap&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Links:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.linkedin.com/in/roberto-de-vargas/&quot;&gt;LinkedIn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/rvneto/trading-broker-asset&quot;&gt;GitHub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/17111/from-stream-to-database-processing-market-data-with-spring-boot-redis-and-flyway</guid>
<pubDate>Fri, 08 May 2026 20:51:06 +0000</pubDate>
</item>
<item>
<title>Building a Microservices Ecosystem: Stock Brokerage Simulator (My Broker B3)</title>
<link>https://coderlegion.com/16327/building-a-microservices-ecosystem-stock-brokerage-simulator-my-broker-b3</link>
<description>&lt;p&gt;Hey folks!&lt;/p&gt;
&lt;p&gt;I'm starting a series of articles to document the development of &lt;strong&gt;My Broker B3&lt;/strong&gt; — a personal project where I apply advanced software engineering concepts, distributed systems and messaging to simulate the real-world operation of a stock brokerage integrated with B3 (the Brazilian stock exchange).&lt;/p&gt;
&lt;p&gt;The goal isn't to build a production system, but rather a &lt;strong&gt;POC (Proof of Concept)&lt;/strong&gt; that validates real-time order processing, financial custody management and market data orchestration using an event-driven architecture.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;-the-ecosystem-overview&quot;&gt;️ The Ecosystem Overview&lt;/h2&gt;
&lt;p&gt;The project was designed following the microservices philosophy with a hybrid stack — each service uses the technology that best fits its function:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;                    [broker-gateway-api]
                           │
          ┌────────────────┼────────────────┐
          ▼                ▼                ▼
   [broker-order-api] [broker-wallet] [broker-asset]
          │                │
     RabbitMQ           Kafka (order-events-v1)
          │
   [b3-matching-engine]
          │
       Redis ◀── [b3-market-sync-api] ◀── [brapi.dev]
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;the-services&quot;&gt;The Services&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Broker Side&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;Service &lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Language &lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Responsibility&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;broker-identity-api&lt;/code&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Java &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Authentication, JWT, user management&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;broker-gateway-api&lt;/code&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Java &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;API Gateway, routing, JWT validation&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;broker-order-api&lt;/code&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Java &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Order lifecycle orchestration&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;broker-wallet-api&lt;/code&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Java &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Financial custody, balance and positions&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;broker-asset-api&lt;/code&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Java &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Asset catalog and internal market data&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;broker-market-data-api&lt;/code&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Python &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Quote ingestion from Brapi → MongoDB + Kafka&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;broker-report-api&lt;/code&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Java &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Reports and history&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;B3 Side (Simulator)&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;Service &lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Language &lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Responsibility&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;b3-matching-engine-api&lt;/code&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Java &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Order execution, price matching&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;b3-market-sync-api&lt;/code&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Java &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Price synchronization to Redis&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id=&quot;-communication-strategy&quot;&gt;⚙️ Communication Strategy&lt;/h2&gt;
&lt;p&gt;One of the most important decisions in the project was choosing &lt;strong&gt;when to use synchronous communication and when to use asynchronous&lt;/strong&gt;. The rule was simple: if the response is needed now to continue the flow, use REST. If it can wait, use messaging.&lt;/p&gt;
&lt;h3 id=&quot;synchronous-rest-feign-&quot;&gt;Synchronous (REST/Feign)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;broker-order-api&lt;/code&gt; → &lt;code&gt;broker-asset-api&lt;/code&gt;: validates ticker before creating order&lt;/li&gt;
&lt;li&gt;&lt;code&gt;broker-order-api&lt;/code&gt; → &lt;code&gt;broker-wallet-api&lt;/code&gt;: validates balance before processing buy&lt;/li&gt;
&lt;li&gt;&lt;code&gt;broker-identity-api&lt;/code&gt; → &lt;code&gt;broker-wallet-api&lt;/code&gt;: creates wallet account on registration&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;asynchronous-rabbitmq-&quot;&gt;Asynchronous (RabbitMQ)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;broker-order-api&lt;/code&gt; → &lt;code&gt;b3-matching-engine-api&lt;/code&gt;: sends orders for execution&lt;/li&gt;
&lt;li&gt;&lt;code&gt;b3-matching-engine-api&lt;/code&gt; → &lt;code&gt;broker-order-api&lt;/code&gt;: execution feedback (FILLED/REJECTED)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;asynchronous-kafka-&quot;&gt;Asynchronous (Kafka)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;broker-market-data-api&lt;/code&gt; → topic &lt;code&gt;assets-market-data-v1&lt;/code&gt; → &lt;code&gt;broker-asset-api&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;broker-order-api&lt;/code&gt; → topic &lt;code&gt;order-events-v1&lt;/code&gt; → &lt;code&gt;broker-wallet-api&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&quot;persistence-strategy&quot;&gt;Persistence Strategy&lt;/h2&gt;
&lt;p&gt;Each service uses the database that best fits its nature:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;Technology &lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Service(s) &lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Why&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;MySQL&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;identity, wallet, order, asset &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Transactional data with strong consistency&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;PostgreSQL&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;b3-matching-engine &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;B3 core with execution history&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;MongoDB&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;broker-market-data &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Quote history — flexible schema&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Redis&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;broker-asset, b3-matching-engine &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Real-time price cache for fast decisions&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id=&quot;the-order-flow-end-to-end&quot;&gt;The Order Flow — End to End&lt;/h2&gt;
&lt;p&gt;To materialize the architecture, here's what happens when a user places a buy order:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;1. POST /api/v1/orders
        │
        ├─ Validate balance → broker-wallet-api (REST)
        ├─ Persist PENDING → MySQL
        ├─ Publish PENDING → Kafka (broker-wallet blocks balance)
        └─ Send to B3 → RabbitMQ

2. B3 processes, looks up price in Redis
        │
        ├─ FILLED/REJECTED → RabbitMQ → broker-order-api
        ├─ Update status → MySQL
        └─ Publish final status → Kafka (broker-wallet settles/refunds)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;From the user's click to the updated wallet balance, through validation, price matching and eventual consistency.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;what-you-ll-find-in-the-series&quot;&gt;What You'll Find in the Series&lt;/h2&gt;
&lt;p&gt;Each post documents the construction of a service, focusing on technical decisions, problems encountered and how they were solved:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Overview&lt;/strong&gt; ← you are here&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Infrastructure&lt;/strong&gt; — Docker Compose with 12+ containers&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;broker-market-data-api&lt;/strong&gt; — Python, Brapi, MongoDB, Kafka&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;b3-market-sync-api&lt;/strong&gt; — Price synchronization to Redis&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;b3-matching-engine-api&lt;/strong&gt; — The B3 execution engine&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;broker-asset-api&lt;/strong&gt; — Asset catalog and cache&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;broker-wallet-api&lt;/strong&gt; — Financial custody&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;broker-order-api&lt;/strong&gt; — The order conductor&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;broker-identity-api&lt;/strong&gt; — Authentication and JWT&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;broker-gateway-api&lt;/strong&gt; — API Gateway&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Feel free to leave feedback or questions in the comments!&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;about-the-series-5&quot;&gt;About the Series&lt;/h2&gt;
&lt;p&gt; Series Index: &lt;a rel=&quot;nofollow&quot; href=&quot;https://dev.to/rvneto/series-roadmap-building-a-stock-brokerage-simulator-with-microservices-kgh&quot;&gt;Series Roadmap&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Links:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.linkedin.com/in/roberto-de-vargas/&quot;&gt;LinkedIn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/rvneto&quot;&gt;GitHub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/16327/building-a-microservices-ecosystem-stock-brokerage-simulator-my-broker-b3</guid>
<pubDate>Fri, 01 May 2026 13:50:40 +0000</pubDate>
</item>
<item>
<title>Skill-Based Sub-Agents with LangGraph4j and Spring AI</title>
<link>https://coderlegion.com/16271/skill-based-sub-agents-with-langgraph4j-and-spring-ai</link>
<description>&lt;h3 id=&quot;combine-tools-amp-skill-to-create-agent&quot;&gt;Combine Tools &amp;amp; Skill to create Agent&lt;/h3&gt;
&lt;p&gt;Modern agentic systems usually expose two strong but separate abstractions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Tools&lt;/strong&gt;, which give an LLM executable capabilities&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Skills&lt;/strong&gt;, which package reusable instructions for a narrow task&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This article shows how to combine both ideas in [LangGraph4j] so that a skill is not just prompt text, but a fully operational &lt;strong&gt;sub-agent exposed as a tool&lt;/strong&gt;. The result is a practical pattern for building modular multi-agent systems in Java with &lt;strong&gt;[Spring AI]&lt;/strong&gt; and &lt;strong&gt;[LangGraph4j]&lt;/strong&gt;, while keeping each agent focused, reusable, and low in context cost.&lt;/p&gt;
&lt;p&gt;Starting from a standard ReACT agent, the approach implemented is quite simple.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Load a &lt;code&gt;SKILL.md&lt;/code&gt; file&lt;/li&gt;
&lt;li&gt;Read its front matter to define the tool contract ( name and argument description)&lt;/li&gt;
&lt;li&gt;Use the skill body as the sub-agent system prompt&lt;/li&gt;
&lt;li&gt;Restrict the sub-agent to only the tools declared by the skill&lt;/li&gt;
&lt;li&gt;Register the sub-agent in a parent ReAct agent as if it were a normal tool&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In other words, a skill becomes an &lt;em&gt;executable tool-backed agent&lt;/em&gt;.&lt;/p&gt;
&lt;h3 id=&quot;why-this-pattern-matters&quot;&gt;Why This Pattern Matters&lt;/h3&gt;
&lt;p&gt;The standard ReAct pattern already gives an LLM a strong execution model: reason, choose a tool, inspect the result, and continue until the task is complete. That model is useful because the LLM does not need to commit to a fixed plan up front. It can adapt after every tool call.&lt;/p&gt;
&lt;p&gt;The next step is to let a tool itself be implemented by another agent.&lt;/p&gt;
&lt;p&gt;Once that happens, the parent agent is no longer forced to know every operational detail. It only needs to know:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;which specialized capability exists&lt;/li&gt;
&lt;li&gt;when to invoke it&lt;/li&gt;
&lt;li&gt;what input to pass&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The specialized logic is delegated to a narrower agent with its own instructions and its own restricted toolset. This gives us a clean &lt;strong&gt;sub-agent architecture&lt;/strong&gt; without inventing a new orchestration primitive. We reuse the same tool-calling semantics that ReAct agents already understand well.&lt;/p&gt;
&amp;lt;!--
## Advantages

There are multiple advantages to use this approach:

### Reuse Tool execution plan (coordination)

One of the most important feature of the LLM, since from beginnig, was able to define a tool execution planning to resolve the problem. The ReAct agent was introdced to add an execution layer and an evaluation loop to execute tools and involve again LLM,  based on tool execution result in order to dynamically revaluate the execution planning itself.
If we consider that a tool can involve a ReACT agent in turn we gain the sub agent involvment that colud involve other sub agents in turn in a iterative way

### Minimize agent context (less token)

Declaring a sub agent as a tool, when its parent agent plan to call it, it provides the information requested by sub agent extracting them from the current context automatically reducing them for the specific call. This guarantee that the sub agent receives only the required information that become the user input for it.
Moreover for each sub agents we can declare the `allowed-tools` that further leaning the context making the agent more efficient and effective
--&amp;gt;
&lt;h3 id=&quot;why-reusing-tool-semantics-is-powerful&quot;&gt;Why Reusing Tool Semantics Is Powerful&lt;/h3&gt;
&lt;p&gt;One of the most valuable properties of LLM-based agents is their ability to build and revise a tool execution plan while reasoning over intermediate results. ReAct made this practical by combining thought, action, and observation in a loop.&lt;/p&gt;
&lt;p&gt;When a tool call can transparently invoke another agent, we preserve that planning model instead of replacing it.&lt;/p&gt;
&lt;p&gt;The parent agent can:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;decide that a specialized capability is needed&lt;/li&gt;
&lt;li&gt;provide only the task-relevant context&lt;/li&gt;
&lt;li&gt;wait for a result exactly as it would for a standard tool&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The child agent can:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;run its own reasoning loop&lt;/li&gt;
&lt;li&gt;invoke its own allowed tools&lt;/li&gt;
&lt;li&gt;return a compact outcome to the parent&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This gives hierarchical coordination without exposing the full inner workflow to the parent prompt.&lt;/p&gt;
&lt;h3 id=&quot;minimizing-context-and-token-usage&quot;&gt;Minimizing Context and Token Usage&lt;/h3&gt;
&lt;p&gt;This pattern also improves context discipline.&lt;/p&gt;
&lt;p&gt;Without sub-agents, a single generalist agent often needs:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;every tool definition&lt;/li&gt;
&lt;li&gt;all operating instructions for every domain&lt;/li&gt;
&lt;li&gt;every procedural rule in one large system prompt&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That quickly becomes expensive and unreliable.&lt;/p&gt;
&lt;p&gt;With skill-based sub-agents:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the parent only sees high-level capabilities&lt;/li&gt;
&lt;li&gt;each child sees only the instructions relevant to its task&lt;/li&gt;
&lt;li&gt;each child receives only the tools it is allowed to use&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This narrower context has practical benefits:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;fewer tokens sent to the model&lt;/li&gt;
&lt;li&gt;lower prompt interference between unrelated domains&lt;/li&gt;
&lt;li&gt;clearer tool choice&lt;/li&gt;
&lt;li&gt;better modularity as the system grows&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;&lt;p&gt;The &lt;code&gt;allowed-tools&lt;/code&gt; skill property plays an important role here anabling tool filtering. It turns tool access into an explicit design decision instead of an accidental side effect of what happened to be registered globally.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;reference-implementation&quot;&gt;Reference implementation&lt;/h3&gt;
&lt;p&gt;I've started implementation in the new [LangGraph4j] release stream &lt;code&gt;1.9&lt;/code&gt; currently in active development. The implementation is centered on [`SkilledReactSubAgent`], which turns a markdown skill into both:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a compiled LangGraph4j sub-graph&lt;/li&gt;
&lt;li&gt;a Spring AI tool (i.e. &lt;code&gt;ToolCallback&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That means the same component can participate in the graph as an agent and present itself to the parent LLM as a callable tool.&lt;/p&gt;
&lt;p&gt;In particular the flow is:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Parse the &lt;code&gt;SKILL.md&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Extract &lt;code&gt;name&lt;/code&gt; and &lt;code&gt;description&lt;/code&gt; from front matter&lt;/li&gt;
&lt;li&gt;Optionally filter tools using &lt;code&gt;allowed-tools&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Use the markdown body as the sub-agent instructions&lt;/li&gt;
&lt;li&gt;Compile the agent&lt;/li&gt;
&lt;li&gt;Wrap it into a function tool whose input is a single &lt;code&gt;context&lt;/code&gt; field&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;&lt;p&gt;The important design choice is that the &lt;strong&gt;tool definition is derived from the skill metadata&lt;/strong&gt;, while the &lt;strong&gt;agent behavior is derived from the skill content&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id=&quot;skill-format&quot;&gt;Skill Format&lt;/h4&gt;
&lt;p&gt;The supported skill format is &lt;/p&gt;
&lt;h3 id=&quot;section&quot;&gt;&lt;/h3&gt;
&lt;p&gt;name: &lt;br&gt;
description: |&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    &amp;lt;multi line description&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;allowed-tools-&quot;&gt;allowed-tools: &lt;/h3&gt;




### From Skill to Tool-Backed Agent

The conversion happens in `SkilledReactSubAgent.Builder#build(...)`. This is the crucial bridge where:

* the skill metadata defines the external API
* the skill body defines the internal operating behavior
* the allowed tools define the sub-agent execution boundary

The resulting agent is then wrapped as a Spring AI function tool.

&amp;gt; From the parent agent point of view, this is just another tool. Internally, it is a complete ReAct-capable agent workflow.


## Examples

### Purchase Assistant Agent

This is an agent that allow to select a produdct from a reference marketplace and purchase it. Below the simplified component diagram.

![](https://bsorrentino.github.io/bsorrentino/assets/langgraph-java/sub-agents/PurchaseAssistantAgent1.png)

### Marketplace Skill

&lt;pre&gt;
---
name: agent-marketplace
description: |
    marketplace agent, ask for information about products. This is agent that provides the information on the product marketplace.
    required data are:
    * product name
allowed-tools: searchByProduct
---

## Retrieve product information
We need all information about about the product of interest in particular is mandatory to have
* product name

with such information we have to invoke tool `searchByProduct` and return the result
&lt;/pre&gt;

### Payment Skill

&lt;pre&gt;
---
name: agent-payment
description: |
    payment agent, this is the agent that provides payment service.
    required data are:
    * Product name
    * Product price
    * Product currency
    * the International Bank Account Number (IBAN) - optional

allowed-tools:
    - submit-payment
    - retrieve-iban
---

## submit a payment for purchasing a specific product
We need all information about purchasing to allow the payment

* Product name
* Product price
* Product price currency
* International Bank Account Number (IBAN)

With such information you must invoke tool `submit-payment` and return the result of the payment transaction plus the IBAN.
If IBAN is not provided invoke tool `retrieve-iban` to retrieve the required IBAN.

&lt;/pre&gt;

Observing the skills above they define three different concerns:

* `name`: the tool name exposed to the parent agent
* `description`: the tool description shown to the model
* `allowed-tools`: the only tools available inside the sub-agent

Everything after the front matter becomes the sub-agent system prompt.

&amp;gt; Again, this is a strong separation of concerns. The parent agent sees a concise tool contract, while the child agent receives richer operating instructions.

## Finally the code

Below a representative code snippet that show how wires two skill-based sub-agents into a parent agent:

```java
var subAgentMarketplace = SkilledReactSubAgent.builder()
        .chatModel(chatModel)
        .tools(Marketplace.tools())
        .build(compileConfig,
                SkillSource.of(Paths.get(&quot;skills/agent-marketplace/&quot;)));

var subAgentPayment = SkilledReactSubAgent.builder()
        .chatModel(chatModel)
        .tools(Payment.tools())
        .build(compileConfig,
                SkillSource.of(Paths.get(&quot;skills/agent-payment/&quot;)));

var purchaseAssistantAgent = AgentExecutorEx.builder()
        .chatModel(chatModel)
        .tool(subAgentMarketplace)
        .tool(subAgentPayment)
        .build(compileConfig);

var input = &quot;&quot;&quot;
        search for product 'X'.
        If found proceed to payment with IBAN US82WEST1234567890123456 
        to purchase it
        &quot;&quot;&quot;;

var result = purchaseAssistantAgent.invoke( 
        GraphInput.args( Map.of(&quot;messages&quot;, new UserMessage(input) )), runnableConfig); 

```

This composition is interesting because the purchase agent (the parent) does not need detailed knowledge of:

* how product lookup works
* how payment submission works
* when IBAN retrieval is required

Those rules are delegated to the sub agents themselves.

That is a simple but good example of behavioral encapsulation. The parent agent only decides to involve marketplace agent to retrieve product and after that to involve payment agent to purchase.

This is a very basic but representative example, it is useful because it shows the intended layering:

* the user gives a single business request
* the parent agent decomposes the work
* specialized sub-agents execute their own logic
* the final answer comes back through the normal tool-response path

&amp;gt; Interesting note that each sub agent, potentially, can relies on different LLM chat model so we can further optimize overall agent execution process

Below a more detailed componet diagram generated by [LangGraph4j] itself.

![](https://bsorrentino.github.io/bsorrentino/assets/langgraph-java/sub-agents/PurchaseAssistantAgent.png)

From this diagram it is quite clear that the sub agents are nothing but sub-graphs

## Design Advantages

This approach has several practical advantages.

1. **Reuse Existing Agent Patterns**
   
   There is no need to invent a separate orchestration protocol for sub-agents. A sub-agent is exposed as a tool, so the parent agent can use standard ReAct behavior without extra prompting complexity.

2. **Better Modularity**

   Each skill owns:
   * its tool-facing identity
   * its internal instructions
   * its allowed tool boundary

   That makes the system easier to evolve than a single monolithic agent prompt.

3. **Smaller and Cleaner Context**

   The child agent receives only what it needs. This reduces token usage and lowers the risk that unrelated instructions distort tool choice or task execution.

4. **Safer Capability Scoping**

   By filtering tools from `allowed-tools`, the framework prevents a child agent from accidentally seeing tools outside its intended domain.

5. **Better Reusability**

   A skill can be packaged, versioned, and reused across different parent agents. The same marketplace or payment skill can participate in many workflows as long as the tool contract remains stable.

## Tradeoffs and Implementation Considerations

This pattern is strong, but it is not free of tradeoffs.

1. **The Parent Must Summarize Well**

   Because the child receives a single `context` field, the parent agent needs to pass the relevant facts clearly. If critical information is omitted, the child may need extra recovery steps or may fail to act.

2. **Tool Descriptions Matter More**

   Since the parent sees the child as a tool, the `name` and `description` fields in the skill front matter become part of the routing surface for the LLM. Weak metadata will reduce invocation quality.

3. **Prompt Boundaries Need Discipline**

   The skill body is effectively the child system prompt. If it is vague, contradictory, or overloaded, the benefits of modularity disappear. Skills need to stay focused.

4. **Observability Is Important**

   Nested agents can make execution harder to inspect if tracing is weak. LangGraph4j's graph structure and step streaming help here, but production systems should still invest in logging and traceability.

## Why LangGraph4j Fits This Well

[LangGraph4j] is a good fit for this pattern because it already models agent workflows as explicit graphs and integrates naturally with Spring AI tool abstractions.

That matters for two reasons:

* the sub-agent is not just prompt composition; it is a compiled graph with state and execution hooks
* the parent integration remains ergonomic because Spring AI already treats capabilities as tools
* [LangGraph4j] support interruptions in subgraph enabling **Human In The Loop** also when involving sub agents

So the implementation is not forcing two incompatible ideas together. It is aligning:

* LangGraph4j graph execution
* Spring AI tool calling
* markdown-defined skills

into a single reusable agent-building pattern.

## Conclusion

Turning a skill into a sub-agent is a small change with a large impact.

It lets us:

* preserve the strengths of the ReAct loop
* delegate specialized behavior cleanly
* reduce context size
* constrain tool access per domain
* build multi-agent systems out of reusable skill modules

The idea is implemented in a compact and pragmatic way: a `SKILL.md` file becomes the source of both the sub-agent prompt and the tool contract exposed to the parent agent.

For Java teams building agentic applications with Spring AI and LangGraph4j, this is a practical pattern for moving from a single large agent toward a modular multi-agent design without abandoning the tool semantics that current LLMs already handle well.

The implementation is in progress on release `1.9-SNAPSHOT` in develop branch of [LangGraph4j] repository but the first result are very promising.

Hope this could help and encourage usage of [LangGraph4j] for your next Agentic Workflow. Checkout project, try it and let me know your feedback and... happy AI coding! 


[LangGraph4j]: https://github.com/langgraph4j/langgraph4j
[Spring AI]: https://spring.io/projects/spring-ai

</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/16271/skill-based-sub-agents-with-langgraph4j-and-spring-ai</guid>
<pubDate>Thu, 30 Apr 2026 21:42:33 +0000</pubDate>
</item>
<item>
<title>Defending the JVM in the Agentic Era</title>
<link>https://coderlegion.com/16214/defending-the-jvm-in-the-agentic-era</link>
<description>&lt;p&gt;The current AI discourse is obsessed with 'Software Hope' — the belief that probabilistic wrappers can accurately govern the world's most critical enterprise logic.&lt;/p&gt;
&lt;p&gt;They cannot.&lt;/p&gt;
&lt;p&gt;For those of us in the engine room, the industrial floor still runs on the JVM. The requirement for deterministic, high-throughput execution has not changed. If anything, the arrival of autonomous agents has made the 'Iron Truth' of Java more critical than ever.&lt;/p&gt;
&lt;p&gt;Transformation is not about a code rewrite. It is about a hard handshake between legacy logic and agentic intent. To survive the Agentic Era, enterprise architecture must define exactly how the hardware fuses with the JVM. We call this the welding seam.&lt;/p&gt;
&lt;h3 id=&quot;the-identity-fallacy&quot;&gt;The Identity Fallacy&lt;/h3&gt;
&lt;p&gt;We are currently repeating the most expensive mistake in systems architecture by governing autonomous agents using security models designed for human browsers. We assume a valid IAM role or JWT equals safe intent.&lt;/p&gt;
&lt;p&gt;It does not. Identity is an entry requirement; authority is a physical constraint. A hallucinating model with valid production credentials is just an automated insider threat.&lt;/p&gt;
&lt;h3 id=&quot;the-sovereign-witness&quot;&gt;The Sovereign Witness&lt;/h3&gt;
&lt;p&gt;Governance should not live in the Java code itself, nor should it rely on soft API gateways. It must live upstream, inside a 'Sovereign Vault' — a Trusted Execution Environment (TEE) like Intel SGX or TDX.&lt;/p&gt;
&lt;p&gt;We are moving toward the Agentic Strangler pattern. We wrap the Java monolith in a hardware-enforced façade. Here is exactly how the data crosses the boundary using our architectural primitives.&lt;/p&gt;
&lt;h3 id=&quot;the-execution-primitives&quot;&gt;The Execution Primitives&lt;/h3&gt;
&lt;p&gt;Instead of relying on middleware to guess intent, we manage the physical state of the AI model before granting the right to act.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Sankalpa&lt;/strong&gt; — The cryptographic vow binding the agent's identity, intent, and time.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Sakshi&lt;/strong&gt; — The decoupled Hardware Witness operating securely inside the TEE airlock. It mathematically evaluates the Sankalpa against the Sovereign Mandate and the real-time empirical state of the model.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Pramana&lt;/strong&gt; — The Admissible Proof generated by the witness, cryptographically sealing the real-time empirical state.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Mudra&lt;/strong&gt; — The single-use execution seal, typically manifested as an ephemeral mTLS certificate.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;deterministic-execution&quot;&gt;Deterministic Execution&lt;/h3&gt;
&lt;p&gt;The Java monolith is configured to physically reject any API call that lacks the Mudra. If the hardware witness detects constraint decay or unauthorized intent, the TEE physically starves the payload. The instruction never reaches the JVM.&lt;/p&gt;
&lt;p&gt;When you align the reasoning truth with a hardware witness, you move past the failure points of standard RBAC. You get an integration layer that does not just connect — it physically enforces.&lt;/p&gt;
&lt;p&gt;Java is not the relic. It is the substrate.&lt;/p&gt;
&lt;p&gt;Stop auditing the vibes of the model. Secure the silicon that runs the core.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/16214/defending-the-jvm-in-the-agentic-era</guid>
<pubDate>Wed, 29 Apr 2026 21:03:53 +0000</pubDate>
</item>
<item>
<title>Stop Rewriting CI/CD: Reusable GitHub Actions for Maven Projects</title>
<link>https://coderlegion.com/16030/stop-rewriting-ci-cd-reusable-github-actions-for-maven-projects</link>
<description>&lt;h2 id=&quot;stop-rewriting-ci-cd-pipelines-for-every-maven-project&quot;&gt;Stop Rewriting CI/CD Pipelines for Every Maven Project&lt;/h2&gt;
&lt;p&gt;If you’ve worked on multiple Java projects, you’ve probably run into this:&lt;/p&gt;
&lt;p&gt;Every repository has its own version of a CI/CD pipeline.&lt;/p&gt;
&lt;p&gt;Slightly different YAML.&lt;br&gt;
Slightly different setup steps.&lt;br&gt;
Slightly different ways of doing the same thing.&lt;/p&gt;
&lt;p&gt;And somehow… none of them are reusable.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-real-problem&quot;&gt;The Real Problem&lt;/h3&gt;
&lt;p&gt;It’s not that CI/CD is hard.&lt;/p&gt;
&lt;p&gt;It’s that it’s repetitive.&lt;/p&gt;
&lt;p&gt;You end up doing the same things over and over:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Set up Java  &lt;/li&gt;
&lt;li&gt;Cache Maven dependencies  &lt;/li&gt;
&lt;li&gt;Run tests  &lt;/li&gt;
&lt;li&gt;Build artifacts  &lt;/li&gt;
&lt;li&gt;Handle versioning  &lt;/li&gt;
&lt;li&gt;Publish or release  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Then copy that workflow into another repo…&lt;br&gt;
…and tweak it just enough to break consistency.&lt;/p&gt;
&lt;p&gt;This is exactly the kind of problem automation should solve — yet we keep duplicating it.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;a-different-approach&quot;&gt;A Different Approach&lt;/h3&gt;
&lt;p&gt;Instead of treating each pipeline as a one-off YAML file, I started thinking:&lt;/p&gt;
&lt;p&gt;What if CI/CD steps were modular and reusable — just like code?&lt;/p&gt;
&lt;p&gt;That’s where NERV-Actions came from.&lt;/p&gt;
&lt;p&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/your-repo-link&quot;&gt;https://github.com/your-repo-link&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-nerv-actions-is&quot;&gt;What NERV-Actions Is&lt;/h3&gt;
&lt;p&gt;NERV-Actions is a collection of reusable GitHub Actions designed for Maven-based projects.&lt;/p&gt;
&lt;p&gt;The idea is simple:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Break CI/CD into small, composable actions  &lt;/li&gt;
&lt;li&gt;Reuse them across repositories  &lt;/li&gt;
&lt;li&gt;Keep pipelines consistent without rewriting everything  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Instead of this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# repeated in every repo
- uses: actions/setup-java@v4
- run: mvn clean install
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You plug in reusable building blocks that already handle those concerns.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;why-this-matters&quot;&gt;Why This Matters&lt;/h3&gt;
&lt;p&gt;Reusable workflows aren’t just about convenience — they solve real scaling problems.&lt;/p&gt;
&lt;p&gt;When you define things once and reuse them:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You reduce duplication  &lt;/li&gt;
&lt;li&gt;You avoid inconsistencies across repos  &lt;/li&gt;
&lt;li&gt;You make updates easier (change once, apply everywhere)  &lt;/li&gt;
&lt;li&gt;You spend less time debugging YAML  &lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;design-philosophy&quot;&gt;Design Philosophy&lt;/h3&gt;
&lt;p&gt;I didn’t try to build a one-size-fits-all pipeline.&lt;/p&gt;
&lt;p&gt;Instead, NERV-Actions focuses on:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Composable units → mix only what you need  &lt;/li&gt;
&lt;li&gt;Sensible defaults → minimal setup required  &lt;/li&gt;
&lt;li&gt;Consistency → same patterns across projects  &lt;/li&gt;
&lt;li&gt;Extensibility → override when necessary  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It’s closer to building blocks than a framework.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;example-use-case&quot;&gt;Example Use Case&lt;/h3&gt;
&lt;p&gt;For a typical Maven project, your pipeline usually boils down to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Run tests  &lt;/li&gt;
&lt;li&gt;Build artifacts  &lt;/li&gt;
&lt;li&gt;Publish packages  &lt;/li&gt;
&lt;li&gt;Create releases  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With reusable actions, you don’t redefine these steps every time — you just assemble them.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;why-not-just-copy-paste-&quot;&gt;Why Not Just Copy-Paste?&lt;/h3&gt;
&lt;p&gt;Because copy-paste doesn’t scale.&lt;/p&gt;
&lt;p&gt;It works for 2–3 repos.&lt;br&gt;
It breaks down at 10+.&lt;/p&gt;
&lt;p&gt;Suddenly:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Fixes don’t propagate  &lt;/li&gt;
&lt;li&gt;Pipelines drift  &lt;/li&gt;
&lt;li&gt;Debugging becomes inconsistent  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Reusable actions solve that by centralizing the logic.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;still-evolving&quot;&gt;Still Evolving&lt;/h3&gt;
&lt;p&gt;This isn’t meant to be a perfect CI/CD solution.&lt;/p&gt;
&lt;p&gt;It’s a practical attempt to reduce the friction I kept running into across projects.&lt;/p&gt;
&lt;p&gt;If you’re dealing with the same repetition in GitHub Actions, this might be useful:&lt;/p&gt;
&lt;p&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/czetsuyatech/nerv-actions&quot;&gt;https://github.com/czetsuyatech/nerv-actions&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;curious-about-your-setup&quot;&gt;Curious About Your Setup&lt;/h3&gt;
&lt;p&gt;Are you:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;copying workflows between repos?  &lt;/li&gt;
&lt;li&gt;using reusable workflows heavily?  &lt;/li&gt;
&lt;li&gt;or building your own internal action libraries?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Would be interesting to hear how others are handling this.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/16030/stop-rewriting-ci-cd-reusable-github-actions-for-maven-projects</guid>
<pubDate>Tue, 28 Apr 2026 01:40:16 +0000</pubDate>
</item>
<item>
<title>nerv-audit</title>
<link>https://coderlegion.com/15089/nerv-audit</link>
<description>&lt;h3 id=&quot;we-needed-an-audit-trail-envers-couldn-t-handle-so-i-built-one&quot;&gt;We needed an audit trail Envers couldn’t handle — so I built one&lt;/h3&gt;
&lt;blockquote&gt;&lt;p&gt;“Who changed this record?”&lt;br&gt;
“What was the previous value?”&lt;br&gt;
“When did it happen?”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Simple questions… until you actually need answers in production.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;the-problem-we-kept-running-into&quot;&gt;The problem we kept running into&lt;/h4&gt;
&lt;p&gt;In one of our systems, audit logs weren’t just “nice to have” — they were critical.&lt;/p&gt;
&lt;p&gt;We needed:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Field-level change tracking&lt;/li&gt;
&lt;li&gt;Historical queries (point-in-time views)&lt;/li&gt;
&lt;li&gt;Reliable audit trails across services&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So naturally, we tried &lt;strong&gt;Hibernate Envers&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;At first, it worked.&lt;/p&gt;
&lt;p&gt;Until it didn’t.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;where-things-started-breaking&quot;&gt;Where things started breaking&lt;/h4&gt;
&lt;p&gt;As the system grew, we hit real limitations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Querying audit history became painful&lt;/li&gt;
&lt;li&gt;Custom audit logic was hard to extend&lt;/li&gt;
&lt;li&gt;Cross-service audit consistency was messy&lt;/li&gt;
&lt;li&gt;Performance started becoming a concern&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We weren’t just storing audit logs anymore —&lt;br&gt;
we needed to &lt;strong&gt;use them as a first-class feature&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;That’s where Envers started to feel like a constraint.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;the-turning-point&quot;&gt;The turning point&lt;/h4&gt;
&lt;p&gt;At some point, we had two options:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Keep patching around the limitations&lt;/li&gt;
&lt;li&gt;Build something that actually fits our needs&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;We chose the second.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;what-we-built&quot;&gt;What we built&lt;/h4&gt;
&lt;p&gt;We created &lt;strong&gt;nerv-audit&lt;/strong&gt; — a modular audit framework designed for real-world backend systems.&lt;/p&gt;
&lt;p&gt;Instead of treating auditing as a side-effect, we designed it as a &lt;strong&gt;core part of the architecture&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Key ideas:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Structured audit work units (not just snapshots)&lt;/li&gt;
&lt;li&gt;Flexible query model for history retrieval&lt;/li&gt;
&lt;li&gt;Clean separation between capture, storage, and querying&lt;/li&gt;
&lt;li&gt;Extensible by design&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h4 id=&quot;a-quick-example&quot;&gt;A quick example&lt;/h4&gt;
&lt;p&gt;Instead of digging through raw audit tables, you can query changes like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;AuditQuery query = AuditQuery.builder()
    .entity(&quot;User&quot;)
    .field(&quot;email&quot;)
    .changed()
    .build();

List&amp;lt;AuditResult&amp;gt; results = auditService.execute(query);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now you’re not just storing history —&lt;br&gt;
you’re actually &lt;strong&gt;asking questions from it&lt;/strong&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;what-i-learned-building-this&quot;&gt;What I learned building this&lt;/h4&gt;
&lt;p&gt;A few things became very clear:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Audit logs become &lt;strong&gt;product features&lt;/strong&gt; faster than expected&lt;/li&gt;
&lt;li&gt;If querying is painful, the audit system is incomplete&lt;/li&gt;
&lt;li&gt;“Just use Envers” works… until scale and complexity kick in&lt;/li&gt;
&lt;li&gt;Designing audit as a &lt;strong&gt;domain&lt;/strong&gt;, not a utility, changes everything&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h4 id=&quot;-about-the-project&quot;&gt;⚠️ About the project&lt;/h4&gt;
&lt;p&gt;The project is structured in modules:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;nerv-audit-core&lt;/code&gt; → full engine (commercial)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;nerv-audit-lite&lt;/code&gt; / examples → publicly available&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I decided to keep the core commercial, but still share:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Public modules&lt;/li&gt;
&lt;li&gt;Examples&lt;/li&gt;
&lt;li&gt;Integration ideas&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h4 id=&quot;explore-it-here&quot;&gt;Explore it here&lt;/h4&gt;
&lt;p&gt;If you’re dealing with similar problems, you might find this useful:&lt;/p&gt;
&lt;p&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/czetsuyatech/nerv-audit&quot;&gt;https://github.com/czetsuyatech/nerv-audit&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;final-thought&quot;&gt;Final thought&lt;/h4&gt;
&lt;p&gt;Most systems don’t think about audit logs until something breaks.&lt;/p&gt;
&lt;p&gt;By then, it’s already too late.&lt;/p&gt;
&lt;p&gt;If your system will ever need to answer:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;“What changed, and why?”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;…it’s worth designing for it early.&lt;/p&gt;
</description>
<category>Launches</category>
<guid isPermaLink="true">https://coderlegion.com/15089/nerv-audit</guid>
<pubDate>Sat, 18 Apr 2026 04:52:03 +0000</pubDate>
</item>
<item>
<title>Stop Rebuilding the Same Spring Boot Setup Every Time</title>
<link>https://coderlegion.com/14723/stop-rebuilding-the-same-spring-boot-setup-every-time</link>
<description>&lt;p&gt;If you’ve built more than one Java backend, you’ve probably done this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;setup auth again&lt;/li&gt;
&lt;li&gt;configure roles and permissions&lt;/li&gt;
&lt;li&gt;handle multi-tenancy&lt;/li&gt;
&lt;li&gt;wire up basic APIs&lt;/li&gt;
&lt;li&gt;fix the same production issues later&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And suddenly a week is gone.&lt;/p&gt;
&lt;p&gt;I’m currently working on &lt;strong&gt;BuildBaseKit&lt;/strong&gt;, focused on solving exactly this.&lt;/p&gt;
&lt;p&gt;The idea is simple:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;give developers a production-ready Spring Boot starting point&lt;/li&gt;
&lt;li&gt;not just demos, but something you can actually build on&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Right now I’m validating it with real use cases like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;file servers&lt;/li&gt;
&lt;li&gt;Discord bots&lt;/li&gt;
&lt;li&gt;internal tools&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Curious to hear from others here:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What’s the one thing you always end up rebuilding in every backend?&lt;/strong&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/14723/stop-rebuilding-the-same-spring-boot-setup-every-time</guid>
<pubDate>Tue, 14 Apr 2026 04:40:51 +0000</pubDate>
</item>
</channel>
</rss>