<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Janardhan Chejarla</title>
    <description>The latest articles on DEV Community by Janardhan Chejarla (@jchejarla).</description>
    <link>https://dev.to/jchejarla</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3254774%2Fdeaa4f1d-714a-43f1-91b1-efb580c8c0a0.jpeg</url>
      <title>DEV Community: Janardhan Chejarla</title>
      <link>https://dev.to/jchejarla</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jchejarla"/>
    <language>en</language>
    <item>
      <title>Distributed Spring Batch Coordination, Part 7: Best Practices for Production</title>
      <dc:creator>Janardhan Chejarla</dc:creator>
      <pubDate>Sun, 03 Aug 2025 13:27:10 +0000</pubDate>
      <link>https://dev.to/jchejarla/distributed-spring-batch-coordination-part-7-best-practices-for-production-5gi3</link>
      <guid>https://dev.to/jchejarla/distributed-spring-batch-coordination-part-7-best-practices-for-production-5gi3</guid>
      <description>&lt;h3&gt;
  
  
  🚀 Introduction
&lt;/h3&gt;

&lt;p&gt;As you prepare to take your distributed Spring Batch jobs into production using the database-backed coordination framework, it’s critical to establish robust operational practices. This article highlights key recommendations for configuring, monitoring, and managing distributed job executions reliably and efficiently at scale.&lt;/p&gt;




&lt;h2&gt;
  
  
  ⚙️ Configuration Best Practices
&lt;/h2&gt;

&lt;h3&gt;
  
  
  ✅ Use Static Node IDs in Production
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;📝 While dynamic UUIDs (e.g., &lt;code&gt;worker-${{random.uuid}}&lt;/code&gt;) are useful for local testing, &lt;strong&gt;static node IDs&lt;/strong&gt; (like &lt;code&gt;worker-1&lt;/code&gt;, &lt;code&gt;worker-2&lt;/code&gt;) are preferred in production.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This ensures:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Clear visibility into node health&lt;/li&gt;
&lt;li&gt;Easier debugging and traceability&lt;/li&gt;
&lt;li&gt;Consistent partition reassignment logic&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  📅 Tune Heartbeat and Failure Detection Intervals
&lt;/h3&gt;

&lt;p&gt;Configure the following properties carefully in your YAML:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;spring&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;batch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;heartbeat-interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5000&lt;/span&gt;
    &lt;span class="na"&gt;unreachable-node-threshold&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;15000&lt;/span&gt;
    &lt;span class="na"&gt;node-cleanup-threshold&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;30000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;heartbeat-interval&lt;/code&gt;: Frequency at which nodes update their status.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;unreachable-node-threshold&lt;/code&gt;: Marks nodes as UNREACHABLE if no update is received.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;node-cleanup-threshold&lt;/code&gt;: Deletes truly failed nodes after grace period.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Choose these values based on your workload and network reliability.&lt;/p&gt;




&lt;h3&gt;
  
  
  🔁 Enable Task Reassignment Safely
&lt;/h3&gt;

&lt;p&gt;When defining a &lt;code&gt;ClusterAwarePartitioner&lt;/code&gt;, explicitly set:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Override&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;PartitionTransferableProp&lt;/span&gt; &lt;span class="nf"&gt;arePartitionsTransferableWhenNodeFailed&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;PartitionTransferableProp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;YES&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allows for automatic reassignment of unfinished tasks to active nodes, improving fault recovery.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;📝 &lt;strong&gt;Note&lt;/strong&gt;: Set &lt;code&gt;PartitionTransferableProp.YES&lt;/code&gt; with caution. Not all tasks are safe to transfer upon failure—especially those involving file I/O, partial state updates, or external system interactions. Ensure your partitioned step is &lt;strong&gt;idempotent&lt;/strong&gt; and can be re-executed without side effects before enabling this.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  📡 Observability and Monitoring
&lt;/h2&gt;

&lt;h3&gt;
  
  
  🩺 Use Built-in Health Indicators
&lt;/h3&gt;

&lt;p&gt;Spring Boot Actuator exposes two indicators:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;/actuator/health&lt;/code&gt; → shows &lt;code&gt;batchCluster&lt;/code&gt; and &lt;code&gt;batchClusterNode&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/actuator/batch-cluster&lt;/code&gt; → detailed view of all active nodes and their load&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example snippet:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"batchCluster"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"UP"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"details"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Total Active Nodes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Total Nodes in Cluster"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"3"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Integrate these with Prometheus, Datadog, or any other monitoring tool.&lt;/p&gt;




&lt;h3&gt;
  
  
  📊 Track Load Per Node
&lt;/h3&gt;

&lt;p&gt;Use &lt;code&gt;/actuator/batch-cluster&lt;/code&gt; to determine:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which node is handling how many tasks&lt;/li&gt;
&lt;li&gt;Status (ACTIVE, UNREACHABLE)&lt;/li&gt;
&lt;li&gt;Heartbeat freshness&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This can help in rebalancing strategies and horizontal scaling decisions.&lt;/p&gt;




&lt;h2&gt;
  
  
  🛡️ Fault Tolerance Tips
&lt;/h2&gt;

&lt;h3&gt;
  
  
  🚨 Plan for Network Glitches
&lt;/h3&gt;

&lt;p&gt;Configure timeouts with a grace period to avoid false positives from brief network issues.&lt;/p&gt;

&lt;h3&gt;
  
  
  🧠 Node Self-Recovery
&lt;/h3&gt;

&lt;p&gt;If a node recovers after being deleted (e.g., due to latency), it can re-register and participate again.&lt;/p&gt;




&lt;h2&gt;
  
  
  📁 Job Design Tips
&lt;/h2&gt;

&lt;h3&gt;
  
  
  🔗 Keep Partition Logic Simple and Stateless
&lt;/h3&gt;

&lt;p&gt;Avoid embedding heavy logic or dependencies in your &lt;code&gt;Partitioner&lt;/code&gt; implementation. It should rely on basic parameters like row ranges, record offsets, or identifiers.&lt;/p&gt;

&lt;h3&gt;
  
  
  🧩 Isolate Shared Resources
&lt;/h3&gt;

&lt;p&gt;When writing to shared output (e.g., XML files or databases), ensure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Thread safety&lt;/li&gt;
&lt;li&gt;Separate output files/directories per partition&lt;/li&gt;
&lt;li&gt;Avoid overwrites and race conditions&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🧭 Final Thoughts
&lt;/h2&gt;

&lt;p&gt;By combining stateless partitioning logic, lightweight DB coordination, and robust monitoring, this framework enables large-scale batch execution with minimal operational overhead.&lt;/p&gt;

&lt;p&gt;These best practices help ensure your distributed Spring Batch jobs are &lt;strong&gt;resilient&lt;/strong&gt;, &lt;strong&gt;traceable&lt;/strong&gt;, and &lt;strong&gt;ready for production&lt;/strong&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  ⭐️ Support the Project
&lt;/h3&gt;

&lt;p&gt;If you found this article series useful or are using the framework in your projects, please consider giving the repository a ⭐️ on GitHub:&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://github.com/jchejarla/spring-batch-db-cluster-partitioning" rel="noopener noreferrer"&gt;GitHub – spring-batch-db-cluster-partitioning&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Your feedback, issues, and contributions are welcome!&lt;/p&gt;




