---
title: Notes on Using Spring Batch's ResourcelessJobRepository to Avoid Saving Metadata
tags: ["Spring Batch", "Spring Boot", "Java", "Spring"]
categories: ["Programming", "Java", "org", "springframework", "batch"]
date: 2025-05-14T17:25:12Z
updated: 2025-05-14T23:51:45Z
---

> [!NOTE]
> 2026-01-24 The [Spring Batch 6 / Spring Boot 4 version](/entries/893/en) of this article has been published.

> [!TIP]
> This article introduces a way to avoid saving metadata in Spring Batch, but I also created a [Spring Batch Dashboard](https://github.com/making/spring-batch-dashboard) to visualize metadata, so please consider saving and making good use of metadata as well.

When creating batch processes with Spring Batch, metadata is automatically created in the database.
For those who do not want to create metadata, a Map-based in-memory `JobRepository` was previously provided, but it was removed in Spring Batch 5.
Instead, it is now recommended to use in-memory databases such as H2 or HSQL.

> [!NOTE]
> In either case, when using an in-memory `JobRepository`, if you start the batch on a resident process such as a web application, there was a problem where memory usage would continue to increase.

In [Spring Batch 5.2](https://spring.io/blog/2024/11/20/spring-batch-5-2-0-goes-ga), in addition to MongoDB, a `ResourcelessJobRepository` implementation that does not save metadata was added.
This article introduces an example of using Spring Batch without saving metadata by using `ResourcelessJobRepository`.

> [!WARNING]
> Metadata is also used for job restarts, so if you use `ResourcelessJobRepository`, you cannot restart jobs. Also, `ResourcelessJobRepository` is not thread-safe.

The content of this article was confirmed to work with Spring Boot 3.4.5 and Spring Batch 5.2.2. In these versions, Spring Batch's AutoConfiguration assumes the use of a database.
Therefore, if you want to use `ResourcelessJobRepository`, you need to disable AutoConfiguration. This is planned to be improved in Spring Batch 6 and Spring Boot 4 ([spring-batch#4718](https://github.com/spring-projects/spring-batch/issues/4718)).

## Creating a Spring Boot Project

Create a Spring Batch project with the following command.

```bash
curl https://start.spring.io/starter.tgz \
       -d type=maven-project \
       -d artifactId=hello-spring-batch \
       -d baseDir=hello-spring-batch \
       -d packageName=com.example.hello \
       -d dependencies=batch,native \
       -d applicationName=HelloSpringBatchApplication | tar -xzvf -
cd hello-spring-batch
```

## Disabling Spring Batch AutoConfiguration

As mentioned above, you need to disable Spring Batch's AutoConfiguration. Add the following setting to `application.properties`.

```properties
cat <<EOF >> src/main/resources/application.properties
spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration
EOF
```

Instead, manually define the core features of Spring Batch as follows.

```java
cat <<EOF > src/main/java/com/example/hello/BatchConfig.java
package com.example.hello;

import java.lang.reflect.Proxy;
import org.springframework.batch.core.configuration.support.ScopeConfiguration;
import org.springframework.batch.core.explore.JobExplorer;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.launch.support.TaskExecutorJobLauncher;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.repository.support.ResourcelessJobRepository;
import org.springframework.boot.autoconfigure.batch.BatchProperties;
import org.springframework.boot.autoconfigure.batch.JobLauncherApplicationRunner;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.util.StringUtils;

@Configuration(proxyBeanMethods = false)
@Import(ScopeConfiguration.class)
@EnableConfigurationProperties(BatchProperties.class)
public class BatchConfig {

	@Bean
	public JobRepository jobRepository() {
		return new ResourcelessJobRepository();
	}

	@Bean
	public JobLauncher jobLauncher(JobRepository jobRepository) {
		TaskExecutorJobLauncher jobLauncher = new TaskExecutorJobLauncher();
		jobLauncher.setJobRepository(jobRepository);
		return jobLauncher;
	}

	@Bean
	public JobLauncherApplicationRunner jobLauncherApplicationRunner(JobLauncher jobLauncher,
			JobRepository jobRepository, BatchProperties properties) {
		JobExplorer jobExplorer = (JobExplorer) Proxy.newProxyInstance(JobExplorer.class.getClassLoader(),
				new Class<?>[] { JobExplorer.class }, (proxy, method, args) -> null);
		JobLauncherApplicationRunner runner = new JobLauncherApplicationRunner(jobLauncher, jobExplorer, jobRepository);
		String jobName = properties.getJob().getName();
		if (StringUtils.hasText(jobName)) {
			runner.setJobName(jobName);
		}
		return runner;
	}

}
EOF
```

## Defining the Job

Define the job as follows. This is the same as the basic usage of Spring Batch, but specify `ResourcelessTransactionManager` as the implementation of `PlatformTransactionManager`.

```java
cat <<EOF > src/main/java/com/example/hello/JobConfig.java
package com.example.hello;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.StepScope;
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.core.step.tasklet.Tasklet;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.batch.support.transaction.ResourcelessTransactionManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration(proxyBeanMethods = false)
public class JobConfig {

	private final Logger log = LoggerFactory.getLogger(JobConfig.class);

	private final JobRepository jobRepository;

	public JobConfig(JobRepository jobRepository) {
		this.jobRepository = jobRepository;
	}

	@Bean
	@StepScope
	public Tasklet helloTasklet() {
		return (contribution, chunkContext) -> {
			log.info("Hello World!");
			return RepeatStatus.FINISHED;
		};
	}

	@Bean
	public Step step1(Tasklet helloTasklet) {
		return new StepBuilder("step1", jobRepository).tasklet(helloTasklet, new ResourcelessTransactionManager())
			.build();
	}

	@Bean
	public Job job1(Step step1) {
		return new JobBuilder("job1", jobRepository).start(step1).build();
	}

}
EOF
```

Since we are not handling a database inside the Tasklet this time, exclude the dependency on `spring-boot-starter-jdbc` that is included by default in Spring Batch. Edit `pom.xml` and set it as follows.

```xml
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-batch</artifactId>
			<exclusions>
				<exclusion>
					<groupId>org.springframework.boot</groupId>
					<artifactId>spring-boot-starter-jdbc</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
```

That's all for the setup.

## Running

Build and run with the following commands.

```bash
./mvnw clean package -DskipTests
java -jar target/hello-spring-batch-0.0.1-SNAPSHOT.jar
```

```
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/

 :: Spring Boot ::                (v3.4.5)

2025-05-15T01:56:40.031+09:00  INFO 7358 --- [demo] [           main] c.e.hello.HelloSpringBatchApplication    : Starting HelloSpringBatchApplication v0.0.1-SNAPSHOT using Java 21.0.6 with PID 7358 (/private/tmp/hello-spring-batch/target/hello-spring-batch-0.0.1-SNAPSHOT.jar started by toshiaki in /private/tmp/hello-spring-batch)
2025-05-15T01:56:40.032+09:00  INFO 7358 --- [demo] [           main] c.e.hello.HelloSpringBatchApplication    : No active profile set, falling back to 1 default profile: "default"
2025-05-15T01:56:40.193+09:00  INFO 7358 --- [demo] [           main] o.s.b.c.l.s.TaskExecutorJobLauncher      : No TaskExecutor has been set, defaulting to synchronous executor.
2025-05-15T01:56:40.245+09:00  INFO 7358 --- [demo] [           main] c.e.hello.HelloSpringBatchApplication    : Started HelloSpringBatchApplication in 0.353 seconds (process running for 0.524)
2025-05-15T01:56:40.246+09:00  INFO 7358 --- [demo] [           main] o.s.b.a.b.JobLauncherApplicationRunner   : Running default command line with: []
2025-05-15T01:56:40.247+09:00  INFO 7358 --- [demo] [           main] o.s.b.c.l.s.TaskExecutorJobLauncher      : Job: [SimpleJob: [name=job1]] launched with the following parameters: [{}]
2025-05-15T01:56:40.249+09:00  INFO 7358 --- [demo] [           main] o.s.batch.core.job.SimpleStepHandler     : Executing step: [step1]
2025-05-15T01:56:40.252+09:00  INFO 7358 --- [demo] [           main] com.example.hello.JobConfig              : Hello World!
2025-05-15T01:56:40.253+09:00  INFO 7358 --- [demo] [           main] o.s.batch.core.step.AbstractStep         : Step: [step1] executed in 3ms
2025-05-15T01:56:40.254+09:00  INFO 7358 --- [demo] [           main] o.s.b.c.l.s.TaskExecutorJobLauncher      : Job: [SimpleJob: [name=job1]] completed with the following parameters: [{}] and the following status: [COMPLETED] in 4ms
```

It started up successfully.

## Running with GraalVM Native Image

To run with GraalVM Native Image, build and run with the following commands.

```bash
./mvnw native:compile -Pnative -DskipTests
./target/hello-spring-batch
```

```
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/

 :: Spring Boot ::                (v3.4.5)

2025-05-15T01:58:15.389+09:00  INFO 7554 --- [demo] [           main] c.e.hello.HelloSpringBatchApplication    : Starting AOT-processed HelloSpringBatchApplication using Java 21.0.6 with PID 7554 (/private/tmp/hello-spring-batch/target/hello-spring-batch started by toshiaki in /private/tmp/hello-spring-batch)
2025-05-15T01:58:15.389+09:00  INFO 7554 --- [demo] [           main] c.e.hello.HelloSpringBatchApplication    : No active profile set, falling back to 1 default profile: "default"
2025-05-15T01:58:15.391+09:00  INFO 7554 --- [demo] [           main] o.s.b.c.l.s.TaskExecutorJobLauncher      : No TaskExecutor has been set, defaulting to synchronous executor.
2025-05-15T01:58:15.393+09:00  INFO 7554 --- [demo] [           main] c.e.hello.HelloSpringBatchApplication    : Started HelloSpringBatchApplication in 0.01 seconds (process running for 0.019)
2025-05-15T01:58:15.393+09:00  INFO 7554 --- [demo] [           main] o.s.b.a.b.JobLauncherApplicationRunner   : Running default command line with: []
2025-05-15T01:58:15.393+09:00  INFO 7554 --- [demo] [           main] o.s.b.c.l.s.TaskExecutorJobLauncher      : Job: [SimpleJob: [name=job1]] launched with the following parameters: [{}]
2025-05-15T01:58:15.393+09:00  INFO 7554 --- [demo] [           main] o.s.batch.core.job.SimpleStepHandler     : Executing step: [step1]
2025-05-15T01:58:15.393+09:00  INFO 7554 --- [demo] [           main] com.example.hello.JobConfig              : Hello World!
2025-05-15T01:58:15.393+09:00  INFO 7554 --- [demo] [           main] o.s.batch.core.step.AbstractStep         : Step: [step1] executed in 
2025-05-15T01:58:15.393+09:00  INFO 7554 --- [demo] [           main] o.s.b.c.l.s.TaskExecutorJobLauncher      : Job: [SimpleJob: [name=job1]] completed with the following parameters: [{}] and the following status: [COMPLETED] in 
```

This also ran without any issues.
