<?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: Pascoal Eddy Bayonne </title>
    <description>The latest articles on DEV Community by Pascoal Eddy Bayonne  (@pascoalbayonne).</description>
    <link>https://dev.to/pascoalbayonne</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%2F33891%2F4bb7adf2-b8b8-4761-a20b-1a2faee6c298.jpeg</url>
      <title>DEV Community: Pascoal Eddy Bayonne </title>
      <link>https://dev.to/pascoalbayonne</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/pascoalbayonne"/>
    <language>en</language>
    <item>
      <title>Spring Batch — Monitoring and Metrics</title>
      <dc:creator>Pascoal Eddy Bayonne </dc:creator>
      <pubDate>Sat, 23 Aug 2025 17:22:22 +0000</pubDate>
      <link>https://dev.to/pascoalbayonne/spring-batch-monitoring-and-metrics-4n59</link>
      <guid>https://dev.to/pascoalbayonne/spring-batch-monitoring-and-metrics-4n59</guid>
      <description>&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%2Fn46bypvkoirl1y3y2koo.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%2Fn46bypvkoirl1y3y2koo.png" alt="Monitoring Short-Lived Jobs" width="800" height="446"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the world of batch processing, &lt;strong&gt;Spring Batch&lt;/strong&gt; has emerged as the de facto standard for building robust, scalable ETL jobs and data-processing pipelines. However, one of the most challenging aspects of batch processing isn’t the processing logic itself—it’s gaining visibility into what’s happening during execution and understanding the performance characteristics of these jobs.&lt;/p&gt;

&lt;p&gt;This blog post explores how to implement comprehensive monitoring and metrics for Spring Batch applications, with a particular focus on the unique challenges posed by &lt;strong&gt;short-lived jobs&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  What is a short-lived job?
&lt;/h2&gt;

&lt;p&gt;A short-lived job is a process that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Runs for a limited time (minutes to hours, not days/weeks)&lt;/li&gt;
&lt;li&gt;Starts, executes a specific task, then terminates&lt;/li&gt;
&lt;li&gt;Doesn’t run continuously or as a long-running service&lt;/li&gt;
&lt;/ul&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%2F15thef55xlnz4rkkzx9i.webp" 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%2F15thef55xlnz4rkkzx9i.webp" alt="Ephemeral or short-lived jobs" width="720" height="73"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What Makes Spring Batch Jobs “Short-Lived”?
&lt;/h2&gt;

&lt;p&gt;Spring Batch jobs are inherently ephemeral by nature. They:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Start&lt;/strong&gt;: Triggered by schedules (cron jobs) or events&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Execute&lt;/strong&gt;: Process data in chunks or as a single unit&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Complete&lt;/strong&gt;: Terminate after finishing their work&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Disappear&lt;/strong&gt;: Leave no long-running process behind&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This pattern is perfect for ETL operations, import/export tasks, cleanup operations, and data transformations—but it creates a significant monitoring challenge.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Traditional Monitoring Problem
&lt;/h2&gt;

&lt;p&gt;In a typical microservices architecture, long-running services expose metrics endpoints that monitoring systems (like Prometheus) can scrape at regular intervals (e.g., every 15–30 seconds).&lt;br&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%2Fq572t23joivc6layl636.webp" 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%2Fq572t23joivc6layl636.webp" alt="Monitoring Long-Lived Microservice with prometheus and grafana" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;However&lt;/strong&gt;, Spring Batch or other short-lived applications present a different scenario:&lt;/p&gt;

&lt;p&gt;The lifecycle of your short-lived job:&lt;br&gt;
&lt;strong&gt;1. Job starts&lt;/strong&gt; → The batch job, ETL process, or task is triggered.&lt;br&gt;
&lt;strong&gt;2. Job runs&lt;/strong&gt; → While running, it may expose metrics (e.g., through an HTTP endpoint or via Spring Boot Actuator).&lt;br&gt;
&lt;strong&gt;3. Job completes&lt;/strong&gt; → The process exits normally (status = SUCCESS/FAILED).&lt;br&gt;
&lt;strong&gt;4. Job disappears&lt;/strong&gt; → Since the process is terminated, the HTTP endpoint is gone, memory is freed, and there is no metrics endpoint anymore.&lt;/p&gt;