</description>
      <category>springbatch</category>
      <category>java</category>
      <category>opensource</category>
      <category>cloudnative</category>
    </item>
    <item>
      <title>Distributed Spring Batch Coordination, Part 6: Developer Control and Partitioning Strategies</title>
      <dc:creator>Janardhan Chejarla</dc:creator>
      <pubDate>Sun, 03 Aug 2025 13:26:56 +0000</pubDate>
      <link>https://dev.to/jchejarla/distributed-spring-batch-coordination-part-6-developer-control-and-partitioning-strategies-1bm5</link>
      <guid>https://dev.to/jchejarla/distributed-spring-batch-coordination-part-6-developer-control-and-partitioning-strategies-1bm5</guid>
      <description>&lt;h3&gt;
  
  
  🔍 Introduction
&lt;/h3&gt;

&lt;p&gt;In any distributed system, failure is not an exception—it’s the norm. Nodes may crash, lose network connectivity, or experience high latency. A resilient Spring Batch framework must gracefully detect and recover from such failures, ensuring consistency and progress without manual intervention.&lt;/p&gt;

&lt;p&gt;This part of the series focuses on &lt;strong&gt;how failure detection and retry logic work&lt;/strong&gt; in the database-backed Spring Batch coordination model.&lt;/p&gt;




&lt;h3&gt;
  
  
  💥 Why Failure Handling Matters
&lt;/h3&gt;

&lt;p&gt;Distributed job execution is susceptible to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;💻 &lt;strong&gt;Node crashes or shutdowns&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;🌐 &lt;strong&gt;Network issues causing delayed heartbeats&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;⏳ &lt;strong&gt;Transient processing failures (e.g., DB timeout, file lock)&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;🔁 &lt;strong&gt;Unacknowledged or abandoned partitions&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without an intelligent retry mechanism and failure awareness, these issues can lead to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Stalled jobs&lt;/li&gt;
&lt;li&gt;Incomplete processing&lt;/li&gt;
&lt;li&gt;Duplicate processing due to incorrect reassignment&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  🧠 Node Failure Detection
&lt;/h3&gt;

&lt;p&gt;The framework monitors node health using periodic heartbeats recorded in the &lt;code&gt;BATCH_NODES&lt;/code&gt; table. A two-step failure detection process is used:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Mark as UNREACHABLE&lt;/strong&gt;: If a node fails to update its heartbeat within a configurable time window (&lt;code&gt;spring.batch.cluster.unreachableNodeThreshold&lt;/code&gt;), other nodes mark it as &lt;code&gt;UNREACHABLE&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Eviction&lt;/strong&gt;: If the node remains stale beyond the configured threshold (&lt;code&gt;spring.batch.cluster.nodeCleanupThreshold&lt;/code&gt;), it's evicted and deleted from the cluster registry.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This gives temporarily slow nodes time to recover, reducing false positives.&lt;/p&gt;




&lt;h3&gt;
  
  
  🔄 Reassignment of Tasks
&lt;/h3&gt;

&lt;p&gt;When a node is marked as unreachable, all &lt;strong&gt;active partitions&lt;/strong&gt; associated with that node are checked for reassignment.&lt;/p&gt;

&lt;p&gt;The decision is governed by the master step’s partitioner:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ClusterAwarePartitioner&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@Override&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ExecutionContext&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;createDistributedPartitions&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;availableNodeCount&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Developer-defined logic to split workload&lt;/span&gt;
        &lt;span class="c1"&gt;// (e.g., based on row ranges, file segments, or other domain inputs)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;generatePartitions&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;availableNodeCount&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@Override&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;PartitionTransferableProp&lt;/span&gt; &lt;span class="nf"&gt;arePartitionsTransferableWhenNodeFailed&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;PartitionTransferableProp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;YES&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// or NO, based on task sensitivity&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@Override&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;PartitionStrategy&lt;/span&gt; &lt;span class="nf"&gt;buildPartitionStrategy&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;PartitionStrategy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;partitioningMode&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;PartitioningMode&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ROUND_ROBIN&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If this &lt;code&gt;PartitionTransferableProp&lt;/code&gt; flag is set to &lt;code&gt;YES&lt;/code&gt;, the framework reassigns those partitions to healthy nodes based on the configured strategy (e.g., round-robin, fixed-node).&lt;/p&gt;




&lt;h3&gt;
  
  
  ✅ What’s Next (Part 7 Preview)
&lt;/h3&gt;

&lt;p&gt;In the next part, we’ll cover:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;📋 Best practices for running in production&lt;/li&gt;
&lt;li&gt;🔐 Securing coordination and metadata tables&lt;/li&gt;
&lt;li&gt;🧪 Performance tuning for large-scale workflows&lt;/li&gt;
&lt;li&gt;🚀 Integrating with monitoring stacks (Grafana, Prometheus)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  👨‍💻 About the Author
&lt;/h3&gt;

&lt;p&gt;Janardhan Reddy Chejarla is a Lead Software Engineer specializing in distributed systems and batch processing frameworks. He is the author of &lt;a href="https://github.com/jchejarla/spring-batch-db-cluster-partitioning" rel="noopener noreferrer"&gt;spring-batch-db-cluster-partitioning&lt;/a&gt; and a contributor to multiple open-source initiatives.&lt;/p&gt;

&lt;p&gt;⭐️ Star the GitHub repo and stay tuned for Part 7 → &lt;a href="https://dev.to/jchejarla/distributed-spring-batch-coordination-part-7-best-practices-for-production-5gi3"&gt;Production-Grade Best Practices!&lt;/a&gt;&lt;/p&gt;

</description>
      <category>springbatch</category>
      <category>java</category>
      <category>opensource</category>
      <category>cloudnative</category>
    </item>
    <item>
      <title>Distributed Spring Batch Coordination, Part 5: Monitoring, Observability, and Health Checks</title>
      <dc:creator>Janardhan Chejarla</dc:creator>
      <pubDate>Sun, 03 Aug 2025 12:47:02 +0000</pubDate>
      <link>https://dev.to/jchejarla/distributed-spring-batch-coordination-part-5-monitoring-observability-and-health-checks-590n</link>
      <guid>https://dev.to/jchejarla/distributed-spring-batch-coordination-part-5-monitoring-observability-and-health-checks-590n</guid>
      <description>&lt;h3&gt;
  
  
  🔍 Introduction
&lt;/h3&gt;

&lt;p&gt;As distributed jobs scale across nodes, &lt;strong&gt;observability&lt;/strong&gt; becomes essential. In this part, we explore how the &lt;code&gt;spring-batch-db-cluster-partitioning&lt;/code&gt; framework exposes &lt;strong&gt;real-time cluster health&lt;/strong&gt;, &lt;strong&gt;task loads&lt;/strong&gt;, and &lt;strong&gt;node statuses&lt;/strong&gt; — giving developers the visibility needed for debugging, performance tuning, and production readiness.&lt;/p&gt;




&lt;h3&gt;
  
  
  📊 Exposing Cluster State with Actuator Endpoints
&lt;/h3&gt;

&lt;p&gt;Spring Boot Actuator endpoints provide a natural interface to expose cluster state. This framework adds &lt;strong&gt;two custom indicators&lt;/strong&gt;:&lt;/p&gt;

