diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/FaultTolerantJobLauncher.java b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/FaultTolerantJobLauncher.java
new file mode 100644
index 0000000000..f1b267bea1
--- /dev/null
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/FaultTolerantJobLauncher.java
@@ -0,0 +1,230 @@
+package org.springframework.batch.core.launch.support;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.batch.core.*;
+import org.springframework.batch.core.launch.JobLauncher;
+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.beans.factory.InitializingBean;
+import org.springframework.core.task.SyncTaskExecutor;
+import org.springframework.core.task.TaskExecutor;
+import org.springframework.core.task.TaskRejectedException;
+import org.springframework.retry.RetryCallback;
+import org.springframework.retry.RetryContext;
+import org.springframework.retry.support.RetryTemplate;
+import org.springframework.util.Assert;
+
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Simple implementation of the {@link JobLauncher} interface. The Spring Core
+ * {@link TaskExecutor} interface is used to launch a {@link Job}. This means
+ * that the type of executor set is very important. If a
+ * {@link SyncTaskExecutor} is used, then the job will be processed
+ * within the same thread that called the launcher. Care should
+ * be taken to ensure any users of this class understand fully whether or not
+ * the implementation of TaskExecutor used will start tasks synchronously or
+ * asynchronously. The default setting uses a synchronous task executor.
+ *
+ * There are two required dependencies of this Launcher, a
+ * {@link JobRepository} and {@link RetryTemplate}. The JobRepository is used to
+ * obtain a valid JobExecution. The Repository must be used because the provided
+ * {@link Job} could be a restart of an existing {@link JobInstance}, and only the
+ * Repository can reliably recreate it. The RetryTemplate is used to automatically
+ * restart and retry the failed job.
+ *
+ * @see JobRepository
+ * @see TaskExecutor
+ */
+public class FaultTolerantJobLauncher implements JobLauncher, InitializingBean {
+
+ protected static final Log logger = LogFactory.getLog(FaultTolerantJobLauncher.class);
+
+ private JobRepository jobRepository;
+
+ private RetryTemplate retryTemplate;
+
+ private TaskExecutor taskExecutor;
+
+ /**
+ * Run the provided job with the given {@link JobParameters}. The
+ * {@link JobParameters} will be used to determine if this is an execution
+ * of an existing job instance, or if a new one should be created.
+ *
+ * @param job the job to be run.
+ * @param jobParameters the {@link JobParameters} for this particular
+ * execution.
+ * @return JobExecutionAlreadyRunningException if the JobInstance already
+ * exists and has an execution already running.
+ * @throws JobRestartException if the execution would be a re-start, but a
+ * re-start is either not allowed or not needed.
+ * @throws JobInstanceAlreadyCompleteException if this instance has already
+ * completed successfully
+ * @throws JobParametersInvalidException
+ */
+ @Override
+ public JobExecution run(final Job job, final JobParameters jobParameters)
+ throws JobExecutionAlreadyRunningException, JobRestartException, JobInstanceAlreadyCompleteException,
+ JobParametersInvalidException {
+
+ Assert.notNull(job, "The Job must not be null.");
+ Assert.notNull(jobParameters, "The JobParameters must not be null.");
+
+ final AtomicReference executionReference = new AtomicReference<>();
+ JobExecution lastExecution = jobRepository.getLastJobExecution(job.getName(), jobParameters);
+ if (lastExecution != null) {
+ if (!job.isRestartable()) {
+ throw new JobRestartException("JobInstance already exists and is not restartable");
+ }
+ /*
+ * validate here if it has stepExecutions that are UNKNOWN, STARTING, STARTED and STOPPING
+ * retrieve the previous execution and check
+ */
+ for (StepExecution execution : lastExecution.getStepExecutions()) {
+ BatchStatus status = execution.getStatus();
+ if (status.isRunning() || status == BatchStatus.STOPPING) {
+ throw new JobExecutionAlreadyRunningException("A job execution for this job is already running: "
+ + lastExecution);
+ } else if (status == BatchStatus.UNKNOWN) {
+ throw new JobRestartException(
+ "Cannot restart step [" + execution.getStepName() + "] from UNKNOWN status. "
+ + "The last execution ended with a failure that could not be rolled back, "
+ + "so it may be dangerous to proceed. Manual intervention is probably necessary.");
+ }
+ }
+ }
+
+ // Check the validity of the parameters before doing creating anything
+ // in the repository...
+ job.getJobParametersValidator().validate(jobParameters);
+
+ taskExecutor.execute(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ retryTemplate.execute(new FaultTolerantJobRetryCallback(executionReference, job, jobParameters));
+ } catch (TaskRejectedException e) {
+ executionReference.get().upgradeStatus(BatchStatus.FAILED);
+ if (executionReference.get().getExitStatus().equals(ExitStatus.UNKNOWN)) {
+ executionReference.get().setExitStatus(ExitStatus.FAILED.addExitDescription(e));
+ }
+ jobRepository.update(executionReference.get());
+ }
+ }
+ });
+
+ return executionReference.get();
+ }
+
+ /**
+ * Set the JobRepsitory.
+ *
+ * @param jobRepository
+ */
+ public void setJobRepository(JobRepository jobRepository) {
+ this.jobRepository = jobRepository;
+ }
+
+ /**
+ * Set the retryTemplate
+ *
+ * @param retryTemplate
+ */
+ public void setRetryTemplate(RetryTemplate retryTemplate) {
+ this.retryTemplate = retryTemplate;
+ }
+
+ /**
+ * Set the TaskExecutor. (Optional)
+ *
+ * @param taskExecutor
+ */
+ public void setTaskExecutor(TaskExecutor taskExecutor) {
+ this.taskExecutor = taskExecutor;
+ }
+
+ /**
+ * Ensure the required dependencies of a {@link JobRepository} have been
+ * set.
+ */
+ @Override
+ public void afterPropertiesSet() throws Exception {
+ Assert.state(jobRepository != null, "A JobRepository has not been set.");
+ Assert.state(retryTemplate != null, "A RetryTemplate has not been set.");
+ if (taskExecutor == null) {
+ logger.info("No TaskExecutor has been set, defaulting to synchronous executor.");
+ taskExecutor = new SyncTaskExecutor();
+ }
+ }
+
+ private class FaultTolerantJobRetryCallback implements RetryCallback