&lt;p&gt;The endpoint has vanished, so &lt;strong&gt;no metrics are collected&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frniu9d9y2nv2499zby6k.webp" 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%2Frniu9d9y2nv2499zby6k.webp" alt="The endpoint has vanished, so no metrics are collected" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Prometheus’s design assumes &lt;strong&gt;long-running targets&lt;/strong&gt; that are always up. For ephemeral jobs such as Spring Batch, Prometheus may miss their metrics completely unless you change how metrics are delivered.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzt74ixxjkgrrvb0gzkkd.webp" 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%2Fzt74ixxjkgrrvb0gzkkd.webp" alt="How to Monitor Short-Lived Jobs such as Spring Batch?&amp;lt;br&amp;gt;
" width="720" height="403"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  The Usual Fix: Monitoring Short-Lived Jobs
&lt;/h2&gt;

&lt;p&gt;Instead of expecting Prometheus to scrape a dead process, you need a &lt;strong&gt;push model&lt;/strong&gt;, e.g., using the &lt;strong&gt;PushGateway&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcy29bx6nmvg8ydj9tx78.webp" 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%2Fcy29bx6nmvg8ydj9tx78.webp" alt="PushGateway" width="566" height="634"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The PushGateway acts as a metrics buffer for ephemeral jobs. Here’s how it solves the problem:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Metrics Persistence&lt;/strong&gt;: PushGateway stores metrics pushed by batch jobs until Prometheus scrapes them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Asynchronous Collection&lt;/strong&gt;: Jobs can push metrics immediately upon completion.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reliable Delivery&lt;/strong&gt;: Ensures no metrics are lost due to timing issues.&lt;/li&gt;
&lt;/ol&gt;


&lt;h2&gt;
  
  
  How It Works
&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%2Fs8f00ms9j3dogmvdiauf.gif" 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%2Fs8f00ms9j3dogmvdiauf.gif" alt="Spring Batch + PushGateway + Prometheus&amp;lt;br&amp;gt;
" width="1920" height="1080"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Spring Batch + PushGateway + Prometheus&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Spring Batch Job → PushGateway (Pushes metrics)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;At the end of each step (or job completion), the job pushes counters, timers, success/failure flags, etc., to the PushGateway.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;2. PushGateway (Stores metrics temporarily)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Even after the Spring Batch job finishes, the metrics remain available for Prometheus to scrape.&lt;/li&gt;
&lt;li&gt;Solves the “job disappears → nothing to scrape” problem.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;3. Prometheus → PushGateway (Scrapes metrics)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Instead of scraping the short-lived job directly (which might be gone), Prometheus scrapes the PushGateway on a regular interval.&lt;/li&gt;
&lt;li&gt;Ensures metrics persistence long enough for Prometheus to record them in its time-series database.&lt;/li&gt;
&lt;/ul&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%2F3d45c93fj3bp13wvbr8m.webp" 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%2F3d45c93fj3bp13wvbr8m.webp" alt="Grafana Dashboard uses Prometheus as datasource&amp;lt;br&amp;gt;
" width="720" height="403"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Grafana → Prometheus (Visualizes metrics)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Grafana queries Prometheus with PromQL.&lt;/li&gt;
&lt;li&gt;Since Prometheus has already scraped and stored the batch-job metrics, Grafana can display:

&lt;ul&gt;
&lt;li&gt;Job duration&lt;/li&gt;
&lt;li&gt;Success/failure counts&lt;/li&gt;
&lt;li&gt;Error rates&lt;/li&gt;
&lt;li&gt;Any custom job metrics&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&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%2Fnrr2orqg8lv15b4fob8q.webp" 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%2Fnrr2orqg8lv15b4fob8q.webp" alt="Example of Grafana Dashboard of spring batch application&amp;lt;br&amp;gt;
" width="800" height="423"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Practice:
&lt;/h2&gt;