&lt;h4&gt;
  
  
  ✅ &lt;code&gt;/actuator/health&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;This includes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"batchCluster"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"UP"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"details"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Total Active Nodes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Total Nodes in Cluster"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"3"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"batchClusterNode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"UP"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"details"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Current Load (number of live tasks)"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Node Status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ACTIVE"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Node Id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"worker-1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Last Heartbeat Update Time"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2025-08-03T02:43:07.133+00:00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Start Time"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2025-08-03T02:42:32.092+00:00"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This helps you instantly verify:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Overall cluster health, how many nodes available in total.&lt;/li&gt;
&lt;li&gt;If the current node is active and responsive&lt;/li&gt;
&lt;li&gt;How many tasks it's executing&lt;/li&gt;
&lt;li&gt;When it last sent a heartbeat&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  🌐 &lt;code&gt;/actuator/batch-cluster&lt;/code&gt; – Full Cluster Snapshot
&lt;/h3&gt;

&lt;p&gt;This custom endpoint provides a full view of the entire cluster state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"nodes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Started At"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2025-08-03T02:42:32.092+00:00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Node Id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"worker-1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Current Load (# of tasks)"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Host"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"worker-1.company.local"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Last Heartbeat"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2025-08-03T02:43:07.133+00:00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ACTIVE"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"totalNodes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;All registered nodes&lt;/li&gt;
&lt;li&gt;Current task count&lt;/li&gt;
&lt;li&gt;Last heartbeat timestamp&lt;/li&gt;
&lt;li&gt;Node status (&lt;code&gt;ACTIVE&lt;/code&gt;, &lt;code&gt;UNREACHABLE&lt;/code&gt;, etc.)&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  📈 Node Load Metrics
&lt;/h3&gt;

&lt;p&gt;Cluster load is computed based on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Active tasks being executed per node&lt;/li&gt;
&lt;li&gt;Heartbeat freshness&lt;/li&gt;
&lt;li&gt;Task reassignment if a node becomes unreachable&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This allows external monitoring tools to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Detect load imbalance&lt;/li&gt;
&lt;li&gt;Alert on stale heartbeats&lt;/li&gt;
&lt;li&gt;Audit execution trends over time&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  📌 Best Practices
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;📦 &lt;strong&gt;Use static node IDs&lt;/strong&gt; (e.g., &lt;code&gt;worker-1&lt;/code&gt;, &lt;code&gt;worker-2&lt;/code&gt;) for easier observability&lt;/li&gt;
&lt;li&gt;🛠️ Integrate with &lt;strong&gt;Prometheus/Grafana&lt;/strong&gt; using custom endpoints or intermediate exporters&lt;/li&gt;
&lt;li&gt;🧪 Monitor node health to detect failures before partitions get stuck&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  ✅ What’s Next (Part 6 Preview)
&lt;/h3&gt;

&lt;p&gt;In the next part, we’ll cover:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;⚠️ &lt;strong&gt;Failure handling and retries&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;🧯 What happens when a node crashes mid-job&lt;/li&gt;
&lt;li&gt;🔄 How tasks are reassigned or resumed&lt;/li&gt;
&lt;li&gt;🧠 Retry strategies to ensure data consistency&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  ⭐ Want More?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Explore the code: &lt;a href="https://github.com/jchejarla/spring-batch-db-cluster-partitioning" rel="noopener noreferrer"&gt;GitHub – spring-batch-db-cluster-partitioning&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Read earlier parts in the series: &lt;a href="https://dev.to/jchejarla/series/32755"&gt;Dev.to article series&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>springbatch</category>
      <category>java</category>
      <category>opensource</category>
      <category>cloudnative</category>
    </item>
    <item>
      <title>Distributed Spring Batch Coordination, Part 4: Getting Started with the Framework</title>
      <dc:creator>Janardhan Chejarla</dc:creator>
      <pubDate>Sun, 03 Aug 2025 12:30:55 +0000</pubDate>
      <link>https://dev.to/jchejarla/distributed-spring-batch-coordination-part-4-getting-started-with-the-framework-2kce</link>
      <guid>https://dev.to/jchejarla/distributed-spring-batch-coordination-part-4-getting-started-with-the-framework-2kce</guid>
      <description>&lt;p&gt;In this part of the series, we'll walk through how to set up and run a distributed Spring Batch job using the open-source database-backed coordination framework we introduced earlier.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;✅ By the end of this guide, you’ll have a working multi-node Spring Batch cluster that can process large datasets in parallel — with no messaging queues or external coordination middleware required.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  📦 Step 1: Add the Framework Dependency
&lt;/h2&gt;

&lt;p&gt;First, include the published artifact from Maven Central.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;io.github.jchejarla&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt; spring-batch-db-cluster-core&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;2.0.0&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- XML Writing Support --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.springframework&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;spring-oxm&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;com.thoughtworks.xstream&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;xstream&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;1.4.21&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make sure you're using &lt;strong&gt;Spring Batch 5+&lt;/strong&gt; and &lt;strong&gt;Spring Boot 3+&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧠 Step 2: Configure Your Cluster
&lt;/h2&gt;

&lt;p&gt;Each node in the cluster is a regular Spring Boot application that shares access to the &lt;strong&gt;same relational database&lt;/strong&gt; (PostgreSQL, Oracle, or MySQL).&lt;/p&gt;

&lt;p&gt;In your &lt;code&gt;application.yml&lt;/code&gt;, configure node identity and coordination settings:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;spring&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
   &lt;span class="na"&gt;batch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="na"&gt;node-id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;worker-${random.uuid}&lt;/span&gt; 
      &lt;span class="na"&gt;grid-size&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt; &lt;span class="c1"&gt;# This is &lt;/span&gt;
      &lt;span class="na"&gt;heartbeat-interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5000&lt;/span&gt;
      &lt;span class="na"&gt;task-polling-interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;📝 &lt;strong&gt;Note&lt;/strong&gt;: While using &lt;code&gt;worker-${random.uuid}&lt;/code&gt; helps during local testing, it's recommended to assign &lt;strong&gt;static node IDs&lt;/strong&gt; (e.g., &lt;code&gt;worker-1&lt;/code&gt;, &lt;code&gt;worker-2&lt;/code&gt;, etc.) in production environments. This improves observability, debugging, and partition tracking across runs.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Make sure all nodes point to the &lt;strong&gt;same database&lt;/strong&gt; and include the required tables.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;⚠️ &lt;strong&gt;Important&lt;/strong&gt;: This framework extends Spring Batch — so make sure to initialize the &lt;strong&gt;core Spring Batch schema&lt;/strong&gt; &lt;em&gt;before&lt;/em&gt; applying the coordination schema.  &lt;/p&gt;

&lt;p&gt;You can find the official Spring Batch schema files &lt;a href="https://github.com/spring-projects/spring-batch/tree/main/spring-batch-core/src/main/resources/org/springframework/batch/core" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Then, execute the coordination framework's schema available &lt;a href="https://github.com/jchejarla/spring-batch-db-cluster-partitioning/tree/main/spring-batch-db-cluster-core/src/main/resources/schema" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;📝 &lt;strong&gt;Note&lt;/strong&gt;: We currently support &lt;strong&gt;Oracle&lt;/strong&gt;, &lt;strong&gt;MySQL&lt;/strong&gt;, &lt;strong&gt;PostgreSQL&lt;/strong&gt;, and &lt;strong&gt;H2&lt;/strong&gt; (for testing purposes).&lt;br&gt;&lt;br&gt;
If you need support for another database, please open an issue on the &lt;a href="https://github.com/jchejarla/spring-batch-db-cluster-partitioning/issues" rel="noopener noreferrer"&gt;Feature Request&lt;/a&gt; page.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  ⚙️ Step-by-Step: CSV to XML Job
&lt;/h2&gt;

