en DZone Java Zone https://dzone.com/java Recent posts in Java on DZone.com Zero-Downtime Deployments for Java Apps on Kubernetes https://dzone.com/articles/zero-downtime-java-kubernetes-deployments <img src="https://dz2cdn1.dzone.com/storage/article-thumb/19006685-thumb.jpg" /><h1>Zero-Downtime Deployments for Java Apps on Kubernetes</h1><div> <div><p>This article provides a comprehensive guide to achieving zero-downtime deployments for Java-based applications on Kubernetes. </p> <p>We cover deployment strategies, Kubernetes primitives, Java-specific considerations, session state handling, database migrations, traffic shifting techniques, CI/CD pipelines, GitHub Actions, Jenkins with automated rollbacks, observability (Prometheus, Grafana, Jaeger), Helm/ArgoCD examples, testing strategies (canary analysis, chaos, smoke tests), and troubleshooting.</p> <h2>Deployment Strategies</h2> <p>Kubernetes offers several strategies for deploying new versions without downtime:</p> <h3>Rolling Update</h3> <p>Incrementally replace old pods with new ones, maintaining availability. Kubernetes Deployment object uses rolling updates by default. You can control maxUnavailable and maxSurge to tune the rollout.</p> <h3>Blue-Green Deployment</h3> <p>Run two separate environments: Blue = current, green = new. Only one serves live traffic at a time. Once the Green version is verified, switch the Service or Ingress to point at Green, then scale down Blue. This allows instant rollback by redirecting traffic back to Blue. Argo Rollouts defines a blue/green strategy with an active and preview Service. Traffic flows only to the active version until promotion.</p> <h3>Canary Deployment</h3> <p>Gradually shift a small percentage of traffic to the new version. Start with a few pods of v2, monitor, then incrementally increase. Tools like Istio or Argo Rollouts can control traffic weights. For instance, sending 10% of traffic to v2 can be done by running 9 v1 pods and 1 v2 pod (10%). Argo defines a canary rollout with setWeight steps and pauses for analysis.</p> <h3>Shadow/Mirroring</h3> <p>The new version receives a copy of live requests for testing under real load, but its responses are not returned to users. This is low risk but does not assist in rollback decisions since users don’t see the new behavior.</p> <h2>Kubernetes Primitives for Zero Downtime</h2> <h3>Deployment</h3> <p>A Deployment naturally performs rolling updates. By default, it creates a new ReplicaSet and scales it up while scaling down the old one controlled by maxUnavailable/maxSurge. This ensures some pods always serve traffic. To use blue/green, you would deploy two separate Deployments (e.g., app-blue, app-green) and switch Services.</p> <h3>Service and Ingress</h3> <p>A Service fronts pods. For blue/green, you can point a single Service at either the blue or green pods. Ingress can also switch between backend services. E.g., label selectors can be adjusted to redirect traffic from version blue to version green pods.</p> <h3>PodDisruptionBudget</h3> <p>Ensures a minimum number of pods stay running during voluntary disruptions. For instance, setting minAvailable 1 ensures at least one pod remains during a rolling update. To avoid complete downtime during maintenance.</p> <h3>Horizontal Pod Autoscaler (HPA)</h3> <p>Scales pods based on CPU/memory or custom metrics. It automatically updates a workload to match demand. An HPA can be attached to the Deployment so that if traffic spikes during a rollout, new pods will be created to handle the load. Example:</p> <div contenteditable="false"> <div contenteditable="false"> <div data-code="apiVersion autoscaling/v2&#xA;kind: HorizontalPodAutoscaler&#xA;metadata:&#xA; name: myapp-hpa&#xA;spec:&#xA; scaleTargetRef:&#xA; apiVersion: apps/v1&#xA; kind: Deployment&#xA; name: myapp&#xA; minReplicas: 2&#xA; maxReplicas: 10&#xA; metrics:&#xA; - type: Resource&#xA; resource:&#xA; name: cpu&#xA; target:&#xA; type: Utilization&#xA; averageUtilization: 50" data-lang="text/x-yaml"> <pre><code lang="text/x-yaml">apiVersion autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: myapp-hpa spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: myapp minReplicas: 2 maxReplicas: 10 metrics: - type: Resource resource: name: cpu target: type: Utilization          averageUtilization: 50</code></pre> </div> </div> </div> <h3>Liveness and Readiness Probes</h3> <p>Critical for zero downtime. A liveness probe checks if the app is alive; if it fails, K8 restarts the pod. A readiness probe tells if the app is ready to serve traffic. During startup or shutdown, the readiness probe should fail, causing the pod to be removed from the service load balancer. Spring Boot Actuator provides /actuator/health for this. In K8S YAML:</p> <div contenteditable="false"> <div contenteditable="false"> <div data-code="livenessProbe:&#xA; httpGet:&#xA; path: /actuator/health/liveness&#xA; port: 8080&#xA; initialDelaySeconds: 15&#xA; periodSeconds: 10&#xA;readinessProbe:&#xA; httpGet:&#xA; path: /actuator/health/readiness&#xA; port: 8080&#xA; initialDelaySeconds: 5&#xA; periodSeconds: 5" data-lang="text/x-yaml"> <pre><code lang="text/x-yaml">livenessProbe: httpGet: path: /actuator/health/liveness port: 8080 initialDelaySeconds: 15 periodSeconds: 10 readinessProbe: httpGet: path: /actuator/health/readiness port: 8080 initialDelaySeconds: 5   periodSeconds: 5</code></pre> </div> </div> </div> <p>Spring Boot exposes health/liveness and health/readiness groups by default. Quarkus and Micronaut have similar health endpoints.</p> <p>Spring Boot supports graceful shutdown by setting server.shutdown is equals to graceful and tuning spring.lifecycle.timeout-per-shutdown-phase. This causes the embedded server, either Tomcat/Jetty/Undertow, to stop accepting traffic and wait up to the timeout for active requests.</p> <div contenteditable="false"> <div contenteditable="false"> <div data-code="@Component&#xA;public class ShutdownListener implements SmartLifecycle {&#xA; private boolean running = true;&#xA; @Override public void stop() {&#xA; running = false;&#xA; }&#xA; @Override public boolean isRunning() {&#xA; return running;&#xA; }&#xA;}" data-lang="text/x-java"> <pre><code lang="text/x-java">@Component public class ShutdownListener implements SmartLifecycle { private boolean running = true; @Override public void stop() { running = false; } @Override public boolean isRunning() { return running; } }</code></pre> </div> </div> </div> <p>Quarkus provides graceful shutdown configuration. By setting quarkus.shutdown.timeout=10s, Quarkus will wait up to 10 seconds for current requests to finish before exiting. You can annotate a bean method with @Shutdown to run cleanup code.</p> <p>Micronaut has @EventListener for ShutdownEvent:</p> <div contenteditable="false"> <div contenteditable="false"> <div data-code="@Singleton&#xA;public class ShutdownBean {&#xA; @EventListener&#xA; void onShutdown(ShutdownEvent event) {&#xA; }&#xA;}" data-lang="text/x-java"> <pre><code lang="text/x-java">@Singleton public class ShutdownBean { @EventListener void onShutdown(ShutdownEvent event) { } }</code></pre> </div> </div> </div> <h3>Kubernetes Hooks</h3> <p>You can use a preStop hook in the Deployment spec to run a script before SIGTERM.</p> <div contenteditable="false"> <div contenteditable="false"> <div data-code="lifecycle:&#xA; preStop:&#xA; exec:&#xA; command: [&quot;/bin/sh&quot;,&quot;-c&quot;,&quot;sleep 5&quot;]&#xA;terminationGracePeriodSeconds: 30" data-lang="text/x-yaml"> <pre><code lang="text/x-yaml">lifecycle: preStop: exec: command: ["/bin/sh","-c","sleep 5"] terminationGracePeriodSeconds: 30</code></pre> </div> </div> </div> <p>The grace period (default 30s) should be tuned to let the app finish. K8S doc 77†L99-L107 describes the sequence container enters Terminating, runs preStop, sends SIGTERM, waits terminationGracePeriodSeconds, then SIGKILL.</p> <h3>JVM Tuning</h3> <p>Set -XX +ExitOnOutOfMemoryError to avoid hanging. Tune thread pools so they drain quickly. Monitor GC pause times, consider using low-latency GC to minimize pause before shutdown.</p> <h3>Session and State Handling</h3> <p>To maintain zero downtime when pods switch:</p> <ul> <li><strong>Stateless services</strong>: Best practice is to keep services stateless. Store session state or user data in an external store, such as Redis or a database. This way, any pod can handle any request, and pods can be replaced without losing the user session.</li> <li><strong>Sticky sessions</strong>: If an app uses in-memory sessions, you can enforce sticky sessions</li> <li><strong>Service affinity</strong>: Set sessionAffinity: ClientIP on the Service. Kubernetes routes requests from the same client IP to the same pod.</li> <li><strong>Ingress affinity</strong>: Use Ingress annotations to bind a user’s requests to one pod. However, sticky sessions introduce risk and are not suitable for autoscaling.</li> <li><strong>StatefulSets</strong>: For true stateful workloads, use StatefulSet with stable identities. StatefulSets pair pods with PersistentVolumes, which are not zero-downtime by themselves.</li> </ul> <p>GitHub Actions CI/CD Pipeline zero-downtime:</p> <div contenteditable="false"> <div contenteditable="false"> <div data-code="name: Deploy&#xA;&#xA;on:&#xA; push:&#xA; branches: [ main ]&#xA;&#xA;jobs:&#xA; build:&#xA; runs-on: ubuntu-latest&#xA; steps:&#xA; - uses: actions/checkout@v2&#xA; - uses: actions/setup-java@v3&#xA; with: { java-version: '17' }&#xA; - name: Build&#xA; run: mvn clean package -DskipTests&#xA; name: Docker Build &amp; Push&#xA; run: |&#xA; docker build -t ghcr.io/myorg/myapp:${{ github.sha }}&#xA; echo ${{ secrets.GITHUB_TOKEN }} | docker login ghcr.io -u ${{ github.actor }} --password-stdin&#xA; docker push ghcr.io/myorg/myapp:${{ github.sha }}&#xA; - name: Set image tag&#xA; run: echo &quot;::set-output name=image::ghcr.io/myorg/myapp:${{ github.sha }}&#xA;&#xA; deploy:&#xA; needs: build&#xA; runs-on: ubuntu-latest&#xA; steps:&#xA; - uses: actions/checkout@v2&#xA; with: { path: manifests }&#xA; - name: Update K8s deployment&#xA; uses: azure/setup-kubectl@v3&#xA; - name: Deploy to Kubernetes&#xA; run: |&#xA; kubectl set image deployment/myapp-deployment myapp=ghcr.io/myorg/myapp:${{ needs.build.outputs.image }}&#xA; kubectl rollout status deployment myapp-deployment" data-lang="text/x-yaml"> <pre><code lang="text/x-yaml">name: Deploy on: push: branches: [ main ] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: actions/setup-java@v3 with: { java-version: '17' } - name: Build run: mvn clean package -DskipTests name: Docker Build &amp; Push run: | docker build -t ghcr.io/myorg/myapp:${{ github.sha }} echo ${{ secrets.GITHUB_TOKEN }} | docker login ghcr.io -u ${{ github.actor }} --password-stdin docker push ghcr.io/myorg/myapp:${{ github.sha }} - name: Set image tag run: echo "::set-output name=image::ghcr.io/myorg/myapp:${{ github.sha }} deploy: needs: build runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 with: { path: manifests } - name: Update K8s deployment uses: azure/setup-kubectl@v3 - name: Deploy to Kubernetes run: | kubectl set image deployment/myapp-deployment myapp=ghcr.io/myorg/myapp:${{ needs.build.outputs.image }}           kubectl rollout status deployment myapp-deployment</code></pre> </div> </div> </div> <p>This workflow builds the image, pushes it, and updates the deployment. The rollout status command waits for all new pods to become ready. If health checks fail, it will abort without downtime.</p> <h2><strong>Conclusion</strong></h2> <p>Zero-downtime deployment on Kubernetes combines careful architecture and automation, using rolling updates, progressive strategies, ensuring graceful shutdown and health checks in your Java apps, externalizing state, managing database changes, and orchestrating with CI/CD pipelines. Kubernetes primitives like Deployments, Services, Probes, and HPA, along with tools like Istio or Argo Rollouts, provide the building blocks.</p></div> </div><p>Opinions expressed by DZone contributors are their own.</p><br><span style='font: #ff0000'>Generated by <a href='https://github.com/andreskrey/readability.php'>Readability.php</a>.</span> Fri, 29 May 2026 14:00:05 GMT https://dzone.com/articles/3639600 Ramya vani Rayala Pragmatica Aether: Let Java Be Java https://dzone.com/articles/pragmatica-aether-let-java-be-java <img src="https://dz2cdn1.dzone.com/storage/article-thumb/18910174-thumb.jpg" /><h1>Pragmatica Aether: Let Java Be Java</h1><div> <div><h2 data-selectable-paragraph="">The Aberration</h2> <p data-selectable-paragraph="">We build Java applications like Go or Rust programs. Fat JARs. Docker images. Kubernetes deployments. Everyone does it, so it looks normal.</p> <p data-selectable-paragraph="">It contradicts Java’s design DNA.</p> <p data-selectable-paragraph="">Java has always been a language for managed environments. Applets ran inside browsers. Servlets ran inside application servers. EJBs ran inside containers like JBoss and WebLogic. OSGi bundles ran inside runtime containers like Eclipse Equinox. In every generation, the pattern was the same: a managed runtime hosts the application. The application handles business logic. The runtime handles infrastructure.</p> <p data-selectable-paragraph="">The fat-jar era threw that away. We stopped letting Java be Java. We started bundling web servers, serialization frameworks, service discovery clients, configuration management, health checks, metrics libraries, and logging frameworks into every application. Then we wrapped the result in a Docker container and deployed it to an orchestration platform that reimplements — poorly — the infrastructure management that Java runtimes used to provide natively.</p> <p data-selectable-paragraph="">This article introduces <a href="https://pragmaticalabs.io/aether.html" rel="noopener ugc nofollow" target="_blank">Pragmatica Aether</a>: a distributed runtime that returns Java to its natural habitat. The application handles business logic. Runtime handles infrastructure. This isn’t radical — it's returning to what Java was designed for.</p> <h2 data-selectable-paragraph="">The Problem: Infrastructure Wearing a Business Logic Mask</h2> <p data-selectable-paragraph="">Think of what a typical Java microservice carries. A web server (Tomcat, Netty, Undertow). A serialization framework (Jackson, Gson). A dependency injection container (Spring, Guice). A service discovery client (Eureka, Consul). Health check endpoints. Configuration management (Spring Cloud Config, Consul KV). A metrics library (Micrometer, Dropwizard). A logging framework (Logback, Log4j2). Retry logic (Resilience4j). Circuit breakers. HTTP client configuration. The application is wearing a heavy winter coat of infrastructure, armed to the teeth to survive in a hostile environment.</p> <p data-selectable-paragraph="">Now consider the coupling this creates.</p> <p data-selectable-paragraph="">Update the Java version — rebuild and test every service. Change your message broker from RabbitMQ to Kafka — modify, rebuild, and redeploy every application that touches messaging. Add a new observability tool and update dependencies in every microservice. Switch cloud providers — rewrite configuration, SDK calls, and deployment manifests across the entire fleet. Each change ripples through dozens or hundreds of services because infrastructure is entangled with business logic at the dependency level.</p> <p data-selectable-paragraph="">This is the coupling trap. Your application’s <code>pom.xml</code> doesn't distinguish between business dependencies and infrastructure dependencies. They compile together, deploy together, and break together. A security patch in Netty requires a new build of every service that embeds a web server, which is all of them.</p> <p data-selectable-paragraph="">Framework lock-in worsens this. It isn’t a vendor problem — it's an architecture problem. Spring’s dependency injection fights with Kubernetes service mesh for control over service routing and circuit breaking. The framework’s configuration system overlaps with Consul KV and Kubernetes ConfigMaps. Your cloud SDK’s retry logic conflicts with Resilience4j. Every layer claims authority over the same cross-cutting concerns, and the conflicts surface as subtle bugs in production — not during development.</p> <p data-selectable-paragraph="">This is an architecture problem. Architectural problems have architectural solutions.</p> <h3 data-selectable-paragraph="">Aether: The Core Idea</h3> <p data-selectable-paragraph="">What you write: an interface annotated with <code>@Slice</code>, plus business logic implementation.</p> <div contenteditable="false"> <div contenteditable="false"> <div data-code="@Slice&#xA;public interface OrderService {&#xA; Promise&lt;OrderResult> placeOrder(PlaceOrderRequest request);&#xA;&#xA; static OrderService orderService(InventoryService inventory, PricingEngine pricing) {&#xA; return request -> inventory.check(request.items())&#xA; .flatMap(available -> pricing.calculate(available))&#xA; .map(priced -> OrderResult.placed(priced));&#xA; }&#xA;}" data-lang="text/x-java"> <pre><code lang="text/x-java">@Slice public interface OrderService { Promise&lt;OrderResult&gt; placeOrder(PlaceOrderRequest request); static OrderService orderService(InventoryService inventory, PricingEngine pricing) { return request -&gt; inventory.check(request.items()) .flatMap(available -&gt; pricing.calculate(available)) .map(priced -&gt; OrderResult.placed(priced)); } }</code></pre> </div> </div> </div> <p data-selectable-paragraph="">What you don’t write: everything else.</p> <p data-selectable-paragraph="">No HTTP clients — inter-slice calls are direct method invocations via generated proxies. No service discovery — the runtime tracks where every slice instance lives. No retry logic — built-in retry with exponential backoff and node failover. No circuit breakers — the reliability fabric handles failure automatically. No serialization code — request/response types are serialized transparently.</p> <p data-selectable-paragraph="">A method call via an imported interface is the only visible contract. The only hint that the actual call might be remote is a design requirement: slice methods should be idempotent. This isn’t a limitation — it's what enables retry, scaling, and fault tolerance to work transparently. The same request, processed by any available instance, produces the same result. Most read operations are naturally idempotent. For writes, standard patterns like idempotency keys and conditional writes handle it cleanly.</p> <p data-selectable-paragraph="">Everything else is the environment’s job: resource provisioning, scaling, transport, discovery, retries, circuit breakers, configuration, observability, logging, tracing, monitoring, and security. None of these are application concerns, and none should be handled at the business logic level.</p> <p data-selectable-paragraph="">The <a href="https://dev.to/siy/the-six-patterns-that-cover-everything-4d8a" rel="noopener ugc nofollow" target="_blank">JBCT Leaf pattern</a> serves two purposes here: it documents the design (“what we expect from an external implementation”) and encourages exactly one interface per dependency. Different implementations may have different technical properties — performance, latency, memory consumption — but as long as they’re compatible with the interface, business logic works unchanged.</p> <p data-selectable-paragraph="">You write basically pure business logic that scales from your local computer to a global multi-zone distributed deployment, transparently.</p> <h3 data-selectable-paragraph="">Under The Hood: What Makes It Work</h3> <p data-selectable-paragraph="">Five architectural decisions make this possible.</p> <p data-selectable-paragraph=""><strong>Consensus KV Store</strong>. A single source of truth for all configuration, deployment state, and service discovery. Based on the <a href="https://dl.acm.org/doi/10.1145/3477132.3483582" rel="noopener ugc nofollow" target="_blank">Rabia protocol</a>, a crash-fault-tolerant, leaderless consensus algorithm was published in 2021. Any node can propose; agreement is reached through a two-round voting protocol with a fast path when a supermajority agrees in round one. No external config servers. No etcd. No Consul. Configuration changes propagate through consensus and take effect cluster-wide.</p> <p data-selectable-paragraph=""><strong>Built-in Artifact Repository</strong>. DHT-based storage with configurable replication — 3 replicas with quorum reads/writes in production, full replication in development. Artifacts are chunked into 64KB pieces, distributed across nodes via consistent hashing, and integrity-verified with MD5 and SHA-1 on every resolve. No external Nexus or Artifactory is needed. During development, slices resolve from your local Maven repository. In production, the cluster is self-contained.</p> <p data-selectable-paragraph=""><strong>ClassLoader Isolation</strong>. Each slice runs inside its own <code>SliceClassLoader</code> with child-first delegation. Two slices can use different versions of the same library without conflict. Shared dependencies like Pragmatica Lite core are loaded once in a parent classloader. No dependency conflicts. No classpath hell between slices.</p> <p data-selectable-paragraph=""><strong>Declarative Deployment.</strong> Blueprints — TOML files — describe the desired state: which slices, how many instances.</p> <div contenteditable="false"> <div contenteditable="false"> <div data-code="id = &quot;org.example:commerce:1.0.0&quot;&#xA;&#xA;[[slices]]&#xA;artifact = &quot;org.example:inventory-service:1.0.0&quot;&#xA;instances = 3&#xA;&#xA;[[slices]]&#xA;artifact = &quot;org.example:order-processor:1.0.0&quot;&#xA;instances = 5" data-lang="text/x-toml"> <pre><code lang="text/x-toml">id = "org.example:commerce:1.0.0" [[slices]] artifact = "org.example:inventory-service:1.0.0" instances = 3 [[slices]] artifact = "org.example:order-processor:1.0.0" instances = 5</code></pre> </div> </div> </div> <p data-selectable-paragraph="">Apply with one command: <code>aether blueprint apply commerce.toml</code>. The cluster resolves artifacts, loads slices, distributes instances across nodes, registers routes, and starts serving traffic. The cluster converges to the desired state automatically.</p> <p data-selectable-paragraph=""><strong>Infrastructure Independence.</strong> Aether nodes are identical — there's only one deployment artifact to manage at the infrastructure level. Node updates and application deployments run on completely independent schedules. Update Java — roll it out across nodes without touching applications. Update the Aether runtime — same. Update business logic — deploy new slice versions without touching infrastructure. Each independently, each without downtime. This is the fundamental benefit of proper separation: when layers don’t share a deployment unit, they don’t share a deployment schedule.</p> <h3 data-selectable-paragraph="">Fault Tolerance: The 50% Rule</h3> <p data-selectable-paragraph="">The system survives the failure of less than half the nodes. Performance may degrade until replacements spin up, but functionality remains intact — actual redundancy, not just graceful degradation. A 5-node cluster tolerates 2 simultaneous failures. A 7-node cluster tolerates 3. The same request, processed by any available node, produces the same result. Quorum requires <code>(N/2) + 1</code> nodes — as long as a majority is alive, the cluster operates normally.</p> <p data-selectable-paragraph="">Leader failover is consensus-based and near-instant. Node replacement happens automatically — the Cluster Deployment Manager detects the deficit and provisions a replacement through the NodeProvider interface. The entire recovery sequence — from failure detection through state restoration to serving traffic — completes without human intervention.</p> <p data-selectable-paragraph="">When a node fails, the recovery is automatic. Requests to slices on the failed node are immediately retried on healthy nodes. A replacement node is provisioned. It connects to peers, restores consensus state from a cluster snapshot, re-resolves artifacts from the DHT, and reactivates assigned slices. Dead nodes are automatically removed from routing tables. The new leader reconciles the stale state. No human intervention required.</p> <p data-selectable-paragraph="">Rolling updates leverage this fault tolerance for zero-downtime deployments with weighted traffic routing:</p> <div contenteditable="false"> <div contenteditable="false"> <div data-code="aether update start org.example:order-processor 2.0.0 -n 3&#xA;aether update routing &lt;id> -r 1:3 # 25% to v2, 75% to v1&#xA;aether update routing &lt;id> -r 1:1 # 50/50&#xA;aether update complete &lt;id> # 100% to v2, drain v1" data-lang="text/x-sql"> <pre><code lang="text/x-sql">aether update start org.example:order-processor 2.0.0 -n 3 aether update routing &lt;id&gt; -r 1:3 # 25% to v2, 75% to v1 aether update routing &lt;id&gt; -r 1:1 # 50/50 aether update complete &lt;id&gt;          # 100% to v2, drain v1</code></pre> </div> </div> </div> <p data-selectable-paragraph="">Deploy during business hours. Shift traffic gradually — 10% canary, then 25%, 50%, 75%, 100%. Monitor health metrics at each step. If health degrades — error rate exceeds thresholds, latency spikes — instant rollback with one command: <code>aether update rollback &lt;id&gt;</code>. Traffic immediately shifts back to the old version. The 3 AM pager alert becomes an audit log entry.</p> <h2 data-selectable-paragraph="">For Every Project: Legacy, Greenfield, And Everything Between</h2> <h3 data-selectable-paragraph="">Legacy Migration</h3> <p data-selectable-paragraph="">Your legacy Java system doesn’t need a complete rewrite. It needs a path forward.</p> <p data-selectable-paragraph="">Pick a relatively independent part of your system — something hitting limits, something with clear boundaries. Extract an interface. Annotate it with <code>@Slice</code>. Wrap the legacy implementation:</p> <div contenteditable="false"> <div contenteditable="false"> <div data-code="private Promise&lt;Report> generateReport(ReportRequest request) {&#xA; return Promise.lift(() -> legacyReportService.generate(request));&#xA;}" data-lang="text/x-java"> <pre><code lang="text/x-java">private Promise&lt;Report&gt; generateReport(ReportRequest request) { return Promise.lift(() -&gt; legacyReportService.generate(request)); }</code></pre> </div> </div> </div> <p data-selectable-paragraph="">One line to enter the Aether world. <code>Promise.lift()</code> wraps the legacy call, catches exceptions, and returns a proper <code>Result</code> inside a <code>Promise</code>. Your legacy code keeps running. Call sites don't change. You haven't added risk — the initial deployment in Ember runs in the same JVM as your existing application, which means it's no worse than what you have today. You've laid the foundation for removing risk, not adding it. Moving from Ember to a full Aether cluster is a configuration change, not a code change — and that's when the 50% rule starts to apply.</p> <p data-selectable-paragraph="">From there, it’s the strangler fig pattern. Extract a hot path, deploy it as a slice, route traffic, repeat. Each extracted slice can be gradually refactored using the <a data-discover="true" href="https://medium.com/@sergiy-yevtushenko/fail-safe-your-legacy-java-in-one-sprint-41ea40345674" rel="noopener">peeling pattern</a>: first wrap everything in <code>Promise.lift()</code>, then decompose into a Sequencer with each step still wrapped, then peel individual steps into clean JBCT patterns. Tests pass at every step. The <code>lift()</code> calls mark exactly where legacy code remains, making progress visible and remaining work obvious.</p> <p data-selectable-paragraph="">No rewrite is required. No big bang migration. One sprint to the first slice in production. The <a data-discover="true" href="https://medium.com/@sergiy-yevtushenko/fail-safe-your-legacy-java-in-one-sprint-41ea40345674" rel="noopener">migration article</a> covers the full path in detail — from initial wrapping through gradual peeling to clean JBCT code.</p> <h3 data-selectable-paragraph="">Greenfield Development</h3> <p data-selectable-paragraph="">For new projects, slices enable a granularity that’s impossible with traditional microservices.</p> <p data-selectable-paragraph="">Each slice can be as lean as a single method — and that’s the recommended approach. There are no operational or complexity tradeoffs for small slices because Aether handles all the infrastructure overhead. No container to configure, no load balancer to provision, no monitoring to set up per service. You get per-use-case scaling: one slice serving 50 instances during peak load while another idles at minimum. That kind of granularity would be operationally insane with traditional microservices — each needing its own container, load balancer, monitoring, and deployment pipeline. With Aether, it’s the default.</p> <p data-selectable-paragraph=""><a data-discover="true" href="https://medium.com/@sergiy-yevtushenko/the-six-patterns-that-cover-everything-6e1a59c0be42" rel="noopener noreferrer" target="_blank">JBCT patterns</a> — Leaf, Sequencer, Fork-Join, Condition, Iteration, and Aspects — compose naturally within slices. Each slice method is a <a data-discover="true" href="https://medium.com/@sergiy-yevtushenko/the-underlying-process-of-request-processing-4683976e17e2" rel="noopener noreferrer" target="_blank">data transformation pipeline</a>: parse input, gather data, process, respond. The patterns provide consistent structure within slices. <a data-discover="true" href="https://medium.com/@sergiy-yevtushenko/slices-the-right-size-for-microservices-c72d67661a32" rel="noopener noreferrer" target="_blank">Slices provide consistent boundaries</a> between them.</p> <h3 data-selectable-paragraph="">The Spectrum</h3> <p data-selectable-paragraph="">Same slice model, different granularity. A service slice wraps an entire legacy component. A lean slice implements a single method. Both coexist in the same cluster, deployed and scaled independently.</p> <p data-selectable-paragraph=""><a data-discover="true" href="https://medium.com/@sergiy-yevtushenko/slices-the-right-size-for-microservices-c72d67661a32" rel="noopener noreferrer" target="_blank">Slice is the executable unit</a>. It can be big or small as necessary and convenient. The architecture accommodates both monolith migration and greenfield development simultaneously. Your legacy system gains fault tolerance while new features get maximum deployment flexibility.</p> <h3 data-selectable-paragraph="">Scaling: Two Levels, Three Tiers of Intelligence</h3> <h3 data-selectable-paragraph="">Two-Level Horizontal Scaling</h3> <p data-selectable-paragraph="">Aether scales in two dimensions independently:</p> <ul> <li data-selectable-paragraph=""><strong>Slice scaling</strong>: Spin up more instances of a specific slice on existing nodes. Classes are already loaded—scaling takes milliseconds, not seconds.</li> <li data-selectable-paragraph=""><strong>Node scaling</strong>: Add more machines to the cluster. The node connects, restores state, and begins accepting work.</li> </ul> <p data-selectable-paragraph="">Independent controls, combined effect. Each node hosts at most one instance of a given slice, so scaling a slice beyond the current node count requires adding nodes first. Add 2 more nodes to a 3-node cluster, then scale a hot slice to 5 instances—one per node. No coordination between the two dimensions is required.</p> <h3 data-selectable-paragraph="">Three-Tier Decision System</h3> <h4 data-selectable-paragraph=""><strong>Tier 1—Decision Tree (1-second intervals)</strong></h4> <p data-selectable-paragraph="">Instant reactive decisions based on CPU utilization, request latency, queue depth, and error rate. CPU above 70%? Add an instance. Below 30% sustained? Remove one (if above minimum). Latency exceeding the P95 threshold? Scale up. Error rate above 1% due to timeouts? Scale up. Deterministic, predictable, fast. Handles routine load changes with configurable cooldown periods — 30 seconds for scale-up, 5 minutes for scale-down — to prevent oscillation.</p> <h4 data-selectable-paragraph=""><strong>Tier 2—TTM Predictor (60-second intervals)</strong></h4> <p data-selectable-paragraph="">An ONNX-based machine learning model (Tiny Time Mixers) analyzes a 60-minute sliding window of metrics — CPU usage, request rate, P95 latency, and active instances. Forecasts load and adjusts the Decision Tree’s thresholds preemptively. If TTM predicts a load increase, it lowers the scale-up CPU threshold by 20% so the reactive tier responds earlier. The cluster scales before the spike arrives, not after. The key design principle: the cluster always survives on Tier 1 alone. TTM enhances; it doesn’t replace. If TTM fails — model load error, insufficient data, inference failure — the Decision Tree continues with default thresholds. The error is logged and recorded in metrics. No scaling disruption.</p> <h4 data-selectable-paragraph=""><strong>Tier 3—LLM-based (planned)</strong></h4> <p data-selectable-paragraph="">Long-term capacity planning and cluster health monitoring. Seasonal pattern prediction, maintenance window planning, anomaly investigation. This tier is not yet implemented — the current system operates with Tiers 1 and 2.</p> <p data-selectable-paragraph="">Fault tolerance makes preemptible instances viable for burst scaling. If a spot instance gets reclaimed, the cluster survives — it was designed for nodes to disappear.</p> <p data-selectable-paragraph="">You don’t need a PhD in distributed systems or a dedicated platform team. The scaling system manages itself.</p> <h2 data-selectable-paragraph="">Development Experience: From Laptop To Production</h2> <h3 data-selectable-paragraph="">Three Environments, Zero Code Changes</h3> <h4 data-selectable-paragraph=""><strong>Ember</strong></h4> <p data-selectable-paragraph="">Single-process runtime with multiple cluster nodes running in the same JVM. Fast startup, simple debugging. Deploy your slices alongside your existing application — slices call each other directly in-process. No network overhead. Standard debugger breakpoints work as expected. Perfect for local development and unit testing.</p> <h4 data-selectable-paragraph=""><strong>Forge</strong></h4> <p data-selectable-paragraph="">A 5-node cluster simulator running on your laptop. Real consensus. Real routing. Real failure scenarios. Kill nodes, crash the leader, trigger rolling restarts — and watch the cluster recover in real time through a web dashboard with D3.js topology visualization, per-node metrics (CPU, heap, leader status), and event timeline. Configurable load generation with TOML-based multi-target configuration lets you stress-test realistic scenarios — set request rates, define body templates, and run duration-limited load tests. Chaos operations include node kill, leader kill, and rolling restart. Forge validates the entire dependency graph before starting anything.</p> <h4 data-selectable-paragraph=""><strong>Aether</strong></h4> <p data-selectable-paragraph="">Production cluster. Same slices, same code, different scale. Your code doesn’t know which environment it’s running in. Whether inter-slice calls are in-process or cross-network is transparent.</p> <h3 data-selectable-paragraph="">Tooling</h3> <p data-selectable-paragraph="">37 CLI commands cover deployment, scaling, updates, artifacts, observability, controller configuration, and alerts — in both single-command and interactive REPL modes. A web dashboard streams real-time metrics via WebSocket — no polling. 30+ REST management endpoints enable full programmatic control of everything the CLI can do. Prometheus-compatible metrics export (<code>/metrics/prometheus</code>) integrates with existing monitoring stacks. </p> <p data-selectable-paragraph="">Metrics are push-based at 1-second intervals, with zero consensus overhead — they bypass the consensus protocol entirely. Per-method invocation tracking with P50/P95/P99 latency and configurable slow-invocation detection strategies (fixed threshold, adaptive, per-method, composite) surfaces performance issues before users notice. Dynamic aspects let you toggle LOG/METRICS/LOG_AND_METRICS modes per method at runtime via REST API, without redeployment.</p> <p data-selectable-paragraph="">Test realistic failure scenarios on your laptop. Deploy to production with a config change, not a code change.</p> <h3 data-selectable-paragraph="">Maturity</h3> <p data-selectable-paragraph="">Aether is a working system, not a concept paper.</p> <p data-selectable-paragraph="">81 end-to-end tests are run against real 5-node clusters in Podman containers, validating cluster formation, quorum establishment, slice deployment and scaling, blueprint application with topological ordering, multi-instance distribution, artifact upload, and cross-node resolution with integrity verification, leader failure and recovery, node restart with state restoration, and orphaned state cleanup after leader changes.</p> <p data-selectable-paragraph="">The recovery and fault tolerance claims come from automated tests against real clusters, not marketing slides.</p> <h3 data-selectable-paragraph="">Let Java Be Java</h3> <p data-selectable-paragraph="">Java’s lineage leads here. From applets managed by browsers, through servlets managed by application servers, through EJBs managed by enterprise containers, through OSGi managed by runtime frameworks, to Aether, managed by a distributed runtime.</p> <p data-selectable-paragraph="">The fat-jar era was a detour. An understandable one — when Docker emerged, it offered a universal packaging format, and the industry standardized on it regardless of language. Java adopted the patterns of languages that were designed to produce standalone binaries. We started treating Java applications like Go programs with a heavier runtime. But it was never the destination. Java was designed for managed environments. The JVM makes it possible. The runtime manages the application. That’s the lineage. Aether continues it.</p> <p data-selectable-paragraph="">Two entry points exist today. Wrap your legacy monolith behind a <code>@Slice</code> interface in one sprint and gain fault tolerance without rewriting anything. Or start fresh with maximum clarity — lean slices, explicit contracts, per-use-case scaling. Both paths converge on the same runtime, the same cluster, the same operational model. Both paths can coexist — legacy service slices and new lean slices running side by side.</p> <p data-selectable-paragraph="">Fault tolerance is not an afterthought — it's the foundation. Scaling is not your problem — it's the environment’s. Infrastructure is not your code — it's the runtime’s. The heavy winter coat comes off. The application breathes.</p> <h2 data-selectable-paragraph="">Resources</h2> <ul> <li data-selectable-paragraph=""><a href="https://pragmaticalabs.io/" rel="noopener ugc nofollow" target="_blank">Pragmatica Aether</a>—project site</li> <li data-selectable-paragraph=""><a href="https://github.com/pragmaticalabs/pragmatica" rel="noopener ugc nofollow" target="_blank">GitHub Repository</a>—source code</li> </ul></div> </div><br><span style='font: #ff0000'>Generated by <a href='https://github.com/andreskrey/readability.php'>Readability.php</a>.</span> Fri, 29 May 2026 13:00:09 GMT https://dzone.com/articles/3639347 Sergiy Yevtushenko Stateless JWT Auth Microservice Architecture With Spring Boot 3 and Redis Sentinel https://dzone.com/articles/jwt-auth-spring-boot-redis-sentinel <img src="https://dz2cdn1.dzone.com/storage/article-thumb/19000553-thumb.jpg" /><h1>Stateless JWT Auth With Spring Boot 3 & Redis Sentinel</h1><div> <div><p>In this article, I will discuss a highly available solution developed using Spring Boot 3 and Spring Security 6 to address the "centralized authentication method" problem frequently seen in modern microservice ecosystems.</p> <p>We are not simply moving to an "authorization service"; we are examining the cache-first pattern, which minimizes DB usage, and the <a href="https://dzone.com/articles/redis-sentinel-with-spring">Redis Sentinel</a> enhancement, which guarantees system persistence.</p> <h2>Why a Separate Authentication Service?</h2> <p>While embedding security into each service is an option in microservices, I have always found it more logical to proceed with a centralized Auth service and API Gateway combination.</p> <ul> <li><strong>DRY (Don't Repeat Yourself)</strong>: Using token authentication logic in many services increases extra maintenance costs.</li> <li><strong>Isolation</strong>: Business services focus only on business logic; they don't deal with "is this token valid?" questions.</li> <li><strong>Performance</strong>: Thanks to the Redis connection, instead of going to the database with every request, we can resolve the validation via the cache in milliseconds.</li> </ul> <div contenteditable="false"> <div contenteditable="false"> <div data-code="[Client] ──► [API Gateway] ──► [Auth Service: validate token]&#xA; │ (valid)&#xA; ▼&#xA; [Backend Microservices]" data-lang="text/plain"> <pre><code lang="text/plain">[Client] ──► [API Gateway] ──► [Auth Service: validate token] │ (valid) ▼                               [Backend Microservices]</code></pre> </div> </div> </div> <h2><strong>Cache-Focused Approach: Reducing Database Load</strong></h2> <p>In the classic workflow, every login request puts a load on the DB.</p> <p>With the cache-first approach, the process proceeds like this with a POST /auth/signin request:</p> <p>First, <a href="https://dzone.com/articles/using-redis-on-cloud-here-are-ten-things-you-shoul">Redis</a> is checked. If there is a valid and unexpired token for the user, it is replicated directly. In case of cache deficiency, AuthManager.authenticate() is activated, a DB query is sent, and a BCrypt check is performed.</p> <p>After a successful login, a token is generated with JJWT (HS256). This token is given to Redis with our changes and TTL (e.g., 24 minutes), and personal responses are converted. In this way, it protects our main database, especially in brute-force or high-intensity login password attacks.</p> <div contenteditable="false"> <div contenteditable="false"> <div data-code="POST /auth/signin&#xA; │&#xA; ▼&#xA;┌──────────────────────────────┐&#xA;│ Token exists in Redis? │──── YES ──► Return token (0 DB queries)&#xA;└──────────────────────────────┘&#xA; │ NO&#xA; ▼&#xA;┌──────────────────────────────┐&#xA;│ AuthManager.authenticate() │ (DB query + BCrypt verification)&#xA;└──────────────────────────────┘&#xA; │&#xA; ▼&#xA;┌──────────────────────────────┐&#xA;│ Generate JWT (JJWT HS256) │&#xA;└──────────────────────────────┘&#xA; │&#xA; ▼&#xA;┌──────────────────────────────┐&#xA;│ Write to Redis (TTL: 24 min)│&#xA;└──────────────────────────────┘&#xA; │&#xA; ▼&#xA; Return token" data-lang="text/plain"> <pre><code lang="text/plain">POST /auth/signin │ ▼ ┌──────────────────────────────┐ │ Token exists in Redis? │──── YES ──► Return token (0 DB queries) └──────────────────────────────┘ │ NO ▼ ┌──────────────────────────────┐ │ AuthManager.authenticate() │ (DB query + BCrypt verification) └──────────────────────────────┘ │ ▼ ┌──────────────────────────────┐ │ Generate JWT (JJWT HS256) │ └──────────────────────────────┘ │ ▼ ┌──────────────────────────────┐ │ Write to Redis (TTL: 24 min)│ └──────────────────────────────┘ │ ▼    Return token</code></pre> </div> </div> </div> <h2><strong>Implementation Details</strong></h2> <h3>User Entity and UserDetails Integration</h3> <p>In most projects, unnecessary mappings are performed between the User asset and the UserDetails objects expected by <a href="https://dzone.com/articles/spring-security-authentication">Spring Security</a>. To reduce complexity, the User Entity is directly derived from the UserDetails interface. This makes the code cleaner and makes it "native," as outlined by Spring Security.</p> <div contenteditable="false"> <div contenteditable="false"> <div data-code="@Data&#xA;@Builder&#xA;@NoArgsConstructor&#xA;@AllArgsConstructor&#xA;@Entity&#xA;@Table(name = &quot;T_APP_USER&quot;)&#xA;public class User implements UserDetails {&#xA;&#xA; @Id&#xA; @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = &quot;seq_user_gen&quot;)&#xA; @SequenceGenerator(name = &quot;seq_user_gen&quot;, sequenceName = &quot;SEQ_APP_USER&quot;, allocationSize = 1)&#xA; @Column(name = &quot;idx&quot;)&#xA; private Long idx;&#xA;&#xA; @Column(name = &quot;firstname&quot;) private String firstName;&#xA; @Column(name = &quot;lastname&quot;) private String lastName;&#xA; @Column(unique = true, name = &quot;email&quot;) private String email;&#xA; @Column(name = &quot;accesskey&quot;) private String accessKey; // BCrypt-hashed&#xA;&#xA; @Column(name = &quot;role&quot;)&#xA; @Enumerated(EnumType.STRING)&#xA; private Role role;&#xA;&#xA; @Override&#xA; public Collection&lt;? extends GrantedAuthority> getAuthorities() {&#xA; return List.of(new SimpleGrantedAuthority(role.name()));&#xA; }&#xA;&#xA; @Override public String getUsername() { return email; }&#xA; @Override public String getPassword() { return accessKey; }&#xA; @Override public boolean isAccountNonExpired() { return true; }&#xA; @Override public boolean isAccountNonLocked() { return true; }&#xA; @Override public boolean isCredentialsNonExpired() { return true; }&#xA; @Override public boolean isEnabled() { return true; }&#xA;}" data-lang="text/x-java"> <pre><code lang="text/x-java">@Data @Builder @NoArgsConstructor @AllArgsConstructor @Entity @Table(name = "T_APP_USER") public class User implements UserDetails { @Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "seq_user_gen") @SequenceGenerator(name = "seq_user_gen", sequenceName = "SEQ_APP_USER", allocationSize = 1) @Column(name = "idx") private Long idx; @Column(name = "firstname") private String firstName; @Column(name = "lastname") private String lastName; @Column(unique = true, name = "email") private String email; @Column(name = "accesskey") private String accessKey; // BCrypt-hashed @Column(name = "role") @Enumerated(EnumType.STRING) private Role role; @Override public Collection&lt;? extends GrantedAuthority&gt; getAuthorities() { return List.of(new SimpleGrantedAuthority(role.name())); } @Override public String getUsername() { return email; } @Override public String getPassword() { return accessKey; } @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return true; } }</code></pre> </div> </div> </div> <h2><strong>JWT Filter: The Gateway to Security</strong></h2> <p>The request to the system passes through the OncePerRequestFilter.</p> <p>Here, using JwtAuthenticationFilter, we parse the token in each request and populate the SecurityContext. By using the new SecurityFilterChain bean introduced with Spring Security 6, we have disabled CSRF and made session management completely stateless.</p> <h3>Token Generation and Validation</h3> <div contenteditable="false"> <div contenteditable="false"> <div data-code="public interface JwtService {&#xA; String extractUserName(String token);&#xA; String generateToken(UserDetails userDetails);&#xA; boolean isTokenValid(String token, UserDetails userDetails);&#xA;}&#xA;&#xA;@Service&#xA;public class JwtServiceImpl implements JwtService {&#xA;&#xA; @Value(&quot;${token.signing.key}&quot;)&#xA; private String jwtSigningKey; // Base64-encoded secret key&#xA;&#xA; @Override&#xA; public String extractUserName(String token) {&#xA; return extractClaim(token, Claims::getSubject);&#xA; }&#xA;&#xA; @Override&#xA; public String generateToken(UserDetails userDetails) {&#xA; return Jwts.builder()&#xA; .setClaims(new HashMap&lt;>())&#xA; .setSubject(userDetails.getUsername())&#xA; .setIssuedAt(new Date(System.currentTimeMillis()))&#xA; .setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 24)) &#xA; .signWith(getSigningKey(), SignatureAlgorithm.HS256)&#xA; .compact();&#xA; }&#xA;&#xA; @Override&#xA; public boolean isTokenValid(String token, UserDetails userDetails) {&#xA; final String userName = extractUserName(token);&#xA; return userName.equals(userDetails.getUsername()) &amp;&amp; !isTokenExpired(token);&#xA; }&#xA;&#xA; private &lt;T> T extractClaim(String token, Function&lt;Claims, T> claimsResolver) {&#xA; return claimsResolver.apply(&#xA; Jwts.parserBuilder()&#xA; .setSigningKey(getSigningKey())&#xA; .build()&#xA; .parseClaimsJws(token)&#xA; .getBody()&#xA; );&#xA; }&#xA;&#xA; private boolean isTokenExpired(String token) {&#xA; return extractClaim(token, Claims::getExpiration).before(new Date());&#xA; }&#xA;&#xA; private Key getSigningKey() {&#xA; return Keys.hmacShaKeyFor(Decoders.BASE64.decode(jwtSigningKey));&#xA; }&#xA;}" data-lang="text/x-java"> <pre><code lang="text/x-java">public interface JwtService { String extractUserName(String token); String generateToken(UserDetails userDetails); boolean isTokenValid(String token, UserDetails userDetails); } @Service public class JwtServiceImpl implements JwtService { @Value("${token.signing.key}") private String jwtSigningKey; // Base64-encoded secret key @Override public String extractUserName(String token) { return extractClaim(token, Claims::getSubject); } @Override public String generateToken(UserDetails userDetails) { return Jwts.builder() .setClaims(new HashMap&lt;&gt;()) .setSubject(userDetails.getUsername()) .setIssuedAt(new Date(System.currentTimeMillis())) .setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 24)) .signWith(getSigningKey(), SignatureAlgorithm.HS256) .compact(); } @Override public boolean isTokenValid(String token, UserDetails userDetails) { final String userName = extractUserName(token); return userName.equals(userDetails.getUsername()) &amp;&amp; !isTokenExpired(token); } private &lt;T&gt; T extractClaim(String token, Function&lt;Claims, T&gt; claimsResolver) { return claimsResolver.apply( Jwts.parserBuilder() .setSigningKey(getSigningKey()) .build() .parseClaimsJws(token) .getBody() ); } private boolean isTokenExpired(String token) { return extractClaim(token, Claims::getExpiration).before(new Date()); } private Key getSigningKey() { return Keys.hmacShaKeyFor(Decoders.BASE64.decode(jwtSigningKey)); } }</code></pre> </div> </div> </div> <h2><strong>High Availability: Redis Sentinel</strong></h2> <p>Using a single Redis instance means that the Auth service has a "Single Point of Failure." If Redis crashes, no one can access the system. This risk mitigation was achieved using Redis Sentinel.</p> <p>Thanks to the Sentinel structure: If the master node crashes, the dependent node is automatically promoted to master via failover. On the application side, we continuously manage these transitions using the Lettuce driver.</p> <p>Technical Stack and Requirements</p> <p><img alt="Technical stack and requirements" data-creationdate="1776635659781" data-creationdateformatted="04/19/2026 09:54 PM" data-id="18993522" data-image="true" data-mimetype="image/png" data-modificationdate="null" data-name="technology.png" data-new="false" data-size="8680" data-sizeformatted="8.7 kB" data-src="https://dz2cdn1.dzone.com/storage/temp/18993522-technology.png" data-type="temp" data-url="https://dz2cdn1.dzone.com/storage/temp/18993522-technology.png"></img></p> <p>Redis Sentinel configuration:</p> <div contenteditable="false"> <div contenteditable="false"> <div data-code="@Configuration&#xA;public class RedisConfig {&#xA;&#xA; @Value(&quot;${spring.redis.sentinel.master}&quot;)&#xA; private String master;&#xA;&#xA; @Value(&quot;${spring.redis.sentinel.nodes}&quot;) &#xA; private String sentinelNodes;&#xA;&#xA; @Value(&quot;${spring.redis.password}&quot;)&#xA; private String password;&#xA;&#xA; @Bean&#xA; public RedisConnectionFactory redisConnectionFactory() {&#xA; RedisSentinelConfiguration sentinelConfig = new RedisSentinelConfiguration()&#xA; .master(master);&#xA;&#xA; for (String node : sentinelNodes.split(&quot;,&quot;)) {&#xA; String[] hostPort = node.split(&quot;:&quot;);&#xA; sentinelConfig.sentinel(hostPort[0], Integer.parseInt(hostPort[1]));&#xA; }&#xA;&#xA; sentinelConfig.setPassword(RedisPassword.of(password));&#xA; return new LettuceConnectionFactory(sentinelConfig);&#xA; }&#xA;}" data-lang="text/x-java"> <pre><code lang="text/x-java">@Configuration public class RedisConfig { @Value("${spring.redis.sentinel.master}") private String master; @Value("${spring.redis.sentinel.nodes}") private String sentinelNodes; @Value("${spring.redis.password}") private String password; @Bean public RedisConnectionFactory redisConnectionFactory() { RedisSentinelConfiguration sentinelConfig = new RedisSentinelConfiguration() .master(master); for (String node : sentinelNodes.split(",")) { String[] hostPort = node.split(":"); sentinelConfig.sentinel(hostPort[0], Integer.parseInt(hostPort[1])); } sentinelConfig.setPassword(RedisPassword.of(password)); return new LettuceConnectionFactory(sentinelConfig); } }</code></pre> </div> </div> </div> <div contenteditable="false"> <div contenteditable="false"> <div data-code="yaml&#xA; &#xA; env:&#xA; - name: spring.redis.sentinel.master&#xA; valueFrom:&#xA; secretKeyRef:&#xA; name: redis-user-secret&#xA; key: username&#xA; - name: spring.redis.password&#xA; valueFrom:&#xA; secretKeyRef:&#xA; name: redis-user-secret&#xA; key: password" data-lang="text/plain"> <pre><code lang="text/plain">yaml env: - name: spring.redis.sentinel.master valueFrom: secretKeyRef: name: redis-user-secret key: username - name: spring.redis.password valueFrom: secretKeyRef: name: redis-user-secret          key: password</code></pre> </div> </div> </div> <p>Token cache service:</p> <div contenteditable="false"> <div contenteditable="false"> <div data-code="@Service&#xA;public class TokenCacheServiceImpl {&#xA;&#xA; private final RedisTemplate&lt;String, String> redisTemplate;&#xA;&#xA; public TokenCacheServiceImpl(RedisTemplate&lt;String, String> redisTemplate) {&#xA; this.redisTemplate = redisTemplate;&#xA; }&#xA;&#xA; public void cacheToken(String username, String token, long duration, TimeUnit unit) {&#xA; redisTemplate.opsForValue().set(username, token, duration, unit);&#xA; }&#xA;&#xA; @Cacheable(value = &quot;tokens&quot;, key = &quot;#username&quot;)&#xA; public String getToken(String username) {&#xA; return redisTemplate.opsForValue().get(username);&#xA; }&#xA;}" data-lang="text/x-java"> <pre><code lang="text/x-java">@Service public class TokenCacheServiceImpl { private final RedisTemplate&lt;String, String&gt; redisTemplate; public TokenCacheServiceImpl(RedisTemplate&lt;String, String&gt; redisTemplate) { this.redisTemplate = redisTemplate; } public void cacheToken(String username, String token, long duration, TimeUnit unit) { redisTemplate.opsForValue().set(username, token, duration, unit); } @Cacheable(value = "tokens", key = "#username") public String getToken(String username) { return redisTemplate.opsForValue().get(username); } }</code></pre> </div> </div> </div> <p>Authentication service: signup and signin:</p> <div contenteditable="false"> <div contenteditable="false"> <div data-code="@Service&#xA;@RequiredArgsConstructor&#xA;public class AuthenticationServiceImpl implements AuthenticationService {&#xA;&#xA; private final UserRepository userRepository;&#xA; private final PasswordEncoder passwordEncoder;&#xA; private final JwtService jwtService;&#xA; private final AuthenticationManager authenticationManager;&#xA; private final TokenCacheServiceImpl tokenCacheService;&#xA;&#xA; @Override&#xA; public JwtAuthenticationResponse signup(SignUpRequest request) {&#xA; var user = User.builder()&#xA; .firstName(request.getFirstName())&#xA; .lastName(request.getLastName())&#xA; .email(request.getEmail())&#xA; .accessKey(passwordEncoder.encode(request.getAccessKey())) // BCrypt&#xA; .role(Role.USER)&#xA; .build();&#xA;&#xA; userRepository.save(user);&#xA; var jwt = jwtService.generateToken(user);&#xA; return JwtAuthenticationResponse.builder().token(jwt).build();&#xA; }&#xA;&#xA; @Override&#xA; public JwtAuthenticationResponse signin(SigninRequest request) {&#xA; // 1. Check Redis cache first&#xA; String cachedToken = tokenCacheService.getToken(request.getEmail());&#xA; if (cachedToken != null) {&#xA; return JwtAuthenticationResponse.builder().token(cachedToken).build();&#xA; }&#xA;&#xA; // 2. If not cached, authenticate (DB + BCrypt)&#xA; authenticationManager.authenticate(&#xA; new UsernamePasswordAuthenticationToken(request.getEmail(), request.getAccessKey())&#xA; );&#xA;&#xA; var user = userRepository.findByEmail(request.getEmail())&#xA; .orElseThrow(() -> new IllegalArgumentException(&quot;Invalid credentials.&quot;));&#xA;&#xA; // 3. Generate token and write to Redis (24 min TTL)&#xA; var jwt = jwtService.generateToken(user);&#xA; tokenCacheService.cacheToken(request.getEmail(), jwt, 24, TimeUnit.MINUTES);&#xA; return JwtAuthenticationResponse.builder().token(jwt).build();&#xA; }&#xA;}" data-lang="text/x-java"> <pre><code lang="text/x-java">@Service @RequiredArgsConstructor public class AuthenticationServiceImpl implements AuthenticationService { private final UserRepository userRepository; private final PasswordEncoder passwordEncoder; private final JwtService jwtService; private final AuthenticationManager authenticationManager; private final TokenCacheServiceImpl tokenCacheService; @Override public JwtAuthenticationResponse signup(SignUpRequest request) { var user = User.builder() .firstName(request.getFirstName()) .lastName(request.getLastName()) .email(request.getEmail()) .accessKey(passwordEncoder.encode(request.getAccessKey())) // BCrypt .role(Role.USER) .build(); userRepository.save(user); var jwt = jwtService.generateToken(user); return JwtAuthenticationResponse.builder().token(jwt).build(); } @Override public JwtAuthenticationResponse signin(SigninRequest request) { // 1. Check Redis cache first String cachedToken = tokenCacheService.getToken(request.getEmail()); if (cachedToken != null) { return JwtAuthenticationResponse.builder().token(cachedToken).build(); } // 2. If not cached, authenticate (DB + BCrypt) authenticationManager.authenticate( new UsernamePasswordAuthenticationToken(request.getEmail(), request.getAccessKey()) ); var user = userRepository.findByEmail(request.getEmail()) .orElseThrow(() -&gt; new IllegalArgumentException("Invalid credentials.")); // 3. Generate token and write to Redis (24 min TTL) var jwt = jwtService.generateToken(user); tokenCacheService.cacheToken(request.getEmail(), jwt, 24, TimeUnit.MINUTES); return JwtAuthenticationResponse.builder().token(jwt).build(); } }</code></pre> </div> </div> </div> <p>JWT authentication filter:</p> <div contenteditable="false"> <div contenteditable="false"> <div data-code="@Component&#xA;@RequiredArgsConstructor&#xA;public class JwtAuthenticationFilter extends OncePerRequestFilter {&#xA;&#xA; private final JwtService jwtService;&#xA; private final UserService userService;&#xA;&#xA; @Override&#xA; protected void doFilterInternal(&#xA; @NonNull HttpServletRequest request,&#xA; @NonNull HttpServletResponse response,&#xA; @NonNull FilterChain filterChain&#xA; ) throws ServletException, IOException {&#xA;&#xA; final String authHeader = request.getHeader(&quot;Authorization&quot;);&#xA;&#xA; // Pass through if no Authorization header or doesn't start with Bearer&#xA; if (StringUtils.isEmpty(authHeader) || !StringUtils.startsWith(authHeader, &quot;Bearer &quot;)) {&#xA; filterChain.doFilter(request, response);&#xA; return;&#xA; }&#xA;&#xA; final String jwt = authHeader.substring(7);&#xA; final String userEmail = jwtService.extractUserName(jwt);&#xA;&#xA; // Process only if SecurityContext has no authentication yet&#xA; if (StringUtils.isNotEmpty(userEmail)&#xA; &amp;&amp; SecurityContextHolder.getContext().getAuthentication() == null) {&#xA;&#xA; UserDetails userDetails = userService.userDetailsService()&#xA; .loadUserByUsername(userEmail);&#xA;&#xA; if (jwtService.isTokenValid(jwt, userDetails)) {&#xA; SecurityContext context = SecurityContextHolder.createEmptyContext();&#xA; UsernamePasswordAuthenticationToken authToken =&#xA; new UsernamePasswordAuthenticationToken(&#xA; userDetails, null, userDetails.getAuthorities()&#xA; );&#xA; authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));&#xA; context.setAuthentication(authToken);&#xA; SecurityContextHolder.setContext(context);&#xA; }&#xA; }&#xA;&#xA; filterChain.doFilter(request, response);&#xA; }&#xA;}" data-lang="text/x-java"> <pre><code lang="text/x-java">@Component @RequiredArgsConstructor public class JwtAuthenticationFilter extends OncePerRequestFilter { private final JwtService jwtService; private final UserService userService; @Override protected void doFilterInternal( @NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull FilterChain filterChain ) throws ServletException, IOException { final String authHeader = request.getHeader("Authorization"); // Pass through if no Authorization header or doesn't start with Bearer if (StringUtils.isEmpty(authHeader) || !StringUtils.startsWith(authHeader, "Bearer ")) { filterChain.doFilter(request, response); return; } final String jwt = authHeader.substring(7); final String userEmail = jwtService.extractUserName(jwt); // Process only if SecurityContext has no authentication yet if (StringUtils.isNotEmpty(userEmail) &amp;&amp; SecurityContextHolder.getContext().getAuthentication() == null) { UserDetails userDetails = userService.userDetailsService() .loadUserByUsername(userEmail); if (jwtService.isTokenValid(jwt, userDetails)) { SecurityContext context = SecurityContextHolder.createEmptyContext(); UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken( userDetails, null, userDetails.getAuthorities() ); authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); context.setAuthentication(authToken); SecurityContextHolder.setContext(context); } } filterChain.doFilter(request, response); } }</code></pre> </div> </div> </div> <p>Spring Security 6 configuration:</p> <div contenteditable="false"> <div contenteditable="false"> <div data-code="@Configuration&#xA;@EnableWebSecurity&#xA;@RequiredArgsConstructor&#xA;public class SecurityConfiguration {&#xA;&#xA; private final JwtAuthenticationFilter jwtAuthenticationFilter;&#xA; private final UserService userService;&#xA;&#xA; @Bean&#xA; public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {&#xA; http&#xA; .csrf(AbstractHttpConfigurer::disable) // Stateless → no CSRF needed&#xA; .authorizeHttpRequests(request -> request&#xA; .requestMatchers(&quot;/auth/**&quot;).permitAll() // Auth endpoints open to all&#xA; .anyRequest().authenticated()&#xA; )&#xA; .sessionManagement(manager ->&#xA; manager.sessionCreationPolicy(STATELESS) // No server-side session&#xA; )&#xA; .authenticationProvider(authenticationProvider())&#xA; .addFilterBefore(jwtAuthenticationFilter, // JWT filter runs first&#xA; UsernamePasswordAuthenticationFilter.class);&#xA;&#xA; return http.build();&#xA; }&#xA;&#xA; @Bean&#xA; public PasswordEncoder passwordEncoder() {&#xA; return new BCryptPasswordEncoder();&#xA; }&#xA;&#xA; @Bean&#xA; public AuthenticationProvider authenticationProvider() {&#xA; DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();&#xA; authProvider.setUserDetailsService(userService.userDetailsService());&#xA; authProvider.setPasswordEncoder(passwordEncoder());&#xA; return authProvider;&#xA; }&#xA;&#xA; @Bean&#xA; public AuthenticationManager authenticationManager(AuthenticationConfiguration config)&#xA; throws Exception {&#xA; return config.getAuthenticationManager();&#xA; }&#xA;}" data-lang="text/x-java"> <pre><code lang="text/x-java">@Configuration @EnableWebSecurity @RequiredArgsConstructor public class SecurityConfiguration { private final JwtAuthenticationFilter jwtAuthenticationFilter; private final UserService userService; @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .csrf(AbstractHttpConfigurer::disable) // Stateless → no CSRF needed .authorizeHttpRequests(request -&gt; request .requestMatchers("/auth/**").permitAll() // Auth endpoints open to all .anyRequest().authenticated() ) .sessionManagement(manager -&gt; manager.sessionCreationPolicy(STATELESS) // No server-side session ) .authenticationProvider(authenticationProvider()) .addFilterBefore(jwtAuthenticationFilter, // JWT filter runs first UsernamePasswordAuthenticationFilter.class); return http.build(); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean public AuthenticationProvider authenticationProvider() { DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider(); authProvider.setUserDetailsService(userService.userDetailsService()); authProvider.setPasswordEncoder(passwordEncoder()); return authProvider; } @Bean public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception { return config.getAuthenticationManager(); } }</code></pre> </div> </div> </div> <p>Unit tests:</p> <div contenteditable="false"> <div contenteditable="false"> <div data-code="@Test&#xA;@DisplayName(&quot;Signin: if token is cached, should not query the DB&quot;)&#xA;void testSignInWithCachedToken() {&#xA; when(tokenCacheService.getToken(TEST_EMAIL)).thenReturn(TEST_TOKEN);&#xA;&#xA; JwtAuthenticationResponse response = authenticationService.signin(&#xA; SigninRequest.builder().email(TEST_EMAIL).accessKey(TEST_PASSWORD).build()&#xA; );&#xA;&#xA; assertEquals(TEST_TOKEN, response.getToken());&#xA; verifyNoInteractions(authenticationManager); // No DB + BCrypt call should happen&#xA; verifyNoInteractions(userRepository);&#xA;}&#xA;&#xA;// Invalid token test — SecurityContext should remain empty&#xA;@Test&#xA;@DisplayName(&quot;With an invalid token, SecurityContext should remain empty&quot;)&#xA;void testDoFilterInternalInvalidToken() throws Exception {&#xA; when(request.getHeader(&quot;Authorization&quot;)).thenReturn(&quot;Bearer &quot; + INVALID_TOKEN);&#xA; when(jwtService.extractUserName(INVALID_TOKEN)).thenReturn(TEST_EMAIL);&#xA; when(userService.userDetailsService()).thenReturn(userDetailsService);&#xA; when(userDetailsService.loadUserByUsername(TEST_EMAIL)).thenReturn(userDetails);&#xA; when(jwtService.isTokenValid(INVALID_TOKEN, userDetails)).thenReturn(false);&#xA;&#xA; jwtAuthenticationFilter.doFilterInternal(request, response, filterChain);&#xA;&#xA; verify(filterChain).doFilter(request, response);&#xA; assertNull(SecurityContextHolder.getContext().getAuthentication());&#xA;}" data-lang="text/x-java"> <pre><code lang="text/x-java">@Test @DisplayName("Signin: if token is cached, should not query the DB") void testSignInWithCachedToken() { when(tokenCacheService.getToken(TEST_EMAIL)).thenReturn(TEST_TOKEN); JwtAuthenticationResponse response = authenticationService.signin( SigninRequest.builder().email(TEST_EMAIL).accessKey(TEST_PASSWORD).build() ); assertEquals(TEST_TOKEN, response.getToken()); verifyNoInteractions(authenticationManager); // No DB + BCrypt call should happen verifyNoInteractions(userRepository); } // Invalid token test — SecurityContext should remain empty @Test @DisplayName("With an invalid token, SecurityContext should remain empty") void testDoFilterInternalInvalidToken() throws Exception { when(request.getHeader("Authorization")).thenReturn("Bearer " + INVALID_TOKEN); when(jwtService.extractUserName(INVALID_TOKEN)).thenReturn(TEST_EMAIL); when(userService.userDetailsService()).thenReturn(userDetailsService); when(userDetailsService.loadUserByUsername(TEST_EMAIL)).thenReturn(userDetails); when(jwtService.isTokenValid(INVALID_TOKEN, userDetails)).thenReturn(false); jwtAuthenticationFilter.doFilterInternal(request, response, filterChain); verify(filterChain).doFilter(request, response); assertNull(SecurityContextHolder.getContext().getAuthentication()); }</code></pre> </div> </div> </div> <h2><strong>Summary and Conclusion</strong></h2> <p>With the purchasing architecture, not only a secure login screen; It has built an architecture that is extremely scalable, overcomes database bottlenecks with caching, and meets high availability (HA) standards. </p> <p>In particular, the modern architecture offered by Spring Boot 3 has made the security layer much more flexible. If you are starting a large-scale microservice project, you can design token management from the outset in this "stateless" and "cached" manner.</p></div> </div><p>Opinions expressed by DZone contributors are their own.</p><br><span style='font: #ff0000'>Generated by <a href='https://github.com/andreskrey/readability.php'>Readability.php</a>.</span> Wed, 27 May 2026 19:00:00 GMT https://dzone.com/articles/3650426 Erkin Karanlık Rethinking Java CRUDs With Event Sourcing and CQRS Patterns https://dzone.com/articles/java-crud-event-sourcing-cqrs <img src="https://dz2cdn1.dzone.com/storage/article-thumb/19034244-thumb.jpg" /><h1>Rethink Java CRUDs With Event Sourcing and CQRS Patterns</h1><div> <div><p>Traditional CRUD systems store only the current state of an entity. When a record is updated, the previous value is overwritten and lost forever. Event Sourcing inverts this model: instead of persisting state, the system persists the sequence of events that caused each state transition. The current state is never stored directly, but it is always derived by replaying the event history.</p> <p>Command Query Responsibility Segregation (CQRS) separates the write model from the read model. A command expresses intent to change state, for example <code>PlaceOrder</code>, <code>AddItem</code>, <code>ShipOrder</code>. A query reads state without modifying it. The two sides use separate models, separate logic, and, in a full implementation, separate storage.</p> <p>CQRS and event sourcing are complementary: the event stream is the write side's source of truth, while one or more projections (read models) are derived from those events for fast querying.</p> <p>This article aims at showing how to apply, in practice, these concepts, using for illustration purposes a modified version of one of <a href="https://www.the-main-thread.com/p/event-sourcing-quarkus-java-records-cqrs-tutorial?utm_source=post-email-title&amp;publication_id=4194688&amp;post_id=181216251&amp;utm_campaign=email-post-title&amp;isFreemail=true&amp;r=3b6280&amp;triedRedirect=true" rel="noopener noreferrer" target="_blank">Markus Eisele's article</a>, from the 27th of December 2025 on Substack. In his article, Markus shows a Quarkus-based project implementing a simplified order management system. Here, I'm presenting the Spring Boot implementation of this same system, to change. You can find it <a href="https://github.com/nicolasduminil/cqrs-showcase" rel="noopener noreferrer" target="_blank">here</a>.</p> <h2>Terminology</h2> <p>In a *classical* order management system, by analyzing the associated data model, we can gather a lot of information about orders and their flow in the organization. But while we would be able to account for any order's current status, the data and the data model analysis wouldn't allow us to reconstitute the story of how each order got to its current state.</p> <h3>Event Sourcing</h3> <p>The <a href="https://dzone.com/articles/event-sourcing-guide-when-to-use-avoid-pitfalls">event sourcing</a> pattern introduces the dimension of time into the data model. Instead of a schema reflecting the orders' current state, an event-sourcing-based system persists events documenting every change in the orders' lifecycle. Then, by querying these events, we can reconstitute the whole story of a given order, or any other general aggregate, from its initial creation until its current status.</p> <h3>CQRS</h3> <p>The only problem here is that querying a single aggregate instance event story at a time doesn't allow us to retrieve and consolidate data relative to other aggregates in the data model. Hence, the CQRS pattern is closely related to the event sourcing one, designed to provide the possibility of materializing<br></br> projected models into logical data structures, reliable enough to support flexible querying options.</p> <h3>Commands</h3> <p>CQRS dedicates commands to execute operations that modify the system state. The command-based execution model is then the only one able to implement business logic, to validate rules, and to enforce invariants.</p> <h3>Projections</h3> <p>The system can define as many models as required to provide data to users or to other systems. <span>Thus, a <em>read model</em> is a fast, denormalized, and pre-cached projection containing read-only data that the application needs to answer queries.</span> The system project changes from the command execution model to all its read models. The projection notion is similar to that of a materialized view in relational databases, meaning that whenever the source tables are updated, the changes have to be reflected in all the read model views.</p> <h3>Model Segregation</h3> <p>In a CQRS architecture, the responsibilities of the system's models are segregated according to their type. A command can only operate on its own execution model, while a query cannot directly modify any of the system's persisted state.</p> <h2>A Use Case</h2> <p>The use case presented here is a *true* CQRS implementation (not just a naming convention) because:</p> <ol> <li>The write path never reads from the read model. <code>CommandHandler</code> reconstructs state exclusively by replaying events from the event store via <code>EventProjection.replayEvents()</code>. It never touches <code>OrderReadModel</code> or <code>OrderRepository</code>.</li> <li>The read path never touches the event store. <code>OrderResource.getOrderReadModel()</code> reads directly from the denormalized <code>ORDERS</code> table. It is a pure query with no business logic.</li> <li>There are two physically distinct storage tables: <code>EVENT_STORE</code> (write side) and <code>ORDERS</code> (read side).</li> <li>The read model is a projection, not a view. <code>OrderProjection</code> listens to domain events and rebuilds the read model incrementally. The <code>ORDERS</code> table could be dropped and rebuilt from scratch by replaying the event store.</li> <li>Commands return <code>CommandResult</code>, a sealed type that communicates success or failure without leaking state. The caller must query the read model separately if it needs current state.</li> </ol> <p>Let's look now at the project's key implementation details:</p> <h3>Modeling State With Records</h3> <p><code>OrderState</code> is a Java record immutable by construction. No setters, no mutation. Every command produces a *new* state object:</p> <div contenteditable="false"> <div contenteditable="false"> <div data-code="public record OrderState(&#xA; UUID orderId,&#xA; String customerEmail,&#xA; List&lt;OrderLine> items,&#xA; OrderStatus status,&#xA; BigDecimal total&#xA;) {&#xA; public static OrderState initial(UUID orderId, String email) { ... }&#xA; public static OrderState empty() { ... }&#xA; }&#xA;" data-lang="text/x-java"> <pre><code lang="text/x-java">public record OrderState( UUID orderId, String customerEmail, List&lt;OrderLine&gt; items, OrderStatus status, BigDecimal total ) { public static OrderState initial(UUID orderId, String email) { ... } public static OrderState empty() { ... } } </code></pre> </div> </div> </div> <p><code>OrderLine</code> is likewise a record with a derived field:</p> <div contenteditable="false"> <div contenteditable="false"> <div data-code="public record OrderLine(String productName, int quantity, BigDecimal price) &#xA;{&#xA; public BigDecimal lineTotal() &#xA; {&#xA; return price.multiply(BigDecimal.valueOf(quantity));&#xA; }&#xA;}&#xA;" data-lang="text/x-java"> <pre><code lang="text/x-java">public record OrderLine(String productName, int quantity, BigDecimal price) { public BigDecimal lineTotal() { return price.multiply(BigDecimal.valueOf(quantity)); } } </code></pre> </div> </div> </div> <p><code>lineTotal()</code> is a derived record component: it is computed, not stored, demonstrating that records can carry behavior alongside.</p> <h3>Events as a Sealed Type Hierarchy</h3> <p><code>OrderEvent</code> is a sealed interface, restricting all permitted implementations to a known, closed set:</p> <div contenteditable="false"> <div contenteditable="false"> <div data-code="public sealed interface OrderEvent&#xA; permits OrderEvent.OrderPlaced,OrderEvent.ItemAdded,&#xA; OrderEvent.ItemRemoved,OrderEvent.OrderCancelled,&#xA; OrderEvent.OrderShipped &#xA;{&#xA; UUID orderId();&#xA; OrderState applyTo(OrderState current);&#xA;&#xA; record OrderPlaced(UUID orderId, String customerEmail) implements OrderEvent &#xA; {&#xA; public OrderState applyTo(OrderState s) &#xA; {&#xA; return OrderState.initial(orderId, customerEmail);&#xA; }&#xA; }&#xA; // ... other event types&#xA;}" data-lang="text/x-java"> <pre><code lang="text/x-java">public sealed interface OrderEvent permits OrderEvent.OrderPlaced,OrderEvent.ItemAdded, OrderEvent.ItemRemoved,OrderEvent.OrderCancelled, OrderEvent.OrderShipped { UUID orderId(); OrderState applyTo(OrderState current); record OrderPlaced(UUID orderId, String customerEmail) implements OrderEvent { public OrderState applyTo(OrderState s) { return OrderState.initial(orderId, customerEmail); } } // ... other event types }</code></pre> </div> </div> </div> <p><br></br> Using a sealed interface means the compiler enforces exhaustiveness in <code>switch</code>expressions. Adding a new event type without handling it is a compile error, not a runtime surprise.</p> <p>Each event carries only the data it needs and knows how to apply itself to the current state via <code>applyTo(OrderState)</code>. This is the <em>self-describing event</em> pattern.</p> <h3>The Fold (Event Replay)</h3> <p>A fold, also known as left reduction, is the process of reconstructing state from a list of events over the event stream:</p> <div contenteditable="false"> <div contenteditable="false"> <div data-code="// EventProjection.java&#xA;public OrderState replayEvents(List&lt;OrderEvent> events) &#xA;{&#xA; return events.stream()&#xA; .reduce(OrderState.empty(), this::apply, (a, b) -> b);&#xA;}&#xA;&#xA;private OrderState apply(OrderState state, OrderEvent event) &#xA;{&#xA; return event.applyTo(state);&#xA;}&#xA;" data-lang="text/x-java"> <pre><code lang="text/x-java">// EventProjection.java public OrderState replayEvents(List&lt;OrderEvent&gt; events) { return events.stream() .reduce(OrderState.empty(), this::apply, (a, b) -&gt; b); } private OrderState apply(OrderState state, OrderEvent event) { return event.applyTo(state); } </code></pre> </div> </div> </div> <p><code>OrderState.empty()</code> is the identity element or the seed. Each event is a step function that transforms one immutable state into the next. This is pure functional programming: no side effects, no shared mutable state, entirely deterministic and testable in isolation.</p> <h3>Commands as Sealed Records</h3> <p>Commands are sealed records grouped in a container interface:</p> <div contenteditable="false"> <div contenteditable="false"> <div data-code="public sealed interface Command permits Command.PlaceOrderCommand, &#xA; Command.AddItemCommand,Command.ShipOrderCommand, Command.CancelOrderCommand &#xA;{&#xA; record PlaceOrderCommand(String customerEmail) implements Command {}&#xA; record AddItemCommand(UUID orderId, String productName,&#xA; int quantity, BigDecimal price) implements Command {}&#xA;// ...&#xA;}" data-lang="text/x-java"> <pre><code lang="text/x-java">public sealed interface Command permits Command.PlaceOrderCommand, Command.AddItemCommand,Command.ShipOrderCommand, Command.CancelOrderCommand { record PlaceOrderCommand(String customerEmail) implements Command {} record AddItemCommand(UUID orderId, String productName, int quantity, BigDecimal price) implements Command {} // ... }</code></pre> </div> </div> </div> <p>\Sealed records give commands value semantics (equality by content, <code>toString</code> for free. and type safety (exhaustive pattern matching in the handler).</p> <h3>Command Results as Sealed Types</h3> <p><code>CommandResult</code> is a sealed interface expressing all possible outcomes without exceptions:</p> <div contenteditable="false"> <div contenteditable="false"> <div data-code="public sealed interface CommandResult permits CommandResult.Success,&#xA; CommandResult.InvalidState, CommandResult.NotFound,&#xA; CommandResult.ValidationError &#xA;{&#xA; record Success(UUID aggregateId) implements CommandResult {}&#xA; record InvalidState(String message) implements CommandResult {}&#xA; record NotFound(String message) implements CommandResult {}&#xA; record ValidationError(String message) implements CommandResult {}&#xA;}" data-lang="text/x-java"> <pre><code lang="text/x-java">public sealed interface CommandResult permits CommandResult.Success, CommandResult.InvalidState, CommandResult.NotFound, CommandResult.ValidationError { record Success(UUID aggregateId) implements CommandResult {} record InvalidState(String message) implements CommandResult {} record NotFound(String message) implements CommandResult {} record ValidationError(String message) implements CommandResult {} }</code></pre> </div> </div> </div> <p>The caller can <code>switch</code> on the result exhaustively. There are no checked exceptions, no nullable returns, and the type system documents all possible failure modes.</p> <h3>The Event Store</h3> <p><code>EventStore</code> is the write-side infrastructure. It does two things atomically:</p> <ol> <li>Persists the event to <code>EVENT_STORE</code> (JPA via <code>EventRepository</code>).</li> <li>Publishes the event to the Spring application event bus.</li> </ol> <div contenteditable="false"> <div contenteditable="false"> <div data-code="public void append(UUID aggregateId, String aggregateType, OrderEvent event) &#xA;{&#xA; int version = nextVersion(aggregateId);&#xA; String json = objectMapper.writeValueAsString(event);&#xA; StoredEvent entity = new StoredEvent(aggregateId, aggregateType,&#xA; version, eventType, json);&#xA; eventRepository.save(entity);&#xA; applicationEventPublisher.publishEvent(event);&#xA;} " data-lang="text/x-java"> <pre><code lang="text/x-java">public void append(UUID aggregateId, String aggregateType, OrderEvent event) { int version = nextVersion(aggregateId); String json = objectMapper.writeValueAsString(event); StoredEvent entity = new StoredEvent(aggregateId, aggregateType, version, eventType, json); eventRepository.save(entity); applicationEventPublisher.publishEvent(event); } </code></pre> </div> </div> </div> <p>Versioning provides a lightweight optimistic concurrency guard by preventing concurrent writes from corrupting the stream, based on the unique value <code>aggregateId + version</code>.</p> <h3>The Read-Side Projection</h3> <p><code>OrderProjection</code> is a Spring component that listens for domain events and updates the read model:</p> <div contenteditable="false"> <div contenteditable="false"> <div data-code="@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)&#xA;public void on(OrderEvent event) &#xA;{&#xA; OrderReadModel model = orderRepository.findByOrderId(event.orderId())&#xA; .orElse(new OrderReadModel());&#xA; // update fields from event ...&#xA; orderRepository.save(model);&#xA;}" data-lang="text/x-java"> <pre><code lang="text/x-java">@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) public void on(OrderEvent event) { OrderReadModel model = orderRepository.findByOrderId(event.orderId()) .orElse(new OrderReadModel()); // update fields from event ... orderRepository.save(model); }</code></pre> </div> </div> </div> <p><code>@TransactionalEventListener(phase = AFTER_COMMIT)</code> ensures the read model is only updated after the event store transaction commits successfully, preventing this way phantom updates if the write-side transaction rolls back.</p> <h2>Running the Application</h2> <p>Prerequisites: Java 21, Maven, Docker (for PostgreSQL via TestContainers in tests).</p> <div contenteditable="false"> <div contenteditable="false"> <div data-code="# Build and run all tests (requires Docker)&#xA;./mvnw clean package&#xA;&#xA;# Run the application (requires a running PostgreSQL instance)&#xA;./mvnw spring-boot:run&#xA;&#xA;# Skip tests&#xA;./mvnw clean package -DskipTests" data-lang="text/x-sh"> <pre><code lang="text/x-sh"># Build and run all tests (requires Docker) ./mvnw clean package # Run the application (requires a running PostgreSQL instance) ./mvnw spring-boot:run # Skip tests ./mvnw clean package -DskipTests</code></pre> </div> </div> </div> <h2>API Reference</h2> <div> <table data-end="765" data-is-last-node="" data-is-only-node="" data-start="318"> <thead data-end="349" data-start="318"> <tr data-end="349" data-start="318" width="auto"> <th data-col-size="sm" data-end="327" data-start="318">Method</th> <th data-col-size="sm" data-end="334" data-start="327">Path</th> <th data-col-size="sm" data-end="349" data-start="334">Description</th> </tr> </thead> <tbody data-end="765" data-is-last-node="" data-start="364"> <tr data-end="404" data-start="364" width="auto"> <td data-col-size="sm" data-end="371" data-start="364">POST</td> <td data-col-size="sm" data-end="383" data-start="371"><code data-end="382" data-start="373">/orders</code></td> <td data-col-size="sm" data-end="404" data-start="383">Place a new order</td> </tr> <tr data-end="462" data-start="405" width="auto"> <td data-col-size="sm" data-end="412" data-start="405">POST</td> <td data-col-size="sm" data-end="435" data-start="412"><code data-end="434" data-start="414">/orders/{id}/items</code></td> <td data-col-size="sm" data-end="462" data-start="435">Add an item to an order</td> </tr> <tr data-end="509" data-start="463" width="auto"> <td data-col-size="sm" data-end="470" data-start="463">POST</td> <td data-col-size="sm" data-end="492" data-start="470"><code data-end="491" data-start="472">/orders/{id}/ship</code></td> <td data-col-size="sm" data-end="509" data-start="492">Ship an order</td> </tr> <tr data-end="560" data-start="510" width="auto"> <td data-col-size="sm" data-end="517" data-start="510">POST</td> <td data-col-size="sm" data-end="541" data-start="517"><code data-end="540" data-start="519">/orders/{id}/cancel</code></td> <td data-col-size="sm" data-end="560" data-start="541">Cancel an order</td> </tr> <tr data-end="625" data-start="561" width="auto"> <td data-col-size="sm" data-end="567" data-start="561">GET</td> <td data-col-size="sm" data-end="584" data-start="567"><code data-end="583" data-start="569">/orders/{id}</code></td> <td data-col-size="sm" data-end="625" data-start="584">Reconstruct current state from events</td> </tr> <tr data-end="690" data-start="626" width="auto"> <td data-col-size="sm" data-end="632" data-start="626">GET</td> <td data-col-size="sm" data-end="656" data-start="632"><code data-end="655" data-start="634">/orders/{id}/events</code></td> <td data-col-size="sm" data-end="690" data-start="656">Retrieve the full event stream</td> </tr> <tr data-end="765" data-is-last-node="" data-start="691" width="auto"> <td data-col-size="sm" data-end="697" data-start="691">GET</td> <td data-col-size="sm" data-end="725" data-start="697"><code data-end="724" data-start="699">/orders/{id}/read-model</code></td> <td data-col-size="sm" data-end="765" data-is-last-node="" data-start="725">Retrieve the denormalized read model</td> </tr> </tbody> </table> </div> <p><br></br> Place an order:</p> <p><br></br> Add an item:</p> <div contenteditable="false"> <div contenteditable="false"> <div data-code="POST /orders/{id}/items&#xA;{ &quot;productName&quot;: &quot;Widget&quot;, &quot;quantity&quot;: 3, &quot;price&quot;: 9.99 }" data-lang="application/json"> <pre><code lang="application/json">POST /orders/{id}/items { "productName": "Widget", "quantity": 3, "price": 9.99 }</code></pre> </div> </div> </div> <p><br></br> Ship an order:</p> <div contenteditable="false"> <div contenteditable="false"> <div data-code="POST /orders/{id}/ship&#xA;{ &quot;trackingNumber&quot;: &quot;TRACK-001&quot; }" data-lang="application/json"> <pre><code lang="application/json">POST /orders/{id}/ship { "trackingNumber": "TRACK-001" }</code></pre> </div> </div> </div> </div> </div><p>Opinions expressed by DZone contributors are their own.</p><br><span style='font: #ff0000'>Generated by <a href='https://github.com/andreskrey/readability.php'>Readability.php</a>.</span> Tue, 26 May 2026 15:30:13 GMT https://dzone.com/articles/3653976 Nicolas Duminil Detecting Bugs and Vulnerabilities in Java With SonarQube https://dzone.com/articles/detecting-bugs-vulnerabilities-java-sonarqube <img src="https://dz2cdn1.dzone.com/storage/article-thumb/18990885-thumb.jpg" /><h1>Detect Bugs and Vulnerabilities in Java With SonarQube</h1><div> <div><p>The security audit report landed unexpectedly. It highlighted a critical vulnerability in our payment processing module. We had passed all unit tests. We had passed all integration tests. The code review looked clean. Yet the auditors found a hardcoded API key hidden in a utility class. This key allowed access to our third-party payment gateway. Anyone with access to the repository could see it. We were lucky the auditors found it before a malicious actor did. This incident was a wake-up call. We realized manual code reviews were not enough. We needed automated static analysis. We needed SonarQube.</p> <p>In this article, I will share how we integrated SonarQube into our Java development workflow. I will explain the specific rules that exposed our vulnerabilities. I will detail how we configured quality gates to prevent future regressions. This is not a generic installation guide. It is a record of how we shifted security left in our pipeline. Static analysis is not just about finding bugs. It is about building a culture of quality.</p> <h2><strong>The Blind Spot in Our Testing</strong></h2> <p>Our testing strategy relied heavily on functional correctness. We wrote tests to ensure features worked as expected. We did not write tests to ensure secrets were absent. We did not write tests to check for <a href="https://dzone.com/articles/what-is-sql-injection-and-how-can-it-be-avoided-1">SQL injection patterns</a>. These security concerns fell outside the scope of standard unit testing. Developers focused on delivering features quickly. Security was an afterthought. This mindset created technical debt. It also created risk.</p> <p>The hardcoded key incident showed us the gap. The developer who wrote the code intended to replace the key later. They forgot. The code merged to the main branch. It reached production. We rotated the key immediately, but the exposure window was dangerous. We needed a safety net. We needed a tool that could scan every commit for these patterns. SonarQube offered this capability.</p> <h2><strong>Integrating SonarQube into CI/CD</strong></h2> <p>We chose to integrate SonarQube into our Jenkins pipeline. This ensured every build was analyzed. We did not want developers to run the scan manually. Manual steps get skipped. Automation enforces consistency. We added a stage to our Jenkinsfile specifically for static analysis.</p> <p><img data-creationdate="1772943233356" data-creationdateformatted="03/08/2026 04:13 AM" data-id="18927823" data-image="true" data-mimetype="image/png" data-modificationdate="null" data-name="1772943232828.png" data-new="false" data-size="7685" data-sizeformatted="7.7 kB" data-src="https://dz2cdn1.dzone.com/storage/temp/18927823-1772943232828.png" data-type="temp" data-url="https://dz2cdn1.dzone.com/storage/temp/18927823-1772943232828.png" width="469"></img></p> <p>This configuration triggered the Maven Sonar plugin. It sent code metrics to the SonarQube server. The server analyzed the code against a set of rules. These rules covered bugs and vulnerabilities, and code smells. The analysis happened in parallel with our tests. It added minimal time to the build. The results were available immediately after deployment.</p> <h2><strong>The Rule That Caught Us</strong></h2> <p>SonarQube has thousands of rules. We did not enable all of them initially. We started with the Sonar Way profile. This profile includes a curated set of essential rules. One specific rule flagged our hardcoded credential issue. The rule key is java:S2068. It searches for strings that look like passwords or keys.</p> <p>Here is the code that triggered the alert.</p> <p><img alt="Code that triggered" data-creationdate="1773258504685" data-creationdateformatted="03/11/2026 07:48 PM" data-id="18933877" data-image="true" data-mimetype="image/png" data-modificationdate="null" data-name="1773258503890.png" data-new="false" data-size="59768" data-sizeformatted="59.8 kB" data-src="https://dz2cdn1.dzone.com/storage/temp/18933877-1773258503890.png" data-type="temp" data-url="https://dz2cdn1.dzone.com/storage/temp/18933877-1773258503890.png"></img></p> <p>SonarQube marked this line as a critical vulnerability. It recognized the pattern of a secret key. It suggested moving the value to an environment variable. This feedback was immediate. The developer saw the issue in the pull request dashboard. They fixed it before merging. This prevented the vulnerability from reaching production.</p> <h2><strong>Configuring Quality Gates</strong></h2> <p>Finding issues is only half the battle. You must prevent bad code from merging. We configured quality gates in SonarQube. A quality gate defines the conditions for a build to pass. We set strict thresholds for our main branch.</p> <ol start="1" type="1"> <li><strong>New bugs</strong>: Must be zero.</li> <li><strong>New vulnerabilities</strong>: Must be zero.</li> <li><strong>New security hotspots</strong>: Must be reviewed.</li> <li><strong>Code coverage:</strong> Must be above 80 percent.</li> </ol> <p>If a pull request failed these conditions, the pipeline failed. This gave us leverage. We could tell developers they could not merge until the issues were resolved. It enforced accountability. It also prevented technical debt from accumulating. We treated security violations like compilation errors. They blocked progress until fixed.</p> <h2><strong>Handling False Positives</strong></h2> <p>No tool is perfect. SonarQube sometimes flags safe code as risky. We encountered this with utility methods that handled strings resembling passwords. The tool raised alarms unnecessarily. This creates noise. Noise leads to alert fatigue. Developers start ignoring the warnings.</p> <p>We learned to tune the rules. We marked specific issues as false positives in the dashboard. We added comments to the code to explain why the pattern was safe.</p> <p><img data-creationdate="1773258521587" data-creationdateformatted="03/11/2026 07:48 PM" data-id="18933878" data-image="true" data-mimetype="image/png" data-modificationdate="null" data-name="1773258520781.png" data-new="false" data-size="35673" data-sizeformatted="35.7 kB" data-src="https://dz2cdn1.dzone.com/storage/temp/18933878-1773258520781.png" data-type="temp" data-url="https://dz2cdn1.dzone.com/storage/temp/18933878-1773258520781.png"></img></p> <p>This annotation told SonarQube to ignore the rule for this line. We used this sparingly. We required a justification for every suppression. This ensured we did not hide real vulnerabilities. We reviewed suppressions during code review. This kept the process honest.</p> <h2><strong>Security Hotspots vs. Vulnerabilities</strong></h2> <p>SonarQube distinguishes between vulnerabilities and security hotspots. Vulnerabilities are confirmed issues. Hotspots are code that needs manual review. This distinction is important. Not every risky pattern is a bug. Some patterns require context to evaluate.</p> <p>We established a process for reviewing hotspots. A senior engineer reviewed each hotspot. They determined if it was a real risk. If it were, they converted it to a vulnerability. If not, they marked it as Safe. This human-in-the-loop approach reduced false positives. It also educated developers on security patterns. They learned why certain code was risky. This improved overall code quality over time.</p> <h2><strong>Lessons Learned and Best Practices</strong></h2> <p>Our journey with SonarQube taught us several lessons. We incorporated these into our development standards.</p> <ol start="1" type="1"> <li><strong>Fail fast</strong>: Run analysis on every commit. Do not wait for nightly builds. Immediate feedback helps developers fix issues while context is fresh.</li> <li><strong>Start small</strong>: Do not enable all rules at once. Start with the critical security rules. Add more rules gradually. This prevents overwhelming the team.</li> <li><strong>Fix the root cause</strong>: Do not just suppress warnings. Understand why the rule exists. Fix the underlying code pattern. This prevents similar issues elsewhere.</li> <li><strong>Track trends</strong>: Monitor the technical debt ratio over time. It should decrease. If it increases, investigate why. Are we rushing features? Are we skipping reviews?</li> <li><strong>Educate the team</strong>: Tools do not replace knowledge. Hold sessions on secure coding practices. Explain the SonarQube rules. Help developers understand the why behind the rules.</li> <li><strong>Integrate with IDE</strong>: Install the SonarLint plugin in IntelliJ or Eclipse. This brings analysis to the developer workstation. They see issues before committing code. This is the fastest feedback loop.</li> <li><strong>Secure the server</strong>: SonarQube itself holds sensitive data. Secure the server with authentication. Restrict access to project settings. Rotate admin passwords regularly. Do not expose the dashboard to the public internet.</li> </ol> <h2><strong>Conclusion</strong></h2> <p>Integrating SonarQube transformed our approach to Java development. We moved from reactive security to proactive quality assurance. The hardcoded key incident never happened again. The tool caught similar patterns early in the cycle. Developers became more aware of security implications. They wrote cleaner code. They respected the quality gates.</p> <p>Static analysis is not a silver bullet. It does not catch logic errors or business rule violations. It does not replace penetration testing. However, it is a powerful layer in a defense-in-depth strategy. It catches the low-hanging fruit automatically. This frees up security engineers to focus on complex threats.</p> <p>We continue to refine our rules and thresholds. We add custom rules for our specific domain logic. We treat the SonarQube dashboard as a health monitor for our codebase. It tells us when our code is getting sick. We treat it before the patient crashes. Java provides a robust ecosystem for building secure applications. SonarQube helps us uphold that standard. Happy coding and keep your code clean.</p></div> </div><p>Opinions expressed by DZone contributors are their own.</p><br><span style='font: #ff0000'>Generated by <a href='https://github.com/andreskrey/readability.php'>Readability.php</a>.</span> Wed, 20 May 2026 18:00:00 GMT https://dzone.com/articles/3641714 Ramya vani Rayala Introduction to Tactical DDD With Java: Steps to Build Semantic Code https://dzone.com/articles/tactical-ddd-with-java <img src="https://dz2cdn1.dzone.com/storage/article-thumb/19025106-thumb.jpg" /><h1>Steps to Build Semantic Code</h1><div> <div><p>Modern software systems rarely fail due to poor coding skills. Most failures occur when teams lose sight of the business problem they are addressing. As systems evolve, requirements shift, teams expand, and new integrations are added, codebases often become collections of technical decisions that lack business context. Classes become generic managers and services, methods devolve into procedural scripts, and communication between developers and domain experts diminishes. <a href="https://dzone.com/articles/tactical-domain-driven-design-bringing-strategy-to">Tactical Domain-Driven Design</a> (DDD) addresses this issue by emphasizing software that directly reflects business language in code, rather than focusing solely on infrastructure or frameworks.</p> <p>The term “semantic” comes from the Greek semantikos, meaning “significant” or “meaningful,” which is central to Tactical DDD. The objective is not just to reorganize classes, but to ensure code communicates intent clearly to both engineers and business experts. In modern Java systems, where complexity increases due to distributed architectures, integrations, and ongoing business changes, this clarity is essential for long-term maintainability. </p> <p>Tactical DDD provides practical patterns, such as entities, value objects, aggregates, repositories, factories, and domain services, to preserve codebase meaning and manage complexity. This article will examine these patterns step by step using <a href="https://dzone.com/articles/java-a-time-tested-programming-language-still-goin">Java</a> and a soccer championship scenario to show how semantic code improves system understanding, evolution, and maintenance.</p> <h2>Entity</h2> <p>Before applying Tactical DDD patterns, it is important to recognize that they should not be the starting point of the design process. A common mistake in software projects is to begin with entities, repositories, and aggregates without first understanding the business. Tactical patterns serve as implementation tools, not discovery tools. <a href="https://dzone.com/articles/strategic-domain-driven-design">Strategic DDD</a> should begin with defining domain boundaries, the ubiquitous language, and the business context. Only after clarifying the problem space should you translate that understanding into code using tactical patterns.</p> <p>An Entity is a core Tactical DDD pattern. The term originates from the Latin ens, meaning “being” or “existing thing.” In software design, it refers to maintaining its identity throughout its lifecycle. An entity is defined not by its current attributes, but by the business’s recognition of it as the same conceptual object over time. Entities are useful when the domain must track the lifecycle of something important to the business.</p> <p>In a soccer championship, a player is a clear example of an entity. A player may change teams, positions, salary, or statistics during a career, but the system continues to recognize the player as the same individual within the domain. Therefore, identity is more important than changes to attributes. The following Java class illustrates this concept:</p> <div contenteditable="false"> <div contenteditable="false"> <div data-code="import java.util.UUID;&#xA;&#xA;public class Player {&#xA;&#xA; private UUID id;&#xA;&#xA; private String name;&#xA;&#xA; private Position position;&#xA;&#xA; public Player(UUID id, String name, Position position) {&#xA; this.id = id;&#xA; this.name = name;&#xA; this.position = position;&#xA; }&#xA;}" data-lang="text/x-java"> <pre><code lang="text/x-java">import java.util.UUID; public class Player { private UUID id; private String name; private Position position; public Player(UUID id, String name, Position position) { this.id = id; this.name = name; this.position = position; } }</code></pre> </div> </div> </div> <p>The Player class demonstrates the Entity pattern by using a unique identifier in the id field. This identifier enables the application to distinguish one player from another, regardless of changes to other attributes. While name and position may change, the identity remains constant. This characteristic defines the object as an entity rather than a simple data structure or value object.</p> <h2>Value Object</h2> <p>Entities are defined by identity, but not all domain concepts require lifecycle tracking or unique identification. Many business concepts describe characteristics, measurements, classifications, or immutable meanings. The Value Object pattern addresses these cases. Here, “value” means the object is defined solely by its attributes, not by identity. In Tactical DDD, value objects reduce the need for primitives and clarify the domain language within the codebase.</p> <p>A Value Object is an immutable object that represents a descriptive aspect of the domain. Unlike entities, two value objects with identical values are considered the same. Value objects are often used for concepts such as money, addresses, coordinates, statuses, measurements, or classifications. Their primary purpose is to improve semantic clarity and encapsulate domain rules for specific concepts.</p> <p>In a soccer championship scenario, player position is a good example of a value object because the application does not need to track its lifecycle. The domain is concerned only with the meaning of the value. The following Java enum illustrates this concept:</p> <div contenteditable="false"> <div contenteditable="false"> <div data-code="public enum Position {&#xA; GOALKEEPER,&#xA; DEFENDER,&#xA; MIDFIELDER,&#xA; FORWARD&#xA;}" data-lang="text/x-java"> <pre><code lang="text/x-java">public enum Position { GOALKEEPER, DEFENDER, MIDFIELDER, FORWARD }</code></pre> </div> </div> </div> <p>The Position enum applies the Value Object pattern by representing a business classification rather than using primitive strings throughout the codebase. By using explicit types instead of raw text, such as "forward" or "goalkeeper", the application improves readability, reduces invalid states, and reinforces a shared language between developers and domain experts.</p> <h2>Factory</h2> <p>As domain models evolve, object creation often requires more than calling a constructor. Business rules, validations, default values, and initialization steps may spread into controllers, services, or application layers, leading to duplication and fragmented domain knowledge. The Factory pattern centralizes object creation, ensuring new domain objects are valid and meaningful.</p> <p>A Factory is a Tactical DDD pattern that encapsulates the creation logic of entities or aggregates. Its purpose is not just to “hide the constructor,” but to express domain intent during object creation. Originating from manufacturing, factories ensure objects are assembled correctly before use. In software, this approach maintains consistency and enforces business rules during instantiation.</p> <p>In a soccer championship scenario, creating a player requires more than allocating memory. A new forward player must have the correct position and a unique identity. Centralizing this logic in a factory prevents duplication across the system.</p> <div contenteditable="false"> <div contenteditable="false"> <div data-code="import java.util.UUID;&#xA;&#xA;public class PlayerFactory {&#xA;&#xA; public Player createFoward(String name) {&#xA; return new Player(&#xA; UUID.randomUUID(),&#xA; name,&#xA; Position.FORWARD&#xA; );&#xA; }&#xA;}" data-lang="text/x-java"> <pre><code lang="text/x-java">import java.util.UUID; public class PlayerFactory { public Player createFoward(String name) { return new Player( UUID.randomUUID(), name, Position.FORWARD ); } }</code></pre> </div> </div> </div> <p>PlayerFactory implements the Factory pattern by encapsulating the details of Player creation. The application layer does not need to manage identifier generation or position assignment for a forward player. The method name communicates business intent, allowing the code to express a meaningful domain operation rather than low-level construction details.</p> <h2>Aggregate Root</h2> <p>As systems scale, maintaining consistency between related entities becomes more challenging. Without clear boundaries, business rules can spread across services, repositories, and transactions. Tactical DDD addresses this by defining explicit consistency boundaries within the domain model. Aggregate and Aggregate Root patterns are essential to this approach.</p> <p>An Aggregate is a group of related entities and value objects managed as a single consistency boundary. The Aggregate Root serves as the main entry point, coordinating and protecting the aggregate’s internal state. In practice, the aggregate root ensures controlled modifications and maintains business rule consistency during state changes.</p> <p>In a soccer championship scenario, a team acts as an aggregate root by managing the lifecycle and consistency of its players. The application should not modify the player collection directly; all changes should occur through the team’s defined behaviors:</p> <div contenteditable="false"> <div contenteditable="false"> <div data-code="import java.util.Collections;&#xA;import java.util.List;&#xA;&#xA;public class Team {&#xA;&#xA; private TeamId id;&#xA;&#xA; private String name;&#xA;&#xA; private List&lt;Player> players;&#xA;&#xA; public Team(TeamId id, String name, List&lt;Player> players) {&#xA; this.id = id;&#xA; this.name = name;&#xA; this.players = players;&#xA; }&#xA;&#xA; public List&lt;Player> getPlayers() {&#xA; return Collections.unmodifiableList(players);&#xA; }&#xA;&#xA; public void remove(Player player) {&#xA; players.remove(player);&#xA; }&#xA;&#xA; public void add(Player player) {&#xA; players.add(player);&#xA; }&#xA;}" data-lang="text/x-java"> <pre><code lang="text/x-java">import java.util.Collections; import java.util.List; public class Team { private TeamId id; private String name; private List&lt;Player&gt; players; public Team(TeamId id, String name, List&lt;Player&gt; players) { this.id = id; this.name = name; this.players = players; } public List&lt;Player&gt; getPlayers() { return Collections.unmodifiableList(players); } public void remove(Player player) { players.remove(player); } public void add(Player player) { players.add(player); } }</code></pre> </div> </div> </div> <p>The Team class implements the Aggregate Root pattern, managing access to its player collection. Rather than allowing direct modification, it has add and remove. This method enables business rules to evolve and ensures consistency across the application. The aggregate root safeguards the domain boundary and maintains the cycle.</p> <h2>Repository</h2> <p>One of the biggest challenges in enterprise applications is avoiding tight coupling between business logic and persistence concerns. Over time, SQL queries, database operations, caching logic, and infrastructure details can start leaking into the domain layer, making the code harder to maintain and evolve. Tactical DDD addresses this problem with the Repository pattern, which provides a collection-like abstraction for managing aggregates.</p> <p>The term “repository” originates from the Latin repositorium, meaning “a place where things are stored.” In Domain-Driven Design, a repository is not merely a DAO or a utility class for executing queries. Its primary goal is to provide access to aggregates while hiding infrastructure complexity from the domain model. A repository allows the application to work with domain concepts instead of persistence mechanisms, preserving the separation between business logic and technical implementation.</p> <p>In the soccer championship scenario, the application needs a mechanism to persist and retrieve teams without exposing database details to the business flow. Since Team acts as the aggregate root, the repository is responsible for managing it as a consistency boundary:</p> <div contenteditable="false"> <div contenteditable="false"> <div data-code="public interface TeamRepository {&#xA;&#xA; Team save(Team team);&#xA;}" data-lang="text/x-java"> <pre><code lang="text/x-java">public interface TeamRepository { Team save(Team team); }</code></pre> </div> </div> </div> <p>The TeamRepository applies the Repository pattern by abstracting persistence operations behind a domain-oriented contract. The application layer does not need to know whether the data is stored in PostgreSQL, MongoDB, Redis, or another technology. More importantly, the repository communicates the business intention directly through the aggregate itself. Instead of manipulating tables or records, the code works with meaningful domain concepts such as Team, preserving the semantic clarity of the model and reducing coupling between the domain and infrastructure layers.</p> <h2>Domain Service</h2> <p>Not every business operation fits within an entity or aggregate root. As the domain evolves, some rules involve multiple entities or coordination logic that do not belong to a single object. Assigning these responsibilities to entities can lead to bloated models and reduced cohesion. Tactical DDD addresses this with the Domain Service pattern.</p> <p>A Domain Service contains business logic that does not belong to a specific entity or value object, but remains part of the domain model. Its role is to execute meaningful business operations involving multiple domain objects, not to handle technical orchestration or infrastructure. In DDD, a service encapsulates domain behavior across aggregates while maintaining the model’s clarity.</p> <p>In the soccer championship scenario, transferring a player between teams is a business operation involving multiple aggregates. The responsibility does not belong exclusively to the player or to a single team. Instead, the operation represents a domain action coordinating both source and destination teams:</p> <div contenteditable="false"> <div contenteditable="false"> <div data-code="public class TransferService {&#xA;&#xA; public void transfer(&#xA; Player player,&#xA; Team source,&#xA; Team destination) {&#xA;&#xA; source.remove(player);&#xA; destination.add(player);&#xA; }&#xA;}" data-lang="text/x-java"> <pre><code lang="text/x-java">public class TransferService { public void transfer( Player player, Team source, Team destination) { source.remove(player); destination.add(player); } }</code></pre> </div> </div> </div> <p>TransferService implements the Domain Service pattern by encapsulating the business logic for transferring a player between teams. This service expresses the domain concept directly, rather than spreading logic across controllers or application layers. The method communicates business intent clearly using the domain’s ubiquitous language. Instead of exposing low-level details, the code now reflects a meaningful operation recognized by both developers and business experts: transferring a player during the championship lifecycle.</p> <h2>Domain Event</h2> <p>In complex systems, important business actions rarely affect only a single part of the application. A change in one domain often triggers reactions in other contexts, such as notifications, analytics, integrations, auditing, or external workflows. Directly coupling these concerns creates rigid architectures in which every new requirement increases dependencies across the system. Tactical DDD addresses this challenge with the Domain Event pattern.</p> <p>A Domain Event represents an important event that has already occurred within the business domain. The emphasis on the past tense is intentional because events describe facts, not commands or intentions. The term “event” originates from the Latin eventus, meaning “outcome” or “occurrence.” In Domain-Driven Design, domain events allow systems to communicate meaningful business changes while reducing coupling between components and bounded contexts. Instead of directly invoking every dependent operation, the domain publishes events that other parts of the system may react to independently.</p> <p>In the soccer championship scenario, hiring a new player is an important business occurrence that other parts of the system may care about. The championship may want to notify fans, update statistics, trigger merchandising actions, or synchronize with external systems. Instead of embedding all these responsibilities directly into the transfer logic, the application can represent the occurrence explicitly through a domain event:</p> <div contenteditable="false"> <div contenteditable="false"> <div data-code="public record NewSoccerHired(&#xA; Team team,&#xA; Player player) {&#xA;}" data-lang="text/x-java"> <pre><code lang="text/x-java">public record NewSoccerHired( Team team, Player player) { }</code></pre> </div> </div> </div> <p>The event can then be published once the business operation finishes successfully:</p> <div contenteditable="false"> <div contenteditable="false"> <div data-code="eventPublisher.publish(&#xA; new NewSoccerHired(destination, player)&#xA;);" data-lang="text/x-java"> <pre><code lang="text/x-java">eventPublisher.publish( new NewSoccerHired(destination, player) );</code></pre> </div> </div> </div> <p>The NewSoccerHired record applies the Domain Event pattern by representing a meaningful business fact inside the domain model. Instead of tightly coupling multiple responsibilities, the system now exposes a semantic business occurrence that other parts of the architecture can react to independently. This approach improves extensibility, reduces direct dependencies, and preserves the ubiquitous language across the application lifecycle.</p> <h2>Application Service</h2> <p>As systems evolve, business operations often require coordination across domain components, persistence, and integration points. Without a clear orchestration layer, this logic may spread across controllers, APIs, and infrastructure classes, resulting in tightly coupled, hard-to-maintain applications. Tactical DDD addresses this with the Application Service pattern.</p> <p>An Application Service orchestrates use cases and coordinates domain operations. Unlike domain services, which encapsulate business rules, an application service manages the execution flow of business actions. In DDD, it serves as the coordination layer, connecting repositories, domain operations, and external interactions, while keeping the domain model focused on business behavior.</p> <p>In a soccer championship scenario, transferring a player between teams requires more than one business rule. This operation coordinates transfer logic, persistence, and event publication. The following class centralizes this orchestration in a single use case:</p> <div contenteditable="false"> <div contenteditable="false"> <div data-code="public class TransferPlayerUserCase {&#xA;&#xA; private final TeamRepository teamRepository;&#xA; private final TransferService transferService;&#xA; private final EventPublisher eventPublisher;&#xA;&#xA; public TransferPlayerUserCase(&#xA; TeamRepository teamRepository,&#xA; TransferService transferService,&#xA; EventPublisher eventPublisher) {&#xA; this.teamRepository = teamRepository;&#xA; this.transferService = transferService;&#xA; this.eventPublisher = eventPublisher;&#xA; }&#xA;&#xA; public void execute(&#xA; Player player,&#xA; Team source,&#xA; Team destination) {&#xA;&#xA; transferService.transfer(player, source, destination);&#xA;&#xA; teamRepository.save(source);&#xA; teamRepository.save(destination);&#xA;&#xA; eventPublisher.publish(&#xA; new NewSoccerHired(destination, player)&#xA; );&#xA; }&#xA;}" data-lang="text/x-java"> <pre><code lang="text/x-java">public class TransferPlayerUserCase { private final TeamRepository teamRepository; private final TransferService transferService; private final EventPublisher eventPublisher; public TransferPlayerUserCase( TeamRepository teamRepository, TransferService transferService, EventPublisher eventPublisher) { this.teamRepository = teamRepository; this.transferService = transferService; this.eventPublisher = eventPublisher; } public void execute( Player player, Team source, Team destination) { transferService.transfer(player, source, destination); teamRepository.save(source); teamRepository.save(destination); eventPublisher.publish( new NewSoccerHired(destination, player) ); } }</code></pre> </div> </div> </div> <p>The TransferPlayerUserCase demonstrates the Application Service pattern by orchestrating the entire player transfer process. Rather than placing orchestration logic in controllers or entities, this class coordinates domain operations, persistence, and event publication within a single workflow. The method represents a meaningful business action within the domain: transferring a player between teams during the championship.</p> <h2>Conclusion</h2> <p>Tactical Domain-Driven Design does not aim to add unnecessary complexity or apply patterns indiscriminately. Its purpose is to help engineers build software that clearly communicates business meaning through code. By introducing concepts such as entities, value objects, factories, aggregates, repositories, domain services, domain events, and application services, developers create systems that are easier to understand, maintain, and adapt as business needs evolve. Tactical DDD also bridges the gap between technical and business perspectives, making code a semantic representation of the domain.</p> <p>This article introduces core Tactical DDD patterns using Java and a soccer championship scenario. The aim is not to cover every aspect of Domain-Driven Design, but to show how these patterns help build expressive and maintainable systems. As projects become more complex, preserving business meaning within the codebase is increasingly important, especially in modern distributed architectures where technical complexity can obscure domain language.</p> <p><span contenteditable="false" draggable="true"><span contenteditable="false"><iframe allowfullscreen="" frameborder="0" height="490" loading="lazy" src="https://www.youtube.com/embed/XUQfA2pqZ2c?&amp;wmode=opaque" width="780"></iframe></span></span><br></br></p></div> </div><p>Opinions expressed by DZone contributors are their own.</p><br><span style='font: #ff0000'>Generated by <a href='https://github.com/andreskrey/readability.php'>Readability.php</a>.</span> Wed, 20 May 2026 15:30:00 GMT https://dzone.com/articles/3654633 Otavio Santana AI Agents in Java: Architecting Intelligent Health Data Systems https://dzone.com/articles/ai-agents-in-java-architecting-intelligent-health <img src="https://dz2cdn1.dzone.com/storage/article-thumb/18991570-thumb.jpg" /><h1>Designing Intelligent Health Data Systems</h1><article> <div> <div> <div x-data="engagementModal(articleId)"> <span id="activity-like-icon" x-on:click="updateStore()"></span> <span x-cloak="" x-on:click="open()"> <span>Likes</span> <span id="activity-like-counter">(0)</span> </span> <template x-teleport="body"> <div x-on:keyup.escape.window="close()" x-show="$store.article.engagement.open &amp;&amp; nodeId === $store.article.id"> <div> <div x-on:click.outside="close()"> <div> <div x-show="isDataUnavailable()"> <div x-show="!$store.article.engagement.unauthorized"> <p>There are no likes...yet! 👀</p> <p>Be the first to like this post!</p> </div> <div x-show="$store.article.engagement.unauthorized"> <p>It looks like you're not logged in.</p> <div><a href="http://java.dzone.com/users/login.html">Sign in</a><p> to see who liked this post!</p></div> </div> </div> </div> </div> </div> </div> </template> </div> </div> <div> <p>Join the DZone community and get the full member experience.</p> <a href="http://java.dzone.com/static/registration.html" id="article-signin-prompt">Join For Free</a> </div> <div> <div><h2 data-end="688" data-start="175"><strong data-end="197" data-start="175">Executive Summary</strong></h2> <p data-end="688" data-start="175">Modern <a href="https://dzone.com/articles/advancements-in-ai-for-health-data-analysis">health data analytics increasingly leverage AI</a> agent software components that process information and make decisions, often using large language models (LLMs) or machine learning models. In Java, you can build agentic systems using libraries like DJL (Deep Java Library), <a href="https://dzone.com/articles/aspects-to-advisors-modular-cross-cutting-spring-ai">Spring AI</a>, or by integrating LLM APIs. This document includes Maven setup, minimal Spring Boot code (controllers and services), a simple agent example, diagrams, and a comparison of different agent approaches.</p> <h2 data-end="688" data-start="175">Flowchart</h2> <p><img alt="Flowchart image" data-creationdate="1776425104400" data-creationdateformatted="04/17/2026 11:25 AM" data-id="18991552" data-image="true" data-mimetype="image/png" data-modificationdate="null" data-name="mermaid-diagram.png" data-new="false" data-size="79350" data-sizeformatted="79.4 kB" data-src="https://dz2cdn1.dzone.com/storage/temp/18991552-mermaid-diagram.png" data-type="temp" data-url="https://dz2cdn1.dzone.com/storage/temp/18991552-mermaid-diagram.png"></img></p> <h2 data-end="1036" data-section-id="xjt4da" data-start="1014">Maven Dependencies</h2> <p data-end="1133" data-start="1038">Define the necessary dependencies in <code data-end="1084" data-start="1075">pom.xml</code> (Spring Web, Validation, DJL, OpenAI SDK, etc.):</p> <div contenteditable="false"> <div contenteditable="false"> <div data-code="&lt;dependencies>&#xA;&#xA; &lt;!-- Spring Boot Web for API -->&#xA; &lt;dependency>&#xA; &lt;groupId>org.springframework.boot&lt;/groupId>&#xA; &lt;artifactId>spring-boot-starter-web&lt;/artifactId>&#xA; &lt;/dependency>&#xA;&#xA; &lt;!-- Validation (optional) -->&#xA; &lt;dependency>&#xA; &lt;groupId>org.springframework.boot&lt;/groupId>&#xA; &lt;artifactId>spring-boot-starter-validation&lt;/artifactId>&#xA; &lt;/dependency>&#xA;&#xA; &lt;!-- OpenAI Java SDK (LLM API) -->&#xA; &lt;dependency>&#xA; &lt;groupId>com.openai&lt;/groupId>&#xA; &lt;artifactId>openai-java&lt;/artifactId>&#xA; &lt;version>4.0.0&lt;/version>&#xA; &lt;/dependency>&#xA;&#xA; &lt;!-- DJL for local ML inference -->&#xA; &lt;dependency>&#xA; &lt;groupId>ai.djl&lt;/groupId>&#xA; &lt;artifactId>api&lt;/artifactId>&#xA; &lt;version>0.36.0&lt;/version>&#xA; &lt;/dependency>&#xA;&#xA; &lt;dependency>&#xA; &lt;groupId>ai.djl.mxnet&lt;/groupId>&#xA; &lt;artifactId>mxnet-engine&lt;/artifactId>&#xA; &lt;version>0.36.0&lt;/version>&#xA; &lt;/dependency>&#xA;&#xA; &lt;!-- (Optional) Spring AI -->&#xA; &lt;dependency>&#xA; &lt;groupId>org.springframework.experimental&lt;/groupId>&#xA; &lt;artifactId>spring-boot-starter-ai&lt;/artifactId>&#xA; &lt;version>0.0.1&lt;/version>&#xA; &lt;/dependency>&#xA;&#xA;&lt;/dependencies>" data-lang="application/xml"> <pre><code lang="application/xml">&lt;dependencies&gt; &lt;!-- Spring Boot Web for API --&gt; &lt;dependency&gt; &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt; &lt;artifactId&gt;spring-boot-starter-web&lt;/artifactId&gt; &lt;/dependency&gt; &lt;!-- Validation (optional) --&gt; &lt;dependency&gt; &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt; &lt;artifactId&gt;spring-boot-starter-validation&lt;/artifactId&gt; &lt;/dependency&gt; &lt;!-- OpenAI Java SDK (LLM API) --&gt; &lt;dependency&gt; &lt;groupId&gt;com.openai&lt;/groupId&gt; &lt;artifactId&gt;openai-java&lt;/artifactId&gt; &lt;version&gt;4.0.0&lt;/version&gt; &lt;/dependency&gt; &lt;!-- DJL for local ML inference --&gt; &lt;dependency&gt; &lt;groupId&gt;ai.djl&lt;/groupId&gt; &lt;artifactId&gt;api&lt;/artifactId&gt; &lt;version&gt;0.36.0&lt;/version&gt; &lt;/dependency&gt; &lt;dependency&gt; &lt;groupId&gt;ai.djl.mxnet&lt;/groupId&gt; &lt;artifactId&gt;mxnet-engine&lt;/artifactId&gt; &lt;version&gt;0.36.0&lt;/version&gt; &lt;/dependency&gt; &lt;!-- (Optional) Spring AI --&gt; &lt;dependency&gt; &lt;groupId&gt;org.springframework.experimental&lt;/groupId&gt; &lt;artifactId&gt;spring-boot-starter-ai&lt;/artifactId&gt; &lt;version&gt;0.0.1&lt;/version&gt; &lt;/dependency&gt; &lt;/dependencies&gt;</code></pre> </div> </div> </div> <p data-end="2329" data-start="2197">This setup assumes you will use the OpenAI Java SDK and DJL. Replace the <code data-end="2294" data-start="2270">spring-boot-starter-ai</code> version with the latest as needed.</p> <h3 data-end="2371" data-section-id="a7salc" data-start="2336">1. Domain Model &amp; Configuration</h3> <p data-end="2467" data-start="2373">Define simple data classes for health analysis requests and responses in <code data-end="2466" data-start="2446">com.example.health</code>:</p> <div contenteditable="false"> <div contenteditable="false"> <div data-code="package com.example.health;&#xA;&#xA;public record VitalSigns(&#xA; double temperature,&#xA; double bloodPressure,&#xA; int heartRate&#xA;) {}" data-lang="text/x-java"> <pre><code lang="text/x-java">package com.example.health; public record VitalSigns( double temperature, double bloodPressure, int heartRate ) {}</code></pre> </div> </div> </div> <p data-end="2747" data-start="2610">This example record holds patient vitals. You can also add a Spring <code data-end="2694" data-start="2678">@Configuration</code> if needed, for example, to configure the DJL engine:</p> <div contenteditable="false"> <div contenteditable="false"> <div data-code="package com.example.config;&#xA;&#xA;import org.springframework.context.annotation.Configuration;&#xA;import ai.djl.Engine;&#xA;&#xA;@Configuration&#xA;public class DjLConfig {&#xA;&#xA; public DjLConfig() {&#xA; Engine.getEngine(&quot;MXNet&quot;);&#xA; }&#xA;}" data-lang="text/x-java"> <pre><code lang="text/x-java">package com.example.config; import org.springframework.context.annotation.Configuration; import ai.djl.Engine; @Configuration public class DjLConfig { public DjLConfig() { Engine.getEngine("MXNet"); } }</code></pre> </div> </div> </div> <p data-end="3098" data-start="2984">No special configuration is required for OpenAI; it reads API keys from the <code data-end="3076" data-start="3060">OPENAI_API_KEY</code> environment variable.</p> <h3 data-end="3143" data-section-id="iqq7q5" data-start="3105">2. Service Layer / Agent Component</h3> <p data-end="3249" data-start="3145">Implement a service that acts as your “agent.” It can use a local DJL model or call an external LLM API:</p> <div contenteditable="false"> <div contenteditable="false"> <div data-code="package com.example.agent;&#xA;&#xA;import com.openai.client.OpenAIClient;&#xA;import com.openai.client.okhttp.OpenAIOkHttpClient;&#xA;import com.openai.models.responses.Response;&#xA;import com.openai.models.responses.ResponseCreateParams;&#xA;import org.springframework.stereotype.Service;&#xA;&#xA;@Service&#xA;public class HealthAgent {&#xA;&#xA; private final OpenAIClient openAiClient;&#xA;&#xA; public HealthAgent() {&#xA; this.openAiClient = OpenAIOkHttpClient.fromEnv();&#xA; }&#xA;&#xA; public String analyzeVitals(String patientId, VitalSigns vitals) {&#xA; String prompt = String.format(&#xA; &quot;Patient %s has temperature %.1f°C, blood pressure %.0f/%d, heart rate %d. Suggest the next diagnostic step.&quot;,&#xA; patientId, vitals.temperature(), vitals.bloodPressure(), vitals.bloodPressure(), vitals.heartRate()&#xA; );&#xA;&#xA; ResponseCreateParams params = ResponseCreateParams.builder()&#xA; .model(&quot;health&quot;)&#xA; .input(prompt)&#xA; .build();&#xA;&#xA; Response response = openAiClient.responses().create(params);&#xA; return response.outputText();&#xA; }&#xA;}" data-lang="text/x-java"> <pre><code lang="text/x-java">package com.example.agent; import com.openai.client.OpenAIClient; import com.openai.client.okhttp.OpenAIOkHttpClient; import com.openai.models.responses.Response; import com.openai.models.responses.ResponseCreateParams; import org.springframework.stereotype.Service; @Service public class HealthAgent { private final OpenAIClient openAiClient; public HealthAgent() { this.openAiClient = OpenAIOkHttpClient.fromEnv(); } public String analyzeVitals(String patientId, VitalSigns vitals) { String prompt = String.format( "Patient %s has temperature %.1f°C, blood pressure %.0f/%d, heart rate %d. Suggest the next diagnostic step.", patientId, vitals.temperature(), vitals.bloodPressure(), vitals.bloodPressure(), vitals.heartRate() ); ResponseCreateParams params = ResponseCreateParams.builder() .model("health") .input(prompt) .build(); Response response = openAiClient.responses().create(params); return response.outputText(); } }</code></pre> </div> </div> </div> <p data-end="4446" data-start="4340">This <code data-end="4358" data-start="4345">HealthAgent</code> service builds a prompt from <code data-end="4400" data-start="4388">VitalSigns</code> and uses the OpenAI Java SDK to call the LLM.</p> <h3 data-end="4475" data-section-id="fstqyf" data-start="4453">3. REST Controller</h3> <p data-end="4517" data-start="4477">Expose an HTTP API to trigger the agent:</p> <div contenteditable="false"> <div contenteditable="false"> <div data-code="package com.example.api;&#xA;&#xA;import com.example.agent.HealthAgent;&#xA;import com.example.health.VitalSigns;&#xA;import org.springframework.http.ResponseEntity;&#xA;import org.springframework.web.bind.annotation.*;&#xA;&#xA;@RestController&#xA;@RequestMapping(&quot;/api/health&quot;)&#xA;public class HealthController {&#xA;&#xA; private final HealthAgent agent;&#xA;&#xA; public HealthController(HealthAgent agent) {&#xA; this.agent = agent;&#xA; }&#xA;&#xA; @PostMapping(&quot;/analyze&quot;)&#xA; public ResponseEntity&lt;String> analyze(&#xA; @RequestParam String patientId,&#xA; @RequestBody VitalSigns vitals) {&#xA;&#xA; String result = agent.analyzeVitals(patientId, vitals);&#xA; return ResponseEntity.ok(result);&#xA; }&#xA;}" data-lang="text/x-java"> <pre><code lang="text/x-java">package com.example.api; import com.example.agent.HealthAgent; import com.example.health.VitalSigns; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/health") public class HealthController { private final HealthAgent agent; public HealthController(HealthAgent agent) { this.agent = agent; } @PostMapping("/analyze") public ResponseEntity&lt;String&gt; analyze( @RequestParam String patientId, @RequestBody VitalSigns vitals) { String result = agent.analyzeVitals(patientId, vitals); return ResponseEntity.ok(result); } }</code></pre> </div> </div> </div> <p data-end="5375" data-start="5212">This controller maps <code data-end="5273" data-start="5233">POST /api/health/analyze?patientId=XYZ</code> with a JSON body containing <code data-end="5314" data-start="5302">VitalSigns</code>. It delegates to the <code data-end="5349" data-start="5336">HealthAgent</code> and returns the response.</p> <h3 data-end="5420" data-section-id="1tnxn6o" data-start="5382">4. Agent Patterns and Architecture</h3> <p data-end="5677" data-start="5422">AI “agents” can follow different patterns. Spring AI documentation distinguishes <strong data-end="5516" data-start="5503">workflows</strong> from fully agentic systems that act autonomously. In healthcare, predictable workflows are often preferred for safety, although <a href="https://dzone.com/articles/building-an-llm-based-agent-step-0">LLM-based agents</a> can be dynamic.</p> <p data-end="5709" data-start="5679">Common agent patterns include:</p> <ul data-end="5940" data-start="5711"> <li data-end="5749" data-section-id="sgkdpp" data-start="5711"><strong data-end="5726" data-start="5713">Chaining:</strong> Multi-step reasoning</li> <li data-end="5822" data-section-id="14eczmf" data-start="5750"><strong data-end="5772" data-start="5752">Parallelization:</strong> Running tasks in parallel and combining results</li> <li data-end="5888" data-section-id="cv5cbz" data-start="5823"><strong data-end="5837" data-start="5825">Routing:</strong> Directing inputs to specialized prompts or tools</li> <li data-end="5940" data-section-id="z9omc4" data-start="5889"><strong data-end="5903" data-start="5891">Looping:</strong> Iterating until a goal is achieved</li> </ul> <p data-end="6123" data-start="5942">Spring AI provides abstractions to implement these patterns. For example, a chain workflow may sequentially call <code data-end="6086" data-start="6055">ChatClient.prompt(...).call()</code> while passing outputs between steps.</p> <p data-end="6207" data-start="6125">The example above is a single-step LLM call, but the architecture can be expanded:</p> <p data-end="6301" data-start="6209"><strong data-end="6301" data-start="6209">Clients → Spring Boot API → Agent (Spring AI or custom logic) → LLM/model → API response</strong></p> <h3 data-end="6338" data-section-id="137i7s0" data-start="6308">5. Table: Agent Approaches</h3> <div> <table> <thead> <tr width="auto"> <td> <p><strong>Approach</strong></p></td> <td> <p><strong>Pros</strong></p></td> <td> <p><strong>Cons</strong></p></td> <td> <p><strong>Use Cases</strong></p></td> </tr> </thead> <tbody> <tr width="auto"> <td> <p><strong>Custom Java Logic </strong></p></td> <td> <p>Fully controllable code no external calls</p></td> <td> <p>Limited intelligence no learning or language understanding</p></td> <td> <p>Simple rule-based analysis strict data privacy requirements</p></td> </tr> <tr width="auto"> <td> <p><strong>DJL </strong></p></td> <td> <p>Runs on JVM, GPU support  full control of model and data</p></td> <td> <p>Heavy dependency must train/download models resource-intensive</p></td> <td> <p>On-premises analytics private data processing  when a fixed ML model suffices</p></td> </tr> <tr width="auto"> <td> <p><strong>LLM API (OpenAI, etc.)</strong></p></td> <td> <p>State-of-art language understanding managed by provider</p></td> <td> <p>Latency, cost, and data privacy concerns requires API key</p></td> <td> <p>NLP-heavy tasks prototyping and research</p></td> </tr> <tr width="auto"> <td> <p><strong>Spring AI Patterns</strong></p></td> <td> <p>High-level workflows (chain, parallel, routing) built-in integrates with Spring</p></td> <td> <p>Underlying calls are still to models/APIs complexity overhead</p></td> <td> <p>Enterprise applications needing structured LLM agents, combining LLM with Spring ecosystem</p></td> </tr> </tbody> </table> </div> <h3 data-end="7095" data-section-id="lrz0l3" data-start="7065">6. Implementation Timeline</h3> <ul data-end="7574" data-start="7097"> <li data-end="7153" data-section-id="1fwc886" data-start="7097"><strong data-end="7114" data-start="7099">2026-02-01:</strong> Project kickoff (set up Spring Boot)</li> <li data-end="7215" data-section-id="1qjlv8b" data-start="7154"><strong data-end="7171" data-start="7156">2026-02-03:</strong> Define domain models (<code data-end="7206" data-start="7194">VitalSigns</code>, etc.)</li> <li data-end="7276" data-section-id="1bbvdlp" data-start="7216"><strong data-end="7233" data-start="7218">2026-02-05:</strong> Configure dependencies (DJL, OpenAI SDK)</li> <li data-end="7339" data-section-id="1nzsm20" data-start="7277"><strong data-end="7294" data-start="7279">2026-02-07:</strong> Implement <code data-end="7318" data-start="7305">HealthAgent</code> service (LLM call)</li> <li data-end="7397" data-section-id="13q7g04" data-start="7340"><strong data-end="7357" data-start="7342">2026-02-10:</strong> Add REST controller and test endpoint</li> <li data-end="7465" data-section-id="tshhc8" data-start="7398"><strong data-end="7415" data-start="7400">2026-02-12:</strong> Optional: integrate DJL model (local inference)</li> <li data-end="7519" data-section-id="1wiug5h" data-start="7466"><strong data-end="7483" data-start="7468">2026-02-14:</strong> Add error handling and validation</li> <li data-end="7574" data-section-id="6uhmev" data-start="7520"><strong data-end="7537" data-start="7522">2026-02-15:</strong> Final testing and local deployment</li> </ul> <h3 data-end="7603" data-section-id="am4m2o" data-start="7581">7. Running Locally</h3> <p data-end="7687" data-start="7605"><strong data-end="7626" data-start="7605">Set your API key:</strong><br data-end="7629" data-start="7626"></br> Ensure <code data-end="7652" data-start="7636">OPENAI_API_KEY</code> is set as an environment variable.</p> <p data-end="7707" data-start="7689"><strong data-end="7707" data-start="7689">Build and run (using Maven):</strong></p> <p data-end="7793" data-start="7741">By default, the app runs on <code data-end="7792" data-start="7769">http://localhost:8080</code>.</p> <p data-end="7812" data-start="7795"><strong data-end="7812" data-start="7795">Test the API:</strong></p> <div contenteditable="false"> <div contenteditable="false"> <div data-code="curl -X POST &quot;http://localhost:8080/api/health/analyze?patientId=123&quot; \&#xA; -H &quot;Content-Type: application/json&quot; \&#xA; -d '{&quot;temperature&quot;: 38.5, &quot;bloodPressure&quot;: 130, &quot;heartRate&quot;: 95}'" data-lang="text/plain"> <pre><code lang="text/plain">curl -X POST "http://localhost:8080/api/health/analyze?patientId=123" \ -H "Content-Type: application/json" \      -d '{"temperature": 38.5, "bloodPressure": 130, "heartRate": 95}'</code></pre> </div> </div> </div> <p data-end="8065" data-start="8012">You should see the AI agent’s response as plain text.</p> <h3 data-end="8090" data-section-id="a4scye" data-start="8072">8. Assumptions</h3> <ul data-end="8515" data-start="8092"> <li data-end="8155" data-section-id="znw4ve" data-start="8092"><strong data-end="8111" data-start="8094">Java Version:</strong> Java 17 (no license restrictions assumed)</li> <li data-end="8249" data-section-id="dvtuno" data-start="8156"><strong data-end="8180" data-start="8158">External Services:</strong> OpenAI or other LLM APIs require network access and valid API keys</li> <li data-end="8345" data-section-id="17lwosc" data-start="8250"><strong data-end="8269" data-start="8252">Data Privacy:</strong> Health data must be handled securely (e.g., encryption, HIPAA compliance)</li> <li data-end="8437" data-section-id="1exwkrn" data-start="8346"><strong data-end="8369" data-start="8348">Machine Learning:</strong> DJL uses MXNet by default; GPU acceleration requires proper setup</li> <li data-end="8515" data-section-id="1fkqxzg" data-start="8438"><strong data-end="8457" data-start="8440">Architecture:</strong> Kafka, WebSockets, or databases are omitted for brevity </li> </ul> <h2 data-end="8536" data-section-id="1079bb9" data-start="8522">Conclusion</h2> <p data-end="8639" data-start="8538">This document outlines how to integrate AI agents into a Spring Boot Java application. Key takeaways:</p> <ul data-end="8959" data-start="8641"> <li data-end="8718" data-section-id="2t0mb0" data-start="8641"><strong data-end="8661" data-start="8643">Project Setup:</strong> Use Spring Boot for REST APIs with ML/LLM dependencies</li> <li data-end="8796" data-section-id="1cp5whw" data-start="8719"><strong data-end="8737" data-start="8721">Agent Logic:</strong> Implement services that call LLM APIs or local ML models</li> <li data-end="8866" data-section-id="r3fv36" data-start="8797"><strong data-end="8812" data-start="8799">Patterns:</strong> Use structured agent patterns for complex workflows</li> <li data-end="8959" data-section-id="1g9699g" data-start="8867"><strong data-end="8884" data-start="8869">Trade-offs:</strong> Choose between custom logic, local ML, or LLM APIs based on requirements</li> </ul></div> </div> <p> <span>AI</span> <span>Health (Apple)</span> <span>Java (programming language)</span> <span>systems</span> </p> <p>Opinions expressed by DZone contributors are their own.</p> </div> </article><br><span style='font: #ff0000'>Generated by <a href='https://github.com/andreskrey/readability.php'>Readability.php</a>.</span> Wed, 20 May 2026 15:00:00 GMT https://dzone.com/articles/3640425 Ramya vani Rayala Ujorm3: A New Lightweight ORM for JavaBeans and Records https://dzone.com/articles/ujorm3-lightweight-orm-java <h1>Attention Required! | Cloudflare</h1><div id="cf-wrapper"> <p>Please enable cookies.</p> <div id="cf-error-details"> <p> <h2><span data-translate="unable_to_access">You are unable to access</span> dzone.com</h2> </p> <div> <div> <div> <h2 data-translate="blocked_why_headline">Why have I been blocked?</h2> <p data-translate="blocked_why_detail">This website is using a security service to protect itself from online attacks. The action you just performed triggered the security solution. There are several actions that could trigger this block including submitting a certain word or phrase, a SQL command or malformed data.</p> </div> <div> <h2 data-translate="blocked_resolve_headline">What can I do to resolve this?</h2> <p data-translate="blocked_resolve_detail">You can email the site owner to let them know you were blocked. Please include what you were doing when this page came up and the Cloudflare Ray ID found at the bottom of this page.</p> </div> </div> </div> <p> <span>Cloudflare Ray ID: <strong>9fe7b3843e47d2ea</strong></span> <span>•</span> <span id="cf-footer-item-ip"> Your IP: <span id="cf-footer-ip">5.1.64.146</span> <span>•</span> </span> <span><span>Performance &amp; security by</span> <a href="https://www.cloudflare.com/5xx-error-landing" id="brand_link" rel="noopener noreferrer" target="_blank">Cloudflare</a></span> </p> </div> </div><br><span style='font: #ff0000'>Generated by <a href='https://github.com/andreskrey/readability.php'>Readability.php</a>.</span> Tue, 19 May 2026 18:00:00 GMT https://dzone.com/articles/3650248 Pavel Ponec OpenAPI From Code With Spring and Java: A Recipe for Your CI https://dzone.com/articles/openapi-ci-spring-java <h1>Attention Required! | Cloudflare</h1><div id="cf-wrapper"> <p>Please enable cookies.</p> <div id="cf-error-details"> <p> <h2><span data-translate="unable_to_access">You are unable to access</span> dzone.com</h2> </p> <div> <div> <div> <h2 data-translate="blocked_why_headline">Why have I been blocked?</h2> <p data-translate="blocked_why_detail">This website is using a security service to protect itself from online attacks. The action you just performed triggered the security solution. There are several actions that could trigger this block including submitting a certain word or phrase, a SQL command or malformed data.</p> </div> <div> <h2 data-translate="blocked_resolve_headline">What can I do to resolve this?</h2> <p data-translate="blocked_resolve_detail">You can email the site owner to let them know you were blocked. Please include what you were doing when this page came up and the Cloudflare Ray ID found at the bottom of this page.</p> </div> </div> </div> <p> <span>Cloudflare Ray ID: <strong>9fe7b3865b12d1ff</strong></span> <span>•</span> <span id="cf-footer-item-ip"> Your IP: <span id="cf-footer-ip">5.1.64.146</span> <span>•</span> </span> <span><span>Performance &amp; security by</span> <a href="https://www.cloudflare.com/5xx-error-landing" id="brand_link" rel="noopener noreferrer" target="_blank">Cloudflare</a></span> </p> </div> </div><br><span style='font: #ff0000'>Generated by <a href='https://github.com/andreskrey/readability.php'>Readability.php</a>.</span> Tue, 19 May 2026 14:00:00 GMT https://dzone.com/articles/3649980 Roman Dubinin Building an Image Classification Pipeline With Apache Camel and Deep Java Library (DJL) https://dzone.com/articles/image-classification-pipeline-camel-djl <h1>Attention Required! | Cloudflare</h1><div id="cf-wrapper"> <p>Please enable cookies.</p> <div id="cf-error-details"> <p> <h2><span data-translate="unable_to_access">You are unable to access</span> dzone.com</h2> </p> <div> <div> <div> <h2 data-translate="blocked_why_headline">Why have I been blocked?</h2> <p data-translate="blocked_why_detail">This website is using a security service to protect itself from online attacks. The action you just performed triggered the security solution. There are several actions that could trigger this block including submitting a certain word or phrase, a SQL command or malformed data.</p> </div> <div> <h2 data-translate="blocked_resolve_headline">What can I do to resolve this?</h2> <p data-translate="blocked_resolve_detail">You can email the site owner to let them know you were blocked. Please include what you were doing when this page came up and the Cloudflare Ray ID found at the bottom of this page.</p> </div> </div> </div> <p> <span>Cloudflare Ray ID: <strong>9fcf867a8c969249</strong></span> <span>•</span> <span id="cf-footer-item-ip"> Your IP: <span id="cf-footer-ip">5.1.64.146</span> <span>•</span> </span> <span><span>Performance &amp; security by</span> <a href="https://www.cloudflare.com/5xx-error-landing" id="brand_link" rel="noopener noreferrer" target="_blank">Cloudflare</a></span> </p> </div> </div><br><span style='font: #ff0000'>Generated by <a href='https://github.com/andreskrey/readability.php'>Readability.php</a>.</span> Fri, 15 May 2026 20:00:00 GMT https://dzone.com/articles/3646826 Vignesh Durai How to Test a DELETE API Request With REST-Assured Java https://dzone.com/articles/test-delete-api-rest-assured-java <h1>Attention Required! | Cloudflare</h1><div id="cf-wrapper"> <p>Please enable cookies.</p> <div id="cf-error-details"> <p> <h2><span data-translate="unable_to_access">You are unable to access</span> dzone.com</h2> </p> <div> <div> <div> <h2 data-translate="blocked_why_headline">Why have I been blocked?</h2> <p data-translate="blocked_why_detail">This website is using a security service to protect itself from online attacks. The action you just performed triggered the security solution. There are several actions that could trigger this block including submitting a certain word or phrase, a SQL command or malformed data.</p> </div> <div> <h2 data-translate="blocked_resolve_headline">What can I do to resolve this?</h2> <p data-translate="blocked_resolve_detail">You can email the site owner to let them know you were blocked. Please include what you were doing when this page came up and the Cloudflare Ray ID found at the bottom of this page.</p> </div> </div> </div> <p> <span>Cloudflare Ray ID: <strong>9fcf867c9d6e1c9d</strong></span> <span>•</span> <span id="cf-footer-item-ip"> Your IP: <span id="cf-footer-ip">5.1.64.146</span> <span>•</span> </span> <span><span>Performance &amp; security by</span> <a href="https://www.cloudflare.com/5xx-error-landing" id="brand_link" rel="noopener noreferrer" target="_blank">Cloudflare</a></span> </p> </div> </div><br><span style='font: #ff0000'>Generated by <a href='https://github.com/andreskrey/readability.php'>Readability.php</a>.</span> Thu, 14 May 2026 14:30:00 GMT https://dzone.com/articles/3654489 Faisal Khatri How to Test a PATCH API Request With REST-Assured Java https://dzone.com/articles/test-patch-api-rest-assured-java <h1>Attention Required! | Cloudflare</h1><div id="cf-wrapper"> <p>Please enable cookies.</p> <div id="cf-error-details"> <p> <h2><span data-translate="unable_to_access">You are unable to access</span> dzone.com</h2> </p> <div> <div> <div> <h2 data-translate="blocked_why_headline">Why have I been blocked?</h2> <p data-translate="blocked_why_detail">This website is using a security service to protect itself from online attacks. The action you just performed triggered the security solution. There are several actions that could trigger this block including submitting a certain word or phrase, a SQL command or malformed data.</p> </div> <div> <h2 data-translate="blocked_resolve_headline">What can I do to resolve this?</h2> <p data-translate="blocked_resolve_detail">You can email the site owner to let them know you were blocked. Please include what you were doing when this page came up and the Cloudflare Ray ID found at the bottom of this page.</p> </div> </div> </div> <p> <span>Cloudflare Ray ID: <strong>9fb681357e36f4f1</strong></span> <span>•</span> <span id="cf-footer-item-ip"> Your IP: <span id="cf-footer-ip">5.1.64.146</span> <span>•</span> </span> <span><span>Performance &amp; security by</span> <a href="https://www.cloudflare.com/5xx-error-landing" id="brand_link" rel="noopener noreferrer" target="_blank">Cloudflare</a></span> </p> </div> </div><br><span style='font: #ff0000'>Generated by <a href='https://github.com/andreskrey/readability.php'>Readability.php</a>.</span> Wed, 13 May 2026 13:30:06 GMT https://dzone.com/articles/3654488 Faisal Khatri Solving the Mystery: Why Java RSS Grows in Docker on M1 Macs https://dzone.com/articles/java-rss-growth-docker-m1 <h1>Attention Required! | Cloudflare</h1><div id="cf-wrapper"> <p>Please enable cookies.</p> <div id="cf-error-details"> <p> <h2><span data-translate="unable_to_access">You are unable to access</span> dzone.com</h2> </p> <div> <div> <div> <h2 data-translate="blocked_why_headline">Why have I been blocked?</h2> <p data-translate="blocked_why_detail">This website is using a security service to protect itself from online attacks. The action you just performed triggered the security solution. There are several actions that could trigger this block including submitting a certain word or phrase, a SQL command or malformed data.</p> </div> <div> <h2 data-translate="blocked_resolve_headline">What can I do to resolve this?</h2> <p data-translate="blocked_resolve_detail">You can email the site owner to let them know you were blocked. Please include what you were doing when this page came up and the Cloudflare Ray ID found at the bottom of this page.</p> </div> </div> </div> <p> <span>Cloudflare Ray ID: <strong>9fb681378a11381b</strong></span> <span>•</span> <span id="cf-footer-item-ip"> Your IP: <span id="cf-footer-ip">5.1.64.146</span> <span>•</span> </span> <span><span>Performance &amp; security by</span> <a href="https://www.cloudflare.com/5xx-error-landing" id="brand_link" rel="noopener noreferrer" target="_blank">Cloudflare</a></span> </p> </div> </div><br><span style='font: #ff0000'>Generated by <a href='https://github.com/andreskrey/readability.php'>Readability.php</a>.</span> Tue, 12 May 2026 19:00:00 GMT https://dzone.com/articles/3638995 Sumeet Sharma Improving Java Application Reliability with Dynatrace AI Engine https://dzone.com/articles/improving-java-application-reliability-with-dyna <h1>Attention Required! | Cloudflare</h1><div id="cf-wrapper"> <p>Please enable cookies.</p> <div id="cf-error-details"> <p> <h2><span data-translate="unable_to_access">You are unable to access</span> dzone.com</h2> </p> <div> <div> <div> <h2 data-translate="blocked_why_headline">Why have I been blocked?</h2> <p data-translate="blocked_why_detail">This website is using a security service to protect itself from online attacks. The action you just performed triggered the security solution. There are several actions that could trigger this block including submitting a certain word or phrase, a SQL command or malformed data.</p> </div> <div> <h2 data-translate="blocked_resolve_headline">What can I do to resolve this?</h2> <p data-translate="blocked_resolve_detail">You can email the site owner to let them know you were blocked. Please include what you were doing when this page came up and the Cloudflare Ray ID found at the bottom of this page.</p> </div> </div> </div> <p> <span>Cloudflare Ray ID: <strong>9fa7292b1cbfdbf3</strong></span> <span>•</span> <span id="cf-footer-item-ip"> Your IP: <span id="cf-footer-ip">5.1.64.146</span> <span>•</span> </span> <span><span>Performance &amp; security by</span> <a href="https://www.cloudflare.com/5xx-error-landing" id="brand_link" rel="noopener noreferrer" target="_blank">Cloudflare</a></span> </p> </div> </div><br><span style='font: #ff0000'>Generated by <a href='https://github.com/andreskrey/readability.php'>Readability.php</a>.</span> Mon, 11 May 2026 12:00:17 GMT https://dzone.com/articles/3641540 Ramya vani Rayala How AI Is Rewriting Full-Stack Java Systems: Practical Patterns with Spring Boot, Kafka and WebSockets https://dzone.com/articles/how-ai-is-rewriting-full-stack-java-systems-practi <h1>Attention Required! | Cloudflare</h1><div id="cf-wrapper"> <p>Please enable cookies.</p> <div id="cf-error-details"> <p> <h2><span data-translate="unable_to_access">You are unable to access</span> dzone.com</h2> </p> <div> <div> <div> <h2 data-translate="blocked_why_headline">Why have I been blocked?</h2> <p data-translate="blocked_why_detail">This website is using a security service to protect itself from online attacks. The action you just performed triggered the security solution. There are several actions that could trigger this block including submitting a certain word or phrase, a SQL command or malformed data.</p> </div> <div> <h2 data-translate="blocked_resolve_headline">What can I do to resolve this?</h2> <p data-translate="blocked_resolve_detail">You can email the site owner to let them know you were blocked. Please include what you were doing when this page came up and the Cloudflare Ray ID found at the bottom of this page.</p> </div> </div> </div> <p> <span>Cloudflare Ray ID: <strong>9f8bacf56d27d2ae</strong></span> <span>•</span> <span id="cf-footer-item-ip"> Your IP: <span id="cf-footer-ip">5.1.64.146</span> <span>•</span> </span> <span><span>Performance &amp; security by</span> <a href="https://www.cloudflare.com/5xx-error-landing" id="brand_link" rel="noopener noreferrer" target="_blank">Cloudflare</a></span> </p> </div> </div><br><span style='font: #ff0000'>Generated by <a href='https://github.com/andreskrey/readability.php'>Readability.php</a>.</span> Fri, 08 May 2026 14:00:00 GMT https://dzone.com/articles/3640373 Ramya vani Rayala How to Test PUT API Request Using REST-Assured Java https://dzone.com/articles/test-put-api-rest-assured-java <h1>Attention Required! | Cloudflare</h1><div id="cf-wrapper"> <p>Please enable cookies.</p> <div id="cf-error-details"> <p> <h2><span data-translate="unable_to_access">You are unable to access</span> dzone.com</h2> </p> <div> <div> <div> <h2 data-translate="blocked_why_headline">Why have I been blocked?</h2> <p data-translate="blocked_why_detail">This website is using a security service to protect itself from online attacks. The action you just performed triggered the security solution. There are several actions that could trigger this block including submitting a certain word or phrase, a SQL command or malformed data.</p> </div> <div> <h2 data-translate="blocked_resolve_headline">What can I do to resolve this?</h2> <p data-translate="blocked_resolve_detail">You can email the site owner to let them know you were blocked. Please include what you were doing when this page came up and the Cloudflare Ray ID found at the bottom of this page.</p> </div> </div> </div> <p> <span>Cloudflare Ray ID: <strong>9f817af95a0bdbb3</strong></span> <span>•</span> <span id="cf-footer-item-ip"> Your IP: <span id="cf-footer-ip">5.1.64.146</span> <span>•</span> </span> <span><span>Performance &amp; security by</span> <a href="https://www.cloudflare.com/5xx-error-landing" id="brand_link" rel="noopener noreferrer" target="_blank">Cloudflare</a></span> </p> </div> </div><br><span style='font: #ff0000'>Generated by <a href='https://github.com/andreskrey/readability.php'>Readability.php</a>.</span> Thu, 07 May 2026 14:30:00 GMT https://dzone.com/articles/3653332 Faisal Khatri Comparing Top Gen AI Frameworks for Java in 2026 https://dzone.com/articles/top-genai-java-frameworks <h1>Attention Required! | Cloudflare</h1><div id="cf-wrapper"> <p>Please enable cookies.</p> <div id="cf-error-details"> <p> <h2><span data-translate="unable_to_access">You are unable to access</span> dzone.com</h2> </p> <div> <div> <div> <h2 data-translate="blocked_why_headline">Why have I been blocked?</h2> <p data-translate="blocked_why_detail">This website is using a security service to protect itself from online attacks. The action you just performed triggered the security solution. There are several actions that could trigger this block including submitting a certain word or phrase, a SQL command or malformed data.</p> </div> <div> <h2 data-translate="blocked_resolve_headline">What can I do to resolve this?</h2> <p data-translate="blocked_resolve_detail">You can email the site owner to let them know you were blocked. Please include what you were doing when this page came up and the Cloudflare Ray ID found at the bottom of this page.</p> </div> </div> </div> <p> <span>Cloudflare Ray ID: <strong>9f817afb59ec755e</strong></span> <span>•</span> <span id="cf-footer-item-ip"> Your IP: <span id="cf-footer-ip">5.1.64.146</span> <span>•</span> </span> <span><span>Performance &amp; security by</span> <a href="https://www.cloudflare.com/5xx-error-landing" id="brand_link" rel="noopener noreferrer" target="_blank">Cloudflare</a></span> </p> </div> </div><br><span style='font: #ff0000'>Generated by <a href='https://github.com/andreskrey/readability.php'>Readability.php</a>.</span> Thu, 07 May 2026 12:30:01 GMT https://dzone.com/articles/3653228 Xavier Portilla Edo Java ProcessBuilder: Deadlocks, Zombies, and the 64 KB Wall https://dzone.com/articles/java-processbuilder-deadlocks-zombies <h1>Attention Required! | Cloudflare</h1><div id="cf-wrapper"> <p>Please enable cookies.</p> <div id="cf-error-details"> <p> <h2><span data-translate="unable_to_access">You are unable to access</span> dzone.com</h2> </p> <div> <div> <div> <h2 data-translate="blocked_why_headline">Why have I been blocked?</h2> <p data-translate="blocked_why_detail">This website is using a security service to protect itself from online attacks. The action you just performed triggered the security solution. There are several actions that could trigger this block including submitting a certain word or phrase, a SQL command or malformed data.</p> </div> <div> <h2 data-translate="blocked_resolve_headline">What can I do to resolve this?</h2> <p data-translate="blocked_resolve_detail">You can email the site owner to let them know you were blocked. Please include what you were doing when this page came up and the Cloudflare Ray ID found at the bottom of this page.</p> </div> </div> </div> <p> <span>Cloudflare Ray ID: <strong>9f56c1ef5a694d7a</strong></span> <span>•</span> <span id="cf-footer-item-ip"> Your IP: <span id="cf-footer-ip">5.1.64.146</span> <span>•</span> </span> <span><span>Performance &amp; security by</span> <a href="https://www.cloudflare.com/5xx-error-landing" id="brand_link" rel="noopener noreferrer" target="_blank">Cloudflare</a></span> </p> </div> </div><br><span style='font: #ff0000'>Generated by <a href='https://github.com/andreskrey/readability.php'>Readability.php</a>.</span> Thu, 30 Apr 2026 17:00:01 GMT https://dzone.com/articles/3642039 Haider Kagalwala Java Backend Development in the Era of Kubernetes and Docker https://dzone.com/articles/java-backend-kubernetes-docker <h1>Attention Required! | Cloudflare</h1><div id="cf-wrapper"> <p>Please enable cookies.</p> <div id="cf-error-details"> <p> <h2><span data-translate="unable_to_access">You are unable to access</span> dzone.com</h2> </p> <div> <div> <div> <h2 data-translate="blocked_why_headline">Why have I been blocked?</h2> <p data-translate="blocked_why_detail">This website is using a security service to protect itself from online attacks. The action you just performed triggered the security solution. There are several actions that could trigger this block including submitting a certain word or phrase, a SQL command or malformed data.</p> </div> <div> <h2 data-translate="blocked_resolve_headline">What can I do to resolve this?</h2> <p data-translate="blocked_resolve_detail">You can email the site owner to let them know you were blocked. Please include what you were doing when this page came up and the Cloudflare Ray ID found at the bottom of this page.</p> </div> </div> </div> <p> <span>Cloudflare Ray ID: <strong>9f3f833ba92d30ee</strong></span> <span>•</span> <span id="cf-footer-item-ip"> Your IP: <span id="cf-footer-ip">5.1.64.146</span> <span>•</span> </span> <span><span>Performance &amp; security by</span> <a href="https://www.cloudflare.com/5xx-error-landing" id="brand_link" rel="noopener noreferrer" target="_blank">Cloudflare</a></span> </p> </div> </div><br><span style='font: #ff0000'>Generated by <a href='https://github.com/andreskrey/readability.php'>Readability.php</a>.</span> Tue, 28 Apr 2026 16:00:00 GMT https://dzone.com/articles/3641690 Ramya vani Rayala Java in a Container: Efficient Development and Deployment With Docker https://dzone.com/articles/java-in-containers-docker <h1>Attention Required! | Cloudflare</h1><div id="cf-wrapper"> <p>Please enable cookies.</p> <div id="cf-error-details"> <p> <h2><span data-translate="unable_to_access">You are unable to access</span> dzone.com</h2> </p> <div> <div> <div> <h2 data-translate="blocked_why_headline">Why have I been blocked?</h2> <p data-translate="blocked_why_detail">This website is using a security service to protect itself from online attacks. The action you just performed triggered the security solution. There are several actions that could trigger this block including submitting a certain word or phrase, a SQL command or malformed data.</p> </div> <div> <h2 data-translate="blocked_resolve_headline">What can I do to resolve this?</h2> <p data-translate="blocked_resolve_detail">You can email the site owner to let them know you were blocked. Please include what you were doing when this page came up and the Cloudflare Ray ID found at the bottom of this page.</p> </div> </div> </div> <p> <span>Cloudflare Ray ID: <strong>9f3f833dbb6fd40f</strong></span> <span>•</span> <span id="cf-footer-item-ip"> Your IP: <span id="cf-footer-ip">5.1.64.146</span> <span>•</span> </span> <span><span>Performance &amp; security by</span> <a href="https://www.cloudflare.com/5xx-error-landing" id="brand_link" rel="noopener noreferrer" target="_blank">Cloudflare</a></span> </p> </div> </div><br><span style='font: #ff0000'>Generated by <a href='https://github.com/andreskrey/readability.php'>Readability.php</a>.</span> Tue, 28 Apr 2026 14:00:00 GMT https://dzone.com/articles/3641716 Ramya vani Rayala