&lt;p&gt;Visit my YouTube channel for the full hands-on code example. Let me summarize.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://youtu.be/K-bFMaSENAs" rel="noopener noreferrer"&gt;Complete example here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Setup docker for Prometheus+Grafana+Pushgateway&lt;/strong&gt;&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;prometheus&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;prom/prometheus&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;prometheus'&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;9090:9090'&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/Users/pascoalbayonne/dev/prometheus.yml:/etc/prometheus/prometheus.yml&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;jaeger-example&lt;/span&gt;

  &lt;span class="na"&gt;pushgateway&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;prom/pushgateway&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;pushgateway'&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;9091:9091'&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;jaeger-example&lt;/span&gt;

  &lt;span class="na"&gt;grafana&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;grafana/grafana&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;grafana'&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3000:3000'&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;jaeger-example&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;prometheus&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Enable Pushgateway in your spring batch application&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;
&lt;span class="py"&gt;management.prometheus.metrics.export.enabled&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;
&lt;span class="py"&gt;management.prometheus.metrics.export.pushgateway.enabled&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;
&lt;span class="py"&gt;management.prometheus.metrics.export.pushgateway.address&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;localhost:9091&lt;/span&gt;
&lt;span class="py"&gt;management.prometheus.metrics.export.pushgateway.push-rate&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;1ms&lt;/span&gt;
&lt;span class="py"&gt;management.prometheus.metrics.export.pushgateway.job&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;${spring.application.name}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Run Spring Batch Application (short-Lived)&lt;/strong&gt;&lt;br&gt;
The spring batch application sends the metrics to pushgateway. Check the logs&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
2025-08-23T17:41:04.457+01:00  INFO 47721 &lt;span class="nt"&gt;---&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;multipart-download] &lt;span class="o"&gt;[&lt;/span&gt;           main] o.h.e.t.j.p.i.JtaPlatformInitiator       : HHH000489: No JTA platform available &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="s1"&gt;'hibernate.transaction.jta.platform'&lt;/span&gt; to &lt;span class="nb"&gt;enable &lt;/span&gt;JTA platform integration&lt;span class="o"&gt;)&lt;/span&gt;
2025-08-23T17:41:04.513+01:00  INFO 47721 &lt;span class="nt"&gt;---&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;multipart-download] &lt;span class="o"&gt;[&lt;/span&gt;           main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory &lt;span class="k"&gt;for &lt;/span&gt;persistence unit &lt;span class="s1"&gt;'default'&lt;/span&gt;
2025-08-23T17:41:04.812+01:00  INFO 47721 &lt;span class="nt"&gt;---&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;multipart-download] &lt;span class="o"&gt;[&lt;/span&gt;           main] p.b.s.m.MultipartDownloadApplication     : Started MultipartDownloadApplication &lt;span class="k"&gt;in &lt;/span&gt;2.932 seconds &lt;span class="o"&gt;(&lt;/span&gt;process running &lt;span class="k"&gt;for &lt;/span&gt;3.378&lt;span class="o"&gt;)&lt;/span&gt;
2025-08-23T17:41:04.814+01:00  INFO 47721 &lt;span class="nt"&gt;---&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;multipart-download] &lt;span class="o"&gt;[&lt;/span&gt;           main] o.s.b.a.b.JobLauncherApplicationRunner   : Running default &lt;span class="nb"&gt;command &lt;/span&gt;line with: &lt;span class="o"&gt;[]&lt;/span&gt;
2025-08-23T17:41:04.887+01:00  INFO 47721 &lt;span class="nt"&gt;---&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;multipart-download] &lt;span class="o"&gt;[&lt;/span&gt;           main] o.s.b.c.l.s.TaskExecutorJobLauncher      : Job: &lt;span class="o"&gt;[&lt;/span&gt;FlowJob: &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;import-customers]] launched with the following parameters: &lt;span class="o"&gt;[{&lt;/span&gt;&lt;span class="s1"&gt;'run.id'&lt;/span&gt;:&lt;span class="s1"&gt;'{value=20, type=class java.lang.Long, identifying=true}'&lt;/span&gt;&lt;span class="o"&gt;}]&lt;/span&gt;
2025-08-23T17:41:04.896+01:00  WARN 47721 &lt;span class="nt"&gt;---&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;multipart-download] &lt;span class="o"&gt;[&lt;/span&gt;           main] i.m.p.PrometheusMeterRegistry            : The meter &lt;span class="o"&gt;(&lt;/span&gt;MeterId&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'spring.batch.job.active'&lt;/span&gt;, &lt;span class="nv"&gt;tags&lt;/span&gt;&lt;span class="o"&gt;=[&lt;/span&gt;tag&lt;span class="o"&gt;(&lt;/span&gt;job-deprecated&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;,tag&lt;span class="o"&gt;(&lt;/span&gt;spring.batch.job.name&lt;span class="o"&gt;=&lt;/span&gt;import-customers&lt;span class="o"&gt;)&lt;/span&gt;,tag&lt;span class="o"&gt;(&lt;/span&gt;spring.batch.job.status&lt;span class="o"&gt;=&lt;/span&gt;UNKNOWN&lt;span class="o"&gt;)]})&lt;/span&gt; registration has failed: Prometheus requires that all meters with the same name have the same &lt;span class="nb"&gt;set &lt;/span&gt;of tag keys. There is already an existing meter named &lt;span class="s1"&gt;'spring_batch_job_active_seconds'&lt;/span&gt; containing tag keys &lt;span class="o"&gt;[&lt;/span&gt;job_deprecated, spring_batch_job_active_name]. The meter you are attempting to register has keys &lt;span class="o"&gt;[&lt;/span&gt;job_deprecated, spring_batch_job_name, spring_batch_job_status]. Note that subsequent logs will be logged at debug level.
2025-08-23T17:41:04.910+01:00  INFO 47721 &lt;span class="nt"&gt;---&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;multipart-download] &lt;span class="o"&gt;[&lt;/span&gt;           main] o.s.batch.core.job.SimpleStepHandler     : Executing step: &lt;span class="o"&gt;[&lt;/span&gt;downloadFileStep]
2025-08-23T17:41:10.097+01:00  INFO 47721 &lt;span class="nt"&gt;---&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;multipart-download] &lt;span class="o"&gt;[&lt;/span&gt;           main] s.a.a.t.s.p.LoggingTransferListener      : Transfer initiated...
2025-08-23T17:41:10.157+01:00  INFO 47721 &lt;span class="nt"&gt;---&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;multipart-download] &lt;span class="o"&gt;[&lt;/span&gt; AwsEventLoop 2] s.a.a.t.s.p.LoggingTransferListener      : |&lt;span class="o"&gt;====================&lt;/span&gt;| 100.0%
2025-08-23T17:41:10.160+01:00  INFO 47721 &lt;span class="nt"&gt;---&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;multipart-download] &lt;span class="o"&gt;[&lt;/span&gt;           main] p.b.s.m.service.CustomS3Client           : file: 2024-sales-info 3000.csv downloaded successfully from bucket sales-info
2025-08-23T17:41:10.160+01:00  INFO 47721 &lt;span class="nt"&gt;---&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;multipart-download] &lt;span class="o"&gt;[&lt;/span&gt;nc-response-1-0] s.a.a.t.s.p.LoggingTransferListener      : Transfer &lt;span class="nb"&gt;complete&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;
2025-08-23T17:41:10.163+01:00  INFO 47721 &lt;span class="nt"&gt;---&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;multipart-download] &lt;span class="o"&gt;[&lt;/span&gt;           main] c.v.flexypool.FlexyPoolDataSource        : Connection leased &lt;span class="k"&gt;for &lt;/span&gt;5246 millis, &lt;span class="k"&gt;while &lt;/span&gt;threshold is &lt;span class="nb"&gt;set &lt;/span&gt;to 1000 &lt;span class="k"&gt;in &lt;/span&gt;dataSource FlexyPoolDataSource
2025-08-23T17:41:10.165+01:00  INFO 47721 &lt;span class="nt"&gt;---&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;multipart-download] &lt;span class="o"&gt;[&lt;/span&gt;           main] o.s.batch.core.step.AbstractStep         : Step: &lt;span class="o"&gt;[&lt;/span&gt;downloadFileStep] executed &lt;span class="k"&gt;in &lt;/span&gt;5s254ms
2025-08-23T17:41:10.177+01:00  INFO 47721 &lt;span class="nt"&gt;---&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;multipart-download] &lt;span class="o"&gt;[&lt;/span&gt;           main] o.s.batch.core.job.SimpleStepHandler     : Executing step: &lt;span class="o"&gt;[&lt;/span&gt;fromFileDownloadedToDb]
2025-08-23T17:41:11.124+01:00  INFO 47721 &lt;span class="nt"&gt;---&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;multipart-download] &lt;span class="o"&gt;[&lt;/span&gt;           main] o.s.batch.core.step.AbstractStep         : Step: &lt;span class="o"&gt;[&lt;/span&gt;fromFileDownloadedToDb] executed &lt;span class="k"&gt;in &lt;/span&gt;946ms
2025-08-23T17:41:11.131+01:00  INFO 47721 &lt;span class="nt"&gt;---&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;multipart-download] &lt;span class="o"&gt;[&lt;/span&gt;           main] o.s.b.c.l.s.TaskExecutorJobLauncher      : Job: &lt;span class="o"&gt;[&lt;/span&gt;FlowJob: &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;import-customers]] completed with the following parameters: &lt;span class="o"&gt;[{&lt;/span&gt;&lt;span class="s1"&gt;'run.id'&lt;/span&gt;:&lt;span class="s1"&gt;'{value=20, type=class java.lang.Long, identifying=true}'&lt;/span&gt;&lt;span class="o"&gt;}]&lt;/span&gt; and the following status: &lt;span class="o"&gt;[&lt;/span&gt;COMPLETED] &lt;span class="k"&gt;in &lt;/span&gt;6s232ms
2025-08-23T17:41:11.135+01:00  INFO 47721 &lt;span class="nt"&gt;---&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;multipart-download] &lt;span class="o"&gt;[&lt;/span&gt;ionShutdownHook] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory &lt;span class="k"&gt;for &lt;/span&gt;persistence unit &lt;span class="s1"&gt;'default'&lt;/span&gt;
2025-08-23T17:41:11.138+01:00  INFO 47721 &lt;span class="nt"&gt;---&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;multipart-download] &lt;span class="o"&gt;[&lt;/span&gt;ionShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown initiated...
2025-08-23T17:41:11.140+01:00  INFO 47721 &lt;span class="nt"&gt;---&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;multipart-download] &lt;span class="o"&gt;[&lt;/span&gt;ionShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown completed.


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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;4. PushGateway Receives metrics&lt;/strong&gt;&lt;br&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%2Ftqztk86tlc9ufuqgf4at.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%2Ftqztk86tlc9ufuqgf4at.png" alt="Pushgateway receives and stores metrics" width="800" height="426"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Prometheus scrapes the metrics from PushGateway&lt;/strong&gt;&lt;br&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%2Fgni406jrh8s4xu90d5m8.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%2Fgni406jrh8s4xu90d5m8.png" alt="PromQL" width="800" height="426"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Grafana: create dashboard using Promethues as Datasource&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwk6bwllwk5answlhlan9.webp" 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%2Fwk6bwllwk5answlhlan9.webp" alt="Grafana Dashboard" width="800" height="423"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Monitoring Spring Batch applications requires a different approach than traditional long-running services. The &lt;strong&gt;Prometheus PushGateway pattern&lt;/strong&gt; provides the missing link that enables comprehensive observability for ephemeral batch jobs.&lt;/p&gt;

&lt;p&gt;By implementing this monitoring strategy, you gain:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Real-time visibility&lt;/strong&gt; into job performance and health&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Historical trends&lt;/strong&gt; for capacity planning and optimization&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Proactive alerting&lt;/strong&gt; for job failures and performance degradation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Business intelligence&lt;/strong&gt; through custom metrics and dashboards&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The combination of &lt;strong&gt;Spring Batch&lt;/strong&gt;, &lt;strong&gt;Prometheus PushGateway&lt;/strong&gt;, and &lt;strong&gt;Grafana&lt;/strong&gt; creates a powerful observability stack that transforms batch processing from a “black box” into a transparent, measurable, and optimizable system.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“Remember: You can’t optimize what you can’t measure.”&lt;/em&gt;&lt;br&gt;
With proper monitoring in place, your Spring Batch applications become not just functional, but truly observable and maintainable.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;&lt;em&gt;This blog post is part of our series on modern batch processing with Spring Boot and Java 21. Stay tuned for more insights on leveraging Virtual Threads and other Java 21 features in your batch applications.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://youtu.be/K-bFMaSENAs" rel="noopener noreferrer"&gt;Watch the complete hands-on code tutorial on YouTube&lt;/a&gt;&lt;/p&gt;




</description>
      <category>springbatch</category>
      <category>monitoring</category>
      <category>batchprocessing</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