&lt;h3&gt;
  
  
  ⚙️ Step 1: Sample CSV File
&lt;/h3&gt;

&lt;p&gt;We’ll use a sample file with 10,000 customer records. You can download the CSV from the GitHub example repo:&lt;/p&gt;

&lt;p&gt;📄 &lt;a href="https://github.com/jchejarla/spring-batch-db-cluster-partitioning/blob/main/examples/data/customers_example_data.csv" rel="noopener noreferrer"&gt;customers_example_data.csv&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;customer_id,first_name,last_name,email,signup_date
C00001,Michael,Miller,michael.miller1@test.net,2023-01-03
C00002,David,Brown,david.brown2@test.net,2023-10-01
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  ⚙️ Step 2: Define the Customer POJO
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Setter&lt;/span&gt;
&lt;span class="nd"&gt;@Getter&lt;/span&gt;
&lt;span class="nd"&gt;@ToString&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Customer&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;customerId&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;firstName&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;lastName&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;signupDate&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  ⚙️ Step 3: ItemReader Setup
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt; &lt;span class="nd"&gt;@Bean&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"customerReader"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nd"&gt;@StepScope&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;FlatFileItemReader&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Customer&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;customerReader&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
            &lt;span class="nd"&gt;@Value&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"#{stepExecutionContext['startRow']}"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;startIndex&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
            &lt;span class="nd"&gt;@Value&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"#{stepExecutionContext['endRow']}"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;endIndex&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
            &lt;span class="nd"&gt;@Value&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"#{jobParameters['inputFile']}"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;inputFile&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
            &lt;span class="nc"&gt;LineMapper&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Customer&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;lineMapper&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;FlatFileItemReader&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Customer&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;reader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;FlatFileItemReader&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Customer&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;currentLine&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

            &lt;span class="nd"&gt;@Override&lt;/span&gt;
            &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;Customer&lt;/span&gt; &lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="nc"&gt;Customer&lt;/span&gt; &lt;span class="n"&gt;customer&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
                &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="o"&gt;((&lt;/span&gt;&lt;span class="n"&gt;customer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;super&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;read&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;currentLine&lt;/span&gt;&lt;span class="o"&gt;++;&lt;/span&gt;
                    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;currentLine&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;startIndex&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                        &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
                    &lt;span class="o"&gt;}&lt;/span&gt;
                    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;currentLine&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;endIndex&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
                    &lt;span class="o"&gt;}&lt;/span&gt;
                    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;customer&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
                &lt;span class="o"&gt;}&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;};&lt;/span&gt;

        &lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setResource&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;FileSystemResource&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inputFile&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
        &lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setLinesToSkip&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Skip header&lt;/span&gt;
        &lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setLineMapper&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lineMapper&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@Bean&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;LineMapper&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Customer&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;lineMapper&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;DefaultLineMapper&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Customer&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;mapper&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;DefaultLineMapper&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Customer&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;();&lt;/span&gt;
        &lt;span class="nc"&gt;DelimitedLineTokenizer&lt;/span&gt; &lt;span class="n"&gt;tokenizer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;DelimitedLineTokenizer&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;tokenizer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setNames&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"customer_id"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"first_name"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"last_name"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"email"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"signup_date"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="nc"&gt;BeanWrapperFieldSetMapper&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Customer&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;fieldSetMapper&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;BeanWrapperFieldSetMapper&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Customer&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;();&lt;/span&gt;
        &lt;span class="n"&gt;fieldSetMapper&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setTargetType&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Customer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;mapper&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setLineTokenizer&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tokenizer&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;mapper&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setFieldSetMapper&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fieldSetMapper&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;mapper&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  ⚙️ Step 4: Optional Processor
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Bean&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;ItemProcessor&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Customer&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Customer&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;customerProcessor&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// No-op for now&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  ⚙️ Step 5: XML Writer
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@StepScope&lt;/span&gt;
&lt;span class="nd"&gt;@Bean&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;StaxEventItemWriter&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Customer&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;customerXmlWriter&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="nd"&gt;@Value&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"#{jobParameters['outputDir']}"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;outputDir&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="nd"&gt;@Value&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"#{stepExecutionContext['partitionId']}"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;partitionId&lt;/span&gt;
&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;StaxEventItemWriter&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Customer&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;writer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;StaxEventItemWriter&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;();&lt;/span&gt;
    &lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setRootTagName&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"customers"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setResource&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;FileSystemResource&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;outputDir&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"/customers-part-"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;partitionId&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;".xml"&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;

    &lt;span class="nc"&gt;XStreamMarshaller&lt;/span&gt; &lt;span class="n"&gt;marshaller&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;XStreamMarshaller&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;marshaller&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setAliases&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"customer"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Customer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
    &lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setMarshaller&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;marshaller&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  ⚙️ Step 6: Job Config with Partitioning Strategy
&lt;/h3&gt;

&lt;p&gt;Use round-robin or fixed-node allocation. Each &lt;code&gt;ExecutionContext&lt;/code&gt; contains:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;  &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;putInt&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"startRow"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;range&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Skip header&lt;/span&gt;
  &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;putInt&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"endRow"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;range&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;putInt&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"partitionId"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Complete Job Config:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;io.github.jchejarla.springbatch.clustering.api.ClusterAwarePartitioner&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;io.github.jchejarla.springbatch.clustering.api.PartitionStrategy&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;io.github.jchejarla.springbatch.clustering.autoconfigure.conditions.ConditionalOnClusterEnabled&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;io.github.jchejarla.springbatch.clustering.partition.ClusterAwarePartitionHandler&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;io.github.jchejarla.springbatch.clustering.partition.PartitionTransferableProp&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;io.github.jchejarla.springbatch.clustering.partition.PartitioningMode&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;lombok.extern.slf4j.Slf4j&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.batch.core.Job&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.batch.core.Step&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.batch.core.configuration.annotation.EnableBatchProcessing&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.batch.core.configuration.annotation.StepScope&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.batch.core.job.builder.JobBuilder&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.batch.core.launch.support.RunIdIncrementer&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.batch.core.partition.support.Partitioner&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.batch.core.partition.support.StepExecutionAggregator&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.batch.core.repository.JobRepository&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.batch.core.step.builder.StepBuilder&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.batch.item.ExecutionContext&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.batch.item.file.FlatFileItemReader&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.batch.item.xml.StaxEventItemWriter&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.beans.factory.annotation.Autowired&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.beans.factory.annotation.Qualifier&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.beans.factory.annotation.Value&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.context.annotation.Bean&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.context.annotation.Configuration&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.transaction.PlatformTransactionManager&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.util.ArrayList&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.util.List&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;@Slf4j&lt;/span&gt;
