JobExecutionListener in Spring Batch (Complete Guide with beforeJob & afterJob)

JobExecutionListener in Spring Batch (Complete Guide)

When building enterprise batch applications, you cannot simply deploy a job and hope it succeeds silently in the background. You need strict observability. You must know exactly when a job starts, how long it takes to run, and most importantly, you need immediate alerts if the job fails.

This is where the JobExecutionListener comes in. It is one of the most powerful callback interfaces in the Spring Batch framework, allowing you to inject custom business logic before a job begins and after a job completes.

๐Ÿ“บ Visual Learner? Explore the Spring Batch Masterclass!
Master the entire Spring Batch framework—including Listeners, Chunk Processing, Tasklets, and Fault Tolerance—by watching my complete video series.

▶ Watch the Spring Batch Playlist

1. What Is JobExecutionListener?

The JobExecutionListener interface provides two simple interception methods that wrap your entire batch job lifecycle:

public interface JobExecutionListener {
    void beforeJob(JobExecution jobExecution);
    void afterJob(JobExecution jobExecution);
}

Through the JobExecution object passed into these methods, you gain full access to the job's metadata, including its running status, failure exceptions, start times, and the ExecutionContext (a key-value store used to share data across the batch job).

2. Real-World Enterprise Use Cases

In production environments, developers rarely use listeners just to print logs. Common enterprise use cases include:

  • Alerting & Notifications: Sending an email or Slack message via afterJob() if the job status evaluates to BatchStatus.FAILED.
  • Performance Auditing: Calculating the exact millisecond duration of a job and persisting it to a monitoring dashboard like Grafana.
  • Resource Management: Opening database connections or cleaning up temporary SFTP download folders in beforeJob().

3. Creating a Production-Ready Listener

Let's build a realistic JobCompletionNotificationListener. In this example, we use the beforeJob method to record the exact start time in the ExecutionContext. In the afterJob method, we calculate the execution duration.

Most importantly, we check the BatchStatus. If the job fails, we extract the exceptions so we can trigger an alert.

import lombok.extern.slf4j.Slf4j;
import org.springframework.batch.core.BatchStatus;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobExecutionListener;
import org.springframework.stereotype.Component;

@Slf4j
@Component
public class JobCompletionNotificationListener implements JobExecutionListener {

    @Override
    public void beforeJob(JobExecution jobExecution) {
        log.info("๐Ÿš€ Job [{}] is starting...", jobExecution.getJobInstance().getJobName());
        
        // Store start time in the ExecutionContext to share it with afterJob()
        jobExecution.getExecutionContext().putLong("startTimeMillis", System.currentTimeMillis());
    }

    @Override
    public void afterJob(JobExecution jobExecution) {
        // Retrieve the start time from the context
        long startTime = jobExecution.getExecutionContext().getLong("startTimeMillis", System.currentTimeMillis());
        long duration = System.currentTimeMillis() - startTime;

        if (jobExecution.getStatus() == BatchStatus.COMPLETED) {
            log.info("✅ Job Completed Successfully in {} ms", duration);
            
        } else if (jobExecution.getStatus() == BatchStatus.FAILED) {
            log.error("❌ Job FAILED after {} ms. Exceptions: {}", duration, jobExecution.getAllFailureExceptions());
            
            // TODO: Integrate email service or PagerDuty API here to alert the engineering team
        }
    }
}

4. Registering the Listener in Spring Batch 5

To activate the listener, you must register it inside your JobBuilder. Notice that the listener is attached to the Job level, not the Step level.

import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.job.builder.JobBuilder;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.step.builder.StepBuilder;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.PlatformTransactionManager;

@Configuration
public class SpringBatchConfig {

    @Bean
    public Job sampleJob(JobRepository jobRepository, 
                         Step sampleStep, 
                         JobCompletionNotificationListener listener) {
                         
        return new JobBuilder("sample-job", jobRepository)
                .listener(listener) // Registering our custom listener here
                .start(sampleStep)
                .build();
    }

    @Bean
    public Step sampleStep(JobRepository jobRepository, PlatformTransactionManager txManager) {
        return new StepBuilder("sample-step", jobRepository)
                .tasklet((contribution, chunkContext) -> {
                    System.out.println("Executing core step business logic...");
                    return RepeatStatus.FINISHED;
                }, txManager)
                .build();
    }
}

5. Frequently Asked Questions

Can I register multiple JobExecutionListeners?
Yes. You can chain multiple listeners together by calling .listener() multiple times in the JobBuilder. They will execute in the order they were registered for beforeJob, and in reverse order for afterJob.

How is JobExecutionListener different from StepExecutionListener?
A JobExecutionListener runs exactly once per job. A StepExecutionListener runs once for every step inside that job. If your job has 5 steps, the Step listener triggers 5 times.

Can a listener prevent a job from running?
Yes. If you throw a RuntimeException inside the beforeJob() method, Spring Batch will immediately halt execution, mark the job as FAILED, and the subsequent steps will never run.

Conclusion

The JobExecutionListener is an indispensable tool for building resilient batch architectures. By moving your logging, metrics tracking, and error-alerting logic out of your core processing steps and into a listener, you adhere to the Single Responsibility Principle and make your codebase significantly easier to maintain.

๐Ÿ‘‚ Related Spring Batch Monitoring & Execution Guides

Enhance your understanding of job execution monitoring by exploring related Spring Batch concepts such as error handling, retry strategies, and conditional flows.

๐Ÿงฑ Spring Batch Core Components

Understand how JobExecutionListener integrates with JobRepository and the Step lifecycle.

๐Ÿšซ Skip Policy & Error Handling

Track skipped items, failures, and step outcomes using advanced listener callbacks.

๐Ÿ” Retry Mechanism

Monitor retry attempts and retry exhaustion events during heavy batch executions.

๐Ÿ”€ Conditional Flow in Jobs

Drive conditional job paths using the execution status captured by your listeners.

๐Ÿงต Multithreaded Step

Observe execution behavior and performance metrics in parallel batch processing scenarios.

⚙️ Spring Batch Tasklet

Combine Job Listeners with Tasklet execution status to build powerful, single-pass utilities.