+ *
*
*
*
@@ -156,7 +141,8 @@
* @author Dave Syer
* @author Mahmoud Ben Hassine
* @author Taeik Lim
- *
+ * @see EnableJdbcJobRepository
+ * @see EnableMongoJobRepository
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@@ -172,114 +158,47 @@
* {@link ApplicationContextFactory}.
* @return boolean indicating whether the configuration is going to be modularized
* into multiple application contexts. Defaults to {@code false}.
+ * @deprecated since 6.0 in favor of Spring's context hierarchies and
+ * {@link GroupAwareJob}s. Scheduled for removal in 6.2 or later.
*/
+ @Deprecated(since = "6.0", forRemoval = true)
boolean modular() default false;
/**
- * Set the data source to use in the job repository and job explorer.
- * @return the bean name of the data source to use. Default to {@literal dataSource}.
- */
- String dataSourceRef() default "dataSource";
-
- /**
- * Set the type of the data source to use in the job repository. The default type will
- * be introspected from the datasource's metadata.
- * @since 5.1
- * @see DatabaseType
- * @return the type of data source.
- */
- String databaseType() default "";
-
- /**
- * Set the transaction manager to use in the job repository.
- * @return the bean name of the transaction manager to use. Defaults to
- * {@literal transactionManager}
- */
- String transactionManagerRef() default "transactionManager";
-
- /**
- * Set the execution context serializer to use in the job repository and job explorer.
- * @return the bean name of the execution context serializer to use. Default to
- * {@literal executionContextSerializer}.
- */
- String executionContextSerializerRef() default "executionContextSerializer";
-
- /**
- * The charset to use in the job repository and job explorer
- * @return the charset to use. Defaults to {@literal UTF-8}.
- */
- String charset() default "UTF-8";
-
- /**
- * The Batch tables prefix. Defaults to {@literal "BATCH_"}.
- * @return the Batch table prefix
- */
- String tablePrefix() default AbstractJdbcBatchMetadataDao.DEFAULT_TABLE_PREFIX;
-
- /**
- * The maximum length of exit messages in the database.
- * @return the maximum length of exit messages in the database
- */
- int maxVarCharLength() default AbstractJdbcBatchMetadataDao.DEFAULT_EXIT_MESSAGE_LENGTH;
-
- /**
- * The incrementer factory to use in various DAOs.
- * @return the bean name of the incrementer factory to use. Defaults to
- * {@literal incrementerFactory}.
- */
- String incrementerFactoryRef() default "incrementerFactory";
-
- /**
- * The generator that determines a unique key for identifying job instance objects
- * @return the bean name of the job key generator to use. Defaults to
- * {@literal jobKeyGenerator}.
- *
- * @since 5.1
- */
- String jobKeyGeneratorRef() default "jobKeyGenerator";
-
- /**
- * The large object handler to use in job repository and job explorer.
- * @return the bean name of the lob handler to use. Defaults to {@literal lobHandler}.
- * @deprecated Since 5.2 with no replacement. Scheduled for removal in v6
- */
- @Deprecated(since = "5.2.0", forRemoval = true)
- String lobHandlerRef() default "lobHandler";
-
- /**
- * The type of large objects.
- * @return the type of large objects.
+ * Set the task executor to use in the job operator.
+ * @return the bean name of the task executor to use. Defaults to
+ * {@literal taskExecutor}
*/
- int clobType() default Types.CLOB;
+ String taskExecutorRef() default "taskExecutor";
/**
- * Set the isolation level for create parameter value. Defaults to
- * {@literal ISOLATION_SERIALIZABLE}.
- * @return the value of the isolation level for create parameter
+ * Set the job registry to use in the job operator.
+ * @return the bean name of the job registry to use. Defaults to
+ * {@literal jobRegistry}
*/
- String isolationLevelForCreate() default "ISOLATION_SERIALIZABLE";
+ String jobRegistryRef() default "jobRegistry";
/**
- * Set the task executor to use in the job launcher.
- * @return the bean name of the task executor to use. Defaults to
- * {@literal taskExecutor}
+ * Set the observation registry to use in batch artifacts.
+ * @return the bean name of the observation registry to use. Defaults to
+ * {@literal observationRegistry}
*/
- String taskExecutorRef() default "taskExecutor";
+ String observationRegistryRef() default "observationRegistry";
/**
- * Set the conversion service to use in the job repository and job explorer. This
- * service is used to convert job parameters from String literal to typed values and
- * vice versa.
- * @return the bean name of the conversion service to use. Defaults to
- * {@literal conversionService}
+ * Set the transaction manager to use in the job operator.
+ * @return the bean name of the transaction manager to use. Defaults to
+ * {@literal transactionManager}
*/
- String conversionServiceRef() default "conversionService";
+ String transactionManagerRef() default "transactionManager";
/**
* Set the {@link JobParametersConverter} to use in the job operator.
* @return the bean name of the job parameters converter to use. Defaults to
* {@literal jobParametersConverter}
+ * @deprecated since 6.0 with no replacement. Scheduled for removal in 6.2 or later
*/
+ @Deprecated(since = "6.0", forRemoval = true)
String jobParametersConverterRef() default "jobParametersConverter";
}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/EnableJdbcJobRepository.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/EnableJdbcJobRepository.java
new file mode 100644
index 0000000000..012e317e1b
--- /dev/null
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/EnableJdbcJobRepository.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright 2012-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.batch.core.configuration.annotation;
+
+import org.springframework.batch.core.repository.dao.AbstractJdbcBatchMetadataDao;
+import org.springframework.batch.support.DatabaseType;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.jdbc.core.JdbcOperations;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.annotation.Isolation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.sql.Types;
+
+import javax.sql.DataSource;
+
+/**
+ * Annotation to enable a JDBC-based infrastructure in a Spring Batch application.
+ *
+ * This annotation should be used on a {@link Configuration @Configuration} class
+ * annotated with {@link EnableBatchProcessing }. It will automatically configure the
+ * necessary beans for a JDBC-based infrastructure, including a job repository.
+ *
+ * The default configuration assumes that a {@link DataSource} bean named "dataSource" and
+ * a {@link PlatformTransactionManager} bean named "transactionManager" are available in
+ * the application context.
+ *
+ * @author Mahmoud Ben Hassine
+ * @since 6.0
+ * @see EnableBatchProcessing
+ */
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface EnableJdbcJobRepository {
+
+ /**
+ * Set the type of the data source to use in the job repository. The default type will
+ * be introspected from the datasource's metadata.
+ * @since 5.1
+ * @see DatabaseType
+ * @return the type of data source.
+ */
+ String databaseType() default "";
+
+ /**
+ * Set the value of the {@code validateTransactionState} parameter. Defaults to
+ * {@code true}.
+ * @return true if the transaction state should be validated, false otherwise
+ */
+ boolean validateTransactionState() default true;
+
+ /**
+ * Set the isolation level for create parameter value. Defaults to
+ * {@link Isolation#SERIALIZABLE}.
+ * @return the value of the isolation level for create parameter
+ */
+ Isolation isolationLevelForCreate() default Isolation.SERIALIZABLE;
+
+ /**
+ * The charset to use in the job repository
+ * @return the charset to use. Defaults to {@literal UTF-8}.
+ */
+ String charset() default "UTF-8";
+
+ /**
+ * The Batch tables prefix. Defaults to
+ * {@link AbstractJdbcBatchMetadataDao#DEFAULT_TABLE_PREFIX}.
+ * @return the Batch table prefix
+ */
+ String tablePrefix() default AbstractJdbcBatchMetadataDao.DEFAULT_TABLE_PREFIX;
+
+ /**
+ * The maximum length of exit messages in the database. Defaults to
+ * {@link AbstractJdbcBatchMetadataDao#DEFAULT_EXIT_MESSAGE_LENGTH}
+ * @return the maximum length of exit messages in the database
+ */
+ int maxVarCharLength() default AbstractJdbcBatchMetadataDao.DEFAULT_EXIT_MESSAGE_LENGTH;
+
+ /**
+ * The type of large objects.
+ * @return the type of large objects.
+ */
+ int clobType() default Types.CLOB;
+
+ /**
+ * Set the data source to use in the job repository.
+ * @return the bean name of the data source to use. Default to {@literal dataSource}.
+ */
+ String dataSourceRef() default "dataSource";
+
+ /**
+ * Set the {@link PlatformTransactionManager} to use in the job repository.
+ * @return the bean name of the transaction manager to use. Defaults to
+ * {@literal transactionManager}
+ */
+ String transactionManagerRef() default "transactionManager";
+
+ /**
+ * Set the {@link JdbcOperations} to use in the job repository.
+ * @return the bean name of the {@link JdbcOperations} to use. Defaults to
+ * {@literal jdbcTemplate}.
+ */
+ String jdbcOperationsRef() default "jdbcTemplate";
+
+ /**
+ * The generator that determines a unique key for identifying job instance objects
+ * @return the bean name of the job key generator to use. Defaults to
+ * {@literal jobKeyGenerator}.
+ *
+ * @since 5.1
+ */
+ String jobKeyGeneratorRef() default "jobKeyGenerator";
+
+ /**
+ * Set the execution context serializer to use in the job repository.
+ * @return the bean name of the execution context serializer to use. Default to
+ * {@literal executionContextSerializer}.
+ */
+ String executionContextSerializerRef() default "executionContextSerializer";
+
+ /**
+ * The incrementer factory to use in various DAOs.
+ * @return the bean name of the incrementer factory to use. Defaults to
+ * {@literal incrementerFactory}.
+ */
+ String incrementerFactoryRef() default "incrementerFactory";
+
+ /**
+ * Set the conversion service to use in the job repository. This service is used to
+ * convert job parameters from String literal to typed values and vice versa.
+ * @return the bean name of the conversion service to use. Defaults to
+ * {@literal conversionService}
+ */
+ String conversionServiceRef() default "conversionService";
+
+}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/EnableMongoJobRepository.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/EnableMongoJobRepository.java
new file mode 100644
index 0000000000..f4233eb1aa
--- /dev/null
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/EnableMongoJobRepository.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2012-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.batch.core.configuration.annotation;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.mongodb.MongoTransactionManager;
+import org.springframework.data.mongodb.core.MongoOperations;
+import org.springframework.transaction.annotation.Isolation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * * Annotation to enable a MongoDB-based job repository in a Spring Batch application.
+ *
+ * This annotation should be used on a {@link Configuration @Configuration} class
+ * annotated with {@link EnableBatchProcessing}. It will automatically configure the
+ * necessary beans for a MongoDB-based infrastructure, including a job repository.
+ *
+ * The default configuration assumes that a {@link MongoOperations} bean named
+ * "mongoTemplate" and a {@link MongoTransactionManager} bean named "transactionManager"
+ * are available in the application context.
+ *
+ * @author Mahmoud Ben Hassine
+ * @since 6.0
+ * @see EnableBatchProcessing
+ */
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface EnableMongoJobRepository {
+
+ String mongoOperationsRef() default "mongoTemplate";
+
+ /**
+ * Set the {@link MongoTransactionManager} to use in the job repository.
+ * @return the bean name of the transaction manager to use. Defaults to
+ * {@literal transactionManager}
+ */
+ String transactionManagerRef() default "transactionManager";
+
+ /**
+ * Set the isolation level for create parameter value. Defaults to
+ * {@link Isolation#SERIALIZABLE}.
+ * @return the value of the isolation level for create parameter
+ */
+ Isolation isolationLevelForCreate() default Isolation.SERIALIZABLE;
+
+ /**
+ * Set the value of the {@code validateTransactionState} parameter. Defaults to
+ * {@code true}.
+ * @return true if the transaction state should be validated, false otherwise
+ */
+ boolean validateTransactionState() default true;
+
+ /**
+ * The generator that determines a unique key for identifying job instance objects
+ * @return the bean name of the job key generator to use. Defaults to
+ * {@literal jobKeyGenerator}.
+ *
+ */
+ String jobKeyGeneratorRef() default "jobKeyGenerator";
+
+}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/AbstractApplicationContextFactory.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/AbstractApplicationContextFactory.java
index 535886f96c..3466e93cd5 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/AbstractApplicationContextFactory.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/AbstractApplicationContextFactory.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006-2023 the original author or authors.
+ * Copyright 2006-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -47,7 +47,10 @@
* every time it is requested. It is lazily initialized and cached. Clients should ensure
* that it is closed when it is no longer needed. If a path is not set, the parent is
* always returned.
+ *
+ * @deprecated since 6.0 with no replacement. Scheduled for removal in 6.2 or later.
*/
+@Deprecated(since = "6.0", forRemoval = true)
public abstract class AbstractApplicationContextFactory implements ApplicationContextFactory, ApplicationContextAware {
private static final Log logger = LogFactory.getLog(AbstractApplicationContextFactory.class);
@@ -196,13 +199,11 @@ protected void prepareContext(ConfigurableApplicationContext parent, Configurabl
protected void prepareBeanFactory(ConfigurableListableBeanFactory parent,
ConfigurableListableBeanFactory beanFactory) {
if (copyConfiguration && parent != null) {
- List parentPostProcessors = new ArrayList<>();
- List childPostProcessors = new ArrayList<>();
-
- childPostProcessors.addAll(beanFactory instanceof AbstractBeanFactory
- ? ((AbstractBeanFactory) beanFactory).getBeanPostProcessors() : new ArrayList<>());
- parentPostProcessors.addAll(parent instanceof AbstractBeanFactory
- ? ((AbstractBeanFactory) parent).getBeanPostProcessors() : new ArrayList<>());
+ List childPostProcessors = new ArrayList<>(
+ beanFactory instanceof AbstractBeanFactory factory ? factory.getBeanPostProcessors()
+ : new ArrayList<>());
+ List parentPostProcessors = new ArrayList<>(parent instanceof AbstractBeanFactory factory
+ ? factory.getBeanPostProcessors() : new ArrayList<>());
try {
Class> applicationContextAwareProcessorClass = ClassUtils.forName(
@@ -237,8 +238,8 @@ protected void prepareBeanFactory(ConfigurableListableBeanFactory parent,
beanFactory.copyConfigurationFrom(parent);
- List beanPostProcessors = beanFactory instanceof AbstractBeanFactory
- ? ((AbstractBeanFactory) beanFactory).getBeanPostProcessors() : new ArrayList<>();
+ List beanPostProcessors = beanFactory instanceof AbstractBeanFactory abstractBeanFactory
+ ? abstractBeanFactory.getBeanPostProcessors() : new ArrayList<>();
beanPostProcessors.clear();
beanPostProcessors.addAll(aggregatedPostProcessors);
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/ApplicationContextFactory.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/ApplicationContextFactory.java
index 7647661970..2ad87be583 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/ApplicationContextFactory.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/ApplicationContextFactory.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006-2022 the original author or authors.
+ * Copyright 2006-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,7 +16,7 @@
package org.springframework.batch.core.configuration.support;
-import org.springframework.batch.core.Job;
+import org.springframework.batch.core.job.Job;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
@@ -25,7 +25,10 @@
* primarily useful when creating a new {@link ApplicationContext} for a {@link Job}.
*
* @author Lucas Ward
+ * @author Mahmoud Ben Hassine
+ * @deprecated since 6.0 with no replacement. Scheduled for removal in 6.2 or later.
*/
+@Deprecated(since = "6.0", forRemoval = true)
public interface ApplicationContextFactory {
ConfigurableApplicationContext createApplicationContext();
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/ApplicationContextJobFactory.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/ApplicationContextJobFactory.java
index a60c6b9615..8167a837dd 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/ApplicationContextJobFactory.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/ApplicationContextJobFactory.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006-2023 the original author or authors.
+ * Copyright 2006-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,7 +15,7 @@
*/
package org.springframework.batch.core.configuration.support;
-import org.springframework.batch.core.Job;
+import org.springframework.batch.core.job.Job;
import org.springframework.batch.core.configuration.JobFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
@@ -26,8 +26,10 @@
*
* @author Dave Syer
* @author Mahmoud Ben Hassine
+ * @deprecated since 6.0 with no replacement. Scheduled for removal in 6.2 or later.
*
*/
+@Deprecated(since = "6.0", forRemoval = true)
public class ApplicationContextJobFactory implements JobFactory {
private final Job job;
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/AutomaticJobRegistrar.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/AutomaticJobRegistrar.java
index e8496b83d6..76d2345bae 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/AutomaticJobRegistrar.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/AutomaticJobRegistrar.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006-2023 the original author or authors.
+ * Copyright 2006-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,7 +20,7 @@
import java.util.Arrays;
import java.util.Collection;
-import org.springframework.batch.core.Job;
+import org.springframework.batch.core.job.Job;
import org.springframework.batch.core.configuration.DuplicateJobException;
import org.springframework.batch.core.configuration.JobRegistry;
import org.springframework.beans.factory.InitializingBean;
@@ -42,7 +42,9 @@
* @author Dave Syer
* @author Mahmoud Ben Hassine
* @since 2.1
+ * @deprecated since 6.0 with no replacement. Scheduled for removal in 6.2 or later.
*/
+@Deprecated(since = "6.0", forRemoval = true)
public class AutomaticJobRegistrar implements Ordered, SmartLifecycle, ApplicationContextAware, InitializingBean {
private final Collection applicationContextFactories = new ArrayList<>();
@@ -79,8 +81,8 @@ public void setApplicationContext(ApplicationContext applicationContext) {
* use
*/
public void addApplicationContextFactory(ApplicationContextFactory applicationContextFactory) {
- if (applicationContextFactory instanceof ApplicationContextAware) {
- ((ApplicationContextAware) applicationContextFactory).setApplicationContext(applicationContext);
+ if (applicationContextFactory instanceof ApplicationContextAware applicationContextAware) {
+ applicationContextAware.setApplicationContext(applicationContext);
}
this.applicationContextFactories.add(applicationContextFactory);
}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/ClasspathXmlApplicationContextsFactoryBean.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/ClasspathXmlApplicationContextsFactoryBean.java
index 316c364527..58bac350c0 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/ClasspathXmlApplicationContextsFactoryBean.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/ClasspathXmlApplicationContextsFactoryBean.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006-2022 the original author or authors.
+ * Copyright 2006-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -35,8 +35,10 @@
*
* @author Dave Syer
* @author Mahmoud Ben Hassine
- *
+ * @deprecated since 6.0 with no replacement. Scheduled for removal in 6.2 or later.
*/
+@SuppressWarnings("removal")
+@Deprecated(since = "6.0", forRemoval = true)
public class ClasspathXmlApplicationContextsFactoryBean
implements FactoryBean, ApplicationContextAware {
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/DefaultBatchConfiguration.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/DefaultBatchConfiguration.java
index 365af8487c..f47ed31559 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/DefaultBatchConfiguration.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/DefaultBatchConfiguration.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2024 the original author or authors.
+ * Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,77 +15,41 @@
*/
package org.springframework.batch.core.configuration.support;
-import java.nio.charset.Charset;
-import java.nio.charset.StandardCharsets;
-import java.sql.Types;
+import io.micrometer.observation.ObservationRegistry;
-import javax.sql.DataSource;
-
-import org.springframework.batch.core.DefaultJobKeyGenerator;
-import org.springframework.batch.core.JobInstance;
-import org.springframework.batch.core.JobKeyGenerator;
+import org.springframework.batch.core.configuration.DuplicateJobException;
+import org.springframework.batch.core.configuration.annotation.BatchObservabilityBeanPostProcessor;
+import org.springframework.batch.core.job.Job;
import org.springframework.batch.core.configuration.BatchConfigurationException;
import org.springframework.batch.core.configuration.JobRegistry;
-import org.springframework.batch.core.converter.DateToStringConverter;
import org.springframework.batch.core.converter.DefaultJobParametersConverter;
import org.springframework.batch.core.converter.JobParametersConverter;
-import org.springframework.batch.core.converter.LocalDateTimeToStringConverter;
-import org.springframework.batch.core.converter.LocalDateToStringConverter;
-import org.springframework.batch.core.converter.LocalTimeToStringConverter;
-import org.springframework.batch.core.converter.StringToDateConverter;
-import org.springframework.batch.core.converter.StringToLocalDateConverter;
-import org.springframework.batch.core.converter.StringToLocalDateTimeConverter;
-import org.springframework.batch.core.converter.StringToLocalTimeConverter;
-import org.springframework.batch.core.explore.JobExplorer;
-import org.springframework.batch.core.explore.support.JobExplorerFactoryBean;
-import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.launch.JobOperator;
import org.springframework.batch.core.launch.support.JobOperatorFactoryBean;
-import org.springframework.batch.core.launch.support.TaskExecutorJobLauncher;
-import org.springframework.batch.core.repository.ExecutionContextSerializer;
+import org.springframework.batch.core.launch.support.TaskExecutorJobOperator;
import org.springframework.batch.core.repository.JobRepository;
-import org.springframework.batch.core.repository.dao.AbstractJdbcBatchMetadataDao;
-import org.springframework.batch.core.repository.dao.DefaultExecutionContextSerializer;
-import org.springframework.batch.core.repository.dao.JdbcExecutionContextDao;
-import org.springframework.batch.core.repository.dao.JdbcJobExecutionDao;
-import org.springframework.batch.core.repository.dao.JdbcStepExecutionDao;
-import org.springframework.batch.core.repository.support.JobRepositoryFactoryBean;
-import org.springframework.batch.item.database.support.DataFieldMaxValueIncrementerFactory;
-import org.springframework.batch.item.database.support.DefaultDataFieldMaxValueIncrementerFactory;
-import org.springframework.batch.support.DatabaseType;
+import org.springframework.batch.core.repository.support.ResourcelessJobRepository;
+import org.springframework.batch.support.transaction.ResourcelessTransactionManager;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
-import org.springframework.core.convert.support.ConfigurableConversionService;
-import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.task.SyncTaskExecutor;
import org.springframework.core.task.TaskExecutor;
-import org.springframework.jdbc.core.JdbcOperations;
-import org.springframework.jdbc.core.JdbcTemplate;
-import org.springframework.jdbc.support.MetaDataAccessException;
-import org.springframework.jdbc.support.incrementer.DataFieldMaxValueIncrementer;
-import org.springframework.jdbc.support.lob.DefaultLobHandler;
-import org.springframework.jdbc.support.lob.LobHandler;
import org.springframework.transaction.PlatformTransactionManager;
-import org.springframework.transaction.annotation.Isolation;
/**
- * Base {@link Configuration} class that provides common JDBC-based infrastructure beans
- * for enabling and using Spring Batch.
+ * Base {@link Configuration} class that provides common infrastructure beans for enabling
+ * and using Spring Batch.
*
* This configuration class configures and registers the following beans in the
* application context:
*
*
- * - a {@link JobRepository} named "jobRepository"
- * - a {@link JobExplorer} named "jobExplorer"
- * - a {@link JobLauncher} named "jobLauncher"
- * - a {@link JobRegistry} named "jobRegistry"
- * - a {@link JobOperator} named "JobOperator"
- * - a {@link JobRegistryBeanPostProcessor} named "jobRegistryBeanPostProcessor"
+ * - a {@link ResourcelessJobRepository} named "jobRepository"
+ * - a {@link TaskExecutorJobOperator} named "jobOperator"
* - a {@link org.springframework.batch.core.scope.StepScope} named "stepScope"
* - a {@link org.springframework.batch.core.scope.JobScope} named "jobScope"
*
@@ -113,7 +77,7 @@
* @since 5.0
*/
@Configuration(proxyBeanMethods = false)
-@Import(ScopeConfiguration.class)
+@Import({ ScopeConfiguration.class, BatchObservabilityBeanPostProcessor.class })
public class DefaultBatchConfiguration implements ApplicationContextAware {
protected ApplicationContext applicationContext;
@@ -124,122 +88,19 @@ public void setApplicationContext(ApplicationContext applicationContext) throws
}
@Bean
- public JobRepository jobRepository() throws BatchConfigurationException {
- JobRepositoryFactoryBean jobRepositoryFactoryBean = new JobRepositoryFactoryBean();
- try {
- jobRepositoryFactoryBean.setDataSource(getDataSource());
- jobRepositoryFactoryBean.setTransactionManager(getTransactionManager());
- jobRepositoryFactoryBean.setDatabaseType(getDatabaseType());
- jobRepositoryFactoryBean.setIncrementerFactory(getIncrementerFactory());
- jobRepositoryFactoryBean.setJobKeyGenerator(getJobKeyGenerator());
- jobRepositoryFactoryBean.setClobType(getClobType());
- jobRepositoryFactoryBean.setTablePrefix(getTablePrefix());
- jobRepositoryFactoryBean.setSerializer(getExecutionContextSerializer());
- jobRepositoryFactoryBean.setConversionService(getConversionService());
- jobRepositoryFactoryBean.setJdbcOperations(getJdbcOperations());
- jobRepositoryFactoryBean.setLobHandler(getLobHandler());
- jobRepositoryFactoryBean.setCharset(getCharset());
- jobRepositoryFactoryBean.setMaxVarCharLength(getMaxVarCharLength());
- jobRepositoryFactoryBean.setIsolationLevelForCreateEnum(getIsolationLevelForCreate());
- jobRepositoryFactoryBean.setValidateTransactionState(getValidateTransactionState());
- jobRepositoryFactoryBean.afterPropertiesSet();
- return jobRepositoryFactoryBean.getObject();
- }
- catch (Exception e) {
- throw new BatchConfigurationException("Unable to configure the default job repository", e);
- }
- }
-
- /**
- * Define a job launcher.
- * @return a job launcher
- * @throws BatchConfigurationException if unable to configure the default job launcher
- * @deprecated Since 5.2. Use {@link #jobLauncher(JobRepository)} instead
- */
- @Deprecated(forRemoval = true)
- public JobLauncher jobLauncher() throws BatchConfigurationException {
- return jobLauncher(jobRepository());
- }
-
- /**
- * Define a job launcher bean.
- * @param jobRepository the job repository
- * @return a job launcher
- * @throws BatchConfigurationException if unable to configure the default job launcher
- * @since 5.2
- */
- @Bean
- public JobLauncher jobLauncher(JobRepository jobRepository) throws BatchConfigurationException {
- TaskExecutorJobLauncher taskExecutorJobLauncher = new TaskExecutorJobLauncher();
- taskExecutorJobLauncher.setJobRepository(jobRepository);
- taskExecutorJobLauncher.setTaskExecutor(getTaskExecutor());
- try {
- taskExecutorJobLauncher.afterPropertiesSet();
- return taskExecutorJobLauncher;
- }
- catch (Exception e) {
- throw new BatchConfigurationException("Unable to configure the default job launcher", e);
- }
- }
-
- @Bean
- public JobExplorer jobExplorer() throws BatchConfigurationException {
- JobExplorerFactoryBean jobExplorerFactoryBean = new JobExplorerFactoryBean();
- jobExplorerFactoryBean.setDataSource(getDataSource());
- jobExplorerFactoryBean.setTransactionManager(getTransactionManager());
- jobExplorerFactoryBean.setJdbcOperations(getJdbcOperations());
- jobExplorerFactoryBean.setJobKeyGenerator(getJobKeyGenerator());
- jobExplorerFactoryBean.setCharset(getCharset());
- jobExplorerFactoryBean.setTablePrefix(getTablePrefix());
- jobExplorerFactoryBean.setLobHandler(getLobHandler());
- jobExplorerFactoryBean.setConversionService(getConversionService());
- jobExplorerFactoryBean.setSerializer(getExecutionContextSerializer());
- try {
- jobExplorerFactoryBean.afterPropertiesSet();
- return jobExplorerFactoryBean.getObject();
- }
- catch (Exception e) {
- throw new BatchConfigurationException("Unable to configure the default job explorer", e);
- }
- }
-
- @Bean
- public JobRegistry jobRegistry() throws BatchConfigurationException {
- return new MapJobRegistry();
+ public JobRepository jobRepository() {
+ return new ResourcelessJobRepository();
}
- /**
- * Define a job operator.
- * @return a job operator
- * @throws BatchConfigurationException if unable to configure the default job operator
- * @deprecated Since 5.2. Use
- * {@link #jobOperator(JobRepository, JobExplorer, JobRegistry, JobLauncher)} instead
- */
- @Deprecated(forRemoval = true)
- public JobOperator jobOperator() throws BatchConfigurationException {
- return jobOperator(jobRepository(), jobExplorer(), jobRegistry(), jobLauncher());
- }
-
- /**
- * Define a job operator bean.
- * @param jobRepository a job repository
- * @param jobExplorer a job explorer
- * @param jobRegistry a job registry
- * @param jobLauncher a job launcher
- * @return a job operator
- * @throws BatchConfigurationException if unable to configure the default job operator
- * @since 5.2
- */
@Bean
- public JobOperator jobOperator(JobRepository jobRepository, JobExplorer jobExplorer, JobRegistry jobRegistry,
- JobLauncher jobLauncher) throws BatchConfigurationException {
+ public JobOperator jobOperator(JobRepository jobRepository) throws BatchConfigurationException {
JobOperatorFactoryBean jobOperatorFactoryBean = new JobOperatorFactoryBean();
- jobOperatorFactoryBean.setTransactionManager(getTransactionManager());
jobOperatorFactoryBean.setJobRepository(jobRepository);
- jobOperatorFactoryBean.setJobExplorer(jobExplorer);
- jobOperatorFactoryBean.setJobRegistry(jobRegistry);
- jobOperatorFactoryBean.setJobLauncher(jobLauncher);
+ jobOperatorFactoryBean.setJobRegistry(getJobRegistry());
+ jobOperatorFactoryBean.setTransactionManager(getTransactionManager());
+ jobOperatorFactoryBean.setObservationRegistry(getObservationRegistry());
jobOperatorFactoryBean.setJobParametersConverter(getJobParametersConverter());
+ jobOperatorFactoryBean.setTaskExecutor(getTaskExecutor());
try {
jobOperatorFactoryBean.afterPropertiesSet();
return jobOperatorFactoryBean.getObject();
@@ -249,221 +110,42 @@ public JobOperator jobOperator(JobRepository jobRepository, JobExplorer jobExplo
}
}
- /**
- * Defines a {@link JobRegistryBeanPostProcessor}.
- * @return a {@link JobRegistryBeanPostProcessor}
- * @throws BatchConfigurationException if unable to register the bean
- * @since 5.1
- * @deprecated Use {@link #jobRegistrySmartInitializingSingleton(JobRegistry)} instead
- */
- @Deprecated(forRemoval = true)
- public JobRegistryBeanPostProcessor jobRegistryBeanPostProcessor() throws BatchConfigurationException {
- JobRegistryBeanPostProcessor jobRegistryBeanPostProcessor = new JobRegistryBeanPostProcessor();
- jobRegistryBeanPostProcessor.setJobRegistry(jobRegistry());
- try {
- jobRegistryBeanPostProcessor.afterPropertiesSet();
- return jobRegistryBeanPostProcessor;
- }
- catch (Exception e) {
- throw new BatchConfigurationException("Unable to configure the default job registry BeanPostProcessor", e);
- }
- }
-
- /**
- * Define a {@link JobRegistrySmartInitializingSingleton} bean.
- * @param jobRegistry the job registry to populate
- * @throws BatchConfigurationException if unable to register the bean
- * @return a bean of type {@link JobRegistrySmartInitializingSingleton}
- * @since 5.2
- */
- @Bean
- public JobRegistrySmartInitializingSingleton jobRegistrySmartInitializingSingleton(JobRegistry jobRegistry)
- throws BatchConfigurationException {
- JobRegistrySmartInitializingSingleton jobRegistrySmartInitializingSingleton = new JobRegistrySmartInitializingSingleton();
- jobRegistrySmartInitializingSingleton.setJobRegistry(jobRegistry);
- try {
- jobRegistrySmartInitializingSingleton.afterPropertiesSet();
- return jobRegistrySmartInitializingSingleton;
- }
- catch (Exception e) {
- throw new BatchConfigurationException(
- "Unable to configure the default job registry SmartInitializingSingleton", e);
- }
- }
-
- /*
- * Getters to customize the configuration of infrastructure beans
- */
-
- /**
- * Return the data source to use for Batch meta-data. Defaults to the bean of type
- * {@link DataSource} and named "dataSource" in the application context.
- * @return The data source to use for Batch meta-data
- */
- protected DataSource getDataSource() {
- String errorMessage = " To use the default configuration, a data source bean named 'dataSource'"
- + " should be defined in the application context but none was found. Override getDataSource()"
- + " to provide the data source to use for Batch meta-data.";
- if (this.applicationContext.getBeansOfType(DataSource.class).isEmpty()) {
- throw new BatchConfigurationException(
- "Unable to find a DataSource bean in the application context." + errorMessage);
- }
- else {
- if (!this.applicationContext.containsBean("dataSource")) {
- throw new BatchConfigurationException(errorMessage);
+ protected JobRegistry getJobRegistry() {
+ MapJobRegistry jobRegistry = new MapJobRegistry();
+ this.applicationContext.getBeansOfType(Job.class).values().forEach(job -> {
+ try {
+ jobRegistry.register(job);
}
- }
- return this.applicationContext.getBean("dataSource", DataSource.class);
- }
-
- /**
- * Return the transaction manager to use for the job repository. Defaults to the bean
- * of type {@link PlatformTransactionManager} and named "transactionManager" in the
- * application context.
- * @return The transaction manager to use for the job repository
- */
- protected PlatformTransactionManager getTransactionManager() {
- String errorMessage = " To use the default configuration, a transaction manager bean named 'transactionManager'"
- + " should be defined in the application context but none was found. Override getTransactionManager()"
- + " to provide the transaction manager to use for the job repository.";
- if (this.applicationContext.getBeansOfType(PlatformTransactionManager.class).isEmpty()) {
- throw new BatchConfigurationException(
- "Unable to find a PlatformTransactionManager bean in the application context." + errorMessage);
- }
- else {
- if (!this.applicationContext.containsBean("transactionManager")) {
- throw new BatchConfigurationException(errorMessage);
+ catch (DuplicateJobException e) {
+ throw new BatchConfigurationException(e);
}
- }
- return this.applicationContext.getBean("transactionManager", PlatformTransactionManager.class);
- }
-
- /**
- * Return the value of the {@code validateTransactionState} parameter. Defaults to
- * {@code true}.
- * @return true if the transaction state should be validated, false otherwise
- */
- protected boolean getValidateTransactionState() {
- return true;
+ });
+ return jobRegistry;
}
/**
- * Return the transaction isolation level when creating job executions. Defaults to
- * {@link Isolation#SERIALIZABLE}.
- * @return the transaction isolation level when creating job executions
+ * Return the {@link ObservationRegistry} to use for the job operator. Defaults to
+ * {@link ObservationRegistry#NOOP}.
+ * @return The ObservationRegistry to use for the job operator
+ * @since 6.0
*/
- protected Isolation getIsolationLevelForCreate() {
- return Isolation.SERIALIZABLE;
+ protected ObservationRegistry getObservationRegistry() {
+ return ObservationRegistry.NOOP;
}
/**
- * Return the length of long string columns in database. Do not override this if you
- * haven't modified the schema. Note this value will be used for the exit message in
- * both {@link JdbcJobExecutionDao} and {@link JdbcStepExecutionDao} and also the
- * short version of the execution context in {@link JdbcExecutionContextDao} . For
- * databases with multi-byte character sets this number can be smaller (by up to a
- * factor of 2 for 2-byte characters) than the declaration of the column length in the
- * DDL for the tables. Defaults to
- * {@link AbstractJdbcBatchMetadataDao#DEFAULT_EXIT_MESSAGE_LENGTH}
+ * Return the transaction manager to use for the job operator. Defaults to
+ * {@link ResourcelessTransactionManager}.
+ * @return The transaction manager to use for the job operator
*/
- protected int getMaxVarCharLength() {
- return AbstractJdbcBatchMetadataDao.DEFAULT_EXIT_MESSAGE_LENGTH;
- }
-
- /**
- * Return the prefix of Batch meta-data tables. Defaults to
- * {@link AbstractJdbcBatchMetadataDao#DEFAULT_TABLE_PREFIX}.
- * @return the prefix of meta-data tables
- */
- protected String getTablePrefix() {
- return AbstractJdbcBatchMetadataDao.DEFAULT_TABLE_PREFIX;
- }
-
- /**
- * Return the {@link Charset} to use when serializing/deserializing the execution
- * context. Defaults to "UTF-8".
- * @return the charset to use when serializing/deserializing the execution context
- */
- protected Charset getCharset() {
- return StandardCharsets.UTF_8;
- }
-
- /**
- * A special handler for large objects. The default is usually fine, except for some
- * (usually older) versions of Oracle.
- * @return the {@link LobHandler} to use
- * @deprecated Since 5.2 with no replacement. Scheduled for removal in v6
- */
- @Deprecated(since = "5.2.0", forRemoval = true)
- protected LobHandler getLobHandler() {
- return new DefaultLobHandler();
- }
-
- /**
- * Return the {@link JdbcOperations}. If this property is not overridden, a new
- * {@link JdbcTemplate} will be created for the configured data source by default.
- * @return the {@link JdbcOperations} to use
- */
- protected JdbcOperations getJdbcOperations() {
- return new JdbcTemplate(getDataSource());
- }
-
- /**
- * A custom implementation of the {@link ExecutionContextSerializer}. The default, if
- * not injected, is the {@link DefaultExecutionContextSerializer}.
- * @return the serializer to use to serialize/deserialize the execution context
- */
- protected ExecutionContextSerializer getExecutionContextSerializer() {
- return new DefaultExecutionContextSerializer();
- }
-
- /**
- * Return the value from {@link java.sql.Types} class to indicate the type to use for
- * a CLOB
- * @return the value from {@link java.sql.Types} class to indicate the type to use for
- * a CLOB
- */
- protected int getClobType() {
- return Types.CLOB;
- }
-
- /**
- * Return the factory for creating {@link DataFieldMaxValueIncrementer}
- * implementations used to increment entity IDs in meta-data tables.
- * @return the factory for creating {@link DataFieldMaxValueIncrementer}
- * implementations.
- */
- protected DataFieldMaxValueIncrementerFactory getIncrementerFactory() {
- return new DefaultDataFieldMaxValueIncrementerFactory(getDataSource());
- }
-
- /**
- * A custom implementation of the {@link JobKeyGenerator}. The default, if not
- * injected, is the {@link DefaultJobKeyGenerator}.
- * @return the generator that creates the key used in identifying {@link JobInstance}
- * objects
- * @since 5.1
- */
- protected JobKeyGenerator getJobKeyGenerator() {
- return new DefaultJobKeyGenerator();
- }
-
- /**
- * Return the database type. The default will be introspected from the JDBC meta-data
- * of the data source.
- * @return the database type
- * @throws MetaDataAccessException if an error occurs when trying to get the database
- * type of JDBC meta-data
- *
- */
- protected String getDatabaseType() throws MetaDataAccessException {
- return DatabaseType.fromMetaData(getDataSource()).name();
+ protected PlatformTransactionManager getTransactionManager() {
+ return new ResourcelessTransactionManager();
}
/**
- * Return the {@link TaskExecutor} to use in the the job launcher. Defaults to
+ * Return the {@link TaskExecutor} to use in the job operator. Defaults to
* {@link SyncTaskExecutor}.
- * @return the {@link TaskExecutor} to use in the the job launcher.
+ * @return the {@link TaskExecutor} to use in the job operator.
*/
protected TaskExecutor getTaskExecutor() {
return new SyncTaskExecutor();
@@ -473,28 +155,12 @@ protected TaskExecutor getTaskExecutor() {
* Return the {@link JobParametersConverter} to use in the job operator. Defaults to
* {@link DefaultJobParametersConverter}
* @return the {@link JobParametersConverter} to use in the job operator.
+ * @deprecated since 6.0 with no replacement and scheduled for removal in 6.2 or
+ * later.
*/
+ @Deprecated(since = "6.0", forRemoval = true)
protected JobParametersConverter getJobParametersConverter() {
return new DefaultJobParametersConverter();
}
- /**
- * Return the conversion service to use in the job repository and job explorer. This
- * service is used to convert job parameters from String literal to typed values and
- * vice versa.
- * @return the {@link ConfigurableConversionService} to use.
- */
- protected ConfigurableConversionService getConversionService() {
- DefaultConversionService conversionService = new DefaultConversionService();
- conversionService.addConverter(new DateToStringConverter());
- conversionService.addConverter(new StringToDateConverter());
- conversionService.addConverter(new LocalDateToStringConverter());
- conversionService.addConverter(new StringToLocalDateConverter());
- conversionService.addConverter(new LocalTimeToStringConverter());
- conversionService.addConverter(new StringToLocalTimeConverter());
- conversionService.addConverter(new LocalDateTimeToStringConverter());
- conversionService.addConverter(new StringToLocalDateTimeConverter());
- return conversionService;
- }
-
}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/DefaultJobLoader.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/DefaultJobLoader.java
index 4dde8ea152..aa14354826 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/DefaultJobLoader.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/DefaultJobLoader.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006-2023 the original author or authors.
+ * Copyright 2006-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,10 +24,9 @@
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
-import org.springframework.batch.core.Job;
-import org.springframework.batch.core.Step;
+import org.springframework.batch.core.job.Job;
+import org.springframework.batch.core.step.Step;
import org.springframework.batch.core.configuration.DuplicateJobException;
-import org.springframework.batch.core.configuration.JobFactory;
import org.springframework.batch.core.configuration.JobRegistry;
import org.springframework.batch.core.configuration.StepRegistry;
import org.springframework.batch.core.launch.NoSuchJobException;
@@ -47,7 +46,9 @@
* @author Dave Syer
* @author Stephane Nicoll
* @author Mahmoud Ben Hassine
+ * @deprecated since 6.0 with no replacement. Scheduled for removal in 6.2 or later.
*/
+@Deprecated(since = "6.0", forRemoval = true)
public class DefaultJobLoader implements JobLoader, InitializingBean {
private static final Log logger = LogFactory.getLog(DefaultJobLoader.class);
@@ -173,7 +174,7 @@ private Collection doLoad(ApplicationContextFactory factory, boolean unregi
if (!autoRegistrationDetected) {
- Job job = (Job) context.getBean(name);
+ Job job = context.getBean(name, Job.class);
String jobName = job.getName();
// On reload try to unregister first
@@ -251,15 +252,14 @@ private Collection getSteps(final StepLocator stepLocator, final Applicati
* @throws DuplicateJobException if that job is already registered
*/
private void doRegister(ConfigurableApplicationContext context, Job job) throws DuplicateJobException {
- final JobFactory jobFactory = new ReferenceJobFactory(job);
- jobRegistry.register(jobFactory);
+ jobRegistry.register(job);
if (stepRegistry != null) {
- if (!(job instanceof StepLocator)) {
+ if (!(job instanceof StepLocator stepLocator)) {
throw new UnsupportedOperationException("Cannot locate steps from a Job that is not a StepLocator: job="
+ job.getName() + " does not implement StepLocator");
}
- stepRegistry.register(job.getName(), getSteps((StepLocator) job, context));
+ stepRegistry.register(job.getName(), getSteps(stepLocator, context));
}
}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/GenericApplicationContextFactory.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/GenericApplicationContextFactory.java
index a9074f6671..0e69248c72 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/GenericApplicationContextFactory.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/GenericApplicationContextFactory.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006-2023 the original author or authors.
+ * Copyright 2006-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -39,7 +39,10 @@
* the child {@link ApplicationContext} is returned. The child context is not re-created
* every time it is requested. It is lazily initialized and cached. Clients should ensure
* that it is closed when it is no longer needed.
+ *
+ * @deprecated since 6.0 with no replacement. Scheduled for removal in 6.2 or later.
*/
+@Deprecated(since = "6.0", forRemoval = true)
public class GenericApplicationContextFactory extends AbstractApplicationContextFactory {
/**
@@ -126,7 +129,7 @@ protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) {
GenericApplicationContextFactory.this.prepareBeanFactory(parentBeanFactory, beanFactory);
for (Class extends BeanFactoryPostProcessor> cls : getBeanFactoryPostProcessorClasses()) {
for (String name : parent.getBeanNamesForType(cls)) {
- beanFactory.registerSingleton(name, (parent.getBean(name)));
+ beanFactory.registerSingleton(name, parent.getBean(name));
}
}
}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/GroupAwareJob.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/GroupAwareJob.java
index 2ea527202c..b0cbce6657 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/GroupAwareJob.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/GroupAwareJob.java
@@ -15,10 +15,10 @@
*/
package org.springframework.batch.core.configuration.support;
-import org.springframework.batch.core.Job;
-import org.springframework.batch.core.JobExecution;
-import org.springframework.batch.core.JobParametersIncrementer;
-import org.springframework.batch.core.JobParametersValidator;
+import org.springframework.batch.core.job.Job;
+import org.springframework.batch.core.job.JobExecution;
+import org.springframework.batch.core.job.parameters.JobParametersIncrementer;
+import org.springframework.batch.core.job.parameters.JobParametersValidator;
import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;
@@ -74,7 +74,7 @@ public void execute(JobExecution execution) {
/**
* Concatenates the group name and the delegate job name (joining with a ".").
*
- * @see org.springframework.batch.core.Job#getName()
+ * @see Job#getName()
*/
@Override
public String getName() {
@@ -99,8 +99,8 @@ public JobParametersValidator getJobParametersValidator() {
@Override
public boolean equals(Object obj) {
- if (obj instanceof GroupAwareJob) {
- return ((GroupAwareJob) obj).delegate.equals(delegate);
+ if (obj instanceof GroupAwareJob groupAwareJob) {
+ return groupAwareJob.delegate.equals(delegate);
}
return false;
}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/JdbcDefaultBatchConfiguration.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/JdbcDefaultBatchConfiguration.java
new file mode 100644
index 0000000000..2b3cc40e15
--- /dev/null
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/JdbcDefaultBatchConfiguration.java
@@ -0,0 +1,291 @@
+/*
+ * Copyright 2012-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.batch.core.configuration.support;
+
+import org.springframework.batch.core.configuration.BatchConfigurationException;
+import org.springframework.batch.core.converter.DateToStringConverter;
+import org.springframework.batch.core.converter.LocalDateTimeToStringConverter;
+import org.springframework.batch.core.converter.LocalDateToStringConverter;
+import org.springframework.batch.core.converter.LocalTimeToStringConverter;
+import org.springframework.batch.core.converter.StringToDateConverter;
+import org.springframework.batch.core.converter.StringToLocalDateConverter;
+import org.springframework.batch.core.converter.StringToLocalDateTimeConverter;
+import org.springframework.batch.core.converter.StringToLocalTimeConverter;
+import org.springframework.batch.core.job.DefaultJobKeyGenerator;
+import org.springframework.batch.core.job.JobInstance;
+import org.springframework.batch.core.job.JobKeyGenerator;
+import org.springframework.batch.core.launch.JobOperator;
+import org.springframework.batch.core.repository.ExecutionContextSerializer;
+import org.springframework.batch.core.repository.JobRepository;
+import org.springframework.batch.core.repository.dao.AbstractJdbcBatchMetadataDao;
+import org.springframework.batch.core.repository.dao.DefaultExecutionContextSerializer;
+import org.springframework.batch.core.repository.dao.jdbc.JdbcExecutionContextDao;
+import org.springframework.batch.core.repository.dao.jdbc.JdbcJobExecutionDao;
+import org.springframework.batch.core.repository.dao.jdbc.JdbcStepExecutionDao;
+import org.springframework.batch.core.repository.support.JdbcJobRepositoryFactoryBean;
+import org.springframework.batch.item.database.support.DataFieldMaxValueIncrementerFactory;
+import org.springframework.batch.item.database.support.DefaultDataFieldMaxValueIncrementerFactory;
+import org.springframework.batch.support.DatabaseType;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.convert.support.ConfigurableConversionService;
+import org.springframework.core.convert.support.DefaultConversionService;
+import org.springframework.jdbc.core.JdbcOperations;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.support.MetaDataAccessException;
+import org.springframework.jdbc.support.incrementer.DataFieldMaxValueIncrementer;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.annotation.Isolation;
+
+import javax.sql.DataSource;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.sql.Types;
+
+/**
+ * Base {@link Configuration} class that provides common JDBC-based infrastructure beans
+ * for enabling and using Spring Batch.
+ *
+ * This configuration class configures and registers the following beans in the
+ * application context:
+ *
+ *
+ * - a {@link JobRepository} named "jobRepository"
+ * - a {@link JobOperator} named "jobOperator"
+ * - a {@link org.springframework.batch.core.scope.StepScope} named "stepScope"
+ * - a {@link org.springframework.batch.core.scope.JobScope} named "jobScope"
+ *
+ *
+ * Customization is possible by extending the class and overriding getters.
+ *
+ * A typical usage of this class is as follows:
+ * @Configuration
+ * public class MyJobConfiguration extends JdbcDefaultBatchConfiguration {
+ *
+ * @Bean
+ * public Job job(JobRepository jobRepository) {
+ * return new JobBuilder("myJob", jobRepository)
+ * // define job flow as needed
+ * .build();
+ * }
+ *
+ * }
+ *
+ *
+ * @author Mahmoud Ben Hassine
+ * @since 6.0
+ */
+@Configuration(proxyBeanMethods = false)
+public class JdbcDefaultBatchConfiguration extends DefaultBatchConfiguration {
+
+ @Bean
+ @Override
+ public JobRepository jobRepository() throws BatchConfigurationException {
+ JdbcJobRepositoryFactoryBean jobRepositoryFactoryBean = new JdbcJobRepositoryFactoryBean();
+ try {
+ jobRepositoryFactoryBean.setDataSource(getDataSource());
+ jobRepositoryFactoryBean.setTransactionManager(getTransactionManager());
+ jobRepositoryFactoryBean.setDatabaseType(getDatabaseType());
+ jobRepositoryFactoryBean.setIncrementerFactory(getIncrementerFactory());
+ jobRepositoryFactoryBean.setJobKeyGenerator(getJobKeyGenerator());
+ jobRepositoryFactoryBean.setClobType(getClobType());
+ jobRepositoryFactoryBean.setTablePrefix(getTablePrefix());
+ jobRepositoryFactoryBean.setSerializer(getExecutionContextSerializer());
+ jobRepositoryFactoryBean.setConversionService(getConversionService());
+ jobRepositoryFactoryBean.setJdbcOperations(getJdbcOperations());
+ jobRepositoryFactoryBean.setCharset(getCharset());
+ jobRepositoryFactoryBean.setMaxVarCharLength(getMaxVarCharLength());
+ jobRepositoryFactoryBean.setIsolationLevelForCreateEnum(getIsolationLevelForCreate());
+ jobRepositoryFactoryBean.setValidateTransactionState(getValidateTransactionState());
+ jobRepositoryFactoryBean.afterPropertiesSet();
+ return jobRepositoryFactoryBean.getObject();
+ }
+ catch (Exception e) {
+ throw new BatchConfigurationException("Unable to configure the default job repository", e);
+ }
+ }
+
+ /*
+ * Getters to customize the configuration of infrastructure beans
+ */
+
+ /**
+ * Return the data source to use for Batch meta-data. Defaults to the bean of type
+ * {@link DataSource} and named "dataSource" in the application context.
+ * @return The data source to use for Batch meta-data
+ */
+ protected DataSource getDataSource() {
+ String errorMessage = " To use the default configuration, a data source bean named 'dataSource'"
+ + " should be defined in the application context but none was found. Override getDataSource()"
+ + " to provide the data source to use for Batch meta-data.";
+ if (this.applicationContext.getBeansOfType(DataSource.class).isEmpty()) {
+ throw new BatchConfigurationException(
+ "Unable to find a DataSource bean in the application context." + errorMessage);
+ }
+ else {
+ if (!this.applicationContext.containsBean("dataSource")) {
+ throw new BatchConfigurationException(errorMessage);
+ }
+ }
+ return this.applicationContext.getBean("dataSource", DataSource.class);
+ }
+
+ @Override
+ protected PlatformTransactionManager getTransactionManager() {
+ String errorMessage = " To use the default configuration, a PlatformTransactionManager bean named 'transactionManager'"
+ + " should be defined in the application context but none was found. Override getTransactionManager()"
+ + " to provide the transaction manager to use for the job repository.";
+ if (this.applicationContext.getBeansOfType(PlatformTransactionManager.class).isEmpty()) {
+ throw new BatchConfigurationException(
+ "Unable to find a PlatformTransactionManager bean in the application context." + errorMessage);
+ }
+ else {
+ if (!this.applicationContext.containsBean("transactionManager")) {
+ throw new BatchConfigurationException(errorMessage);
+ }
+ }
+ return this.applicationContext.getBean("transactionManager", PlatformTransactionManager.class);
+ }
+
+ /**
+ * Return the length of long string columns in database. Do not override this if you
+ * haven't modified the schema. Note this value will be used for the exit message in
+ * both {@link JdbcJobExecutionDao} and {@link JdbcStepExecutionDao} and also the
+ * short version of the execution context in {@link JdbcExecutionContextDao} . For
+ * databases with multi-byte character sets this number can be smaller (by up to a
+ * factor of 2 for 2-byte characters) than the declaration of the column length in the
+ * DDL for the tables. Defaults to
+ * {@link AbstractJdbcBatchMetadataDao#DEFAULT_EXIT_MESSAGE_LENGTH}
+ */
+ protected int getMaxVarCharLength() {
+ return AbstractJdbcBatchMetadataDao.DEFAULT_EXIT_MESSAGE_LENGTH;
+ }
+
+ /**
+ * Return the prefix of Batch meta-data tables. Defaults to
+ * {@link AbstractJdbcBatchMetadataDao#DEFAULT_TABLE_PREFIX}.
+ * @return the prefix of meta-data tables
+ */
+ protected String getTablePrefix() {
+ return AbstractJdbcBatchMetadataDao.DEFAULT_TABLE_PREFIX;
+ }
+
+ /**
+ * Return the {@link Charset} to use when serializing/deserializing the execution
+ * context. Defaults to "UTF-8".
+ * @return the charset to use when serializing/deserializing the execution context
+ */
+ protected Charset getCharset() {
+ return StandardCharsets.UTF_8;
+ }
+
+ /**
+ * Return the {@link JdbcOperations}. If this property is not overridden, a new
+ * {@link JdbcTemplate} will be created for the configured data source by default.
+ * @return the {@link JdbcOperations} to use
+ */
+ protected JdbcOperations getJdbcOperations() {
+ return new JdbcTemplate(getDataSource());
+ }
+
+ /**
+ * A custom implementation of the {@link ExecutionContextSerializer}. The default, if
+ * not injected, is the {@link DefaultExecutionContextSerializer}.
+ * @return the serializer to use to serialize/deserialize the execution context
+ */
+ protected ExecutionContextSerializer getExecutionContextSerializer() {
+ return new DefaultExecutionContextSerializer();
+ }
+
+ /**
+ * Return the value from {@link Types} class to indicate the type to use for a CLOB
+ * @return the value from {@link Types} class to indicate the type to use for a CLOB
+ */
+ protected int getClobType() {
+ return Types.CLOB;
+ }
+
+ /**
+ * Return the factory for creating {@link DataFieldMaxValueIncrementer}
+ * implementations used to increment entity IDs in meta-data tables.
+ * @return the factory for creating {@link DataFieldMaxValueIncrementer}
+ * implementations.
+ */
+ protected DataFieldMaxValueIncrementerFactory getIncrementerFactory() {
+ return new DefaultDataFieldMaxValueIncrementerFactory(getDataSource());
+ }
+
+ /**
+ * Return the database type. The default will be introspected from the JDBC meta-data
+ * of the data source.
+ * @return the database type
+ * @throws MetaDataAccessException if an error occurs when trying to get the database
+ * type of JDBC meta-data
+ *
+ */
+ protected String getDatabaseType() throws MetaDataAccessException {
+ return DatabaseType.fromMetaData(getDataSource()).name();
+ }
+
+ /**
+ * Return the conversion service to use in the job repository and job explorer. This
+ * service is used to convert job parameters from String literal to typed values and
+ * vice versa.
+ * @return the {@link ConfigurableConversionService} to use.
+ */
+ protected ConfigurableConversionService getConversionService() {
+ DefaultConversionService conversionService = new DefaultConversionService();
+ conversionService.addConverter(new DateToStringConverter());
+ conversionService.addConverter(new StringToDateConverter());
+ conversionService.addConverter(new LocalDateToStringConverter());
+ conversionService.addConverter(new StringToLocalDateConverter());
+ conversionService.addConverter(new LocalTimeToStringConverter());
+ conversionService.addConverter(new StringToLocalTimeConverter());
+ conversionService.addConverter(new LocalDateTimeToStringConverter());
+ conversionService.addConverter(new StringToLocalDateTimeConverter());
+ return conversionService;
+ }
+
+ /**
+ * Return the value of the {@code validateTransactionState} parameter. Defaults to
+ * {@code true}.
+ * @return true if the transaction state should be validated, false otherwise
+ */
+ protected boolean getValidateTransactionState() {
+ return true;
+ }
+
+ /**
+ * Return the transaction isolation level when creating job executions. Defaults to
+ * {@link Isolation#SERIALIZABLE}.
+ * @return the transaction isolation level when creating job executions
+ */
+ protected Isolation getIsolationLevelForCreate() {
+ return Isolation.SERIALIZABLE;
+ }
+
+ /**
+ * A custom implementation of the {@link JobKeyGenerator}. The default, if not
+ * injected, is the {@link DefaultJobKeyGenerator}.
+ * @return the generator that creates the key used in identifying {@link JobInstance}
+ * objects
+ * @since 5.1
+ */
+ protected JobKeyGenerator getJobKeyGenerator() {
+ return new DefaultJobKeyGenerator();
+ }
+
+}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/JobFactoryRegistrationListener.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/JobFactoryRegistrationListener.java
index 3ed14c2974..b55ce50e71 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/JobFactoryRegistrationListener.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/JobFactoryRegistrationListener.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006-2023 the original author or authors.
+ * Copyright 2006-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -27,8 +27,10 @@
* Generic service that can bind and unbind a {@link JobFactory} in a {@link JobRegistry}.
*
* @author Dave Syer
- *
+ * @author Mahmoud Ben Hassine
+ * @deprecated since 6.0 with no replacement. Scheduled for removal in 6.2 or later.
*/
+@Deprecated(since = "6.0", forRemoval = true)
public class JobFactoryRegistrationListener {
private final Log logger = LogFactory.getLog(getClass());
@@ -53,7 +55,7 @@ public void bind(JobFactory jobFactory, Map params) throws Exception
if (logger.isInfoEnabled()) {
logger.info("Binding JobFactory: " + jobFactory.getJobName());
}
- jobRegistry.register(jobFactory);
+ jobRegistry.register(jobFactory.createJob());
}
/**
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/JobLoader.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/JobLoader.java
index e4821843fe..1b4288c785 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/JobLoader.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/JobLoader.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2009-2022 the original author or authors.
+ * Copyright 2009-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,13 +17,16 @@
import java.util.Collection;
-import org.springframework.batch.core.Job;
+import org.springframework.batch.core.job.Job;
import org.springframework.batch.core.configuration.DuplicateJobException;
/**
* @author Dave Syer
+ * @author Mahmoud Ben Hassine
* @since 2.1
+ * @deprecated since 6.0 with no replacement. Scheduled for removal in 6.2 or later.
*/
+@Deprecated(since = "6.0", forRemoval = true)
public interface JobLoader {
/**
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/JobRegistryBeanPostProcessor.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/JobRegistryBeanPostProcessor.java
deleted file mode 100644
index 1f6ba7acfa..0000000000
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/JobRegistryBeanPostProcessor.java
+++ /dev/null
@@ -1,176 +0,0 @@
-/*
- * Copyright 2006-2024 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.springframework.batch.core.configuration.support;
-
-import java.util.Collection;
-import java.util.HashSet;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.springframework.batch.core.Job;
-import org.springframework.batch.core.configuration.DuplicateJobException;
-import org.springframework.batch.core.configuration.JobLocator;
-import org.springframework.batch.core.configuration.JobRegistry;
-import org.springframework.beans.BeansException;
-import org.springframework.beans.FatalBeanException;
-import org.springframework.beans.factory.BeanFactory;
-import org.springframework.beans.factory.BeanFactoryAware;
-import org.springframework.beans.factory.DisposableBean;
-import org.springframework.beans.factory.InitializingBean;
-import org.springframework.beans.factory.config.BeanDefinition;
-import org.springframework.beans.factory.config.BeanPostProcessor;
-import org.springframework.beans.factory.support.DefaultListableBeanFactory;
-import org.springframework.util.Assert;
-
-/**
- * A {@link BeanPostProcessor} that registers {@link Job} beans with a
- * {@link JobRegistry}. Include a bean of this type along with your job configuration and
- * use the same {@link JobRegistry} as a {@link JobLocator} when you need to locate a
- * {@link Job} to launch.
- *
- * An alternative to this class is {@link JobRegistrySmartInitializingSingleton}, which is
- * recommended in cases where this class may cause early bean initializations. You must
- * include at most one of either of them as a bean.
- *
- * @deprecated since 5.2 in favor of {@link JobRegistrySmartInitializingSingleton}.
- * @author Dave Syer
- * @author Mahmoud Ben Hassine
- *
- */
-@Deprecated(since = "5.2")
-public class JobRegistryBeanPostProcessor
- implements BeanPostProcessor, BeanFactoryAware, InitializingBean, DisposableBean {
-
- private static final Log logger = LogFactory.getLog(JobRegistryBeanPostProcessor.class);
-
- // It doesn't make sense for this to have a default value...
- private JobRegistry jobRegistry = null;
-
- private final Collection jobNames = new HashSet<>();
-
- private String groupName = null;
-
- private DefaultListableBeanFactory beanFactory;
-
- /**
- * The group name for jobs registered by this component. Optional (defaults to null,
- * which means that jobs are registered with their bean names). Useful where there is
- * a hierarchy of application contexts all contributing to the same
- * {@link JobRegistry}: child contexts can then define an instance with a unique group
- * name to avoid clashes between job names.
- * @param groupName the groupName to set
- */
- public void setGroupName(String groupName) {
- this.groupName = groupName;
- }
-
- /**
- * Injection setter for {@link JobRegistry}.
- * @param jobRegistry the jobConfigurationRegistry to set
- */
- public void setJobRegistry(JobRegistry jobRegistry) {
- this.jobRegistry = jobRegistry;
- }
-
- @Override
- public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
- if (beanFactory instanceof DefaultListableBeanFactory) {
- this.beanFactory = (DefaultListableBeanFactory) beanFactory;
- }
- }
-
- /**
- * Make sure the registry is set before use.
- *
- * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
- */
- @Override
- public void afterPropertiesSet() throws Exception {
- Assert.state(jobRegistry != null, "JobRegistry must not be null");
- }
-
- /**
- * Unregister all the {@link Job} instances that were registered by this post
- * processor.
- * @see org.springframework.beans.factory.DisposableBean#destroy()
- */
- @Override
- public void destroy() throws Exception {
- for (String name : jobNames) {
- if (logger.isDebugEnabled()) {
- logger.debug("Unregistering job: " + name);
- }
- jobRegistry.unregister(name);
- }
- jobNames.clear();
- }
-
- /**
- * If the bean is an instance of {@link Job}, then register it.
- * @throws FatalBeanException if there is a {@link DuplicateJobException}.
- *
- * @see org.springframework.beans.factory.config.BeanPostProcessor#postProcessAfterInitialization(java.lang.Object,
- * java.lang.String)
- */
- @Override
- public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
- if (bean instanceof Job job) {
- try {
- String groupName = this.groupName;
- if (beanFactory != null && beanFactory.containsBean(beanName)) {
- groupName = getGroupName(beanFactory.getBeanDefinition(beanName), job);
- }
- job = groupName == null ? job : new GroupAwareJob(groupName, job);
- ReferenceJobFactory jobFactory = new ReferenceJobFactory(job);
- String name = jobFactory.getJobName();
- if (logger.isDebugEnabled()) {
- logger.debug("Registering job: " + name);
- }
- jobRegistry.register(jobFactory);
- jobNames.add(name);
- }
- catch (DuplicateJobException e) {
- throw new FatalBeanException("Cannot register job configuration", e);
- }
- return job;
- }
- return bean;
- }
-
- /**
- * Determine a group name for the job to be registered. The default implementation
- * returns the {@link #setGroupName(String) groupName} configured. Provides an
- * extension point for specialised subclasses.
- * @param beanDefinition the bean definition for the job
- * @param job the job
- * @return a group name for the job (or null if not needed)
- */
- protected String getGroupName(BeanDefinition beanDefinition, Job job) {
- return groupName;
- }
-
- /**
- * Do nothing.
- *
- * @see org.springframework.beans.factory.config.BeanPostProcessor#postProcessBeforeInitialization(java.lang.Object,
- * java.lang.String)
- */
- @Override
- public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
- return bean;
- }
-
-}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/JobRegistrySmartInitializingSingleton.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/JobRegistrySmartInitializingSingleton.java
index ede418cf23..aafa8b4a49 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/JobRegistrySmartInitializingSingleton.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/JobRegistrySmartInitializingSingleton.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2024 the original author or authors.
+ * Copyright 2024-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,7 +21,7 @@
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
-import org.springframework.batch.core.Job;
+import org.springframework.batch.core.job.Job;
import org.springframework.batch.core.configuration.DuplicateJobException;
import org.springframework.batch.core.configuration.JobLocator;
import org.springframework.batch.core.configuration.JobRegistry;
@@ -42,13 +42,13 @@
* {@link JobRegistry}. Include a bean of this type along with your job configuration and
* use the same {@link JobRegistry} as a {@link JobLocator} when you need to locate a
* {@link Job} to launch.
- *
- * This class is an alternative to {@link JobRegistryBeanPostProcessor} and prevents early
- * bean initializations. You must include at most one of either of them as a bean.
*
* @author Henning Pöttker
* @since 5.1.1
+ * @deprecated since 6.0 with no replacement. Register a {@link MapJobRegistry} as a bean,
+ * and it will automatically register all {@link Job} beans in the application context.
*/
+@Deprecated(since = "6.0", forRemoval = true)
public class JobRegistrySmartInitializingSingleton
implements SmartInitializingSingleton, BeanFactoryAware, InitializingBean, DisposableBean {
@@ -146,12 +146,11 @@ private void postProcessAfterInitialization(Job job, String beanName) {
groupName = getGroupName(defaultListableBeanFactory.getBeanDefinition(beanName), job);
}
job = groupName == null ? job : new GroupAwareJob(groupName, job);
- ReferenceJobFactory jobFactory = new ReferenceJobFactory(job);
- String name = jobFactory.getJobName();
+ String name = job.getName();
if (logger.isDebugEnabled()) {
logger.debug("Registering job: " + name);
}
- jobRegistry.register(jobFactory);
+ jobRegistry.register(job);
jobNames.add(name);
}
catch (DuplicateJobException e) {
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/MapJobRegistry.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/MapJobRegistry.java
index 3e55bedc0c..9058740855 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/MapJobRegistry.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/MapJobRegistry.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006-2019 the original author or authors.
+ * Copyright 2006-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,68 +16,89 @@
package org.springframework.batch.core.configuration.support;
import java.util.Collections;
+import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
-import org.springframework.batch.core.Job;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.batch.core.job.Job;
import org.springframework.batch.core.configuration.DuplicateJobException;
-import org.springframework.batch.core.configuration.JobFactory;
import org.springframework.batch.core.configuration.JobRegistry;
import org.springframework.batch.core.launch.NoSuchJobException;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.SmartInitializingSingleton;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
- * Simple, thread-safe, map-based implementation of {@link JobRegistry}.
+ * Simple, thread-safe, map-based implementation of {@link JobRegistry}. This registry is
+ * a {@link SmartInitializingSingleton} that is automatically populated with all
+ * {@link Job} beans in the {@link ApplicationContext}.
*
* @author Dave Syer
* @author Robert Fischer
* @author Mahmoud Ben Hassine
*/
-public class MapJobRegistry implements JobRegistry {
+public class MapJobRegistry implements JobRegistry, SmartInitializingSingleton, ApplicationContextAware {
+
+ protected final Log logger = LogFactory.getLog(getClass());
/**
- * The map holding the registered job factories.
+ * The map holding the registered jobs.
*/
- // The "final" ensures that it is visible and initialized when the constructor
- // resolves.
- private final ConcurrentMap map = new ConcurrentHashMap<>();
+ private final ConcurrentMap map = new ConcurrentHashMap<>();
+
+ private ApplicationContext applicationContext;
+
+ @Override
+ public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
+ this.applicationContext = applicationContext;
+ }
+
+ @Override
+ public void afterSingletonsInstantiated() {
+ Map jobBeans = this.applicationContext.getBeansOfType(Job.class);
+ this.map.putAll(jobBeans);
+ }
@Override
- public void register(JobFactory jobFactory) throws DuplicateJobException {
- Assert.notNull(jobFactory, "jobFactory is null");
- String name = jobFactory.getJobName();
- Assert.notNull(name, "Job configuration must have a name.");
- JobFactory previousValue = map.putIfAbsent(name, jobFactory);
+ public void register(Job job) throws DuplicateJobException {
+ Assert.notNull(job, "job must not be null");
+ String jobName = job.getName();
+ Assert.notNull(jobName, "Job name must not be null");
+ Job previousValue = this.map.putIfAbsent(jobName, job);
if (previousValue != null) {
- throw new DuplicateJobException("A job configuration with this name [" + name + "] was already registered");
+ throw new DuplicateJobException("A job with this name [" + jobName + "] was already registered");
}
}
@Override
public void unregister(String name) {
- Assert.notNull(name, "Job configuration must have a name.");
- map.remove(name);
+ Assert.notNull(name, "Job name must not be null");
+ this.map.remove(name);
}
@Override
public Job getJob(@Nullable String name) throws NoSuchJobException {
- JobFactory factory = map.get(name);
- if (factory == null) {
- throw new NoSuchJobException("No job configuration with the name [" + name + "] was registered");
+ Job job = this.map.get(name);
+ if (job == null) {
+ throw new NoSuchJobException("No job with the name [" + name + "] was registered");
}
else {
- return factory.createJob();
+ return job;
}
}
/**
- * Provides an unmodifiable view of the job names.
+ * Provides an unmodifiable view of job names.
*/
@Override
public Set getJobNames() {
- return Collections.unmodifiableSet(map.keySet());
+ return Collections.unmodifiableSet(this.map.keySet());
}
}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/MapStepRegistry.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/MapStepRegistry.java
index 0d3aa396b9..051a44edd0 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/MapStepRegistry.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/MapStepRegistry.java
@@ -21,7 +21,7 @@
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
-import org.springframework.batch.core.Step;
+import org.springframework.batch.core.step.Step;
import org.springframework.batch.core.configuration.DuplicateJobException;
import org.springframework.batch.core.configuration.StepRegistry;
import org.springframework.batch.core.launch.NoSuchJobException;
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/MongoDefaultBatchConfiguration.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/MongoDefaultBatchConfiguration.java
new file mode 100644
index 0000000000..fdb18c63d3
--- /dev/null
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/MongoDefaultBatchConfiguration.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright 2012-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.batch.core.configuration.support;
+
+import org.springframework.batch.core.configuration.BatchConfigurationException;
+import org.springframework.batch.core.job.DefaultJobKeyGenerator;
+import org.springframework.batch.core.job.JobInstance;
+import org.springframework.batch.core.job.JobKeyGenerator;
+import org.springframework.batch.core.launch.JobOperator;
+import org.springframework.batch.core.repository.JobRepository;
+import org.springframework.batch.core.repository.support.MongoJobRepositoryFactoryBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.mongodb.MongoTransactionManager;
+import org.springframework.data.mongodb.core.MongoOperations;
+import org.springframework.transaction.annotation.Isolation;
+
+/**
+ * Base {@link Configuration} class that provides common MongoDB-based infrastructure
+ * beans for enabling and using Spring Batch.
+ *
+ * This configuration class configures and registers the following beans in the
+ * application context:
+ *
+ *
+ * - a {@link JobRepository} named "jobRepository"
+ * - a {@link JobOperator} named "jobOperator"
+ * - a {@link org.springframework.batch.core.scope.StepScope} named "stepScope"
+ * - a {@link org.springframework.batch.core.scope.JobScope} named "jobScope"
+ *
+ *
+ * Customization is possible by extending the class and overriding getters.
+ *
+ * A typical usage of this class is as follows:
+ * @Configuration
+ * public class MyJobConfiguration extends MongoDefaultBatchConfiguration {
+ *
+ * @Bean
+ * public Job job(JobRepository jobRepository) {
+ * return new JobBuilder("myJob", jobRepository)
+ * // define job flow as needed
+ * .build();
+ * }
+ *
+ * }
+ *
+ *
+ * @author Mahmoud Ben Hassine
+ * @since 6.0
+ */
+@Configuration(proxyBeanMethods = false)
+public class MongoDefaultBatchConfiguration extends DefaultBatchConfiguration {
+
+ @Bean
+ @Override
+ public JobRepository jobRepository() throws BatchConfigurationException {
+ MongoJobRepositoryFactoryBean jobRepositoryFactoryBean = new MongoJobRepositoryFactoryBean();
+ try {
+ jobRepositoryFactoryBean.setMongoOperations(getMongoOperations());
+ jobRepositoryFactoryBean.setTransactionManager(getTransactionManager());
+ jobRepositoryFactoryBean.setIsolationLevelForCreateEnum(getIsolationLevelForCreate());
+ jobRepositoryFactoryBean.setValidateTransactionState(getValidateTransactionState());
+ jobRepositoryFactoryBean.setJobKeyGenerator(getJobKeyGenerator());
+ jobRepositoryFactoryBean.afterPropertiesSet();
+ return jobRepositoryFactoryBean.getObject();
+ }
+ catch (Exception e) {
+ throw new BatchConfigurationException("Unable to configure the default job repository", e);
+ }
+ }
+
+ /*
+ * Getters to customize the configuration of infrastructure beans
+ */
+
+ protected MongoOperations getMongoOperations() {
+ String errorMessage = " To use the default configuration, a MongoOperations bean named 'mongoTemplate'"
+ + " should be defined in the application context but none was found. Override getMongoOperations()"
+ + " to provide the MongoOperations for Batch meta-data.";
+ if (this.applicationContext.getBeansOfType(MongoOperations.class).isEmpty()) {
+ throw new BatchConfigurationException(
+ "Unable to find a MongoOperations bean in the application context." + errorMessage);
+ }
+ else {
+ if (!this.applicationContext.containsBean("mongoTemplate")) {
+ throw new BatchConfigurationException(errorMessage);
+ }
+ }
+ return this.applicationContext.getBean("mongoTemplate", MongoOperations.class);
+ }
+
+ @Override
+ protected MongoTransactionManager getTransactionManager() {
+ String errorMessage = " To use the default configuration, a MongoTransactionManager bean named 'transactionManager'"
+ + " should be defined in the application context but none was found. Override getTransactionManager()"
+ + " to provide the transaction manager to use for the job repository.";
+ if (this.applicationContext.getBeansOfType(MongoTransactionManager.class).isEmpty()) {
+ throw new BatchConfigurationException(
+ "Unable to find a MongoTransactionManager bean in the application context." + errorMessage);
+ }
+ else {
+ if (!this.applicationContext.containsBean("transactionManager")) {
+ throw new BatchConfigurationException(errorMessage);
+ }
+ }
+ return this.applicationContext.getBean("transactionManager", MongoTransactionManager.class);
+ }
+
+ /**
+ * Return the value of the {@code validateTransactionState} parameter. Defaults to
+ * {@code true}.
+ * @return true if the transaction state should be validated, false otherwise
+ */
+ protected boolean getValidateTransactionState() {
+ return true;
+ }
+
+ /**
+ * Return the transaction isolation level when creating job executions. Defaults to
+ * {@link Isolation#SERIALIZABLE}.
+ * @return the transaction isolation level when creating job executions
+ */
+ protected Isolation getIsolationLevelForCreate() {
+ return Isolation.SERIALIZABLE;
+ }
+
+ /**
+ * A custom implementation of the {@link JobKeyGenerator}. The default, if not
+ * injected, is the {@link DefaultJobKeyGenerator}.
+ * @return the generator that creates the key used in identifying {@link JobInstance}
+ * objects
+ * @since 5.1
+ */
+ protected JobKeyGenerator getJobKeyGenerator() {
+ return new DefaultJobKeyGenerator();
+ }
+
+}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/ReferenceJobFactory.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/ReferenceJobFactory.java
index 4664448c0c..aed7d306c6 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/ReferenceJobFactory.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/ReferenceJobFactory.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006-2023 the original author or authors.
+ * Copyright 2006-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,7 +15,7 @@
*/
package org.springframework.batch.core.configuration.support;
-import org.springframework.batch.core.Job;
+import org.springframework.batch.core.job.Job;
import org.springframework.batch.core.configuration.JobFactory;
/**
@@ -23,8 +23,10 @@
* {@link Job}.
*
* @author Dave Syer
- *
+ * @author Mahmoud Ben Hassine
+ * @deprecated since 6.0 with no replacement. Scheduled for removal in 6.2 or later.
*/
+@Deprecated(since = "6.0", forRemoval = true)
public class ReferenceJobFactory implements JobFactory {
private final Job job;
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/AbstractFlowParser.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/AbstractFlowParser.java
index 03ec94b23e..c378258a91 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/AbstractFlowParser.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/AbstractFlowParser.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006-2023 the original author or authors.
+ * Copyright 2006-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -404,13 +404,12 @@ protected static Collection createTransition(FlowExecutionStatus
endBuilder.addConstructorArgValue(exitCodeExists ? exitCode : status.getName());
String endName = (status == FlowExecutionStatus.STOPPED ? STOP_ELE
- : status == FlowExecutionStatus.FAILED ? FAIL_ELE : END_ELE) + (endCounter++);
+ : status == FlowExecutionStatus.FAILED ? FAIL_ELE : END_ELE) + endCounter++;
endBuilder.addConstructorArgValue(endName);
endBuilder.addConstructorArgValue(abandon);
- String nextOnEnd = exitCodeExists ? null : next;
- endState = getStateTransitionReference(parserContext, endBuilder.getBeanDefinition(), null, nextOnEnd);
+ endState = getStateTransitionReference(parserContext, endBuilder.getBeanDefinition(), null, next);
next = endName;
}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/AbstractStepParser.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/AbstractStepParser.java
index c12eaf8633..73ef8f82cc 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/AbstractStepParser.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/AbstractStepParser.java
@@ -20,6 +20,7 @@
import org.w3c.dom.NodeList;
import org.springframework.batch.core.listener.StepListenerMetaData;
+import org.springframework.batch.core.step.Step;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
@@ -35,9 +36,9 @@
/**
* Internal parser for the <step/> elements inside a job. A step element references
- * a bean definition for a {@link org.springframework.batch.core.Step} and goes on to
- * (optionally) list a set of transitions from that step to others with <next
- * on="pattern" to="stepName"/>. Used by the {@link JobParser}.
+ * a bean definition for a {@link Step} and goes on to (optionally) list a set of
+ * transitions from that step to others with <next on="pattern" to="stepName"/>.
+ * Used by the {@link JobParser}.
*
* @author Dave Syer
* @author Thomas Risberg
@@ -118,16 +119,13 @@ protected AbstractBeanDefinition parseStep(Element stepElement, ParserContext pa
new TaskletParser().parseTasklet(stepElement, nestedElement, bd, parserContext, stepUnderspecified);
}
else if (FLOW_ELE.equals(name)) {
- boolean stepUnderspecified = CoreNamespaceUtils.isUnderspecified(stepElement);
- parseFlow(stepElement, nestedElement, bd, parserContext, stepUnderspecified);
+ parseFlow(stepElement, nestedElement, bd);
}
else if (PARTITION_ELE.equals(name)) {
- boolean stepUnderspecified = CoreNamespaceUtils.isUnderspecified(stepElement);
- parsePartition(stepElement, nestedElement, bd, parserContext, stepUnderspecified, jobFactoryRef);
+ parsePartition(stepElement, nestedElement, bd, parserContext, jobFactoryRef);
}
else if (JOB_ELE.equals(name)) {
- boolean stepUnderspecified = CoreNamespaceUtils.isUnderspecified(stepElement);
- parseJob(stepElement, nestedElement, bd, parserContext, stepUnderspecified);
+ parseJob(nestedElement, bd, parserContext);
}
else if ("description".equals(name)) {
bd.setDescription(nestedElement.getTextContent());
@@ -199,7 +197,7 @@ else if (ns.equals("http://www.springframework.org/schema/batch")) {
}
private void parsePartition(Element stepElement, Element partitionElement, AbstractBeanDefinition bd,
- ParserContext parserContext, boolean stepUnderspecified, String jobFactoryRef) {
+ ParserContext parserContext, String jobFactoryRef) {
bd.setBeanClass(StepParserStepFactoryBean.class);
bd.setAttribute("isNamespaceStep", true);
@@ -258,8 +256,7 @@ else if (inlineStepElement != null) {
}
- private void parseJob(Element stepElement, Element jobElement, AbstractBeanDefinition bd,
- ParserContext parserContext, boolean stepUnderspecified) {
+ private void parseJob(Element jobElement, AbstractBeanDefinition bd, ParserContext parserContext) {
bd.setBeanClass(StepParserStepFactoryBean.class);
bd.setAttribute("isNamespaceStep", true);
@@ -285,8 +282,7 @@ private void parseJob(Element stepElement, Element jobElement, AbstractBeanDefin
}
- private void parseFlow(Element stepElement, Element flowElement, AbstractBeanDefinition bd,
- ParserContext parserContext, boolean stepUnderspecified) {
+ private void parseFlow(Element stepElement, Element flowElement, AbstractBeanDefinition bd) {
bd.setBeanClass(StepParserStepFactoryBean.class);
bd.setAttribute("isNamespaceStep", true);
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/CoreNamespacePostProcessor.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/CoreNamespacePostProcessor.java
index 7f250d389c..cbaed045bb 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/CoreNamespacePostProcessor.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/CoreNamespacePostProcessor.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006-2023 the original author or authors.
+ * Copyright 2006-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -115,23 +115,21 @@ public Object postProcessBeforeInitialization(Object bean, String beanName) thro
* @return the bean with default collaborators injected into it
*/
private Object injectDefaults(Object bean) {
- if (bean instanceof JobParserJobFactoryBean) {
- JobParserJobFactoryBean fb = (JobParserJobFactoryBean) bean;
+ if (bean instanceof JobParserJobFactoryBean fb) {
JobRepository jobRepository = fb.getJobRepository();
if (jobRepository == null) {
- fb.setJobRepository((JobRepository) applicationContext.getBean(DEFAULT_JOB_REPOSITORY_NAME));
+ fb.setJobRepository(applicationContext.getBean(DEFAULT_JOB_REPOSITORY_NAME, JobRepository.class));
}
}
- else if (bean instanceof StepParserStepFactoryBean) {
- StepParserStepFactoryBean, ?> fb = (StepParserStepFactoryBean, ?>) bean;
+ else if (bean instanceof StepParserStepFactoryBean, ?> fb) {
JobRepository jobRepository = fb.getJobRepository();
if (jobRepository == null) {
- fb.setJobRepository((JobRepository) applicationContext.getBean(DEFAULT_JOB_REPOSITORY_NAME));
+ fb.setJobRepository(applicationContext.getBean(DEFAULT_JOB_REPOSITORY_NAME, JobRepository.class));
}
PlatformTransactionManager transactionManager = fb.getTransactionManager();
if (transactionManager == null && fb.requiresTransactionManager()) {
fb.setTransactionManager(
- (PlatformTransactionManager) applicationContext.getBean(DEFAULT_TRANSACTION_MANAGER_NAME));
+ applicationContext.getBean(DEFAULT_TRANSACTION_MANAGER_NAME, PlatformTransactionManager.class));
}
}
return bean;
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/ExceptionElementParser.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/ExceptionElementParser.java
index 382a7b6d97..de8aff9119 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/ExceptionElementParser.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/ExceptionElementParser.java
@@ -32,8 +32,8 @@ public ManagedMap parse(Element element, ParserContex
if (children.size() == 1) {
ManagedMap map = new ManagedMap<>();
Element exceptionClassesElement = children.get(0);
- addExceptionClasses("include", true, exceptionClassesElement, map, parserContext);
- addExceptionClasses("exclude", false, exceptionClassesElement, map, parserContext);
+ addExceptionClasses("include", true, exceptionClassesElement, map);
+ addExceptionClasses("exclude", false, exceptionClassesElement, map);
map.put(new TypedStringValue(ForceRollbackForWriteSkipException.class.getName(), Class.class), true);
return map;
}
@@ -46,7 +46,7 @@ else if (children.size() > 1) {
}
private void addExceptionClasses(String elementName, boolean include, Element exceptionClassesElement,
- ManagedMap map, ParserContext parserContext) {
+ ManagedMap map) {
for (Element child : DomUtils.getChildElementsByTagName(exceptionClassesElement, elementName)) {
String className = child.getAttribute("class");
map.put(new TypedStringValue(className, Class.class), include);
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/InlineStepParser.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/InlineStepParser.java
index 22fa9bb18c..4e9123a0bc 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/InlineStepParser.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/InlineStepParser.java
@@ -18,6 +18,7 @@
import java.util.Collection;
import org.springframework.batch.core.job.flow.support.state.StepState;
+import org.springframework.batch.core.step.Step;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
@@ -27,9 +28,9 @@
/**
* Internal parser for the <step/> elements inside a job. A step element references
- * a bean definition for a {@link org.springframework.batch.core.Step} and goes on to
- * (optionally) list a set of transitions from that step to others with <next
- * on="pattern" to="stepName"/>. Used by the {@link JobParser}.
+ * a bean definition for a {@link Step} and goes on to (optionally) list a set of
+ * transitions from that step to others with <next on="pattern" to="stepName"/>.
+ * Used by the {@link JobParser}.
*
* @see JobParser
* @author Dave Syer
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/JobParser.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/JobParser.java
index 8254cd66d8..9931c03172 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/JobParser.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/JobParser.java
@@ -18,6 +18,7 @@
import java.util.Arrays;
import java.util.List;
+import org.springframework.batch.core.job.Job;
import org.springframework.beans.BeanMetadataElement;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
@@ -33,7 +34,7 @@
/**
* Parser for the <job/> element in the Batch namespace. Sets up and returns a bean
- * definition for a {@link org.springframework.batch.core.Job}.
+ * definition for a {@link Job}.
*
* @author Dave Syer
* @author Mahmoud Ben Hassine
@@ -103,7 +104,7 @@ protected void doParse(Element element, ParserContext parserContext, BeanDefinit
builder.addPropertyValue("restartable", restartableAttribute);
}
- String incrementer = (element.getAttribute("incrementer"));
+ String incrementer = element.getAttribute("incrementer");
if (StringUtils.hasText(incrementer)) {
builder.addPropertyReference("jobParametersIncrementer", incrementer);
}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/JobParserJobFactoryBean.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/JobParserJobFactoryBean.java
index b46ff39874..cd299c48ee 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/JobParserJobFactoryBean.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/JobParserJobFactoryBean.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006-2023 the original author or authors.
+ * Copyright 2006-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,9 +15,9 @@
*/
package org.springframework.batch.core.configuration.xml;
-import org.springframework.batch.core.JobExecutionListener;
-import org.springframework.batch.core.JobParametersIncrementer;
-import org.springframework.batch.core.JobParametersValidator;
+import org.springframework.batch.core.listener.JobExecutionListener;
+import org.springframework.batch.core.job.parameters.JobParametersIncrementer;
+import org.springframework.batch.core.job.parameters.JobParametersValidator;
import org.springframework.batch.core.job.flow.Flow;
import org.springframework.batch.core.job.flow.FlowJob;
import org.springframework.batch.core.repository.JobRepository;
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/JobRepositoryParser.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/JobRepositoryParser.java
index be88087562..730296ea77 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/JobRepositoryParser.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/JobRepositoryParser.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006-2022 the original author or authors.
+ * Copyright 2006-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,6 +15,7 @@
*/
package org.springframework.batch.core.configuration.xml;
+import org.springframework.batch.core.repository.support.JdbcJobRepositoryFactoryBean;
import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.RuntimeBeanReference;
@@ -28,7 +29,7 @@
/**
* Parser for the <job-repository/> element in the Batch namespace. Sets up and
- * returns a JobRepositoryFactoryBean.
+ * returns a {@link JdbcJobRepositoryFactoryBean}.
*
* @author Thomas Risberg
* @author Mahmoud Ben Hassine
@@ -39,7 +40,7 @@ public class JobRepositoryParser extends AbstractSingleBeanDefinitionParser {
@Override
protected String getBeanClassName(Element element) {
- return "org.springframework.batch.core.repository.support.JobRepositoryFactoryBean";
+ return "org.springframework.batch.core.repository.support.JdbcJobRepositoryFactoryBean";
}
@Override
@@ -57,7 +58,7 @@ protected String resolveId(Element element, AbstractBeanDefinition definition, P
/**
* Parse and create a bean definition for a
- * {@link org.springframework.batch.core.repository.support.JobRepositoryFactoryBean}
+ * {@link org.springframework.batch.core.repository.support.JdbcJobRepositoryFactoryBean}
* .
*/
@Override
@@ -77,8 +78,6 @@ protected void doParse(Element element, ParserContext parserContext, BeanDefinit
String maxVarCharLength = element.getAttribute("max-varchar-length");
- String lobHandler = element.getAttribute("lob-handler");
-
String serializer = element.getAttribute("serializer");
String conversionService = element.getAttribute("conversion-service");
@@ -97,9 +96,6 @@ protected void doParse(Element element, ParserContext parserContext, BeanDefinit
if (StringUtils.hasText(tablePrefix)) {
builder.addPropertyValue("tablePrefix", tablePrefix);
}
- if (StringUtils.hasText(lobHandler)) {
- builder.addPropertyReference("lobHandler", lobHandler);
- }
if (StringUtils.hasText(maxVarCharLength)) {
builder.addPropertyValue("maxVarCharLength", maxVarCharLength);
}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/SimpleFlowFactoryBean.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/SimpleFlowFactoryBean.java
index 216935b2ef..85fc80fe1d 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/SimpleFlowFactoryBean.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/SimpleFlowFactoryBean.java
@@ -205,7 +205,7 @@ public FlowExecutionStatus handle(FlowExecutor executor) throws Exception {
@Override
public Collection getFlows() {
- return (state instanceof FlowHolder) ? ((FlowHolder) state).getFlows() : Collections.emptyList();
+ return (state instanceof FlowHolder flowHolder) ? flowHolder.getFlows() : Collections.emptyList();
}
}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/StandaloneStepParser.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/StandaloneStepParser.java
index 2b07ad9677..ad39ddcffe 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/StandaloneStepParser.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/StandaloneStepParser.java
@@ -15,13 +15,14 @@
*/
package org.springframework.batch.core.configuration.xml;
+import org.springframework.batch.core.step.Step;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.xml.ParserContext;
import org.w3c.dom.Element;
/**
* Internal parser for the <step/> elements for a job. A step element references a
- * bean definition for a {@link org.springframework.batch.core.Step}.
+ * bean definition for a {@link Step}.
*
* @author Dave Syer
* @author Thomas Risberg
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/StepParserStepFactoryBean.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/StepParserStepFactoryBean.java
index cbad5b1cee..67e745d58d 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/StepParserStepFactoryBean.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/StepParserStepFactoryBean.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006-2023 the original author or authors.
+ * Copyright 2006-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,20 +23,20 @@
import java.util.Map;
import java.util.Set;
-import org.springframework.batch.core.ChunkListener;
-import org.springframework.batch.core.ItemProcessListener;
-import org.springframework.batch.core.ItemReadListener;
-import org.springframework.batch.core.ItemWriteListener;
-import org.springframework.batch.core.Job;
-import org.springframework.batch.core.SkipListener;
-import org.springframework.batch.core.Step;
-import org.springframework.batch.core.StepExecutionListener;
-import org.springframework.batch.core.StepListener;
+import org.springframework.batch.core.launch.JobOperator;
+import org.springframework.batch.core.listener.ChunkListener;
+import org.springframework.batch.core.listener.ItemProcessListener;
+import org.springframework.batch.core.listener.ItemReadListener;
+import org.springframework.batch.core.listener.ItemWriteListener;
+import org.springframework.batch.core.job.Job;
+import org.springframework.batch.core.listener.SkipListener;
+import org.springframework.batch.core.step.Step;
+import org.springframework.batch.core.listener.StepExecutionListener;
+import org.springframework.batch.core.listener.StepListener;
import org.springframework.batch.core.job.flow.Flow;
-import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.partition.PartitionHandler;
-import org.springframework.batch.core.partition.support.Partitioner;
-import org.springframework.batch.core.partition.support.StepExecutionAggregator;
+import org.springframework.batch.core.partition.Partitioner;
+import org.springframework.batch.core.partition.StepExecutionAggregator;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.step.AbstractStep;
import org.springframework.batch.core.step.builder.AbstractTaskletStepBuilder;
@@ -61,7 +61,6 @@
import org.springframework.batch.item.ItemWriter;
import org.springframework.batch.repeat.CompletionPolicy;
import org.springframework.batch.repeat.policy.SimpleCompletionPolicy;
-import org.springframework.batch.repeat.support.TaskExecutorRepeatTemplate;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.classify.BinaryExceptionClassifier;
@@ -126,7 +125,7 @@ public class StepParserStepFactoryBean implements FactoryBean, BeanN
//
private Job job;
- private JobLauncher jobLauncher;
+ private JobOperator jobOperator;
private JobParametersExtractor jobParametersExtractor;
@@ -185,8 +184,6 @@ public class StepParserStepFactoryBean implements FactoryBean, BeanN
private TaskExecutor taskExecutor;
- private Integer throttleLimit;
-
private ItemReader extends I> itemReader;
private ItemProcessor super I, ? extends O> itemProcessor;
@@ -275,8 +272,8 @@ protected void enhanceCommonStep(StepBuilderHelper> builder) {
builder.startLimit(startLimit);
}
for (Object listener : stepExecutionListeners) {
- if (listener instanceof StepExecutionListener) {
- builder.listener((StepExecutionListener) listener);
+ if (listener instanceof StepExecutionListener stepExecutionListener) {
+ builder.listener(stepExecutionListener);
}
}
}
@@ -473,9 +470,6 @@ protected void enhanceTaskletStepBuilder(AbstractTaskletStepBuilder> builder)
}
builder.taskExecutor(taskExecutor);
- if (throttleLimit != null) {
- builder.throttleLimit(throttleLimit);
- }
builder.transactionManager(transactionManager);
if (transactionTimeout != null || propagation != null || isolation != null
|| noRollbackExceptionClasses != null) {
@@ -522,7 +516,7 @@ private Step createJobStep() throws Exception {
JobStepBuilder builder = new StepBuilder(name, jobRepository).job(job);
enhanceCommonStep(builder);
builder.parametersExtractor(jobParametersExtractor);
- builder.launcher(jobLauncher);
+ builder.operator(jobOperator);
return builder.build();
}
@@ -571,14 +565,14 @@ private void validateDependency(String dependentName, Object dependentValue, Str
* @return {@code true} if the object has a value
*/
private boolean isPresent(Object o) {
- if (o instanceof Integer) {
- return isPositive((Integer) o);
+ if (o instanceof Integer i) {
+ return isPositive(i);
}
- if (o instanceof Collection) {
- return !((Collection>) o).isEmpty();
+ if (o instanceof Collection> collection) {
+ return !collection.isEmpty();
}
- if (o instanceof Map) {
- return !((Map, ?>) o).isEmpty();
+ if (o instanceof Map, ?> map) {
+ return !map.isEmpty();
}
return o != null;
}
@@ -662,8 +656,8 @@ public void setJobParametersExtractor(JobParametersExtractor jobParametersExtrac
this.jobParametersExtractor = jobParametersExtractor;
}
- public void setJobLauncher(JobLauncher jobLauncher) {
- this.jobLauncher = jobLauncher;
+ public void setJobOperator(JobOperator jobOperator) {
+ this.jobOperator = jobOperator;
}
// =========================================================
@@ -893,7 +887,7 @@ public void setKeyGenerator(KeyGenerator keyGenerator) {
/**
*
* Public setter for the capacity of the cache in the retry policy. If there are more
- * items than the specified capacity, the the step fails without being skipped or
+ * items than the specified capacity, the step fails without being skipped or
* recovered, and an exception is thrown. This guards against inadvertent infinite
* loops generated by item identity problems.
*
@@ -992,19 +986,6 @@ public void setTaskExecutor(TaskExecutor taskExecutor) {
this.taskExecutor = taskExecutor;
}
- /**
- * Public setter for the throttle limit. This limits the number of tasks queued for
- * concurrent processing to prevent thread pools from being overwhelmed. Defaults to
- * {@link TaskExecutorRepeatTemplate#DEFAULT_THROTTLE_LIMIT}.
- * @param throttleLimit The throttle limit to set.
- * @deprecated since 5.0, scheduled for removal in 6.0. This API is not intended for
- * end users anyway. It is only used by the XML namespace parser.
- */
- @Deprecated(since = "5.0", forRemoval = true)
- public void setThrottleLimit(Integer throttleLimit) {
- this.throttleLimit = throttleLimit;
- }
-
/**
* @param itemReader The {@link ItemReader} to set.
*/
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/TaskletParser.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/TaskletParser.java
index a1a6668579..0f316b81ed 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/TaskletParser.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/TaskletParser.java
@@ -213,7 +213,7 @@ private void handleExceptionElement(Element element, ParserContext parserContext
ManagedList list = new ManagedList<>();
list.setMergeEnabled(exceptionClassesElement.hasAttribute(MERGE_ATTR)
&& Boolean.parseBoolean(exceptionClassesElement.getAttribute(MERGE_ATTR)));
- addExceptionClasses("include", exceptionClassesElement, list, parserContext);
+ addExceptionClasses("include", exceptionClassesElement, list);
propertyValues.addPropertyValue(propertyName, list);
}
else if (children.size() > 1) {
@@ -224,7 +224,7 @@ else if (children.size() > 1) {
}
private void addExceptionClasses(String elementName, Element exceptionClassesElement,
- ManagedList list, ParserContext parserContext) {
+ ManagedList list) {
for (Element child : DomUtils.getChildElementsByTagName(exceptionClassesElement, elementName)) {
String className = child.getAttribute("class");
list.add(new TypedStringValue(className, Class.class));
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/TopLevelStepParser.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/TopLevelStepParser.java
index 19f5fffb80..297c6ef6bb 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/TopLevelStepParser.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/TopLevelStepParser.java
@@ -15,6 +15,7 @@
*/
package org.springframework.batch.core.configuration.xml;
+import org.springframework.batch.core.step.Step;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
@@ -22,7 +23,7 @@
/**
* Parser for the <step/> top level element in the Batch namespace. Sets up and
- * returns a bean definition for a {@link org.springframework.batch.core.Step}.
+ * returns a bean definition for a {@link Step}.
*
* @author Thomas Risberg
*
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/converter/DefaultJobParametersConverter.java b/spring-batch-core/src/main/java/org/springframework/batch/core/converter/DefaultJobParametersConverter.java
index a9f671ca56..454c691872 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/converter/DefaultJobParametersConverter.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/converter/DefaultJobParametersConverter.java
@@ -19,9 +19,9 @@
import java.util.Map.Entry;
import java.util.Properties;
-import org.springframework.batch.core.JobParameter;
-import org.springframework.batch.core.JobParameters;
-import org.springframework.batch.core.JobParametersBuilder;
+import org.springframework.batch.core.job.parameters.JobParameter;
+import org.springframework.batch.core.job.parameters.JobParameters;
+import org.springframework.batch.core.job.parameters.JobParametersBuilder;
import org.springframework.core.convert.support.ConfigurableConversionService;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.lang.NonNull;
@@ -108,7 +108,7 @@ public JobParameters getJobParameters(@Nullable Properties properties) {
}
/**
- * @see org.springframework.batch.core.converter.JobParametersConverter#getProperties(org.springframework.batch.core.JobParameters)
+ * @see org.springframework.batch.core.converter.JobParametersConverter#getProperties(JobParameters)
*/
@Override
public Properties getProperties(@Nullable JobParameters jobParameters) {
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/converter/JobParametersConverter.java b/spring-batch-core/src/main/java/org/springframework/batch/core/converter/JobParametersConverter.java
index 60d9f58ab5..128938f48f 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/converter/JobParametersConverter.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/converter/JobParametersConverter.java
@@ -18,8 +18,8 @@
import java.util.Properties;
-import org.springframework.batch.core.JobParameters;
-import org.springframework.batch.core.JobParametersBuilder;
+import org.springframework.batch.core.job.parameters.JobParameters;
+import org.springframework.batch.core.job.parameters.JobParametersBuilder;
import org.springframework.lang.Nullable;
/**
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/converter/JsonJobParametersConverter.java b/spring-batch-core/src/main/java/org/springframework/batch/core/converter/JsonJobParametersConverter.java
index c7a0c784f7..a38b071c0f 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/converter/JsonJobParametersConverter.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/converter/JsonJobParametersConverter.java
@@ -18,8 +18,8 @@
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
-import org.springframework.batch.core.JobParameter;
-import org.springframework.batch.core.JobParameters;
+import org.springframework.batch.core.job.parameters.JobParameter;
+import org.springframework.batch.core.job.parameters.JobParameters;
/**
* Converter for {@link JobParameters} instances that uses a JSON naming convention for
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/job/AbstractJob.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/AbstractJob.java
index 34d6d19f58..783a239cb0 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/job/AbstractJob.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/AbstractJob.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006-2023 the original author or authors.
+ * Copyright 2006-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,10 +21,6 @@
import java.util.List;
import java.util.stream.Collectors;
-import io.micrometer.core.instrument.LongTaskTimer;
-import io.micrometer.core.instrument.MeterRegistry;
-import io.micrometer.core.instrument.Metrics;
-import io.micrometer.core.instrument.Tag;
import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationRegistry;
import org.apache.commons.logging.Log;
@@ -32,25 +28,19 @@
import org.springframework.batch.core.BatchStatus;
import org.springframework.batch.core.ExitStatus;
-import org.springframework.batch.core.Job;
-import org.springframework.batch.core.JobExecution;
-import org.springframework.batch.core.JobExecutionException;
-import org.springframework.batch.core.JobExecutionListener;
-import org.springframework.batch.core.JobInterruptedException;
-import org.springframework.batch.core.JobParametersIncrementer;
-import org.springframework.batch.core.JobParametersValidator;
+import org.springframework.batch.core.job.parameters.DefaultJobParametersValidator;
+import org.springframework.batch.core.job.parameters.JobParametersIncrementer;
+import org.springframework.batch.core.job.parameters.JobParametersValidator;
+import org.springframework.batch.core.listener.JobExecutionListener;
import org.springframework.batch.core.SpringBatchVersion;
-import org.springframework.batch.core.StartLimitExceededException;
-import org.springframework.batch.core.Step;
-import org.springframework.batch.core.StepExecution;
+import org.springframework.batch.core.observability.BatchMetrics;
+import org.springframework.batch.core.observability.jfr.events.job.JobExecutionEvent;
+import org.springframework.batch.core.observability.micrometer.MicrometerMetrics;
+import org.springframework.batch.core.step.Step;
+import org.springframework.batch.core.step.StepExecution;
import org.springframework.batch.core.launch.NoSuchJobException;
import org.springframework.batch.core.launch.support.ExitCodeMapper;
import org.springframework.batch.core.listener.CompositeJobExecutionListener;
-import org.springframework.batch.core.observability.BatchJobContext;
-import org.springframework.batch.core.observability.BatchJobObservation;
-import org.springframework.batch.core.observability.BatchJobObservationConvention;
-import org.springframework.batch.core.observability.BatchMetrics;
-import org.springframework.batch.core.observability.DefaultBatchJobObservationConvention;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.repository.JobRestartException;
import org.springframework.batch.core.scope.context.JobSynchronizationManager;
@@ -90,11 +80,7 @@ public abstract class AbstractJob implements Job, StepLocator, BeanNameAware, In
private StepHandler stepHandler;
- private ObservationRegistry observationRegistry = ObservationRegistry.NOOP;
-
- private MeterRegistry meterRegistry = Metrics.globalRegistry;
-
- private BatchJobObservationConvention observationConvention = new DefaultBatchJobObservationConvention();
+ private ObservationRegistry observationRegistry;
/**
* Default constructor.
@@ -129,6 +115,10 @@ public void setJobParametersValidator(JobParametersValidator jobParametersValida
@Override
public void afterPropertiesSet() throws Exception {
Assert.state(jobRepository != null, "JobRepository must be set");
+ if (this.observationRegistry == null) {
+ logger.info("No ObservationRegistry has been set, defaulting to ObservationRegistry NOOP");
+ this.observationRegistry = ObservationRegistry.NOOP;
+ }
}
/**
@@ -281,16 +271,15 @@ public final void execute(JobExecution execution) {
}
JobSynchronizationManager.register(execution);
- String activeJobMeterName = "job.active";
- LongTaskTimer longTaskTimer = BatchMetrics.createLongTaskTimer(this.meterRegistry, activeJobMeterName,
- "Active jobs", Tag.of(BatchMetrics.METRICS_PREFIX + activeJobMeterName + ".name",
- execution.getJobInstance().getJobName()));
- LongTaskTimer.Sample longTaskTimerSample = longTaskTimer.start();
- Observation observation = BatchMetrics
- .createObservation(BatchJobObservation.BATCH_JOB_OBSERVATION.getName(), new BatchJobContext(execution),
- this.observationRegistry)
- .contextualName(execution.getJobInstance().getJobName())
- .observationConvention(this.observationConvention)
+ JobExecutionEvent jobExecutionEvent = new JobExecutionEvent(execution.getJobInstance().getJobName(),
+ execution.getJobInstance().getId(), execution.getId());
+ jobExecutionEvent.begin();
+ Observation observation = MicrometerMetrics
+ .createObservation(BatchMetrics.METRICS_PREFIX + "job", this.observationRegistry)
+ .highCardinalityKeyValue(BatchMetrics.METRICS_PREFIX + "job.instanceId",
+ execution.getJobInstance().getId().toString())
+ .highCardinalityKeyValue(BatchMetrics.METRICS_PREFIX + "job.executionId", execution.getId().toString())
+ .lowCardinalityKeyValue(BatchMetrics.METRICS_PREFIX + "job.name", execution.getJobInstance().getJobName())
.start();
try (Observation.Scope scope = observation.openScope()) {
@@ -353,7 +342,8 @@ public final void execute(JobExecution execution) {
execution.setExitStatus(exitStatus.and(newExitStatus));
}
stopObservation(execution, observation);
- longTaskTimerSample.stop();
+ jobExecutionEvent.exitStatus = execution.getExitStatus().getExitCode();
+ jobExecutionEvent.commit();
execution.setEndTime(LocalDateTime.now());
try {
@@ -378,6 +368,8 @@ private void stopObservation(JobExecution execution, Observation observation) {
if (!throwables.isEmpty()) {
observation.error(mergedThrowables(throwables));
}
+ observation.lowCardinalityKeyValue(BatchMetrics.METRICS_PREFIX + "job.status",
+ execution.getExitStatus().getExitCode());
observation.stop();
}
@@ -435,18 +427,10 @@ private void updateStatus(JobExecution jobExecution, BatchStatus status) {
jobRepository.update(jobExecution);
}
- public void setObservationConvention(BatchJobObservationConvention observationConvention) {
- this.observationConvention = observationConvention;
- }
-
public void setObservationRegistry(ObservationRegistry observationRegistry) {
this.observationRegistry = observationRegistry;
}
- public void setMeterRegistry(MeterRegistry meterRegistry) {
- this.meterRegistry = meterRegistry;
- }
-
@Override
public String toString() {
return ClassUtils.getShortName(getClass()) + ": [name=" + name + "]";
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/DefaultJobKeyGenerator.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/DefaultJobKeyGenerator.java
similarity index 86%
rename from spring-batch-core/src/main/java/org/springframework/batch/core/DefaultJobKeyGenerator.java
rename to spring-batch-core/src/main/java/org/springframework/batch/core/job/DefaultJobKeyGenerator.java
index 9944fdfadd..5da1bcde58 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/DefaultJobKeyGenerator.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/DefaultJobKeyGenerator.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006-2023 the original author or authors.
+ * Copyright 2006-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.springframework.batch.core;
+package org.springframework.batch.core.job;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
@@ -21,6 +21,8 @@
import java.util.List;
import java.util.Map;
+import org.springframework.batch.core.job.parameters.JobParameter;
+import org.springframework.batch.core.job.parameters.JobParameters;
import org.springframework.util.Assert;
import org.springframework.util.DigestUtils;
@@ -34,7 +36,7 @@
* @author Mahmoud Ben Hassine
* @since 2.2
*/
-public class DefaultJobKeyGenerator implements JobKeyGenerator {
+public class DefaultJobKeyGenerator implements JobKeyGenerator {
/**
* Generates the job key to be used based on the {@link JobParameters} instance
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/Job.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/Job.java
similarity index 77%
rename from spring-batch-core/src/main/java/org/springframework/batch/core/Job.java
rename to spring-batch-core/src/main/java/org/springframework/batch/core/job/Job.java
index fe0d0fbf15..22c91a5651 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/Job.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/Job.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006-2022 the original author or authors.
+ * Copyright 2006-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,9 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.springframework.batch.core;
+package org.springframework.batch.core.job;
-import org.springframework.batch.core.job.DefaultJobParametersValidator;
+import org.springframework.batch.core.job.parameters.DefaultJobParametersValidator;
+import org.springframework.batch.core.job.parameters.JobParametersIncrementer;
+import org.springframework.batch.core.job.parameters.JobParametersValidator;
import org.springframework.lang.Nullable;
/**
@@ -26,9 +28,18 @@
* @author Dave Syer
* @author Mahmoud Ben Hassine
*/
+@FunctionalInterface
public interface Job {
- String getName();
+ /**
+ * The name of the job. This is used to distinguish between different jobs and must be
+ * unique within the job repository. If not explicitly set, the name will default to
+ * the fully qualified class name.
+ * @return the name of the job (never {@code null})
+ */
+ default String getName() {
+ return this.getClass().getName();
+ }
/**
* Flag to indicate if this job can be restarted, at least in principle.
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/JobExecution.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/JobExecution.java
similarity index 96%
rename from spring-batch-core/src/main/java/org/springframework/batch/core/JobExecution.java
rename to spring-batch-core/src/main/java/org/springframework/batch/core/job/JobExecution.java
index c595f43e40..bfccba5e4b 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/JobExecution.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/JobExecution.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.batch.core;
+package org.springframework.batch.core.job;
import java.io.IOException;
import java.io.ObjectInputStream;
@@ -28,6 +28,9 @@
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
+import org.springframework.batch.core.*;
+import org.springframework.batch.core.job.parameters.JobParameters;
+import org.springframework.batch.core.step.StepExecution;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.lang.Nullable;
@@ -203,7 +206,7 @@ public void upgradeStatus(BatchStatus status) {
/**
* Convenience getter for the {@code id} of the enclosing job. Useful for DAO
* implementations.
- * @return the @{code id} of the enclosing job.
+ * @return the {@code id} of the enclosing job.
*/
public Long getJobId() {
if (jobInstance != null) {
@@ -302,11 +305,10 @@ public void setCreateTime(LocalDateTime createTime) {
}
/**
- * Package-private method for re-constituting the step executions from existing
- * instances.
+ * Add a step execution from an existing instance.
* @param stepExecution The {@code stepExecution} execution to be added.
*/
- void addStepExecution(StepExecution stepExecution) {
+ public void addStepExecution(StepExecution stepExecution) {
stepExecutions.add(stepExecution);
}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/JobExecutionException.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/JobExecutionException.java
similarity index 97%
rename from spring-batch-core/src/main/java/org/springframework/batch/core/JobExecutionException.java
rename to spring-batch-core/src/main/java/org/springframework/batch/core/job/JobExecutionException.java
index f64cd022ef..c808e40845 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/JobExecutionException.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/JobExecutionException.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.springframework.batch.core;
+package org.springframework.batch.core.job;
/**
* Root of exception hierarchy for checked exceptions in job and step execution. Clients
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/JobInstance.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/JobInstance.java
similarity index 93%
rename from spring-batch-core/src/main/java/org/springframework/batch/core/JobInstance.java
rename to spring-batch-core/src/main/java/org/springframework/batch/core/job/JobInstance.java
index cecdc5481e..0feb0dfa7a 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/JobInstance.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/JobInstance.java
@@ -14,8 +14,10 @@
* limitations under the License.
*/
-package org.springframework.batch.core;
+package org.springframework.batch.core.job;
+import org.springframework.batch.core.Entity;
+import org.springframework.batch.core.job.parameters.JobParameters;
import org.springframework.util.Assert;
/**
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/JobInterruptedException.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/JobInterruptedException.java
similarity index 95%
rename from spring-batch-core/src/main/java/org/springframework/batch/core/JobInterruptedException.java
rename to spring-batch-core/src/main/java/org/springframework/batch/core/job/JobInterruptedException.java
index 8ba4c75832..7282e81894 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/JobInterruptedException.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/JobInterruptedException.java
@@ -14,7 +14,9 @@
* limitations under the License.
*/
-package org.springframework.batch.core;
+package org.springframework.batch.core.job;
+
+import org.springframework.batch.core.BatchStatus;
/**
* Exception to indicate the job has been interrupted. The exception state indicated is
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/JobKeyGenerator.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/JobKeyGenerator.java
similarity index 78%
rename from spring-batch-core/src/main/java/org/springframework/batch/core/JobKeyGenerator.java
rename to spring-batch-core/src/main/java/org/springframework/batch/core/job/JobKeyGenerator.java
index 589434b97f..36371d5ebd 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/JobKeyGenerator.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/JobKeyGenerator.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2013-2022 the original author or authors.
+ * Copyright 2013-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,7 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.springframework.batch.core;
+package org.springframework.batch.core.job;
+
+import org.springframework.batch.core.job.parameters.JobParameters;
/**
* Strategy interface for the generation of the key used in identifying unique
@@ -21,10 +23,11 @@
*
* @author Michael Minella
* @author Mahmoud Ben Hassine
- * @param The type of the source data used to calculate the key.
+ * @author Taeik Lim
* @since 2.2
*/
-public interface JobKeyGenerator {
+@FunctionalInterface
+public interface JobKeyGenerator {
/**
* Method to generate the unique key used to identify a job instance.
@@ -32,6 +35,6 @@ public interface JobKeyGenerator {
* {@code null}).
* @return a unique string identifying the job based on the information supplied.
*/
- String generateKey(T source);
+ String generateKey(JobParameters source);
}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/job/SimpleJob.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/SimpleJob.java
index b22317ef28..d2d2db1825 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/job/SimpleJob.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/SimpleJob.java
@@ -21,12 +21,8 @@
import java.util.List;
import org.springframework.batch.core.BatchStatus;
-import org.springframework.batch.core.Job;
-import org.springframework.batch.core.JobExecution;
-import org.springframework.batch.core.JobInterruptedException;
-import org.springframework.batch.core.StartLimitExceededException;
-import org.springframework.batch.core.Step;
-import org.springframework.batch.core.StepExecution;
+import org.springframework.batch.core.step.Step;
+import org.springframework.batch.core.step.StepExecution;
import org.springframework.batch.core.repository.JobRestartException;
import org.springframework.batch.core.step.StepLocator;
@@ -79,8 +75,8 @@ public Collection getStepNames() {
for (Step step : steps) {
names.add(step.getName());
- if (step instanceof StepLocator) {
- names.addAll(((StepLocator) step).getStepNames());
+ if (step instanceof StepLocator stepLocator) {
+ names.addAll(stepLocator.getStepNames());
}
}
return names;
@@ -100,8 +96,8 @@ public Step getStep(String stepName) {
if (step.getName().equals(stepName)) {
return step;
}
- else if (step instanceof StepLocator) {
- Step result = ((StepLocator) step).getStep(stepName);
+ else if (step instanceof StepLocator stepLocator) {
+ Step result = stepLocator.getStep(stepName);
if (result != null) {
return result;
}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/job/SimpleStepHandler.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/SimpleStepHandler.java
index 930ab7f0cb..3693272865 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/job/SimpleStepHandler.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/SimpleStepHandler.java
@@ -19,12 +19,8 @@
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.batch.core.BatchStatus;
-import org.springframework.batch.core.JobExecution;
-import org.springframework.batch.core.JobInstance;
-import org.springframework.batch.core.JobInterruptedException;
-import org.springframework.batch.core.StartLimitExceededException;
-import org.springframework.batch.core.Step;
-import org.springframework.batch.core.StepExecution;
+import org.springframework.batch.core.step.Step;
+import org.springframework.batch.core.step.StepExecution;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.repository.JobRestartException;
import org.springframework.batch.item.ExecutionContext;
@@ -179,8 +175,7 @@ public StepExecution handleStep(Step step, JobExecution execution)
* Detect whether a step execution belongs to this job execution.
* @param jobExecution the current job execution
* @param stepExecution an existing step execution
- * @return true if the {@link org.springframework.batch.core.StepExecution} is part of
- * the {@link org.springframework.batch.core.JobExecution}
+ * @return true if the {@link StepExecution} is part of the {@link JobExecution}
*/
private boolean stepExecutionPartOfExistingJobExecution(JobExecution jobExecution, StepExecution stepExecution) {
return stepExecution != null && stepExecution.getJobExecutionId() != null
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/StartLimitExceededException.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/StartLimitExceededException.java
similarity index 95%
rename from spring-batch-core/src/main/java/org/springframework/batch/core/StartLimitExceededException.java
rename to spring-batch-core/src/main/java/org/springframework/batch/core/job/StartLimitExceededException.java
index 46e6582585..90eb31eb3d 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/StartLimitExceededException.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/StartLimitExceededException.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.batch.core;
+package org.springframework.batch.core.job;
/**
* Indicates the step's start limit has been exceeded.
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/job/StepHandler.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/StepHandler.java
index ebe18808e3..59052f6512 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/job/StepHandler.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/StepHandler.java
@@ -16,12 +16,8 @@
package org.springframework.batch.core.job;
-import org.springframework.batch.core.Job;
-import org.springframework.batch.core.JobExecution;
-import org.springframework.batch.core.JobInterruptedException;
-import org.springframework.batch.core.StartLimitExceededException;
-import org.springframework.batch.core.Step;
-import org.springframework.batch.core.StepExecution;
+import org.springframework.batch.core.step.Step;
+import org.springframework.batch.core.step.StepExecution;
import org.springframework.batch.core.repository.JobRestartException;
/**
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/UnexpectedJobExecutionException.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/UnexpectedJobExecutionException.java
similarity index 96%
rename from spring-batch-core/src/main/java/org/springframework/batch/core/UnexpectedJobExecutionException.java
rename to spring-batch-core/src/main/java/org/springframework/batch/core/job/UnexpectedJobExecutionException.java
index eda11002f3..82cecb6aeb 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/UnexpectedJobExecutionException.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/UnexpectedJobExecutionException.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.batch.core;
+package org.springframework.batch.core.job;
/**
* Indicates to the framework that a critical error has occurred and processing should
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/job/builder/FlowBuilder.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/builder/FlowBuilder.java
index 17d3559471..963e7bb92f 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/job/builder/FlowBuilder.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/builder/FlowBuilder.java
@@ -25,7 +25,7 @@
import java.util.Set;
import org.springframework.batch.core.ExitStatus;
-import org.springframework.batch.core.Step;
+import org.springframework.batch.core.step.Step;
import org.springframework.batch.core.job.flow.Flow;
import org.springframework.batch.core.job.flow.FlowExecutionStatus;
import org.springframework.batch.core.job.flow.JobExecutionDecider;
@@ -238,8 +238,8 @@ protected Flow flow() {
}
flow = new SimpleFlow(name);
// optimization for flows that only have one state that itself is a flow:
- if (currentState instanceof FlowState && states.size() == 1) {
- return ((FlowState) currentState).getFlows().iterator().next();
+ if (currentState instanceof FlowState flowState && states.size() == 1) {
+ return flowState.getFlows().iterator().next();
}
addDanglingEndStates();
flow.setStateTransitions(transitions);
@@ -282,23 +282,21 @@ private void doFrom(Object input) {
private State createState(Object input) {
State result;
- if (input instanceof Step) {
+ if (input instanceof Step step) {
if (!states.containsKey(input)) {
- Step step = (Step) input;
- states.put(input, new StepState(prefix + "step" + (stepCounter++), step));
+ states.put(input, new StepState(prefix + "step" + stepCounter++, step));
}
result = states.get(input);
}
- else if (input instanceof JobExecutionDecider) {
+ else if (input instanceof JobExecutionDecider jobExecutionDecider) {
if (!states.containsKey(input)) {
- states.put(input,
- new DecisionState((JobExecutionDecider) input, prefix + "decision" + (decisionCounter++)));
+ states.put(input, new DecisionState(jobExecutionDecider, prefix + "decision" + decisionCounter++));
}
result = states.get(input);
}
- else if (input instanceof Flow) {
+ else if (input instanceof Flow f) {
if (!states.containsKey(input)) {
- states.put(input, new FlowState((Flow) input, prefix + "flow" + (flowCounter++)));
+ states.put(input, new FlowState(f, prefix + "flow" + flowCounter++));
}
result = states.get(input);
}
@@ -311,7 +309,7 @@ else if (input instanceof Flow) {
private SplitState createState(Collection flows, TaskExecutor executor, SplitState parentSplit) {
if (!states.containsKey(flows)) {
- states.put(flows, new SplitState(flows, prefix + "split" + (splitCounter++), parentSplit));
+ states.put(flows, new SplitState(flows, prefix + "split" + splitCounter++, parentSplit));
}
SplitState result = (SplitState) states.get(flows);
if (executor != null) {
@@ -392,7 +390,7 @@ protected void stop(String pattern) {
}
protected void stop(String pattern, State restart) {
- EndState next = new EndState(FlowExecutionStatus.STOPPED, "STOPPED", prefix + "stop" + (endCounter++), true);
+ EndState next = new EndState(FlowExecutionStatus.STOPPED, "STOPPED", prefix + "stop" + endCounter++, true);
addTransition(pattern, next);
currentState = next;
addTransition("*", restart);
@@ -403,7 +401,7 @@ private void end(String pattern) {
}
private void end(String pattern, String code) {
- addTransition(pattern, new EndState(FlowExecutionStatus.COMPLETED, code, prefix + "end" + (endCounter++)));
+ addTransition(pattern, new EndState(FlowExecutionStatus.COMPLETED, code, prefix + "end" + endCounter++));
}
private void fail(String pattern) {
@@ -634,11 +632,11 @@ public SplitBuilder(FlowBuilder parent, TaskExecutor executor) {
*/
public FlowBuilder add(Flow... flows) {
Collection list = new ArrayList<>(Arrays.asList(flows));
- String name = "split" + (parent.splitCounter++);
+ String name = "split" + parent.splitCounter++;
State one = parent.currentState;
- if (one instanceof SplitState) {
- parent.currentState = parent.createState(list, executor, (SplitState) one);
+ if (one instanceof SplitState splitState) {
+ parent.currentState = parent.createState(list, executor, splitState);
return parent;
}
@@ -647,8 +645,8 @@ public FlowBuilder add(Flow... flows) {
stateBuilder.currentState = one;
list.add(stateBuilder.build());
}
- else if (one instanceof FlowState && parent.states.size() == 1) {
- list.add(((FlowState) one).getFlows().iterator().next());
+ else if (one instanceof FlowState flowState && parent.states.size() == 1) {
+ list.add(flowState.getFlows().iterator().next());
}
parent.currentState = parent.createState(list, executor, null);
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/job/builder/FlowJobBuilder.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/builder/FlowJobBuilder.java
index ccb718d39f..0e75832001 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/job/builder/FlowJobBuilder.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/builder/FlowJobBuilder.java
@@ -15,8 +15,8 @@
*/
package org.springframework.batch.core.job.builder;
-import org.springframework.batch.core.Job;
-import org.springframework.batch.core.Step;
+import org.springframework.batch.core.job.Job;
+import org.springframework.batch.core.step.Step;
import org.springframework.batch.core.job.flow.Flow;
import org.springframework.batch.core.job.flow.FlowJob;
import org.springframework.batch.core.job.flow.JobExecutionDecider;
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/job/builder/JobBuilder.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/builder/JobBuilder.java
index 6ea4823189..c42eb8e6d7 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/job/builder/JobBuilder.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/builder/JobBuilder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006-2023 the original author or authors.
+ * Copyright 2006-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,7 +15,7 @@
*/
package org.springframework.batch.core.job.builder;
-import org.springframework.batch.core.Step;
+import org.springframework.batch.core.step.Step;
import org.springframework.batch.core.job.flow.Flow;
import org.springframework.batch.core.job.flow.JobExecutionDecider;
import org.springframework.batch.core.repository.JobRepository;
@@ -31,17 +31,17 @@
public class JobBuilder extends JobBuilderHelper {
/**
- * Create a new builder for a job with the given name.
- * @param name the name of the job
- * @deprecated use {@link JobBuilder#JobBuilder(String, JobRepository)}
+ * Create a new builder for a job with the given job repository. The name of the job
+ * will be set to the bean name by default.
+ * @param jobRepository the job repository to which the job should report to.
+ * @since 6.0
*/
- @Deprecated(since = "5.0", forRemoval = true)
- public JobBuilder(String name) {
- super(name);
+ public JobBuilder(JobRepository jobRepository) {
+ super(jobRepository);
}
/**
- * Create a new builder for a job with the given name.
+ * Create a new builder for a job with the given name and job repository.
* @param name the name of the job
* @param jobRepository the job repository to which the job should report to
* @since 5.0
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/job/builder/JobBuilderHelper.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/builder/JobBuilderHelper.java
index cacf2eab6f..48028c94a1 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/job/builder/JobBuilderHelper.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/builder/JobBuilderHelper.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006-2023 the original author or authors.
+ * Copyright 2006-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,20 +22,17 @@
import java.util.List;
import java.util.Set;
-import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.observation.ObservationRegistry;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
-import org.springframework.batch.core.JobExecutionListener;
-import org.springframework.batch.core.JobParametersIncrementer;
-import org.springframework.batch.core.JobParametersValidator;
+import org.springframework.batch.core.listener.JobExecutionListener;
+import org.springframework.batch.core.job.parameters.JobParametersIncrementer;
+import org.springframework.batch.core.job.parameters.JobParametersValidator;
import org.springframework.batch.core.annotation.AfterJob;
import org.springframework.batch.core.annotation.BeforeJob;
import org.springframework.batch.core.job.AbstractJob;
import org.springframework.batch.core.listener.JobListenerFactoryBean;
-import org.springframework.batch.core.observability.BatchJobObservationConvention;
-import org.springframework.batch.core.observability.DefaultBatchJobObservationConvention;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.support.ReflectionUtils;
@@ -56,13 +53,12 @@ public abstract class JobBuilderHelper> {
/**
* Create a new {@link JobBuilderHelper}.
- * @param name the job name
- * @deprecated use {@link JobBuilderHelper#JobBuilderHelper(String, JobRepository)}
+ * @param jobRepository the job repository
+ * @since 6.0
*/
- @Deprecated(since = "5.1", forRemoval = true)
- public JobBuilderHelper(String name) {
+ public JobBuilderHelper(JobRepository jobRepository) {
this.properties = new CommonJobProperties();
- properties.name = name;
+ properties.jobRepository = jobRepository;
}
/**
@@ -110,33 +106,6 @@ public B incrementer(JobParametersIncrementer jobParametersIncrementer) {
return result;
}
- /**
- * Sets the job repository for the job.
- * @param jobRepository the job repository (mandatory)
- * @return this to enable fluent chaining
- * @deprecated use {@link JobBuilderHelper#JobBuilderHelper(String, JobRepository)}
- */
- @Deprecated(since = "5.1", forRemoval = true)
- public B repository(JobRepository jobRepository) {
- properties.jobRepository = jobRepository;
- @SuppressWarnings("unchecked")
- B result = (B) this;
- return result;
- }
-
- /**
- * Sets the job observation convention.
- * @param observationConvention the job observation convention (optional)
- * @return this to enable fluent chaining
- * @since 5.1
- */
- public B observationConvention(BatchJobObservationConvention observationConvention) {
- properties.observationConvention = observationConvention;
- @SuppressWarnings("unchecked")
- B result = (B) this;
- return result;
- }
-
/**
* Sets the observation registry for the job.
* @param observationRegistry the observation registry (optional)
@@ -149,18 +118,6 @@ public B observationRegistry(ObservationRegistry observationRegistry) {
return result;
}
- /**
- * Sets the meter registry for the job.
- * @param meterRegistry the meter registry (optional)
- * @return this to enable fluent chaining
- */
- public B meterRegistry(MeterRegistry meterRegistry) {
- properties.meterRegistry = meterRegistry;
- @SuppressWarnings("unchecked")
- B result = (B) this;
- return result;
- }
-
/**
* Registers objects using the annotation based listener configuration.
* @param listener the object that has a method configured with listener annotation
@@ -228,18 +185,10 @@ protected void enhance(AbstractJob job) {
if (jobParametersValidator != null) {
job.setJobParametersValidator(jobParametersValidator);
}
- BatchJobObservationConvention observationConvention = properties.getObservationConvention();
- if (observationConvention != null) {
- job.setObservationConvention(observationConvention);
- }
ObservationRegistry observationRegistry = properties.getObservationRegistry();
if (observationRegistry != null) {
job.setObservationRegistry(observationRegistry);
}
- MeterRegistry meterRegistry = properties.getMeterRegistry();
- if (meterRegistry != null) {
- job.setMeterRegistry(meterRegistry);
- }
Boolean restartable = properties.getRestartable();
if (restartable != null) {
@@ -254,18 +203,16 @@ protected void enhance(AbstractJob job) {
public static class CommonJobProperties {
+ private String name;
+
private Set jobExecutionListeners = new LinkedHashSet<>();
private boolean restartable = true;
private JobRepository jobRepository;
- private BatchJobObservationConvention observationConvention = new DefaultBatchJobObservationConvention();
-
private ObservationRegistry observationRegistry;
- private MeterRegistry meterRegistry;
-
private JobParametersIncrementer jobParametersIncrementer;
private JobParametersValidator jobParametersValidator;
@@ -277,9 +224,7 @@ public CommonJobProperties(CommonJobProperties properties) {
this.name = properties.name;
this.restartable = properties.restartable;
this.jobRepository = properties.jobRepository;
- this.observationConvention = properties.observationConvention;
this.observationRegistry = properties.observationRegistry;
- this.meterRegistry = properties.meterRegistry;
this.jobExecutionListeners = new LinkedHashSet<>(properties.jobExecutionListeners);
this.jobParametersIncrementer = properties.jobParametersIncrementer;
this.jobParametersValidator = properties.jobParametersValidator;
@@ -309,14 +254,6 @@ public void setJobRepository(JobRepository jobRepository) {
this.jobRepository = jobRepository;
}
- public BatchJobObservationConvention getObservationConvention() {
- return observationConvention;
- }
-
- public void setObservationConvention(BatchJobObservationConvention observationConvention) {
- this.observationConvention = observationConvention;
- }
-
public ObservationRegistry getObservationRegistry() {
return observationRegistry;
}
@@ -325,14 +262,6 @@ public void setObservationRegistry(ObservationRegistry observationRegistry) {
this.observationRegistry = observationRegistry;
}
- public MeterRegistry getMeterRegistry() {
- return meterRegistry;
- }
-
- public void setMeterRegistry(MeterRegistry meterRegistry) {
- this.meterRegistry = meterRegistry;
- }
-
public String getName() {
return name;
}
@@ -361,8 +290,6 @@ public void setRestartable(boolean restartable) {
this.restartable = restartable;
}
- private String name;
-
}
}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/job/builder/JobFlowBuilder.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/builder/JobFlowBuilder.java
index db456d4863..0ae824d3dc 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/job/builder/JobFlowBuilder.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/builder/JobFlowBuilder.java
@@ -15,7 +15,7 @@
*/
package org.springframework.batch.core.job.builder;
-import org.springframework.batch.core.Step;
+import org.springframework.batch.core.step.Step;
import org.springframework.batch.core.job.flow.Flow;
import org.springframework.batch.core.job.flow.JobExecutionDecider;
import org.springframework.beans.factory.InitializingBean;
@@ -63,9 +63,9 @@ public JobFlowBuilder(FlowJobBuilder parent, Flow flow) {
public FlowJobBuilder build() {
Flow flow = flow();
- if (flow instanceof InitializingBean) {
+ if (flow instanceof InitializingBean initializingBean) {
try {
- ((InitializingBean) flow).afterPropertiesSet();
+ initializingBean.afterPropertiesSet();
}
catch (Exception e) {
throw new FlowBuilderException(e);
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/job/builder/SimpleJobBuilder.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/builder/SimpleJobBuilder.java
index b714d484ea..5668353f4c 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/job/builder/SimpleJobBuilder.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/builder/SimpleJobBuilder.java
@@ -18,8 +18,8 @@
import java.util.ArrayList;
import java.util.List;
-import org.springframework.batch.core.Job;
-import org.springframework.batch.core.Step;
+import org.springframework.batch.core.job.Job;
+import org.springframework.batch.core.step.Step;
import org.springframework.batch.core.job.SimpleJob;
import org.springframework.batch.core.job.flow.JobExecutionDecider;
import org.springframework.core.task.TaskExecutor;
@@ -156,7 +156,7 @@ public SimpleJobBuilder next(Step step) {
* @param executor instance of {@link TaskExecutor} to be used.
* @return builder for fluent chaining
*/
- public JobFlowBuilder.SplitBuilder split(TaskExecutor executor) {
+ public FlowBuilder.SplitBuilder split(TaskExecutor executor) {
for (Step step : steps) {
if (builder == null) {
builder = new JobFlowBuilder(new FlowJobBuilder(this), step);
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/FlowExecutor.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/FlowExecutor.java
index 4f24417f36..9b916d749a 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/FlowExecutor.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/FlowExecutor.java
@@ -15,11 +15,11 @@
*/
package org.springframework.batch.core.job.flow;
-import org.springframework.batch.core.JobExecution;
-import org.springframework.batch.core.JobInterruptedException;
-import org.springframework.batch.core.StartLimitExceededException;
-import org.springframework.batch.core.Step;
-import org.springframework.batch.core.StepExecution;
+import org.springframework.batch.core.job.JobExecution;
+import org.springframework.batch.core.job.JobInterruptedException;
+import org.springframework.batch.core.job.StartLimitExceededException;
+import org.springframework.batch.core.step.Step;
+import org.springframework.batch.core.step.StepExecution;
import org.springframework.batch.core.repository.JobRestartException;
import org.springframework.lang.Nullable;
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/FlowJob.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/FlowJob.java
index 33e2f491fe..65e3604e85 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/FlowJob.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/FlowJob.java
@@ -19,10 +19,10 @@
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
-import org.springframework.batch.core.Job;
-import org.springframework.batch.core.JobExecution;
-import org.springframework.batch.core.JobExecutionException;
-import org.springframework.batch.core.Step;
+import org.springframework.batch.core.job.Job;
+import org.springframework.batch.core.job.JobExecution;
+import org.springframework.batch.core.job.JobExecutionException;
+import org.springframework.batch.core.step.Step;
import org.springframework.batch.core.job.AbstractJob;
import org.springframework.batch.core.job.SimpleStepHandler;
import org.springframework.batch.core.step.StepHolder;
@@ -96,13 +96,13 @@ private void findSteps(Flow flow, Map map) {
map.put(name, locator.getStep(name));
}
}
- else if (state instanceof StepHolder) {
- Step step = ((StepHolder) state).getStep();
+ else if (state instanceof StepHolder stepHolder) {
+ Step step = stepHolder.getStep();
String name = step.getName();
stepMap.put(name, step);
}
- else if (state instanceof FlowHolder) {
- for (Flow subflow : ((FlowHolder) state).getFlows()) {
+ else if (state instanceof FlowHolder flowHolder) {
+ for (Flow subflow : flowHolder.getFlows()) {
findSteps(subflow, map);
}
}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/FlowStep.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/FlowStep.java
index de1ca1b5c0..4dea1a8b49 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/FlowStep.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/FlowStep.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2009-2012 the original author or authors.
+ * Copyright 2009-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,9 +15,9 @@
*/
package org.springframework.batch.core.job.flow;
-import org.springframework.batch.core.JobExecutionException;
-import org.springframework.batch.core.Step;
-import org.springframework.batch.core.StepExecution;
+import org.springframework.batch.core.job.JobExecutionException;
+import org.springframework.batch.core.step.Step;
+import org.springframework.batch.core.step.StepExecution;
import org.springframework.batch.core.job.SimpleStepHandler;
import org.springframework.batch.core.job.StepHandler;
import org.springframework.batch.core.repository.JobRepository;
@@ -32,6 +32,7 @@
* parent and one each for the flow steps).
*
* @author Dave Syer
+ * @author Mahmoud Ben Hassine
*
*/
public class FlowStep extends AbstractStep {
@@ -39,10 +40,12 @@ public class FlowStep extends AbstractStep {
private Flow flow;
/**
- * Default constructor convenient for configuration purposes.
+ * Create a new instance of a {@link FlowStep} with the given job repository.
+ * @param jobRepository the job repository to use. Must not be null.
+ * @since 6.0
*/
- public FlowStep() {
- super(null);
+ public FlowStep(JobRepository jobRepository) {
+ super(jobRepository);
}
/**
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/JobExecutionDecider.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/JobExecutionDecider.java
index 9ccad19835..34db827b1d 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/JobExecutionDecider.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/JobExecutionDecider.java
@@ -15,8 +15,8 @@
*/
package org.springframework.batch.core.job.flow;
-import org.springframework.batch.core.JobExecution;
-import org.springframework.batch.core.StepExecution;
+import org.springframework.batch.core.job.JobExecution;
+import org.springframework.batch.core.step.StepExecution;
import org.springframework.lang.Nullable;
/**
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/JobFlowExecutor.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/JobFlowExecutor.java
index c1583e25f1..e72278f638 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/JobFlowExecutor.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/JobFlowExecutor.java
@@ -18,11 +18,11 @@
import org.springframework.batch.core.BatchStatus;
import org.springframework.batch.core.ExitStatus;
-import org.springframework.batch.core.JobExecution;
-import org.springframework.batch.core.JobInterruptedException;
-import org.springframework.batch.core.StartLimitExceededException;
-import org.springframework.batch.core.Step;
-import org.springframework.batch.core.StepExecution;
+import org.springframework.batch.core.job.JobExecution;
+import org.springframework.batch.core.job.JobInterruptedException;
+import org.springframework.batch.core.job.StartLimitExceededException;
+import org.springframework.batch.core.step.Step;
+import org.springframework.batch.core.step.StepExecution;
import org.springframework.batch.core.job.StepHandler;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.repository.JobRestartException;
@@ -40,7 +40,7 @@
*/
public class JobFlowExecutor implements FlowExecutor {
- private final ThreadLocal stepExecutionHolder = new ThreadLocal<>();
+ private static final ThreadLocal stepExecutionHolder = new ThreadLocal<>();
private final JobExecution execution;
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/support/SimpleFlow.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/support/SimpleFlow.java
index 1818d017fd..e90f7db82a 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/support/SimpleFlow.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/support/SimpleFlow.java
@@ -29,8 +29,8 @@
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
-import org.springframework.batch.core.Step;
-import org.springframework.batch.core.StepExecution;
+import org.springframework.batch.core.step.Step;
+import org.springframework.batch.core.step.StepExecution;
import org.springframework.batch.core.job.flow.Flow;
import org.springframework.batch.core.job.flow.FlowExecution;
import org.springframework.batch.core.job.flow.FlowExecutionException;
@@ -243,9 +243,8 @@ protected State nextState(String stateName, FlowExecutionStatus status, StepExec
}
protected boolean isFlowContinued(State state, FlowExecutionStatus status, StepExecution stepExecution) {
- boolean continued = true;
- continued = state != null && status != FlowExecutionStatus.STOPPED;
+ boolean continued = state != null && status != FlowExecutionStatus.STOPPED;
if (stepExecution != null) {
Boolean reRun = (Boolean) stepExecution.getExecutionContext().get("batch.restart");
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/support/state/EndState.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/support/state/EndState.java
index c6528b03d0..f628c11878 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/support/state/EndState.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/support/state/EndState.java
@@ -17,7 +17,7 @@
package org.springframework.batch.core.job.flow.support.state;
import org.springframework.batch.core.BatchStatus;
-import org.springframework.batch.core.StepExecution;
+import org.springframework.batch.core.step.StepExecution;
import org.springframework.batch.core.job.flow.FlowExecutionStatus;
import org.springframework.batch.core.job.flow.FlowExecutor;
import org.springframework.batch.core.job.flow.State;
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/support/state/SplitState.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/support/state/SplitState.java
index 1caef3c1a0..8bedef1114 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/support/state/SplitState.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/support/state/SplitState.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006-2023 the original author or authors.
+ * Copyright 2006-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,6 +19,7 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
+import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
@@ -119,7 +120,7 @@ public FlowExecutionStatus handle(final FlowExecutor executor) throws Exception
FlowExecutionStatus parentSplitStatus = parentSplit == null ? null : parentSplit.handle(executor);
Collection results = new ArrayList<>();
-
+ List exceptions = new ArrayList<>();
// Could use a CompletionService here?
for (Future task : tasks) {
try {
@@ -128,15 +129,19 @@ public FlowExecutionStatus handle(final FlowExecutor executor) throws Exception
catch (ExecutionException e) {
// Unwrap the expected exceptions
Throwable cause = e.getCause();
- if (cause instanceof Exception) {
- throw (Exception) cause;
+ if (cause instanceof Exception exception) {
+ exceptions.add(exception);
}
else {
- throw e;
+ exceptions.add(e);
}
}
}
+ if (!exceptions.isEmpty()) {
+ throw exceptions.get(0);
+ }
+
FlowExecutionStatus flowExecutionStatus = doAggregation(results, executor);
if (parentSplitStatus != null) {
return Collections.max(Arrays.asList(flowExecutionStatus, parentSplitStatus));
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/support/state/StepState.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/support/state/StepState.java
index 73d20b4193..ec38ae382a 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/support/state/StepState.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/support/state/StepState.java
@@ -20,7 +20,7 @@
import java.util.Collection;
import java.util.List;
-import org.springframework.batch.core.Step;
+import org.springframework.batch.core.step.Step;
import org.springframework.batch.core.job.flow.FlowExecutionStatus;
import org.springframework.batch.core.job.flow.FlowExecutor;
import org.springframework.batch.core.job.flow.State;
@@ -84,8 +84,8 @@ public Collection getStepNames() {
names.add(step.getName());
- if (step instanceof StepLocator) {
- names.addAll(((StepLocator) step).getStepNames());
+ if (step instanceof StepLocator stepLocator) {
+ names.addAll(stepLocator.getStepNames());
}
return names;
@@ -98,8 +98,8 @@ public Step getStep(String stepName) throws NoSuchStepException {
if (step.getName().equals(stepName)) {
result = step;
}
- else if (step instanceof StepLocator) {
- result = ((StepLocator) step).getStep(stepName);
+ else if (step instanceof StepLocator stepLocator) {
+ result = stepLocator.getStep(stepName);
}
return result;
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/job/CompositeJobParametersValidator.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/parameters/CompositeJobParametersValidator.java
similarity index 90%
rename from spring-batch-core/src/main/java/org/springframework/batch/core/job/CompositeJobParametersValidator.java
rename to spring-batch-core/src/main/java/org/springframework/batch/core/job/parameters/CompositeJobParametersValidator.java
index 8ed88989d9..743afa4d85 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/job/CompositeJobParametersValidator.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/parameters/CompositeJobParametersValidator.java
@@ -13,13 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.springframework.batch.core.job;
+package org.springframework.batch.core.job.parameters;
import java.util.List;
-import org.springframework.batch.core.JobParameters;
-import org.springframework.batch.core.JobParametersInvalidException;
-import org.springframework.batch.core.JobParametersValidator;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/job/DefaultJobParametersValidator.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/parameters/DefaultJobParametersValidator.java
similarity index 95%
rename from spring-batch-core/src/main/java/org/springframework/batch/core/job/DefaultJobParametersValidator.java
rename to spring-batch-core/src/main/java/org/springframework/batch/core/job/parameters/DefaultJobParametersValidator.java
index 114670c294..836cc74803 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/job/DefaultJobParametersValidator.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/parameters/DefaultJobParametersValidator.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.springframework.batch.core.job;
+package org.springframework.batch.core.job.parameters;
import java.util.Arrays;
import java.util.Collection;
@@ -23,9 +23,6 @@
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
-import org.springframework.batch.core.JobParameters;
-import org.springframework.batch.core.JobParametersInvalidException;
-import org.springframework.batch.core.JobParametersValidator;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/JobParameter.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/parameters/JobParameter.java
similarity index 98%
rename from spring-batch-core/src/main/java/org/springframework/batch/core/JobParameter.java
rename to spring-batch-core/src/main/java/org/springframework/batch/core/job/parameters/JobParameter.java
index cd9853a5aa..7c02f48b5c 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/JobParameter.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/parameters/JobParameter.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.batch.core;
+package org.springframework.batch.core.job.parameters;
import java.io.Serializable;
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/JobParameters.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/parameters/JobParameters.java
similarity index 98%
rename from spring-batch-core/src/main/java/org/springframework/batch/core/JobParameters.java
rename to spring-batch-core/src/main/java/org/springframework/batch/core/job/parameters/JobParameters.java
index a5e54b0c65..b4de56936f 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/JobParameters.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/parameters/JobParameters.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.batch.core;
+package org.springframework.batch.core.job.parameters;
import java.io.Serializable;
import java.time.LocalDate;
@@ -373,7 +373,7 @@ public String toString() {
for (Map.Entry> entry : this.parameters.entrySet()) {
parameters.add(String.format("'%s':'%s'", entry.getKey(), entry.getValue()));
}
- return new StringBuilder("{").append(String.join(",", parameters)).append("}").toString();
+ return "{" + String.join(",", parameters) + "}";
}
}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/JobParametersBuilder.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/parameters/JobParametersBuilder.java
similarity index 78%
rename from spring-batch-core/src/main/java/org/springframework/batch/core/JobParametersBuilder.java
rename to spring-batch-core/src/main/java/org/springframework/batch/core/job/parameters/JobParametersBuilder.java
index a12ad7bc67..7bebeedcd9 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/JobParametersBuilder.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/parameters/JobParametersBuilder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006-2024 the original author or authors.
+ * Copyright 2006-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.batch.core;
+package org.springframework.batch.core.job.parameters;
import java.time.LocalDate;
import java.time.LocalDateTime;
@@ -23,7 +23,7 @@
import java.util.HashMap;
import java.util.Map;
-import org.springframework.batch.core.explore.JobExplorer;
+import org.springframework.batch.core.job.JobInstance;
import org.springframework.lang.NonNull;
import org.springframework.util.Assert;
@@ -49,8 +49,6 @@ public class JobParametersBuilder {
private Map> parameterMap;
- private JobExplorer jobExplorer;
-
/**
* Default constructor. Initializes the builder with empty parameters.
*/
@@ -58,31 +56,11 @@ public JobParametersBuilder() {
this.parameterMap = new HashMap<>();
}
- /**
- * @param jobExplorer {@link JobExplorer} used for looking up previous job parameter
- * information.
- */
- public JobParametersBuilder(JobExplorer jobExplorer) {
- this.jobExplorer = jobExplorer;
- this.parameterMap = new HashMap<>();
- }
-
/**
* Copy constructor. Initializes the builder with the supplied parameters.
* @param jobParameters {@link JobParameters} instance used to initialize the builder.
*/
public JobParametersBuilder(JobParameters jobParameters) {
- this(jobParameters, null);
- }
-
- /**
- * Copy constructor. Initializes the builder with the supplied parameters.
- * @param jobParameters {@link JobParameters} instance used to initialize the builder.
- * @param jobExplorer {@link JobExplorer} used for looking up previous job parameter
- * information.
- */
- public JobParametersBuilder(JobParameters jobParameters, JobExplorer jobExplorer) {
- this.jobExplorer = jobExplorer;
this.parameterMap = new HashMap<>(jobParameters.getParameters());
}
@@ -316,51 +294,4 @@ public JobParametersBuilder addJobParameters(JobParameters jobParameters) {
return this;
}
- /**
- * Initializes the {@link JobParameters} based on the state of the {@link Job}. This
- * should be called after all parameters have been entered into the builder. All
- * parameters already set on this builder instance are appended to those retrieved
- * from the job incrementer, overriding any with the same key (this is the same
- * behavior as
- * {@link org.springframework.batch.core.launch.support.CommandLineJobRunner} with the
- * {@code -next} option and
- * {@link org.springframework.batch.core.launch.JobOperator#startNextInstance(String)}).
- * @param job The job for which the {@link JobParameters} are being constructed.
- * @return a reference to this object.
- *
- * @since 4.0
- */
- public JobParametersBuilder getNextJobParameters(Job job) {
- Assert.state(this.jobExplorer != null, "A JobExplorer is required to get next job parameters");
- Assert.notNull(job, "Job must not be null");
- Assert.notNull(job.getJobParametersIncrementer(),
- "No job parameters incrementer found for job=" + job.getName());
-
- String name = job.getName();
- JobParameters nextParameters;
- JobInstance lastInstance = this.jobExplorer.getLastJobInstance(name);
- JobParametersIncrementer incrementer = job.getJobParametersIncrementer();
- if (lastInstance == null) {
- // Start from a completely clean sheet
- nextParameters = incrementer.getNext(new JobParameters());
- }
- else {
- JobExecution previousExecution = this.jobExplorer.getLastJobExecution(lastInstance);
- if (previousExecution == null) {
- // Normally this will not happen - an instance exists with no executions
- nextParameters = incrementer.getNext(new JobParameters());
- }
- else {
- nextParameters = incrementer.getNext(previousExecution.getJobParameters());
- }
- }
-
- // start with parameters from the incrementer
- Map> nextParametersMap = new HashMap<>(nextParameters.getParameters());
- // append new parameters (overriding those with the same key)
- nextParametersMap.putAll(this.parameterMap);
- this.parameterMap = nextParametersMap;
- return this;
- }
-
}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/JobParametersIncrementer.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/parameters/JobParametersIncrementer.java
similarity index 95%
rename from spring-batch-core/src/main/java/org/springframework/batch/core/JobParametersIncrementer.java
rename to spring-batch-core/src/main/java/org/springframework/batch/core/job/parameters/JobParametersIncrementer.java
index 86d94dc52a..61caebe6a2 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/JobParametersIncrementer.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/parameters/JobParametersIncrementer.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.springframework.batch.core;
+package org.springframework.batch.core.job.parameters;
import org.springframework.lang.Nullable;
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/JobParametersInvalidException.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/parameters/JobParametersInvalidException.java
similarity index 86%
rename from spring-batch-core/src/main/java/org/springframework/batch/core/JobParametersInvalidException.java
rename to spring-batch-core/src/main/java/org/springframework/batch/core/job/parameters/JobParametersInvalidException.java
index c769bda7c6..2e9b2a139e 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/JobParametersInvalidException.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/parameters/JobParametersInvalidException.java
@@ -13,7 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.springframework.batch.core;
+package org.springframework.batch.core.job.parameters;
+
+import org.springframework.batch.core.job.Job;
+import org.springframework.batch.core.job.JobExecutionException;
/**
* Exception for {@link Job} to signal that some {@link JobParameters} are invalid.
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/JobParametersValidator.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/parameters/JobParametersValidator.java
similarity index 92%
rename from spring-batch-core/src/main/java/org/springframework/batch/core/JobParametersValidator.java
rename to spring-batch-core/src/main/java/org/springframework/batch/core/job/parameters/JobParametersValidator.java
index 15e691bc34..c794e2b385 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/JobParametersValidator.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/parameters/JobParametersValidator.java
@@ -13,8 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.springframework.batch.core;
+package org.springframework.batch.core.job.parameters;
+import org.springframework.batch.core.job.Job;
import org.springframework.lang.Nullable;
/**
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/JobExecutionNotFailedException.java b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/JobExecutionNotFailedException.java
index b76206d945..688dfc7eb5 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/JobExecutionNotFailedException.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/JobExecutionNotFailedException.java
@@ -15,7 +15,7 @@
*/
package org.springframework.batch.core.launch;
-import org.springframework.batch.core.JobExecutionException;
+import org.springframework.batch.core.job.JobExecutionException;
/**
* Checked exception to indicate that user asked for a job execution to be resumed when
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/JobExecutionNotRunningException.java b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/JobExecutionNotRunningException.java
index d376735ee9..ac588eca01 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/JobExecutionNotRunningException.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/JobExecutionNotRunningException.java
@@ -15,7 +15,7 @@
*/
package org.springframework.batch.core.launch;
-import org.springframework.batch.core.JobExecutionException;
+import org.springframework.batch.core.job.JobExecutionException;
/**
* Checked exception indicating that a JobExecution that is not currently running has been
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/JobExecutionNotStoppedException.java b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/JobExecutionNotStoppedException.java
index 11567df815..9fa0ab46f8 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/JobExecutionNotStoppedException.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/JobExecutionNotStoppedException.java
@@ -15,7 +15,7 @@
*/
package org.springframework.batch.core.launch;
-import org.springframework.batch.core.JobExecutionException;
+import org.springframework.batch.core.job.JobExecutionException;
/**
* Checked exception to indicate that user asked for a job execution to be aborted when
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/JobInstanceAlreadyExistsException.java b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/JobInstanceAlreadyExistsException.java
index f99bc19725..748f94af2c 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/JobInstanceAlreadyExistsException.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/JobInstanceAlreadyExistsException.java
@@ -15,8 +15,8 @@
*/
package org.springframework.batch.core.launch;
-import org.springframework.batch.core.Job;
-import org.springframework.batch.core.JobExecutionException;
+import org.springframework.batch.core.job.Job;
+import org.springframework.batch.core.job.JobExecutionException;
/**
* Checked exception to indicate that a required {@link Job} is not available.
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/JobLauncher.java b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/JobLauncher.java
index 79afede5ce..20f3eaf4d8 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/JobLauncher.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/JobLauncher.java
@@ -15,10 +15,10 @@
*/
package org.springframework.batch.core.launch;
-import org.springframework.batch.core.Job;
-import org.springframework.batch.core.JobExecution;
-import org.springframework.batch.core.JobParameters;
-import org.springframework.batch.core.JobParametersInvalidException;
+import org.springframework.batch.core.job.Job;
+import org.springframework.batch.core.job.JobExecution;
+import org.springframework.batch.core.job.parameters.JobParameters;
+import org.springframework.batch.core.job.parameters.JobParametersInvalidException;
import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException;
import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException;
import org.springframework.batch.core.repository.JobRestartException;
@@ -34,8 +34,11 @@
* @author Dave Syer
* @author Taeik Lim
* @author Mahmoud Ben Hassine
+ * @deprecated since 6.0 in favor of {@link JobOperator}. Scheduled for removal in 6.2 or
+ * later.
*/
@FunctionalInterface
+@Deprecated(since = "6.0", forRemoval = true)
public interface JobLauncher {
/**
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/JobOperator.java b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/JobOperator.java
index a56947412b..a50d0dda95 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/JobOperator.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/JobOperator.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006-2024 the original author or authors.
+ * Copyright 2006-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,86 +20,40 @@
import java.util.Properties;
import java.util.Set;
-import org.springframework.batch.core.Job;
-import org.springframework.batch.core.JobExecution;
-import org.springframework.batch.core.JobInstance;
-import org.springframework.batch.core.JobParameters;
-import org.springframework.batch.core.JobParametersIncrementer;
-import org.springframework.batch.core.JobParametersInvalidException;
-import org.springframework.batch.core.StepExecution;
-import org.springframework.batch.core.UnexpectedJobExecutionException;
+import org.springframework.batch.core.job.Job;
+import org.springframework.batch.core.job.JobExecution;
+import org.springframework.batch.core.job.JobInstance;
+import org.springframework.batch.core.job.parameters.JobParameters;
+import org.springframework.batch.core.job.parameters.JobParametersIncrementer;
+import org.springframework.batch.core.job.parameters.JobParametersInvalidException;
+import org.springframework.batch.core.step.StepExecution;
+import org.springframework.batch.core.job.UnexpectedJobExecutionException;
+import org.springframework.batch.core.configuration.JobRegistry;
import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException;
import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException;
import org.springframework.batch.core.repository.JobRestartException;
import org.springframework.lang.Nullable;
/**
- * Low level interface for inspecting and controlling jobs with access only to primitive
- * and collection types. Suitable for a command-line client (e.g. that launches a new
- * process for each operation), or a remote launcher like a JMX console.
+ * High level interface for operating batch jobs.
*
* @author Dave Syer
* @author Mahmoud Ben Hassine
+ * @author Yejeong Ham
* @since 2.0
*/
-public interface JobOperator {
+@SuppressWarnings("removal")
+public interface JobOperator extends JobLauncher {
/**
- * List the {@link JobExecution JobExecutions} associated with a particular
- * {@link JobInstance}, in reverse order of creation (and therefore usually of
- * execution).
- * @param instanceId the id of a {@link JobInstance}
- * @return the id values of all the {@link JobExecution JobExecutions} associated with
- * this instance
- * @throws NoSuchJobInstanceException if the {@link JobInstance} associated with the
- * {@code instanceId} cannot be found.
- */
- List getExecutions(long instanceId) throws NoSuchJobInstanceException;
-
- /**
- * List the {@link JobInstance JobInstances} for a given job name, in reverse order of
- * creation (and therefore usually of first execution).
- * @param jobName the job name that all the instances have
- * @param start the start index of the instances
- * @param count the maximum number of values to return
- * @return the id values of the {@link JobInstance JobInstances}
- * @throws NoSuchJobException is thrown if no {@link JobInstance}s for the jobName
- * exist.
- */
- List getJobInstances(String jobName, int start, int count) throws NoSuchJobException;
-
- /**
- * @param jobName {@link String} name of the job.
- * @param jobParameters {@link JobParameters} parameters for the job instance.
- * @return the {@link JobInstance} with the given name and parameters, or
- * {@code null}.
- *
- * @since 5.0
- */
- @Nullable
- default JobInstance getJobInstance(String jobName, JobParameters jobParameters) {
- throw new UnsupportedOperationException();
- }
-
- /**
- * Get the id values of all the running {@link JobExecution JobExecutions} with the
- * given job name.
- * @param jobName the name of the job to search under
- * @return the id values of the running {@link JobExecution} instances
- * @throws NoSuchJobException if there are no {@link JobExecution JobExecutions} with
- * that job name
- */
- Set getRunningExecutions(String jobName) throws NoSuchJobException;
-
- /**
- * Get the {@link JobParameters} as a human readable String (new line separated
- * key=value pairs).
- * @param executionId the id of an existing {@link JobExecution}
- * @return the job parameters that were used to launch the associated instance
- * @throws NoSuchJobExecutionException if the id was not associated with any
- * {@link JobExecution}
+ * List the available job names that can be launched with
+ * {@link #start(String, Properties)}.
+ * @return a set of job names
+ * @deprecated since 6.0 in favor of {@link JobRegistry#getJobNames()}. Scheduled for
+ * removal in 6.2 or later.
*/
- String getParameters(long executionId) throws NoSuchJobExecutionException;
+ @Deprecated(since = "6.0", forRemoval = true)
+ Set getJobNames();
/**
* Start a new instance of a job with the parameters specified.
@@ -111,12 +65,38 @@ default JobInstance getJobInstance(String jobName, JobParameters jobParameters)
* parameters already exists
* @throws JobParametersInvalidException thrown if any of the job parameters are
* invalid.
+ * @deprecated since 6.0 in favor of {@link #start(Job, JobParameters)}. Scheduled for
+ * removal in 6.2 or later.
*/
+ @Deprecated(since = "6.0", forRemoval = true)
default Long start(String jobName, Properties parameters)
throws NoSuchJobException, JobInstanceAlreadyExistsException, JobParametersInvalidException {
throw new UnsupportedOperationException();
}
+ /**
+ * Start a new instance of a job with the specified parameters. If the job defines a
+ * {@link JobParametersIncrementer}, then the incrementer will be used to calculate
+ * the next parameters in the sequence and the provided parameters will be ignored.
+ * @param job the {@link Job} to start
+ * @param jobParameters the {@link JobParameters} to start the job with
+ * @return the {@link JobExecution} that was started
+ * @throws NoSuchJobException if the given {@link Job} is not registered
+ * @throws JobParametersInvalidException thrown if any of the job parameters are
+ * @throws JobExecutionAlreadyRunningException if the JobInstance identified by the
+ * properties already has an execution running. invalid.
+ * @throws JobRestartException if the execution would be a re-start, but a re-start is
+ * either not allowed or not needed.
+ * @throws JobInstanceAlreadyCompleteException if the job has been run before with the
+ * same parameters and completed successfully
+ * @throws IllegalArgumentException if the job or job parameters are null.
+ */
+ default JobExecution start(Job job, JobParameters jobParameters)
+ throws NoSuchJobException, JobInstanceAlreadyCompleteException, JobExecutionAlreadyRunningException,
+ JobRestartException, JobParametersInvalidException {
+ throw new UnsupportedOperationException();
+ }
+
/**
* Restart a failed or stopped {@link JobExecution}. Fails with an exception if the id
* provided does not exist or corresponds to a {@link JobInstance} that in normal
@@ -132,10 +112,32 @@ default Long start(String jobName, Properties parameters)
* @throws JobRestartException if there is a non-specific error with the restart (e.g.
* corrupt or inconsistent restart data)
* @throws JobParametersInvalidException if the parameters are not valid for this job
+ * @deprecated since 6.0 in favor of {@link #restart(JobExecution)}. Scheduled for
+ * removal in 6.2 or later.
*/
+ @Deprecated(since = "6.0", forRemoval = true)
Long restart(long executionId) throws JobInstanceAlreadyCompleteException, NoSuchJobExecutionException,
NoSuchJobException, JobRestartException, JobParametersInvalidException;
+ /**
+ * Restart a failed or stopped {@link JobExecution}. Fails with an exception if the
+ * execution provided does not exist or corresponds to a {@link JobInstance} that in
+ * normal circumstances already completed successfully.
+ * @param jobExecution the failed or stopped {@link JobExecution} to restart
+ * @return the {@link JobExecution} that was started
+ * @throws JobInstanceAlreadyCompleteException if the job was already successfully
+ * completed
+ * @throws NoSuchJobExecutionException if the id was not associated with any
+ * {@link JobExecution}
+ * @throws NoSuchJobException if the {@link JobExecution} was found, but its
+ * corresponding {@link Job} is no longer available for launching
+ * @throws JobRestartException if there is a non-specific error with the restart (e.g.
+ * corrupt or inconsistent restart data)
+ * @throws JobParametersInvalidException if the parameters are not valid for this job
+ */
+ JobExecution restart(JobExecution jobExecution) throws JobInstanceAlreadyCompleteException,
+ NoSuchJobExecutionException, NoSuchJobException, JobRestartException, JobParametersInvalidException;
+
/**
* Launch the next in a sequence of {@link JobInstance} determined by the
* {@link JobParametersIncrementer} attached to the specified job. If the previous
@@ -160,11 +162,37 @@ Long restart(long executionId) throws JobInstanceAlreadyCompleteException, NoSuc
* that is already executing.
* @throws JobInstanceAlreadyCompleteException thrown if attempting to restart a
* completed job.
+ * @deprecated since 6.0 in favor of {@link #startNextInstance(Job)}. Scheduled for
+ * removal in 6.2 or later.
*/
+ @Deprecated(since = "6.0", forRemoval = true)
Long startNextInstance(String jobName) throws NoSuchJobException, JobParametersNotFoundException,
JobRestartException, JobExecutionAlreadyRunningException, JobInstanceAlreadyCompleteException,
UnexpectedJobExecutionException, JobParametersInvalidException;
+ /**
+ * Launch the next in a sequence of {@link JobInstance} determined by the
+ * {@link JobParametersIncrementer} attached to the specified job. If the previous
+ * instance is still in a failed state, this method should still create a new instance
+ * and run it with different parameters (as long as the
+ * {@link JobParametersIncrementer} is working).
+ *
+ *
+ * The last three exception described below should be extremely unlikely, but cannot
+ * be ruled out entirely. It points to some other thread or process trying to use this
+ * method (or a similar one) at the same time.
+ * @param job the job to launch
+ * @return the {@link JobExecution} created when the job is launched
+ * @throws UnexpectedJobExecutionException if an unexpected condition arises
+ * @throws JobRestartException thrown if a job is restarted illegally.
+ * @throws JobExecutionAlreadyRunningException thrown if attempting to restart a job
+ * that is already executing.
+ * @throws JobInstanceAlreadyCompleteException thrown if attempting to restart a
+ * completed job.
+ */
+ JobExecution startNextInstance(Job job) throws JobRestartException, JobExecutionAlreadyRunningException,
+ JobInstanceAlreadyCompleteException, UnexpectedJobExecutionException;
+
/**
* Send a stop signal to the {@link JobExecution} with the supplied id. The signal is
* successfully sent if this method returns true, but that doesn't mean that the job
@@ -176,9 +204,137 @@ Long startNextInstance(String jobName) throws NoSuchJobException, JobParametersN
* supplied
* @throws JobExecutionNotRunningException if the {@link JobExecution} is not running
* (so cannot be stopped)
+ * @deprecated since 6.0 in favor of {@link #stop(JobExecution)}. Scheduled for
+ * removal in 6.2 or later.
*/
+ @Deprecated(since = "6.0", forRemoval = true)
boolean stop(long executionId) throws NoSuchJobExecutionException, JobExecutionNotRunningException;
+ /**
+ * Send a stop signal to the supplied {@link JobExecution}. The signal is successfully
+ * sent if this method returns true, but that doesn't mean that the job has stopped.
+ * The only way to be sure of that is to poll the job execution status.
+ * @param jobExecution the running {@link JobExecution}
+ * @return true if the message was successfully sent (does not guarantee that the job
+ * has stopped)
+ * @throws JobExecutionNotRunningException if the supplied {@link JobExecution} is not
+ * running (so cannot be stopped)
+ */
+ boolean stop(JobExecution jobExecution) throws JobExecutionNotRunningException;
+
+ /**
+ * Mark the {@link JobExecution} as ABANDONED. If a stop signal is ignored because the
+ * process died this is the best way to mark a job as finished with (as opposed to
+ * STOPPED). An abandoned job execution cannot be restarted by the framework.
+ * @param jobExecutionId the job execution id to abort
+ * @return the {@link JobExecution} that was aborted
+ * @throws NoSuchJobExecutionException thrown if there is no job execution for the
+ * jobExecutionId.
+ * @throws JobExecutionAlreadyRunningException if the job is running (it should be
+ * stopped first)
+ * @deprecated since 6.0 in favor of {@link #abandon(JobExecution)}. Scheduled for
+ * removal in 6.2 or later.
+ */
+ @Deprecated(since = "6.0", forRemoval = true)
+ JobExecution abandon(long jobExecutionId) throws NoSuchJobExecutionException, JobExecutionAlreadyRunningException;
+
+ /**
+ * Mark the {@link JobExecution} as ABANDONED. If a stop signal is ignored because the
+ * process died this is the best way to mark a job as finished with (as opposed to
+ * STOPPED). An abandoned job execution cannot be restarted by the framework.
+ * @param jobExecution the job execution to abort
+ * @return the {@link JobExecution} that was aborted
+ * @throws JobExecutionAlreadyRunningException if the job execution is running (it
+ * should be stopped first)
+ */
+ JobExecution abandon(JobExecution jobExecution) throws JobExecutionAlreadyRunningException;
+
+ /**
+ * Marks the given {@link JobExecution} as {@code FAILED} when it is stuck in a
+ * {@code STARTED} state due to an abrupt shutdown or failure, in order to make it
+ * restartable. This operation makes a previously non-restartable execution eligible
+ * for restart by updating its execution context with the flag {@code recovered=true}.
+ * @param jobExecution the {@link JobExecution} to recover
+ * @return the {@link JobExecution} after it has been marked as recovered
+ * @since 6.0
+ */
+ JobExecution recover(JobExecution jobExecution);
+
+ /**
+ * List the {@link JobExecution JobExecutions} associated with a particular
+ * {@link JobInstance}, in reverse order of creation (and therefore usually of
+ * execution).
+ * @param instanceId the id of a {@link JobInstance}
+ * @return the id values of all the {@link JobExecution JobExecutions} associated with
+ * this instance
+ * @throws NoSuchJobInstanceException if the {@link JobInstance} associated with the
+ * {@code instanceId} cannot be found.
+ * @deprecated Since 6.0 in favor of
+ * {@link org.springframework.batch.core.repository.JobRepository#getJobExecutions(JobInstance)}.
+ * Scheduled for removal in 6.2 or later.
+ */
+ @Deprecated(since = "6.0", forRemoval = true)
+ List getExecutions(long instanceId) throws NoSuchJobInstanceException;
+
+ /**
+ * List the {@link JobInstance JobInstances} for a given job name, in reverse order of
+ * creation (and therefore usually of first execution).
+ * @param jobName the job name that all the instances have
+ * @param start the start index of the instances
+ * @param count the maximum number of values to return
+ * @return the id values of the {@link JobInstance JobInstances}
+ * @throws NoSuchJobException is thrown if no {@link JobInstance}s for the jobName
+ * exist.
+ * @deprecated Since 6.0 in favor of
+ * {@link org.springframework.batch.core.repository.JobRepository#getJobInstances(String, int, int)}.
+ * Scheduled for removal in 6.2 or later.
+ */
+ @Deprecated(since = "6.0", forRemoval = true)
+ List getJobInstances(String jobName, int start, int count) throws NoSuchJobException;
+
+ /**
+ * @param jobName {@link String} name of the job.
+ * @param jobParameters {@link JobParameters} parameters for the job instance.
+ * @return the {@link JobInstance} with the given name and parameters, or
+ * {@code null}.
+ * @deprecated Since 6.0 in favor of
+ * {@link org.springframework.batch.core.repository.JobRepository#getJobInstance(String, JobParameters)}.
+ * Scheduled for removal in 6.2 or later.
+ */
+ @Deprecated(since = "6.0", forRemoval = true)
+ @Nullable
+ default JobInstance getJobInstance(String jobName, JobParameters jobParameters) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Get the id values of all the running {@link JobExecution JobExecutions} with the
+ * given job name.
+ * @param jobName the name of the job to search under
+ * @return the id values of the running {@link JobExecution} instances
+ * @throws NoSuchJobException if there are no {@link JobExecution JobExecutions} with
+ * that job name
+ * @deprecated Since 6.0 in favor of
+ * {@link org.springframework.batch.core.repository.JobRepository#findRunningJobExecutions(String)}.
+ * Scheduled for removal in 6.2 or later.
+ */
+ @Deprecated(since = "6.0", forRemoval = true)
+ Set getRunningExecutions(String jobName) throws NoSuchJobException;
+
+ /**
+ * Get the {@link JobParameters} as a human readable String (new line separated
+ * key=value pairs).
+ * @param executionId the id of an existing {@link JobExecution}
+ * @return the job parameters that were used to launch the associated instance
+ * @throws NoSuchJobExecutionException if the id was not associated with any
+ * {@link JobExecution}
+ * @deprecated Since 6.0 in favor of the getJobParameters()
method of
+ * {@link org.springframework.batch.core.repository.JobRepository#getJobExecution(Long)}.
+ * Scheduled for removal in 6.2 or later.
+ */
+ @Deprecated(since = "6.0", forRemoval = true)
+ String getParameters(long executionId) throws NoSuchJobExecutionException;
+
/**
* Summarise the {@link JobExecution} with the supplied id, giving details of status,
* start and end times etc.
@@ -186,7 +342,11 @@ Long startNextInstance(String jobName) throws NoSuchJobException, JobParametersN
* @return a String summarising the state of the job execution
* @throws NoSuchJobExecutionException if there is no {@link JobExecution} with the
* supplied id
+ * @deprecated Since 6.0 in favor of the toString()
method of
+ * {@link org.springframework.batch.core.repository.JobRepository#getJobExecution(Long)}.
+ * Scheduled for removal in 6.2 or later.
*/
+ @Deprecated(since = "6.0", forRemoval = true)
String getSummary(long executionId) throws NoSuchJobExecutionException;
/**
@@ -196,27 +356,11 @@ Long startNextInstance(String jobName) throws NoSuchJobException, JobParametersN
* @return a map of step execution id to String summarising the state of the execution
* @throws NoSuchJobExecutionException if there is no {@link JobExecution} with the
* supplied id
+ * @deprecated Since 6.0 in favor of the getStepExecutions()
method of
+ * {@link org.springframework.batch.core.repository.JobRepository#getJobExecution(Long)}.
+ * Scheduled for removal in 6.2 or later.
*/
+ @Deprecated(since = "6.0", forRemoval = true)
Map getStepExecutionSummaries(long executionId) throws NoSuchJobExecutionException;
- /**
- * List the available job names that can be launched with
- * {@link #start(String, Properties)}.
- * @return a set of job names
- */
- Set getJobNames();
-
- /**
- * Mark the {@link JobExecution} as ABANDONED. If a stop signal is ignored because the
- * process died this is the best way to mark a job as finished with (as opposed to
- * STOPPED). An abandoned job execution cannot be restarted by the framework.
- * @param jobExecutionId the job execution id to abort
- * @return the {@link JobExecution} that was aborted
- * @throws NoSuchJobExecutionException thrown if there is no job execution for the
- * jobExecutionId.
- * @throws JobExecutionAlreadyRunningException if the job is running (it should be
- * stopped first)
- */
- JobExecution abandon(long jobExecutionId) throws NoSuchJobExecutionException, JobExecutionAlreadyRunningException;
-
}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/JobParametersNotFoundException.java b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/JobParametersNotFoundException.java
index 8ff1f36633..cc2db0986c 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/JobParametersNotFoundException.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/JobParametersNotFoundException.java
@@ -15,8 +15,8 @@
*/
package org.springframework.batch.core.launch;
-import org.springframework.batch.core.JobExecutionException;
-import org.springframework.batch.core.JobParametersIncrementer;
+import org.springframework.batch.core.job.JobExecutionException;
+import org.springframework.batch.core.job.parameters.JobParametersIncrementer;
/**
* Checked exception to indicate that a required {@link JobParametersIncrementer} is not
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/NoSuchJobException.java b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/NoSuchJobException.java
index 8131ecfa6a..d7d053a660 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/NoSuchJobException.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/NoSuchJobException.java
@@ -15,8 +15,8 @@
*/
package org.springframework.batch.core.launch;
-import org.springframework.batch.core.Job;
-import org.springframework.batch.core.JobExecutionException;
+import org.springframework.batch.core.job.Job;
+import org.springframework.batch.core.job.JobExecutionException;
/**
* Checked exception to indicate that a required {@link Job} is not available.
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/NoSuchJobExecutionException.java b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/NoSuchJobExecutionException.java
index 7135d5cfe9..13f17c7bbf 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/NoSuchJobExecutionException.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/NoSuchJobExecutionException.java
@@ -15,8 +15,8 @@
*/
package org.springframework.batch.core.launch;
-import org.springframework.batch.core.JobExecution;
-import org.springframework.batch.core.JobExecutionException;
+import org.springframework.batch.core.job.JobExecution;
+import org.springframework.batch.core.job.JobExecutionException;
/**
* Checked exception to indicate that a required {@link JobExecution} is not available.
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/NoSuchJobInstanceException.java b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/NoSuchJobInstanceException.java
index 1f6a48c9fc..29d4a4ba07 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/NoSuchJobInstanceException.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/NoSuchJobInstanceException.java
@@ -15,8 +15,8 @@
*/
package org.springframework.batch.core.launch;
-import org.springframework.batch.core.JobExecutionException;
-import org.springframework.batch.core.JobInstance;
+import org.springframework.batch.core.job.JobExecutionException;
+import org.springframework.batch.core.job.JobInstance;
/**
* Exception that signals that the user requested an operation on a non-existent
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/CommandLineJobOperator.java b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/CommandLineJobOperator.java
new file mode 100644
index 0000000000..0ef3b38733
--- /dev/null
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/CommandLineJobOperator.java
@@ -0,0 +1,354 @@
+/*
+ * Copyright 2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.batch.core.launch.support;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Properties;
+
+import org.springframework.batch.core.configuration.JobRegistry;
+import org.springframework.batch.core.converter.DefaultJobParametersConverter;
+import org.springframework.batch.core.converter.JobParametersConverter;
+import org.springframework.batch.core.job.Job;
+import org.springframework.batch.core.job.JobExecution;
+import org.springframework.batch.core.job.parameters.JobParameters;
+import org.springframework.batch.core.launch.JobOperator;
+import org.springframework.batch.core.repository.JobRepository;
+import org.springframework.beans.BeansException;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+import org.springframework.core.log.LogAccessor;
+
+import static org.springframework.batch.core.launch.support.ExitCodeMapper.JVM_EXITCODE_COMPLETED;
+import static org.springframework.batch.core.launch.support.ExitCodeMapper.JVM_EXITCODE_GENERIC_ERROR;
+
+/**
+ * A command-line utility to operate Spring Batch jobs using the {@link JobOperator}. It
+ * allows starting, stopping, restarting, abandoning and recovering jobs from the command
+ * line.
+ *
+ * This utility requires a Spring application context to be set up with the necessary
+ * batch infrastructure, including a {@link JobOperator}, a {@link JobRepository}, and a
+ * {@link JobRegistry} populated with the jobs to operate. It can also be configured with
+ * a custom {@link ExitCodeMapper} and a {@link JobParametersConverter}.
+ *
+ *
+ * This class is designed to be run from the command line, and the Javadoc of the
+ * {@link #main(String[])} method explains the various operations and exit codes.
+ *
+ * @author Mahmoud Ben Hassine
+ * @author Yejeong Ham
+ * @since 6.0
+ */
+public class CommandLineJobOperator {
+
+ private static final LogAccessor logger = new LogAccessor(CommandLineJobOperator.class);
+
+ private final JobOperator jobOperator;
+
+ private final JobRepository jobRepository;
+
+ private final JobRegistry jobRegistry;
+
+ private ExitCodeMapper exitCodeMapper = new SimpleJvmExitCodeMapper();
+
+ private JobParametersConverter jobParametersConverter = new DefaultJobParametersConverter();
+
+ /**
+ * Create a new {@link CommandLineJobOperator} instance.
+ * @param jobOperator the {@link JobOperator} to use for job operations
+ * @param jobRepository the {@link JobRepository} to use for job meta-data management
+ * @param jobRegistry the {@link JobRegistry} to use for job lookup by name
+ */
+ public CommandLineJobOperator(JobOperator jobOperator, JobRepository jobRepository, JobRegistry jobRegistry) {
+ this.jobOperator = jobOperator;
+ this.jobRepository = jobRepository;
+ this.jobRegistry = jobRegistry;
+ }
+
+ /**
+ * Set the {@link JobParametersConverter} to use for converting command line
+ * parameters to {@link JobParameters}. Defaults to a
+ * {@link DefaultJobParametersConverter}.
+ * @param jobParametersConverter the job parameters converter to set
+ */
+ public void setJobParametersConverter(JobParametersConverter jobParametersConverter) {
+ this.jobParametersConverter = jobParametersConverter;
+ }
+
+ /**
+ * Set the {@link ExitCodeMapper} to use for converting job exit codes to JVM exit
+ * codes. Defaults to a {@link SimpleJvmExitCodeMapper}.
+ * @param exitCodeMapper the exit code mapper to set
+ */
+ public void setExitCodeMapper(ExitCodeMapper exitCodeMapper) {
+ this.exitCodeMapper = exitCodeMapper;
+ }
+
+ /**
+ * Start a job with the given name and parameters.
+ * @param jobName the name of the job to start
+ * @param parameters the parameters for the job
+ * @return the exit code of the job execution, or JVM_EXITCODE_GENERIC_ERROR if an
+ * error occurs
+ */
+ public int start(String jobName, Properties parameters) {
+ logger.info(() -> "Starting job with name '" + jobName + "' and parameters: " + parameters);
+ try {
+ Job job = this.jobRegistry.getJob(jobName);
+ JobParameters jobParameters = this.jobParametersConverter.getJobParameters(parameters);
+ JobExecution jobExecution = this.jobOperator.start(job, jobParameters);
+ return this.exitCodeMapper.intValue(jobExecution.getExitStatus().getExitCode());
+ }
+ catch (Exception e) {
+ return JVM_EXITCODE_GENERIC_ERROR;
+ }
+ }
+
+ /**
+ * Start the next instance of the job with the given name.
+ * @param jobName the name of the job to start
+ * @return the exit code of the job execution, or JVM_EXITCODE_GENERIC_ERROR if an
+ * error occurs
+ */
+ public int startNextInstance(String jobName) {
+ logger.info(() -> "Starting next instance of job '" + jobName + "'");
+ try {
+ Job job = this.jobRegistry.getJob(jobName);
+ JobExecution jobExecution = this.jobOperator.startNextInstance(job);
+ return this.exitCodeMapper.intValue(jobExecution.getExitStatus().getExitCode());
+ }
+ catch (Exception e) {
+ return JVM_EXITCODE_GENERIC_ERROR;
+ }
+ }
+
+ /**
+ * Send a stop signal to the job execution with given ID. The signal is successfully
+ * sent if this method returns JVM_EXITCODE_COMPLETED, but that doesn't mean that the
+ * job has stopped. The only way to be sure of that is to poll the job execution
+ * status.
+ * @param jobExecutionId the ID of the job execution to stop
+ * @return JVM_EXITCODE_COMPLETED if the stop signal was successfully sent to the job
+ * execution, JVM_EXITCODE_GENERIC_ERROR otherwise
+ * @see JobOperator#stop(JobExecution)
+ */
+ public int stop(long jobExecutionId) {
+ logger.info(() -> "Stopping job execution with ID: " + jobExecutionId);
+ try {
+ JobExecution jobExecution = this.jobRepository.getJobExecution(jobExecutionId);
+ if (jobExecution == null) {
+ logger.error(() -> "No job execution found with ID: " + jobExecutionId);
+ return JVM_EXITCODE_GENERIC_ERROR;
+ }
+ boolean stopSignalSent = this.jobOperator.stop(jobExecution);
+ return stopSignalSent ? JVM_EXITCODE_COMPLETED : JVM_EXITCODE_GENERIC_ERROR;
+ }
+ catch (Exception e) {
+ return JVM_EXITCODE_GENERIC_ERROR;
+ }
+ }
+
+ /**
+ * Restart the job execution with the given ID.
+ * @param jobExecutionId the ID of the job execution to restart
+ * @return the exit code of the restarted job execution, or JVM_EXITCODE_GENERIC_ERROR
+ * if an error occurs
+ */
+ public int restart(long jobExecutionId) {
+ logger.info(() -> "Restarting job execution with ID: " + jobExecutionId);
+ try {
+ JobExecution jobExecution = this.jobRepository.getJobExecution(jobExecutionId);
+ if (jobExecution == null) {
+ logger.error(() -> "No job execution found with ID: " + jobExecutionId);
+ return JVM_EXITCODE_GENERIC_ERROR;
+ }
+ JobExecution restartedExecution = this.jobOperator.restart(jobExecution);
+ return this.exitCodeMapper.intValue(restartedExecution.getExitStatus().getExitCode());
+ }
+ catch (Exception e) {
+ return JVM_EXITCODE_GENERIC_ERROR;
+ }
+ }
+
+ /**
+ * Abandon the job execution with the given ID.
+ * @param jobExecutionId the ID of the job execution to abandon
+ * @return the exit code of the abandoned job execution, or JVM_EXITCODE_GENERIC_ERROR
+ * if an error occurs
+ */
+ public int abandon(long jobExecutionId) {
+ logger.info(() -> "Abandoning job execution with ID: " + jobExecutionId);
+ try {
+ JobExecution jobExecution = this.jobRepository.getJobExecution(jobExecutionId);
+ if (jobExecution == null) {
+ logger.error(() -> "No job execution found with ID: " + jobExecutionId);
+ return JVM_EXITCODE_GENERIC_ERROR;
+ }
+ JobExecution abandonedExecution = this.jobOperator.abandon(jobExecution);
+ return this.exitCodeMapper.intValue(abandonedExecution.getExitStatus().getExitCode());
+ }
+ catch (Exception e) {
+ return JVM_EXITCODE_GENERIC_ERROR;
+ }
+ }
+
+ /**
+ * Recover the job execution with the given ID that is stuck in a {@code STARTED}
+ * state due to an abrupt shutdown or failure, making it eligible for restart.
+ * @param jobExecutionId the ID of the job execution to recover
+ * @return the exit code of the recovered job execution, or JVM_EXITCODE_GENERIC_ERROR
+ * if an error occurs
+ */
+ public int recover(long jobExecutionId) {
+ logger.info(() -> "Recovering job execution with ID: " + jobExecutionId);
+ try {
+ JobExecution jobExecution = this.jobRepository.getJobExecution(jobExecutionId);
+ if (jobExecution == null) {
+ logger.error(() -> "No job execution found with ID: " + jobExecutionId);
+ return JVM_EXITCODE_GENERIC_ERROR;
+ }
+ JobExecution recoveredExecution = this.jobOperator.recover(jobExecution);
+ return this.exitCodeMapper.intValue(recoveredExecution.getExitStatus().getExitCode());
+ }
+ catch (Exception e) {
+ return JVM_EXITCODE_GENERIC_ERROR;
+ }
+ }
+
+ // @formatter:off
+ /**
+ * Main method to operate jobs from the command line.
+ *
+ * Usage:
+ *
+ * java org.springframework.batch.core.launch.support.CommandLineJobOperator \
+ * fully.qualified.name.of.JobConfigurationClass \
+ * operation \
+ * parameters
+ *
+ *
+ * where operation
is one of the following:
+ *
+ * - start jobName
[jobParameters]
+ * - startNextInstance jobName
+ * - restart jobExecutionId
+ * - stop jobExecutionId
+ * - abandon jobExecutionId
+ * - recover jobExecutionId
+ *
+ *
+ * and jobParameters
are key-value pairs in the form name=value,type,identifying.
+ *
+ * Exit status:
+ *
+ * - 0: Job completed successfully
+ * - 1: Job failed to (re)start or an error occurred
+ * - 2: Job configuration class not found
+ *
+ */
+ // @formatter:on
+ public static void main(String[] args) {
+ if (args.length < 3) {
+ String usage = """
+ Usage: java %s
+ where operation is one of the following:
+ - start jobName [jobParameters]
+ - startNextInstance jobName
+ - restart jobExecutionId
+ - stop jobExecutionId
+ - abandon jobExecutionId
+ - recover jobExecutionId
+ and jobParameters are key-value pairs in the form name=value,type,identifying.
+ """;
+ System.err.printf(String.format(usage, CommandLineJobOperator.class.getName()));
+ System.exit(1);
+ }
+
+ String jobConfigurationClassName = args[0];
+ String operation = args[1];
+
+ ConfigurableApplicationContext context = null;
+ try {
+ Class> jobConfigurationClass = Class.forName(jobConfigurationClassName);
+ context = new AnnotationConfigApplicationContext(jobConfigurationClass);
+ }
+ catch (ClassNotFoundException classNotFoundException) {
+ System.err.println("Job configuration class not found: " + jobConfigurationClassName);
+ System.exit(2);
+ }
+
+ JobOperator jobOperator = null;
+ JobRepository jobRepository = null;
+ JobRegistry jobRegistry = null;
+ try {
+ jobOperator = context.getBean(JobOperator.class);
+ jobRepository = context.getBean(JobRepository.class);
+ jobRegistry = context.getBean(JobRegistry.class);
+ }
+ catch (BeansException e) {
+ System.err.println("A required bean was not found in the application context: " + e.getMessage());
+ System.exit(1);
+ }
+ CommandLineJobOperator operator = new CommandLineJobOperator(jobOperator, jobRepository, jobRegistry);
+
+ int exitCode;
+ String jobName;
+ long jobExecutionId;
+ switch (operation) {
+ case "start":
+ jobName = args[2];
+ List jobParameters = Arrays.asList(args).subList(3, args.length);
+ exitCode = operator.start(jobName, parse(jobParameters));
+ break;
+ case "startNextInstance":
+ jobName = args[2];
+ exitCode = operator.startNextInstance(jobName);
+ break;
+ case "stop":
+ jobExecutionId = Long.parseLong(args[2]);
+ exitCode = operator.stop(jobExecutionId);
+ break;
+ case "restart":
+ jobExecutionId = Long.parseLong(args[2]);
+ exitCode = operator.restart(jobExecutionId);
+ break;
+ case "abandon":
+ jobExecutionId = Long.parseLong(args[2]);
+ exitCode = operator.abandon(jobExecutionId);
+ break;
+ case "recover":
+ jobExecutionId = Long.parseLong(args[2]);
+ exitCode = operator.recover(jobExecutionId);
+ break;
+ default:
+ System.err.println("Unknown operation: " + operation);
+ exitCode = JVM_EXITCODE_GENERIC_ERROR;
+ }
+
+ System.exit(exitCode);
+ }
+
+ private static Properties parse(List jobParameters) {
+ Properties properties = new Properties();
+ for (String jobParameter : jobParameters) {
+ String[] tokens = jobParameter.split("=");
+ properties.put(tokens[0], tokens[1]);
+ }
+ return properties;
+ }
+
+}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/CommandLineJobRunner.java b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/CommandLineJobRunner.java
index 34bdf928b0..d4071ba3cb 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/CommandLineJobRunner.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/CommandLineJobRunner.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006-2023 the original author or authors.
+ * Copyright 2006-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -31,21 +31,17 @@
import org.springframework.batch.core.BatchStatus;
import org.springframework.batch.core.ExitStatus;
-import org.springframework.batch.core.Job;
-import org.springframework.batch.core.JobExecution;
-import org.springframework.batch.core.JobInstance;
-import org.springframework.batch.core.JobParameters;
-import org.springframework.batch.core.JobParametersBuilder;
-import org.springframework.batch.core.JobParametersIncrementer;
+import org.springframework.batch.core.job.Job;
+import org.springframework.batch.core.job.JobExecution;
+import org.springframework.batch.core.job.JobInstance;
+import org.springframework.batch.core.job.parameters.JobParameters;
+import org.springframework.batch.core.job.parameters.JobParametersIncrementer;
import org.springframework.batch.core.configuration.JobLocator;
+import org.springframework.batch.core.configuration.JobRegistry;
import org.springframework.batch.core.converter.DefaultJobParametersConverter;
import org.springframework.batch.core.converter.JobParametersConverter;
-import org.springframework.batch.core.explore.JobExplorer;
-import org.springframework.batch.core.launch.JobExecutionNotFailedException;
-import org.springframework.batch.core.launch.JobExecutionNotRunningException;
-import org.springframework.batch.core.launch.JobExecutionNotStoppedException;
-import org.springframework.batch.core.launch.JobLauncher;
-import org.springframework.batch.core.launch.NoSuchJobException;
+import org.springframework.batch.core.launch.*;
+import org.springframework.batch.core.repository.explore.JobExplorer;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
@@ -74,7 +70,7 @@
* can be used to load the job and its context from a single location. All dependencies of
* the launcher will then be satisfied by autowiring by type from the combined application
* context. Default values are provided for all fields except the {@link JobLauncher} and
- * {@link JobLocator} . Therefore, if autowiring fails to set it (it should be noted that
+ * {@link JobRegistry} . Therefore, if autowiring fails to set it (it should be noted that
* dependency checking is disabled because most of the fields have default values and thus
* don't require dependencies to be fulfilled via autowiring) then an exception will be
* thrown. It should also be noted that even if an exception is thrown by this class, it
@@ -163,8 +159,8 @@
* {@link BeanDefinitionStoreException} will be thrown. The same exception will also be
* thrown if there is more than one present. Assuming the JobLauncher has been set
* correctly, the jobIdentifier argument will be used to obtain an actual {@link Job}. If
- * a {@link JobLocator} has been set, then it will be used, if not the beanFactory will be
- * asked, using the jobIdentifier as the bean id.
+ * a {@link JobRegistry} has been set, then it will be used, if not the beanFactory will
+ * be asked, using the jobIdentifier as the bean id.
*
*
* @author Dave Syer
@@ -172,7 +168,10 @@
* @author Mahmoud Ben Hassine
* @author Minsoo Kim
* @since 1.0
+ * @deprecated since 6.0 in favor of {@link CommandLineJobOperator}. Scheduled for removal
+ * in 6.2 or later.
*/
+@Deprecated(since = "6.0", forRemoval = true)
public class CommandLineJobRunner {
protected static final Log logger = LogFactory.getLog(CommandLineJobRunner.class);
@@ -183,6 +182,8 @@ public class CommandLineJobRunner {
private JobLocator jobLocator;
+ private JobRegistry jobRegistry;
+
private static SystemExiter systemExiter = new JvmSystemExiter();
private static String message = "";
@@ -274,11 +275,22 @@ public void exit(int status) {
/**
* {@link JobLocator} to find a job to run.
* @param jobLocator a {@link JobLocator}
+ * @deprecated since 6.0 in favor of {{@link #setJobRegistry(JobRegistry)}}. Scheduled
+ * for removal in 6.2 or later.
*/
+ @Deprecated(since = "6.0", forRemoval = true)
public void setJobLocator(JobLocator jobLocator) {
this.jobLocator = jobLocator;
}
+ /**
+ * Set the {@link JobRegistry}.
+ * @param jobRegistry a {@link JobRegistry}
+ */
+ public void setJobRegistry(JobRegistry jobRegistry) {
+ this.jobRegistry = jobRegistry;
+ }
+
/*
* Start a job by obtaining a combined classpath using the job launcher and job paths.
* If a JobLocator has been set, then use it to obtain an actual job, if not ask the
@@ -348,20 +360,35 @@ int start(String jobPath, String jobIdentifier, String[] parameters, Set
}
Job job = null;
- if (jobLocator != null) {
+ if (jobRegistry != null) {
try {
- job = jobLocator.getJob(jobName);
+ job = jobRegistry.getJob(jobName);
}
- catch (NoSuchJobException e) {
+ catch (NoSuchJobException ignored) {
}
}
if (job == null) {
- job = (Job) context.getBean(jobName);
+ job = context.getBean(jobName, Job.class);
}
if (opts.contains("-next")) {
- jobParameters = new JobParametersBuilder(jobParameters, jobExplorer).getNextJobParameters(job)
- .toJobParameters();
+ JobInstance lastInstance = jobRepository.getLastJobInstance(jobName);
+ JobParametersIncrementer incrementer = job.getJobParametersIncrementer();
+ if (lastInstance == null) {
+ // Start from a completely clean sheet
+ jobParameters = incrementer.getNext(new JobParameters());
+ }
+ else {
+ JobExecution previousExecution = jobRepository.getLastJobExecution(lastInstance);
+ if (previousExecution == null) {
+ // Normally this will not happen - an instance exists with no
+ // executions
+ jobParameters = incrementer.getNext(new JobParameters());
+ }
+ else {
+ jobParameters = incrementer.getNext(previousExecution.getJobParameters());
+ }
+ }
}
JobExecution jobExecution = launcher.run(job, jobParameters);
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/DataFieldMaxValueJobParametersIncrementer.java b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/DataFieldMaxValueJobParametersIncrementer.java
index 5cce9c53f9..759aa4400e 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/DataFieldMaxValueJobParametersIncrementer.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/DataFieldMaxValueJobParametersIncrementer.java
@@ -15,9 +15,9 @@
*/
package org.springframework.batch.core.launch.support;
-import org.springframework.batch.core.JobParameters;
-import org.springframework.batch.core.JobParametersBuilder;
-import org.springframework.batch.core.JobParametersIncrementer;
+import org.springframework.batch.core.job.parameters.JobParameters;
+import org.springframework.batch.core.job.parameters.JobParametersBuilder;
+import org.springframework.batch.core.job.parameters.JobParametersIncrementer;
import org.springframework.jdbc.support.incrementer.DataFieldMaxValueIncrementer;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/JobOperatorFactoryBean.java b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/JobOperatorFactoryBean.java
index a3c07375ec..c30a35eaca 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/JobOperatorFactoryBean.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/JobOperatorFactoryBean.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2022-2024 the original author or authors.
+ * Copyright 2022-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,23 +15,34 @@
*/
package org.springframework.batch.core.launch.support;
-import java.util.Properties;
+import java.lang.reflect.Method;
+import io.micrometer.observation.ObservationRegistry;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
import org.springframework.aop.framework.ProxyFactory;
+import org.springframework.batch.core.configuration.BatchConfigurationException;
+import org.springframework.batch.core.configuration.DuplicateJobException;
import org.springframework.batch.core.configuration.JobRegistry;
+import org.springframework.batch.core.configuration.support.MapJobRegistry;
import org.springframework.batch.core.converter.DefaultJobParametersConverter;
import org.springframework.batch.core.converter.JobParametersConverter;
-import org.springframework.batch.core.explore.JobExplorer;
-import org.springframework.batch.core.launch.JobLauncher;
+import org.springframework.batch.core.job.Job;
+import org.springframework.batch.core.job.JobExecution;
import org.springframework.batch.core.launch.JobOperator;
import org.springframework.batch.core.repository.JobRepository;
+import org.springframework.batch.support.transaction.ResourcelessTransactionManager;
+import org.springframework.beans.BeansException;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.core.task.SyncTaskExecutor;
+import org.springframework.core.task.TaskExecutor;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionManager;
-import org.springframework.transaction.annotation.Isolation;
-import org.springframework.transaction.annotation.Propagation;
-import org.springframework.transaction.interceptor.NameMatchTransactionAttributeSource;
+import org.springframework.transaction.interceptor.DefaultTransactionAttribute;
+import org.springframework.transaction.interceptor.MethodMapTransactionAttributeSource;
import org.springframework.transaction.interceptor.TransactionAttributeSource;
import org.springframework.transaction.interceptor.TransactionInterceptor;
import org.springframework.util.Assert;
@@ -41,15 +52,15 @@
* {@link JobOperator}.
*
* @see JobOperator
- * @see SimpleJobOperator
+ * @see TaskExecutorJobOperator
* @author Mahmoud Ben Hassine
* @since 5.0
*/
-public class JobOperatorFactoryBean implements FactoryBean, InitializingBean {
+public class JobOperatorFactoryBean implements FactoryBean, ApplicationContextAware, InitializingBean {
- private static final String TRANSACTION_ISOLATION_LEVEL_PREFIX = "ISOLATION_";
+ protected static final Log logger = LogFactory.getLog(JobOperatorFactoryBean.class);
- private static final String TRANSACTION_PROPAGATION_PREFIX = "PROPAGATION_";
+ private ApplicationContext applicationContext;
private PlatformTransactionManager transactionManager;
@@ -57,33 +68,54 @@ public class JobOperatorFactoryBean implements FactoryBean, Initial
private JobRegistry jobRegistry;
- private JobLauncher jobLauncher;
-
private JobRepository jobRepository;
- private JobExplorer jobExplorer;
-
private JobParametersConverter jobParametersConverter = new DefaultJobParametersConverter();
+ private TaskExecutor taskExecutor;
+
+ private ObservationRegistry observationRegistry;
+
private final ProxyFactory proxyFactory = new ProxyFactory();
+ @Override
+ public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
+ this.applicationContext = applicationContext;
+ }
+
@Override
public void afterPropertiesSet() throws Exception {
- Assert.notNull(this.transactionManager, "TransactionManager must not be null");
- Assert.notNull(this.jobLauncher, "JobLauncher must not be null");
- Assert.notNull(this.jobRegistry, "JobRegistry must not be null");
- Assert.notNull(this.jobExplorer, "JobExplorer must not be null");
Assert.notNull(this.jobRepository, "JobRepository must not be null");
+ if (this.jobRegistry == null) {
+ this.jobRegistry = new MapJobRegistry();
+ populateJobRegistry();
+ logger.info(
+ "No JobRegistry has been set, defaulting to a MapJobRegistry populated with jobs defined in the application context.");
+ }
+ if (this.transactionManager == null) {
+ this.transactionManager = new ResourcelessTransactionManager();
+ logger.info("No transaction manager has been set, defaulting to ResourcelessTransactionManager.");
+ }
+ if (this.taskExecutor == null) {
+ logger.info("No TaskExecutor has been set, defaulting to synchronous executor.");
+ this.taskExecutor = new SyncTaskExecutor();
+ }
if (this.transactionAttributeSource == null) {
- Properties transactionAttributes = new Properties();
- String transactionProperties = String.join(",", TRANSACTION_PROPAGATION_PREFIX + Propagation.REQUIRED,
- TRANSACTION_ISOLATION_LEVEL_PREFIX + Isolation.DEFAULT);
- transactionAttributes.setProperty("stop*", transactionProperties);
- this.transactionAttributeSource = new NameMatchTransactionAttributeSource();
- ((NameMatchTransactionAttributeSource) transactionAttributeSource).setProperties(transactionAttributes);
+ this.transactionAttributeSource = new DefaultJobOperatorTransactionAttributeSource();
}
}
+ private void populateJobRegistry() {
+ this.applicationContext.getBeansOfType(Job.class).values().forEach(job -> {
+ try {
+ jobRegistry.register(job);
+ }
+ catch (DuplicateJobException e) {
+ throw new BatchConfigurationException(e);
+ }
+ });
+ }
+
/**
* Setter for the job registry.
* @param jobRegistry the job registry to set
@@ -92,14 +124,6 @@ public void setJobRegistry(JobRegistry jobRegistry) {
this.jobRegistry = jobRegistry;
}
- /**
- * Setter for the job launcher.
- * @param jobLauncher the job launcher to set
- */
- public void setJobLauncher(JobLauncher jobLauncher) {
- this.jobLauncher = jobLauncher;
- }
-
/**
* Setter for the job repository.
* @param jobRepository the job repository to set
@@ -108,22 +132,35 @@ public void setJobRepository(JobRepository jobRepository) {
this.jobRepository = jobRepository;
}
- /**
- * Setter for the job explorer.
- * @param jobExplorer the job explorer to set
- */
- public void setJobExplorer(JobExplorer jobExplorer) {
- this.jobExplorer = jobExplorer;
- }
-
/**
* Setter for the job parameters converter.
* @param jobParametersConverter the job parameters converter to set
+ * @deprecated since 6.0 with nor replacement. Scheduled for removal in 6.2 or later.
*/
+ @Deprecated(since = "6.0", forRemoval = true)
public void setJobParametersConverter(JobParametersConverter jobParametersConverter) {
this.jobParametersConverter = jobParametersConverter;
}
+ /**
+ * Set the TaskExecutor. (Optional)
+ * @param taskExecutor instance of {@link TaskExecutor}.
+ * @since 6.0
+ */
+ public void setTaskExecutor(TaskExecutor taskExecutor) {
+ this.taskExecutor = taskExecutor;
+ }
+
+ /**
+ * Set the observation registry to use for metrics. Defaults to
+ * {@link ObservationRegistry#NOOP}.
+ * @param observationRegistry the observation registry to use
+ * @since 6.0
+ */
+ public void setObservationRegistry(ObservationRegistry observationRegistry) {
+ this.observationRegistry = observationRegistry;
+ }
+
/**
* Setter for the transaction manager.
* @param transactionManager the transaction manager to set
@@ -163,15 +200,38 @@ public JobOperator getObject() throws Exception {
return (JobOperator) this.proxyFactory.getProxy(getClass().getClassLoader());
}
- private SimpleJobOperator getTarget() throws Exception {
- SimpleJobOperator simpleJobOperator = new SimpleJobOperator();
- simpleJobOperator.setJobRegistry(this.jobRegistry);
- simpleJobOperator.setJobExplorer(this.jobExplorer);
- simpleJobOperator.setJobRepository(this.jobRepository);
- simpleJobOperator.setJobLauncher(this.jobLauncher);
- simpleJobOperator.setJobParametersConverter(this.jobParametersConverter);
- simpleJobOperator.afterPropertiesSet();
- return simpleJobOperator;
+ @SuppressWarnings("removal")
+ private TaskExecutorJobOperator getTarget() throws Exception {
+ TaskExecutorJobOperator taskExecutorJobOperator = new TaskExecutorJobOperator();
+ taskExecutorJobOperator.setJobRegistry(this.jobRegistry);
+ taskExecutorJobOperator.setJobRepository(this.jobRepository);
+ taskExecutorJobOperator.setTaskExecutor(this.taskExecutor);
+ if (this.observationRegistry != null) {
+ taskExecutorJobOperator.setObservationRegistry(this.observationRegistry);
+ }
+ taskExecutorJobOperator.setJobParametersConverter(this.jobParametersConverter);
+ taskExecutorJobOperator.afterPropertiesSet();
+ return taskExecutorJobOperator;
+ }
+
+ private static class DefaultJobOperatorTransactionAttributeSource extends MethodMapTransactionAttributeSource {
+
+ public DefaultJobOperatorTransactionAttributeSource() {
+ DefaultTransactionAttribute transactionAttribute = new DefaultTransactionAttribute();
+ try {
+ Method stopMethod = TaskExecutorJobOperator.class.getMethod("stop", JobExecution.class);
+ Method abandonMethod = TaskExecutorJobOperator.class.getMethod("abandon", JobExecution.class);
+ Method recoverMethod = TaskExecutorJobOperator.class.getMethod("recover", JobExecution.class);
+ addTransactionalMethod(stopMethod, transactionAttribute);
+ addTransactionalMethod(abandonMethod, transactionAttribute);
+ addTransactionalMethod(recoverMethod, transactionAttribute);
+ }
+ catch (NoSuchMethodException e) {
+ throw new IllegalStateException("Failed to initialize default transaction attributes for JobOperator",
+ e);
+ }
+ }
+
}
}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/JvmSystemExiter.java b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/JvmSystemExiter.java
index b0d9e855f2..7834bfab69 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/JvmSystemExiter.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/JvmSystemExiter.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006-2013 the original author or authors.
+ * Copyright 2006-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,8 +23,9 @@
*
* @author Lucas Ward
* @author Dave Syer
- *
+ * @deprecated since 6.0 with no replacement. Scheduled for removal in 6.2 or later.
*/
+@Deprecated(since = "6.0", forRemoval = true)
public class JvmSystemExiter implements SystemExiter {
/**
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/RunIdIncrementer.java b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/RunIdIncrementer.java
index 824aa10363..ee2fac0417 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/RunIdIncrementer.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/RunIdIncrementer.java
@@ -15,10 +15,10 @@
*/
package org.springframework.batch.core.launch.support;
-import org.springframework.batch.core.JobParameter;
-import org.springframework.batch.core.JobParameters;
-import org.springframework.batch.core.JobParametersBuilder;
-import org.springframework.batch.core.JobParametersIncrementer;
+import org.springframework.batch.core.job.parameters.JobParameter;
+import org.springframework.batch.core.job.parameters.JobParameters;
+import org.springframework.batch.core.job.parameters.JobParametersBuilder;
+import org.springframework.batch.core.job.parameters.JobParametersIncrementer;
import org.springframework.lang.Nullable;
/**
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/RuntimeExceptionTranslator.java b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/RuntimeExceptionTranslator.java
index cc0e3536d4..4a1bf31bf7 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/RuntimeExceptionTranslator.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/RuntimeExceptionTranslator.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006-2023 the original author or authors.
+ * Copyright 2006-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,8 +20,10 @@
/**
* @author Dave Syer
- *
+ * @author Mahmoud Ben Hassine
+ * @deprecated since 6.0 with no replacement, for removal in 6.2 or later.
*/
+@Deprecated(since = "6.0", forRemoval = true)
public class RuntimeExceptionTranslator implements MethodInterceptor {
@Override
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/SimpleJobOperator.java b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/SimpleJobOperator.java
index 059e769960..db0c1a351a 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/SimpleJobOperator.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/SimpleJobOperator.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006-2024 the original author or authors.
+ * Copyright 2006-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -29,23 +29,21 @@
import org.apache.commons.logging.LogFactory;
import org.springframework.batch.core.BatchStatus;
-import org.springframework.batch.core.Job;
-import org.springframework.batch.core.JobExecution;
-import org.springframework.batch.core.JobInstance;
-import org.springframework.batch.core.JobParameters;
-import org.springframework.batch.core.JobParametersBuilder;
-import org.springframework.batch.core.JobParametersInvalidException;
-import org.springframework.batch.core.Step;
-import org.springframework.batch.core.StepExecution;
-import org.springframework.batch.core.UnexpectedJobExecutionException;
+import org.springframework.batch.core.job.Job;
+import org.springframework.batch.core.job.JobExecution;
+import org.springframework.batch.core.job.JobInstance;
+import org.springframework.batch.core.job.parameters.JobParameters;
+import org.springframework.batch.core.job.parameters.JobParametersIncrementer;
+import org.springframework.batch.core.job.parameters.JobParametersInvalidException;
+import org.springframework.batch.core.step.Step;
+import org.springframework.batch.core.step.StoppableStep;
+import org.springframework.batch.core.step.StepExecution;
+import org.springframework.batch.core.job.UnexpectedJobExecutionException;
import org.springframework.batch.core.configuration.JobRegistry;
-import org.springframework.batch.core.configuration.ListableJobLocator;
import org.springframework.batch.core.converter.DefaultJobParametersConverter;
import org.springframework.batch.core.converter.JobParametersConverter;
-import org.springframework.batch.core.explore.JobExplorer;
import org.springframework.batch.core.launch.JobExecutionNotRunningException;
import org.springframework.batch.core.launch.JobInstanceAlreadyExistsException;
-import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.launch.JobOperator;
import org.springframework.batch.core.launch.NoSuchJobException;
import org.springframework.batch.core.launch.NoSuchJobExecutionException;
@@ -66,12 +64,10 @@
import org.springframework.util.Assert;
/**
- * Simple implementation of the JobOperator interface. Due to the amount of functionality
- * the implementation is combining, the following dependencies are required:
+ * Simple implementation of the {@link JobOperator} interface. the following dependencies
+ * are required:
*
*
- * - {@link JobLauncher}
- *
- {@link JobExplorer}
*
- {@link JobRepository}
*
- {@link JobRegistry}
*
@@ -84,22 +80,23 @@
* @author Lucas Ward
* @author Will Schipp
* @author Mahmoud Ben Hassine
+ * @author Andrey Litvitski
+ * @author Yejeong Ham
+ * @author Hyunsang Han
* @since 2.0
+ * @deprecated since 6.0 in favor of {@link TaskExecutorJobOperator}. Scheduled for
+ * removal in 6.2 or later.
*/
-public class SimpleJobOperator implements JobOperator, InitializingBean {
+@SuppressWarnings("removal")
+@Deprecated(since = "6.0", forRemoval = true)
+public class SimpleJobOperator extends TaskExecutorJobLauncher implements JobOperator, InitializingBean {
private static final String ILLEGAL_STATE_MSG = "Illegal state (only happens on a race condition): "
+ "%s with name=%s and parameters=%s";
- private ListableJobLocator jobRegistry;
+ protected JobRegistry jobRegistry;
- private JobExplorer jobExplorer;
-
- private JobLauncher jobLauncher;
-
- private JobRepository jobRepository;
-
- private JobParametersConverter jobParametersConverter = new DefaultJobParametersConverter();
+ protected JobParametersConverter jobParametersConverter = new DefaultJobParametersConverter();
private final Log logger = LogFactory.getLog(getClass());
@@ -110,124 +107,102 @@ public class SimpleJobOperator implements JobOperator, InitializingBean {
*/
@Override
public void afterPropertiesSet() throws Exception {
- Assert.state(jobLauncher != null, "JobLauncher must be provided");
+ super.afterPropertiesSet();
Assert.state(jobRegistry != null, "JobLocator must be provided");
- Assert.state(jobExplorer != null, "JobExplorer must be provided");
- Assert.state(jobRepository != null, "JobRepository must be provided");
}
/**
* Public setter for the {@link JobParametersConverter}.
* @param jobParametersConverter the {@link JobParametersConverter} to set
+ * @deprecated since 6.0 with no replacement. Scheduled for removal in 6.2 or later.
*/
+ @Deprecated(since = "6.0", forRemoval = true)
public void setJobParametersConverter(JobParametersConverter jobParametersConverter) {
this.jobParametersConverter = jobParametersConverter;
}
/**
- * Public setter for the {@link ListableJobLocator}.
- * @param jobRegistry the {@link ListableJobLocator} to set
+ * Public setter for the {@link JobRegistry}.
+ * @param jobRegistry the {@link JobRegistry} to set
*/
- public void setJobRegistry(ListableJobLocator jobRegistry) {
+ public void setJobRegistry(JobRegistry jobRegistry) {
this.jobRegistry = jobRegistry;
}
- /**
- * Public setter for the {@link JobExplorer}.
- * @param jobExplorer the {@link JobExplorer} to set
- */
- public void setJobExplorer(JobExplorer jobExplorer) {
- this.jobExplorer = jobExplorer;
- }
-
- public void setJobRepository(JobRepository jobRepository) {
- this.jobRepository = jobRepository;
- }
-
- /**
- * Public setter for the {@link JobLauncher}.
- * @param jobLauncher the {@link JobLauncher} to set
- */
- public void setJobLauncher(JobLauncher jobLauncher) {
- this.jobLauncher = jobLauncher;
- }
-
@Override
- public List getExecutions(long instanceId) throws NoSuchJobInstanceException {
- JobInstance jobInstance = jobExplorer.getJobInstance(instanceId);
- if (jobInstance == null) {
- throw new NoSuchJobInstanceException(String.format("No job instance with id=%d", instanceId));
- }
- List list = new ArrayList<>();
- for (JobExecution jobExecution : jobExplorer.getJobExecutions(jobInstance)) {
- list.add(jobExecution.getId());
+ @Deprecated(since = "6.0", forRemoval = true)
+ public Long start(String jobName, Properties parameters)
+ throws NoSuchJobException, JobInstanceAlreadyExistsException, JobParametersInvalidException {
+ if (logger.isInfoEnabled()) {
+ logger.info("Checking status of job with name=" + jobName);
}
- return list;
- }
- @Override
- public Set getJobNames() {
- return new TreeSet<>(jobRegistry.getJobNames());
- }
+ JobParameters jobParameters = jobParametersConverter.getJobParameters(parameters);
- @Override
- public List getJobInstances(String jobName, int start, int count) throws NoSuchJobException {
- List list = new ArrayList<>();
- List jobInstances = jobExplorer.getJobInstances(jobName, start, count);
- for (JobInstance jobInstance : jobInstances) {
- list.add(jobInstance.getId());
- }
- if (list.isEmpty() && !jobRegistry.getJobNames().contains(jobName)) {
- throw new NoSuchJobException("No such job (either in registry or in historical data): " + jobName);
+ if (jobRepository.getJobInstance(jobName, jobParameters) != null) {
+ throw new JobInstanceAlreadyExistsException(
+ String.format("Cannot start a job instance that already exists with name=%s and parameters={%s}",
+ jobName, parameters));
}
- return list;
- }
- @Override
- @Nullable
- public JobInstance getJobInstance(String jobName, JobParameters jobParameters) {
- return this.jobExplorer.getJobInstance(jobName, jobParameters);
- }
-
- @Override
- public String getParameters(long executionId) throws NoSuchJobExecutionException {
- JobExecution jobExecution = findExecutionById(executionId);
-
- Properties properties = this.jobParametersConverter.getProperties(jobExecution.getJobParameters());
-
- return PropertiesConverter.propertiesToString(properties);
- }
-
- @Override
- public Set getRunningExecutions(String jobName) throws NoSuchJobException {
- Set set = new LinkedHashSet<>();
- for (JobExecution jobExecution : jobExplorer.findRunningJobExecutions(jobName)) {
- set.add(jobExecution.getId());
+ Job job = jobRegistry.getJob(jobName);
+ if (logger.isInfoEnabled()) {
+ logger
+ .info(String.format("Attempting to launch job with name=%s and parameters={%s}", jobName, parameters));
}
- if (set.isEmpty() && !jobRegistry.getJobNames().contains(jobName)) {
- throw new NoSuchJobException("No such job (either in registry or in historical data): " + jobName);
+ try {
+ return run(job, jobParameters).getId();
}
- return set;
- }
-
- @Override
- public Map getStepExecutionSummaries(long executionId) throws NoSuchJobExecutionException {
- JobExecution jobExecution = findExecutionById(executionId);
-
- Map map = new LinkedHashMap<>();
- for (StepExecution stepExecution : jobExecution.getStepExecutions()) {
- map.put(stepExecution.getId(), stepExecution.toString());
+ catch (JobExecutionAlreadyRunningException e) {
+ throw new UnexpectedJobExecutionException(
+ String.format(ILLEGAL_STATE_MSG, "job execution already running", jobName, parameters), e);
}
- return map;
+ catch (JobRestartException e) {
+ throw new UnexpectedJobExecutionException(
+ String.format(ILLEGAL_STATE_MSG, "job not restartable", jobName, parameters), e);
+ }
+ catch (JobInstanceAlreadyCompleteException e) {
+ throw new UnexpectedJobExecutionException(
+ String.format(ILLEGAL_STATE_MSG, "job already complete", jobName, parameters), e);
+ }
+
}
- @Override
- public String getSummary(long executionId) throws NoSuchJobExecutionException {
- JobExecution jobExecution = findExecutionById(executionId);
- return jobExecution.toString();
+ /**
+ * Start a new instance of a job with the specified parameters. If the job defines a
+ * {@link JobParametersIncrementer}, then the incrementer will be used to calculate
+ * the next parameters in the sequence and the provided parameters will be ignored.
+ * @param job the {@link Job} to start
+ * @param jobParameters the {@link JobParameters} to start the job with
+ * @return the {@link JobExecution} that was started
+ * @throws NoSuchJobException if the given {@link Job} is not registered
+ * @throws JobParametersInvalidException thrown if any of the job parameters are
+ * @throws JobExecutionAlreadyRunningException if the JobInstance identified by the
+ * properties already has an execution running. invalid.
+ * @throws JobRestartException if the execution would be a re-start, but a re-start is
+ * either not allowed or not needed.
+ * @throws JobInstanceAlreadyCompleteException if the job has been run before with the
+ * same parameters and completed successfully
+ * @throws IllegalArgumentException if the job or job parameters are null.
+ */
+ public JobExecution start(Job job, JobParameters jobParameters)
+ throws NoSuchJobException, JobInstanceAlreadyCompleteException, JobExecutionAlreadyRunningException,
+ JobRestartException, JobParametersInvalidException {
+ Assert.notNull(job, "The Job must not be null.");
+ Assert.notNull(jobParameters, "The JobParameters must not be null.");
+ if (job.getJobParametersIncrementer() != null) {
+ if (!jobParameters.isEmpty() && logger.isWarnEnabled()) {
+ logger.warn(String.format(
+ "Attempting to launch job: [%s] which defines an incrementer with additional parameters: [%s]. Additional parameters will be ignored.",
+ job.getName(), jobParameters));
+ }
+ return startNextInstance(job);
+ }
+ return run(job, jobParameters);
}
@Override
+ @Deprecated(since = "6.0", forRemoval = true)
public Long restart(long executionId) throws JobInstanceAlreadyCompleteException, NoSuchJobExecutionException,
NoSuchJobException, JobRestartException, JobParametersInvalidException {
@@ -244,7 +219,7 @@ public Long restart(long executionId) throws JobInstanceAlreadyCompleteException
logger.info(String.format("Attempting to resume job with name=%s and parameters=%s", jobName, parameters));
}
try {
- return jobLauncher.run(job, parameters).getId();
+ return run(job, parameters).getId();
}
catch (JobExecutionAlreadyRunningException e) {
throw new UnexpectedJobExecutionException(
@@ -254,44 +229,28 @@ public Long restart(long executionId) throws JobInstanceAlreadyCompleteException
}
@Override
- public Long start(String jobName, Properties parameters)
- throws NoSuchJobException, JobInstanceAlreadyExistsException, JobParametersInvalidException {
- if (logger.isInfoEnabled()) {
- logger.info("Checking status of job with name=" + jobName);
- }
-
- JobParameters jobParameters = jobParametersConverter.getJobParameters(parameters);
-
- if (jobRepository.isJobInstanceExists(jobName, jobParameters)) {
- throw new JobInstanceAlreadyExistsException(
- String.format("Cannot start a job instance that already exists with name=%s and parameters={%s}",
- jobName, parameters));
- }
+ public JobExecution restart(JobExecution jobExecution) throws JobInstanceAlreadyCompleteException,
+ NoSuchJobExecutionException, NoSuchJobException, JobRestartException, JobParametersInvalidException {
+ String jobName = jobExecution.getJobInstance().getJobName();
Job job = jobRegistry.getJob(jobName);
+ JobParameters parameters = jobExecution.getJobParameters();
+
if (logger.isInfoEnabled()) {
- logger
- .info(String.format("Attempting to launch job with name=%s and parameters={%s}", jobName, parameters));
+ logger.info("Resuming job execution: " + jobExecution);
}
try {
- return jobLauncher.run(job, jobParameters).getId();
+ return run(job, parameters);
}
catch (JobExecutionAlreadyRunningException e) {
throw new UnexpectedJobExecutionException(
String.format(ILLEGAL_STATE_MSG, "job execution already running", jobName, parameters), e);
}
- catch (JobRestartException e) {
- throw new UnexpectedJobExecutionException(
- String.format(ILLEGAL_STATE_MSG, "job not restartable", jobName, parameters), e);
- }
- catch (JobInstanceAlreadyCompleteException e) {
- throw new UnexpectedJobExecutionException(
- String.format(ILLEGAL_STATE_MSG, "job already complete", jobName, parameters), e);
- }
}
@Override
+ @Deprecated(since = "6.0", forRemoval = true)
public Long startNextInstance(String jobName)
throws NoSuchJobException, UnexpectedJobExecutionException, JobParametersInvalidException {
if (logger.isInfoEnabled()) {
@@ -299,32 +258,69 @@ public Long startNextInstance(String jobName)
}
Job job = jobRegistry.getJob(jobName);
- JobParameters parameters = new JobParametersBuilder(jobExplorer).getNextJobParameters(job).toJobParameters();
+ return startNextInstance(job).getId();
+ }
+
+ @Override
+ public JobExecution startNextInstance(Job job) throws UnexpectedJobExecutionException {
+ Assert.notNull(job, "Job must not be null");
+ Assert.notNull(job.getJobParametersIncrementer(),
+ "No job parameters incrementer found for job=" + job.getName());
+ String name = job.getName();
+ JobParameters nextParameters;
+ JobInstance lastInstance = jobRepository.getLastJobInstance(name);
+ JobParametersIncrementer incrementer = job.getJobParametersIncrementer();
+ if (lastInstance == null) {
+ // Start from a completely clean sheet
+ nextParameters = incrementer.getNext(new JobParameters());
+ }
+ else {
+ JobExecution previousExecution = jobRepository.getLastJobExecution(lastInstance);
+ if (previousExecution == null) {
+ // Normally this will not happen - an instance exists with no executions
+ nextParameters = incrementer.getNext(new JobParameters());
+ }
+ else {
+ nextParameters = incrementer.getNext(previousExecution.getJobParameters());
+ }
+ }
if (logger.isInfoEnabled()) {
- logger.info(String.format("Attempting to launch job with name=%s and parameters=%s", jobName, parameters));
+ logger.info("Launching next instance of job: [" + job.getName() + "] with parameters: [" + nextParameters
+ + "]");
}
try {
- return jobLauncher.run(job, parameters).getId();
+ return run(job, nextParameters);
}
catch (JobExecutionAlreadyRunningException e) {
throw new UnexpectedJobExecutionException(
- String.format(ILLEGAL_STATE_MSG, "job already running", jobName, parameters), e);
+ String.format(ILLEGAL_STATE_MSG, "job already running", job.getName(), nextParameters), e);
}
catch (JobRestartException e) {
throw new UnexpectedJobExecutionException(
- String.format(ILLEGAL_STATE_MSG, "job not restartable", jobName, parameters), e);
+ String.format(ILLEGAL_STATE_MSG, "job not restartable", job.getName(), nextParameters), e);
}
catch (JobInstanceAlreadyCompleteException e) {
throw new UnexpectedJobExecutionException(
- String.format(ILLEGAL_STATE_MSG, "job instance already complete", jobName, parameters), e);
+ String.format(ILLEGAL_STATE_MSG, "job instance already complete", job.getName(), nextParameters),
+ e);
+ }
+ catch (JobParametersInvalidException e) {
+ throw new UnexpectedJobExecutionException("Invalid job parameters " + nextParameters, e);
}
}
@Override
+ @Deprecated(since = "6.0", forRemoval = true)
public boolean stop(long executionId) throws NoSuchJobExecutionException, JobExecutionNotRunningException {
JobExecution jobExecution = findExecutionById(executionId);
+ return stop(jobExecution);
+ }
+
+ @Override
+ public boolean stop(JobExecution jobExecution) throws JobExecutionNotRunningException {
+ Assert.notNull(jobExecution, "JobExecution must not be null");
// Indicate the execution should be stopped by setting it's status to
// 'STOPPING'. It is assumed that
// the step implementation will check this status at chunk boundaries.
@@ -333,27 +329,35 @@ public boolean stop(long executionId) throws NoSuchJobExecutionException, JobExe
throw new JobExecutionNotRunningException(
"JobExecution must be running so that it can be stopped: " + jobExecution);
}
+ if (logger.isInfoEnabled()) {
+ logger.info("Stopping job execution: " + jobExecution);
+ }
jobExecution.setStatus(BatchStatus.STOPPING);
jobRepository.update(jobExecution);
try {
Job job = jobRegistry.getJob(jobExecution.getJobInstance().getJobName());
- if (job instanceof StepLocator) {// can only process as StepLocator is the
- // only way to get the step object
+ if (job instanceof StepLocator stepLocator) {
+ // can only process as StepLocator is the only way to get the step object
// get the current stepExecution
for (StepExecution stepExecution : jobExecution.getStepExecutions()) {
if (stepExecution.getStatus().isRunning()) {
try {
// have the step execution that's running -> need to 'stop' it
- Step step = ((StepLocator) job).getStep(stepExecution.getStepName());
- if (step instanceof TaskletStep) {
- Tasklet tasklet = ((TaskletStep) step).getTasklet();
- if (tasklet instanceof StoppableTasklet) {
+ Step step = stepLocator.getStep(stepExecution.getStepName());
+ if (step instanceof TaskletStep taskletStep) {
+ Tasklet tasklet = taskletStep.getTasklet();
+ if (tasklet instanceof StoppableTasklet stoppableTasklet) {
StepSynchronizationManager.register(stepExecution);
- ((StoppableTasklet) tasklet).stop();
+ stoppableTasklet.stop(stepExecution);
StepSynchronizationManager.release();
}
}
+ if (step instanceof StoppableStep stoppableStep) {
+ StepSynchronizationManager.register(stepExecution);
+ stoppableStep.stop(stepExecution);
+ StepSynchronizationManager.release();
+ }
}
catch (NoSuchStepException e) {
logger.warn("Step not found", e);
@@ -363,17 +367,26 @@ public boolean stop(long executionId) throws NoSuchJobExecutionException, JobExe
}
}
catch (NoSuchJobException e) {
- logger.warn("Cannot find Job object in the job registry. StoppableTasklet#stop() will not be called", e);
+ logger.warn(
+ "Cannot find Job object in the job registry. StoppableTasklet#stop(StepExecution stepExecution) will not be called",
+ e);
}
return true;
}
@Override
+ @Deprecated(since = "6.0", forRemoval = true)
public JobExecution abandon(long jobExecutionId)
throws NoSuchJobExecutionException, JobExecutionAlreadyRunningException {
JobExecution jobExecution = findExecutionById(jobExecutionId);
+ return abandon(jobExecution);
+ }
+
+ @Override
+ public JobExecution abandon(JobExecution jobExecution) throws JobExecutionAlreadyRunningException {
+ Assert.notNull(jobExecution, "JobExecution must not be null");
if (jobExecution.getStatus().isLessThan(BatchStatus.STOPPING)) {
throw new JobExecutionAlreadyRunningException(
"JobExecution is running or complete and therefore cannot be aborted");
@@ -388,8 +401,134 @@ public JobExecution abandon(long jobExecutionId)
return jobExecution;
}
+ @Override
+ public JobExecution recover(JobExecution jobExecution) {
+ Assert.notNull(jobExecution, "JobExecution must not be null");
+ if (jobExecution.getExecutionContext().containsKey("recovered")) {
+ if (logger.isWarnEnabled()) {
+ logger.warn("Job execution already recovered: " + jobExecution);
+ }
+ return jobExecution;
+ }
+
+ BatchStatus jobStatus = jobExecution.getStatus();
+ if (jobStatus == BatchStatus.COMPLETED || jobStatus == BatchStatus.ABANDONED
+ || jobStatus == BatchStatus.UNKNOWN) {
+ if (logger.isWarnEnabled()) {
+ logger.warn(
+ "JobExecution is already complete or abandoned or in an unknown state, and therefore cannot be recovered: "
+ + jobExecution);
+ }
+ return jobExecution;
+ }
+
+ if (logger.isInfoEnabled()) {
+ logger.info("Recovering job execution: " + jobExecution);
+ }
+
+ for (StepExecution stepExecution : jobExecution.getStepExecutions()) {
+ BatchStatus stepStatus = stepExecution.getStatus();
+ if (stepStatus.isRunning()) {
+ stepExecution.setStatus(BatchStatus.FAILED);
+ stepExecution.setEndTime(LocalDateTime.now());
+ stepExecution.getExecutionContext().put("recovered", true);
+ jobRepository.update(stepExecution);
+ }
+ }
+
+ jobExecution.setStatus(BatchStatus.FAILED);
+ jobExecution.setEndTime(LocalDateTime.now());
+ jobExecution.getExecutionContext().put("recovered", true);
+ jobRepository.update(jobExecution);
+
+ return jobExecution;
+ }
+
+ @Override
+ @Deprecated(since = "6.0", forRemoval = true)
+ public Set getJobNames() {
+ return new TreeSet<>(jobRegistry.getJobNames());
+ }
+
+ @Override
+ @Deprecated(since = "6.0", forRemoval = true)
+ public List getExecutions(long instanceId) throws NoSuchJobInstanceException {
+ JobInstance jobInstance = jobRepository.getJobInstance(instanceId);
+ if (jobInstance == null) {
+ throw new NoSuchJobInstanceException(String.format("No job instance with id=%d", instanceId));
+ }
+ List list = new ArrayList<>();
+ for (JobExecution jobExecution : jobRepository.getJobExecutions(jobInstance)) {
+ list.add(jobExecution.getId());
+ }
+ return list;
+ }
+
+ @Override
+ @Deprecated(since = "6.0", forRemoval = true)
+ public List getJobInstances(String jobName, int start, int count) throws NoSuchJobException {
+ List list = new ArrayList<>();
+ List jobInstances = jobRepository.getJobInstances(jobName, start, count);
+ for (JobInstance jobInstance : jobInstances) {
+ list.add(jobInstance.getId());
+ }
+ if (list.isEmpty() && !jobRegistry.getJobNames().contains(jobName)) {
+ throw new NoSuchJobException("No such job (either in registry or in historical data): " + jobName);
+ }
+ return list;
+ }
+
+ @Override
+ @Nullable
+ @Deprecated(since = "6.0", forRemoval = true)
+ public JobInstance getJobInstance(String jobName, JobParameters jobParameters) {
+ return this.jobRepository.getJobInstance(jobName, jobParameters);
+ }
+
+ @Override
+ @Deprecated(since = "6.0", forRemoval = true)
+ public String getParameters(long executionId) throws NoSuchJobExecutionException {
+ JobExecution jobExecution = findExecutionById(executionId);
+
+ Properties properties = this.jobParametersConverter.getProperties(jobExecution.getJobParameters());
+
+ return PropertiesConverter.propertiesToString(properties);
+ }
+
+ @Override
+ @Deprecated(since = "6.0", forRemoval = true)
+ public Set getRunningExecutions(String jobName) throws NoSuchJobException {
+ Set set = new LinkedHashSet<>();
+ for (JobExecution jobExecution : jobRepository.findRunningJobExecutions(jobName)) {
+ set.add(jobExecution.getId());
+ }
+ if (set.isEmpty() && !jobRegistry.getJobNames().contains(jobName)) {
+ throw new NoSuchJobException("No such job (either in registry or in historical data): " + jobName);
+ }
+ return set;
+ }
+
+ @Override
+ @Deprecated(since = "6.0", forRemoval = true)
+ public Map getStepExecutionSummaries(long executionId) throws NoSuchJobExecutionException {
+ JobExecution jobExecution = findExecutionById(executionId);
+
+ Map map = new LinkedHashMap<>();
+ for (StepExecution stepExecution : jobExecution.getStepExecutions()) {
+ map.put(stepExecution.getId(), stepExecution.toString());
+ }
+ return map;
+ }
+
+ @Override
+ @Deprecated(since = "6.0", forRemoval = true)
+ public String getSummary(long executionId) throws NoSuchJobExecutionException {
+ JobExecution jobExecution = findExecutionById(executionId);
+ return jobExecution.toString();
+ }
+
private JobExecution findExecutionById(long executionId) throws NoSuchJobExecutionException {
- JobExecution jobExecution = jobExplorer.getJobExecution(executionId);
+ JobExecution jobExecution = jobRepository.getJobExecution(executionId);
if (jobExecution == null) {
throw new NoSuchJobExecutionException("No JobExecution found for id: [" + executionId + "]");
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/SystemExiter.java b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/SystemExiter.java
index a94c8b116f..d3b980fb9b 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/SystemExiter.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/SystemExiter.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006-2023 the original author or authors.
+ * Copyright 2006-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,8 +21,9 @@
* unit test would cause the entire jvm to finish.
*
* @author Lucas Ward
- *
+ * @deprecated since 6.0 with no replacement. Scheduled for removal in 6.2 or later.
*/
+@Deprecated(since = "6.0", forRemoval = true)
public interface SystemExiter {
/**
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/TaskExecutorJobLauncher.java b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/TaskExecutorJobLauncher.java
index aab3443cd5..afede31346 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/TaskExecutorJobLauncher.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/TaskExecutorJobLauncher.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2022-2024 the original author or authors.
+ * Copyright 2022-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,20 +17,18 @@
import java.time.Duration;
-import io.micrometer.core.instrument.Counter;
-import io.micrometer.core.instrument.MeterRegistry;
-import io.micrometer.core.instrument.Metrics;
+import io.micrometer.observation.ObservationRegistry;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.batch.core.BatchStatus;
import org.springframework.batch.core.ExitStatus;
-import org.springframework.batch.core.Job;
-import org.springframework.batch.core.JobExecution;
-import org.springframework.batch.core.JobInstance;
-import org.springframework.batch.core.JobParameters;
-import org.springframework.batch.core.JobParametersInvalidException;
-import org.springframework.batch.core.StepExecution;
+import org.springframework.batch.core.job.Job;
+import org.springframework.batch.core.job.JobExecution;
+import org.springframework.batch.core.job.JobInstance;
+import org.springframework.batch.core.job.parameters.JobParameters;
+import org.springframework.batch.core.job.parameters.JobParametersInvalidException;
+import org.springframework.batch.core.step.StepExecution;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.observability.BatchMetrics;
import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException;
@@ -65,18 +63,20 @@
* @since 1.0
* @see JobRepository
* @see TaskExecutor
+ * @deprecated since 6.0 in favor of {@link TaskExecutorJobOperator}. Scheduled for
+ * removal in 6.2 or later.
*/
+@SuppressWarnings("removal")
+@Deprecated(since = "6.0", forRemoval = true)
public class TaskExecutorJobLauncher implements JobLauncher, InitializingBean {
protected static final Log logger = LogFactory.getLog(TaskExecutorJobLauncher.class);
- private JobRepository jobRepository;
+ protected JobRepository jobRepository;
- private TaskExecutor taskExecutor;
+ protected TaskExecutor taskExecutor;
- private MeterRegistry meterRegistry = Metrics.globalRegistry;
-
- private Counter jobLaunchCount; // NoopCounter is still incubating
+ protected ObservationRegistry observationRegistry;
/**
* Run the provided job with the given {@link JobParameters}. The
@@ -101,9 +101,6 @@ public JobExecution run(final Job job, final JobParameters jobParameters)
Assert.notNull(job, "The Job must not be null.");
Assert.notNull(jobParameters, "The JobParameters must not be null.");
- if (this.jobLaunchCount != null) {
- this.jobLaunchCount.increment();
- }
final JobExecution jobExecution;
JobExecution lastExecution = jobRepository.getLastJobExecution(job.getName(), jobParameters);
@@ -173,11 +170,11 @@ public void run() {
}
private void rethrow(Throwable t) {
- if (t instanceof RuntimeException) {
- throw (RuntimeException) t;
+ if (t instanceof RuntimeException runtimeException) {
+ throw runtimeException;
}
- else if (t instanceof Error) {
- throw (Error) t;
+ else if (t instanceof Error error) {
+ throw error;
}
throw new IllegalStateException(t);
}
@@ -210,16 +207,6 @@ public void setTaskExecutor(TaskExecutor taskExecutor) {
this.taskExecutor = taskExecutor;
}
- /**
- * Set the meter registry to use for metrics. Defaults to
- * {@link Metrics#globalRegistry}.
- * @param meterRegistry the meter registry
- * @since 5.0
- */
- public void setMeterRegistry(MeterRegistry meterRegistry) {
- this.meterRegistry = meterRegistry;
- }
-
/**
* Ensure the required dependencies of a {@link JobRepository} have been set.
*/
@@ -230,7 +217,6 @@ public void afterPropertiesSet() throws Exception {
logger.info("No TaskExecutor has been set, defaulting to synchronous executor.");
taskExecutor = new SyncTaskExecutor();
}
- this.jobLaunchCount = BatchMetrics.createCounter(this.meterRegistry, "job.launch.count", "Job launch count");
}
}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/TaskExecutorJobOperator.java b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/TaskExecutorJobOperator.java
new file mode 100644
index 0000000000..68ef26c9df
--- /dev/null
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/TaskExecutorJobOperator.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright 2022-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.batch.core.launch.support;
+
+import io.micrometer.observation.Observation;
+import io.micrometer.observation.ObservationRegistry;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.batch.core.configuration.JobRegistry;
+import org.springframework.batch.core.job.Job;
+import org.springframework.batch.core.job.JobExecution;
+import org.springframework.batch.core.job.UnexpectedJobExecutionException;
+import org.springframework.batch.core.job.parameters.JobParameters;
+import org.springframework.batch.core.job.parameters.JobParametersInvalidException;
+import org.springframework.batch.core.launch.JobExecutionNotRunningException;
+import org.springframework.batch.core.launch.JobOperator;
+import org.springframework.batch.core.launch.NoSuchJobException;
+import org.springframework.batch.core.launch.NoSuchJobExecutionException;
+import org.springframework.batch.core.observability.jfr.events.job.JobLaunchEvent;
+import org.springframework.batch.core.observability.micrometer.MicrometerMetrics;
+import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException;
+import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException;
+import org.springframework.batch.core.repository.JobRepository;
+import org.springframework.batch.core.repository.JobRestartException;
+import org.springframework.core.task.TaskExecutor;
+import org.springframework.util.Assert;
+
+import static org.springframework.batch.core.observability.BatchMetrics.METRICS_PREFIX;
+
+/**
+ * A {@link org.springframework.core.task.TaskExecutor}-based implementation of the
+ * {@link JobOperator} interface. The following dependencies are required:
+ *
+ *
+ * - {@link JobRepository}
+ *
- {@link JobRegistry}
+ *
+ *
+ * This class can be instantiated with a {@link JobOperatorFactoryBean} to create a
+ * transactional proxy around the job operator.
+ *
+ * @see JobOperatorFactoryBean
+ * @author Dave Syer
+ * @author Lucas Ward
+ * @author Will Schipp
+ * @author Mahmoud Ben Hassine
+ * @author Yejeong Ham
+ * @since 6.0
+ */
+@SuppressWarnings("removal")
+public class TaskExecutorJobOperator extends SimpleJobOperator {
+
+ private static final Log logger = LogFactory.getLog(TaskExecutorJobOperator.class.getName());
+
+ protected ObservationRegistry observationRegistry;
+
+ @Override
+ public void afterPropertiesSet() throws Exception {
+ super.afterPropertiesSet();
+ if (this.observationRegistry == null) {
+ logger.info("No ObservationRegistry has been set, defaulting to ObservationRegistry NOOP");
+ this.observationRegistry = ObservationRegistry.NOOP;
+ }
+ }
+
+ @Override
+ public void setJobRegistry(JobRegistry jobRegistry) {
+ Assert.notNull(jobRegistry, "JobRegistry must not be null");
+ this.jobRegistry = jobRegistry;
+ }
+
+ @Override
+ public void setJobRepository(JobRepository jobRepository) {
+ Assert.notNull(jobRepository, "JobRepository must not be null");
+ this.jobRepository = jobRepository;
+ }
+
+ @Override
+ public void setTaskExecutor(TaskExecutor taskExecutor) {
+ Assert.notNull(taskExecutor, "TaskExecutor must not be null");
+ this.taskExecutor = taskExecutor;
+ }
+
+ /**
+ * Set the observation registry to use for observations. Defaults to
+ * {@link ObservationRegistry#NOOP}.
+ * @param observationRegistry the observation registry
+ * @since 6.0
+ */
+ public void setObservationRegistry(ObservationRegistry observationRegistry) {
+ Assert.notNull(observationRegistry, "ObservationRegistry must not be null");
+ this.observationRegistry = observationRegistry;
+ }
+
+ @Override
+ public JobExecution start(Job job, JobParameters jobParameters)
+ throws NoSuchJobException, JobInstanceAlreadyCompleteException, JobExecutionAlreadyRunningException,
+ JobRestartException, JobParametersInvalidException {
+ Assert.notNull(job, "Job must not be null");
+ Assert.notNull(jobParameters, "JobParameters must not be null");
+ new JobLaunchEvent(job.getName(), jobParameters.toString()).commit();
+ Observation observation = MicrometerMetrics
+ .createObservation(METRICS_PREFIX + "job.launch.count", this.observationRegistry)
+ .start();
+ try (var scope = observation.openScope()) {
+ return super.start(job, jobParameters);
+ }
+ finally {
+ observation.stop();
+ }
+ }
+
+ @Override
+ public JobExecution restart(JobExecution jobExecution) throws JobInstanceAlreadyCompleteException,
+ NoSuchJobExecutionException, NoSuchJobException, JobRestartException, JobParametersInvalidException {
+ Assert.notNull(jobExecution, "JobExecution must not be null");
+ return super.restart(jobExecution);
+ }
+
+ @Override
+ public JobExecution startNextInstance(Job job) throws UnexpectedJobExecutionException {
+ Assert.notNull(job, "Job must not be null");
+ return super.startNextInstance(job);
+ }
+
+ @Override
+ public boolean stop(JobExecution jobExecution) throws JobExecutionNotRunningException {
+ Assert.notNull(jobExecution, "JobExecution must not be null");
+ return super.stop(jobExecution);
+ }
+
+ @Override
+ public JobExecution abandon(JobExecution jobExecution) throws JobExecutionAlreadyRunningException {
+ Assert.notNull(jobExecution, "JobExecution must not be null");
+ return super.abandon(jobExecution);
+ }
+
+ @Override
+ public JobExecution recover(JobExecution jobExecution) {
+ Assert.notNull(jobExecution, "JobExecution must not be null");
+ return super.recover(jobExecution);
+ }
+
+}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/AbstractListenerFactoryBean.java b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/AbstractListenerFactoryBean.java
index 18c9e4cf2e..00ca69e6fd 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/AbstractListenerFactoryBean.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/AbstractListenerFactoryBean.java
@@ -150,8 +150,8 @@ public Object getObject() {
// create a proxy listener for only the interfaces that have methods to
// be called
ProxyFactory proxyFactory = new ProxyFactory();
- if (delegate instanceof Advised) {
- proxyFactory.setTargetSource(((Advised) delegate).getTargetSource());
+ if (delegate instanceof Advised advised) {
+ proxyFactory.setTargetSource(advised.getTargetSource());
}
else {
proxyFactory.setTarget(delegate);
@@ -214,15 +214,13 @@ public static boolean isListener(Object target, Class> listenerType, ListenerM
if (listenerType.isInstance(target)) {
return true;
}
- if (target instanceof Advised) {
- TargetSource targetSource = ((Advised) target).getTargetSource();
- if (targetSource != null && targetSource.getTargetClass() != null
- && listenerType.isAssignableFrom(targetSource.getTargetClass())) {
+ if (target instanceof Advised advised) {
+ TargetSource targetSource = advised.getTargetSource();
+ if (targetSource.getTargetClass() != null && listenerType.isAssignableFrom(targetSource.getTargetClass())) {
return true;
}
- if (targetSource != null && targetSource.getTargetClass() != null
- && targetSource.getTargetClass().isInterface()) {
+ if (targetSource.getTargetClass() != null && targetSource.getTargetClass().isInterface()) {
logger.warn(String.format(
"%s is an interface. The implementing class will not be queried for annotation based listener configurations. If using @StepScope on a @Bean method, be sure to return the implementing class so listener annotations can be used.",
targetSource.getTargetClass().getName()));
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/ChunkListener.java b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/ChunkListener.java
similarity index 55%
rename from spring-batch-core/src/main/java/org/springframework/batch/core/ChunkListener.java
rename to spring-batch-core/src/main/java/org/springframework/batch/core/listener/ChunkListener.java
index 951410235b..45b91756db 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/ChunkListener.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/ChunkListener.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006-2024 the original author or authors.
+ * Copyright 2006-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,9 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.springframework.batch.core;
+package org.springframework.batch.core.listener;
import org.springframework.batch.core.scope.context.ChunkContext;
+import org.springframework.batch.item.Chunk;
/**
* Listener interface for the lifecycle of a chunk. A chunk can be thought of as a
@@ -23,6 +24,9 @@
*
* {@link ChunkListener} shouldn't throw exceptions and expect continued processing, they
* must be handled in the implementation or the step will terminate.
+ *
+ * Note: This listener is not called in concurrent steps.
+ *
*
* @author Lucas Ward
* @author Michael Minella
@@ -30,24 +34,32 @@
* @author Parikshit Dutta
* @author Injae Kim
*/
-public interface ChunkListener extends StepListener {
+public interface ChunkListener extends StepListener {
/**
* The key for retrieving the rollback exception.
+ * @deprecated since 6.0 with no replacement. Scheduled for removal in 6.2 or later.
*/
+ @Deprecated(since = "6.0", forRemoval = true)
String ROLLBACK_EXCEPTION_KEY = "sb_rollback_exception";
/**
* Callback before the chunk is executed, but inside the transaction.
* @param context The current {@link ChunkContext}
+ * @deprecated since 6.0, use {@link #beforeChunk(Chunk)} instead. Scheduled for
+ * removal in 6.2 or later.
*/
+ @Deprecated(since = "6.0", forRemoval = true)
default void beforeChunk(ChunkContext context) {
}
/**
* Callback after the chunk is executed, outside the transaction.
* @param context The current {@link ChunkContext}
+ * @deprecated since 6.0, use {@link #afterChunk(Chunk)} instead. Scheduled for
+ * removal in 6.2 or later.
*/
+ @Deprecated(since = "6.0", forRemoval = true)
default void afterChunk(ChunkContext context) {
}
@@ -61,8 +73,39 @@ default void afterChunk(ChunkContext context) {
* from here.
* @param context the chunk context containing the exception that caused the
* underlying rollback.
+ * @deprecated since 6.0, use {@link #onChunkError(Exception,Chunk)} instead.
+ * Scheduled for removal in 6.2 or later.
*/
+ @Deprecated(since = "6.0", forRemoval = true)
default void afterChunkError(ChunkContext context) {
}
+ /**
+ * Callback before the chunk is processed, inside the transaction. This method is not
+ * called in concurrent steps.
+ * @since 6.0
+ */
+ default void beforeChunk(Chunk chunk) {
+ }
+
+ /**
+ * Callback after the chunk is written, inside the transaction. This method is not
+ * called in concurrent steps.
+ * @since 6.0
+ */
+ default void afterChunk(Chunk chunk) {
+ }
+
+ /**
+ * Callback if an exception occurs while processing or writing a chunk, inside the
+ * transaction, which is about to be rolled back. As a result, you should use
+ * {@code PROPAGATION_REQUIRES_NEW} for any transactional operation that is called
+ * here. This method is not called in concurrent steps.
+ * @param exception the exception that caused the underlying rollback.
+ * @param chunk the processed chunk
+ * @since 6.0
+ */
+ default void onChunkError(Exception exception, Chunk chunk) {
+ }
+
}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/ChunkListenerSupport.java b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/ChunkListenerSupport.java
deleted file mode 100644
index 79d742240e..0000000000
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/ChunkListenerSupport.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright 2006-2023 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.springframework.batch.core.listener;
-
-import org.springframework.batch.core.ChunkListener;
-import org.springframework.batch.core.scope.context.ChunkContext;
-
-/**
- * Basic support implementation of {@link ChunkListener}
- *
- * @author Lucas Ward
- * @author Michael Minella
- * @deprecated as of 5.0, in favor of the default methods on the {@link ChunkListener}
- */
-@Deprecated
-public class ChunkListenerSupport implements ChunkListener {
-
- @Override
- public void afterChunk(ChunkContext context) {
- }
-
- @Override
- public void beforeChunk(ChunkContext context) {
- }
-
- @Override
- public void afterChunkError(ChunkContext context) {
- }
-
-}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/CompositeChunkListener.java b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/CompositeChunkListener.java
index 1d7b747012..eb5baf7134 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/CompositeChunkListener.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/CompositeChunkListener.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006-2023 the original author or authors.
+ * Copyright 2006-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,8 +19,8 @@
import java.util.Iterator;
import java.util.List;
-import org.springframework.batch.core.ChunkListener;
import org.springframework.batch.core.scope.context.ChunkContext;
+import org.springframework.batch.item.Chunk;
import org.springframework.core.Ordered;
/**
@@ -28,7 +28,7 @@
* @author Mahmoud Ben Hassine
*
*/
-public class CompositeChunkListener implements ChunkListener {
+public class CompositeChunkListener implements ChunkListener {
private final OrderedComposite listeners = new OrderedComposite<>();
@@ -74,8 +74,11 @@ public void register(ChunkListener chunkListener) {
/**
* Call the registered listeners in reverse order.
*
- * @see org.springframework.batch.core.ChunkListener#afterChunk(ChunkContext context)
+ * @see ChunkListener#afterChunk(ChunkContext context)
+ * @deprecated since 6.0, use {@link #afterChunk(Chunk)} instead. Scheduled for
+ * removal in 6.2 or later.
*/
+ @Deprecated(since = "6.0", forRemoval = true)
@Override
public void afterChunk(ChunkContext context) {
for (Iterator iterator = listeners.reverse(); iterator.hasNext();) {
@@ -84,12 +87,28 @@ public void afterChunk(ChunkContext context) {
}
}
+ /**
+ * Call the registered listeners in reverse order.
+ *
+ * @see ChunkListener#afterChunk(Chunk)
+ */
+ @Override
+ public void afterChunk(Chunk chunk) {
+ for (Iterator iterator = listeners.reverse(); iterator.hasNext();) {
+ ChunkListener listener = iterator.next();
+ listener.afterChunk(chunk);
+ }
+ }
+
/**
* Call the registered listeners in order, respecting and prioritizing those that
* implement {@link Ordered}.
*
- * @see org.springframework.batch.core.ChunkListener#beforeChunk(ChunkContext context)
+ * @see ChunkListener#beforeChunk(ChunkContext context)
+ * @deprecated since 6.0, use {@link #beforeChunk(Chunk)} instead. Scheduled for
+ * removal in 6.2 or later.
*/
+ @Deprecated(since = "6.0", forRemoval = true)
@Override
public void beforeChunk(ChunkContext context) {
for (Iterator iterator = listeners.iterator(); iterator.hasNext();) {
@@ -98,12 +117,28 @@ public void beforeChunk(ChunkContext context) {
}
}
+ /**
+ * Call the registered listeners in order, respecting and prioritizing those that
+ * implement {@link Ordered}.
+ *
+ * @see ChunkListener#beforeChunk(Chunk chunk)
+ */
+ @Override
+ public void beforeChunk(Chunk chunk) {
+ for (Iterator iterator = listeners.iterator(); iterator.hasNext();) {
+ ChunkListener listener = iterator.next();
+ listener.beforeChunk(chunk);
+ }
+ }
+
/**
* Call the registered listeners in reverse order.
*
- * @see org.springframework.batch.core.ChunkListener#afterChunkError(ChunkContext
- * context)
+ * @see ChunkListener#afterChunkError(ChunkContext context)
+ * @deprecated since 6.0, use {@link #onChunkError(Exception,Chunk)} instead.
+ * Scheduled for removal in 6.2 or later.
*/
+ @Deprecated(since = "6.0", forRemoval = true)
@Override
public void afterChunkError(ChunkContext context) {
for (Iterator iterator = listeners.reverse(); iterator.hasNext();) {
@@ -112,4 +147,17 @@ public void afterChunkError(ChunkContext context) {
}
}
+ /**
+ * Call the registered listeners in reverse order.
+ *
+ * @see ChunkListener#onChunkError(Exception, Chunk)
+ */
+ @Override
+ public void onChunkError(Exception exception, Chunk chunk) {
+ for (Iterator iterator = listeners.reverse(); iterator.hasNext();) {
+ ChunkListener listener = iterator.next();
+ listener.onChunkError(exception, chunk);
+ }
+ }
+
}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/CompositeItemProcessListener.java b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/CompositeItemProcessListener.java
index 882770dbd4..19c966a503 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/CompositeItemProcessListener.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/CompositeItemProcessListener.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006-2023 the original author or authors.
+ * Copyright 2006-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,7 +18,6 @@
import java.util.Iterator;
import java.util.List;
-import org.springframework.batch.core.ItemProcessListener;
import org.springframework.core.Ordered;
import org.springframework.lang.Nullable;
@@ -52,8 +51,7 @@ public void register(ItemProcessListener super T, ? super S> itemProcessorList
/**
* Call the registered listeners in reverse order, respecting and prioritising those
* that implement {@link Ordered}.
- * @see org.springframework.batch.core.ItemProcessListener#afterProcess(java.lang.Object,
- * java.lang.Object)
+ * @see ItemProcessListener#afterProcess(java.lang.Object, java.lang.Object)
*/
@Override
public void afterProcess(T item, @Nullable S result) {
@@ -66,7 +64,7 @@ public void afterProcess(T item, @Nullable S result) {
/**
* Call the registered listeners in order, respecting and prioritising those that
* implement {@link Ordered}.
- * @see org.springframework.batch.core.ItemProcessListener#beforeProcess(java.lang.Object)
+ * @see ItemProcessListener#beforeProcess(java.lang.Object)
*/
@Override
public void beforeProcess(T item) {
@@ -79,8 +77,7 @@ public void beforeProcess(T item) {
/**
* Call the registered listeners in reverse order, respecting and prioritising those
* that implement {@link Ordered}.
- * @see org.springframework.batch.core.ItemProcessListener#onProcessError(java.lang.Object,
- * java.lang.Exception)
+ * @see ItemProcessListener#onProcessError(java.lang.Object, java.lang.Exception)
*/
@Override
public void onProcessError(T item, Exception e) {
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/CompositeItemReadListener.java b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/CompositeItemReadListener.java
index 18fa7599a7..f006af5458 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/CompositeItemReadListener.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/CompositeItemReadListener.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006-2023 the original author or authors.
+ * Copyright 2006-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,7 +18,6 @@
import java.util.Iterator;
import java.util.List;
-import org.springframework.batch.core.ItemReadListener;
import org.springframework.core.Ordered;
/**
@@ -51,7 +50,7 @@ public void register(ItemReadListener super T> itemReaderListener) {
/**
* Call the registered listeners in reverse order, respecting and prioritising those
* that implement {@link Ordered}.
- * @see org.springframework.batch.core.ItemReadListener#afterRead(java.lang.Object)
+ * @see ItemReadListener#afterRead(java.lang.Object)
*/
@Override
public void afterRead(T item) {
@@ -64,7 +63,7 @@ public void afterRead(T item) {
/**
* Call the registered listeners in order, respecting and prioritising those that
* implement {@link Ordered}.
- * @see org.springframework.batch.core.ItemReadListener#beforeRead()
+ * @see ItemReadListener#beforeRead()
*/
@Override
public void beforeRead() {
@@ -77,7 +76,7 @@ public void beforeRead() {
/**
* Call the registered listeners in reverse order, respecting and prioritising those
* that implement {@link Ordered}.
- * @see org.springframework.batch.core.ItemReadListener#onReadError(java.lang.Exception)
+ * @see ItemReadListener#onReadError(java.lang.Exception)
*/
@Override
public void onReadError(Exception ex) {
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/CompositeItemWriteListener.java b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/CompositeItemWriteListener.java
index 300bc30a9e..cf7b1916ac 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/CompositeItemWriteListener.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/CompositeItemWriteListener.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006-2023 the original author or authors.
+ * Copyright 2006-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,7 +18,6 @@
import java.util.Iterator;
import java.util.List;
-import org.springframework.batch.core.ItemWriteListener;
import org.springframework.batch.item.Chunk;
import org.springframework.core.Ordered;
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/CompositeJobExecutionListener.java b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/CompositeJobExecutionListener.java
index 304b1b2a92..c769d1ceaa 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/CompositeJobExecutionListener.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/CompositeJobExecutionListener.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006-2023 the original author or authors.
+ * Copyright 2006-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,8 +18,7 @@
import java.util.Iterator;
import java.util.List;
-import org.springframework.batch.core.JobExecution;
-import org.springframework.batch.core.JobExecutionListener;
+import org.springframework.batch.core.job.JobExecution;
import org.springframework.core.Ordered;
/**
@@ -51,7 +50,7 @@ public void register(JobExecutionListener jobExecutionListener) {
/**
* Call the registered listeners in reverse order, respecting and prioritising those
* that implement {@link Ordered}.
- * @see org.springframework.batch.core.JobExecutionListener#afterJob(org.springframework.batch.core.JobExecution)
+ * @see JobExecutionListener#afterJob(JobExecution)
*/
@Override
public void afterJob(JobExecution jobExecution) {
@@ -64,7 +63,7 @@ public void afterJob(JobExecution jobExecution) {
/**
* Call the registered listeners in order, respecting and prioritising those that
* implement {@link Ordered}.
- * @see org.springframework.batch.core.JobExecutionListener#beforeJob(org.springframework.batch.core.JobExecution)
+ * @see JobExecutionListener#beforeJob(JobExecution)
*/
@Override
public void beforeJob(JobExecution jobExecution) {
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/CompositeSkipListener.java b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/CompositeSkipListener.java
index 13a355b8c0..d2f969527f 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/CompositeSkipListener.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/CompositeSkipListener.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006-2023 the original author or authors.
+ * Copyright 2006-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,7 +18,6 @@
import java.util.Iterator;
import java.util.List;
-import org.springframework.batch.core.SkipListener;
import org.springframework.core.Ordered;
/**
@@ -49,7 +48,7 @@ public void register(SkipListener super T, ? super S> listener) {
/**
* Call the registered listeners in order, respecting and prioritising those that
* implement {@link Ordered}.
- * @see org.springframework.batch.core.SkipListener#onSkipInRead(java.lang.Throwable)
+ * @see SkipListener#onSkipInRead(java.lang.Throwable)
*/
@Override
public void onSkipInRead(Throwable t) {
@@ -62,8 +61,7 @@ public void onSkipInRead(Throwable t) {
/**
* Call the registered listeners in order, respecting and prioritising those that
* implement {@link Ordered}.
- * @see org.springframework.batch.core.SkipListener#onSkipInWrite(java.lang.Object,
- * java.lang.Throwable)
+ * @see SkipListener#onSkipInWrite(java.lang.Object, java.lang.Throwable)
*/
@Override
public void onSkipInWrite(S item, Throwable t) {
@@ -76,8 +74,7 @@ public void onSkipInWrite(S item, Throwable t) {
/**
* Call the registered listeners in order, respecting and prioritising those that
* implement {@link Ordered}.
- * @see org.springframework.batch.core.SkipListener#onSkipInWrite(java.lang.Object,
- * java.lang.Throwable)
+ * @see SkipListener#onSkipInWrite(java.lang.Object, java.lang.Throwable)
*/
@Override
public void onSkipInProcess(T item, Throwable t) {
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/CompositeStepExecutionListener.java b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/CompositeStepExecutionListener.java
index bfaa770926..b3421897ae 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/CompositeStepExecutionListener.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/CompositeStepExecutionListener.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006-2023 the original author or authors.
+ * Copyright 2006-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,8 +19,7 @@
import java.util.Iterator;
import org.springframework.batch.core.ExitStatus;
-import org.springframework.batch.core.StepExecution;
-import org.springframework.batch.core.StepExecutionListener;
+import org.springframework.batch.core.step.StepExecution;
import org.springframework.core.Ordered;
import org.springframework.lang.Nullable;
@@ -55,7 +54,7 @@ public void register(StepExecutionListener stepExecutionListener) {
/**
* Call the registered listeners in reverse order, respecting and prioritizing those
* that implement {@link Ordered}.
- * @see org.springframework.batch.core.StepExecutionListener#afterStep(StepExecution)
+ * @see StepExecutionListener#afterStep(StepExecution)
*/
@Nullable
@Override
@@ -71,7 +70,7 @@ public ExitStatus afterStep(StepExecution stepExecution) {
/**
* Call the registered listeners in order, respecting and prioritizing those that
* implement {@link Ordered}.
- * @see org.springframework.batch.core.StepExecutionListener#beforeStep(StepExecution)
+ * @see StepExecutionListener#beforeStep(StepExecution)
*/
@Override
public void beforeStep(StepExecution stepExecution) {
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/ExecutionContextPromotionListener.java b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/ExecutionContextPromotionListener.java
index 525dc86b92..262cda639a 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/ExecutionContextPromotionListener.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/ExecutionContextPromotionListener.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006-2023 the original author or authors.
+ * Copyright 2006-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,10 +16,9 @@
package org.springframework.batch.core.listener;
import org.springframework.batch.core.ExitStatus;
-import org.springframework.batch.core.Job;
-import org.springframework.batch.core.Step;
-import org.springframework.batch.core.StepExecution;
-import org.springframework.batch.core.StepExecutionListener;
+import org.springframework.batch.core.job.Job;
+import org.springframework.batch.core.step.Step;
+import org.springframework.batch.core.step.StepExecution;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.batch.support.PatternMatcher;
import org.springframework.beans.factory.InitializingBean;
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/ItemListenerSupport.java b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/ItemListenerSupport.java
index f2023a9294..e283904216 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/ItemListenerSupport.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/ItemListenerSupport.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006-2021 the original author or authors.
+ * Copyright 2006-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,10 +15,6 @@
*/
package org.springframework.batch.core.listener;
-import org.springframework.batch.core.ItemProcessListener;
-import org.springframework.batch.core.ItemReadListener;
-import org.springframework.batch.core.ItemWriteListener;
-
/**
* Basic no-op implementation of the {@link ItemReadListener},
* {@link ItemProcessListener}, and {@link ItemWriteListener} interfaces. All are
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/ItemProcessListener.java b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/ItemProcessListener.java
similarity index 94%
rename from spring-batch-core/src/main/java/org/springframework/batch/core/ItemProcessListener.java
rename to spring-batch-core/src/main/java/org/springframework/batch/core/listener/ItemProcessListener.java
index 23f6cf4bd4..fb3d394fcc 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/ItemProcessListener.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/ItemProcessListener.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006-2022 the original author or authors.
+ * Copyright 2006-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.springframework.batch.core;
+package org.springframework.batch.core.listener;
import org.springframework.batch.item.ItemProcessor;
import org.springframework.lang.Nullable;
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/ItemReadListener.java b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/ItemReadListener.java
similarity index 92%
rename from spring-batch-core/src/main/java/org/springframework/batch/core/ItemReadListener.java
rename to spring-batch-core/src/main/java/org/springframework/batch/core/listener/ItemReadListener.java
index d12e80e629..7a6dc6f710 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/ItemReadListener.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/ItemReadListener.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006-2022 the original author or authors.
+ * Copyright 2006-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.springframework.batch.core;
+package org.springframework.batch.core.listener;
import org.springframework.batch.item.ItemReader;
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/ItemWriteListener.java b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/ItemWriteListener.java
similarity index 95%
rename from spring-batch-core/src/main/java/org/springframework/batch/core/ItemWriteListener.java
rename to spring-batch-core/src/main/java/org/springframework/batch/core/listener/ItemWriteListener.java
index 46c55786c0..9e05c5458b 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/ItemWriteListener.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/ItemWriteListener.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006-2022 the original author or authors.
+ * Copyright 2006-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.springframework.batch.core;
+package org.springframework.batch.core.listener;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.item.Chunk;
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/JobExecutionListener.java b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/JobExecutionListener.java
similarity index 87%
rename from spring-batch-core/src/main/java/org/springframework/batch/core/JobExecutionListener.java
rename to spring-batch-core/src/main/java/org/springframework/batch/core/listener/JobExecutionListener.java
index bd0c7b6a92..814fd8846a 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/JobExecutionListener.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/JobExecutionListener.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006-2022 the original author or authors.
+ * Copyright 2006-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,7 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.springframework.batch.core;
+package org.springframework.batch.core.listener;
+
+import org.springframework.batch.core.job.Job;
+import org.springframework.batch.core.job.JobExecution;
/**
* Provide callbacks at specific points in the lifecycle of a {@link Job}. Implementations
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/JobListenerFactoryBean.java b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/JobListenerFactoryBean.java
index 76ae37e5f0..87428c364c 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/JobListenerFactoryBean.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/JobListenerFactoryBean.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,8 +15,6 @@
*/
package org.springframework.batch.core.listener;
-import org.springframework.batch.core.JobExecutionListener;
-
/**
* This {@link AbstractListenerFactoryBean} implementation is used to create a
* {@link JobExecutionListener}.
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/JobListenerMetaData.java b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/JobListenerMetaData.java
index 3f5b515502..a268ebcc3d 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/JobListenerMetaData.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/JobListenerMetaData.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2018 the original author or authors.
+ * Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,8 +19,7 @@
import java.util.HashMap;
import java.util.Map;
-import org.springframework.batch.core.JobExecution;
-import org.springframework.batch.core.JobExecutionListener;
+import org.springframework.batch.core.job.JobExecution;
import org.springframework.batch.core.annotation.AfterJob;
import org.springframework.batch.core.annotation.BeforeJob;
import org.springframework.lang.Nullable;
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/JobParameterExecutionContextCopyListener.java b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/JobParameterExecutionContextCopyListener.java
index c26d473ad0..277a712389 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/JobParameterExecutionContextCopyListener.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/JobParameterExecutionContextCopyListener.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006-2021 the original author or authors.
+ * Copyright 2006-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,10 +18,9 @@
import java.util.Arrays;
import java.util.Collection;
-import org.springframework.batch.core.JobParameters;
-import org.springframework.batch.core.Step;
-import org.springframework.batch.core.StepExecution;
-import org.springframework.batch.core.StepExecutionListener;
+import org.springframework.batch.core.job.parameters.JobParameters;
+import org.springframework.batch.core.step.Step;
+import org.springframework.batch.core.step.StepExecution;
import org.springframework.batch.item.ExecutionContext;
/**
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/MethodInvokerMethodInterceptor.java b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/MethodInvokerMethodInterceptor.java
index b6cd083e9a..78a5d81701 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/MethodInvokerMethodInterceptor.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/MethodInvokerMethodInterceptor.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2023 the original author or authors.
+ * Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,7 +21,7 @@
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.batch.core.ExitStatus;
-import org.springframework.batch.core.StepExecutionListener;
+import org.springframework.batch.core.step.StepExecution;
import org.springframework.batch.support.MethodInvoker;
/**
@@ -29,8 +29,7 @@
* will execute all methods tied to a particular method name, with the provided arguments.
* The only possible return value that is handled is of type ExitStatus, since the only
* StepListener implementation that isn't void is
- * {@link StepExecutionListener#afterStep(org.springframework.batch.core.StepExecution)} ,
- * which returns ExitStatus.
+ * {@link StepExecutionListener#afterStep(StepExecution)} , which returns ExitStatus.
*
* @author Lucas Ward
* @author Mahmoud Ben Hassine
@@ -68,12 +67,12 @@ public Object invoke(MethodInvocation invocation) throws Throwable {
ExitStatus status = null;
for (MethodInvoker invoker : invokers) {
Object retVal = invoker.invokeMethod(invocation.getArguments());
- if (retVal instanceof ExitStatus) {
+ if (retVal instanceof ExitStatus exitStatus) {
if (status != null) {
- status = status.and((ExitStatus) retVal);
+ status = status.and(exitStatus);
}
else {
- status = (ExitStatus) retVal;
+ status = exitStatus;
}
}
}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/MulticasterBatchListener.java b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/MulticasterBatchListener.java
index 81db370944..02ef2821fa 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/MulticasterBatchListener.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/MulticasterBatchListener.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006-2023 the original author or authors.
+ * Copyright 2006-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,15 +18,8 @@
import java.lang.reflect.InvocationTargetException;
import java.util.List;
-import org.springframework.batch.core.ChunkListener;
import org.springframework.batch.core.ExitStatus;
-import org.springframework.batch.core.ItemProcessListener;
-import org.springframework.batch.core.ItemReadListener;
-import org.springframework.batch.core.ItemWriteListener;
-import org.springframework.batch.core.SkipListener;
-import org.springframework.batch.core.StepExecution;
-import org.springframework.batch.core.StepExecutionListener;
-import org.springframework.batch.core.StepListener;
+import org.springframework.batch.core.step.StepExecution;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.item.Chunk;
import org.springframework.batch.item.ItemStream;
@@ -78,11 +71,11 @@ public void setListeners(List extends StepListener> listeners) {
* @param listener the {@link StepListener} instance to be registered.
*/
public void register(StepListener listener) {
- if (listener instanceof StepExecutionListener) {
- this.stepListener.register((StepExecutionListener) listener);
+ if (listener instanceof StepExecutionListener stepExecutionListener) {
+ this.stepListener.register(stepExecutionListener);
}
- if (listener instanceof ChunkListener) {
- this.chunkListener.register((ChunkListener) listener);
+ if (listener instanceof ChunkListener cl) {
+ this.chunkListener.register(cl);
}
if (listener instanceof ItemReadListener>) {
@SuppressWarnings("unchecked")
@@ -162,7 +155,7 @@ public ExitStatus afterStep(StepExecution stepExecution) {
}
/**
- * @see org.springframework.batch.core.listener.CompositeStepExecutionListener#beforeStep(org.springframework.batch.core.StepExecution)
+ * @see org.springframework.batch.core.listener.CompositeStepExecutionListener#beforeStep(StepExecution)
*/
@Override
public void beforeStep(StepExecution stepExecution) {
@@ -323,8 +316,8 @@ public void afterChunkError(ChunkContext context) {
*/
private Throwable getTargetException(RuntimeException e) {
Throwable cause = e.getCause();
- if (cause != null && cause instanceof InvocationTargetException) {
- return ((InvocationTargetException) cause).getTargetException();
+ if (cause instanceof InvocationTargetException invocationTargetException) {
+ return invocationTargetException.getTargetException();
}
return e;
}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/SkipListener.java b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/SkipListener.java
similarity index 92%
rename from spring-batch-core/src/main/java/org/springframework/batch/core/SkipListener.java
rename to spring-batch-core/src/main/java/org/springframework/batch/core/listener/SkipListener.java
index 57c79e56dc..64c08b0a03 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/SkipListener.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/SkipListener.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006-2022 the original author or authors.
+ * Copyright 2006-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,7 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.springframework.batch.core;
+package org.springframework.batch.core.listener;
+
+import org.springframework.batch.core.step.Step;
/**
* Interface for listener to skipped items. Callbacks are called by {@link Step}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/SkipListenerSupport.java b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/SkipListenerSupport.java
deleted file mode 100644
index 00001e2ee3..0000000000
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/SkipListenerSupport.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright 2006-2023 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.springframework.batch.core.listener;
-
-import org.springframework.batch.core.SkipListener;
-
-/**
- * Basic no-op implementations of all {@link SkipListener} implementations.
- *
- * @author Dave Syer
- * @author Mahmoud Ben Hassine
- * @deprecated as of v5.0 in favor of the default methods in {@link SkipListener}.
- *
- */
-@Deprecated
-public class SkipListenerSupport implements SkipListener {
-
- @Override
- public void onSkipInRead(Throwable t) {
- }
-
- @Override
- public void onSkipInWrite(S item, Throwable t) {
- }
-
- @Override
- public void onSkipInProcess(T item, Throwable t) {
- }
-
-}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/StepExecutionListener.java b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/StepExecutionListener.java
similarity index 86%
rename from spring-batch-core/src/main/java/org/springframework/batch/core/StepExecutionListener.java
rename to spring-batch-core/src/main/java/org/springframework/batch/core/listener/StepExecutionListener.java
index f1e9a26baf..9c451b417c 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/StepExecutionListener.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/StepExecutionListener.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006-2023 the original author or authors.
+ * Copyright 2006-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,8 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.springframework.batch.core;
+package org.springframework.batch.core.listener;
+import org.springframework.batch.core.ExitStatus;
+import org.springframework.batch.core.step.Step;
+import org.springframework.batch.core.step.StepExecution;
import org.springframework.lang.Nullable;
/**
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/StepExecutionListenerSupport.java b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/StepExecutionListenerSupport.java
deleted file mode 100644
index bd4bedb07f..0000000000
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/StepExecutionListenerSupport.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright 2006-2023 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.springframework.batch.core.listener;
-
-import org.springframework.batch.core.ExitStatus;
-import org.springframework.batch.core.StepExecution;
-import org.springframework.batch.core.StepExecutionListener;
-import org.springframework.lang.Nullable;
-
-/**
- * @author Dave Syer
- * @deprecated as of 5.0, in favor of the default methods on the
- * {@link StepExecutionListener}
- */
-@Deprecated
-public class StepExecutionListenerSupport implements StepExecutionListener {
-
- @Nullable
- @Override
- public ExitStatus afterStep(StepExecution stepExecution) {
- return null;
- }
-
- @Override
- public void beforeStep(StepExecution stepExecution) {
- }
-
-}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/StepListener.java b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/StepListener.java
similarity index 89%
rename from spring-batch-core/src/main/java/org/springframework/batch/core/StepListener.java
rename to spring-batch-core/src/main/java/org/springframework/batch/core/listener/StepListener.java
index 7e12fa48f5..e3282e4901 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/StepListener.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/StepListener.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006-2022 the original author or authors.
+ * Copyright 2006-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.springframework.batch.core;
+package org.springframework.batch.core.listener;
/**
* Marker interface that acts as a parent to all step domain listeners, such as:
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/StepListenerFactoryBean.java b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/StepListenerFactoryBean.java
index ace030474c..196f4ca16a 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/StepListenerFactoryBean.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/StepListenerFactoryBean.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,8 +15,6 @@
*/
package org.springframework.batch.core.listener;
-import org.springframework.batch.core.StepListener;
-
/**
* This {@link AbstractListenerFactoryBean} implementation is used to create a
* {@link StepListener}.
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/StepListenerMetaData.java b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/StepListenerMetaData.java
index 943497828f..4fb5aad1b1 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/StepListenerMetaData.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/StepListenerMetaData.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2023 the original author or authors.
+ * Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,14 +19,7 @@
import java.util.HashMap;
import java.util.Map;
-import org.springframework.batch.core.ChunkListener;
-import org.springframework.batch.core.ItemProcessListener;
-import org.springframework.batch.core.ItemReadListener;
-import org.springframework.batch.core.ItemWriteListener;
-import org.springframework.batch.core.SkipListener;
-import org.springframework.batch.core.StepExecution;
-import org.springframework.batch.core.StepExecutionListener;
-import org.springframework.batch.core.StepListener;
+import org.springframework.batch.core.step.StepExecution;
import org.springframework.batch.core.annotation.AfterChunk;
import org.springframework.batch.core.annotation.AfterChunkError;
import org.springframework.batch.core.annotation.AfterProcess;
@@ -53,6 +46,7 @@
* methods, their interfaces, annotation, and expected arguments.
*
* @author Lucas Ward
+ * @author Hyunsang Han
* @since 2.0
* @see StepListenerFactoryBean
*/
@@ -60,8 +54,8 @@ public enum StepListenerMetaData implements ListenerMetaData {
BEFORE_STEP("beforeStep", "before-step-method", BeforeStep.class, StepExecutionListener.class, StepExecution.class),
AFTER_STEP("afterStep", "after-step-method", AfterStep.class, StepExecutionListener.class, StepExecution.class),
- BEFORE_CHUNK("beforeChunk", "before-chunk-method", BeforeChunk.class, ChunkListener.class, ChunkContext.class),
- AFTER_CHUNK("afterChunk", "after-chunk-method", AfterChunk.class, ChunkListener.class, ChunkContext.class),
+ BEFORE_CHUNK("beforeChunk", "before-chunk-method", BeforeChunk.class, ChunkListener.class, Chunk.class),
+ AFTER_CHUNK("afterChunk", "after-chunk-method", AfterChunk.class, ChunkListener.class, Chunk.class),
AFTER_CHUNK_ERROR("afterChunkError", "after-chunk-error-method", AfterChunkError.class, ChunkListener.class,
ChunkContext.class),
BEFORE_READ("beforeRead", "before-read-method", BeforeRead.class, ItemReadListener.class),
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/StepListenerSupport.java b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/StepListenerSupport.java
index bc10b1d2bd..ca707f9874 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/StepListenerSupport.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/StepListenerSupport.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006-2021 the original author or authors.
+ * Copyright 2006-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,11 +15,6 @@
*/
package org.springframework.batch.core.listener;
-import org.springframework.batch.core.ChunkListener;
-import org.springframework.batch.core.SkipListener;
-import org.springframework.batch.core.StepExecutionListener;
-import org.springframework.batch.core.StepListener;
-
/**
* Basic no-op implementations of all {@link StepListener} interfaces.
*
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/observability/BatchJobContext.java b/spring-batch-core/src/main/java/org/springframework/batch/core/observability/BatchJobContext.java
deleted file mode 100644
index 4c593fd3f0..0000000000
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/observability/BatchJobContext.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright 2022 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.batch.core.observability;
-
-import io.micrometer.observation.Observation;
-
-import org.springframework.batch.core.JobExecution;
-
-import java.util.function.Supplier;
-
-/**
- * Observation context for batch jobs.
- *
- * @author Marcin Grzejszczak
- * @since 5.0
- */
-public class BatchJobContext extends Observation.Context implements Supplier {
-
- private final JobExecution jobExecution;
-
- public BatchJobContext(JobExecution jobExecution) {
- this.jobExecution = jobExecution;
- }
-
- public JobExecution getJobExecution() {
- return jobExecution;
- }
-
- @Override
- public BatchJobContext get() {
- return this;
- }
-
-}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/observability/BatchJobObservation.java b/spring-batch-core/src/main/java/org/springframework/batch/core/observability/BatchJobObservation.java
deleted file mode 100644
index 75132a7458..0000000000
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/observability/BatchJobObservation.java
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * Copyright 2022 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.batch.core.observability;
-
-import io.micrometer.common.docs.KeyName;
-import io.micrometer.observation.docs.ObservationDocumentation;
-
-/**
- * Observation created around a Job execution.
- *
- * @author Marcin Grzejszczak
- * @author Mahmoud Ben Hassine
- * @since 5.0
- */
-public enum BatchJobObservation implements ObservationDocumentation {
-
- BATCH_JOB_OBSERVATION {
- @Override
- public String getName() {
- return "spring.batch.job";
- }
-
- @Override
- public String getContextualName() {
- return "%s";
- }
-
- @Override
- public KeyName[] getLowCardinalityKeyNames() {
- return JobLowCardinalityTags.values();
- }
-
- @Override
- public KeyName[] getHighCardinalityKeyNames() {
- return JobHighCardinalityTags.values();
- }
-
- @Override
- public String getPrefix() {
- return "spring.batch";
- }
- };
-
- enum JobLowCardinalityTags implements KeyName {
-
- /**
- * Name of the Spring Batch job.
- */
- JOB_NAME {
- @Override
- public String asString() {
- return "spring.batch.job.name";
- }
- },
-
- /**
- * Job status.
- */
- JOB_STATUS {
- @Override
- public String asString() {
- return "spring.batch.job.status";
- }
- }
-
- }
-
- enum JobHighCardinalityTags implements KeyName {
-
- /**
- * ID of the Spring Batch job instance.
- */
- JOB_INSTANCE_ID {
- @Override
- public String asString() {
- return "spring.batch.job.instanceId";
- }
- },
-
- /**
- * ID of the Spring Batch job execution.
- */
- JOB_EXECUTION_ID {
- @Override
- public String asString() {
- return "spring.batch.job.executionId";
- }
- }
-
- }
-
-}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/observability/BatchJobObservationConvention.java b/spring-batch-core/src/main/java/org/springframework/batch/core/observability/BatchJobObservationConvention.java
deleted file mode 100644
index 52521f43eb..0000000000
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/observability/BatchJobObservationConvention.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright 2022 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.batch.core.observability;
-
-import io.micrometer.observation.Observation;
-import io.micrometer.observation.ObservationConvention;
-
-/**
- * {@link ObservationConvention} for {@link BatchJobContext}.
- *
- * @author Marcin Grzejszczak
- * @author Mahmoud Ben Hassine
- * @since 5.0
- */
-public interface BatchJobObservationConvention extends ObservationConvention {
-
- @Override
- default boolean supportsContext(Observation.Context context) {
- return context instanceof BatchJobContext;
- }
-
-}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/observability/BatchMetrics.java b/spring-batch-core/src/main/java/org/springframework/batch/core/observability/BatchMetrics.java
index a0d6196b1a..0d6d8b8f1d 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/observability/BatchMetrics.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/observability/BatchMetrics.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2019-2023 the original author or authors.
+ * Copyright 2019-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,31 +17,16 @@
import java.time.Duration;
import java.time.LocalDateTime;
-import java.util.Arrays;
import java.util.concurrent.TimeUnit;
-import io.micrometer.core.instrument.Counter;
-import io.micrometer.core.instrument.LongTaskTimer;
-import io.micrometer.core.instrument.MeterRegistry;
-import io.micrometer.core.instrument.Tag;
-import io.micrometer.core.instrument.Timer;
-import io.micrometer.core.instrument.observation.DefaultMeterObservationHandler;
-import io.micrometer.observation.Observation;
-import io.micrometer.observation.ObservationRegistry;
-
import org.springframework.lang.Nullable;
/**
- * Central class for batch metrics. It provides:
- *
- *
- * - the main entry point to interact with Micrometer's API to create common metrics
- * such as {@link Timer} and {@link LongTaskTimer}.
- * - Some utility methods like calculating durations and formatting them in a human
- * readable format.
- *
- *
- * Only intended for internal use.
+ * Central class for batch metrics. It provides some utility methods like calculating
+ * durations and formatting them in a human-readable format.
+ *
+ * Only intended for internal use.
+ *
*
* @author Mahmoud Ben Hassine
* @author Glenn Renfro
@@ -55,99 +40,11 @@ public final class BatchMetrics {
public static final String STATUS_FAILURE = "FAILURE";
- private BatchMetrics() {
- }
-
- /**
- * Create a {@link Timer}.
- * @param meterRegistry the meter registry to use
- * @param name of the timer. Will be prefixed with
- * {@link BatchMetrics#METRICS_PREFIX}.
- * @param description of the timer
- * @param tags of the timer
- * @return a new timer instance
- */
- public static Timer createTimer(MeterRegistry meterRegistry, String name, String description, Tag... tags) {
- return Timer.builder(METRICS_PREFIX + name)
- .description(description)
- .tags(Arrays.asList(tags))
- .register(meterRegistry);
- }
+ public static final String STATUS_COMMITTED = "COMMITTED";
- /**
- * Create a {@link Counter}.
- * @param meterRegistry the meter registry to use
- * @param name of the counter. Will be prefixed with
- * {@link BatchMetrics#METRICS_PREFIX}.
- * @param description of the counter
- * @param tags of the counter
- * @return a new timer instance
- */
- public static Counter createCounter(MeterRegistry meterRegistry, String name, String description, Tag... tags) {
- return Counter.builder(METRICS_PREFIX + name)
- .description(description)
- .tags(Arrays.asList(tags))
- .register(meterRegistry);
- }
+ public static final String STATUS_ROLLED_BACK = "ROLLED_BACK";
- /**
- * Create a new {@link Observation}. It's not started, you must explicitly call
- * {@link Observation#start()} to start it.
- *
- * Remember to register the {@link DefaultMeterObservationHandler} via the
- * {@code Metrics.globalRegistry.withTimerObservationHandler()} in the user code.
- * Otherwise you won't observe any metrics.
- * @param name of the observation
- * @param context of the batch job observation
- * @return a new observation instance
- * @since 5.0
- */
- public static Observation createObservation(String name, BatchJobContext context,
- ObservationRegistry observationRegistry) {
- return Observation.createNotStarted(name, context, observationRegistry);
- }
-
- /**
- * Create a new {@link Observation}. It's not started, you must explicitly call
- * {@link Observation#start()} to start it.
- *
- * Remember to register the {@link DefaultMeterObservationHandler} via the
- * {@code Metrics.globalRegistry.withTimerObservationHandler()} in the user code.
- * Otherwise you won't observe any metrics.
- * @param name of the observation
- * @param context of the observation step context
- * @return a new observation instance
- * @since 5.0
- */
- public static Observation createObservation(String name, BatchStepContext context,
- ObservationRegistry observationRegistry) {
- return Observation.createNotStarted(name, context, observationRegistry);
- }
-
- /**
- * Create a new {@link Timer.Sample}.
- * @param meterRegistry the meter registry to use
- * @return a new timer sample instance
- */
- public static Timer.Sample createTimerSample(MeterRegistry meterRegistry) {
- return Timer.start(meterRegistry);
- }
-
- /**
- * Create a new {@link LongTaskTimer}.
- * @param meterRegistry the meter registry to use
- * @param name of the long task timer. Will be prefixed with
- * {@link BatchMetrics#METRICS_PREFIX}.
- * @param description of the long task timer.
- * @param tags of the timer
- * @return a new long task timer instance
- */
- public static LongTaskTimer createLongTaskTimer(MeterRegistry meterRegistry, String name, String description,
- Tag... tags) {
- return LongTaskTimer.builder(METRICS_PREFIX + name)
- .description(description)
- .tags(Arrays.asList(tags))
- .register(meterRegistry);
+ private BatchMetrics() {
}
/**
@@ -176,7 +73,7 @@ public static String formatDuration(@Nullable Duration duration) {
StringBuilder formattedDuration = new StringBuilder();
long hours = duration.toHours();
long minutes = duration.toMinutes();
- long seconds = duration.getSeconds();
+ long seconds = duration.toSeconds();
long millis = duration.toMillis();
if (hours != 0) {
formattedDuration.append(hours).append("h");
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/observability/BatchStepContext.java b/spring-batch-core/src/main/java/org/springframework/batch/core/observability/BatchStepContext.java
deleted file mode 100644
index 7b1a3a0bdc..0000000000
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/observability/BatchStepContext.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright 2022 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.batch.core.observability;
-
-import io.micrometer.observation.Observation;
-
-import org.springframework.batch.core.StepExecution;
-
-import java.util.function.Supplier;
-
-/**
- * Observation context for batch steps.
- *
- * @author Marcin Grzejszczak
- * @since 5.0
- */
-public class BatchStepContext extends Observation.Context implements Supplier {
-
- private final StepExecution stepExecution;
-
- public BatchStepContext(StepExecution stepExecution) {
- this.stepExecution = stepExecution;
- }
-
- public StepExecution getStepExecution() {
- return stepExecution;
- }
-
- @Override
- public BatchStepContext get() {
- return this;
- }
-
-}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/observability/BatchStepObservation.java b/spring-batch-core/src/main/java/org/springframework/batch/core/observability/BatchStepObservation.java
deleted file mode 100644
index 7244fe02b8..0000000000
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/observability/BatchStepObservation.java
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * Copyright 2022 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.batch.core.observability;
-
-import io.micrometer.common.docs.KeyName;
-import io.micrometer.observation.docs.ObservationDocumentation;
-
-/**
- * Observation created around a step execution.
- *
- * @author Marcin Grzejszczak
- * @author Mahmoud Ben Hassine
- * @since 5.0
- */
-public enum BatchStepObservation implements ObservationDocumentation {
-
- BATCH_STEP_OBSERVATION {
- @Override
- public String getName() {
- return "spring.batch.step";
- }
-
- @Override
- public String getContextualName() {
- return "%s";
- }
-
- @Override
- public KeyName[] getLowCardinalityKeyNames() {
- return StepLowCardinalityTags.values();
- }
-
- @Override
- public KeyName[] getHighCardinalityKeyNames() {
- return StepHighCardinalityTags.values();
- }
-
- @Override
- public String getPrefix() {
- return "spring.batch";
- }
- };
-
- enum StepLowCardinalityTags implements KeyName {
-
- /**
- * Name of the Spring Batch step.
- */
- STEP_NAME {
- @Override
- public String asString() {
- return "spring.batch.step.name";
- }
- },
-
- /**
- * Type of the Spring Batch step.
- */
- STEP_TYPE {
- @Override
- public String asString() {
- return "spring.batch.step.type";
- }
- },
-
- /**
- * Name of the Spring Batch job enclosing the step.
- */
- JOB_NAME {
- @Override
- public String asString() {
- return "spring.batch.step.job.name";
- }
- },
-
- /**
- * Step status.
- */
- STEP_STATUS {
- @Override
- public String asString() {
- return "spring.batch.step.status";
- }
- }
-
- }
-
- enum StepHighCardinalityTags implements KeyName {
-
- /**
- * ID of the Spring Batch step execution.
- */
- STEP_EXECUTION_ID {
- @Override
- public String asString() {
- return "spring.batch.step.executionId";
- }
- }
-
- }
-
-}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/observability/DefaultBatchJobObservationConvention.java b/spring-batch-core/src/main/java/org/springframework/batch/core/observability/DefaultBatchJobObservationConvention.java
deleted file mode 100644
index 84712acf62..0000000000
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/observability/DefaultBatchJobObservationConvention.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright 2022 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.springframework.batch.core.observability;
-
-import io.micrometer.common.KeyValues;
-
-import org.springframework.batch.core.JobExecution;
-
-/**
- * Default {@link BatchJobObservationConvention} implementation.
- *
- * @author Marcin Grzejszczak
- * @author Mahmoud Ben Hassine
- * @since 5.0
- */
-public class DefaultBatchJobObservationConvention implements BatchJobObservationConvention {
-
- @Override
- public KeyValues getLowCardinalityKeyValues(BatchJobContext context) {
- JobExecution execution = context.getJobExecution();
- return KeyValues.of(
- BatchJobObservation.JobLowCardinalityTags.JOB_NAME.withValue(execution.getJobInstance().getJobName()),
- BatchJobObservation.JobLowCardinalityTags.JOB_STATUS
- .withValue(execution.getExitStatus().getExitCode()));
- }
-
- @Override
- public KeyValues getHighCardinalityKeyValues(BatchJobContext context) {
- JobExecution execution = context.getJobExecution();
- return KeyValues.of(
- BatchJobObservation.JobHighCardinalityTags.JOB_INSTANCE_ID
- .withValue(String.valueOf(execution.getJobInstance().getInstanceId())),
- BatchJobObservation.JobHighCardinalityTags.JOB_EXECUTION_ID
- .withValue(String.valueOf(execution.getId())));
- }
-
-}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/observability/DefaultBatchStepObservationConvention.java b/spring-batch-core/src/main/java/org/springframework/batch/core/observability/DefaultBatchStepObservationConvention.java
deleted file mode 100644
index 6fcf6b0508..0000000000
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/observability/DefaultBatchStepObservationConvention.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright 2022 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.springframework.batch.core.observability;
-
-import io.micrometer.common.KeyValues;
-
-import org.springframework.batch.core.StepExecution;
-
-/**
- * Default {@link BatchStepObservationConvention} implementation.
- *
- * @author Marcin Grzejszczak
- * @author Mahmoud Ben Hassine
- * @since 5.0
- */
-public class DefaultBatchStepObservationConvention implements BatchStepObservationConvention {
-
- @Override
- public KeyValues getLowCardinalityKeyValues(BatchStepContext context) {
- StepExecution execution = context.getStepExecution();
- return KeyValues.of(BatchStepObservation.StepLowCardinalityTags.STEP_NAME.withValue(execution.getStepName()),
- BatchStepObservation.StepLowCardinalityTags.JOB_NAME
- .withValue(execution.getJobExecution().getJobInstance().getJobName()),
- BatchStepObservation.StepLowCardinalityTags.STEP_STATUS
- .withValue(execution.getExitStatus().getExitCode()));
- }
-
- @Override
- public KeyValues getHighCardinalityKeyValues(BatchStepContext context) {
- StepExecution execution = context.getStepExecution();
- return KeyValues.of(BatchStepObservation.StepHighCardinalityTags.STEP_EXECUTION_ID
- .withValue(String.valueOf(execution.getId())));
- }
-
-}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/job/JobExecutionEvent.java b/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/job/JobExecutionEvent.java
new file mode 100644
index 0000000000..416c524444
--- /dev/null
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/job/JobExecutionEvent.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2025-present the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.batch.core.observability.jfr.events.job;
+
+import jdk.jfr.Category;
+import jdk.jfr.Description;
+import jdk.jfr.Event;
+import jdk.jfr.Label;
+
+@Label("Job Execution")
+@Description("Job Execution Event")
+@Category({ "Spring Batch", "Job" })
+public class JobExecutionEvent extends Event {
+
+ @Label("Job Name")
+ public String jobName;
+
+ @Label("Job Instance Id")
+ public long jobInstanceId;
+
+ @Label("Job Execution Id")
+ public long jobExecutionId;
+
+ @Label("Job Exit Status")
+ public String exitStatus;
+
+ public JobExecutionEvent(String jobName, long jobInstanceId, long jobExecutionId) {
+ this.jobName = jobName;
+ this.jobInstanceId = jobInstanceId;
+ this.jobExecutionId = jobExecutionId;
+ }
+
+}
\ No newline at end of file
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/job/JobLaunchEvent.java b/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/job/JobLaunchEvent.java
new file mode 100644
index 0000000000..269d099196
--- /dev/null
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/job/JobLaunchEvent.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2025-present the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.batch.core.observability.jfr.events.job;
+
+import jdk.jfr.Category;
+import jdk.jfr.Description;
+import jdk.jfr.Event;
+import jdk.jfr.Label;
+
+@Label("Job Launch Request")
+@Description("Job Launch Request Event")
+@Category({ "Spring Batch", "Job" })
+public class JobLaunchEvent extends Event {
+
+ @Label("Job Name")
+ public String jobName;
+
+ @Label("Job Parameters")
+ public String jobParameters;
+
+ public JobLaunchEvent(String jobName, String jobParameters) {
+ this.jobParameters = jobParameters;
+ this.jobName = jobName;
+ }
+
+}
\ No newline at end of file
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/step/StepExecutionEvent.java b/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/step/StepExecutionEvent.java
new file mode 100644
index 0000000000..6e7784fc79
--- /dev/null
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/step/StepExecutionEvent.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2025-present the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.batch.core.observability.jfr.events.step;
+
+import jdk.jfr.Category;
+import jdk.jfr.Description;
+import jdk.jfr.Event;
+import jdk.jfr.Label;
+
+@Label("Step Execution")
+@Description("Step Execution Event")
+@Category({ "Spring Batch", "Step" })
+public class StepExecutionEvent extends Event {
+
+ @Label("Step Name")
+ public String stepName;
+
+ @Label("Job Name")
+ public String jobName;
+
+ @Label("Step Execution Id")
+ public long stepExecutionId;
+
+ @Label("Job Execution Id")
+ public long jobExecutionId;
+
+ @Label("Step Exit Status")
+ public String exitStatus;
+
+ public StepExecutionEvent(String stepName, String jobName, long stepExecutionId, long jobExecutionId) {
+ this.stepName = stepName;
+ this.jobName = jobName;
+ this.stepExecutionId = stepExecutionId;
+ this.jobExecutionId = jobExecutionId;
+ }
+
+}
\ No newline at end of file
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/step/chunk/ChunkScanEvent.java b/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/step/chunk/ChunkScanEvent.java
new file mode 100644
index 0000000000..d5d11e7d1b
--- /dev/null
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/step/chunk/ChunkScanEvent.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2025-present the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.batch.core.observability.jfr.events.step.chunk;
+
+import jdk.jfr.Category;
+import jdk.jfr.Description;
+import jdk.jfr.Event;
+import jdk.jfr.Label;
+
+@Label("Chunk Scan")
+@Description("Chunk Scan Event")
+@Category({ "Spring Batch", "Step", "Chunk" })
+public class ChunkScanEvent extends Event {
+
+ @Label("Step Name")
+ public String stepName;
+
+ @Label("Step Execution Id")
+ public long stepExecutionId;
+
+ @Label("Skip Count")
+ public long skipCount;
+
+ public ChunkScanEvent(String stepName, long stepExecutionId) {
+ this.stepName = stepName;
+ this.stepExecutionId = stepExecutionId;
+ }
+
+}
\ No newline at end of file
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/step/chunk/ChunkTransactionEvent.java b/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/step/chunk/ChunkTransactionEvent.java
new file mode 100644
index 0000000000..695f3afcfa
--- /dev/null
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/step/chunk/ChunkTransactionEvent.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2025-present the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.batch.core.observability.jfr.events.step.chunk;
+
+import jdk.jfr.Category;
+import jdk.jfr.Description;
+import jdk.jfr.Event;
+import jdk.jfr.Label;
+
+@Label("Chunk Transaction")
+@Description("Chunk Transaction Event")
+@Category({ "Spring Batch", "Step", "Chunk" })
+public class ChunkTransactionEvent extends Event {
+
+ @Label("Step Name")
+ public String stepName;
+
+ @Label("Step Execution Id")
+ public long stepExecutionId;
+
+ @Label("Transaction Status")
+ public String transactionStatus;
+
+ public ChunkTransactionEvent(String stepName, long stepExecutionId) {
+ this.stepName = stepName;
+ this.stepExecutionId = stepExecutionId;
+ }
+
+}
\ No newline at end of file
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/step/chunk/ChunkWriteEvent.java b/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/step/chunk/ChunkWriteEvent.java
new file mode 100644
index 0000000000..6139abb60b
--- /dev/null
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/step/chunk/ChunkWriteEvent.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2025-present the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.batch.core.observability.jfr.events.step.chunk;
+
+import jdk.jfr.Category;
+import jdk.jfr.Description;
+import jdk.jfr.Event;
+import jdk.jfr.Label;
+
+@Label("Chunk Write")
+@Description("Chunk Write Event")
+@Category({ "Spring Batch", "Step", "Chunk" })
+public class ChunkWriteEvent extends Event {
+
+ @Label("Step Name")
+ public String stepName;
+
+ @Label("Step Execution Id")
+ public long stepExecutionId;
+
+ @Label("Chunk Write Status")
+ public String chunkWriteStatus;
+
+ @Label("Item Count")
+ public long itemCount;
+
+ public ChunkWriteEvent(String stepName, long stepExecutionId, long itemCount) {
+ this.itemCount = itemCount;
+ this.stepName = stepName;
+ this.stepExecutionId = stepExecutionId;
+ }
+
+}
\ No newline at end of file
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/step/chunk/ItemProcessEvent.java b/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/step/chunk/ItemProcessEvent.java
new file mode 100644
index 0000000000..358794dcff
--- /dev/null
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/step/chunk/ItemProcessEvent.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2025-present the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.batch.core.observability.jfr.events.step.chunk;
+
+import jdk.jfr.Category;
+import jdk.jfr.Description;
+import jdk.jfr.Event;
+import jdk.jfr.Label;
+
+@Label("Item Process")
+@Description("Item Process Event")
+@Category({ "Spring Batch", "Step", "Chunk" })
+public class ItemProcessEvent extends Event {
+
+ @Label("Step Name")
+ public String stepName;
+
+ @Label("Step Execution Id")
+ public long stepExecutionId;
+
+ @Label("Item Process Status")
+ public String itemProcessStatus;
+
+ public ItemProcessEvent(String stepName, long stepExecutionId) {
+ this.stepName = stepName;
+ this.stepExecutionId = stepExecutionId;
+ }
+
+}
\ No newline at end of file
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/step/chunk/ItemReadEvent.java b/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/step/chunk/ItemReadEvent.java
new file mode 100644
index 0000000000..5e55c0de3d
--- /dev/null
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/step/chunk/ItemReadEvent.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2025-present the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.batch.core.observability.jfr.events.step.chunk;
+
+import jdk.jfr.Category;
+import jdk.jfr.Description;
+import jdk.jfr.Event;
+import jdk.jfr.Label;
+
+@Label("Item Read")
+@Description("Item Read Event")
+@Category({ "Spring Batch", "Step", "Chunk" })
+public class ItemReadEvent extends Event {
+
+ @Label("Step Name")
+ public String stepName;
+
+ @Label("Step Execution Id")
+ public long stepExecutionId;
+
+ @Label("Item Read Status")
+ public String itemReadStatus;
+
+ public ItemReadEvent(String stepName, long stepExecutionId) {
+ this.stepName = stepName;
+ this.stepExecutionId = stepExecutionId;
+ }
+
+}
\ No newline at end of file
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/step/partition/PartitionAggregateEvent.java b/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/step/partition/PartitionAggregateEvent.java
new file mode 100644
index 0000000000..f516b08de1
--- /dev/null
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/step/partition/PartitionAggregateEvent.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2025-present the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.batch.core.observability.jfr.events.step.partition;
+
+import jdk.jfr.Category;
+import jdk.jfr.Description;
+import jdk.jfr.Event;
+import jdk.jfr.Label;
+
+@Label("Partition Aggregate")
+@Description("Partition Aggregate Event")
+@Category({ "Spring Batch", "Step", "Partition" })
+public class PartitionAggregateEvent extends Event {
+
+ @Label("Step Name")
+ public String stepName;
+
+ @Label("Step Execution Id")
+ public long stepExecutionId;
+
+ public PartitionAggregateEvent(String stepName, long stepExecutionId) {
+ this.stepName = stepName;
+ this.stepExecutionId = stepExecutionId;
+ }
+
+}
\ No newline at end of file
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/step/partition/PartitionSplitEvent.java b/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/step/partition/PartitionSplitEvent.java
new file mode 100644
index 0000000000..26504edc2f
--- /dev/null
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/step/partition/PartitionSplitEvent.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2025-present the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.batch.core.observability.jfr.events.step.partition;
+
+import jdk.jfr.Category;
+import jdk.jfr.Description;
+import jdk.jfr.Event;
+import jdk.jfr.Label;
+
+@Label("Partition Split")
+@Description("Partition Split Event")
+@Category({ "Spring Batch", "Step", "Partition" })
+public class PartitionSplitEvent extends Event {
+
+ @Label("Step Name")
+ public String stepName;
+
+ @Label("Step Execution Id")
+ public long stepExecutionId;
+
+ @Label("Partition count")
+ public long partitionCount;
+
+ public PartitionSplitEvent(String stepName, long stepExecutionId) {
+ this.stepName = stepName;
+ this.stepExecutionId = stepExecutionId;
+ }
+
+}
\ No newline at end of file
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/step/tasklet/TaskletExecutionEvent.java b/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/step/tasklet/TaskletExecutionEvent.java
new file mode 100644
index 0000000000..97fbb7b6d7
--- /dev/null
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/step/tasklet/TaskletExecutionEvent.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2025-present the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.batch.core.observability.jfr.events.step.tasklet;
+
+import jdk.jfr.Category;
+import jdk.jfr.Description;
+import jdk.jfr.Event;
+import jdk.jfr.Label;
+
+@Label("Tasklet Execution")
+@Description("Tasklet Execution Event")
+@Category({ "Spring Batch", "Step", "Tasklet" })
+public class TaskletExecutionEvent extends Event {
+
+ @Label("Step Name")
+ public String stepName;
+
+ @Label("Step Execution Id")
+ public long stepExecutionId;
+
+ @Label("Tasklet Type")
+ public String taskletType;
+
+ @Label("Tasklet Status")
+ public String taskletStatus;
+
+ public TaskletExecutionEvent(String stepName, long stepExecutionId, String taskletType) {
+ this.taskletType = taskletType;
+ this.stepName = stepName;
+ this.stepExecutionId = stepExecutionId;
+ }
+
+}
\ No newline at end of file
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/observability/micrometer/MicrometerMetrics.java b/spring-batch-core/src/main/java/org/springframework/batch/core/observability/micrometer/MicrometerMetrics.java
new file mode 100644
index 0000000000..cd0d7e9ff9
--- /dev/null
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/observability/micrometer/MicrometerMetrics.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2025-present the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.batch.core.observability.micrometer;
+
+import java.util.Arrays;
+
+import io.micrometer.core.instrument.Counter;
+import io.micrometer.core.instrument.LongTaskTimer;
+import io.micrometer.core.instrument.MeterRegistry;
+import io.micrometer.core.instrument.Tag;
+import io.micrometer.core.instrument.Timer;
+import io.micrometer.observation.Observation;
+import io.micrometer.observation.ObservationRegistry;
+
+import org.springframework.batch.core.observability.BatchMetrics;
+
+/**
+ * Central class for Micrometer metrics. Only intended for internal use.
+ *
+ * @author Mahmoud Ben Hassine
+ * @since 6.0
+ */
+public final class MicrometerMetrics {
+
+ private MicrometerMetrics() {
+ }
+
+ /**
+ * Create a new {@link Observation}. It's not started, you must explicitly call
+ * {@link Observation#start()} to start it.
+ * @param name of the observation
+ * @param observationRegistry the observation registry to use
+ * @return a new observation instance
+ * @since 6.0
+ */
+ public static Observation createObservation(String name, ObservationRegistry observationRegistry) {
+ return Observation.createNotStarted(name, observationRegistry);
+ }
+
+ /**
+ * Create a {@link Timer}.
+ * @param meterRegistry the meter registry to use
+ * @param name of the timer. Will be prefixed with
+ * {@link BatchMetrics#METRICS_PREFIX}.
+ * @param description of the timer
+ * @param tags of the timer
+ * @return a new timer instance
+ */
+ public static Timer createTimer(MeterRegistry meterRegistry, String name, String description, Tag... tags) {
+ return Timer.builder(BatchMetrics.METRICS_PREFIX + name)
+ .description(description)
+ .tags(Arrays.asList(tags))
+ .register(meterRegistry);
+ }
+
+ /**
+ * Create a {@link Counter}.
+ * @param meterRegistry the meter registry to use
+ * @param name of the counter. Will be prefixed with
+ * {@link BatchMetrics#METRICS_PREFIX}.
+ * @param description of the counter
+ * @param tags of the counter
+ * @return a new timer instance
+ */
+ public static Counter createCounter(MeterRegistry meterRegistry, String name, String description, Tag... tags) {
+ return Counter.builder(BatchMetrics.METRICS_PREFIX + name)
+ .description(description)
+ .tags(Arrays.asList(tags))
+ .register(meterRegistry);
+ }
+
+ /**
+ * Create a new {@link Timer.Sample}.
+ * @param meterRegistry the meter registry to use
+ * @return a new timer sample instance
+ */
+ public static Timer.Sample createTimerSample(MeterRegistry meterRegistry) {
+ return Timer.start(meterRegistry);
+ }
+
+ /**
+ * Create a new {@link LongTaskTimer}.
+ * @param meterRegistry the meter registry to use
+ * @param name of the long task timer. Will be prefixed with
+ * {@link BatchMetrics#METRICS_PREFIX}.
+ * @param description of the long task timer.
+ * @param tags of the timer
+ * @return a new long task timer instance
+ */
+ public static LongTaskTimer createLongTaskTimer(MeterRegistry meterRegistry, String name, String description,
+ Tag... tags) {
+ return LongTaskTimer.builder(BatchMetrics.METRICS_PREFIX + name)
+ .description(description)
+ .tags(Arrays.asList(tags))
+ .register(meterRegistry);
+ }
+
+}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/partition/PartitionHandler.java b/spring-batch-core/src/main/java/org/springframework/batch/core/partition/PartitionHandler.java
index 81373f9cae..648bc46473 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/partition/PartitionHandler.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/partition/PartitionHandler.java
@@ -18,7 +18,7 @@
import java.util.Collection;
-import org.springframework.batch.core.StepExecution;
+import org.springframework.batch.core.step.StepExecution;
import org.springframework.batch.item.ExecutionContext;
/**
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/partition/support/PartitionNameProvider.java b/spring-batch-core/src/main/java/org/springframework/batch/core/partition/PartitionNameProvider.java
similarity index 87%
rename from spring-batch-core/src/main/java/org/springframework/batch/core/partition/support/PartitionNameProvider.java
rename to spring-batch-core/src/main/java/org/springframework/batch/core/partition/PartitionNameProvider.java
index 6198b3a1d9..745111eb57 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/partition/support/PartitionNameProvider.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/partition/PartitionNameProvider.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006-2009 the original author or authors.
+ * Copyright 2006-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,9 @@
* limitations under the License.
*/
-package org.springframework.batch.core.partition.support;
+package org.springframework.batch.core.partition;
+
+import org.springframework.batch.core.partition.support.SimplePartitioner;
import java.util.Collection;
@@ -33,6 +35,7 @@
*
*
* @author Dave Syer
+ * @author Mahmoud Ben Hassine
* @since 2.1.3
*
*/
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/partition/support/PartitionStep.java b/spring-batch-core/src/main/java/org/springframework/batch/core/partition/PartitionStep.java
similarity index 73%
rename from spring-batch-core/src/main/java/org/springframework/batch/core/partition/support/PartitionStep.java
rename to spring-batch-core/src/main/java/org/springframework/batch/core/partition/PartitionStep.java
index 2b84033c87..4cf74fb1f0 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/partition/support/PartitionStep.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/partition/PartitionStep.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006-2022 the original author or authors.
+ * Copyright 2006-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,14 +14,16 @@
* limitations under the License.
*/
-package org.springframework.batch.core.partition.support;
+package org.springframework.batch.core.partition;
import org.springframework.batch.core.BatchStatus;
-import org.springframework.batch.core.JobExecutionException;
-import org.springframework.batch.core.Step;
-import org.springframework.batch.core.StepExecution;
-import org.springframework.batch.core.partition.PartitionHandler;
-import org.springframework.batch.core.partition.StepExecutionSplitter;
+import org.springframework.batch.core.job.JobExecutionException;
+import org.springframework.batch.core.observability.jfr.events.step.partition.PartitionAggregateEvent;
+import org.springframework.batch.core.observability.jfr.events.step.partition.PartitionSplitEvent;
+import org.springframework.batch.core.repository.JobRepository;
+import org.springframework.batch.core.step.Step;
+import org.springframework.batch.core.step.StepExecution;
+import org.springframework.batch.core.partition.support.DefaultStepExecutionAggregator;
import org.springframework.batch.core.step.AbstractStep;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.util.Assert;
@@ -44,6 +46,15 @@ public class PartitionStep extends AbstractStep {
private StepExecutionAggregator stepExecutionAggregator = new DefaultStepExecutionAggregator();
+ /**
+ * Create a new instance of a {@link PartitionStep} with the given job repository.
+ * @param jobRepository the job repository to use. Must not be null.
+ * @since 6.0
+ */
+ public PartitionStep(JobRepository jobRepository) {
+ super(jobRepository);
+ }
+
/**
* A {@link PartitionHandler} which can send out step executions for remote processing
* and bring back the results.
@@ -98,10 +109,21 @@ public void afterPropertiesSet() throws Exception {
protected void doExecute(StepExecution stepExecution) throws Exception {
stepExecution.getExecutionContext().put(STEP_TYPE_KEY, this.getClass().getName());
- // Wait for task completion and then aggregate the results
+ // Split execution into partitions and wait for task completion
+ PartitionSplitEvent partitionSplitEvent = new PartitionSplitEvent(stepExecution.getStepName(),
+ stepExecution.getId());
+ partitionSplitEvent.begin();
Collection executions = partitionHandler.handle(stepExecutionSplitter, stepExecution);
+ partitionSplitEvent.partitionCount = executions.size();
stepExecution.upgradeStatus(BatchStatus.COMPLETED);
+ partitionSplitEvent.commit();
+
+ // aggregate the results of the executions
+ PartitionAggregateEvent partitionAggregateEvent = new PartitionAggregateEvent(stepExecution.getStepName(),
+ stepExecution.getId());
+ partitionAggregateEvent.begin();
stepExecutionAggregator.aggregate(stepExecution, executions);
+ partitionAggregateEvent.commit();
// If anything failed or had a problem we need to crap out
if (stepExecution.getStatus().isUnsuccessful()) {
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/partition/support/Partitioner.java b/spring-batch-core/src/main/java/org/springframework/batch/core/partition/Partitioner.java
similarity index 91%
rename from spring-batch-core/src/main/java/org/springframework/batch/core/partition/support/Partitioner.java
rename to spring-batch-core/src/main/java/org/springframework/batch/core/partition/Partitioner.java
index 2df66d1adb..5943450deb 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/partition/support/Partitioner.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/partition/Partitioner.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006-2022 the original author or authors.
+ * Copyright 2006-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.batch.core.partition.support;
+package org.springframework.batch.core.partition;
import java.util.Map;
@@ -28,6 +28,7 @@
*
* @author Dave Syer
* @author Taeik Lim
+ * @author Mahmoud Ben Hassine
* @since 2.0
*/
@FunctionalInterface
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/partition/support/StepExecutionAggregator.java b/spring-batch-core/src/main/java/org/springframework/batch/core/partition/StepExecutionAggregator.java
similarity index 78%
rename from spring-batch-core/src/main/java/org/springframework/batch/core/partition/support/StepExecutionAggregator.java
rename to spring-batch-core/src/main/java/org/springframework/batch/core/partition/StepExecutionAggregator.java
index bffa64ade0..892d8df05e 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/partition/support/StepExecutionAggregator.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/partition/StepExecutionAggregator.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2008-2022 the original author or authors.
+ * Copyright 2008-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,18 +13,19 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.springframework.batch.core.partition.support;
+package org.springframework.batch.core.partition;
import java.util.Collection;
-import org.springframework.batch.core.StepExecution;
+import org.springframework.batch.core.step.StepExecution;
/**
- * Strategy for a aggregating step executions, usually when they are the result of
+ * Strategy for aggregating step executions, usually when they are the result of
* partitioned or remote execution.
*
* @author Dave Syer
* @author Taeik Lim
+ * @author Mahmoud Ben Hassine
* @since 2.1
*
*/
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/partition/StepExecutionSplitter.java b/spring-batch-core/src/main/java/org/springframework/batch/core/partition/StepExecutionSplitter.java
index 0b5e83f952..394effa2af 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/partition/StepExecutionSplitter.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/partition/StepExecutionSplitter.java
@@ -16,9 +16,9 @@
package org.springframework.batch.core.partition;
-import org.springframework.batch.core.JobExecution;
-import org.springframework.batch.core.JobExecutionException;
-import org.springframework.batch.core.StepExecution;
+import org.springframework.batch.core.job.JobExecution;
+import org.springframework.batch.core.job.JobExecutionException;
+import org.springframework.batch.core.step.StepExecution;
import java.util.Set;
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/partition/support/AbstractPartitionHandler.java b/spring-batch-core/src/main/java/org/springframework/batch/core/partition/support/AbstractPartitionHandler.java
index 0d4538511e..0d7692eba1 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/partition/support/AbstractPartitionHandler.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/partition/support/AbstractPartitionHandler.java
@@ -18,16 +18,15 @@
import java.util.Collection;
import java.util.Set;
-import org.springframework.batch.core.StepExecution;
+import org.springframework.batch.core.step.StepExecution;
import org.springframework.batch.core.partition.PartitionHandler;
import org.springframework.batch.core.partition.StepExecutionSplitter;
/**
* Base {@link PartitionHandler} implementation providing common base features. Subclasses
- * are expected to implement only the
- * {@link #doHandle(org.springframework.batch.core.StepExecution, java.util.Set)} method
- * which returns with the result of the execution(s) or an exception if the step failed to
- * process.
+ * are expected to implement only the {@link #doHandle(StepExecution, java.util.Set)}
+ * method which returns with the result of the execution(s) or an exception if the step
+ * failed to process.
*
* @author Sebastien Gerard
* @author Dave Syer
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/partition/support/DefaultStepExecutionAggregator.java b/spring-batch-core/src/main/java/org/springframework/batch/core/partition/support/DefaultStepExecutionAggregator.java
index 27ba91b018..1f0f4fe75b 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/partition/support/DefaultStepExecutionAggregator.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/partition/support/DefaultStepExecutionAggregator.java
@@ -18,7 +18,8 @@
import org.springframework.batch.core.BatchStatus;
import org.springframework.batch.core.ExitStatus;
-import org.springframework.batch.core.StepExecution;
+import org.springframework.batch.core.step.StepExecution;
+import org.springframework.batch.core.partition.StepExecutionAggregator;
import org.springframework.util.Assert;
import java.util.Collection;
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/partition/support/MultiResourcePartitioner.java b/spring-batch-core/src/main/java/org/springframework/batch/core/partition/support/MultiResourcePartitioner.java
index 32cfe6f066..6ed5de1c3a 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/partition/support/MultiResourcePartitioner.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/partition/support/MultiResourcePartitioner.java
@@ -20,6 +20,7 @@
import java.util.HashMap;
import java.util.Map;
+import org.springframework.batch.core.partition.Partitioner;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.core.io.Resource;
import org.springframework.util.Assert;
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/partition/support/RemoteStepExecutionAggregator.java b/spring-batch-core/src/main/java/org/springframework/batch/core/partition/support/RemoteStepExecutionAggregator.java
index 8f3b5f4f59..b603017e38 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/partition/support/RemoteStepExecutionAggregator.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/partition/support/RemoteStepExecutionAggregator.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006-2023 the original author or authors.
+ * Copyright 2006-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,9 +21,10 @@
import java.util.Set;
import java.util.stream.Collectors;
-import org.springframework.batch.core.JobExecution;
-import org.springframework.batch.core.StepExecution;
-import org.springframework.batch.core.explore.JobExplorer;
+import org.springframework.batch.core.job.JobExecution;
+import org.springframework.batch.core.step.StepExecution;
+import org.springframework.batch.core.partition.StepExecutionAggregator;
+import org.springframework.batch.core.repository.JobRepository;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;
@@ -38,7 +39,7 @@ public class RemoteStepExecutionAggregator implements StepExecutionAggregator, I
private StepExecutionAggregator delegate = new DefaultStepExecutionAggregator();
- private JobExplorer jobExplorer;
+ private JobRepository jobRepository;
/**
* Create a new instance (useful for configuration purposes).
@@ -47,20 +48,20 @@ public RemoteStepExecutionAggregator() {
}
/**
- * Create a new instance with a job explorer that can be used to refresh the data when
- * aggregating.
- * @param jobExplorer the {@link JobExplorer} to use
+ * Create a new instance with a job repository that can be used to refresh the data
+ * when aggregating.
+ * @param jobRepository the {@link JobRepository} to use
*/
- public RemoteStepExecutionAggregator(JobExplorer jobExplorer) {
+ public RemoteStepExecutionAggregator(JobRepository jobRepository) {
super();
- this.jobExplorer = jobExplorer;
+ this.jobRepository = jobRepository;
}
/**
- * @param jobExplorer the jobExplorer to set
+ * @param jobRepository the jobRepository to set
*/
- public void setJobExplorer(JobExplorer jobExplorer) {
- this.jobExplorer = jobExplorer;
+ public void setJobRepository(JobRepository jobRepository) {
+ this.jobRepository = jobRepository;
}
/**
@@ -75,13 +76,13 @@ public void setDelegate(StepExecutionAggregator delegate) {
*/
@Override
public void afterPropertiesSet() throws Exception {
- Assert.state(jobExplorer != null, "A JobExplorer must be provided");
+ Assert.state(jobRepository != null, "A JobRepository must be provided");
}
/**
* Aggregates the input executions into the result {@link StepExecution} delegating to
* the delegate aggregator once the input has been refreshed from the
- * {@link JobExplorer}.
+ * {@link JobRepository}.
*
* @see StepExecutionAggregator #aggregate(StepExecution, Collection)
*/
@@ -96,7 +97,7 @@ public void aggregate(StepExecution result, Collection executions
Assert.state(id != null, "StepExecution has null id. It must be saved first: " + stepExecution);
return id;
}).collect(Collectors.toSet());
- JobExecution jobExecution = jobExplorer.getJobExecution(result.getJobExecutionId());
+ JobExecution jobExecution = jobRepository.getJobExecution(result.getJobExecutionId());
Assert.state(jobExecution != null,
"Could not load JobExecution from JobRepository for id " + result.getJobExecutionId());
List updates = jobExecution.getStepExecutions()
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/partition/support/SimplePartitioner.java b/spring-batch-core/src/main/java/org/springframework/batch/core/partition/support/SimplePartitioner.java
index 9e3ebbaa10..6f7230225f 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/partition/support/SimplePartitioner.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/partition/support/SimplePartitioner.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006-2013 the original author or authors.
+ * Copyright 2006-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,13 +19,14 @@
import java.util.HashMap;
import java.util.Map;
+import org.springframework.batch.core.partition.Partitioner;
import org.springframework.batch.item.ExecutionContext;
/**
* Simplest possible implementation of {@link Partitioner}. Just creates a set of empty
* {@link ExecutionContext} instances, and labels them as
* {partition0, partition1, ..., partitionN}
, where N
is the
- * grid size.
+ * grid size - 1.
*
* @author Dave Syer
* @since 2.0
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/partition/support/SimpleStepExecutionSplitter.java b/spring-batch-core/src/main/java/org/springframework/batch/core/partition/support/SimpleStepExecutionSplitter.java
index 699e95fc12..c31f85b62f 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/partition/support/SimpleStepExecutionSplitter.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/partition/support/SimpleStepExecutionSplitter.java
@@ -24,11 +24,13 @@
import java.util.Set;
import org.springframework.batch.core.BatchStatus;
-import org.springframework.batch.core.JobExecution;
-import org.springframework.batch.core.JobExecutionException;
-import org.springframework.batch.core.JobInstance;
-import org.springframework.batch.core.Step;
-import org.springframework.batch.core.StepExecution;
+import org.springframework.batch.core.job.JobExecution;
+import org.springframework.batch.core.job.JobExecutionException;
+import org.springframework.batch.core.job.JobInstance;
+import org.springframework.batch.core.step.Step;
+import org.springframework.batch.core.step.StepExecution;
+import org.springframework.batch.core.partition.PartitionNameProvider;
+import org.springframework.batch.core.partition.Partitioner;
import org.springframework.batch.core.partition.StepExecutionSplitter;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.item.ExecutionContext;
@@ -191,9 +193,9 @@ private Map getContexts(StepExecution stepExecution, i
result = partitioner.partition(splitSize);
}
else {
- if (partitioner instanceof PartitionNameProvider) {
+ if (partitioner instanceof PartitionNameProvider partitionNameProvider) {
result = new HashMap<>();
- Collection names = ((PartitionNameProvider) partitioner).getPartitionNames(splitSize);
+ Collection names = partitionNameProvider.getPartitionNames(splitSize);
for (String name : names) {
/*
* We need to return the same keys as the original (failed) execution,
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/partition/support/TaskExecutorPartitionHandler.java b/spring-batch-core/src/main/java/org/springframework/batch/core/partition/support/TaskExecutorPartitionHandler.java
index 25f55aa78c..bc0ff6d1a7 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/partition/support/TaskExecutorPartitionHandler.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/partition/support/TaskExecutorPartitionHandler.java
@@ -23,8 +23,8 @@
import org.springframework.batch.core.BatchStatus;
import org.springframework.batch.core.ExitStatus;
-import org.springframework.batch.core.Step;
-import org.springframework.batch.core.StepExecution;
+import org.springframework.batch.core.step.Step;
+import org.springframework.batch.core.step.StepExecution;
import org.springframework.batch.core.partition.PartitionHandler;
import org.springframework.batch.core.step.StepHolder;
import org.springframework.beans.factory.InitializingBean;
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/JobExecutionAlreadyRunningException.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/JobExecutionAlreadyRunningException.java
index a2f682896e..43384a9902 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/JobExecutionAlreadyRunningException.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/JobExecutionAlreadyRunningException.java
@@ -15,7 +15,7 @@
*/
package org.springframework.batch.core.repository;
-import org.springframework.batch.core.JobExecutionException;
+import org.springframework.batch.core.job.JobExecutionException;
/**
* @author Dave Syer
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/JobInstanceAlreadyCompleteException.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/JobInstanceAlreadyCompleteException.java
index 577ae8ad13..59dd702f70 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/JobInstanceAlreadyCompleteException.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/JobInstanceAlreadyCompleteException.java
@@ -15,7 +15,7 @@
*/
package org.springframework.batch.core.repository;
-import org.springframework.batch.core.JobExecutionException;
+import org.springframework.batch.core.job.JobExecutionException;
/**
* An exception indicating an illegal attempt to restart a job that was already completed
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/JobRepository.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/JobRepository.java
index b8db9253b2..0fbf671699 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/JobRepository.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/JobRepository.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006-2023 the original author or authors.
+ * Copyright 2006-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,12 +16,14 @@
package org.springframework.batch.core.repository;
-import org.springframework.batch.core.Job;
-import org.springframework.batch.core.JobExecution;
-import org.springframework.batch.core.JobInstance;
-import org.springframework.batch.core.JobParameters;
-import org.springframework.batch.core.Step;
-import org.springframework.batch.core.StepExecution;
+import org.springframework.batch.core.job.Job;
+import org.springframework.batch.core.job.JobExecution;
+import org.springframework.batch.core.job.JobInstance;
+import org.springframework.batch.core.job.parameters.JobParameters;
+import org.springframework.batch.core.launch.NoSuchJobException;
+import org.springframework.batch.core.step.Step;
+import org.springframework.batch.core.step.StepExecution;
+import org.springframework.batch.core.repository.explore.JobExplorer;
import org.springframework.batch.core.repository.dao.JobExecutionDao;
import org.springframework.batch.core.repository.dao.JobInstanceDao;
import org.springframework.batch.item.ExecutionContext;
@@ -31,6 +33,7 @@
import java.util.Collection;
import java.util.Collections;
import java.util.List;
+import java.util.Set;
/**
*
@@ -48,51 +51,209 @@
* @author Mahmoud Ben Hassine
* @author Parikshit Dutta
*/
-public interface JobRepository {
+@SuppressWarnings("removal")
+public interface JobRepository extends JobExplorer {
+
+ /*
+ * ===================================================================================
+ * Read operations
+ * ===================================================================================
+ */
+
+ /*
+ * ===================================================================================
+ * Job operations
+ * ===================================================================================
+ */
/**
- * Retrieve the names of all job instances sorted alphabetically - i.e. jobs that have
- * ever been executed.
- * @return the names of all job instances
- * @since 5.0
+ * Query the repository for all unique {@link JobInstance} names (sorted
+ * alphabetically).
+ * @return the list of job names that have been executed.
*/
default List getJobNames() {
return Collections.emptyList();
}
+ /*
+ * ===================================================================================
+ * Job instance operations
+ * ===================================================================================
+ */
+
/**
- * Fetch the last job instances with the provided name, sorted backwards by primary
- * key, using a 'like' criteria
- * @param jobName {@link String} containing the name of the job.
- * @param start int containing the offset of where list of job instances results
- * should begin.
- * @param count int containing the number of job instances to return.
- * @return a list of {@link JobInstance} for the job name requested.
- * @since 5.0
+ * Fetch {@link JobInstance} values in descending order of creation (and, therefore,
+ * usually, of first execution).
+ * @param jobName The name of the job to query.
+ * @param start The start index of the instances to return.
+ * @param count The maximum number of instances to return.
+ * @return the {@link JobInstance} values up to a maximum of count values.
*/
- default List findJobInstancesByName(String jobName, int start, int count) {
+ default List getJobInstances(String jobName, int start, int count) {
return Collections.emptyList();
}
/**
- * Return all {@link JobExecution}s for given {@link JobInstance}, sorted backwards by
- * creation order (so the first element is the most recent).
- * @param jobInstance parent {@link JobInstance} of the {@link JobExecution}s to find.
- * @return {@link List} containing JobExecutions for the jobInstance.
+ * @param instanceId {@link Long} The ID for the {@link JobInstance} to obtain.
+ * @return the {@code JobInstance} that has this ID, or {@code null} if not found.
+ */
+ @Nullable
+ default JobInstance getJobInstance(@Nullable Long instanceId) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Find the last job instance, by ID, for the given job.
+ * @param jobName The name of the job.
+ * @return the last job instance by Id if any or {@code null} otherwise.
+ *
+ * @since 4.2
+ */
+ @Nullable
+ default JobInstance getLastJobInstance(String jobName) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * @param jobName {@link String} name of the job.
+ * @param jobParameters {@link JobParameters} parameters for the job instance.
+ * @return the {@link JobInstance} with the given name and parameters, or
+ * {@code null}.
+ *
* @since 5.0
*/
- default List findJobExecutions(JobInstance jobInstance) {
+ @Nullable
+ default JobInstance getJobInstance(String jobName, JobParameters jobParameters) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Query the repository for the number of unique {@link JobInstance} objects
+ * associated with the supplied job name.
+ * @param jobName The name of the job for which to query.
+ * @return the number of {@link JobInstance}s that exist within the associated job
+ * repository.
+ * @throws NoSuchJobException thrown when there is no {@link JobInstance} for the
+ * jobName specified.
+ */
+ default long getJobInstanceCount(@Nullable String jobName) throws NoSuchJobException {
+ throw new UnsupportedOperationException();
+ }
+
+ /*
+ * ===================================================================================
+ * Job execution operations
+ * ===================================================================================
+ */
+
+ /**
+ * Retrieve a {@link JobExecution} by its ID. The complete object graph for this
+ * execution should be returned (unless otherwise indicated), including the parent
+ * {@link JobInstance} and associated {@link ExecutionContext} and
+ * {@link StepExecution} instances (also including their execution contexts).
+ * @param executionId The job execution ID.
+ * @return the {@link JobExecution} that has this ID or {@code null} if not found.
+ */
+ @Nullable
+ default JobExecution getJobExecution(@Nullable Long executionId) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Retrieve job executions by their job instance. The corresponding step executions
+ * may not be fully hydrated (for example, their execution context may be missing),
+ * depending on the implementation. In that case, use
+ * {@link #getStepExecution(Long, Long)} to hydrate them.
+ * @param jobInstance The {@link JobInstance} to query.
+ * @return the list of all executions for the specified {@link JobInstance}.
+ */
+ default List getJobExecutions(JobInstance jobInstance) {
return Collections.emptyList();
}
/**
- * Check if an instance of this job already exists with the parameters provided.
- * @param jobName the name of the job
- * @param jobParameters the parameters to match
- * @return true if a {@link JobInstance} already exists for this job name and job
- * parameters
+ * Find the last {@link JobExecution} that has been created for a given
+ * {@link JobInstance}.
+ * @param jobInstance The {@code JobInstance} for which to find the last
+ * {@code JobExecution}.
+ * @return the last {@code JobExecution} that has been created for this instance or
+ * {@code null} if no job execution is found for the given job instance.
+ *
+ * @since 4.2
+ */
+ @Nullable
+ default JobExecution getLastJobExecution(JobInstance jobInstance) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * @param jobName the name of the job that might have run
+ * @param jobParameters parameters identifying the {@link JobInstance}
+ * @return the last execution of job if exists, null otherwise
+ */
+ @Nullable
+ default JobExecution getLastJobExecution(String jobName, JobParameters jobParameters) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Retrieve running job executions. The corresponding step executions may not be fully
+ * hydrated (for example, their execution context may be missing), depending on the
+ * implementation. In that case, use {@link #getStepExecution(Long, Long)} to hydrate
+ * them.
+ * @param jobName The name of the job.
+ * @return the set of running executions for jobs with the specified name.
+ */
+ default Set findRunningJobExecutions(@Nullable String jobName) {
+ return Collections.emptySet();
+ }
+
+ /*
+ * ===================================================================================
+ * Step execution operations
+ * ===================================================================================
+ */
+
+ /**
+ * Retrieve a {@link StepExecution} by its ID and parent {@link JobExecution} ID. The
+ * execution context for the step should be available in the result, and the parent
+ * job execution should have its primitive properties, but it may not contain the job
+ * instance information.
+ * @param jobExecutionId The parent job execution ID.
+ * @param stepExecutionId The step execution ID.
+ * @return the {@link StepExecution} that has this ID or {@code null} if not found.
+ *
+ * @see #getJobExecution(Long)
+ */
+ @Nullable
+ default StepExecution getStepExecution(@Nullable Long jobExecutionId, @Nullable Long stepExecutionId) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * @param jobInstance {@link JobInstance} instance containing the step executions.
+ * @param stepName the name of the step execution that might have run.
+ * @return the last execution of step for the given job instance.
+ */
+ @Nullable
+ default StepExecution getLastStepExecution(JobInstance jobInstance, String stepName) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * @param jobInstance {@link JobInstance} instance containing the step executions.
+ * @param stepName the name of the step execution that might have run.
+ * @return the execution count of the step within the given job instance.
+ */
+ default long getStepExecutionCount(JobInstance jobInstance, String stepName) {
+ throw new UnsupportedOperationException();
+ }
+
+ /*
+ * ===================================================================================
+ * Write operations
+ * ===================================================================================
*/
- boolean isJobInstanceExists(String jobName, JobParameters jobParameters);
/**
* Create a new {@link JobInstance} with the name and job parameters provided.
@@ -187,42 +348,6 @@ JobExecution createJobExecution(String jobName, JobParameters jobParameters)
*/
void updateExecutionContext(JobExecution jobExecution);
- /**
- * @param jobName {@link String} name of the job.
- * @param jobParameters {@link JobParameters} parameters for the job instance.
- * @return the {@link JobInstance} with the given name and parameters, or
- * {@code null}.
- *
- * @since 5.0
- */
- @Nullable
- default JobInstance getJobInstance(String jobName, JobParameters jobParameters) {
- throw new UnsupportedOperationException();
- }
-
- /**
- * @param jobInstance {@link JobInstance} instance containing the step executions.
- * @param stepName the name of the step execution that might have run.
- * @return the last execution of step for the given job instance.
- */
- @Nullable
- StepExecution getLastStepExecution(JobInstance jobInstance, String stepName);
-
- /**
- * @param jobInstance {@link JobInstance} instance containing the step executions.
- * @param stepName the name of the step execution that might have run.
- * @return the execution count of the step within the given job instance.
- */
- long getStepExecutionCount(JobInstance jobInstance, String stepName);
-
- /**
- * @param jobName the name of the job that might have run
- * @param jobParameters parameters identifying the {@link JobInstance}
- * @return the last execution of job if exists, null otherwise
- */
- @Nullable
- JobExecution getLastJobExecution(String jobName, JobParameters jobParameters);
-
/**
* Delete the step execution along with its execution context.
* @param stepExecution the step execution to delete
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/JobRestartException.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/JobRestartException.java
index 4a33ee182e..21ec468b3e 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/JobRestartException.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/JobRestartException.java
@@ -15,7 +15,7 @@
*/
package org.springframework.batch.core.repository;
-import org.springframework.batch.core.JobExecutionException;
+import org.springframework.batch.core.job.JobExecutionException;
/**
* An exception indicating an illegal attempt to restart a job.
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/ExecutionContextDao.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/ExecutionContextDao.java
index c27b7b264f..53921956e4 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/ExecutionContextDao.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/ExecutionContextDao.java
@@ -18,8 +18,8 @@
import java.util.Collection;
-import org.springframework.batch.core.JobExecution;
-import org.springframework.batch.core.StepExecution;
+import org.springframework.batch.core.job.JobExecution;
+import org.springframework.batch.core.step.StepExecution;
import org.springframework.batch.item.ExecutionContext;
/**
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/Jackson2ExecutionContextStringSerializer.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/Jackson2ExecutionContextStringSerializer.java
index 9890c09853..813a7756d5 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/Jackson2ExecutionContextStringSerializer.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/Jackson2ExecutionContextStringSerializer.java
@@ -55,8 +55,8 @@
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
-import org.springframework.batch.core.JobParameter;
-import org.springframework.batch.core.JobParameters;
+import org.springframework.batch.core.job.parameters.JobParameter;
+import org.springframework.batch.core.job.parameters.JobParameters;
import org.springframework.batch.core.repository.ExecutionContextSerializer;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.Assert;
@@ -173,7 +173,8 @@ private JobParametersModule() {
addSerializer(JobParameter.class, new JobParameterSerializer(JobParameter.class));
}
- private abstract class JobParametersMixIn {
+ @SuppressWarnings("unused")
+ private abstract static class JobParametersMixIn {
@JsonIgnore
abstract boolean isEmpty();
@@ -183,7 +184,7 @@ private abstract class JobParametersMixIn {
}
- private class JobParameterSerializer extends StdSerializer {
+ private static class JobParameterSerializer extends StdSerializer {
protected JobParameterSerializer(Class type) {
super(type);
@@ -303,8 +304,9 @@ static class TrustedTypeIdResolver implements TypeIdResolver {
"java.lang.Byte", "java.lang.Short", "java.lang.Integer", "java.lang.Long", "java.lang.Double",
"java.lang.Float", "java.math.BigDecimal", "java.math.BigInteger", "java.lang.String",
"java.lang.Character", "java.lang.CharSequence", "java.util.Properties", "[Ljava.util.Properties;",
- "org.springframework.batch.core.JobParameter", "org.springframework.batch.core.JobParameters",
- "java.util.concurrent.ConcurrentHashMap", "java.sql.Date");
+ "org.springframework.batch.core.job.parameters.JobParameter",
+ "org.springframework.batch.core.job.parameters.JobParameters", "java.util.concurrent.ConcurrentHashMap",
+ "java.sql.Date");
private final Set trustedClassNames = new LinkedHashSet<>(TRUSTED_CLASS_NAMES);
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JobExecutionDao.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JobExecutionDao.java
index ec8da0c7a6..4bdb677018 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JobExecutionDao.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JobExecutionDao.java
@@ -19,8 +19,8 @@
import java.util.List;
import java.util.Set;
-import org.springframework.batch.core.JobExecution;
-import org.springframework.batch.core.JobInstance;
+import org.springframework.batch.core.job.JobExecution;
+import org.springframework.batch.core.job.JobInstance;
import org.springframework.lang.Nullable;
/**
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JobInstanceDao.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JobInstanceDao.java
index 4c2ac43be4..581e02c00d 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JobInstanceDao.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JobInstanceDao.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006-2023 the original author or authors.
+ * Copyright 2006-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,9 +18,9 @@
import java.util.List;
-import org.springframework.batch.core.JobExecution;
-import org.springframework.batch.core.JobInstance;
-import org.springframework.batch.core.JobParameters;
+import org.springframework.batch.core.job.JobExecution;
+import org.springframework.batch.core.job.JobInstance;
+import org.springframework.batch.core.job.parameters.JobParameters;
import org.springframework.batch.core.launch.NoSuchJobException;
import org.springframework.lang.Nullable;
@@ -117,7 +117,10 @@ default JobInstance getLastJobInstance(String jobName) {
* should begin.
* @param count int containing the number of job instances to return.
* @return a list of {@link JobInstance} for the job name requested.
+ * @deprecated Since v6.0 and scheduled for removal in v6.2. Use
+ * {@link #getJobInstances(String, int, int)}
*/
+ @Deprecated(forRemoval = true)
List findJobInstancesByName(String jobName, int start, int count);
/**
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/StepExecutionDao.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/StepExecutionDao.java
index 58e43bd8ef..5bdc678471 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/StepExecutionDao.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/StepExecutionDao.java
@@ -18,9 +18,9 @@
import java.util.Collection;
-import org.springframework.batch.core.JobExecution;
-import org.springframework.batch.core.JobInstance;
-import org.springframework.batch.core.StepExecution;
+import org.springframework.batch.core.job.JobExecution;
+import org.springframework.batch.core.job.JobInstance;
+import org.springframework.batch.core.step.StepExecution;
import org.springframework.lang.Nullable;
public interface StepExecutionDao {
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JdbcExecutionContextDao.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/jdbc/JdbcExecutionContextDao.java
similarity index 90%
rename from spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JdbcExecutionContextDao.java
rename to spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/jdbc/JdbcExecutionContextDao.java
index 07915965c0..e585661a80 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JdbcExecutionContextDao.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/jdbc/JdbcExecutionContextDao.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006-2024 the original author or authors.
+ * Copyright 2006-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.batch.core.repository.dao;
+package org.springframework.batch.core.repository.dao.jdbc;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
@@ -27,21 +27,22 @@
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
-import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
+import java.util.stream.Stream;
-import org.springframework.batch.core.JobExecution;
-import org.springframework.batch.core.StepExecution;
+import org.springframework.batch.core.job.JobExecution;
+import org.springframework.batch.core.step.StepExecution;
import org.springframework.batch.core.repository.ExecutionContextSerializer;
+import org.springframework.batch.core.repository.dao.AbstractJdbcBatchMetadataDao;
+import org.springframework.batch.core.repository.dao.DefaultExecutionContextSerializer;
+import org.springframework.batch.core.repository.dao.ExecutionContextDao;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.core.serializer.Serializer;
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.jdbc.core.RowMapper;
-import org.springframework.jdbc.support.lob.DefaultLobHandler;
-import org.springframework.jdbc.support.lob.LobHandler;
import org.springframework.lang.NonNull;
import org.springframework.util.Assert;
@@ -57,6 +58,7 @@
* @author Michael Minella
* @author David Turanski
* @author Mahmoud Ben Hassine
+ * @author Yanming Zhou
*/
public class JdbcExecutionContextDao extends AbstractJdbcBatchMetadataDao implements ExecutionContextDao {
@@ -110,8 +112,6 @@ public class JdbcExecutionContextDao extends AbstractJdbcBatchMetadataDao implem
private int shortContextLength = DEFAULT_MAX_VARCHAR_LENGTH;
- private LobHandler lobHandler = new DefaultLobHandler();
-
private ExecutionContextSerializer serializer = new DefaultExecutionContextSerializer();
private final Lock lock = new ReentrantLock();
@@ -154,13 +154,9 @@ public ExecutionContext getExecutionContext(JobExecution jobExecution) {
Long executionId = jobExecution.getId();
Assert.notNull(executionId, "ExecutionId must not be null.");
- List results = getJdbcTemplate().query(getQuery(FIND_JOB_EXECUTION_CONTEXT),
- new ExecutionContextRowMapper(), executionId);
- if (!results.isEmpty()) {
- return results.get(0);
- }
- else {
- return new ExecutionContext();
+ try (Stream stream = getJdbcTemplate().queryForStream(getQuery(FIND_JOB_EXECUTION_CONTEXT),
+ new ExecutionContextRowMapper(), executionId)) {
+ return stream.findFirst().orElseGet(ExecutionContext::new);
}
}
@@ -169,13 +165,9 @@ public ExecutionContext getExecutionContext(StepExecution stepExecution) {
Long executionId = stepExecution.getId();
Assert.notNull(executionId, "ExecutionId must not be null.");
- List results = getJdbcTemplate().query(getQuery(FIND_STEP_EXECUTION_CONTEXT),
- new ExecutionContextRowMapper(), executionId);
- if (results.size() > 0) {
- return results.get(0);
- }
- else {
- return new ExecutionContext();
+ try (Stream stream = getJdbcTemplate().queryForStream(getQuery(FIND_STEP_EXECUTION_CONTEXT),
+ new ExecutionContextRowMapper(), executionId)) {
+ return stream.findFirst().orElseGet(ExecutionContext::new);
}
}
@@ -268,15 +260,6 @@ public void deleteExecutionContext(StepExecution stepExecution) {
getJdbcTemplate().update(getQuery(DELETE_STEP_EXECUTION_CONTEXT), stepExecution.getId());
}
- /**
- * @deprecated Since 5.2 with no replacement. Scheduled for removal in v6
- * @param lobHandler the lob handler to use
- */
- @Deprecated(since = "5.2.0", forRemoval = true)
- public void setLobHandler(LobHandler lobHandler) {
- this.lobHandler = lobHandler;
- }
-
@Override
public void afterPropertiesSet() throws Exception {
super.afterPropertiesSet();
@@ -306,7 +289,7 @@ private void persistSerializedContext(final Long executionId, String serializedC
getJdbcTemplate().update(getQuery(sql), ps -> {
ps.setString(1, shortContext);
if (longContext != null) {
- lobHandler.getLobCreator().setClobAsString(ps, 2, longContext);
+ ps.setString(2, longContext);
}
else {
ps.setNull(2, getClobTypeToUse());
@@ -342,7 +325,7 @@ public void setValues(PreparedStatement ps, int i) throws SQLException {
}
ps.setString(1, shortContext);
if (longContext != null) {
- lobHandler.getLobCreator().setClobAsString(ps, 2, longContext);
+ ps.setString(2, longContext);
}
else {
ps.setNull(2, getClobTypeToUse());
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JdbcJobExecutionDao.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/jdbc/JdbcJobExecutionDao.java
similarity index 90%
rename from spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JdbcJobExecutionDao.java
rename to spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/jdbc/JdbcJobExecutionDao.java
index 9ec0a9e2d8..012f42982f 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JdbcJobExecutionDao.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/jdbc/JdbcJobExecutionDao.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006-2023 the original author or authors.
+ * Copyright 2006-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.batch.core.repository.dao;
+package org.springframework.batch.core.repository.dao.jdbc;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
@@ -28,16 +28,17 @@
import java.util.Set;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
+import java.util.stream.Stream;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.batch.core.BatchStatus;
import org.springframework.batch.core.ExitStatus;
-import org.springframework.batch.core.JobExecution;
-import org.springframework.batch.core.JobInstance;
-import org.springframework.batch.core.JobParameter;
-import org.springframework.batch.core.JobParameters;
+import org.springframework.batch.core.job.JobExecution;
+import org.springframework.batch.core.job.JobInstance;
+import org.springframework.batch.core.job.parameters.JobParameter;
+import org.springframework.batch.core.job.parameters.JobParameters;
import org.springframework.batch.core.converter.DateToStringConverter;
import org.springframework.batch.core.converter.LocalDateTimeToStringConverter;
import org.springframework.batch.core.converter.LocalDateToStringConverter;
@@ -46,6 +47,9 @@
import org.springframework.batch.core.converter.StringToLocalDateConverter;
import org.springframework.batch.core.converter.StringToLocalDateTimeConverter;
import org.springframework.batch.core.converter.StringToLocalTimeConverter;
+import org.springframework.batch.core.repository.dao.AbstractJdbcBatchMetadataDao;
+import org.springframework.batch.core.repository.dao.JobExecutionDao;
+import org.springframework.batch.core.repository.dao.NoSuchObjectException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.convert.support.ConfigurableConversionService;
import org.springframework.core.convert.support.DefaultConversionService;
@@ -74,6 +78,7 @@
* @author Dimitrios Liapis
* @author Philippe Marschall
* @author Jinwoo Bae
+ * @author Yanming Zhou
*/
public class JdbcJobExecutionDao extends AbstractJdbcBatchMetadataDao implements JobExecutionDao, InitializingBean {
@@ -98,28 +103,22 @@ SELECT COUNT(*)
private static final String UPDATE_JOB_EXECUTION = """
UPDATE %PREFIX%JOB_EXECUTION
- SET START_TIME = ?, END_TIME = ?, STATUS = ?, EXIT_CODE = ?, EXIT_MESSAGE = ?, VERSION = ?, CREATE_TIME = ?, LAST_UPDATED = ?
+ SET START_TIME = ?, END_TIME = ?, STATUS = ?, EXIT_CODE = ?, EXIT_MESSAGE = ?, VERSION = VERSION + 1, CREATE_TIME = ?, LAST_UPDATED = ?
WHERE JOB_EXECUTION_ID = ? AND VERSION = ?
""";
- private static final String FIND_JOB_EXECUTIONS = """
+ private static final String GET_JOB_EXECUTIONS = """
SELECT JOB_EXECUTION_ID, START_TIME, END_TIME, STATUS, EXIT_CODE, EXIT_MESSAGE, CREATE_TIME, LAST_UPDATED, VERSION
FROM %PREFIX%JOB_EXECUTION
- WHERE JOB_INSTANCE_ID = ?
- ORDER BY JOB_EXECUTION_ID DESC
""";
- private static final String GET_LAST_EXECUTION = """
- SELECT JOB_EXECUTION_ID, START_TIME, END_TIME, STATUS, EXIT_CODE, EXIT_MESSAGE, CREATE_TIME, LAST_UPDATED, VERSION
- FROM %PREFIX%JOB_EXECUTION E
- WHERE JOB_INSTANCE_ID = ? AND JOB_EXECUTION_ID IN (SELECT MAX(JOB_EXECUTION_ID) FROM %PREFIX%JOB_EXECUTION E2 WHERE E2.JOB_INSTANCE_ID = ?)
- """;
+ private static final String FIND_JOB_EXECUTIONS = GET_JOB_EXECUTIONS
+ + " WHERE JOB_INSTANCE_ID = ? ORDER BY JOB_EXECUTION_ID DESC";
- private static final String GET_EXECUTION_BY_ID = """
- SELECT JOB_EXECUTION_ID, START_TIME, END_TIME, STATUS, EXIT_CODE, EXIT_MESSAGE, CREATE_TIME, LAST_UPDATED, VERSION
- FROM %PREFIX%JOB_EXECUTION
- WHERE JOB_EXECUTION_ID = ?
- """;
+ private static final String GET_LAST_EXECUTION = GET_JOB_EXECUTIONS
+ + " WHERE JOB_INSTANCE_ID = ? AND JOB_EXECUTION_ID IN (SELECT MAX(JOB_EXECUTION_ID) FROM %PREFIX%JOB_EXECUTION E2 WHERE E2.JOB_INSTANCE_ID = ?)";
+
+ private static final String GET_EXECUTION_BY_ID = GET_JOB_EXECUTIONS + " WHERE JOB_EXECUTION_ID = ?";
private static final String GET_RUNNING_EXECUTIONS = """
SELECT E.JOB_EXECUTION_ID, E.START_TIME, E.END_TIME, E.STATUS, E.EXIT_CODE, E.EXIT_MESSAGE, E.CREATE_TIME, E.LAST_UPDATED, E.VERSION, E.JOB_INSTANCE_ID
@@ -146,7 +145,7 @@ SELECT COUNT(*)
private static final String DELETE_JOB_EXECUTION = """
DELETE FROM %PREFIX%JOB_EXECUTION
- WHERE JOB_EXECUTION_ID = ?
+ WHERE JOB_EXECUTION_ID = ? AND VERSION = ?
""";
private static final String DELETE_JOB_EXECUTION_PARAMETERS = """
@@ -284,7 +283,6 @@ public void updateJobExecution(JobExecution jobExecution) {
this.lock.lock();
try {
- Integer version = jobExecution.getVersion() + 1;
String exitDescription = jobExecution.getExitStatus().getExitDescription();
if (exitDescription != null && exitDescription.length() > exitMessageLength) {
@@ -301,7 +299,7 @@ public void updateJobExecution(JobExecution jobExecution) {
Timestamp lastUpdated = jobExecution.getLastUpdated() == null ? null
: Timestamp.valueOf(jobExecution.getLastUpdated());
Object[] parameters = new Object[] { startTime, endTime, jobExecution.getStatus().toString(),
- jobExecution.getExitStatus().getExitCode(), exitDescription, version, createTime, lastUpdated,
+ jobExecution.getExitStatus().getExitCode(), exitDescription, createTime, lastUpdated,
jobExecution.getId(), jobExecution.getVersion() };
// Check if given JobExecution's Id already exists, if none is found
@@ -315,7 +313,7 @@ public void updateJobExecution(JobExecution jobExecution) {
int count = getJdbcTemplate().update(getQuery(UPDATE_JOB_EXECUTION), parameters,
new int[] { Types.TIMESTAMP, Types.TIMESTAMP, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR,
- Types.INTEGER, Types.TIMESTAMP, Types.TIMESTAMP, Types.BIGINT, Types.INTEGER });
+ Types.TIMESTAMP, Types.TIMESTAMP, Types.BIGINT, Types.INTEGER });
// Avoid concurrent modifications...
if (count == 0) {
@@ -339,16 +337,9 @@ public JobExecution getLastJobExecution(JobInstance jobInstance) {
Long id = jobInstance.getId();
- List executions = getJdbcTemplate().query(getQuery(GET_LAST_EXECUTION),
- new JobExecutionRowMapper(jobInstance), id, id);
-
- Assert.state(executions.size() <= 1, "There must be at most one latest job execution");
-
- if (executions.isEmpty()) {
- return null;
- }
- else {
- return executions.get(0);
+ try (Stream stream = getJdbcTemplate().queryForStream(getQuery(GET_LAST_EXECUTION),
+ new JobExecutionRowMapper(jobInstance), id, id)) {
+ return stream.findFirst().orElse(null);
}
}
@@ -395,7 +386,13 @@ public void synchronizeStatus(JobExecution jobExecution) {
*/
@Override
public void deleteJobExecution(JobExecution jobExecution) {
- getJdbcTemplate().update(getQuery(DELETE_JOB_EXECUTION), jobExecution.getId());
+ int count = getJdbcTemplate().update(getQuery(DELETE_JOB_EXECUTION), jobExecution.getId(),
+ jobExecution.getVersion());
+
+ if (count == 0) {
+ throw new OptimisticLockingFailureException("Attempt to delete job execution id=" + jobExecution.getId()
+ + " with wrong version (" + jobExecution.getVersion() + ")");
+ }
}
/**
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JdbcJobInstanceDao.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/jdbc/JdbcJobInstanceDao.java
similarity index 79%
rename from spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JdbcJobInstanceDao.java
rename to spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/jdbc/JdbcJobInstanceDao.java
index 27dcc8b7a2..6e39d21584 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JdbcJobInstanceDao.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/jdbc/JdbcJobInstanceDao.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006-2023 the original author or authors.
+ * Copyright 2006-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,23 +14,27 @@
* limitations under the License.
*/
-package org.springframework.batch.core.repository.dao;
+package org.springframework.batch.core.repository.dao.jdbc;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.ArrayList;
import java.util.List;
+import java.util.stream.Stream;
-import org.springframework.batch.core.DefaultJobKeyGenerator;
-import org.springframework.batch.core.JobExecution;
-import org.springframework.batch.core.JobInstance;
-import org.springframework.batch.core.JobKeyGenerator;
-import org.springframework.batch.core.JobParameters;
+import org.springframework.batch.core.job.DefaultJobKeyGenerator;
+import org.springframework.batch.core.job.JobExecution;
+import org.springframework.batch.core.job.JobInstance;
+import org.springframework.batch.core.job.JobKeyGenerator;
+import org.springframework.batch.core.job.parameters.JobParameters;
import org.springframework.batch.core.launch.NoSuchJobException;
+import org.springframework.batch.core.repository.dao.AbstractJdbcBatchMetadataDao;
+import org.springframework.batch.core.repository.dao.JobInstanceDao;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.EmptyResultDataAccessException;
+import org.springframework.dao.OptimisticLockingFailureException;
import org.springframework.jdbc.core.ResultSetExtractor;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.support.incrementer.DataFieldMaxValueIncrementer;
@@ -53,11 +57,14 @@
* @author Will Schipp
* @author Mahmoud Ben Hassine
* @author Parikshit Dutta
+ * @author Yanming Zhou
*/
public class JdbcJobInstanceDao extends AbstractJdbcBatchMetadataDao implements JobInstanceDao, InitializingBean {
+ @SuppressWarnings("unused")
private static final String STAR_WILDCARD = "*";
+ @SuppressWarnings("unused")
private static final String SQL_WILDCARD = "%";
private static final String CREATE_JOB_INSTANCE = """
@@ -71,7 +78,7 @@ public class JdbcJobInstanceDao extends AbstractJdbcBatchMetadataDao implements
WHERE JOB_NAME = ?
""";
- private static final String FIND_JOBS_WITH_KEY = FIND_JOBS_WITH_NAME + " and JOB_KEY = ?";
+ private static final String FIND_JOBS_WITH_KEY = FIND_JOBS_WITH_NAME + " AND JOB_KEY = ?";
private static final String COUNT_JOBS_WITH_NAME = """
SELECT COUNT(*)
@@ -79,11 +86,8 @@ SELECT COUNT(*)
WHERE JOB_NAME = ?
""";
- private static final String FIND_JOBS_WITH_EMPTY_KEY = """
- SELECT JOB_INSTANCE_ID, JOB_NAME
- FROM %PREFIX%JOB_INSTANCE
- WHERE JOB_NAME = ? AND (JOB_KEY = ? OR JOB_KEY IS NULL)
- """;
+ private static final String FIND_JOBS_WITH_EMPTY_KEY = FIND_JOBS_WITH_NAME
+ + " AND (JOB_KEY = ? OR JOB_KEY IS NULL)";
private static final String GET_JOB_FROM_ID = """
SELECT JOB_INSTANCE_ID, JOB_NAME, JOB_KEY, VERSION
@@ -106,7 +110,7 @@ SELECT COUNT(*)
private static final String FIND_LAST_JOBS_BY_NAME = """
SELECT JOB_INSTANCE_ID, JOB_NAME
FROM %PREFIX%JOB_INSTANCE
- WHERE JOB_NAME = ?
+ WHERE JOB_NAME LIKE ?
ORDER BY JOB_INSTANCE_ID DESC
""";
@@ -116,20 +120,14 @@ SELECT COUNT(*)
WHERE I1.JOB_NAME = ? AND I1.JOB_INSTANCE_ID = (SELECT MAX(I2.JOB_INSTANCE_ID) FROM %PREFIX%JOB_INSTANCE I2 WHERE I2.JOB_NAME = ?)
""";
- private static final String FIND_LAST_JOBS_LIKE_NAME = """
- SELECT JOB_INSTANCE_ID, JOB_NAME
- FROM %PREFIX%JOB_INSTANCE
- WHERE JOB_NAME LIKE ? ORDER BY JOB_INSTANCE_ID DESC
- """;
-
private static final String DELETE_JOB_INSTANCE = """
DELETE FROM %PREFIX%JOB_INSTANCE
- WHERE JOB_INSTANCE_ID = ?
+ WHERE JOB_INSTANCE_ID = ? AND VERSION = ?
""";
private DataFieldMaxValueIncrementer jobInstanceIncrementer;
- private JobKeyGenerator jobKeyGenerator = new DefaultJobKeyGenerator();
+ private JobKeyGenerator jobKeyGenerator = new DefaultJobKeyGenerator();
/**
* In this JDBC implementation a job instance id is obtained by asking the
@@ -178,21 +176,12 @@ public JobInstance getJobInstance(final String jobName, final JobParameters jobP
RowMapper rowMapper = new JobInstanceRowMapper();
- List instances;
- if (StringUtils.hasLength(jobKey)) {
- instances = getJdbcTemplate().query(getQuery(FIND_JOBS_WITH_KEY), rowMapper, jobName, jobKey);
- }
- else {
- instances = getJdbcTemplate().query(getQuery(FIND_JOBS_WITH_EMPTY_KEY), rowMapper, jobName, jobKey);
+ try (Stream stream = getJdbcTemplate().queryForStream(
+ getQuery(StringUtils.hasLength(jobKey) ? FIND_JOBS_WITH_KEY : FIND_JOBS_WITH_EMPTY_KEY), rowMapper,
+ jobName, jobKey)) {
+ return stream.findFirst().orElse(null);
}
- if (instances.isEmpty()) {
- return null;
- }
- else {
- Assert.state(instances.size() == 1, "instance count must be 1 but was " + instances.size());
- return instances.get(0);
- }
}
@Override
@@ -236,6 +225,10 @@ public List extractData(ResultSet rs) throws SQLException, DataAcce
};
+ if (jobName.contains(STAR_WILDCARD)) {
+ jobName = jobName.replaceAll("\\" + STAR_WILDCARD, SQL_WILDCARD);
+ }
+
return getJdbcTemplate().query(getQuery(FIND_LAST_JOBS_BY_NAME), extractor, jobName);
}
@@ -281,18 +274,13 @@ public long getJobInstanceCount(@Nullable String jobName) throws NoSuchJobExcept
*/
@Override
public void deleteJobInstance(JobInstance jobInstance) {
- getJdbcTemplate().update(getQuery(DELETE_JOB_INSTANCE), jobInstance.getId());
- }
+ int count = getJdbcTemplate().update(getQuery(DELETE_JOB_INSTANCE), jobInstance.getId(),
+ jobInstance.getVersion());
- /**
- * Setter for {@link DataFieldMaxValueIncrementer} to be used when generating primary
- * keys for {@link JobInstance} instances.
- * @param jobIncrementer the {@link DataFieldMaxValueIncrementer}
- * @deprecated as of v5.0 in favor of using the {@link #setJobInstanceIncrementer}
- */
- @Deprecated
- public void setJobIncrementer(DataFieldMaxValueIncrementer jobIncrementer) {
- this.setJobInstanceIncrementer(jobIncrementer);
+ if (count == 0) {
+ throw new OptimisticLockingFailureException("Attempt to delete job instance id=" + jobInstance.getId()
+ + " with wrong version (" + jobInstance.getVersion() + ")");
+ }
}
/**
@@ -343,33 +331,15 @@ public JobInstance mapRow(ResultSet rs, int rowNum) throws SQLException {
}
+ /**
+ * @deprecated since v6.0 and scheduled for removal in v6.2. Use
+ * {@link #getJobInstances(String, int, int)} instead.
+ */
+ @SuppressWarnings("removal")
+ @Deprecated(forRemoval = true)
@Override
- @SuppressWarnings({ "rawtypes", "unchecked" })
public List findJobInstancesByName(String jobName, final int start, final int count) {
- ResultSetExtractor extractor = new ResultSetExtractor() {
- private final List list = new ArrayList<>();
-
- @Override
- public Object extractData(ResultSet rs) throws SQLException, DataAccessException {
- int rowNum = 0;
- while (rowNum < start && rs.next()) {
- rowNum++;
- }
- while (rowNum < start + count && rs.next()) {
- RowMapper rowMapper = new JobInstanceRowMapper();
- list.add(rowMapper.mapRow(rs, rowNum));
- rowNum++;
- }
- return list;
- }
- };
-
- if (jobName.contains(STAR_WILDCARD)) {
- jobName = jobName.replaceAll("\\" + STAR_WILDCARD, SQL_WILDCARD);
- }
-
- return (List) getJdbcTemplate().query(getQuery(FIND_LAST_JOBS_LIKE_NAME), extractor, jobName);
-
+ return getJobInstances(jobName, start, count);
}
}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JdbcStepExecutionDao.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/jdbc/JdbcStepExecutionDao.java
similarity index 89%
rename from spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JdbcStepExecutionDao.java
rename to spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/jdbc/JdbcStepExecutionDao.java
index 594e2e7eef..a9c910eb09 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JdbcStepExecutionDao.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/jdbc/JdbcStepExecutionDao.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006-2024 the original author or authors.
+ * Copyright 2006-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.batch.core.repository.dao;
+package org.springframework.batch.core.repository.dao.jdbc;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
@@ -29,15 +29,18 @@
import java.util.List;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
+import java.util.stream.Stream;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.batch.core.BatchStatus;
import org.springframework.batch.core.ExitStatus;
-import org.springframework.batch.core.JobExecution;
-import org.springframework.batch.core.JobInstance;
-import org.springframework.batch.core.StepExecution;
+import org.springframework.batch.core.job.JobExecution;
+import org.springframework.batch.core.job.JobInstance;
+import org.springframework.batch.core.step.StepExecution;
+import org.springframework.batch.core.repository.dao.AbstractJdbcBatchMetadataDao;
+import org.springframework.batch.core.repository.dao.StepExecutionDao;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.dao.OptimisticLockingFailureException;
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
@@ -66,6 +69,7 @@
* @author Mahmoud Ben Hassine
* @author Baris Cubukcuoglu
* @author Minsoo Kim
+ * @author Yanming Zhou
* @see StepExecutionDao
*/
public class JdbcStepExecutionDao extends AbstractJdbcBatchMetadataDao implements StepExecutionDao, InitializingBean {
@@ -79,19 +83,19 @@ public class JdbcStepExecutionDao extends AbstractJdbcBatchMetadataDao implement
private static final String UPDATE_STEP_EXECUTION = """
UPDATE %PREFIX%STEP_EXECUTION
- SET START_TIME = ?, END_TIME = ?, STATUS = ?, COMMIT_COUNT = ?, READ_COUNT = ?, FILTER_COUNT = ?, WRITE_COUNT = ?, EXIT_CODE = ?, EXIT_MESSAGE = ?, VERSION = ?, READ_SKIP_COUNT = ?, PROCESS_SKIP_COUNT = ?, WRITE_SKIP_COUNT = ?, ROLLBACK_COUNT = ?, LAST_UPDATED = ?
+ SET START_TIME = ?, END_TIME = ?, STATUS = ?, COMMIT_COUNT = ?, READ_COUNT = ?, FILTER_COUNT = ?, WRITE_COUNT = ?, EXIT_CODE = ?, EXIT_MESSAGE = ?, VERSION = VERSION + 1, READ_SKIP_COUNT = ?, PROCESS_SKIP_COUNT = ?, WRITE_SKIP_COUNT = ?, ROLLBACK_COUNT = ?, LAST_UPDATED = ?
WHERE STEP_EXECUTION_ID = ? AND VERSION = ?
""";
private static final String GET_RAW_STEP_EXECUTIONS = """
SELECT STEP_EXECUTION_ID, STEP_NAME, START_TIME, END_TIME, STATUS, COMMIT_COUNT, READ_COUNT, FILTER_COUNT, WRITE_COUNT, EXIT_CODE, EXIT_MESSAGE, READ_SKIP_COUNT, WRITE_SKIP_COUNT, PROCESS_SKIP_COUNT, ROLLBACK_COUNT, LAST_UPDATED, VERSION, CREATE_TIME
FROM %PREFIX%STEP_EXECUTION
- WHERE JOB_EXECUTION_ID = ?
""";
- private static final String GET_STEP_EXECUTIONS = GET_RAW_STEP_EXECUTIONS + " ORDER BY STEP_EXECUTION_ID";
+ private static final String GET_STEP_EXECUTIONS = GET_RAW_STEP_EXECUTIONS
+ + " WHERE JOB_EXECUTION_ID = ? ORDER BY STEP_EXECUTION_ID";
- private static final String GET_STEP_EXECUTION = GET_RAW_STEP_EXECUTIONS + " AND STEP_EXECUTION_ID = ?";
+ private static final String GET_STEP_EXECUTION = GET_RAW_STEP_EXECUTIONS + " WHERE STEP_EXECUTION_ID = ?";
private static final String GET_LAST_STEP_EXECUTION = """
SELECT SE.STEP_EXECUTION_ID, SE.STEP_NAME, SE.START_TIME, SE.END_TIME, SE.STATUS, SE.COMMIT_COUNT, SE.READ_COUNT, SE.FILTER_COUNT, SE.WRITE_COUNT, SE.EXIT_CODE, SE.EXIT_MESSAGE, SE.READ_SKIP_COUNT, SE.WRITE_SKIP_COUNT, SE.PROCESS_SKIP_COUNT, SE.ROLLBACK_COUNT, SE.LAST_UPDATED, SE.VERSION, SE.CREATE_TIME, JE.JOB_EXECUTION_ID, JE.START_TIME, JE.END_TIME, JE.STATUS, JE.EXIT_CODE, JE.EXIT_MESSAGE, JE.CREATE_TIME, JE.LAST_UPDATED, JE.VERSION
@@ -114,7 +118,7 @@ SELECT COUNT(*)
private static final String DELETE_STEP_EXECUTION = """
DELETE FROM %PREFIX%STEP_EXECUTION
- WHERE STEP_EXECUTION_ID = ?
+ WHERE STEP_EXECUTION_ID = ? and VERSION = ?
""";
private static final Comparator BY_CREATE_TIME_DESC_ID_DESC = Comparator
@@ -267,7 +271,6 @@ public void updateStepExecution(StepExecution stepExecution) {
this.lock.lock();
try {
- Integer version = stepExecution.getVersion() + 1;
Timestamp startTime = stepExecution.getStartTime() == null ? null
: Timestamp.valueOf(stepExecution.getStartTime());
Timestamp endTime = stepExecution.getEndTime() == null ? null
@@ -277,14 +280,13 @@ public void updateStepExecution(StepExecution stepExecution) {
Object[] parameters = new Object[] { startTime, endTime, stepExecution.getStatus().toString(),
stepExecution.getCommitCount(), stepExecution.getReadCount(), stepExecution.getFilterCount(),
stepExecution.getWriteCount(), stepExecution.getExitStatus().getExitCode(), exitDescription,
- version, stepExecution.getReadSkipCount(), stepExecution.getProcessSkipCount(),
+ stepExecution.getReadSkipCount(), stepExecution.getProcessSkipCount(),
stepExecution.getWriteSkipCount(), stepExecution.getRollbackCount(), lastUpdated,
stepExecution.getId(), stepExecution.getVersion() };
int count = getJdbcTemplate().update(getQuery(UPDATE_STEP_EXECUTION), parameters,
- new int[] { Types.TIMESTAMP, Types.TIMESTAMP, Types.VARCHAR, Types.INTEGER, Types.INTEGER,
- Types.INTEGER, Types.INTEGER, Types.VARCHAR, Types.VARCHAR, Types.INTEGER, Types.INTEGER,
- Types.INTEGER, Types.INTEGER, Types.INTEGER, Types.TIMESTAMP, Types.BIGINT,
- Types.INTEGER });
+ new int[] { Types.TIMESTAMP, Types.TIMESTAMP, Types.VARCHAR, Types.BIGINT, Types.BIGINT,
+ Types.BIGINT, Types.BIGINT, Types.VARCHAR, Types.VARCHAR, Types.BIGINT, Types.BIGINT,
+ Types.BIGINT, Types.BIGINT, Types.TIMESTAMP, Types.BIGINT, Types.INTEGER });
// Avoid concurrent modifications...
if (count == 0) {
@@ -325,16 +327,9 @@ private String truncateExitDescription(String description) {
@Override
@Nullable
public StepExecution getStepExecution(JobExecution jobExecution, Long stepExecutionId) {
- List executions = getJdbcTemplate().query(getQuery(GET_STEP_EXECUTION),
- new StepExecutionRowMapper(jobExecution), jobExecution.getId(), stepExecutionId);
-
- Assert.state(executions.size() <= 1,
- "There can be at most one step execution with given name for single job execution");
- if (executions.isEmpty()) {
- return null;
- }
- else {
- return executions.get(0);
+ try (Stream stream = getJdbcTemplate().queryForStream(getQuery(GET_STEP_EXECUTION),
+ new StepExecutionRowMapper(jobExecution), stepExecutionId)) {
+ return stream.findFirst().orElse(null);
}
}
@@ -379,7 +374,13 @@ public long countStepExecutions(JobInstance jobInstance, String stepName) {
*/
@Override
public void deleteStepExecution(StepExecution stepExecution) {
- getJdbcTemplate().update(getQuery(DELETE_STEP_EXECUTION), stepExecution.getId());
+ int count = getJdbcTemplate().update(getQuery(DELETE_STEP_EXECUTION), stepExecution.getId(),
+ stepExecution.getVersion());
+
+ if (count == 0) {
+ throw new OptimisticLockingFailureException("Attempt to delete step execution id=" + stepExecution.getId()
+ + " with wrong version (" + stepExecution.getVersion() + ")");
+ }
}
private static class StepExecutionRowMapper implements RowMapper {
@@ -396,15 +397,15 @@ public StepExecution mapRow(ResultSet rs, int rowNum) throws SQLException {
stepExecution.setStartTime(rs.getTimestamp(3) == null ? null : rs.getTimestamp(3).toLocalDateTime());
stepExecution.setEndTime(rs.getTimestamp(4) == null ? null : rs.getTimestamp(4).toLocalDateTime());
stepExecution.setStatus(BatchStatus.valueOf(rs.getString(5)));
- stepExecution.setCommitCount(rs.getInt(6));
- stepExecution.setReadCount(rs.getInt(7));
- stepExecution.setFilterCount(rs.getInt(8));
- stepExecution.setWriteCount(rs.getInt(9));
+ stepExecution.setCommitCount(rs.getLong(6));
+ stepExecution.setReadCount(rs.getLong(7));
+ stepExecution.setFilterCount(rs.getLong(8));
+ stepExecution.setWriteCount(rs.getLong(9));
stepExecution.setExitStatus(new ExitStatus(rs.getString(10), rs.getString(11)));
- stepExecution.setReadSkipCount(rs.getInt(12));
- stepExecution.setWriteSkipCount(rs.getInt(13));
- stepExecution.setProcessSkipCount(rs.getInt(14));
- stepExecution.setRollbackCount(rs.getInt(15));
+ stepExecution.setReadSkipCount(rs.getLong(12));
+ stepExecution.setWriteSkipCount(rs.getLong(13));
+ stepExecution.setProcessSkipCount(rs.getLong(14));
+ stepExecution.setRollbackCount(rs.getLong(15));
stepExecution.setLastUpdated(rs.getTimestamp(16) == null ? null : rs.getTimestamp(16).toLocalDateTime());
stepExecution.setVersion(rs.getInt(17));
stepExecution.setCreateTime(rs.getTimestamp(18) == null ? null : rs.getTimestamp(18).toLocalDateTime());
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MongoExecutionContextDao.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/mongodb/MongoExecutionContextDao.java
similarity index 80%
rename from spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MongoExecutionContextDao.java
rename to spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/mongodb/MongoExecutionContextDao.java
index 485882a163..ce61e8b8d2 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MongoExecutionContextDao.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/mongodb/MongoExecutionContextDao.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2024 the original author or authors.
+ * Copyright 2024-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,13 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.springframework.batch.core.repository.dao;
+package org.springframework.batch.core.repository.dao.mongodb;
import java.util.Collection;
-import java.util.Map;
-import org.springframework.batch.core.JobExecution;
-import org.springframework.batch.core.StepExecution;
+import org.springframework.batch.core.job.JobExecution;
+import org.springframework.batch.core.step.StepExecution;
+import org.springframework.batch.core.repository.dao.ExecutionContextDao;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.query.Query;
@@ -46,8 +46,9 @@ public MongoExecutionContextDao(MongoOperations mongoOperations) {
@Override
public ExecutionContext getExecutionContext(JobExecution jobExecution) {
- org.springframework.batch.core.repository.persistence.JobExecution execution = this.mongoOperations.findById(
- jobExecution.getId(), org.springframework.batch.core.repository.persistence.JobExecution.class,
+ Query query = query(where("jobExecutionId").is(jobExecution.getId()));
+ org.springframework.batch.core.repository.persistence.JobExecution execution = this.mongoOperations.findOne(
+ query, org.springframework.batch.core.repository.persistence.JobExecution.class,
JOB_EXECUTIONS_COLLECTION_NAME);
if (execution == null) {
return new ExecutionContext();
@@ -57,8 +58,9 @@ public ExecutionContext getExecutionContext(JobExecution jobExecution) {
@Override
public ExecutionContext getExecutionContext(StepExecution stepExecution) {
- org.springframework.batch.core.repository.persistence.StepExecution execution = this.mongoOperations.findById(
- stepExecution.getId(), org.springframework.batch.core.repository.persistence.StepExecution.class,
+ Query query = query(where("stepExecutionId").is(stepExecution.getId()));
+ org.springframework.batch.core.repository.persistence.StepExecution execution = this.mongoOperations.findOne(
+ query, org.springframework.batch.core.repository.persistence.StepExecution.class,
STEP_EXECUTIONS_COLLECTION_NAME);
if (execution == null) {
return new ExecutionContext();
@@ -69,7 +71,7 @@ public ExecutionContext getExecutionContext(StepExecution stepExecution) {
@Override
public void saveExecutionContext(JobExecution jobExecution) {
ExecutionContext executionContext = jobExecution.getExecutionContext();
- Query query = query(where("_id").is(jobExecution.getId()));
+ Query query = query(where("jobExecutionId").is(jobExecution.getId()));
Update update = Update.update("executionContext",
new org.springframework.batch.core.repository.persistence.ExecutionContext(executionContext.toMap(),
@@ -82,7 +84,7 @@ public void saveExecutionContext(JobExecution jobExecution) {
@Override
public void saveExecutionContext(StepExecution stepExecution) {
ExecutionContext executionContext = stepExecution.getExecutionContext();
- Query query = query(where("_id").is(stepExecution.getId()));
+ Query query = query(where("stepExecutionId").is(stepExecution.getId()));
Update update = Update.update("executionContext",
new org.springframework.batch.core.repository.persistence.ExecutionContext(executionContext.toMap(),
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MongoJobExecutionDao.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/mongodb/MongoJobExecutionDao.java
similarity index 84%
rename from spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MongoJobExecutionDao.java
rename to spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/mongodb/MongoJobExecutionDao.java
index c4525970d7..d95c8d9105 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MongoJobExecutionDao.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/mongodb/MongoJobExecutionDao.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2024 the original author or authors.
+ * Copyright 2024-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,20 +13,20 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.springframework.batch.core.repository.dao;
+package org.springframework.batch.core.repository.dao.mongodb;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
-import org.springframework.batch.core.JobExecution;
-import org.springframework.batch.core.JobInstance;
+import org.springframework.batch.core.job.JobExecution;
+import org.springframework.batch.core.job.JobInstance;
+import org.springframework.batch.core.repository.dao.JobExecutionDao;
import org.springframework.batch.core.repository.persistence.converter.JobExecutionConverter;
import org.springframework.batch.core.repository.persistence.converter.JobInstanceConverter;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.query.Query;
-import org.springframework.data.mongodb.core.query.Update;
import org.springframework.jdbc.support.incrementer.DataFieldMaxValueIncrementer;
import static org.springframework.data.mongodb.core.query.Criteria.where;
@@ -126,29 +126,30 @@ public Set findRunningJobExecutions(String jobName) {
@Override
public JobExecution getJobExecution(Long executionId) {
- org.springframework.batch.core.repository.persistence.JobExecution jobExecution = this.mongoOperations.findById(
- executionId, org.springframework.batch.core.repository.persistence.JobExecution.class,
+ Query jobExecutionQuery = query(where("jobExecutionId").is(executionId));
+ org.springframework.batch.core.repository.persistence.JobExecution jobExecution = this.mongoOperations.findOne(
+ jobExecutionQuery, org.springframework.batch.core.repository.persistence.JobExecution.class,
JOB_EXECUTIONS_COLLECTION_NAME);
if (jobExecution == null) {
return null;
}
- org.springframework.batch.core.repository.persistence.JobInstance jobInstance = this.mongoOperations.findById(
- jobExecution.getJobInstanceId(),
- org.springframework.batch.core.repository.persistence.JobInstance.class, JOB_INSTANCES_COLLECTION_NAME);
+ Query jobInstanceQuery = query(where("jobInstanceId").is(jobExecution.getJobInstanceId()));
+ org.springframework.batch.core.repository.persistence.JobInstance jobInstance = this.mongoOperations.findOne(
+ jobInstanceQuery, org.springframework.batch.core.repository.persistence.JobInstance.class,
+ JOB_INSTANCES_COLLECTION_NAME);
return this.jobExecutionConverter.toJobExecution(jobExecution,
this.jobInstanceConverter.toJobInstance(jobInstance));
}
@Override
public void synchronizeStatus(JobExecution jobExecution) {
- Query query = query(where("jobExecutionId").is(jobExecution.getId()));
- Update update = Update.update("status", jobExecution.getStatus());
+ JobExecution currentJobExecution = getJobExecution(jobExecution.getId());
+ if (currentJobExecution != null && currentJobExecution.getStatus().isGreaterThan(jobExecution.getStatus())) {
+ jobExecution.upgradeStatus(currentJobExecution.getStatus());
+ }
// TODO the contract mentions to update the version as well. Double check if this
// is needed as the version is not used in the tests following the call sites of
// synchronizeStatus
- this.mongoOperations.updateFirst(query, update,
- org.springframework.batch.core.repository.persistence.JobExecution.class,
- JOB_EXECUTIONS_COLLECTION_NAME);
}
}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MongoJobInstanceDao.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/mongodb/MongoJobInstanceDao.java
similarity index 84%
rename from spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MongoJobInstanceDao.java
rename to spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/mongodb/MongoJobInstanceDao.java
index b967e35f77..2d742aa9e6 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MongoJobInstanceDao.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/mongodb/MongoJobInstanceDao.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2024 the original author or authors.
+ * Copyright 2024-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,18 +13,18 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.springframework.batch.core.repository.dao;
+package org.springframework.batch.core.repository.dao.mongodb;
import java.util.List;
-import org.springframework.batch.core.DefaultJobKeyGenerator;
-import org.springframework.batch.core.JobExecution;
-import org.springframework.batch.core.JobInstance;
-import org.springframework.batch.core.JobKeyGenerator;
-import org.springframework.batch.core.JobParameters;
+import org.springframework.batch.core.job.DefaultJobKeyGenerator;
+import org.springframework.batch.core.job.JobExecution;
+import org.springframework.batch.core.job.JobInstance;
+import org.springframework.batch.core.job.JobKeyGenerator;
+import org.springframework.batch.core.job.parameters.JobParameters;
import org.springframework.batch.core.launch.NoSuchJobException;
+import org.springframework.batch.core.repository.dao.JobInstanceDao;
import org.springframework.batch.core.repository.persistence.converter.JobInstanceConverter;
-import org.springframework.data.domain.Example;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.query.Query;
@@ -48,7 +48,7 @@ public class MongoJobInstanceDao implements JobInstanceDao {
private DataFieldMaxValueIncrementer jobInstanceIncrementer;
- private JobKeyGenerator jobKeyGenerator = new DefaultJobKeyGenerator();
+ private JobKeyGenerator jobKeyGenerator = new DefaultJobKeyGenerator();
private final JobInstanceConverter jobInstanceConverter = new JobInstanceConverter();
@@ -58,7 +58,7 @@ public MongoJobInstanceDao(MongoOperations mongoOperations) {
this.jobInstanceIncrementer = new MongoSequenceIncrementer(mongoOperations, SEQUENCE_NAME);
}
- public void setJobKeyGenerator(JobKeyGenerator jobKeyGenerator) {
+ public void setJobKeyGenerator(JobKeyGenerator jobKeyGenerator) {
this.jobKeyGenerator = jobKeyGenerator;
}
@@ -143,20 +143,15 @@ public List getJobNames() {
.toList();
}
+ /**
+ * @deprecated since v6.0 and scheduled for removal in v6.2. Use
+ * {@link #getJobInstances(String, int, int)} instead.
+ */
+ @SuppressWarnings("removal")
+ @Deprecated(forRemoval = true)
@Override
public List findJobInstancesByName(String jobName, int start, int count) {
- Query query = query(where("jobName").alike(Example.of(jobName)));
- Sort.Order sortOrder = Sort.Order.desc("jobInstanceId");
- List jobInstances = this.mongoOperations
- .find(query.with(Sort.by(sortOrder)),
- org.springframework.batch.core.repository.persistence.JobInstance.class, COLLECTION_NAME)
- .stream()
- .toList();
- return jobInstances.subList(start, jobInstances.size())
- .stream()
- .map(this.jobInstanceConverter::toJobInstance)
- .limit(count)
- .toList();
+ return getJobInstances(jobName, start, count);
}
@Override
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MongoSequenceIncrementer.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/mongodb/MongoSequenceIncrementer.java
similarity index 57%
rename from spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MongoSequenceIncrementer.java
rename to spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/mongodb/MongoSequenceIncrementer.java
index 683d2ad69e..9722db637f 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MongoSequenceIncrementer.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/mongodb/MongoSequenceIncrementer.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2024 the original author or authors.
+ * Copyright 2024-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,22 +13,22 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.springframework.batch.core.repository.dao;
+package org.springframework.batch.core.repository.dao.mongodb;
+
+import com.mongodb.client.model.FindOneAndUpdateOptions;
+import com.mongodb.client.model.ReturnDocument;
+import org.bson.Document;
import org.springframework.dao.DataAccessException;
import org.springframework.data.mongodb.core.MongoOperations;
-import org.springframework.data.mongodb.core.query.Query;
-import org.springframework.data.mongodb.core.query.Update;
import org.springframework.jdbc.support.incrementer.DataFieldMaxValueIncrementer;
-import static org.springframework.data.mongodb.core.query.Criteria.where;
-import static org.springframework.data.mongodb.core.query.Query.query;
-
// Based on https://www.mongodb.com/blog/post/generating-globally-unique-identifiers-for-use-with-mongodb
// Section: Use a single counter document to generate unique identifiers one at a time
/**
* @author Mahmoud Ben Hassine
+ * @author Christoph Strobl
* @since 5.2.0
*/
public class MongoSequenceIncrementer implements DataFieldMaxValueIncrementer {
@@ -44,13 +44,11 @@ public MongoSequenceIncrementer(MongoOperations mongoTemplate, String sequenceNa
@Override
public long nextLongValue() throws DataAccessException {
- // TODO optimize
- MongoSequence sequence = mongoTemplate.findOne(new Query(), MongoSequence.class, sequenceName);
- Query query = query(where("_id").is(sequence.getId()));
- Update update = new Update().inc("count", 1);
- // The following does not return the modified document
- mongoTemplate.findAndModify(query, update, MongoSequence.class, sequenceName);
- return mongoTemplate.findOne(new Query(), MongoSequence.class, sequenceName).getCount();
+ return mongoTemplate.execute("BATCH_SEQUENCES",
+ collection -> collection
+ .findOneAndUpdate(new Document("_id", sequenceName), new Document("$inc", new Document("count", 1)),
+ new FindOneAndUpdateOptions().returnDocument(ReturnDocument.AFTER))
+ .getLong("count"));
}
@Override
@@ -63,33 +61,4 @@ public String nextStringValue() throws DataAccessException {
throw new UnsupportedOperationException();
}
- public static final class MongoSequence {
-
- private String id;
-
- private long count;
-
- public String getId() {
- return id;
- }
-
- public void setId(String id) {
- this.id = id;
- }
-
- public long getCount() {
- return count;
- }
-
- public void setCount(long count) {
- this.count = count;
- }
-
- @Override
- public String toString() {
- return "MongoSequence{" + "id='" + id + '\'' + ", count=" + count + '}';
- }
-
- }
-
}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MongoStepExecutionDao.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/mongodb/MongoStepExecutionDao.java
similarity index 92%
rename from spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MongoStepExecutionDao.java
rename to spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/mongodb/MongoStepExecutionDao.java
index 44215babd7..a7bac8ce26 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MongoStepExecutionDao.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/mongodb/MongoStepExecutionDao.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2024 the original author or authors.
+ * Copyright 2024-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.springframework.batch.core.repository.dao;
+package org.springframework.batch.core.repository.dao.mongodb;
import java.util.ArrayList;
import java.util.Collection;
@@ -21,9 +21,10 @@
import java.util.List;
import java.util.Optional;
-import org.springframework.batch.core.JobExecution;
-import org.springframework.batch.core.JobInstance;
-import org.springframework.batch.core.StepExecution;
+import org.springframework.batch.core.job.JobExecution;
+import org.springframework.batch.core.job.JobInstance;
+import org.springframework.batch.core.step.StepExecution;
+import org.springframework.batch.core.repository.dao.StepExecutionDao;
import org.springframework.batch.core.repository.persistence.converter.JobExecutionConverter;
import org.springframework.batch.core.repository.persistence.converter.StepExecutionConverter;
import org.springframework.data.mongodb.core.MongoOperations;
@@ -89,8 +90,9 @@ public void updateStepExecution(StepExecution stepExecution) {
@Override
public StepExecution getStepExecution(JobExecution jobExecution, Long stepExecutionId) {
+ Query query = query(where("stepExecutionId").is(stepExecutionId));
org.springframework.batch.core.repository.persistence.StepExecution stepExecution = this.mongoOperations
- .findById(stepExecutionId, org.springframework.batch.core.repository.persistence.StepExecution.class,
+ .findOne(query, org.springframework.batch.core.repository.persistence.StepExecution.class,
STEP_EXECUTIONS_COLLECTION_NAME);
return stepExecution != null ? this.stepExecutionConverter.toStepExecution(stepExecution, jobExecution) : null;
}
@@ -111,6 +113,7 @@ public StepExecution getLastStepExecution(JobInstance jobInstance, String stepNa
// first one
Optional lastStepExecution = stepExecutions
.stream()
+ .filter(stepExecution -> stepExecution.getName().equals(stepName))
.min(Comparator
.comparing(org.springframework.batch.core.repository.persistence.StepExecution::getCreateTime)
.thenComparing(org.springframework.batch.core.repository.persistence.StepExecution::getId));
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/explore/JobExplorer.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/explore/JobExplorer.java
similarity index 50%
rename from spring-batch-core/src/main/java/org/springframework/batch/core/explore/JobExplorer.java
rename to spring-batch-core/src/main/java/org/springframework/batch/core/repository/explore/JobExplorer.java
index 85c69655f9..aae7366d76 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/explore/JobExplorer.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/explore/JobExplorer.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006-2023 the original author or authors.
+ * Copyright 2006-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,16 +13,18 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.springframework.batch.core.explore;
+package org.springframework.batch.core.repository.explore;
+import java.util.Collections;
import java.util.List;
import java.util.Set;
-import org.springframework.batch.core.JobExecution;
-import org.springframework.batch.core.JobInstance;
-import org.springframework.batch.core.JobParameters;
-import org.springframework.batch.core.StepExecution;
+import org.springframework.batch.core.job.JobExecution;
+import org.springframework.batch.core.job.JobInstance;
+import org.springframework.batch.core.job.parameters.JobParameters;
+import org.springframework.batch.core.step.StepExecution;
import org.springframework.batch.core.launch.NoSuchJobException;
+import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.lang.Nullable;
@@ -37,9 +39,33 @@
* @author Mahmoud Ben Hassine
* @author Parikshit Dutta
* @since 2.0
+ * @deprecated since 6.0 in favor of {@link JobRepository}. Scheduled for removal in 6.2
+ * or later.
*/
+@Deprecated(since = "6.0", forRemoval = true)
public interface JobExplorer {
+ /*
+ * ===================================================================================
+ * Job operations
+ * ===================================================================================
+ */
+
+ /**
+ * Query the repository for all unique {@link JobInstance} names (sorted
+ * alphabetically).
+ * @return the list of job names that have been executed.
+ */
+ default List getJobNames() {
+ return Collections.emptyList();
+ }
+
+ /*
+ * ===================================================================================
+ * Job instance operations
+ * ===================================================================================
+ */
+
/**
* Fetch {@link JobInstance} values in descending order of creation (and, therefore,
* usually, of first execution).
@@ -48,51 +74,77 @@ public interface JobExplorer {
* @param count The maximum number of instances to return.
* @return the {@link JobInstance} values up to a maximum of count values.
*/
- List getJobInstances(String jobName, int start, int count);
+ default List getJobInstances(String jobName, int start, int count) {
+ return Collections.emptyList();
+ }
/**
- * Find the last job instance, by ID, for the given job.
- * @param jobName The name of the job.
- * @return the last job instance by Id if any or {@code null} otherwise.
- *
- * @since 4.2
+ * Fetch {@link JobInstance} values in descending order of creation (and, therefore,
+ * usually of first execution) with a 'like' or wildcard criteria.
+ * @param jobName The name of the job for which to query.
+ * @param start The start index of the instances to return.
+ * @param count The maximum number of instances to return.
+ * @return a list of {@link JobInstance} for the requested job name.
+ * @deprecated Since v6.0 and scheduled for removal in v6.2. Use
+ * {@link #getJobInstances(String, int, int)}
*/
- @Nullable
- default JobInstance getLastJobInstance(String jobName) {
- throw new UnsupportedOperationException();
+ @Deprecated(since = "6.0", forRemoval = true)
+ default List findJobInstancesByJobName(String jobName, int start, int count) {
+ return Collections.emptyList();
}
/**
- * Retrieve a {@link JobExecution} by its ID. The complete object graph for this
- * execution should be returned (unless otherwise indicated), including the parent
- * {@link JobInstance} and associated {@link ExecutionContext} and
- * {@link StepExecution} instances (also including their execution contexts).
- * @param executionId The job execution ID.
- * @return the {@link JobExecution} that has this ID or {@code null} if not found.
+ * Fetch the last job instances with the provided name, sorted backwards by primary
+ * key, using a 'like' criteria
+ * @param jobName {@link String} containing the name of the job.
+ * @param start int containing the offset of where list of job instances results
+ * should begin.
+ * @param count int containing the number of job instances to return.
+ * @return a list of {@link JobInstance} for the job name requested.
+ * @since 5.0
+ * @deprecated since v6.0 and scheduled for removal in v6.2. Use
+ * {@link #getJobInstances(String, int, int)}
*/
- @Nullable
- JobExecution getJobExecution(@Nullable Long executionId);
+ @Deprecated(since = "6.0", forRemoval = true)
+ default List findJobInstancesByName(String jobName, int start, int count) {
+ return Collections.emptyList();
+ }
/**
- * Retrieve a {@link StepExecution} by its ID and parent {@link JobExecution} ID. The
- * execution context for the step should be available in the result, and the parent
- * job execution should have its primitive properties, but it may not contain the job
- * instance information.
- * @param jobExecutionId The parent job execution ID.
- * @param stepExecutionId The step execution ID.
- * @return the {@link StepExecution} that has this ID or {@code null} if not found.
- *
- * @see #getJobExecution(Long)
+ * Check if an instance of this job already exists with the parameters provided.
+ * @param jobName the name of the job
+ * @param jobParameters the parameters to match
+ * @return true if a {@link JobInstance} already exists for this job name and job
+ * parameters
+ * @deprecated Since v6.0 and scheduled for removal in v6.2. Use
+ * {@link #getJobInstance(String, JobParameters)} and check for {@code null} result
+ * instead.
*/
- @Nullable
- StepExecution getStepExecution(@Nullable Long jobExecutionId, @Nullable Long stepExecutionId);
+ @Deprecated(since = "6.0", forRemoval = true)
+ default boolean isJobInstanceExists(String jobName, JobParameters jobParameters) {
+ return getJobInstance(jobName, jobParameters) != null;
+ }
/**
* @param instanceId {@link Long} The ID for the {@link JobInstance} to obtain.
* @return the {@code JobInstance} that has this ID, or {@code null} if not found.
*/
@Nullable
- JobInstance getJobInstance(@Nullable Long instanceId);
+ default JobInstance getJobInstance(@Nullable Long instanceId) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Find the last job instance, by ID, for the given job.
+ * @param jobName The name of the job.
+ * @return the last job instance by Id if any or {@code null} otherwise.
+ *
+ * @since 4.2
+ */
+ @Nullable
+ default JobInstance getLastJobInstance(String jobName) {
+ throw new UnsupportedOperationException();
+ }
/**
* @param jobName {@link String} name of the job.
@@ -107,6 +159,38 @@ default JobInstance getJobInstance(String jobName, JobParameters jobParameters)
throw new UnsupportedOperationException();
}
+ /**
+ * Query the repository for the number of unique {@link JobInstance} objects
+ * associated with the supplied job name.
+ * @param jobName The name of the job for which to query.
+ * @return the number of {@link JobInstance}s that exist within the associated job
+ * repository.
+ * @throws NoSuchJobException thrown when there is no {@link JobInstance} for the
+ * jobName specified.
+ */
+ default long getJobInstanceCount(@Nullable String jobName) throws NoSuchJobException {
+ throw new UnsupportedOperationException();
+ }
+
+ /*
+ * ===================================================================================
+ * Job execution operations
+ * ===================================================================================
+ */
+
+ /**
+ * Retrieve a {@link JobExecution} by its ID. The complete object graph for this
+ * execution should be returned (unless otherwise indicated), including the parent
+ * {@link JobInstance} and associated {@link ExecutionContext} and
+ * {@link StepExecution} instances (also including their execution contexts).
+ * @param executionId The job execution ID.
+ * @return the {@link JobExecution} that has this ID or {@code null} if not found.
+ */
+ @Nullable
+ default JobExecution getJobExecution(@Nullable Long executionId) {
+ throw new UnsupportedOperationException();
+ }
+
/**
* Retrieve job executions by their job instance. The corresponding step executions
* may not be fully hydrated (for example, their execution context may be missing),
@@ -115,7 +199,23 @@ default JobInstance getJobInstance(String jobName, JobParameters jobParameters)
* @param jobInstance The {@link JobInstance} to query.
* @return the list of all executions for the specified {@link JobInstance}.
*/
- List getJobExecutions(JobInstance jobInstance);
+ default List getJobExecutions(JobInstance jobInstance) {
+ return Collections.emptyList();
+ }
+
+ /**
+ * Return all {@link JobExecution}s for given {@link JobInstance}, sorted backwards by
+ * creation order (so the first element is the most recent).
+ * @param jobInstance parent {@link JobInstance} of the {@link JobExecution}s to find.
+ * @return {@link List} containing JobExecutions for the jobInstance.
+ * @since 5.0
+ * @deprecated since v6.0 and scheduled for removal in v6.2. Use
+ * {@link #getJobExecutions(JobInstance)}
+ */
+ @Deprecated(since = "6.0", forRemoval = true)
+ default List findJobExecutions(JobInstance jobInstance) {
+ return Collections.emptyList();
+ }
/**
* Find the last {@link JobExecution} that has been created for a given
@@ -132,6 +232,16 @@ default JobExecution getLastJobExecution(JobInstance jobInstance) {
throw new UnsupportedOperationException();
}
+ /**
+ * @param jobName the name of the job that might have run
+ * @param jobParameters parameters identifying the {@link JobInstance}
+ * @return the last execution of job if exists, null otherwise
+ */
+ @Nullable
+ default JobExecution getLastJobExecution(String jobName, JobParameters jobParameters) {
+ throw new UnsupportedOperationException();
+ }
+
/**
* Retrieve running job executions. The corresponding step executions may not be fully
* hydrated (for example, their execution context may be missing), depending on the
@@ -140,34 +250,49 @@ default JobExecution getLastJobExecution(JobInstance jobInstance) {
* @param jobName The name of the job.
* @return the set of running executions for jobs with the specified name.
*/
- Set findRunningJobExecutions(@Nullable String jobName);
+ default Set findRunningJobExecutions(@Nullable String jobName) {
+ return Collections.emptySet();
+ }
+
+ /*
+ * ===================================================================================
+ * Step execution operations
+ * ===================================================================================
+ */
/**
- * Query the repository for all unique {@link JobInstance} names (sorted
- * alphabetically).
- * @return the list of job names that have been executed.
+ * Retrieve a {@link StepExecution} by its ID and parent {@link JobExecution} ID. The
+ * execution context for the step should be available in the result, and the parent
+ * job execution should have its primitive properties, but it may not contain the job
+ * instance information.
+ * @param jobExecutionId The parent job execution ID.
+ * @param stepExecutionId The step execution ID.
+ * @return the {@link StepExecution} that has this ID or {@code null} if not found.
+ *
+ * @see #getJobExecution(Long)
*/
- List getJobNames();
+ @Nullable
+ default StepExecution getStepExecution(@Nullable Long jobExecutionId, @Nullable Long stepExecutionId) {
+ throw new UnsupportedOperationException();
+ }
/**
- * Fetch {@link JobInstance} values in descending order of creation (and, therefore,
- * usually of first execution) with a 'like' or wildcard criteria.
- * @param jobName The name of the job for which to query.
- * @param start The start index of the instances to return.
- * @param count The maximum number of instances to return.
- * @return a list of {@link JobInstance} for the requested job name.
+ * @param jobInstance {@link JobInstance} instance containing the step executions.
+ * @param stepName the name of the step execution that might have run.
+ * @return the last execution of step for the given job instance.
*/
- List findJobInstancesByJobName(String jobName, int start, int count);
+ @Nullable
+ default StepExecution getLastStepExecution(JobInstance jobInstance, String stepName) {
+ throw new UnsupportedOperationException();
+ }
/**
- * Query the repository for the number of unique {@link JobInstance} objects
- * associated with the supplied job name.
- * @param jobName The name of the job for which to query.
- * @return the number of {@link JobInstance}s that exist within the associated job
- * repository.
- * @throws NoSuchJobException thrown when there is no {@link JobInstance} for the
- * jobName specified.
+ * @param jobInstance {@link JobInstance} instance containing the step executions.
+ * @param stepName the name of the step execution that might have run.
+ * @return the execution count of the step within the given job instance.
*/
- long getJobInstanceCount(@Nullable String jobName) throws NoSuchJobException;
+ default long getStepExecutionCount(JobInstance jobInstance, String stepName) {
+ throw new UnsupportedOperationException();
+ }
}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/explore/package-info.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/explore/package-info.java
similarity index 76%
rename from spring-batch-core/src/main/java/org/springframework/batch/core/explore/package-info.java
rename to spring-batch-core/src/main/java/org/springframework/batch/core/repository/explore/package-info.java
index b5671f50be..c759d4d869 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/explore/package-info.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/explore/package-info.java
@@ -5,6 +5,6 @@
* @author Mahmoud Ben Hassine
*/
@NonNullApi
-package org.springframework.batch.core.explore;
+package org.springframework.batch.core.repository.explore;
import org.springframework.lang.NonNullApi;
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/explore/support/AbstractJobExplorerFactoryBean.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/explore/support/AbstractJobExplorerFactoryBean.java
similarity index 95%
rename from spring-batch-core/src/main/java/org/springframework/batch/core/explore/support/AbstractJobExplorerFactoryBean.java
rename to spring-batch-core/src/main/java/org/springframework/batch/core/repository/explore/support/AbstractJobExplorerFactoryBean.java
index 8f6ae2052c..1b8627688b 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/explore/support/AbstractJobExplorerFactoryBean.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/explore/support/AbstractJobExplorerFactoryBean.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006-2023 the original author or authors.
+ * Copyright 2006-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,12 +14,12 @@
* limitations under the License.
*/
-package org.springframework.batch.core.explore.support;
+package org.springframework.batch.core.repository.explore.support;
import java.util.Properties;
import org.springframework.aop.framework.ProxyFactory;
-import org.springframework.batch.core.explore.JobExplorer;
+import org.springframework.batch.core.repository.explore.JobExplorer;
import org.springframework.batch.core.repository.dao.ExecutionContextDao;
import org.springframework.batch.core.repository.dao.JobExecutionDao;
import org.springframework.batch.core.repository.dao.JobInstanceDao;
@@ -43,7 +43,9 @@
* @author Dave Syer
* @author Mahmoud Ben Hassine
* @since 2.0
+ * @deprecated since 6.0 with no replacement. Scheduled for removal in 6.2 or later.
*/
+@Deprecated(since = "6.0", forRemoval = true)
public abstract class AbstractJobExplorerFactoryBean implements FactoryBean, InitializingBean {
private static final String TRANSACTION_ISOLATION_LEVEL_PREFIX = "ISOLATION_";
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/explore/support/JdbcJobExplorerFactoryBean.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/explore/support/JdbcJobExplorerFactoryBean.java
new file mode 100644
index 0000000000..495fff19e3
--- /dev/null
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/explore/support/JdbcJobExplorerFactoryBean.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2002-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.batch.core.repository.explore.support;
+
+import org.springframework.batch.core.repository.support.JdbcJobRepositoryFactoryBean;
+import org.springframework.beans.factory.FactoryBean;
+
+/**
+ * A {@link FactoryBean} that automates the creation of a {@link SimpleJobExplorer} by
+ * using JDBC DAO implementations. Requires the user to describe what kind of database
+ * they use.
+ *
+ * @author Dave Syer
+ * @author Mahmoud Ben Hassine
+ * @deprecated since 6.0 in favor of {@link JdbcJobRepositoryFactoryBean}. Scheduled for
+ * removal in 6.2 or later.
+ */
+@SuppressWarnings("removal")
+@Deprecated(since = "6.0", forRemoval = true)
+public class JdbcJobExplorerFactoryBean extends JobExplorerFactoryBean {
+
+}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/explore/support/JobExplorerFactoryBean.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/explore/support/JobExplorerFactoryBean.java
similarity index 82%
rename from spring-batch-core/src/main/java/org/springframework/batch/core/explore/support/JobExplorerFactoryBean.java
rename to spring-batch-core/src/main/java/org/springframework/batch/core/repository/explore/support/JobExplorerFactoryBean.java
index 9d3e24dae5..98da269d85 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/explore/support/JobExplorerFactoryBean.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/explore/support/JobExplorerFactoryBean.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2024 the original author or authors.
+ * Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,15 +14,15 @@
* limitations under the License.
*/
-package org.springframework.batch.core.explore.support;
+package org.springframework.batch.core.repository.explore.support;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import javax.sql.DataSource;
-import org.springframework.batch.core.DefaultJobKeyGenerator;
-import org.springframework.batch.core.JobKeyGenerator;
+import org.springframework.batch.core.job.DefaultJobKeyGenerator;
+import org.springframework.batch.core.job.JobKeyGenerator;
import org.springframework.batch.core.converter.DateToStringConverter;
import org.springframework.batch.core.converter.LocalDateTimeToStringConverter;
import org.springframework.batch.core.converter.LocalDateToStringConverter;
@@ -35,14 +35,14 @@
import org.springframework.batch.core.repository.dao.AbstractJdbcBatchMetadataDao;
import org.springframework.batch.core.repository.dao.DefaultExecutionContextSerializer;
import org.springframework.batch.core.repository.dao.ExecutionContextDao;
-import org.springframework.batch.core.repository.dao.JdbcExecutionContextDao;
-import org.springframework.batch.core.repository.dao.JdbcJobExecutionDao;
-import org.springframework.batch.core.repository.dao.JdbcJobInstanceDao;
-import org.springframework.batch.core.repository.dao.JdbcStepExecutionDao;
+import org.springframework.batch.core.repository.dao.jdbc.JdbcExecutionContextDao;
+import org.springframework.batch.core.repository.dao.jdbc.JdbcJobExecutionDao;
+import org.springframework.batch.core.repository.dao.jdbc.JdbcJobInstanceDao;
+import org.springframework.batch.core.repository.dao.jdbc.JdbcStepExecutionDao;
import org.springframework.batch.core.repository.dao.JobExecutionDao;
import org.springframework.batch.core.repository.dao.JobInstanceDao;
import org.springframework.batch.core.repository.dao.StepExecutionDao;
-import org.springframework.batch.item.ExecutionContext;
+import org.springframework.batch.core.repository.support.JdbcJobRepositoryFactoryBean;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.convert.support.ConfigurableConversionService;
@@ -51,7 +51,6 @@
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.support.incrementer.AbstractDataFieldMaxValueIncrementer;
import org.springframework.jdbc.support.incrementer.DataFieldMaxValueIncrementer;
-import org.springframework.jdbc.support.lob.LobHandler;
import org.springframework.lang.NonNull;
import org.springframework.util.Assert;
@@ -63,31 +62,32 @@
* @author Dave Syer
* @author Mahmoud Ben Hassine
* @since 2.0
+ * @deprecated since 6.0 in favor of {@link JdbcJobRepositoryFactoryBean}. Scheduled for
+ * removal in 6.2 or later.
*/
+@Deprecated(since = "6.0", forRemoval = true)
public class JobExplorerFactoryBean extends AbstractJobExplorerFactoryBean implements InitializingBean {
- private DataSource dataSource;
+ protected DataSource dataSource;
- private JdbcOperations jdbcOperations;
+ protected JdbcOperations jdbcOperations;
- private String tablePrefix = AbstractJdbcBatchMetadataDao.DEFAULT_TABLE_PREFIX;
+ protected String tablePrefix = AbstractJdbcBatchMetadataDao.DEFAULT_TABLE_PREFIX;
- private final DataFieldMaxValueIncrementer incrementer = new AbstractDataFieldMaxValueIncrementer() {
+ protected final DataFieldMaxValueIncrementer incrementer = new AbstractDataFieldMaxValueIncrementer() {
@Override
protected long getNextKey() {
throw new IllegalStateException("JobExplorer is read only.");
}
};
- private JobKeyGenerator jobKeyGenerator;
+ protected JobKeyGenerator jobKeyGenerator;
- private LobHandler lobHandler;
+ protected ExecutionContextSerializer serializer;
- private ExecutionContextSerializer serializer;
+ protected Charset charset = StandardCharsets.UTF_8;
- private Charset charset = StandardCharsets.UTF_8;
-
- private ConfigurableConversionService conversionService;
+ protected ConfigurableConversionService conversionService;
/**
* A custom implementation of {@link ExecutionContextSerializer}. The default, if not
@@ -138,18 +138,6 @@ public void setJobKeyGenerator(JobKeyGenerator jobKeyGenerator) {
this.jobKeyGenerator = jobKeyGenerator;
}
- /**
- * The lob handler to use when saving {@link ExecutionContext} instances. Defaults to
- * {@code null}, which works for most databases.
- * @param lobHandler Large object handler for saving an
- * {@link org.springframework.batch.item.ExecutionContext}.
- * @deprecated Since 5.2 with no replacement. Scheduled for removal in v6
- */
- @Deprecated(since = "5.2.0", forRemoval = true)
- public void setLobHandler(LobHandler lobHandler) {
- this.lobHandler = lobHandler;
- }
-
/**
* Sets the {@link Charset} to use when deserializing the execution context. Defaults
* to "UTF-8". Must not be {@code null}.
@@ -210,7 +198,6 @@ public void afterPropertiesSet() throws Exception {
protected ExecutionContextDao createExecutionContextDao() throws Exception {
JdbcExecutionContextDao dao = new JdbcExecutionContextDao();
dao.setJdbcTemplate(jdbcOperations);
- dao.setLobHandler(lobHandler);
dao.setTablePrefix(tablePrefix);
dao.setSerializer(serializer);
dao.setCharset(charset);
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/explore/support/MongoJobExplorerFactoryBean.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/explore/support/MongoJobExplorerFactoryBean.java
similarity index 61%
rename from spring-batch-core/src/main/java/org/springframework/batch/core/explore/support/MongoJobExplorerFactoryBean.java
rename to spring-batch-core/src/main/java/org/springframework/batch/core/repository/explore/support/MongoJobExplorerFactoryBean.java
index 8b24b7febb..13997a0fd7 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/explore/support/MongoJobExplorerFactoryBean.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/explore/support/MongoJobExplorerFactoryBean.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2024 the original author or authors.
+ * Copyright 2024-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,24 +13,36 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.springframework.batch.core.explore.support;
+package org.springframework.batch.core.repository.explore.support;
import org.springframework.batch.core.repository.dao.ExecutionContextDao;
import org.springframework.batch.core.repository.dao.JobExecutionDao;
import org.springframework.batch.core.repository.dao.JobInstanceDao;
import org.springframework.batch.core.repository.dao.StepExecutionDao;
-import org.springframework.batch.core.repository.dao.MongoExecutionContextDao;
-import org.springframework.batch.core.repository.dao.MongoJobExecutionDao;
-import org.springframework.batch.core.repository.dao.MongoJobInstanceDao;
-import org.springframework.batch.core.repository.dao.MongoStepExecutionDao;
+import org.springframework.batch.core.repository.dao.mongodb.MongoExecutionContextDao;
+import org.springframework.batch.core.repository.dao.mongodb.MongoJobExecutionDao;
+import org.springframework.batch.core.repository.dao.mongodb.MongoJobInstanceDao;
+import org.springframework.batch.core.repository.dao.mongodb.MongoStepExecutionDao;
+import org.springframework.batch.core.repository.support.MongoJobRepositoryFactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.data.mongodb.core.MongoOperations;
+import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
import org.springframework.util.Assert;
/**
+ * This factory bean creates a job explorer backed by MongoDB. It requires a mongo
+ * template and a mongo transaction manager. The mongo template must be configured
+ * with a {@link MappingMongoConverter} having a {@code MapKeyDotReplacement} set to a non
+ * null value. See {@code MongoDBJobRepositoryIntegrationTests} for an example. This is
+ * required to support execution context keys containing dots (like "step.type" or
+ * "batch.version")
+ *
* @author Mahmoud Ben Hassine
* @since 5.2.0
+ * @deprecated since 6.0 in favor of {@link MongoJobRepositoryFactoryBean}. Scheduled for
+ * removal in 6.2 or later.
*/
+@Deprecated(since = "6.0", forRemoval = true)
public class MongoJobExplorerFactoryBean extends AbstractJobExplorerFactoryBean implements InitializingBean {
private MongoOperations mongoOperations;
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/explore/support/SimpleJobExplorer.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/explore/support/SimpleJobExplorer.java
similarity index 60%
rename from spring-batch-core/src/main/java/org/springframework/batch/core/explore/support/SimpleJobExplorer.java
rename to spring-batch-core/src/main/java/org/springframework/batch/core/repository/explore/support/SimpleJobExplorer.java
index 236be9902d..5e846060a3 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/explore/support/SimpleJobExplorer.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/explore/support/SimpleJobExplorer.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006-2023 the original author or authors.
+ * Copyright 2006-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,18 +14,20 @@
* limitations under the License.
*/
-package org.springframework.batch.core.explore.support;
+package org.springframework.batch.core.repository.explore.support;
-import org.springframework.batch.core.JobExecution;
-import org.springframework.batch.core.JobInstance;
-import org.springframework.batch.core.JobParameters;
-import org.springframework.batch.core.StepExecution;
-import org.springframework.batch.core.explore.JobExplorer;
+import org.springframework.batch.core.job.JobExecution;
+import org.springframework.batch.core.job.JobInstance;
+import org.springframework.batch.core.job.parameters.JobParameters;
+import org.springframework.batch.core.step.StepExecution;
+import org.springframework.batch.core.repository.explore.JobExplorer;
import org.springframework.batch.core.launch.NoSuchJobException;
import org.springframework.batch.core.repository.dao.ExecutionContextDao;
import org.springframework.batch.core.repository.dao.JobExecutionDao;
import org.springframework.batch.core.repository.dao.JobInstanceDao;
import org.springframework.batch.core.repository.dao.StepExecutionDao;
+import org.springframework.batch.core.repository.support.SimpleJobRepository;
+import org.springframework.batch.item.ExecutionContext;
import org.springframework.lang.Nullable;
import java.util.List;
@@ -46,23 +48,20 @@
* @see JobExecutionDao
* @see StepExecutionDao
* @since 2.0
+ * @deprecated since 6.0 in favor of {@link SimpleJobRepository}. Scheduled for removal in
+ * 6.2 or later.
*/
+@SuppressWarnings("removal")
+@Deprecated(since = "6.0", forRemoval = true)
public class SimpleJobExplorer implements JobExplorer {
- private JobInstanceDao jobInstanceDao;
+ protected JobInstanceDao jobInstanceDao;
- private JobExecutionDao jobExecutionDao;
+ protected JobExecutionDao jobExecutionDao;
- private StepExecutionDao stepExecutionDao;
+ protected StepExecutionDao stepExecutionDao;
- private ExecutionContextDao ecDao;
-
- /**
- * Provides a default constructor with low visibility in case you want to use
- * aop:proxy-target-class="true" for the AOP interceptor.
- */
- SimpleJobExplorer() {
- }
+ protected ExecutionContextDao ecDao;
/**
* Constructor to initialize the job {@link SimpleJobExplorer}.
@@ -80,6 +79,79 @@ public SimpleJobExplorer(JobInstanceDao jobInstanceDao, JobExecutionDao jobExecu
this.ecDao = ecDao;
}
+ /*
+ * ===================================================================================
+ * Job operations
+ * ===================================================================================
+ */
+
+ @Override
+ public List getJobNames() {
+ return jobInstanceDao.getJobNames();
+ }
+
+ /*
+ * ===================================================================================
+ * Job instance operations
+ * ===================================================================================
+ */
+
+ @Override
+ @Deprecated(since = "6.0", forRemoval = true)
+ public boolean isJobInstanceExists(String jobName, JobParameters jobParameters) {
+ return jobInstanceDao.getJobInstance(jobName, jobParameters) != null;
+ }
+
+ /**
+ * @deprecated since v6.0 and scheduled for removal in v6.2. Use
+ * {@link #getJobInstances(String, int, int)} instead.
+ */
+ @Deprecated(since = "6.0", forRemoval = true)
+ @Override
+ public List findJobInstancesByJobName(String jobName, int start, int count) {
+ return getJobInstances(jobName, start, count);
+ }
+
+ @Override
+ @Deprecated(since = "6.0", forRemoval = true)
+ public List findJobInstancesByName(String jobName, int start, int count) {
+ return getJobInstances(jobName, start, count);
+ }
+
+ @Nullable
+ @Override
+ public JobInstance getJobInstance(@Nullable Long instanceId) {
+ return jobInstanceDao.getJobInstance(instanceId);
+ }
+
+ @Nullable
+ @Override
+ public JobInstance getJobInstance(String jobName, JobParameters jobParameters) {
+ return jobInstanceDao.getJobInstance(jobName, jobParameters);
+ }
+
+ @Nullable
+ @Override
+ public JobInstance getLastJobInstance(String jobName) {
+ return jobInstanceDao.getLastJobInstance(jobName);
+ }
+
+ @Override
+ public List getJobInstances(String jobName, int start, int count) {
+ return jobInstanceDao.getJobInstances(jobName, start, count);
+ }
+
+ @Override
+ public long getJobInstanceCount(@Nullable String jobName) throws NoSuchJobException {
+ return jobInstanceDao.getJobInstanceCount(jobName);
+ }
+
+ /*
+ * ===================================================================================
+ * Job execution operations
+ * ===================================================================================
+ */
+
@Override
public List getJobExecutions(JobInstance jobInstance) {
List executions = jobExecutionDao.findJobExecutions(jobInstance);
@@ -105,6 +177,32 @@ public JobExecution getLastJobExecution(JobInstance jobInstance) {
return lastJobExecution;
}
+ @Deprecated(since = "6.0", forRemoval = true)
+ @Override
+ public List findJobExecutions(JobInstance jobInstance) {
+ List jobExecutions = this.jobExecutionDao.findJobExecutions(jobInstance);
+ for (JobExecution jobExecution : jobExecutions) {
+ this.stepExecutionDao.addStepExecutions(jobExecution);
+ }
+ return jobExecutions;
+ }
+
+ @Override
+ @Nullable
+ public JobExecution getLastJobExecution(String jobName, JobParameters jobParameters) {
+ JobInstance jobInstance = jobInstanceDao.getJobInstance(jobName, jobParameters);
+ if (jobInstance == null) {
+ return null;
+ }
+ JobExecution jobExecution = jobExecutionDao.getLastJobExecution(jobInstance);
+
+ if (jobExecution != null) {
+ jobExecution.setExecutionContext(ecDao.getExecutionContext(jobExecution));
+ stepExecutionDao.addStepExecutions(jobExecution);
+ }
+ return jobExecution;
+ }
+
@Override
public Set findRunningJobExecutions(@Nullable String jobName) {
Set executions = jobExecutionDao.findRunningJobExecutions(jobName);
@@ -134,6 +232,24 @@ public JobExecution getJobExecution(@Nullable Long executionId) {
return jobExecution;
}
+ /*
+ * Find all dependencies for a JobExecution, including JobInstance (which requires
+ * JobParameters) plus StepExecutions
+ */
+ private void getJobExecutionDependencies(JobExecution jobExecution) {
+ JobInstance jobInstance = jobInstanceDao.getJobInstance(jobExecution);
+ stepExecutionDao.addStepExecutions(jobExecution);
+ jobExecution.setJobInstance(jobInstance);
+ jobExecution.setExecutionContext(ecDao.getExecutionContext(jobExecution));
+
+ }
+
+ /*
+ * ===================================================================================
+ * Step execution operations
+ * ===================================================================================
+ */
+
@Nullable
@Override
public StepExecution getStepExecution(@Nullable Long jobExecutionId, @Nullable Long executionId) {
@@ -147,39 +263,41 @@ public StepExecution getStepExecution(@Nullable Long jobExecutionId, @Nullable L
return stepExecution;
}
- @Nullable
@Override
- public JobInstance getJobInstance(@Nullable Long instanceId) {
- return jobInstanceDao.getJobInstance(instanceId);
- }
-
@Nullable
- @Override
- public JobInstance getJobInstance(String jobName, JobParameters jobParameters) {
- return jobInstanceDao.getJobInstance(jobName, jobParameters);
- }
+ public StepExecution getLastStepExecution(JobInstance jobInstance, String stepName) {
+ StepExecution latest = stepExecutionDao.getLastStepExecution(jobInstance, stepName);
- @Nullable
- @Override
- public JobInstance getLastJobInstance(String jobName) {
- return jobInstanceDao.getLastJobInstance(jobName);
- }
+ if (latest != null) {
+ ExecutionContext stepExecutionContext = ecDao.getExecutionContext(latest);
+ latest.setExecutionContext(stepExecutionContext);
+ ExecutionContext jobExecutionContext = ecDao.getExecutionContext(latest.getJobExecution());
+ latest.getJobExecution().setExecutionContext(jobExecutionContext);
+ }
- @Override
- public List getJobInstances(String jobName, int start, int count) {
- return jobInstanceDao.getJobInstances(jobName, start, count);
+ return latest;
}
+ /**
+ * @return number of executions of the step within given job instance
+ */
@Override
- public List getJobNames() {
- return jobInstanceDao.getJobNames();
+ public long getStepExecutionCount(JobInstance jobInstance, String stepName) {
+ return stepExecutionDao.countStepExecutions(jobInstance, stepName);
}
- @Override
- public long getJobInstanceCount(@Nullable String jobName) throws NoSuchJobException {
- return jobInstanceDao.getJobInstanceCount(jobName);
+ private void getStepExecutionDependencies(StepExecution stepExecution) {
+ if (stepExecution != null) {
+ stepExecution.setExecutionContext(ecDao.getExecutionContext(stepExecution));
+ }
}
+ /*
+ * ===================================================================================
+ * protected methods
+ * ===================================================================================
+ */
+
/**
* @return instance of {@link JobInstanceDao}.
* @since 5.1
@@ -212,27 +330,4 @@ protected ExecutionContextDao getEcDao() {
return ecDao;
}
- /*
- * Find all dependencies for a JobExecution, including JobInstance (which requires
- * JobParameters) plus StepExecutions
- */
- private void getJobExecutionDependencies(JobExecution jobExecution) {
- JobInstance jobInstance = jobInstanceDao.getJobInstance(jobExecution);
- stepExecutionDao.addStepExecutions(jobExecution);
- jobExecution.setJobInstance(jobInstance);
- jobExecution.setExecutionContext(ecDao.getExecutionContext(jobExecution));
-
- }
-
- private void getStepExecutionDependencies(StepExecution stepExecution) {
- if (stepExecution != null) {
- stepExecution.setExecutionContext(ecDao.getExecutionContext(stepExecution));
- }
- }
-
- @Override
- public List findJobInstancesByJobName(String jobName, int start, int count) {
- return jobInstanceDao.findJobInstancesByName(jobName, start, count);
- }
-
}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/explore/support/package-info.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/explore/support/package-info.java
similarity index 72%
rename from spring-batch-core/src/main/java/org/springframework/batch/core/explore/support/package-info.java
rename to spring-batch-core/src/main/java/org/springframework/batch/core/repository/explore/support/package-info.java
index 6150d736cb..44b0a8f465 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/explore/support/package-info.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/explore/support/package-info.java
@@ -5,6 +5,6 @@
* @author Mahmoud Ben Hassine
*/
@NonNullApi
-package org.springframework.batch.core.explore.support;
+package org.springframework.batch.core.repository.explore.support;
import org.springframework.lang.NonNullApi;
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/persistence/converter/JobExecutionConverter.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/persistence/converter/JobExecutionConverter.java
index 686c48464c..3239b485b9 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/persistence/converter/JobExecutionConverter.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/persistence/converter/JobExecutionConverter.java
@@ -18,8 +18,8 @@
import java.util.HashMap;
import java.util.Map;
-import org.springframework.batch.core.JobInstance;
-import org.springframework.batch.core.JobParameters;
+import org.springframework.batch.core.job.JobInstance;
+import org.springframework.batch.core.job.parameters.JobParameters;
import org.springframework.batch.core.repository.persistence.ExecutionContext;
import org.springframework.batch.core.repository.persistence.ExitStatus;
import org.springframework.batch.core.repository.persistence.JobExecution;
@@ -35,11 +35,12 @@ public class JobExecutionConverter {
private final StepExecutionConverter stepExecutionConverter = new StepExecutionConverter();
- public org.springframework.batch.core.JobExecution toJobExecution(JobExecution source, JobInstance jobInstance) {
- Map> parameterMap = new HashMap<>();
+ public org.springframework.batch.core.job.JobExecution toJobExecution(JobExecution source,
+ JobInstance jobInstance) {
+ Map> parameterMap = new HashMap<>();
source.getJobParameters()
.forEach((key, value) -> parameterMap.put(key, this.jobParameterConverter.toJobParameter(value)));
- org.springframework.batch.core.JobExecution jobExecution = new org.springframework.batch.core.JobExecution(
+ org.springframework.batch.core.job.JobExecution jobExecution = new org.springframework.batch.core.job.JobExecution(
jobInstance, source.getJobExecutionId(), new JobParameters(parameterMap));
jobExecution.addStepExecutions(source.getStepExecutions()
.stream()
@@ -57,7 +58,7 @@ public org.springframework.batch.core.JobExecution toJobExecution(JobExecution s
return jobExecution;
}
- public JobExecution fromJobExecution(org.springframework.batch.core.JobExecution source) {
+ public JobExecution fromJobExecution(org.springframework.batch.core.job.JobExecution source) {
JobExecution jobExecution = new JobExecution();
jobExecution.setJobExecutionId(source.getId());
jobExecution.setJobInstanceId(source.getJobInstance().getInstanceId());
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/persistence/converter/JobInstanceConverter.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/persistence/converter/JobInstanceConverter.java
index 82b3a277de..a52f23ee75 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/persistence/converter/JobInstanceConverter.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/persistence/converter/JobInstanceConverter.java
@@ -23,11 +23,11 @@
*/
public class JobInstanceConverter {
- public org.springframework.batch.core.JobInstance toJobInstance(JobInstance source) {
- return new org.springframework.batch.core.JobInstance(source.getJobInstanceId(), source.getJobName());
+ public org.springframework.batch.core.job.JobInstance toJobInstance(JobInstance source) {
+ return new org.springframework.batch.core.job.JobInstance(source.getJobInstanceId(), source.getJobName());
}
- public JobInstance fromJobInstance(org.springframework.batch.core.JobInstance source) {
+ public JobInstance fromJobInstance(org.springframework.batch.core.job.JobInstance source) {
JobInstance jobInstance = new JobInstance();
jobInstance.setJobName(source.getJobName());
jobInstance.setJobInstanceId(source.getInstanceId());
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/persistence/converter/JobParameterConverter.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/persistence/converter/JobParameterConverter.java
index 361c98c36b..dfa6a89b82 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/persistence/converter/JobParameterConverter.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/persistence/converter/JobParameterConverter.java
@@ -23,9 +23,9 @@
*/
public class JobParameterConverter {
- public org.springframework.batch.core.JobParameter toJobParameter(JobParameter source) {
+ public org.springframework.batch.core.job.parameters.JobParameter toJobParameter(JobParameter source) {
try {
- return new org.springframework.batch.core.JobParameter<>(source.value(),
+ return new org.springframework.batch.core.job.parameters.JobParameter<>(source.value(),
(Class) Class.forName(source.type()), source.identifying());
}
catch (ClassNotFoundException e) {
@@ -33,7 +33,7 @@ public org.springframework.batch.core.JobParameter toJobParameter(JobPara
}
}
- public JobParameter fromJobParameter(org.springframework.batch.core.JobParameter source) {
+ public JobParameter fromJobParameter(org.springframework.batch.core.job.parameters.JobParameter source) {
return new JobParameter<>(source.getValue(), source.getType().getName(), source.isIdentifying());
}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/persistence/converter/StepExecutionConverter.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/persistence/converter/StepExecutionConverter.java
index 221e9c50cf..785cd2456f 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/persistence/converter/StepExecutionConverter.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/persistence/converter/StepExecutionConverter.java
@@ -15,7 +15,7 @@
*/
package org.springframework.batch.core.repository.persistence.converter;
-import org.springframework.batch.core.JobExecution;
+import org.springframework.batch.core.job.JobExecution;
import org.springframework.batch.core.repository.persistence.ExecutionContext;
import org.springframework.batch.core.repository.persistence.ExitStatus;
import org.springframework.batch.core.repository.persistence.StepExecution;
@@ -26,9 +26,9 @@
*/
public class StepExecutionConverter {
- public org.springframework.batch.core.StepExecution toStepExecution(StepExecution source,
+ public org.springframework.batch.core.step.StepExecution toStepExecution(StepExecution source,
JobExecution jobExecution) {
- org.springframework.batch.core.StepExecution stepExecution = new org.springframework.batch.core.StepExecution(
+ org.springframework.batch.core.step.StepExecution stepExecution = new org.springframework.batch.core.step.StepExecution(
source.getName(), jobExecution, source.getStepExecutionId());
stepExecution.setStatus(source.getStatus());
stepExecution.setReadCount(source.getReadCount());
@@ -53,7 +53,7 @@ public org.springframework.batch.core.StepExecution toStepExecution(StepExecutio
return stepExecution;
}
- public StepExecution fromStepExecution(org.springframework.batch.core.StepExecution source) {
+ public StepExecution fromStepExecution(org.springframework.batch.core.step.StepExecution source) {
StepExecution stepExecution = new StepExecution();
stepExecution.setStepExecutionId(source.getId());
stepExecution.setJobExecutionId(source.getJobExecutionId());
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/support/AbstractJobRepositoryFactoryBean.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/support/AbstractJobRepositoryFactoryBean.java
index 639a034ebd..50a5ef4186 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/support/AbstractJobRepositoryFactoryBean.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/support/AbstractJobRepositoryFactoryBean.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006-2023 the original author or authors.
+ * Copyright 2006-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,6 +22,9 @@
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.aop.support.NameMatchMethodPointcut;
+import org.springframework.batch.core.job.DefaultJobKeyGenerator;
+import org.springframework.batch.core.job.JobKeyGenerator;
+import org.springframework.batch.core.job.parameters.JobParameters;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.repository.dao.ExecutionContextDao;
import org.springframework.batch.core.repository.dao.JobExecutionDao;
@@ -43,7 +46,8 @@
* A {@link FactoryBean} that automates the creation of a {@link SimpleJobRepository}.
* Declares abstract methods for providing DAO object implementations.
*
- * @see JobRepositoryFactoryBean
+ * @see JdbcJobRepositoryFactoryBean
+ * @see MongoJobRepositoryFactoryBean
* @author Ben Hale
* @author Lucas Ward
* @author Robert Kasanicky
@@ -70,6 +74,8 @@ public abstract class AbstractJobRepositoryFactoryBean implements FactoryBeanThe mongo template must be configured
+ * with a {@link MappingMongoConverter} having a {@code MapKeyDotReplacement} set to a non
+ * null value. See {@code MongoDBJobRepositoryIntegrationTests} for an example. This is
+ * required to support execution context keys containing dots (like "step.type" or
+ * "batch.version")
+ *
* @author Mahmoud Ben Hassine
* @since 5.2.0
*/
@@ -41,7 +49,9 @@ public void setMongoOperations(MongoOperations mongoOperations) {
@Override
protected JobInstanceDao createJobInstanceDao() {
- return new MongoJobInstanceDao(this.mongoOperations);
+ MongoJobInstanceDao mongoJobInstanceDao = new MongoJobInstanceDao(this.mongoOperations);
+ mongoJobInstanceDao.setJobKeyGenerator(this.jobKeyGenerator);
+ return mongoJobInstanceDao;
}
@Override
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/support/ResourcelessJobRepository.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/support/ResourcelessJobRepository.java
index 9fb6b33dd8..694cd471a7 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/support/ResourcelessJobRepository.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/support/ResourcelessJobRepository.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2024 the original author or authors.
+ * Copyright 2024-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,16 +16,17 @@
package org.springframework.batch.core.repository.support;
import java.time.LocalDateTime;
-import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.List;
-import org.springframework.batch.core.JobExecution;
-import org.springframework.batch.core.JobInstance;
-import org.springframework.batch.core.JobParameters;
-import org.springframework.batch.core.StepExecution;
+import org.springframework.batch.core.job.JobExecution;
+import org.springframework.batch.core.job.JobInstance;
+import org.springframework.batch.core.job.parameters.JobParameters;
+import org.springframework.batch.core.step.StepExecution;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.support.transaction.ResourcelessTransactionManager;
+import org.springframework.lang.Nullable;
/**
* A {@link JobRepository} implementation that does not use or store batch meta-data. It
@@ -49,53 +50,117 @@ public class ResourcelessJobRepository implements JobRepository {
private JobExecution jobExecution;
+ private long stepExecutionIdIncrementer = 0L;
+
+ /*
+ * ===================================================================================
+ * Job operations
+ * ===================================================================================
+ */
+
+ @Override
+ public List getJobNames() {
+ if (this.jobInstance == null) {
+ return Collections.emptyList();
+ }
+ return Collections.singletonList(this.jobInstance.getJobName());
+ }
+
+ /*
+ * ===================================================================================
+ * Job instance operations
+ * ===================================================================================
+ */
+
+ @Override
+ public List getJobInstances(String jobName, int start, int count) {
+ if (this.jobInstance == null) {
+ return Collections.emptyList();
+ }
+ return Collections.singletonList(this.jobInstance);
+ }
+
+ @Override
+ @Nullable
+ public JobInstance getJobInstance(@Nullable Long instanceId) {
+ return this.jobInstance;
+ }
+
+ @Override
+ @Nullable
+ public JobInstance getLastJobInstance(String jobName) {
+ return this.jobInstance;
+ }
+
+ @Override
+ @Nullable
+ public JobInstance getJobInstance(String jobName, JobParameters jobParameters) {
+ return this.jobInstance;
+ }
+
+ @SuppressWarnings("removal")
@Override
+ @Deprecated(since = "6.0", forRemoval = true)
public boolean isJobInstanceExists(String jobName, JobParameters jobParameters) {
return false;
}
+ @Override
+ public long getJobInstanceCount(String jobName) {
+ return 1;
+ }
+
@Override
public JobInstance createJobInstance(String jobName, JobParameters jobParameters) {
this.jobInstance = new JobInstance(1L, jobName);
return this.jobInstance;
}
+ /*
+ * ===================================================================================
+ * Job execution operations
+ * ===================================================================================
+ */
+
@Override
- public JobExecution createJobExecution(String jobName, JobParameters jobParameters) {
- if (this.jobInstance == null) {
- createJobInstance(jobName, jobParameters);
- }
- this.jobExecution = new JobExecution(this.jobInstance, 1L, jobParameters);
+ @Nullable
+ public JobExecution getJobExecution(Long executionId) {
return this.jobExecution;
}
@Override
- public void update(JobExecution jobExecution) {
- jobExecution.setLastUpdated(LocalDateTime.now());
- this.jobExecution = jobExecution;
+ @Nullable
+ public JobExecution getLastJobExecution(String jobName, JobParameters jobParameters) {
+ return this.jobExecution;
}
@Override
- public void add(StepExecution stepExecution) {
- this.addAll(Collections.singletonList(stepExecution));
+ @Nullable
+ public JobExecution getLastJobExecution(JobInstance jobInstance) {
+ return this.jobExecution;
}
@Override
- public void addAll(Collection stepExecutions) {
- this.jobExecution.addStepExecutions(new ArrayList<>(stepExecutions));
+ public List getJobExecutions(JobInstance jobInstance) {
+ if (this.jobExecution == null) {
+ return Collections.emptyList();
+ }
+ return Collections.singletonList(this.jobExecution);
}
@Override
- public void update(StepExecution stepExecution) {
- stepExecution.setLastUpdated(LocalDateTime.now());
- if (this.jobExecution.isStopping()) {
- stepExecution.setTerminateOnly();
+ public JobExecution createJobExecution(String jobName, JobParameters jobParameters) {
+ if (this.jobInstance == null) {
+ createJobInstance(jobName, jobParameters);
}
+ this.jobExecution = new JobExecution(this.jobInstance, 1L, jobParameters);
+ return this.jobExecution;
}
@Override
- public void updateExecutionContext(StepExecution stepExecution) {
- stepExecution.setLastUpdated(LocalDateTime.now());
+ public void update(JobExecution jobExecution) {
+ jobExecution.setLastUpdated(LocalDateTime.now());
+ this.jobExecution = jobExecution;
}
@Override
@@ -103,8 +168,31 @@ public void updateExecutionContext(JobExecution jobExecution) {
jobExecution.setLastUpdated(LocalDateTime.now());
}
+ /*
+ * ===================================================================================
+ * Step execution operations
+ * ===================================================================================
+ */
+
+ @Override
+ @Nullable
+ public StepExecution getStepExecution(Long jobExecutionId, Long stepExecutionId) {
+ if (this.jobExecution == null || !this.jobExecution.getId().equals(jobExecutionId)) {
+ return null;
+ }
+ return this.jobExecution.getStepExecutions()
+ .stream()
+ .filter(stepExecution -> stepExecution.getId().equals(stepExecutionId))
+ .findFirst()
+ .orElse(null);
+ }
+
@Override
+ @Nullable
public StepExecution getLastStepExecution(JobInstance jobInstance, String stepName) {
+ if (this.jobExecution == null || !this.jobExecution.getJobInstance().getId().equals(jobInstance.getId())) {
+ return null;
+ }
return this.jobExecution.getStepExecutions()
.stream()
.filter(stepExecution -> stepExecution.getStepName().equals(stepName))
@@ -121,8 +209,28 @@ public long getStepExecutionCount(JobInstance jobInstance, String stepName) {
}
@Override
- public JobExecution getLastJobExecution(String jobName, JobParameters jobParameters) {
- return this.jobExecution;
+ public void add(StepExecution stepExecution) {
+ stepExecution.setId(this.stepExecutionIdIncrementer++);
+ }
+
+ @Override
+ public void addAll(Collection stepExecutions) {
+ for (StepExecution stepExecution : stepExecutions) {
+ this.add(stepExecution);
+ }
+ }
+
+ @Override
+ public void update(StepExecution stepExecution) {
+ stepExecution.setLastUpdated(LocalDateTime.now());
+ if (this.jobExecution.isStopping()) {
+ stepExecution.setTerminateOnly();
+ }
+ }
+
+ @Override
+ public void updateExecutionContext(StepExecution stepExecution) {
+ stepExecution.setLastUpdated(LocalDateTime.now());
}
}
\ No newline at end of file
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/support/SimpleJobRepository.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/support/SimpleJobRepository.java
index e98752c987..202e024d23 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/support/SimpleJobRepository.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/support/SimpleJobRepository.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006-2024 the original author or authors.
+ * Copyright 2006-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,10 +19,11 @@
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.batch.core.BatchStatus;
-import org.springframework.batch.core.JobExecution;
-import org.springframework.batch.core.JobInstance;
-import org.springframework.batch.core.JobParameters;
-import org.springframework.batch.core.StepExecution;
+import org.springframework.batch.core.job.JobExecution;
+import org.springframework.batch.core.job.JobInstance;
+import org.springframework.batch.core.job.parameters.JobParameters;
+import org.springframework.batch.core.step.StepExecution;
+import org.springframework.batch.core.repository.explore.support.SimpleJobExplorer;
import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException;
import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException;
import org.springframework.batch.core.repository.JobRepository;
@@ -32,7 +33,6 @@
import org.springframework.batch.core.repository.dao.JobInstanceDao;
import org.springframework.batch.core.repository.dao.StepExecutionDao;
import org.springframework.batch.item.ExecutionContext;
-import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import java.time.LocalDateTime;
@@ -60,56 +60,14 @@
* @see StepExecutionDao
*
*/
-public class SimpleJobRepository implements JobRepository {
+@SuppressWarnings("removal")
+public class SimpleJobRepository extends SimpleJobExplorer implements JobRepository {
private static final Log logger = LogFactory.getLog(SimpleJobRepository.class);
- private JobInstanceDao jobInstanceDao;
-
- private JobExecutionDao jobExecutionDao;
-
- private StepExecutionDao stepExecutionDao;
-
- private ExecutionContextDao ecDao;
-
- /**
- * Provide default constructor with low visibility in case user wants to use
- * aop:proxy-target-class="true" for AOP interceptor.
- */
- SimpleJobRepository() {
- }
-
public SimpleJobRepository(JobInstanceDao jobInstanceDao, JobExecutionDao jobExecutionDao,
StepExecutionDao stepExecutionDao, ExecutionContextDao ecDao) {
- super();
- this.jobInstanceDao = jobInstanceDao;
- this.jobExecutionDao = jobExecutionDao;
- this.stepExecutionDao = stepExecutionDao;
- this.ecDao = ecDao;
- }
-
- @Override
- public List getJobNames() {
- return this.jobInstanceDao.getJobNames();
- }
-
- @Override
- public List findJobInstancesByName(String jobName, int start, int count) {
- return this.jobInstanceDao.findJobInstancesByName(jobName, start, count);
- }
-
- @Override
- public List findJobExecutions(JobInstance jobInstance) {
- List jobExecutions = this.jobExecutionDao.findJobExecutions(jobInstance);
- for (JobExecution jobExecution : jobExecutions) {
- this.stepExecutionDao.addStepExecutions(jobExecution);
- }
- return jobExecutions;
- }
-
- @Override
- public boolean isJobInstanceExists(String jobName, JobParameters jobParameters) {
- return jobInstanceDao.getJobInstance(jobName, jobParameters) != null;
+ super(jobInstanceDao, jobExecutionDao, stepExecutionDao, ecDao);
}
@Override
@@ -249,34 +207,6 @@ public void updateExecutionContext(JobExecution jobExecution) {
ecDao.updateExecutionContext(jobExecution);
}
- @Override
- public JobInstance getJobInstance(String jobName, JobParameters jobParameters) {
- return jobInstanceDao.getJobInstance(jobName, jobParameters);
- }
-
- @Override
- @Nullable
- public StepExecution getLastStepExecution(JobInstance jobInstance, String stepName) {
- StepExecution latest = stepExecutionDao.getLastStepExecution(jobInstance, stepName);
-
- if (latest != null) {
- ExecutionContext stepExecutionContext = ecDao.getExecutionContext(latest);
- latest.setExecutionContext(stepExecutionContext);
- ExecutionContext jobExecutionContext = ecDao.getExecutionContext(latest.getJobExecution());
- latest.getJobExecution().setExecutionContext(jobExecutionContext);
- }
-
- return latest;
- }
-
- /**
- * @return number of executions of the step within given job instance
- */
- @Override
- public long getStepExecutionCount(JobInstance jobInstance, String stepName) {
- return stepExecutionDao.countStepExecutions(jobInstance, stepName);
- }
-
/**
* Check to determine whether or not the JobExecution that is the parent of the
* provided StepExecution has been interrupted. If, after synchronizing the status
@@ -293,23 +223,6 @@ private void checkForInterruption(StepExecution stepExecution) {
}
}
- @Override
- @Nullable
- public JobExecution getLastJobExecution(String jobName, JobParameters jobParameters) {
- JobInstance jobInstance = jobInstanceDao.getJobInstance(jobName, jobParameters);
- if (jobInstance == null) {
- return null;
- }
- JobExecution jobExecution = jobExecutionDao.getLastJobExecution(jobInstance);
-
- if (jobExecution != null) {
- jobExecution.setExecutionContext(ecDao.getExecutionContext(jobExecution));
- stepExecutionDao.addStepExecutions(jobExecution);
- }
- return jobExecution;
-
- }
-
@Override
public void deleteStepExecution(StepExecution stepExecution) {
this.ecDao.deleteExecutionContext(stepExecution);
@@ -328,7 +241,7 @@ public void deleteJobExecution(JobExecution jobExecution) {
@Override
public void deleteJobInstance(JobInstance jobInstance) {
- List jobExecutions = findJobExecutions(jobInstance);
+ List jobExecutions = getJobExecutions(jobInstance);
for (JobExecution jobExecution : jobExecutions) {
deleteJobExecution(jobExecution);
}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/resource/StepExecutionSimpleCompletionPolicy.java b/spring-batch-core/src/main/java/org/springframework/batch/core/resource/StepExecutionSimpleCompletionPolicy.java
index 3bc7bc0aeb..64d8f45b6e 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/resource/StepExecutionSimpleCompletionPolicy.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/resource/StepExecutionSimpleCompletionPolicy.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006-2021 the original author or authors.
+ * Copyright 2006-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,9 +16,9 @@
package org.springframework.batch.core.resource;
-import org.springframework.batch.core.JobParameters;
-import org.springframework.batch.core.StepExecution;
-import org.springframework.batch.core.StepExecutionListener;
+import org.springframework.batch.core.job.parameters.JobParameters;
+import org.springframework.batch.core.step.StepExecution;
+import org.springframework.batch.core.listener.StepExecutionListener;
import org.springframework.batch.repeat.CompletionPolicy;
import org.springframework.batch.repeat.RepeatContext;
import org.springframework.batch.repeat.RepeatStatus;
@@ -44,7 +44,9 @@
* @author Dave Syer
* @author Mahmoud Ben Hassine
* @see CompletionPolicy
+ * @deprecated since 6.0 with no replacement. Scheduled for removal in 6.2 or later.
*/
+@Deprecated(since = "6.0", forRemoval = true)
public class StepExecutionSimpleCompletionPolicy implements StepExecutionListener, CompletionPolicy {
private CompletionPolicy delegate;
@@ -65,7 +67,7 @@ public void setKeyName(String keyName) {
* {@link JobParameters}. If there is a Long parameter with the given key name, the
* intValue of this parameter is used. If not an exception will be thrown.
*
- * @see org.springframework.batch.core.StepExecutionListener#beforeStep(org.springframework.batch.core.StepExecution)
+ * @see StepExecutionListener#beforeStep(StepExecution)
*/
@Override
public void beforeStep(StepExecution stepExecution) {
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/scope/BatchScopeSupport.java b/spring-batch-core/src/main/java/org/springframework/batch/core/scope/BatchScopeSupport.java
index 7cf74c855c..1b8da10bf5 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/scope/BatchScopeSupport.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/scope/BatchScopeSupport.java
@@ -185,8 +185,8 @@ protected Object resolveValue(Object value) {
BeanDefinition definition = null;
String beanName = null;
- if (value instanceof BeanDefinition) {
- definition = (BeanDefinition) value;
+ if (value instanceof BeanDefinition beanDefinition) {
+ definition = beanDefinition;
beanName = BeanDefinitionReaderUtils.generateBeanName(definition, registry);
}
else if (value instanceof BeanDefinitionHolder holder) {
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/scope/context/JobContext.java b/spring-batch-core/src/main/java/org/springframework/batch/core/scope/context/JobContext.java
index 25e51964c7..bd92302649 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/scope/context/JobContext.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/scope/context/JobContext.java
@@ -25,11 +25,11 @@
import java.util.Properties;
import java.util.Set;
-import org.springframework.batch.core.JobExecution;
-import org.springframework.batch.core.JobInstance;
-import org.springframework.batch.core.JobParameter;
-import org.springframework.batch.core.JobParameters;
-import org.springframework.batch.core.UnexpectedJobExecutionException;
+import org.springframework.batch.core.job.JobExecution;
+import org.springframework.batch.core.job.JobInstance;
+import org.springframework.batch.core.job.parameters.JobParameter;
+import org.springframework.batch.core.job.parameters.JobParameters;
+import org.springframework.batch.core.job.UnexpectedJobExecutionException;
import org.springframework.batch.core.scope.StepScope;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.batch.repeat.context.SynchronizedAttributeAccessor;
@@ -161,8 +161,8 @@ public void close() {
}
Exception error = errors.get(0);
- if (error instanceof RuntimeException) {
- throw (RuntimeException) error;
+ if (error instanceof RuntimeException runtimeException) {
+ throw runtimeException;
}
else {
throw new UnexpectedJobExecutionException(
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/scope/context/JobScopeManager.java b/spring-batch-core/src/main/java/org/springframework/batch/core/scope/context/JobScopeManager.java
index 668f53c8fb..c7b5162529 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/scope/context/JobScopeManager.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/scope/context/JobScopeManager.java
@@ -18,8 +18,8 @@
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
-import org.springframework.batch.core.Job;
-import org.springframework.batch.core.JobExecution;
+import org.springframework.batch.core.job.Job;
+import org.springframework.batch.core.job.JobExecution;
/**
* Convenient aspect to wrap a single threaded job execution, where the implementation of
@@ -32,7 +32,7 @@
@Aspect
public class JobScopeManager {
- @Around("execution(void org.springframework.batch.core.Job+.execute(*)) && target(job) && args(jobExecution)")
+ @Around("execution(void org.springframework.batch.core.job.Job+.execute(*)) && target(job) && args(jobExecution)")
public void execute(Job job, JobExecution jobExecution) {
JobSynchronizationManager.register(jobExecution);
try {
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/scope/context/JobSynchronizationManager.java b/spring-batch-core/src/main/java/org/springframework/batch/core/scope/context/JobSynchronizationManager.java
index 0471cb4143..e3fa5d2ee3 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/scope/context/JobSynchronizationManager.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/scope/context/JobSynchronizationManager.java
@@ -15,8 +15,8 @@
*/
package org.springframework.batch.core.scope.context;
-import org.springframework.batch.core.Job;
-import org.springframework.batch.core.JobExecution;
+import org.springframework.batch.core.job.Job;
+import org.springframework.batch.core.job.JobExecution;
import org.springframework.lang.Nullable;
/**
@@ -60,12 +60,12 @@ public static JobContext getContext() {
* Register a context with the current thread - always put a matching {@link #close()}
* call in a finally block to ensure that the correct context is available in the
* enclosing block.
- * @param JobExecution the step context to register
+ * @param jobExecution the step context to register
* @return a new {@link JobContext} or the current one if it has the same
* {@link JobExecution}
*/
- public static JobContext register(JobExecution JobExecution) {
- return manager.register(JobExecution);
+ public static JobContext register(JobExecution jobExecution) {
+ return manager.register(jobExecution);
}
/**
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/scope/context/StepContext.java b/spring-batch-core/src/main/java/org/springframework/batch/core/scope/context/StepContext.java
index a076b5bee9..579aab3879 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/scope/context/StepContext.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/scope/context/StepContext.java
@@ -25,11 +25,11 @@
import java.util.Properties;
import java.util.Set;
-import org.springframework.batch.core.JobInstance;
-import org.springframework.batch.core.JobParameter;
-import org.springframework.batch.core.JobParameters;
-import org.springframework.batch.core.StepExecution;
-import org.springframework.batch.core.UnexpectedJobExecutionException;
+import org.springframework.batch.core.job.JobInstance;
+import org.springframework.batch.core.job.parameters.JobParameter;
+import org.springframework.batch.core.job.parameters.JobParameters;
+import org.springframework.batch.core.step.StepExecution;
+import org.springframework.batch.core.job.UnexpectedJobExecutionException;
import org.springframework.batch.core.scope.StepScope;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.batch.repeat.context.SynchronizedAttributeAccessor;
@@ -197,8 +197,8 @@ public void close() {
}
Exception error = errors.get(0);
- if (error instanceof RuntimeException) {
- throw (RuntimeException) error;
+ if (error instanceof RuntimeException runtimeException) {
+ throw runtimeException;
}
else {
throw new UnexpectedJobExecutionException(
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/scope/context/StepContextRepeatCallback.java b/spring-batch-core/src/main/java/org/springframework/batch/core/scope/context/StepContextRepeatCallback.java
index a30466c378..7ad0ca1dd4 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/scope/context/StepContextRepeatCallback.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/scope/context/StepContextRepeatCallback.java
@@ -20,8 +20,8 @@
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
-import org.springframework.batch.core.Step;
-import org.springframework.batch.core.StepExecution;
+import org.springframework.batch.core.step.Step;
+import org.springframework.batch.core.step.StepExecution;
import org.springframework.batch.repeat.RepeatCallback;
import org.springframework.batch.repeat.RepeatContext;
import org.springframework.batch.repeat.RepeatStatus;
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/scope/context/StepScopeManager.java b/spring-batch-core/src/main/java/org/springframework/batch/core/scope/context/StepScopeManager.java
index 7780dc950c..7ee8cff9c0 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/scope/context/StepScopeManager.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/scope/context/StepScopeManager.java
@@ -18,9 +18,9 @@
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
-import org.springframework.batch.core.JobInterruptedException;
-import org.springframework.batch.core.Step;
-import org.springframework.batch.core.StepExecution;
+import org.springframework.batch.core.job.JobInterruptedException;
+import org.springframework.batch.core.step.Step;
+import org.springframework.batch.core.step.StepExecution;
/**
* Convenient aspect to wrap a single threaded step execution, where the implementation of
@@ -32,7 +32,7 @@
@Aspect
public class StepScopeManager {
- @Around("execution(void org.springframework.batch.core.Step+.execute(*)) && target(step) && args(stepExecution)")
+ @Around("execution(void org.springframework.batch.core.step.Step+.execute(*)) && target(step) && args(stepExecution)")
public void execute(Step step, StepExecution stepExecution) throws JobInterruptedException {
StepSynchronizationManager.register(stepExecution);
try {
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/scope/context/StepSynchronizationManager.java b/spring-batch-core/src/main/java/org/springframework/batch/core/scope/context/StepSynchronizationManager.java
index 9fbf4ef853..34c24d0dc0 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/scope/context/StepSynchronizationManager.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/scope/context/StepSynchronizationManager.java
@@ -15,8 +15,8 @@
*/
package org.springframework.batch.core.scope.context;
-import org.springframework.batch.core.Step;
-import org.springframework.batch.core.StepExecution;
+import org.springframework.batch.core.step.Step;
+import org.springframework.batch.core.step.StepExecution;
import org.springframework.lang.Nullable;
/**
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/scope/context/SynchronizationManagerSupport.java b/spring-batch-core/src/main/java/org/springframework/batch/core/scope/context/SynchronizationManagerSupport.java
index 1891f55883..f76a48b55a 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/scope/context/SynchronizationManagerSupport.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/scope/context/SynchronizationManagerSupport.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2013-2021 the original author or authors.
+ * Copyright 2013-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -28,6 +28,7 @@
* @author Dave Syer
* @author Jimmy Praet
* @author Mahmoud Ben Hassine
+ * @author Yanming Zhou
* @since 3.0
*/
public abstract class SynchronizationManagerSupport {
@@ -87,11 +88,7 @@ public C register(@Nullable E execution) {
getCurrent().push(execution);
C context;
synchronized (contexts) {
- context = contexts.get(execution);
- if (context == null) {
- context = createNewContext(execution);
- contexts.put(execution, context);
- }
+ context = contexts.computeIfAbsent(execution, this::createNewContext);
}
increment();
return context;
@@ -131,11 +128,7 @@ public void increment() {
if (current != null) {
AtomicInteger count;
synchronized (counts) {
- count = counts.get(current);
- if (count == null) {
- count = new AtomicInteger();
- counts.put(current, count);
- }
+ count = counts.computeIfAbsent(current, k -> new AtomicInteger());
}
count.incrementAndGet();
}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/AbstractStep.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/AbstractStep.java
index c2339b95df..4cac2f98b9 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/AbstractStep.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/AbstractStep.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006-2024 the original author or authors.
+ * Copyright 2006-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,8 +20,6 @@
import java.util.List;
import java.util.stream.Collectors;
-import io.micrometer.core.instrument.MeterRegistry;
-import io.micrometer.core.instrument.Metrics;
import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationRegistry;
import org.apache.commons.logging.Log;
@@ -29,21 +27,17 @@
import org.springframework.batch.core.BatchStatus;
import org.springframework.batch.core.ExitStatus;
-import org.springframework.batch.core.JobInterruptedException;
+import org.springframework.batch.core.job.JobInterruptedException;
import org.springframework.batch.core.SpringBatchVersion;
-import org.springframework.batch.core.Step;
-import org.springframework.batch.core.StepExecution;
-import org.springframework.batch.core.StepExecutionListener;
-import org.springframework.batch.core.UnexpectedJobExecutionException;
+import org.springframework.batch.core.listener.StepExecutionListener;
+import org.springframework.batch.core.job.UnexpectedJobExecutionException;
import org.springframework.batch.core.configuration.annotation.StepScope;
import org.springframework.batch.core.launch.NoSuchJobException;
import org.springframework.batch.core.launch.support.ExitCodeMapper;
import org.springframework.batch.core.listener.CompositeStepExecutionListener;
import org.springframework.batch.core.observability.BatchMetrics;
-import org.springframework.batch.core.observability.BatchStepContext;
-import org.springframework.batch.core.observability.BatchStepObservation;
-import org.springframework.batch.core.observability.BatchStepObservationConvention;
-import org.springframework.batch.core.observability.DefaultBatchStepObservationConvention;
+import org.springframework.batch.core.observability.jfr.events.step.StepExecutionEvent;
+import org.springframework.batch.core.observability.micrometer.MicrometerMetrics;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.scope.context.StepSynchronizationManager;
import org.springframework.batch.item.ExecutionContext;
@@ -65,7 +59,7 @@
* @author Mahmoud Ben Hassine
* @author Jinwoo Bae
*/
-public abstract class AbstractStep implements Step, InitializingBean, BeanNameAware {
+public abstract class AbstractStep implements StoppableStep, InitializingBean, BeanNameAware {
private static final Log logger = LogFactory.getLog(AbstractStep.class);
@@ -79,23 +73,45 @@ public abstract class AbstractStep implements Step, InitializingBean, BeanNameAw
private JobRepository jobRepository;
- private ObservationRegistry observationRegistry = ObservationRegistry.NOOP;
+ protected ObservationRegistry observationRegistry;
- private MeterRegistry meterRegistry = Metrics.globalRegistry;
+ /**
+ * Create a new {@link AbstractStep}.
+ * @deprecated since 6.0 for removal in 7.0. Use {@link #AbstractStep(JobRepository)}
+ * instead.
+ */
+ @Deprecated(since = "6.0", forRemoval = true)
+ public AbstractStep() {
+ }
- private BatchStepObservationConvention observationConvention = new DefaultBatchStepObservationConvention();
+ /**
+ * Create a new {@link AbstractStep}.
+ * @deprecated since 6.0 for removal in 7.0. Use {@link #AbstractStep(JobRepository)}
+ * instead.
+ */
+ @Deprecated(since = "6.0", forRemoval = true)
+ public AbstractStep(String name) {
+ Assert.notNull(name, "Step name must not be null");
+ this.name = name;
+ }
/**
- * Default constructor.
+ * Create a new {@link AbstractStep} with the given job repository.
+ * @param jobRepository the job repository. Must not be null.
+ * @since 6.0
*/
- public AbstractStep() {
- super();
+ public AbstractStep(JobRepository jobRepository) {
+ Assert.notNull(jobRepository, "JobRepository must not be null");
+ this.jobRepository = jobRepository;
}
@Override
public void afterPropertiesSet() throws Exception {
- Assert.state(name != null, "A Step must have a name");
Assert.state(jobRepository != null, "JobRepository is mandatory");
+ if (this.observationRegistry == null) {
+ logger.info("No ObservationRegistry has been set, defaulting to ObservationRegistry NOOP");
+ this.observationRegistry = ObservationRegistry.NOOP;
+ }
}
@Override
@@ -156,14 +172,6 @@ public void setAllowStartIfComplete(boolean allowStartIfComplete) {
this.allowStartIfComplete = allowStartIfComplete;
}
- /**
- * Convenient constructor for setting only the name property.
- * @param name Name of the step
- */
- public AbstractStep(String name) {
- this.name = name;
- }
-
/**
* Extension point for subclasses to execute business logic. Subclasses should set the
* {@link ExitStatus} on the {@link StepExecution} before returning.
@@ -202,18 +210,26 @@ public final void execute(StepExecution stepExecution)
throws JobInterruptedException, UnexpectedJobExecutionException {
Assert.notNull(stepExecution, "stepExecution must not be null");
+ Assert.state(stepExecution.getId() != null,
+ "StepExecution has no id. It must be saved before it can be executed.");
stepExecution.getExecutionContext().put(SpringBatchVersion.BATCH_VERSION_KEY, SpringBatchVersion.getVersion());
if (logger.isDebugEnabled()) {
logger.debug("Executing: id=" + stepExecution.getId());
}
+ StepExecutionEvent stepExecutionEvent = new StepExecutionEvent(stepExecution.getStepName(),
+ stepExecution.getJobExecution().getJobInstance().getJobName(), stepExecution.getId(),
+ stepExecution.getJobExecutionId());
+ stepExecutionEvent.begin();
stepExecution.setStartTime(LocalDateTime.now());
stepExecution.setStatus(BatchStatus.STARTED);
- Observation observation = BatchMetrics
- .createObservation(BatchStepObservation.BATCH_STEP_OBSERVATION.getName(),
- new BatchStepContext(stepExecution), this.observationRegistry)
- .contextualName(stepExecution.getStepName())
- .observationConvention(this.observationConvention)
+ Observation observation = MicrometerMetrics
+ .createObservation(BatchMetrics.METRICS_PREFIX + "step", this.observationRegistry)
+ .highCardinalityKeyValue(BatchMetrics.METRICS_PREFIX + "step.executionId", stepExecution.getId().toString())
+ .lowCardinalityKeyValue(BatchMetrics.METRICS_PREFIX + "step.name", stepExecution.getStepName())
+ .lowCardinalityKeyValue(BatchMetrics.METRICS_PREFIX + "step.type", getClass().getName())
+ .lowCardinalityKeyValue(BatchMetrics.METRICS_PREFIX + "step.job.name",
+ stepExecution.getJobExecution().getJobInstance().getJobName())
.start();
getJobRepository().update(stepExecution);
@@ -293,6 +309,8 @@ public final void execute(StepExecution stepExecution)
+ "This job is now in an unknown state and should not be restarted.",
name, stepExecution.getJobExecution().getJobInstance().getJobName()), e);
}
+ stepExecutionEvent.exitStatus = stepExecution.getExitStatus().getExitCode();
+ stepExecutionEvent.commit();
stopObservation(stepExecution, observation);
stepExecution.setExitStatus(exitStatus);
@@ -331,6 +349,8 @@ private void stopObservation(StepExecution stepExecution, Observation observatio
if (!throwables.isEmpty()) {
observation.error(mergedThrowables(throwables));
}
+ observation.lowCardinalityKeyValue(BatchMetrics.METRICS_PREFIX + "step.status",
+ stepExecution.getExitStatus().getExitCode());
observation.stop();
}
@@ -430,16 +450,8 @@ else if (ex instanceof NoSuchJobException || ex.getCause() instanceof NoSuchJobE
return exitStatus;
}
- public void setObservationConvention(BatchStepObservationConvention observationConvention) {
- this.observationConvention = observationConvention;
- }
-
public void setObservationRegistry(ObservationRegistry observationRegistry) {
this.observationRegistry = observationRegistry;
}
- public void setMeterRegistry(MeterRegistry meterRegistry) {
- this.meterRegistry = meterRegistry;
- }
-
}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/FatalStepExecutionException.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/FatalStepExecutionException.java
index 2b40a9bde3..5be3202068 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/FatalStepExecutionException.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/FatalStepExecutionException.java
@@ -15,7 +15,7 @@
*/
package org.springframework.batch.core.step;
-import org.springframework.batch.core.UnexpectedJobExecutionException;
+import org.springframework.batch.core.job.UnexpectedJobExecutionException;
/**
* @author Dave Syer
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/NoWorkFoundStepExecutionListener.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/NoWorkFoundStepExecutionListener.java
index e30e9bd426..940230080a 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/NoWorkFoundStepExecutionListener.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/NoWorkFoundStepExecutionListener.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2021 the original author or authors.
+ * Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,8 +17,7 @@
package org.springframework.batch.core.step;
import org.springframework.batch.core.ExitStatus;
-import org.springframework.batch.core.StepExecution;
-import org.springframework.batch.core.StepExecutionListener;
+import org.springframework.batch.core.listener.StepExecutionListener;
import org.springframework.lang.Nullable;
/**
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/Step.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/Step.java
similarity index 78%
rename from spring-batch-core/src/main/java/org/springframework/batch/core/Step.java
rename to spring-batch-core/src/main/java/org/springframework/batch/core/step/Step.java
index 834cfac6ce..03447ddb0e 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/Step.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/Step.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006-2022 the original author or authors.
+ * Copyright 2006-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,7 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.springframework.batch.core;
+package org.springframework.batch.core.step;
+
+import org.springframework.batch.core.job.Job;
+import org.springframework.batch.core.job.JobInterruptedException;
/**
* Batch domain interface representing the configuration of a step. As with a {@link Job},
@@ -24,6 +27,7 @@
* @author Mahmoud Ben Hassine
*
*/
+@FunctionalInterface
public interface Step {
/**
@@ -32,9 +36,14 @@ public interface Step {
String STEP_TYPE_KEY = "batch.stepType";
/**
- * @return the name of this step.
+ * The name of the step. This is used to distinguish between different steps and must
+ * be unique within a job. If not explicitly set, the name will default to the fully
+ * qualified class name.
+ * @return the name of the step (never {@code null})
*/
- String getName();
+ default String getName() {
+ return this.getClass().getName();
+ }
/**
* @return {@code true} if a step that is already marked as complete can be started
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/StepContribution.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/StepContribution.java
similarity index 89%
rename from spring-batch-core/src/main/java/org/springframework/batch/core/StepContribution.java
rename to spring-batch-core/src/main/java/org/springframework/batch/core/step/StepContribution.java
index fcbeaa9284..ae703f9277 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/StepContribution.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/StepContribution.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006-2023 the original author or authors.
+ * Copyright 2006-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,10 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.springframework.batch.core;
+package org.springframework.batch.core.step;
import java.io.Serializable;
+import org.springframework.batch.core.ExitStatus;
+
/**
* Represents a contribution to a {@link StepExecution}, buffering changes until they can
* be applied at a chunk boundary.
@@ -71,7 +73,15 @@ public ExitStatus getExitStatus() {
}
/**
- * Increment the counter for the number of items processed.
+ * Increment the counter for the number of filtered items.
+ * @since 6.0.0
+ */
+ public void incrementFilterCount() {
+ this.incrementFilterCount(1);
+ }
+
+ /**
+ * Increment the counter for the number of filtered items.
* @param count The {@code long} amount to increment by.
*/
public void incrementFilterCount(long count) {
@@ -155,6 +165,15 @@ public void incrementWriteSkipCount() {
writeSkipCount++;
}
+ /**
+ * Increment the write skip count for this contribution.
+ * @param count The {@code long} amount to increment by.
+ * @since 6.0.0
+ */
+ public void incrementWriteSkipCount(long count) {
+ writeSkipCount += count;
+ }
+
/**
*
*/
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/StepExecution.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/StepExecution.java
similarity index 97%
rename from spring-batch-core/src/main/java/org/springframework/batch/core/StepExecution.java
rename to spring-batch-core/src/main/java/org/springframework/batch/core/step/StepExecution.java
index 1bf5164778..939102aaa5 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/StepExecution.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/StepExecution.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.batch.core;
+package org.springframework.batch.core.step;
import java.io.IOException;
import java.io.ObjectInputStream;
@@ -23,6 +23,11 @@
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
+import org.springframework.batch.core.BatchStatus;
+import org.springframework.batch.core.Entity;
+import org.springframework.batch.core.ExitStatus;
+import org.springframework.batch.core.job.JobExecution;
+import org.springframework.batch.core.job.parameters.JobParameters;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@@ -492,7 +497,7 @@ public boolean equals(Object obj) {
return super.equals(obj);
}
- return stepName.equals(other.getStepName()) && (jobExecutionId.equals(other.getJobExecutionId()))
+ return stepName.equals(other.getStepName()) && jobExecutionId.equals(other.getJobExecutionId())
&& getId().equals(other.getId());
}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/StepHolder.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/StepHolder.java
index 1f4a51c91d..33ad81ef12 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/StepHolder.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/StepHolder.java
@@ -15,8 +15,6 @@
*/
package org.springframework.batch.core.step;
-import org.springframework.batch.core.Step;
-
/**
* Interface for holders of a {@link Step} as a convenience for callers who need access to
* the underlying instance.
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/StepInterruptionPolicy.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/StepInterruptionPolicy.java
index 20a90fa6d6..1c2f74c75c 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/StepInterruptionPolicy.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/StepInterruptionPolicy.java
@@ -16,9 +16,7 @@
package org.springframework.batch.core.step;
-import org.springframework.batch.core.JobInterruptedException;
-import org.springframework.batch.core.Step;
-import org.springframework.batch.core.StepExecution;
+import org.springframework.batch.core.job.JobInterruptedException;
/**
* Strategy interface for an interruption policy. This policy allows {@link Step}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/StepLocator.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/StepLocator.java
index c275d20623..bc32a2b984 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/StepLocator.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/StepLocator.java
@@ -17,8 +17,6 @@
import java.util.Collection;
-import org.springframework.batch.core.Step;
-
/**
* Interface for locating a {@link Step} instance by name.
*
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/StepLocatorStepFactoryBean.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/StepLocatorStepFactoryBean.java
index 98faf4b483..734d519ff4 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/StepLocatorStepFactoryBean.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/StepLocatorStepFactoryBean.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2013 the original author or authors.
+ * Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,8 +15,7 @@
*/
package org.springframework.batch.core.step;
-import org.springframework.batch.core.Job;
-import org.springframework.batch.core.Step;
+import org.springframework.batch.core.job.Job;
import org.springframework.beans.factory.FactoryBean;
/**
@@ -25,8 +24,9 @@
* point.
*
* @author Dave Syer
- *
+ * @deprecated since 6.0 with no replacement. Scheduled for removal in 7.0.
*/
+@Deprecated(since = "6.0", forRemoval = true)
public class StepLocatorStepFactoryBean implements FactoryBean {
public StepLocator stepLocator;
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/observability/BatchStepObservationConvention.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/StoppableStep.java
similarity index 50%
rename from spring-batch-core/src/main/java/org/springframework/batch/core/observability/BatchStepObservationConvention.java
rename to spring-batch-core/src/main/java/org/springframework/batch/core/step/StoppableStep.java
index 00ac6ad04a..e455861f96 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/observability/BatchStepObservationConvention.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/StoppableStep.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2022 the original author or authors.
+ * Copyright 2025-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,24 +13,25 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
-package org.springframework.batch.core.observability;
-
-import io.micrometer.observation.Observation;
-import io.micrometer.observation.ObservationConvention;
+package org.springframework.batch.core.step;
/**
- * {@link ObservationConvention} for {@link BatchStepContext}.
+ * Extension of the {@link Step} interface to be implemented by steps that support being
+ * stopped.
*
- * @author Marcin Grzejszczak
* @author Mahmoud Ben Hassine
- * @since 5.0
+ * @since 6.0
*/
-public interface BatchStepObservationConvention extends ObservationConvention {
+public interface StoppableStep extends Step {
- @Override
- default boolean supportsContext(Observation.Context context) {
- return context instanceof BatchStepContext;
+ /**
+ * Callback to signal the step to stop. The default implementation sets the
+ * {@link StepExecution} to terminate only. Concrete implementations can override this
+ * method to add custom stop logic.
+ * @param stepExecution the current step execution
+ */
+ default void stop(StepExecution stepExecution) {
+ stepExecution.setTerminateOnly();
}
}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/ThreadStepInterruptionPolicy.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/ThreadStepInterruptionPolicy.java
index f1ee332fb1..6f815c966b 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/ThreadStepInterruptionPolicy.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/ThreadStepInterruptionPolicy.java
@@ -18,8 +18,7 @@
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
-import org.springframework.batch.core.JobInterruptedException;
-import org.springframework.batch.core.StepExecution;
+import org.springframework.batch.core.job.JobInterruptedException;
/**
* Policy that checks the current thread to see if it has been interrupted.
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/AbstractTaskletStepBuilder.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/AbstractTaskletStepBuilder.java
index 55e6a0fdce..fa454da245 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/AbstractTaskletStepBuilder.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/AbstractTaskletStepBuilder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2024 the original author or authors.
+ * Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,8 +20,8 @@
import java.util.LinkedHashSet;
import java.util.Set;
-import org.springframework.batch.core.ChunkListener;
-import org.springframework.batch.core.StepExecutionListener;
+import org.springframework.batch.core.listener.ChunkListener;
+import org.springframework.batch.core.listener.StepExecutionListener;
import org.springframework.batch.core.annotation.AfterChunk;
import org.springframework.batch.core.annotation.AfterChunkError;
import org.springframework.batch.core.annotation.BeforeChunk;
@@ -67,8 +67,6 @@ public abstract class AbstractTaskletStepBuilder parent) {
@@ -88,7 +86,6 @@ public AbstractTaskletStepBuilder(AbstractTaskletStepBuilder> parent) {
this.transactionAttribute = parent.transactionAttribute;
this.streams.addAll(parent.streams);
this.exceptionHandler = parent.exceptionHandler;
- this.throttleLimit = parent.throttleLimit;
this.taskExecutor = parent.taskExecutor;
}
@@ -125,7 +122,6 @@ public TaskletStep build() {
if (taskExecutor != null) {
TaskExecutorRepeatTemplate repeatTemplate = new TaskExecutorRepeatTemplate();
repeatTemplate.setTaskExecutor(taskExecutor);
- repeatTemplate.setThrottleLimit(throttleLimit);
stepOperations = repeatTemplate;
}
@@ -150,8 +146,8 @@ public TaskletStep build() {
protected void registerStepListenerAsChunkListener() {
for (StepExecutionListener stepExecutionListener : properties.getStepExecutionListeners()) {
- if (stepExecutionListener instanceof ChunkListener) {
- listener((ChunkListener) stepExecutionListener);
+ if (stepExecutionListener instanceof ChunkListener chunkListener) {
+ listener(chunkListener);
}
}
}
@@ -210,24 +206,6 @@ public B taskExecutor(TaskExecutor taskExecutor) {
return self();
}
- /**
- * In the case of an asynchronous {@link #taskExecutor(TaskExecutor)} the number of
- * concurrent tasklet executions can be throttled (beyond any throttling provided by a
- * thread pool). The throttle limit should be less than the data source pool size used
- * in the job repository for this step.
- * @param throttleLimit maximum number of concurrent tasklet executions allowed
- * @return this for fluent chaining
- * @deprecated with no replacement since 5.0, scheduled for removal in 6.0. Use a
- * custom {@link RepeatOperations} implementation (based on a {@link TaskExecutor}
- * with a bounded task queue) and set it on the step with
- * {@link #stepOperations(RepeatOperations)}.
- */
- @Deprecated(since = "5.0", forRemoval = true)
- public B throttleLimit(int throttleLimit) {
- this.throttleLimit = throttleLimit;
- return self();
- }
-
/**
* Sets the exception handler to use in the case of tasklet failures. Default is to
* rethrow everything.
@@ -302,11 +280,6 @@ protected TaskExecutor getTaskExecutor() {
return taskExecutor;
}
- @Deprecated(since = "5.0", forRemoval = true)
- protected int getThrottleLimit() {
- return throttleLimit;
- }
-
protected TransactionAttribute getTransactionAttribute() {
return transactionAttribute;
}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/ChunkOrientedStepBuilder.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/ChunkOrientedStepBuilder.java
new file mode 100644
index 0000000000..376ec86dcb
--- /dev/null
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/ChunkOrientedStepBuilder.java
@@ -0,0 +1,443 @@
+/*
+ * Copyright 2025-present the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.batch.core.step.builder;
+
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+import io.micrometer.observation.ObservationRegistry;
+
+import org.springframework.batch.core.annotation.AfterChunk;
+import org.springframework.batch.core.annotation.AfterProcess;
+import org.springframework.batch.core.annotation.AfterRead;
+import org.springframework.batch.core.annotation.AfterWrite;
+import org.springframework.batch.core.annotation.BeforeChunk;
+import org.springframework.batch.core.annotation.BeforeProcess;
+import org.springframework.batch.core.annotation.BeforeRead;
+import org.springframework.batch.core.annotation.BeforeWrite;
+import org.springframework.batch.core.annotation.OnChunkError;
+import org.springframework.batch.core.annotation.OnProcessError;
+import org.springframework.batch.core.annotation.OnReadError;
+import org.springframework.batch.core.annotation.OnWriteError;
+import org.springframework.batch.core.listener.ChunkListener;
+import org.springframework.batch.core.listener.ItemProcessListener;
+import org.springframework.batch.core.listener.ItemReadListener;
+import org.springframework.batch.core.listener.ItemWriteListener;
+import org.springframework.batch.core.listener.SkipListener;
+import org.springframework.batch.core.listener.StepListener;
+import org.springframework.batch.core.listener.StepListenerFactoryBean;
+import org.springframework.batch.core.repository.JobRepository;
+import org.springframework.batch.core.step.StepInterruptionPolicy;
+import org.springframework.batch.core.step.ThreadStepInterruptionPolicy;
+import org.springframework.batch.core.step.item.ChunkOrientedStep;
+import org.springframework.batch.core.step.skip.AlwaysSkipItemSkipPolicy;
+import org.springframework.batch.core.step.skip.LimitCheckingExceptionHierarchySkipPolicy;
+import org.springframework.batch.core.step.skip.SkipPolicy;
+import org.springframework.batch.item.ItemProcessor;
+import org.springframework.batch.item.ItemReader;
+import org.springframework.batch.item.ItemStream;
+import org.springframework.batch.item.ItemWriter;
+import org.springframework.batch.support.ReflectionUtils;
+import org.springframework.batch.support.transaction.ResourcelessTransactionManager;
+import org.springframework.core.retry.RetryListener;
+import org.springframework.core.retry.RetryPolicy;
+import org.springframework.core.task.AsyncTaskExecutor;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.interceptor.DefaultTransactionAttribute;
+import org.springframework.transaction.interceptor.TransactionAttribute;
+import org.springframework.util.Assert;
+
+/**
+ * A builder for {@link ChunkOrientedStep}. This class extends {@link StepBuilderHelper}
+ * to provide common properties and methods for building chunk-oriented steps.
+ *
+ * @author Mahmoud Ben Hassine
+ * @since 6.0
+ */
+public class ChunkOrientedStepBuilder extends StepBuilderHelper> {
+
+ private final int chunkSize;
+
+ private ItemReader reader;
+
+ private ItemProcessor processor;
+
+ private ItemWriter writer;
+
+ private PlatformTransactionManager transactionManager = new ResourcelessTransactionManager();
+
+ private TransactionAttribute transactionAttribute = new DefaultTransactionAttribute();
+
+ private final Set streams = new LinkedHashSet<>();
+
+ private final Set stepListeners = new LinkedHashSet<>();
+
+ private StepInterruptionPolicy interruptionPolicy = new ThreadStepInterruptionPolicy();
+
+ private boolean faultTolerant;
+
+ private RetryPolicy retryPolicy;
+
+ private final Set retryListeners = new LinkedHashSet<>();
+
+ private final Set> retryableExceptions = new HashSet<>();
+
+ private long retryLimit = -1;
+
+ private SkipPolicy skipPolicy;
+
+ private final Set> skipListeners = new LinkedHashSet<>();
+
+ private final Set> skippableExceptions = new HashSet<>();
+
+ private long skipLimit = -1;
+
+ private AsyncTaskExecutor asyncTaskExecutor;
+
+ private ObservationRegistry observationRegistry;
+
+ ChunkOrientedStepBuilder(StepBuilderHelper> parent, int chunkSize) {
+ super(parent);
+ this.chunkSize = chunkSize;
+ }
+
+ /**
+ * Create a new {@link ChunkOrientedStepBuilder} with the given job repository and
+ * transaction manager. The step name will be assigned to the bean name.
+ * @param jobRepository the job repository
+ * @param chunkSize the size of the chunk to be processed
+ */
+ public ChunkOrientedStepBuilder(JobRepository jobRepository, int chunkSize) {
+ super(jobRepository);
+ this.chunkSize = chunkSize;
+ }
+
+ /**
+ * Create a new {@link ChunkOrientedStepBuilder} with the given step name, job
+ * repository and transaction manager.
+ * @param name the step name
+ * @param jobRepository the job repository
+ * @param chunkSize the size of the chunk to be processed
+ */
+ public ChunkOrientedStepBuilder(String name, JobRepository jobRepository, int chunkSize) {
+ super(name, jobRepository);
+ this.chunkSize = chunkSize;
+ }
+
+ @Override
+ protected ChunkOrientedStepBuilder self() {
+ return this;
+ }
+
+ /**
+ * An item reader that provides a stream of items. Will be automatically registered as
+ * a {@link #stream(ItemStream)} or listener if it implements the corresponding
+ * interface.
+ * @param reader an item reader
+ * @return this for fluent chaining
+ */
+ public ChunkOrientedStepBuilder reader(ItemReader reader) {
+ this.reader = reader;
+ return self();
+ }
+
+ /**
+ * An item processor that processes or transforms a stream of items. Will be
+ * automatically registered as a {@link #stream(ItemStream)} or listener if it
+ * implements the corresponding interface.
+ * @param processor an item processor
+ * @return this for fluent chaining
+ */
+ public ChunkOrientedStepBuilder processor(ItemProcessor processor) {
+ this.processor = processor;
+ return self();
+ }
+
+ /**
+ * An item writer that writes a chunk of items. Will be automatically registered as a
+ * {@link #stream(ItemStream)} or listener if it implements the corresponding
+ * interface.
+ * @param writer an item writer
+ * @return this for fluent chaining
+ */
+ public ChunkOrientedStepBuilder writer(ItemWriter writer) {
+ this.writer = writer;
+ return self();
+ }
+
+ /**
+ * Sets the transaction manager to use for the chunk-oriented tasklet. Defaults to a
+ * {@link ResourcelessTransactionManager} if none is provided.
+ * @param transactionManager a transaction manager set
+ * @return this for fluent chaining
+ */
+ public ChunkOrientedStepBuilder transactionManager(PlatformTransactionManager transactionManager) {
+ this.transactionManager = transactionManager;
+ return self();
+ }
+
+ /**
+ * Sets the transaction attributes for the tasklet execution. Defaults to the default
+ * values for the transaction manager, but can be manipulated to provide longer
+ * timeouts for instance.
+ * @param transactionAttribute a transaction attribute set
+ * @return this for fluent chaining
+ */
+ public ChunkOrientedStepBuilder transactionAttribute(TransactionAttribute transactionAttribute) {
+ this.transactionAttribute = transactionAttribute;
+ return self();
+ }
+
+ /**
+ * Register a stream for callbacks that manage restart data.
+ * @param stream the stream to register
+ * @return this for fluent chaining
+ */
+ public ChunkOrientedStepBuilder stream(ItemStream stream) {
+ streams.add(stream);
+ return self();
+ }
+
+ /**
+ * Register an item reader listener.
+ * @param listener the listener to register
+ * @return this for fluent chaining
+ */
+ public ChunkOrientedStepBuilder listener(StepListener listener) {
+ this.stepListeners.add(listener);
+ return self();
+ }
+
+ /**
+ * Registers objects using the annotation-based listener configuration.
+ * @param listener the object that has a method configured with listener annotation(s)
+ * @return this for fluent chaining
+ */
+ public ChunkOrientedStepBuilder listener(Object listener) {
+ Set listenerMethods = new HashSet<>();
+ listenerMethods.addAll(ReflectionUtils.findMethod(listener.getClass(), BeforeChunk.class));
+ listenerMethods.addAll(ReflectionUtils.findMethod(listener.getClass(), AfterChunk.class));
+ listenerMethods.addAll(ReflectionUtils.findMethod(listener.getClass(), OnChunkError.class));
+ listenerMethods.addAll(ReflectionUtils.findMethod(listener.getClass(), BeforeRead.class));
+ listenerMethods.addAll(ReflectionUtils.findMethod(listener.getClass(), AfterRead.class));
+ listenerMethods.addAll(ReflectionUtils.findMethod(listener.getClass(), OnReadError.class));
+ listenerMethods.addAll(ReflectionUtils.findMethod(listener.getClass(), BeforeProcess.class));
+ listenerMethods.addAll(ReflectionUtils.findMethod(listener.getClass(), AfterProcess.class));
+ listenerMethods.addAll(ReflectionUtils.findMethod(listener.getClass(), OnProcessError.class));
+ listenerMethods.addAll(ReflectionUtils.findMethod(listener.getClass(), BeforeWrite.class));
+ listenerMethods.addAll(ReflectionUtils.findMethod(listener.getClass(), AfterWrite.class));
+ listenerMethods.addAll(ReflectionUtils.findMethod(listener.getClass(), OnWriteError.class));
+
+ if (!listenerMethods.isEmpty()) {
+ StepListenerFactoryBean factory = new StepListenerFactoryBean();
+ factory.setDelegate(listener);
+ this.stepListeners.add((StepListener) factory.getObject());
+ }
+
+ return self();
+ }
+
+ /**
+ * Set the interruption policy for the step. This policy determines how the step
+ * handles interruptions, such as when a job is stopped or restarted. The policy is
+ * checked at chunk boundaries to decide whether to continue processing or stop.
+ * Defaults to {@link ThreadStepInterruptionPolicy}.
+ * @return this for fluent chaining
+ */
+ public ChunkOrientedStepBuilder interruptionPolicy(StepInterruptionPolicy interruptionPolicy) {
+ this.interruptionPolicy = interruptionPolicy;
+ return self();
+ }
+
+ /**
+ * Set whether the step is fault-tolerant or not. A fault-tolerant step can handle
+ * failures and continue processing without failing the entire step. This is useful
+ * for scenarios where individual items may fail and be skipped, but the overall step
+ * should still complete successfully. Defaults to false.
+ * @return this for fluent chaining
+ */
+ public ChunkOrientedStepBuilder faultTolerant() {
+ this.faultTolerant = true;
+ return self();
+ }
+
+ /**
+ * Set the retry policy for the step. This policy determines how the step handles
+ * retries in case of failures. It can be used to define the number of retry attempts
+ * and the conditions under which retries should occur. Defaults to no retry policy.
+ * @param retryPolicy the retry policy to use
+ * @return this for fluent chaining
+ */
+ public ChunkOrientedStepBuilder retryPolicy(RetryPolicy retryPolicy) {
+ Assert.notNull(retryPolicy, "retryPolicy must not be null");
+ this.retryPolicy = retryPolicy;
+ return self();
+ }
+
+ /**
+ * Add a retry listener to the step. Retry listeners are notified of retry events and
+ * can be used to implement custom retry logic or logging.
+ * @param retryListener the retry listener to add
+ * @return this for fluent chaining
+ */
+ public ChunkOrientedStepBuilder retryListener(RetryListener retryListener) {
+ this.retryListeners.add(retryListener);
+ return self();
+ }
+
+ @SafeVarargs
+ public final ChunkOrientedStepBuilder retry(Class extends Throwable>... retryableExceptions) {
+ this.retryableExceptions.addAll(Arrays.stream(retryableExceptions).toList());
+ return self();
+ }
+
+ public ChunkOrientedStepBuilder retryLimit(long retryLimit) {
+ Assert.isTrue(retryLimit > 0, "retryLimit must be positive");
+ this.retryLimit = retryLimit;
+ return self();
+ }
+
+ /**
+ * Set the skip policy for the step. This policy determines how the step handles
+ * skipping items in case of failures. It can be used to define the conditions under
+ * which items should be skipped and how many times an item can be skipped before the
+ * step fails. Defaults to {@link AlwaysSkipItemSkipPolicy}.
+ * @param skipPolicy the skip policy to use
+ * @return this for fluent chaining
+ */
+ public ChunkOrientedStepBuilder skipPolicy(SkipPolicy skipPolicy) {
+ Assert.notNull(skipPolicy, "skipPolicy must not be null");
+ this.skipPolicy = skipPolicy;
+ return self();
+ }
+
+ /**
+ * Add a skip listener to the step. Skip listeners are notified when an item is
+ * skipped due to a failure or an error. They can be used to implement custom skip
+ * logic or logging.
+ * @param skipListener the skip listener to add
+ * @return this for fluent chaining
+ */
+ public ChunkOrientedStepBuilder skipListener(SkipListener skipListener) {
+ this.skipListeners.add(skipListener);
+ return self();
+ }
+
+ @SafeVarargs
+ public final ChunkOrientedStepBuilder skip(Class extends Throwable>... skippableExceptions) {
+ this.skippableExceptions.addAll(Arrays.stream(skippableExceptions).toList());
+ return self();
+ }
+
+ public ChunkOrientedStepBuilder skipLimit(long skipLimit) {
+ Assert.isTrue(skipLimit > 0, "skipLimit must be positive");
+ this.skipLimit = skipLimit;
+ return self();
+ }
+
+ /**
+ * Set the asynchronous task executor to be used for processing items concurrently.
+ * This allows for concurrent processing of items, improving performance and
+ * throughput. If not set, the step will process items sequentially.
+ * @param asyncTaskExecutor the asynchronous task executor to use
+ * @return this for fluent chaining
+ */
+ public ChunkOrientedStepBuilder taskExecutor(AsyncTaskExecutor asyncTaskExecutor) {
+ this.asyncTaskExecutor = asyncTaskExecutor;
+ return self();
+ }
+
+ /**
+ * Set the observation registry to be used for collecting metrics during step
+ * execution. This allows for monitoring and analyzing the performance of the step. If
+ * not set, it will default to {@link ObservationRegistry#NOOP}.
+ * @param observationRegistry the observation registry to use
+ * @return this for fluent chaining
+ */
+ public ChunkOrientedStepBuilder observationRegistry(ObservationRegistry observationRegistry) {
+ this.observationRegistry = observationRegistry;
+ return self();
+ }
+
+ @SuppressWarnings("unchecked")
+ public ChunkOrientedStep build() {
+ ChunkOrientedStep chunkOrientedStep = new ChunkOrientedStep<>(this.getName(), this.chunkSize, this.reader,
+ this.writer, this.getJobRepository());
+ if (this.processor != null) {
+ chunkOrientedStep.setItemProcessor(this.processor);
+ }
+ chunkOrientedStep.setTransactionManager(this.transactionManager);
+ chunkOrientedStep.setTransactionAttribute(this.transactionAttribute);
+ chunkOrientedStep.setInterruptionPolicy(this.interruptionPolicy);
+ if (this.retryPolicy == null) {
+ if (!this.retryableExceptions.isEmpty() || this.retryLimit > 0) {
+ this.retryPolicy = RetryPolicy.builder()
+ .maxAttempts(this.retryLimit)
+ .includes(this.retryableExceptions)
+ .build();
+ }
+ else {
+ this.retryPolicy = throwable -> false;
+ }
+ }
+ chunkOrientedStep.setRetryPolicy(this.retryPolicy);
+ if (this.skipPolicy == null) {
+ if (!this.skippableExceptions.isEmpty() || this.skipLimit > 0) {
+ this.skipPolicy = new LimitCheckingExceptionHierarchySkipPolicy(this.skippableExceptions,
+ this.skipLimit);
+ }
+ else {
+ this.skipPolicy = new AlwaysSkipItemSkipPolicy();
+ }
+ }
+ chunkOrientedStep.setSkipPolicy(this.skipPolicy);
+ chunkOrientedStep.setFaultTolerant(this.faultTolerant);
+ if (this.asyncTaskExecutor != null) {
+ chunkOrientedStep.setTaskExecutor(this.asyncTaskExecutor);
+ }
+ streams.forEach(chunkOrientedStep::registerItemStream);
+ stepListeners.forEach(stepListener -> {
+ if (stepListener instanceof ItemReadListener) {
+ chunkOrientedStep.registerItemReadListener((ItemReadListener) stepListener);
+ }
+ if (stepListener instanceof ItemProcessListener) {
+ chunkOrientedStep.registerItemProcessListener((ItemProcessListener) stepListener);
+ }
+ if (stepListener instanceof ItemWriteListener) {
+ chunkOrientedStep.registerItemWriteListener((ItemWriteListener) stepListener);
+ }
+ if (stepListener instanceof ChunkListener) {
+ chunkOrientedStep.registerChunkListener((ChunkListener) stepListener);
+ }
+ });
+ retryListeners.forEach(chunkOrientedStep::registerRetryListener);
+ skipListeners.forEach(chunkOrientedStep::registerSkipListener);
+ if (this.observationRegistry != null) {
+ chunkOrientedStep.setObservationRegistry(this.observationRegistry);
+ }
+ try {
+ chunkOrientedStep.afterPropertiesSet();
+ }
+ catch (Exception e) {
+ throw new StepBuilderException("Unable to build a chunk-oriented step", e);
+ }
+ return chunkOrientedStep;
+ }
+
+}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/FaultTolerantStepBuilder.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/FaultTolerantStepBuilder.java
index 635c54550a..f18c567eb3 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/FaultTolerantStepBuilder.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/FaultTolerantStepBuilder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006-2024 the original author or authors.
+ * Copyright 2006-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -25,11 +25,11 @@
import java.util.Map;
import java.util.Set;
-import org.springframework.batch.core.ChunkListener;
-import org.springframework.batch.core.JobInterruptedException;
-import org.springframework.batch.core.SkipListener;
-import org.springframework.batch.core.StepExecutionListener;
-import org.springframework.batch.core.StepListener;
+import org.springframework.batch.core.listener.ChunkListener;
+import org.springframework.batch.core.job.JobInterruptedException;
+import org.springframework.batch.core.listener.SkipListener;
+import org.springframework.batch.core.listener.StepExecutionListener;
+import org.springframework.batch.core.listener.StepListener;
import org.springframework.batch.core.annotation.OnSkipInProcess;
import org.springframework.batch.core.annotation.OnSkipInRead;
import org.springframework.batch.core.annotation.OnSkipInWrite;
@@ -90,8 +90,13 @@
* @author Chris Schaefer
* @author Michael Minella
* @author Mahmoud Ben Hassine
+ * @author Ian Choi
* @since 2.2
+ * @deprecated Since 6.0, use
+ * {@link org.springframework.batch.core.step.builder.ChunkOrientedStepBuilder} instead.
+ * Scheduled for removal in 7.0.
*/
+@Deprecated(since = "6.0", forRemoval = true)
public class FaultTolerantStepBuilder extends SimpleStepBuilder {
private final ChunkMonitor chunkMonitor = new ChunkMonitor();
@@ -122,7 +127,7 @@ public class FaultTolerantStepBuilder extends SimpleStepBuilder {
private final Set> skipListeners = new LinkedHashSet<>();
- private int skipLimit = 0;
+ private int skipLimit = 10;
private SkipPolicy skipPolicy;
@@ -306,7 +311,7 @@ public FaultTolerantStepBuilder retryContextCache(RetryContextCache retryC
/**
* Sets the maximum number of failed items to skip before the step fails. Ignored if
* an explicit {@link #skipPolicy(SkipPolicy)} is provided.
- * @param skipLimit the skip limit to set
+ * @param skipLimit the skip limit to set. Default is 10.
* @return this for fluent chaining
*/
public FaultTolerantStepBuilder skipLimit(int skipLimit) {
@@ -554,8 +559,11 @@ protected SkipPolicy createSkipPolicy() {
map.put(ForceRollbackForWriteSkipException.class, true);
LimitCheckingItemSkipPolicy limitCheckingItemSkipPolicy = new LimitCheckingItemSkipPolicy(skipLimit, map);
if (skipPolicy == null) {
- Assert.state(!(skippableExceptionClasses.isEmpty() && skipLimit > 0),
- "If a skip limit is provided then skippable exceptions must also be specified");
+ if (skippableExceptionClasses.isEmpty() && skipLimit > 0) {
+ logger.debug(String.format(
+ "A skip limit of %s is set but no skippable exceptions are defined. Consider defining skippable exceptions.",
+ skipLimit));
+ }
skipPolicy = limitCheckingItemSkipPolicy;
}
else if (limitCheckingItemSkipPolicy != null) {
@@ -570,11 +578,10 @@ else if (limitCheckingItemSkipPolicy != null) {
protected BatchRetryTemplate createRetryOperations() {
RetryPolicy retryPolicy = this.retryPolicy;
- SimpleRetryPolicy simpleRetryPolicy = null;
Map, Boolean> map = new HashMap<>(retryableExceptionClasses);
map.put(ForceRollbackForWriteSkipException.class, true);
- simpleRetryPolicy = new SimpleRetryPolicy(retryLimit, map);
+ SimpleRetryPolicy simpleRetryPolicy = new SimpleRetryPolicy(retryLimit, map);
if (retryPolicy == null) {
Assert.state(!(retryableExceptionClasses.isEmpty() && retryLimit > 0),
@@ -597,10 +604,10 @@ else if ((!retryableExceptionClasses.isEmpty() && retryLimit > 0)) {
// Coordinate the retry policy with the exception handler:
RepeatOperations stepOperations = getStepOperations();
- if (stepOperations instanceof RepeatTemplate) {
+ if (stepOperations instanceof RepeatTemplate repeatTemplate) {
SimpleRetryExceptionHandler exceptionHandler = new SimpleRetryExceptionHandler(retryPolicyWrapper,
getExceptionHandler(), nonRetryableExceptionClasses);
- ((RepeatTemplate) stepOperations).setExceptionHandler(exceptionHandler);
+ repeatTemplate.setExceptionHandler(exceptionHandler);
}
if (retryContextCache != null) {
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/FlowStepBuilder.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/FlowStepBuilder.java
index d838c9075c..c0004d143b 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/FlowStepBuilder.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/FlowStepBuilder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006-2022 the original author or authors.
+ * Copyright 2006-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,7 +15,7 @@
*/
package org.springframework.batch.core.step.builder;
-import org.springframework.batch.core.Step;
+import org.springframework.batch.core.step.Step;
import org.springframework.batch.core.job.flow.Flow;
import org.springframework.batch.core.job.flow.FlowStep;
@@ -24,6 +24,7 @@
* nested flow composed of other steps.
*
* @author Dave Syer
+ * @author Mahmoud Ben Hassine
* @since 2.2
*/
public class FlowStepBuilder extends StepBuilderHelper {
@@ -56,7 +57,7 @@ public FlowStepBuilder flow(Flow flow) {
* @return a flow step
*/
public Step build() {
- FlowStep step = new FlowStep();
+ FlowStep step = new FlowStep(getJobRepository());
step.setName(getName());
step.setFlow(flow);
super.enhance(step);
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/JobStepBuilder.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/JobStepBuilder.java
index a9abb9663b..95b07c1387 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/JobStepBuilder.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/JobStepBuilder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006-2022 the original author or authors.
+ * Copyright 2006-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,10 +15,11 @@
*/
package org.springframework.batch.core.step.builder;
-import org.springframework.batch.core.Job;
-import org.springframework.batch.core.Step;
-import org.springframework.batch.core.launch.JobLauncher;
-import org.springframework.batch.core.launch.support.TaskExecutorJobLauncher;
+import org.springframework.batch.core.configuration.support.MapJobRegistry;
+import org.springframework.batch.core.job.Job;
+import org.springframework.batch.core.launch.JobOperator;
+import org.springframework.batch.core.launch.support.TaskExecutorJobOperator;
+import org.springframework.batch.core.step.Step;
import org.springframework.batch.core.step.job.JobParametersExtractor;
import org.springframework.batch.core.step.job.JobStep;
@@ -27,13 +28,14 @@
* with parameters taken from the parent job or from the step execution.
*
* @author Dave Syer
+ * @author Mahmoud Ben Hassine
* @since 2.2
*/
public class JobStepBuilder extends StepBuilderHelper {
private Job job;
- private JobLauncher jobLauncher;
+ private JobOperator jobOperator;
private JobParametersExtractor jobParametersExtractor;
@@ -57,12 +59,12 @@ public JobStepBuilder job(Job job) {
}
/**
- * Add a job launcher. Defaults to a simple job launcher.
- * @param jobLauncher the job launcher to use
+ * Add a job operator. Defaults to a {@link TaskExecutorJobOperator}.
+ * @param jobOperator the job operator to use
* @return this for fluent chaining
*/
- public JobStepBuilder launcher(JobLauncher jobLauncher) {
- this.jobLauncher = jobLauncher;
+ public JobStepBuilder operator(JobOperator jobOperator) {
+ this.jobOperator = jobOperator;
return this;
}
@@ -83,7 +85,7 @@ public JobStepBuilder parametersExtractor(JobParametersExtractor jobParametersEx
*/
public Step build() {
- JobStep step = new JobStep();
+ JobStep step = new JobStep(getJobRepository());
step.setName(getName());
super.enhance(step);
if (job != null) {
@@ -92,18 +94,19 @@ public Step build() {
if (jobParametersExtractor != null) {
step.setJobParametersExtractor(jobParametersExtractor);
}
- if (jobLauncher == null) {
- TaskExecutorJobLauncher jobLauncher = new TaskExecutorJobLauncher();
- jobLauncher.setJobRepository(getJobRepository());
+ if (jobOperator == null) {
+ TaskExecutorJobOperator jobOperator = new TaskExecutorJobOperator();
+ jobOperator.setJobRepository(getJobRepository());
+ jobOperator.setJobRegistry(new MapJobRegistry());
try {
- jobLauncher.afterPropertiesSet();
+ jobOperator.afterPropertiesSet();
}
catch (Exception e) {
throw new StepBuilderException(e);
}
- this.jobLauncher = jobLauncher;
+ this.jobOperator = jobOperator;
}
- step.setJobLauncher(jobLauncher);
+ step.setJobOperator(jobOperator);
try {
step.afterPropertiesSet();
}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/PartitionStepBuilder.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/PartitionStepBuilder.java
index cd4ffa0cbb..c9afe1012d 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/PartitionStepBuilder.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/PartitionStepBuilder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006-2022 the original author or authors.
+ * Copyright 2006-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,13 +15,13 @@
*/
package org.springframework.batch.core.step.builder;
-import org.springframework.batch.core.Step;
+import org.springframework.batch.core.step.Step;
import org.springframework.batch.core.partition.PartitionHandler;
import org.springframework.batch.core.partition.StepExecutionSplitter;
-import org.springframework.batch.core.partition.support.PartitionStep;
-import org.springframework.batch.core.partition.support.Partitioner;
+import org.springframework.batch.core.partition.PartitionStep;
+import org.springframework.batch.core.partition.Partitioner;
import org.springframework.batch.core.partition.support.SimpleStepExecutionSplitter;
-import org.springframework.batch.core.partition.support.StepExecutionAggregator;
+import org.springframework.batch.core.partition.StepExecutionAggregator;
import org.springframework.batch.core.partition.support.TaskExecutorPartitionHandler;
import org.springframework.core.task.SyncTaskExecutor;
import org.springframework.core.task.TaskExecutor;
@@ -159,7 +159,7 @@ public PartitionStepBuilder aggregator(StepExecutionAggregator aggregator) {
}
public Step build() {
- PartitionStep step = new PartitionStep();
+ PartitionStep step = new PartitionStep(getJobRepository());
step.setName(getName());
super.enhance(step);
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/SimpleStepBuilder.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/SimpleStepBuilder.java
index fed10c44a1..bd711e81e1 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/SimpleStepBuilder.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/SimpleStepBuilder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006-2024 the original author or authors.
+ * Copyright 2006-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,12 +24,12 @@
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Metrics;
-import org.springframework.batch.core.ChunkListener;
-import org.springframework.batch.core.ItemProcessListener;
-import org.springframework.batch.core.ItemReadListener;
-import org.springframework.batch.core.ItemWriteListener;
-import org.springframework.batch.core.StepExecutionListener;
-import org.springframework.batch.core.StepListener;
+import org.springframework.batch.core.listener.ChunkListener;
+import org.springframework.batch.core.listener.ItemProcessListener;
+import org.springframework.batch.core.listener.ItemReadListener;
+import org.springframework.batch.core.listener.ItemWriteListener;
+import org.springframework.batch.core.listener.StepExecutionListener;
+import org.springframework.batch.core.listener.StepListener;
import org.springframework.batch.core.annotation.AfterProcess;
import org.springframework.batch.core.annotation.AfterRead;
import org.springframework.batch.core.annotation.AfterWrite;
@@ -66,7 +66,10 @@
* @author Mahmoud Ben Hassine
* @author Parikshit Dutta
* @since 2.2
+ * @deprecated Since 6.0 in favor of {@link ChunkOrientedStepBuilder}. Scheduled for
+ * removal in 7.0.
*/
+@Deprecated(since = "6.0", forRemoval = true)
public class SimpleStepBuilder extends AbstractTaskletStepBuilder> {
private static final int DEFAULT_COMMIT_INTERVAL = 1;
@@ -383,16 +386,16 @@ protected CompletionPolicy getChunkCompletionPolicy() {
protected void registerAsStreamsAndListeners(ItemReader extends I> itemReader,
ItemProcessor super I, ? extends O> itemProcessor, ItemWriter super O> itemWriter) {
for (Object itemHandler : new Object[] { itemReader, itemWriter, itemProcessor }) {
- if (itemHandler instanceof ItemStream) {
- stream((ItemStream) itemHandler);
+ if (itemHandler instanceof ItemStream itemStream) {
+ stream(itemStream);
}
if (StepListenerFactoryBean.isListener(itemHandler)) {
StepListener listener = StepListenerFactoryBean.getListener(itemHandler);
- if (listener instanceof StepExecutionListener) {
- listener((StepExecutionListener) listener);
+ if (listener instanceof StepExecutionListener stepExecutionListener) {
+ listener(stepExecutionListener);
}
- if (listener instanceof ChunkListener) {
- listener((ChunkListener) listener);
+ if (listener instanceof ChunkListener chunkListener) {
+ listener(chunkListener);
}
if (listener instanceof ItemReadListener> || listener instanceof ItemProcessListener, ?>
|| listener instanceof ItemWriteListener>) {
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/StepBuilder.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/StepBuilder.java
index 8d49029a7a..9442429842 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/StepBuilder.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/StepBuilder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006-2023 the original author or authors.
+ * Copyright 2006-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,10 +15,10 @@
*/
package org.springframework.batch.core.step.builder;
-import org.springframework.batch.core.Job;
-import org.springframework.batch.core.Step;
+import org.springframework.batch.core.job.Job;
+import org.springframework.batch.core.step.Step;
import org.springframework.batch.core.job.flow.Flow;
-import org.springframework.batch.core.partition.support.Partitioner;
+import org.springframework.batch.core.partition.Partitioner;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.repeat.CompletionPolicy;
@@ -35,13 +35,13 @@
public class StepBuilder extends StepBuilderHelper {
/**
- * Initialize a step builder for a step with the given name.
- * @param name the name of the step
- * @deprecated use {@link StepBuilder#StepBuilder(String, JobRepository)}
+ * Initialize a step builder for a step with the given job repository. The name of the
+ * step will be set to the bean name by default.
+ * @param jobRepository the job repository to which the step should report to.
+ * @since 6.0
*/
- @Deprecated(since = "5.0", forRemoval = true)
- public StepBuilder(String name) {
- super(name);
+ public StepBuilder(JobRepository jobRepository) {
+ super(jobRepository);
}
/**
@@ -54,17 +54,6 @@ public StepBuilder(String name, JobRepository jobRepository) {
super(name, jobRepository);
}
- /**
- * Build a step with a custom tasklet, not necessarily item processing.
- * @param tasklet a tasklet
- * @return a {@link TaskletStepBuilder}
- * @deprecated use {@link StepBuilder#tasklet(Tasklet, PlatformTransactionManager)}
- */
- @Deprecated(since = "5.0", forRemoval = true)
- public TaskletStepBuilder tasklet(Tasklet tasklet) {
- return new TaskletStepBuilder(this).tasklet(tasklet);
- }
-
/**
* Build a step with a custom tasklet, not necessarily item processing.
* @param tasklet a tasklet
@@ -77,24 +66,13 @@ public TaskletStepBuilder tasklet(Tasklet tasklet, PlatformTransactionManager tr
}
/**
- * Build a step that processes items in chunks with the size provided. To extend the
- * step to being fault tolerant, call the {@link SimpleStepBuilder#faultTolerant()}
- * method on the builder. In most cases you will want to parameterize your call to
- * this method, to preserve the type safety of your readers and writers, e.g.
- *
- *
- * new StepBuilder("step1").<Order, Ledger> chunk(100).reader(new OrderReader()).writer(new LedgerWriter())
- * // ... etc.
- *
- * @param chunkSize the chunk size (commit interval)
- * @return a {@link SimpleStepBuilder}
- * @param the type of item to be processed as input
- * @param the type of item to be output
- * @deprecated use {@link StepBuilder#chunk(int, PlatformTransactionManager)}
+ * Build a step with a custom tasklet, not necessarily item processing.
+ * @param tasklet a tasklet
+ * @return a {@link TaskletStepBuilder}
+ * @since 6.0
*/
- @Deprecated(since = "5.0", forRemoval = true)
- public SimpleStepBuilder chunk(int chunkSize) {
- return new SimpleStepBuilder(this).chunk(chunkSize);
+ public TaskletStepBuilder tasklet(Tasklet tasklet) {
+ return new TaskletStepBuilder(this).tasklet(tasklet);
}
/**
@@ -114,32 +92,26 @@ public SimpleStepBuilder chunk(int chunkSize) {
* @param the type of item to be processed as input
* @param the type of item to be output
* @since 5.0
+ * @deprecated since 6.0, use {@link #chunk(int)} instead. Scheduled for removal in
+ * 7.0.
*/
+ @Deprecated(since = "6.0", forRemoval = true)
public SimpleStepBuilder chunk(int chunkSize, PlatformTransactionManager transactionManager) {
return new SimpleStepBuilder(this).transactionManager(transactionManager).chunk(chunkSize);
}
/**
- * Build a step that processes items in chunks with the completion policy provided. To
- * extend the step to being fault tolerant, call the
- * {@link SimpleStepBuilder#faultTolerant()} method on the builder. In most cases you
- * will want to parameterize your call to this method, to preserve the type safety of
- * your readers and writers, e.g.
- *
- *
- * new StepBuilder("step1").<Order, Ledger> chunk(100).reader(new OrderReader()).writer(new LedgerWriter())
- * // ... etc.
- *
- * @param completionPolicy the completion policy to use to control chunk processing
- * @return a {@link SimpleStepBuilder}
+ * Build a step that processes items in chunks with the size provided. To extend the
+ * step to being fault-tolerant, call the
+ * {@link ChunkOrientedStepBuilder#faultTolerant()} method on the builder.
+ * @param chunkSize the chunk size (commit interval)
+ * @return a {@link ChunkOrientedStepBuilder} for method chaining
* @param the type of item to be processed as input
* @param the type of item to be output
- * @deprecated use
- * {@link StepBuilder#chunk(CompletionPolicy, PlatformTransactionManager)}
+ * @since 6.0
*/
- @Deprecated(since = "5.0", forRemoval = true)
- public SimpleStepBuilder chunk(CompletionPolicy completionPolicy) {
- return new SimpleStepBuilder(this).chunk(completionPolicy);
+ public ChunkOrientedStepBuilder chunk(int chunkSize) {
+ return new ChunkOrientedStepBuilder<>(this, chunkSize);
}
/**
@@ -160,7 +132,10 @@ public SimpleStepBuilder chunk(CompletionPolicy completionPolicy) {
* @param the type of item to be processed as input
* @param the type of item to be output
* @since 5.0
+ * @deprecated since 6.0, use {@link #chunk(int)} instead. Scheduled for removal in
+ * 7.0.
*/
+ @Deprecated(since = "6.0", forRemoval = true)
public SimpleStepBuilder chunk(CompletionPolicy completionPolicy,
PlatformTransactionManager transactionManager) {
return new SimpleStepBuilder(this).transactionManager(transactionManager).chunk(completionPolicy);
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/StepBuilderException.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/StepBuilderException.java
index 540c8970ac..3dfbff7bea 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/StepBuilderException.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/StepBuilderException.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006-2023 the original author or authors.
+ * Copyright 2006-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,6 +24,10 @@
*/
public class StepBuilderException extends RuntimeException {
+ public StepBuilderException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
public StepBuilderException(Exception e) {
super(e);
}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/StepBuilderHelper.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/StepBuilderHelper.java
index 577f383e08..45568cee55 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/StepBuilderHelper.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/StepBuilderHelper.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006-2023 the original author or authors.
+ * Copyright 2006-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,18 +21,14 @@
import java.util.List;
import java.util.Set;
-import io.micrometer.core.instrument.MeterRegistry;
-import io.micrometer.core.instrument.Metrics;
import io.micrometer.observation.ObservationRegistry;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
-import org.springframework.batch.core.StepExecutionListener;
+import org.springframework.batch.core.listener.StepExecutionListener;
import org.springframework.batch.core.annotation.AfterStep;
import org.springframework.batch.core.annotation.BeforeStep;
import org.springframework.batch.core.listener.StepListenerFactoryBean;
-import org.springframework.batch.core.observability.BatchStepObservationConvention;
-import org.springframework.batch.core.observability.DefaultBatchStepObservationConvention;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.step.AbstractStep;
import org.springframework.batch.support.ReflectionUtils;
@@ -54,14 +50,13 @@ public abstract class StepBuilderHelper> {
protected final CommonStepProperties properties;
/**
- * Create a new {@link StepBuilderHelper}.
- * @param name the step name
- * @deprecated use {@link StepBuilderHelper#StepBuilderHelper(String, JobRepository)}
+ * Create a new {@link StepBuilderHelper} with the given job repository.
+ * @param jobRepository the job repository
+ * @since 6.0
*/
- @Deprecated(since = "5.1", forRemoval = true)
- public StepBuilderHelper(String name) {
+ public StepBuilderHelper(JobRepository jobRepository) {
this.properties = new CommonStepProperties();
- properties.name = name;
+ properties.jobRepository = jobRepository;
}
/**
@@ -85,39 +80,11 @@ protected StepBuilderHelper(StepBuilderHelper> parent) {
this.properties = new CommonStepProperties(parent.properties);
}
- /**
- * Set the job repository
- * @param jobRepository the repository to set
- * @return this to enable fluent chaining
- * @deprecated use {@link StepBuilderHelper#StepBuilderHelper(String, JobRepository)}
- */
- @Deprecated(since = "5.1", forRemoval = true)
- public B repository(JobRepository jobRepository) {
- properties.jobRepository = jobRepository;
- return self();
- }
-
- /**
- * Sets the step observation convention.
- * @param observationConvention the step observation convention (optional)
- * @return this to enable fluent chaining
- * @since 5.1
- */
- public B observationConvention(BatchStepObservationConvention observationConvention) {
- properties.observationConvention = observationConvention;
- return self();
- }
-
public B observationRegistry(ObservationRegistry observationRegistry) {
properties.observationRegistry = observationRegistry;
return self();
}
- public B meterRegistry(MeterRegistry meterRegistry) {
- properties.meterRegistry = meterRegistry;
- return self();
- }
-
public B startLimit(int startLimit) {
properties.startLimit = startLimit;
return self();
@@ -169,21 +136,11 @@ protected boolean isAllowStartIfComplete() {
protected void enhance(AbstractStep step) {
step.setJobRepository(properties.getJobRepository());
- BatchStepObservationConvention observationConvention = properties.getObservationConvention();
- if (observationConvention != null) {
- step.setObservationConvention(observationConvention);
- }
-
ObservationRegistry observationRegistry = properties.getObservationRegistry();
if (observationRegistry != null) {
step.setObservationRegistry(observationRegistry);
}
- MeterRegistry meterRegistry = properties.getMeterRegistry();
- if (meterRegistry != null) {
- step.setMeterRegistry(meterRegistry);
- }
-
Boolean allowStartIfComplete = properties.allowStartIfComplete;
if (allowStartIfComplete != null) {
step.setAllowStartIfComplete(allowStartIfComplete);
@@ -199,6 +156,8 @@ protected void enhance(AbstractStep step) {
public static class CommonStepProperties {
+ private String name;
+
private List stepExecutionListeners = new ArrayList<>();
private int startLimit = Integer.MAX_VALUE;
@@ -207,12 +166,8 @@ public static class CommonStepProperties {
private JobRepository jobRepository;
- private BatchStepObservationConvention observationConvention = new DefaultBatchStepObservationConvention();
-
private ObservationRegistry observationRegistry = ObservationRegistry.NOOP;
- private MeterRegistry meterRegistry = Metrics.globalRegistry;
-
public CommonStepProperties() {
}
@@ -221,9 +176,7 @@ public CommonStepProperties(CommonStepProperties properties) {
this.startLimit = properties.startLimit;
this.allowStartIfComplete = properties.allowStartIfComplete;
this.jobRepository = properties.jobRepository;
- this.observationConvention = properties.observationConvention;
this.observationRegistry = properties.observationRegistry;
- this.meterRegistry = properties.meterRegistry;
this.stepExecutionListeners = new ArrayList<>(properties.stepExecutionListeners);
}
@@ -235,14 +188,6 @@ public void setJobRepository(JobRepository jobRepository) {
this.jobRepository = jobRepository;
}
- public BatchStepObservationConvention getObservationConvention() {
- return observationConvention;
- }
-
- public void setObservationConvention(BatchStepObservationConvention observationConvention) {
- this.observationConvention = observationConvention;
- }
-
public ObservationRegistry getObservationRegistry() {
return observationRegistry;
}
@@ -251,14 +196,6 @@ public void setObservationRegistry(ObservationRegistry observationRegistry) {
this.observationRegistry = observationRegistry;
}
- public MeterRegistry getMeterRegistry() {
- return meterRegistry;
- }
-
- public void setMeterRegistry(MeterRegistry meterRegistry) {
- this.meterRegistry = meterRegistry;
- }
-
public String getName() {
return name;
}
@@ -295,8 +232,6 @@ public void setAllowStartIfComplete(Boolean allowStartIfComplete) {
this.allowStartIfComplete = allowStartIfComplete;
}
- private String name;
-
}
}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/TaskletStepBuilder.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/TaskletStepBuilder.java
index bf4aad229f..d21ccc5a31 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/TaskletStepBuilder.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/TaskletStepBuilder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006-2023 the original author or authors.
+ * Copyright 2006-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -41,23 +41,21 @@ public TaskletStepBuilder(StepBuilderHelper> parent) {
/**
* @param tasklet the tasklet to use
* @return this for fluent chaining
- * @deprecated use
- * {@link TaskletStepBuilder#tasklet(Tasklet, PlatformTransactionManager)}
+ * @since 5.0
*/
- @Deprecated(since = "5.0", forRemoval = true)
- public TaskletStepBuilder tasklet(Tasklet tasklet) {
+ public TaskletStepBuilder tasklet(Tasklet tasklet, PlatformTransactionManager transactionManager) {
this.tasklet = tasklet;
+ super.transactionManager(transactionManager);
return this;
}
/**
* @param tasklet the tasklet to use
* @return this for fluent chaining
- * @since 5.0
+ * @since 6.0
*/
- public TaskletStepBuilder tasklet(Tasklet tasklet, PlatformTransactionManager transactionManager) {
+ public TaskletStepBuilder tasklet(Tasklet tasklet) {
this.tasklet = tasklet;
- super.transactionManager(transactionManager);
return this;
}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/factory/BatchListenerFactoryHelper.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/factory/BatchListenerFactoryHelper.java
index 901ca75b3e..0204b7dfea 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/factory/BatchListenerFactoryHelper.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/factory/BatchListenerFactoryHelper.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006-2007 the original author or authors.
+ * Copyright 2006-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,14 +18,15 @@
import java.util.ArrayList;
import java.util.List;
-import org.springframework.batch.core.StepListener;
+import org.springframework.batch.core.listener.StepListener;
/**
* Package private helper for step factory beans.
- *
+ *
* @author Dave Syer
- *
+ * @deprecated Since 6.0 with no replacement. Scheduled for removal in 7.0.
*/
+@Deprecated(since = "6.0", forRemoval = true)
abstract class BatchListenerFactoryHelper {
public static List getListeners(StepListener[] listeners, Class super T> cls) {
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/factory/FaultTolerantStepFactoryBean.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/factory/FaultTolerantStepFactoryBean.java
index 48aed1eae5..e85c960622 100755
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/factory/FaultTolerantStepFactoryBean.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/factory/FaultTolerantStepFactoryBean.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006-2023 the original author or authors.
+ * Copyright 2006-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,8 +21,8 @@
import java.util.HashSet;
import java.util.Map;
-import org.springframework.batch.core.SkipListener;
-import org.springframework.batch.core.Step;
+import org.springframework.batch.core.listener.SkipListener;
+import org.springframework.batch.core.step.Step;
import org.springframework.batch.core.step.builder.FaultTolerantStepBuilder;
import org.springframework.batch.core.step.builder.SimpleStepBuilder;
import org.springframework.batch.core.step.builder.StepBuilder;
@@ -47,8 +47,10 @@
* @author Dave Syer
* @author Robert Kasanicky
* @author Morten Andersen-Gott
- *
+ * @author Ian Choi
+ * @deprecated Since 6.0 with no replacement. Scheduled for removal in 7.0.
*/
+@Deprecated(since = "6.0", forRemoval = true)
public class FaultTolerantStepFactoryBean extends SimpleStepFactoryBean {
private Map, Boolean> skippableExceptionClasses = new HashMap<>();
@@ -61,7 +63,7 @@ public class FaultTolerantStepFactoryBean