&lt;span class="nd"&gt;@Configuration&lt;/span&gt;
&lt;span class="nd"&gt;@EnableBatchProcessing&lt;/span&gt;
&lt;span class="nd"&gt;@ConditionalOnClusterEnabled&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ETLJobConfig&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@Autowired&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;FlatFileItemReader&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Customer&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;customerItemReader&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="nd"&gt;@Autowired&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;CustomerProcessor&lt;/span&gt; &lt;span class="n"&gt;customerProcessor&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="nd"&gt;@Autowired&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;StaxEventItemWriter&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Customer&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;customerXmlWriter&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@Autowired&lt;/span&gt;
    &lt;span class="nd"&gt;@Qualifier&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"etlJobPartitioner"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Partitioner&lt;/span&gt; &lt;span class="n"&gt;partitioner&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="nd"&gt;@Autowired&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;ClusterAwarePartitionHandler&lt;/span&gt; &lt;span class="n"&gt;partitionHandler&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@Bean&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"etlClusteredJob"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;Job&lt;/span&gt; &lt;span class="nf"&gt;clusteredJob&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;JobRepository&lt;/span&gt; &lt;span class="n"&gt;jobRepository&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;PlatformTransactionManager&lt;/span&gt; &lt;span class="n"&gt;platformTransactionManager&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nd"&gt;@Qualifier&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"multiStepAggregator"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nc"&gt;StepExecutionAggregator&lt;/span&gt; &lt;span class="n"&gt;clusterAwareAggregator&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;JobBuilder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"etl-clustered-job"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;jobRepository&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;incrementer&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RunIdIncrementer&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;preventRestart&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;start&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;multiNodeExecutionStep&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;jobRepository&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;platformTransactionManager&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;clusterAwareAggregator&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;Step&lt;/span&gt; &lt;span class="nf"&gt;multiNodeExecutionStep&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;JobRepository&lt;/span&gt; &lt;span class="n"&gt;jobRepository&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;PlatformTransactionManager&lt;/span&gt; &lt;span class="n"&gt;txnManager&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;StepExecutionAggregator&lt;/span&gt; &lt;span class="n"&gt;clusterAwareAggregator&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;StepBuilder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"etlStep.manager"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;jobRepository&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;partitioner&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"multiNodeExecStep"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;partitioner&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;partitionHandler&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;partitionHandler&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;step&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;etlReaderWriterStep&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;jobRepository&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;txnManager&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;aggregator&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clusterAwareAggregator&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@Bean&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;Step&lt;/span&gt; &lt;span class="nf"&gt;etlReaderWriterStep&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;JobRepository&lt;/span&gt; &lt;span class="n"&gt;jobRepository&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;PlatformTransactionManager&lt;/span&gt; &lt;span class="n"&gt;txnManager&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;StepBuilder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"etlReaderWriterStep"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;jobRepository&lt;/span&gt;&lt;span class="o"&gt;).&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Customer&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Customer&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;txnManager&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;reader&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;customerItemReader&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;processor&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;customerProcessor&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;writer&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;customerXmlWriter&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@Bean&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"etlJobPartitioner"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nd"&gt;@StepScope&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;Partitioner&lt;/span&gt; &lt;span class="nf"&gt;etlJobPartitioner&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@Value&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"#{jobParameters['rows']}"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nc"&gt;Integer&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ClusterAwarePartitioner&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

            &lt;span class="nd"&gt;@Override&lt;/span&gt;
            &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ExecutionContext&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;createDistributedPartitions&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;availableNodeCount&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;range&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;availableNodeCount&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
                &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ExecutionContext&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;partitions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ArrayList&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;availableNodeCount&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

                &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;availableNodeCount&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                    &lt;span class="nc"&gt;ExecutionContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ExecutionContext&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
                    &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;putInt&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"startRow"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;range&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Skip header&lt;/span&gt;
                    &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;putInt&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"endRow"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;range&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
                    &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;putInt&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"partitionId"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
                    &lt;span class="n"&gt;partitions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
                &lt;span class="o"&gt;}&lt;/span&gt;

                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;partitions&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt;

            &lt;span class="nd"&gt;@Override&lt;/span&gt;
            &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;PartitionTransferableProp&lt;/span&gt; &lt;span class="nf"&gt;arePartitionsTransferableWhenNodeFailed&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;PartitionTransferableProp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;YES&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt;

            &lt;span class="nd"&gt;@Override&lt;/span&gt;
            &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;PartitionStrategy&lt;/span&gt; &lt;span class="nf"&gt;buildPartitionStrategy&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;PartitionStrategy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;partitioningMode&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;PartitioningMode&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ROUND_ROBIN&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;};&lt;/span&gt;

    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  ⚙️ Step 7: Launching Job with parameters
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;JobParameters&lt;/span&gt; &lt;span class="n"&gt;parameters&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;JobParametersBuilder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addString&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"RUN_TIME"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;LocalDateTime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;now&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;toString&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addLong&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"rows"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addString&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"inputFile"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="no"&gt;ETL_JOB_INPUT_FILE&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addString&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"outputDir"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="no"&gt;ETL_JOB_OUTPUT_DIR&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toJobParameters&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="nc"&gt;Job&lt;/span&gt; &lt;span class="n"&gt;job&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;applicationContext&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getBean&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"etlClusteredJob"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Job&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="nc"&gt;JobExecution&lt;/span&gt; &lt;span class="n"&gt;jobExecution&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;jobLauncher&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;parameters&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
     &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
         &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;RuntimeException&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Exception occurred when launching the Job"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
     &lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  ✅ What’s Next (Part 5 Preview)
&lt;/h2&gt;

&lt;p&gt;In the next part, we’ll cover:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;📈 &lt;strong&gt;Monitoring batch executions&lt;/strong&gt; (e.g., via &lt;code&gt;/actuator/batch-cluster&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;⚠️ &lt;strong&gt;Failure handling and retries&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;📊 &lt;strong&gt;Best practices for large-scale ingestion&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;🚀 Future roadmap (e.g., metrics, better dashboards)&lt;/li&gt;
&lt;/ul&gt;




</description>
      <category>springbatch</category>
      <category>java</category>
      <category>opensource</category>
      <category>cloudnative</category>
    </item>
    <item>
      <title>Distributed Spring Batch Coordination – Part 3: Fault Tolerance and Node Recovery</title>
      <dc:creator>Janardhan Chejarla</dc:creator>
      <pubDate>Sun, 03 Aug 2025 12:30:44 +0000</pubDate>
      <link>https://dev.to/jchejarla/distributed-spring-batch-coordination-part-3-fault-tolerance-and-node-recovery-3fch</link>
      <guid>https://dev.to/jchejarla/distributed-spring-batch-coordination-part-3-fault-tolerance-and-node-recovery-3fch</guid>
      <description>&lt;p&gt;In this part of the series, we’ll dive into how the framework ensures reliability and resilience by detecting failures and automatically rebalancing the workload across a dynamic cluster of nodes. With no centralized scheduler or messaging broker dependency, node health and task recovery are entirely &lt;strong&gt;database-coordinated&lt;/strong&gt;, lightweight, and highly configurable.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔥 Failure Happens — Let’s Handle It Gracefully
&lt;/h2&gt;

&lt;p&gt;In traditional Spring Batch setups, node failure often leads to partial execution or requires manual intervention. With this coordination framework, failure is a first-class citizen, and node recovery is &lt;strong&gt;built-in&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The system uses a &lt;strong&gt;two-step failure detection&lt;/strong&gt; mechanism:&lt;/p&gt;

&lt;h3&gt;
  
  
  ✅ Step 1: Detecting Unreachable Nodes
&lt;/h3&gt;

&lt;p&gt;Every active node updates its timestamp in the &lt;code&gt;BATCH_NODES&lt;/code&gt; table at regular intervals (heartbeat).&lt;br&gt;&lt;br&gt;
If a node fails to update within a configurable timeout (e.g., 30 seconds), it is marked as &lt;code&gt;UNREACHABLE&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This state gives the node time to recover from temporary issues like GC pauses or transient network glitches.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  ✅ Step 2: Deregistering Stale Nodes
&lt;/h3&gt;

&lt;p&gt;If the node remains unreachable beyond a second threshold (e.g., 2–3 minutes), it is considered stale and removed from the coordination table.&lt;/p&gt;

&lt;p&gt;At this point, any tasks (partitions) it was executing become &lt;strong&gt;eligible for reassignment&lt;/strong&gt; — but only if they were marked as &lt;code&gt;is_transferrable = true&lt;/code&gt; when created. This allows for fine-grained control over which partitions can move between nodes and which are sticky by design.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔄 Node Rejoin Logic
&lt;/h2&gt;

&lt;p&gt;If the node recovers after being marked unreachable or deregistered, it can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Re-register itself into the &lt;code&gt;BATCH_NODES&lt;/code&gt; table&lt;/li&gt;
&lt;li&gt;Participate in future partition assignment rounds&lt;/li&gt;
&lt;li&gt;Remain fully stateless from a master node’s perspective&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🧠 Masterless but Intelligent Coordination
&lt;/h2&gt;

&lt;p&gt;This design avoids a single point of failure by decentralizing the intelligence:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The node that launches the job acts as the temporary “master” for coordination&lt;/li&gt;
&lt;li&gt;Other nodes participate in a shared decision-making protocol using the database&lt;/li&gt;
&lt;li&gt;No Zookeeper, Redis, or Kafka needed&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  📊 Built-in Observability in v2.0.0
&lt;/h2&gt;

&lt;p&gt;This version includes Actuator-based monitoring endpoints:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;/actuator/health&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;batchCluster&lt;/code&gt;: shows total and active nodes&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;batchClusterNode&lt;/code&gt;: current node ID, load, heartbeat, status, uptime&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;code&gt;/actuator/batch-cluster&lt;/code&gt;: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Lists all registered nodes with status, load, heartbeat timestamps, and host info&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;This gives real-time visibility into the state of the cluster without requiring any additional dashboards or monitoring tools.&lt;/p&gt;




&lt;h2&gt;
  
  
  💡 What's Next?
&lt;/h2&gt;

&lt;p&gt;In Part 4, we’ll walk through how to build a real-world distributed Spring Batch job using this coordination framework. You'll learn:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How to integrate the framework into your Spring Boot project using Maven&lt;/li&gt;
&lt;li&gt;How to launch a multi-node cluster and observe active node discovery&lt;/li&gt;
&lt;li&gt;How round-robin and fixed-node partitioning work in practice&lt;/li&gt;
&lt;li&gt;How task distribution is calculated dynamically based on the current cluster size&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We'll demonstrate this with a &lt;strong&gt;practical ETL example&lt;/strong&gt;, where the master node splits a large CSV file into logical partitions (e.g., by line number or ID range), and each worker node processes its assigned chunk by transforming it into XML.&lt;/p&gt;

&lt;p&gt;This simulates real-world scenarios like customer data migration or financial transaction reporting, helping you confidently apply the framework to production-scale workloads.&lt;/p&gt;

&lt;p&gt;Stay tuned — the next part is hands-on and code-heavy!&lt;/p&gt;

</description>
      <category>springbatch</category>
      <category>java</category>
      <category>opensource</category>
      <category>cloudnative</category>
    </item>
    <item>
      <title>Distributed Spring Batch Coordination, Part 2: How Database-Backed Partitioning Works</title>
      <dc:creator>Janardhan Chejarla</dc:creator>
      <pubDate>Sun, 03 Aug 2025 12:30:23 +0000</pubDate>
      <link>https://dev.to/jchejarla/distributed-spring-batch-coordination-part-2-how-database-backed-partitioning-works-p5h</link>
      <guid>https://dev.to/jchejarla/distributed-spring-batch-coordination-part-2-how-database-backed-partitioning-works-p5h</guid>
      <description>&lt;h2&gt;
  
  
  📘 Part 2: How Database-Backed Partitioning Works
&lt;/h2&gt;

&lt;p&gt;In &lt;a href="https://dev.to/your-username/your-part-1-link"&gt;Part 1&lt;/a&gt;, we discussed the challenges with traditional Spring Batch scaling — especially when relying on Kafka or RabbitMQ. In this part, let’s explore how we can simplify distributed coordination using a &lt;strong&gt;relational database as the central source of truth&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  💡 Key Idea
&lt;/h2&gt;

&lt;p&gt;Rather than broadcasting partition instructions via messaging middleware, the master node writes coordination state into the database. Worker nodes read from the database to discover &lt;strong&gt;which partitions they are responsible for&lt;/strong&gt; — no messaging layer required.&lt;/p&gt;




&lt;h2&gt;
  
  
  ⚙️ Core Coordination Tables
&lt;/h2&gt;

&lt;p&gt;This model relies on three lightweight tables:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;BATCH_NODES&lt;/code&gt;&lt;/strong&gt;: Registers active nodes in the cluster&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;BATCH_JOB_COORDINATION&lt;/code&gt;&lt;/strong&gt;: Tracks coordination for each partitioned step&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;BATCH_PARTITIONS&lt;/code&gt;&lt;/strong&gt;: Stores partition metadata and execution state (assigned node, status, result)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These tables allow for real-time visibility into job execution without external queues or in-memory state.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔁 Execution Flow
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Master node receives the job request&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;It queries &lt;code&gt;BATCH_NODES&lt;/code&gt; to find all currently active nodes&lt;/li&gt;
&lt;li&gt;Using either:

&lt;ul&gt;
&lt;li&gt;🌀 Round-Robin, or
&lt;/li&gt;
&lt;li&gt;🎯 Fixed-Node allocation
the master assigns partitions and stores them in &lt;code&gt;BATCH_PARTITIONS&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Workers poll for tasks where &lt;code&gt;assigned_node = self&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Once complete, they update their partition status&lt;/li&gt;
&lt;li&gt;The master monitors for completion and performs &lt;strong&gt;final aggregation&lt;/strong&gt;, if needed&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;✅ This works with any JDBC-compatible database and integrates well with Kubernetes, CI/CD workflows, or container-based clusters.&lt;/p&gt;




&lt;h2&gt;
  
  
  📊 Visual Overview
&lt;/h2&gt;

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




&lt;h2&gt;
  
  
  🧠 Why This Works
&lt;/h2&gt;

&lt;p&gt;This architecture avoids common pitfalls like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tight coupling to messaging infrastructure&lt;/li&gt;
&lt;li&gt;Delayed worker availability&lt;/li&gt;
&lt;li&gt;Lack of visibility into node state&lt;/li&gt;
&lt;li&gt;Hard-to-debug coordination failures&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead, all coordination is &lt;strong&gt;transparent&lt;/strong&gt;, &lt;strong&gt;queryable&lt;/strong&gt;, and &lt;strong&gt;resilient to restarts&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  🏁 What’s Next
&lt;/h2&gt;

&lt;p&gt;In the next part of the series, we’ll cover:&lt;/p&gt;

&lt;p&gt;🔹 Failure handling strategies&lt;br&gt;
🔹 Retry logic&lt;br&gt;
🔹 Node liveness and rebalancing&lt;/p&gt;




&lt;p&gt;📘 &lt;a href="https://dev.to/your-username/your-part-1-link"&gt;Read Part 1 here&lt;/a&gt;&lt;br&gt;&lt;br&gt;
🌟 GitHub Repo: &lt;a href="https://github.com/jchejarla/spring-batch-db-cluster-partitioning" rel="noopener noreferrer"&gt;jchejarla/spring-batch-db-cluster-partitioning&lt;/a&gt;&lt;/p&gt;

</description>
      <category>springbatch</category>
      <category>java</category>
      <category>cloudnative</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Distributed Spring Batch Coordination, Part 1: The Problem with Traditional Spring Batch Scaling</title>
      <dc:creator>Janardhan Chejarla</dc:creator>
      <pubDate>Sat, 02 Aug 2025 18:19:38 +0000</pubDate>
      <link>https://dev.to/jchejarla/distributed-spring-batch-coordination-part-1-the-problem-with-traditional-spring-batch-scaling-3he4</link>
      <guid>https://dev.to/jchejarla/distributed-spring-batch-coordination-part-1-the-problem-with-traditional-spring-batch-scaling-3he4</guid>
      <description>&lt;h2&gt;
  
  
  📘 Part 1: The Problem with Traditional Spring Batch Scaling
&lt;/h2&gt;

&lt;p&gt;Scaling Spring Batch across multiple nodes typically involves complex setup — often requiring messaging middleware like &lt;strong&gt;RabbitMQ&lt;/strong&gt; or &lt;strong&gt;Apache Kafka&lt;/strong&gt; to enable remote partitioning. While effective, this approach introduces &lt;strong&gt;tight infrastructure coupling, deployment overhead&lt;/strong&gt;, and &lt;strong&gt;runtime fragility&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Here’s the crux of the problem:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You need &lt;strong&gt;message brokers&lt;/strong&gt; to send partition instructions to remote workers.&lt;/li&gt;
&lt;li&gt;The master has &lt;strong&gt;no real visibility into how many workers are available&lt;/strong&gt; at job launch.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Late-arriving or unavailable nodes&lt;/strong&gt; may lead to skewed partitioning, failures, or idle workers.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;coordination state lives in memory&lt;/strong&gt; or is dispersed across the cluster.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For modern, container-based workloads, this makes orchestration harder — especially when trying to run Spring Batch inside Kubernetes, CI/CD workflows, or ephemeral cloud environments.&lt;/p&gt;

&lt;h2&gt;
  
  
  🔧 Why I Built This Project
&lt;/h2&gt;

&lt;p&gt;To simplify this, I built an open-source framework:&lt;br&gt;&lt;br&gt;
➡️ &lt;a href="https://github.com/jchejarla/spring-batch-db-cluster-partitioning" rel="noopener noreferrer"&gt;&lt;strong&gt;spring-batch-db-cluster-partitioning&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This framework replaces message brokers with a &lt;strong&gt;relational database&lt;/strong&gt; as the central coordination hub. It supports:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ &lt;strong&gt;Round-robin and fixed-node partition assignment&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Dynamic node discovery&lt;/strong&gt; before the job starts&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Fully stateless master logic&lt;/strong&gt;, with all orchestration handled via SQL&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It’s lightweight, easy to plug into your Spring Batch step, and ready to run in Docker, Kubernetes, or CI pipelines.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧠 Design Note: Master Node Uptime
&lt;/h2&gt;

&lt;p&gt;While the coordination model is stateless and database-driven, the &lt;strong&gt;node that initiates a job acts as its master&lt;/strong&gt; for the duration of execution. This node:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Launches the job and assigns partitions&lt;/li&gt;
&lt;li&gt;Monitors worker progress and failures&lt;/li&gt;
&lt;li&gt;Executes final aggregation or post-partition steps, if any&lt;/li&gt;
&lt;li&gt;Writes final job completion status&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To preserve job integrity, the master node &lt;strong&gt;must remain available while the job is running&lt;/strong&gt;. However, since coordination state is fully persisted in the database, this node can be any eligible participant — making the model decentralized, resilient, and cloud-native in spirit.&lt;/p&gt;

&lt;h2&gt;
  
  
  📚 What You’ll Learn in This Series
&lt;/h2&gt;

&lt;p&gt;In the coming parts of this series, I’ll walk you through:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The architecture and coordination flow&lt;/li&gt;
&lt;li&gt;Partitioning strategies (round-robin, fixed-node)&lt;/li&gt;
&lt;li&gt;Failure handling and node resilience&lt;/li&gt;
&lt;li&gt;How to build and run distributed jobs with this framework&lt;/li&gt;
&lt;li&gt;A real-world ETL use case (CSV to XML conversion) to demonstrate end-to-end job orchestration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you’ve ever struggled with Spring Batch scaling or want a more DevOps-friendly model, this series is for you.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔜 Coming Up Next:
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Part 2 – How Database-Backed Partitioning Works in Spring Batch&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Stay tuned — and ⭐️ the repo if you're excited:&lt;br&gt;&lt;br&gt;
👉 &lt;a href="https://github.com/jchejarla/spring-batch-db-cluster-partitioning" rel="noopener noreferrer"&gt;github.com/jchejarla/spring-batch-db-cluster-partitioning&lt;/a&gt;&lt;/p&gt;

</description>
      <category>springbatch</category>
      <category>java</category>
      <category>opensource</category>
      <category>cloudnative</category>
    </item>
    <item>
      <title>Released v1.0.0 of a Spring Batch clustering library — pure DB-based coordination, no Kafka, no ZooKeeper. Feedback welcome!</title>
      <dc:creator>Janardhan Chejarla</dc:creator>
      <pubDate>Tue, 08 Jul 2025 02:15:23 +0000</pubDate>
      <link>https://dev.to/jchejarla/released-v100-of-a-spring-batch-clustering-library-pure-db-based-coordination-no-kafka-no-1i69</link>
      <guid>https://dev.to/jchejarla/released-v100-of-a-spring-batch-clustering-library-pure-db-based-coordination-no-kafka-no-1i69</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/jchejarla/spring-batch-clustering-with-zero-messaging-introducing-spring-batch-db-cluster-partitioning-39p4" class="crayons-story__hidden-navigation-link"&gt;Spring Batch Clustering With Zero Messaging: Introducing spring-batch-db-cluster-partitioning (v1.0.0)&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/jchejarla" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3254774%2Fdeaa4f1d-714a-43f1-91b1-efb580c8c0a0.jpeg" alt="jchejarla profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/jchejarla" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Janardhan Chejarla
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Janardhan Chejarla
                
              
              &lt;div id="story-author-preview-content-2666433" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/jchejarla" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3254774%2Fdeaa4f1d-714a-43f1-91b1-efb580c8c0a0.jpeg" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Janardhan Chejarla&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/jchejarla/spring-batch-clustering-with-zero-messaging-introducing-spring-batch-db-cluster-partitioning-39p4" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Jul 8 '25&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/jchejarla/spring-batch-clustering-with-zero-messaging-introducing-spring-batch-db-cluster-partitioning-39p4" id="article-link-2666433"&gt;
          Spring Batch Clustering With Zero Messaging: Introducing spring-batch-db-cluster-partitioning (v1.0.0)
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/springboot"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;springboot&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/springbatch"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;springbatch&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/java"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;java&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/opensource"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;opensource&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/jchejarla/spring-batch-clustering-with-zero-messaging-introducing-spring-batch-db-cluster-partitioning-39p4" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;2&lt;span class="hidden s:inline"&gt; reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/jchejarla/spring-batch-clustering-with-zero-messaging-introducing-spring-batch-db-cluster-partitioning-39p4#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              1&lt;span class="hidden s:inline"&gt; comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            3 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>springboot</category>
      <category>springbatch</category>
      <category>java</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Spring Batch Clustering With Zero Messaging: Introducing spring-batch-db-cluster-partitioning (v1.0.0)</title>
      <dc:creator>Janardhan Chejarla</dc:creator>
      <pubDate>Tue, 08 Jul 2025 02:09:02 +0000</pubDate>
      <link>https://dev.to/jchejarla/spring-batch-clustering-with-zero-messaging-introducing-spring-batch-db-cluster-partitioning-39p4</link>
      <guid>https://dev.to/jchejarla/spring-batch-clustering-with-zero-messaging-introducing-spring-batch-db-cluster-partitioning-39p4</guid>
      <description>&lt;p&gt;&lt;strong&gt;⚡ TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A production-ready Spring Batch extension for dynamic clustering using &lt;strong&gt;only the database&lt;/strong&gt; — no Kafka, no RabbitMQ, no coordination servers. Version 1.0.0 is now released on Maven Central!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;🚀 Why I Built This&lt;/strong&gt;&lt;br&gt;
Distributed batch processing often requires complex infrastructure — think messaging systems, zookeeper, or centralized schedulers. In many real-world deployments, especially in financial institutions, this adds cost, risk, and tight operational coupling.&lt;/p&gt;

&lt;p&gt;To solve this, I built a lightweight, pluggable Spring Batch extension that enables &lt;strong&gt;cluster-aware partitioned step execution&lt;/strong&gt; using nothing but a &lt;strong&gt;shared database&lt;/strong&gt; as the coordination layer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🛠️ Key Features&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;✅ &lt;strong&gt;Cluster Coordination Using DB Only&lt;/strong&gt;&lt;br&gt;
Each node registers, heartbeats, and participates in job execution using lightweight database tables — no brokers required.&lt;/p&gt;

&lt;p&gt;✅ &lt;strong&gt;Dynamic Partition Assignment&lt;/strong&gt;&lt;br&gt;
Partitions are assigned and rebalanced at runtime based on node availability — perfect for ephemeral cloud-native deployments.&lt;/p&gt;

&lt;p&gt;✅ &lt;strong&gt;Failover &amp;amp; Recovery&lt;/strong&gt;&lt;br&gt;
If a node dies mid-job, remaining nodes detect the loss and reassign unfinished partitions.&lt;/p&gt;

&lt;p&gt;✅ &lt;strong&gt;Pluggable Coordination Tables&lt;/strong&gt;&lt;br&gt;
Custom schemas (&lt;code&gt;BATCH_NODES&lt;/code&gt;, &lt;code&gt;BATCH_PARTITIONS&lt;/code&gt;, &lt;code&gt;BATCH_JOB_COORDINATION&lt;/code&gt;) provide fine-grained visibility and control.&lt;/p&gt;

&lt;p&gt;✅ &lt;strong&gt;Zero External Dependencies&lt;/strong&gt;&lt;br&gt;
Built purely on Spring Batch 5.x, Spring JDBC, and standard transaction semantics.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;📦 Released Artifacts&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;io.github.jchejarla&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;clustering-core&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;1.0.0&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Available now on &lt;a href="https://central.sonatype.com/artifact/io.github.jchejarla/clustering-core" rel="noopener noreferrer"&gt;Maven Central&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🔍 Architecture Overview&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Master Node&lt;/strong&gt; — Automatically elected (no Zookeeper!) based on job launcher.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Coordinator Tables&lt;/strong&gt; — Shared DB tables used to track:

&lt;ul&gt;
&lt;li&gt;   Active nodes&lt;/li&gt;
&lt;li&gt;   Step partition states&lt;/li&gt;
&lt;li&gt;   Ongoing executions&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PartitionHandler&lt;/strong&gt; — Custom implementation that uses SQL to dynamically assign work.&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;✅ Example Code:&lt;/strong&gt; &lt;a href="https://github.com/jchejarla/spring-batch-db-cluster-partitioning/tree/main/examples" rel="noopener noreferrer"&gt;GitHub - examples directory&lt;/a&gt; for ready-to-run Spring Boot projects demonstrating the cluster partitioning in action.&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;p&gt;&lt;strong&gt;💡 Use Cases&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Spring Batch jobs in Kubernetes (nodes scale up/down)&lt;/li&gt;
&lt;li&gt;FinTech ETL pipelines where messaging systems are overkill&lt;/li&gt;
&lt;li&gt;On-prem enterprise environments with restricted tech stacks&lt;/li&gt;
&lt;li&gt;Batch workloads on serverless compute (like AWS Fargate or Google Cloud Run)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;🧪 Example: Dynamic Step Execution&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Bean&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;Step&lt;/span&gt; &lt;span class="nf"&gt;partitionedStep&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;stepBuilderFactory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"partitionedStep"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;partitioner&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"workerStep"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;customPartitioner&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;partitionHandler&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clusterAwarePartitionHandler&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;clusterAwarePartitionHandler&lt;/code&gt; is where the DB magic happens.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;📈 What’s Next&lt;/strong&gt;&lt;br&gt;
🔹 Monitoring endpoints&lt;br&gt;
🔹 Retry policies and smarter failure detection&lt;/p&gt;

&lt;p&gt;If you’re interested in how this framework works under the hood, I’ve started a detailed article series: &lt;br&gt;
📘 &lt;a href="https://dev.to/jchejarla/distributed-spring-batch-coordination-part-1-the-problem-with-traditional-spring-batch-scaling-3he4"&gt;Distributed Spring Batch Coordination: Part 1 – The Problem with Traditional Scaling&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;More parts coming soon!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🙌 Contribute or Follow&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/jchejarla/spring-batch-db-cluster-partitioning" rel="noopener noreferrer"&gt;⭐ GitHub Repo&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://central.sonatype.com/artifact/io.github.jchejarla/clustering-core" rel="noopener noreferrer"&gt;📦 Maven Central&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;💬 Issues and PRs welcome — especially around test cases and Kubernetes integrations.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;🧠 Behind the Scenes&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This project was born from practical needs in building scalable ETL pipelines in a large financial services ecosystem. By removing the messaging layer, we reduced infra cost and simplified failover handling — while maintaining full reliability.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;👋 Final Thoughts&lt;/strong&gt;&lt;br&gt;
If you're tired of spinning up Kafka just to run partitioned jobs — this is for you.&lt;/p&gt;

&lt;p&gt;This library is designed for engineers, architects, and teams who want &lt;strong&gt;reliability without orchestration overload&lt;/strong&gt;. Try it out, share feedback, and help shape the roadmap.&lt;/p&gt;

</description>
      <category>springboot</category>
      <category>springbatch</category>
      <category>java</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
