diff --git a/spring-batch/.gitignore b/spring-batch/.gitignore
new file mode 100644
index 0000000000..0ef6d10b38
--- /dev/null
+++ b/spring-batch/.gitignore
@@ -0,0 +1 @@
+output.csv
\ No newline at end of file
diff --git a/spring-batch/README.md b/spring-batch/README.md
new file mode 100644
index 0000000000..95abbaf931
--- /dev/null
+++ b/spring-batch/README.md
@@ -0,0 +1,9 @@
+## Spring Batch
+
+This module contains articles about Spring Batch
+
+### Relevant Articles:
+- [Introduction to Spring Batch](https://www.baeldung.com/introduction-to-spring-batch)
+- [Spring Batch using Partitioner](https://www.baeldung.com/spring-batch-partitioner)
+- [Spring Batch – Tasklets vs Chunks](https://www.baeldung.com/spring-batch-tasklet-chunk)
+- [How to Trigger and Stop a Scheduled Spring Batch Job](https://www.baeldung.com/spring-batch-start-stop-job)
diff --git a/spring-batch/pom.xml b/spring-batch/pom.xml
new file mode 100644
index 0000000000..e81078568b
--- /dev/null
+++ b/spring-batch/pom.xml
@@ -0,0 +1,72 @@
+
+ 4.0.0
+ com.baeldung
+ spring-batch
+ 0.1-SNAPSHOT
+ spring-batch
+ jar
+ http://maven.apache.org
+
+
+ com.baeldung
+ parent-modules
+ 1.0.0-SNAPSHOT
+
+
+
+
+
+ org.xerial
+ sqlite-jdbc
+ ${sqlite.version}
+
+
+ org.springframework
+ spring-oxm
+ ${spring.version}
+
+
+ commons-logging
+ commons-logging
+
+
+
+
+ org.springframework
+ spring-jdbc
+ ${spring.version}
+
+
+ org.springframework.batch
+ spring-batch-core
+ ${spring.batch.version}
+
+
+ org.springframework.batch
+ spring-batch-test
+ ${spring.batch.version}
+
+
+ com.opencsv
+ opencsv
+ ${opencsv.version}
+
+
+
+ org.awaitility
+ awaitility
+ ${awaitility.version}
+ test
+
+
+
+
+ 5.0.3.RELEASE
+ 4.0.0.RELEASE
+ 3.15.1
+ 4.1
+ 3.1.1
+
+
+
diff --git a/spring-batch/repository.sqlite b/spring-batch/repository.sqlite
new file mode 100644
index 0000000000..4456ef63cc
Binary files /dev/null and b/spring-batch/repository.sqlite differ
diff --git a/spring-batch/src/main/java/org/baeldung/batch/App.java b/spring-batch/src/main/java/org/baeldung/batch/App.java
new file mode 100644
index 0000000000..749591aa03
--- /dev/null
+++ b/spring-batch/src/main/java/org/baeldung/batch/App.java
@@ -0,0 +1,45 @@
+package org.baeldung.batch;
+
+import org.springframework.batch.core.Job;
+import org.springframework.batch.core.JobExecution;
+import org.springframework.batch.core.JobParameters;
+import org.springframework.batch.core.JobParametersBuilder;
+import org.springframework.batch.core.launch.JobLauncher;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+
+public class App {
+ public static void main(final String[] args) {
+ // Spring Java config
+ final AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
+ context.register(SpringConfig.class);
+ context.register(SpringBatchConfig.class);
+ context.refresh();
+
+ // Spring xml config
+ // ApplicationContext context = new ClassPathXmlApplicationContext("spring-batch.xml");
+
+ runJob(context, "firstBatchJob");
+ runJob(context, "skippingBatchJob");
+ runJob(context, "skipPolicyBatchJob");
+ }
+
+ private static void runJob(AnnotationConfigApplicationContext context, String batchJobName) {
+ final JobLauncher jobLauncher = (JobLauncher) context.getBean("jobLauncher");
+ final Job job = (Job) context.getBean(batchJobName);
+
+ System.out.println("----------------------------------------");
+ System.out.println("Starting the batch job: " + batchJobName);
+ try {
+ // To enable multiple execution of a job with the same parameters
+ JobParameters jobParameters = new JobParametersBuilder()
+ .addString("jobID", String.valueOf(System.currentTimeMillis()))
+ .toJobParameters();
+ final JobExecution execution = jobLauncher.run(job, jobParameters);
+ System.out.println("Job Status : " + execution.getStatus());
+ System.out.println("Job succeeded");
+ } catch (final Exception e) {
+ e.printStackTrace();
+ System.out.println("Job failed");
+ }
+ }
+}
\ No newline at end of file
diff --git a/spring-batch/src/main/java/org/baeldung/batch/SpringBatchConfig.java b/spring-batch/src/main/java/org/baeldung/batch/SpringBatchConfig.java
new file mode 100644
index 0000000000..b318dda154
--- /dev/null
+++ b/spring-batch/src/main/java/org/baeldung/batch/SpringBatchConfig.java
@@ -0,0 +1,145 @@
+package org.baeldung.batch;
+
+import org.baeldung.batch.model.Transaction;
+import org.baeldung.batch.service.CustomItemProcessor;
+import org.baeldung.batch.service.CustomSkipPolicy;
+import org.baeldung.batch.service.MissingUsernameException;
+import org.baeldung.batch.service.NegativeAmountException;
+import org.baeldung.batch.service.RecordFieldSetMapper;
+import org.baeldung.batch.service.SkippingItemProcessor;
+import org.springframework.batch.core.Job;
+import org.springframework.batch.core.Step;
+import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
+import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
+import org.springframework.batch.item.ItemProcessor;
+import org.springframework.batch.item.ItemReader;
+import org.springframework.batch.item.ItemWriter;
+import org.springframework.batch.item.UnexpectedInputException;
+import org.springframework.batch.item.file.FlatFileItemReader;
+import org.springframework.batch.item.file.mapping.DefaultLineMapper;
+import org.springframework.batch.item.file.transform.DelimitedLineTokenizer;
+import org.springframework.batch.item.xml.StaxEventItemWriter;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.core.io.Resource;
+import org.springframework.oxm.Marshaller;
+import org.springframework.oxm.jaxb.Jaxb2Marshaller;
+
+import java.text.ParseException;
+
+public class SpringBatchConfig {
+ @Autowired
+ private JobBuilderFactory jobBuilderFactory;
+
+ @Autowired
+ private StepBuilderFactory stepBuilderFactory;
+
+ @Value("input/record.csv")
+ private Resource inputCsv;
+
+ @Value("input/recordWithInvalidData.csv")
+ private Resource invalidInputCsv;
+
+ @Value("file:xml/output.xml")
+ private Resource outputXml;
+
+ public ItemReader itemReader(Resource inputData) throws UnexpectedInputException, ParseException {
+ FlatFileItemReader reader = new FlatFileItemReader<>();
+ DelimitedLineTokenizer tokenizer = new DelimitedLineTokenizer();
+ String[] tokens = {"username", "userid", "transactiondate", "amount"};
+ tokenizer.setNames(tokens);
+ reader.setResource(inputData);
+ DefaultLineMapper lineMapper = new DefaultLineMapper<>();
+ lineMapper.setLineTokenizer(tokenizer);
+ lineMapper.setFieldSetMapper(new RecordFieldSetMapper());
+ reader.setLinesToSkip(1);
+ reader.setLineMapper(lineMapper);
+ return reader;
+ }
+
+ @Bean
+ public ItemProcessor itemProcessor() {
+ return new CustomItemProcessor();
+ }
+
+ @Bean
+ public ItemProcessor skippingItemProcessor() {
+ return new SkippingItemProcessor();
+ }
+
+ @Bean
+ public ItemWriter itemWriter(Marshaller marshaller) {
+ StaxEventItemWriter itemWriter = new StaxEventItemWriter<>();
+ itemWriter.setMarshaller(marshaller);
+ itemWriter.setRootTagName("transactionRecord");
+ itemWriter.setResource(outputXml);
+ return itemWriter;
+ }
+
+ @Bean
+ public Marshaller marshaller() {
+ Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
+ marshaller.setClassesToBeBound(Transaction.class);
+ return marshaller;
+ }
+
+ @Bean
+ protected Step step1(@Qualifier("itemProcessor") ItemProcessor processor,
+ ItemWriter writer) throws ParseException {
+ return stepBuilderFactory.get("step1").chunk(10).reader(itemReader(inputCsv)).processor(processor).writer(writer).build();
+ }
+
+ @Bean(name = "firstBatchJob")
+ public Job job(@Qualifier("step1") Step step1) {
+ return jobBuilderFactory.get("firstBatchJob").start(step1).build();
+ }
+
+ @Bean
+ public Step skippingStep(@Qualifier("skippingItemProcessor") ItemProcessor processor,
+ ItemWriter writer) throws ParseException {
+ return stepBuilderFactory
+ .get("skippingStep")
+ .chunk(10)
+ .reader(itemReader(invalidInputCsv))
+ .processor(processor)
+ .writer(writer)
+ .faultTolerant()
+ .skipLimit(2)
+ .skip(MissingUsernameException.class)
+ .skip(NegativeAmountException.class)
+ .build();
+ }
+
+ @Bean(name = "skippingBatchJob")
+ public Job skippingJob(@Qualifier("skippingStep") Step skippingStep) {
+ return jobBuilderFactory
+ .get("skippingBatchJob")
+ .start(skippingStep)
+ .build();
+ }
+
+ @Bean
+ public Step skipPolicyStep(@Qualifier("skippingItemProcessor") ItemProcessor processor,
+ ItemWriter writer) throws ParseException {
+ return stepBuilderFactory
+ .get("skipPolicyStep")
+ .chunk(10)
+ .reader(itemReader(invalidInputCsv))
+ .processor(processor)
+ .writer(writer)
+ .faultTolerant()
+ .skipPolicy(new CustomSkipPolicy())
+ .build();
+ }
+
+ @Bean(name = "skipPolicyBatchJob")
+ public Job skipPolicyBatchJob(@Qualifier("skipPolicyStep") Step skipPolicyStep) {
+ return jobBuilderFactory
+ .get("skipPolicyBatchJob")
+ .start(skipPolicyStep)
+ .build();
+ }
+
+}
diff --git a/spring-batch/src/main/java/org/baeldung/batch/SpringConfig.java b/spring-batch/src/main/java/org/baeldung/batch/SpringConfig.java
new file mode 100644
index 0000000000..35abcb2d16
--- /dev/null
+++ b/spring-batch/src/main/java/org/baeldung/batch/SpringConfig.java
@@ -0,0 +1,78 @@
+package org.baeldung.batch;
+
+import java.net.MalformedURLException;
+
+import javax.sql.DataSource;
+
+import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
+import org.springframework.batch.core.launch.JobLauncher;
+import org.springframework.batch.core.launch.support.SimpleJobLauncher;
+import org.springframework.batch.core.repository.JobRepository;
+import org.springframework.batch.core.repository.support.JobRepositoryFactoryBean;
+import org.springframework.batch.support.transaction.ResourcelessTransactionManager;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.io.Resource;
+import org.springframework.jdbc.datasource.DriverManagerDataSource;
+import org.springframework.jdbc.datasource.init.DataSourceInitializer;
+import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
+import org.springframework.transaction.PlatformTransactionManager;
+
+@Configuration
+@EnableBatchProcessing
+public class SpringConfig {
+
+ @Value("org/springframework/batch/core/schema-drop-sqlite.sql")
+ private Resource dropReopsitoryTables;
+
+ @Value("org/springframework/batch/core/schema-sqlite.sql")
+ private Resource dataReopsitorySchema;
+
+ @Bean
+ public DataSource dataSource() {
+ DriverManagerDataSource dataSource = new DriverManagerDataSource();
+ dataSource.setDriverClassName("org.sqlite.JDBC");
+ dataSource.setUrl("jdbc:sqlite:repository.sqlite");
+ return dataSource;
+ }
+
+ @Bean
+ public DataSourceInitializer dataSourceInitializer(DataSource dataSource) throws MalformedURLException {
+ ResourceDatabasePopulator databasePopulator = new ResourceDatabasePopulator();
+
+ databasePopulator.addScript(dropReopsitoryTables);
+ databasePopulator.addScript(dataReopsitorySchema);
+ databasePopulator.setIgnoreFailedDrops(true);
+
+ DataSourceInitializer initializer = new DataSourceInitializer();
+ initializer.setDataSource(dataSource);
+ initializer.setDatabasePopulator(databasePopulator);
+
+ return initializer;
+ }
+
+ private JobRepository getJobRepository() throws Exception {
+ JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean();
+ factory.setDataSource(dataSource());
+ factory.setTransactionManager(getTransactionManager());
+ // JobRepositoryFactoryBean's methods Throws Generic Exception,
+ // it would have been better to have a specific one
+ factory.afterPropertiesSet();
+ return (JobRepository) factory.getObject();
+ }
+
+ private PlatformTransactionManager getTransactionManager() {
+ return new ResourcelessTransactionManager();
+ }
+
+ public JobLauncher getJobLauncher() throws Exception {
+ SimpleJobLauncher jobLauncher = new SimpleJobLauncher();
+ // SimpleJobLauncher's methods Throws Generic Exception,
+ // it would have been better to have a specific one
+ jobLauncher.setJobRepository(getJobRepository());
+ jobLauncher.afterPropertiesSet();
+ return jobLauncher;
+ }
+
+}
\ No newline at end of file
diff --git a/spring-batch/src/main/java/org/baeldung/batch/model/Transaction.java b/spring-batch/src/main/java/org/baeldung/batch/model/Transaction.java
new file mode 100644
index 0000000000..0ce3a413ab
--- /dev/null
+++ b/spring-batch/src/main/java/org/baeldung/batch/model/Transaction.java
@@ -0,0 +1,54 @@
+package org.baeldung.batch.model;
+
+import java.util.Date;
+
+import javax.xml.bind.annotation.XmlRootElement;
+
+@SuppressWarnings("restriction")
+@XmlRootElement(name = "transactionRecord")
+public class Transaction {
+ private String username;
+ private int userId;
+ private Date transactionDate;
+ private double amount;
+
+ /* getters and setters for the attributes */
+
+ public String getUsername() {
+ return username;
+ }
+
+ public void setUsername(String username) {
+ this.username = username;
+ }
+
+ public int getUserId() {
+ return userId;
+ }
+
+ public void setUserId(int userId) {
+ this.userId = userId;
+ }
+
+ public Date getTransactionDate() {
+ return transactionDate;
+ }
+
+ public void setTransactionDate(Date transactionDate) {
+ this.transactionDate = transactionDate;
+ }
+
+ public double getAmount() {
+ return amount;
+ }
+
+ public void setAmount(double amount) {
+ this.amount = amount;
+ }
+
+ @Override
+ public String toString() {
+ return "Transaction [username=" + username + ", userId=" + userId + ", transactionDate=" + transactionDate + ", amount=" + amount + "]";
+ }
+
+}
diff --git a/spring-batch/src/main/java/org/baeldung/batch/partitioner/CustomMultiResourcePartitioner.java b/spring-batch/src/main/java/org/baeldung/batch/partitioner/CustomMultiResourcePartitioner.java
new file mode 100644
index 0000000000..667e013c35
--- /dev/null
+++ b/spring-batch/src/main/java/org/baeldung/batch/partitioner/CustomMultiResourcePartitioner.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2006-2013 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
+ *
+ * http://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.baeldung.batch.partitioner;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.springframework.batch.core.partition.support.Partitioner;
+import org.springframework.batch.item.ExecutionContext;
+import org.springframework.core.io.Resource;
+import org.springframework.util.Assert;
+
+public class CustomMultiResourcePartitioner implements Partitioner {
+
+ private static final String DEFAULT_KEY_NAME = "fileName";
+
+ private static final String PARTITION_KEY = "partition";
+
+ private Resource[] resources = new Resource[0];
+
+ private String keyName = DEFAULT_KEY_NAME;
+
+ /**
+ * The resources to assign to each partition. In Spring configuration you
+ * can use a pattern to select multiple resources.
+ * @param resources the resources to use
+ */
+ public void setResources(Resource[] resources) {
+ this.resources = resources;
+ }
+
+ /**
+ * The name of the key for the file name in each {@link ExecutionContext}.
+ * Defaults to "fileName".
+ * @param keyName the value of the key
+ */
+ public void setKeyName(String keyName) {
+ this.keyName = keyName;
+ }
+
+ /**
+ * Assign the filename of each of the injected resources to an
+ * {@link ExecutionContext}.
+ *
+ * @see Partitioner#partition(int)
+ */
+ @Override
+ public Map partition(int gridSize) {
+ Map map = new HashMap(gridSize);
+ int i = 0, k = 1;
+ for (Resource resource : resources) {
+ ExecutionContext context = new ExecutionContext();
+ Assert.state(resource.exists(), "Resource does not exist: " + resource);
+ context.putString(keyName, resource.getFilename());
+ context.putString("opFileName", "output" + k++ + ".xml");
+
+ map.put(PARTITION_KEY + i, context);
+ i++;
+ }
+ return map;
+ }
+
+}
diff --git a/spring-batch/src/main/java/org/baeldung/batch/partitioner/SpringbatchPartitionConfig.java b/spring-batch/src/main/java/org/baeldung/batch/partitioner/SpringbatchPartitionConfig.java
new file mode 100644
index 0000000000..ad3aee4a2e
--- /dev/null
+++ b/spring-batch/src/main/java/org/baeldung/batch/partitioner/SpringbatchPartitionConfig.java
@@ -0,0 +1,166 @@
+package org.baeldung.batch.partitioner;
+
+import org.baeldung.batch.model.Transaction;
+import org.baeldung.batch.service.RecordFieldSetMapper;
+import org.springframework.batch.core.Job;
+import org.springframework.batch.core.Step;
+import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
+import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
+import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
+import org.springframework.batch.core.configuration.annotation.StepScope;
+import org.springframework.batch.core.launch.JobLauncher;
+import org.springframework.batch.core.launch.support.SimpleJobLauncher;
+import org.springframework.batch.core.repository.JobRepository;
+import org.springframework.batch.core.repository.support.JobRepositoryFactoryBean;
+import org.springframework.batch.item.UnexpectedInputException;
+import org.springframework.batch.item.file.FlatFileItemReader;
+import org.springframework.batch.item.file.mapping.DefaultLineMapper;
+import org.springframework.batch.item.file.transform.DelimitedLineTokenizer;
+import org.springframework.batch.item.xml.StaxEventItemWriter;
+import org.springframework.batch.support.transaction.ResourcelessTransactionManager;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.core.io.FileSystemResource;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.support.ResourcePatternResolver;
+import org.springframework.core.task.TaskExecutor;
+import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
+import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
+import org.springframework.oxm.Marshaller;
+import org.springframework.oxm.jaxb.Jaxb2Marshaller;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
+import org.springframework.transaction.PlatformTransactionManager;
+
+import javax.sql.DataSource;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.text.ParseException;
+
+@Configuration
+@EnableBatchProcessing
+public class SpringbatchPartitionConfig {
+
+ @Autowired
+ ResourcePatternResolver resoursePatternResolver;
+
+ @Autowired
+ private JobBuilderFactory jobs;
+
+ @Autowired
+ private StepBuilderFactory steps;
+
+ @Bean(name = "partitionerJob")
+ public Job partitionerJob() throws UnexpectedInputException, MalformedURLException, ParseException {
+ return jobs.get("partitionerJob")
+ .start(partitionStep())
+ .build();
+ }
+
+ @Bean
+ public Step partitionStep() throws UnexpectedInputException, MalformedURLException, ParseException {
+ return steps.get("partitionStep")
+ .partitioner("slaveStep", partitioner())
+ .step(slaveStep())
+ .taskExecutor(taskExecutor())
+ .build();
+ }
+
+ @Bean
+ public Step slaveStep() throws UnexpectedInputException, MalformedURLException, ParseException {
+ return steps.get("slaveStep")
+ .chunk(1)
+ .reader(itemReader(null))
+ .writer(itemWriter(marshaller(), null))
+ .build();
+ }
+
+ @Bean
+ public CustomMultiResourcePartitioner partitioner() {
+ CustomMultiResourcePartitioner partitioner = new CustomMultiResourcePartitioner();
+ Resource[] resources;
+ try {
+ resources = resoursePatternResolver.getResources("file:src/main/resources/input/partitioner/*.csv");
+ } catch (IOException e) {
+ throw new RuntimeException("I/O problems when resolving the input file pattern.", e);
+ }
+ partitioner.setResources(resources);
+ return partitioner;
+ }
+
+ @Bean
+ @StepScope
+ public FlatFileItemReader itemReader(@Value("#{stepExecutionContext[fileName]}") String filename) throws UnexpectedInputException, ParseException {
+ FlatFileItemReader reader = new FlatFileItemReader<>();
+ DelimitedLineTokenizer tokenizer = new DelimitedLineTokenizer();
+ String[] tokens = {"username", "userid", "transactiondate", "amount"};
+ tokenizer.setNames(tokens);
+ reader.setResource(new ClassPathResource("input/partitioner/" + filename));
+ DefaultLineMapper lineMapper = new DefaultLineMapper<>();
+ lineMapper.setLineTokenizer(tokenizer);
+ lineMapper.setFieldSetMapper(new RecordFieldSetMapper());
+ reader.setLinesToSkip(1);
+ reader.setLineMapper(lineMapper);
+ return reader;
+ }
+
+ @Bean(destroyMethod = "")
+ @StepScope
+ public StaxEventItemWriter itemWriter(Marshaller marshaller, @Value("#{stepExecutionContext[opFileName]}") String filename) throws MalformedURLException {
+ StaxEventItemWriter itemWriter = new StaxEventItemWriter<>();
+ itemWriter.setMarshaller(marshaller);
+ itemWriter.setRootTagName("transactionRecord");
+ itemWriter.setResource(new FileSystemResource("src/main/resources/output/" + filename));
+ return itemWriter;
+ }
+
+ @Bean
+ public Marshaller marshaller() {
+ Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
+ marshaller.setClassesToBeBound(Transaction.class);
+ return marshaller;
+ }
+
+ @Bean
+ public TaskExecutor taskExecutor() {
+ ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
+ taskExecutor.setMaxPoolSize(5);
+ taskExecutor.setCorePoolSize(5);
+ taskExecutor.setQueueCapacity(5);
+ taskExecutor.afterPropertiesSet();
+ return taskExecutor;
+ }
+
+ private JobRepository getJobRepository() throws Exception {
+ JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean();
+ factory.setDataSource(dataSource());
+ factory.setTransactionManager(getTransactionManager());
+ // JobRepositoryFactoryBean's methods Throws Generic Exception,
+ // it would have been better to have a specific one
+ factory.afterPropertiesSet();
+ return factory.getObject();
+ }
+
+ private DataSource dataSource() {
+ EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder();
+ return builder.setType(EmbeddedDatabaseType.HSQL)
+ .addScript("classpath:org/springframework/batch/core/schema-drop-h2.sql")
+ .addScript("classpath:org/springframework/batch/core/schema-h2.sql")
+ .build();
+ }
+
+ private PlatformTransactionManager getTransactionManager() {
+ return new ResourcelessTransactionManager();
+ }
+
+ public JobLauncher getJobLauncher() throws Exception {
+ SimpleJobLauncher jobLauncher = new SimpleJobLauncher();
+ // SimpleJobLauncher's methods Throws Generic Exception,
+ // it would have been better to have a specific one
+ jobLauncher.setJobRepository(getJobRepository());
+ jobLauncher.afterPropertiesSet();
+ return jobLauncher;
+ }
+}
diff --git a/spring-batch/src/main/java/org/baeldung/batch/partitioner/SpringbatchPartitionerApp.java b/spring-batch/src/main/java/org/baeldung/batch/partitioner/SpringbatchPartitionerApp.java
new file mode 100644
index 0000000000..e56afc591c
--- /dev/null
+++ b/spring-batch/src/main/java/org/baeldung/batch/partitioner/SpringbatchPartitionerApp.java
@@ -0,0 +1,28 @@
+package org.baeldung.batch.partitioner;
+
+import org.springframework.batch.core.Job;
+import org.springframework.batch.core.JobExecution;
+import org.springframework.batch.core.JobParameters;
+import org.springframework.batch.core.launch.JobLauncher;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+
+public class SpringbatchPartitionerApp {
+ public static void main(final String[] args) {
+ // Spring Java config
+ final AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
+ context.register(SpringbatchPartitionConfig.class);
+ context.refresh();
+
+ final JobLauncher jobLauncher = (JobLauncher) context.getBean("jobLauncher");
+ final Job job = (Job) context.getBean("partitionerJob");
+ System.out.println("Starting the batch job");
+ try {
+ final JobExecution execution = jobLauncher.run(job, new JobParameters());
+ System.out.println("Job Status : " + execution.getStatus());
+ System.out.println("Job succeeded");
+ } catch (final Exception e) {
+ e.printStackTrace();
+ System.out.println("Job failed");
+ }
+ }
+}
\ No newline at end of file
diff --git a/spring-batch/src/main/java/org/baeldung/batch/service/CustomItemProcessor.java b/spring-batch/src/main/java/org/baeldung/batch/service/CustomItemProcessor.java
new file mode 100644
index 0000000000..8ca7892fec
--- /dev/null
+++ b/spring-batch/src/main/java/org/baeldung/batch/service/CustomItemProcessor.java
@@ -0,0 +1,12 @@
+package org.baeldung.batch.service;
+
+import org.baeldung.batch.model.Transaction;
+import org.springframework.batch.item.ItemProcessor;
+
+public class CustomItemProcessor implements ItemProcessor {
+
+ public Transaction process(Transaction item) {
+ System.out.println("Processing..." + item);
+ return item;
+ }
+}
\ No newline at end of file
diff --git a/spring-batch/src/main/java/org/baeldung/batch/service/CustomSkipPolicy.java b/spring-batch/src/main/java/org/baeldung/batch/service/CustomSkipPolicy.java
new file mode 100644
index 0000000000..a156a65b6e
--- /dev/null
+++ b/spring-batch/src/main/java/org/baeldung/batch/service/CustomSkipPolicy.java
@@ -0,0 +1,29 @@
+package org.baeldung.batch.service;
+
+import org.springframework.batch.core.step.skip.SkipLimitExceededException;
+import org.springframework.batch.core.step.skip.SkipPolicy;
+
+public class CustomSkipPolicy implements SkipPolicy {
+
+ private static final int MAX_SKIP_COUNT = 2;
+ private static final int INVALID_TX_AMOUNT_LIMIT = -1000;
+
+ @Override
+ public boolean shouldSkip(Throwable throwable, int skipCount) throws SkipLimitExceededException {
+
+ if (throwable instanceof MissingUsernameException && skipCount < MAX_SKIP_COUNT) {
+ return true;
+ }
+
+ if (throwable instanceof NegativeAmountException && skipCount < MAX_SKIP_COUNT ) {
+ NegativeAmountException ex = (NegativeAmountException) throwable;
+ if(ex.getAmount() < INVALID_TX_AMOUNT_LIMIT){
+ return false;
+ } else{
+ return true;
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/spring-batch/src/main/java/org/baeldung/batch/service/MissingUsernameException.java b/spring-batch/src/main/java/org/baeldung/batch/service/MissingUsernameException.java
new file mode 100644
index 0000000000..2cf8f4d334
--- /dev/null
+++ b/spring-batch/src/main/java/org/baeldung/batch/service/MissingUsernameException.java
@@ -0,0 +1,4 @@
+package org.baeldung.batch.service;
+
+public class MissingUsernameException extends RuntimeException {
+}
diff --git a/spring-batch/src/main/java/org/baeldung/batch/service/NegativeAmountException.java b/spring-batch/src/main/java/org/baeldung/batch/service/NegativeAmountException.java
new file mode 100644
index 0000000000..c9c05be671
--- /dev/null
+++ b/spring-batch/src/main/java/org/baeldung/batch/service/NegativeAmountException.java
@@ -0,0 +1,14 @@
+package org.baeldung.batch.service;
+
+public class NegativeAmountException extends RuntimeException {
+
+ private double amount;
+
+ public NegativeAmountException(double amount){
+ this.amount = amount;
+ }
+
+ public double getAmount() {
+ return amount;
+ }
+}
diff --git a/spring-batch/src/main/java/org/baeldung/batch/service/RecordFieldSetMapper.java b/spring-batch/src/main/java/org/baeldung/batch/service/RecordFieldSetMapper.java
new file mode 100644
index 0000000000..fa6f0870aa
--- /dev/null
+++ b/spring-batch/src/main/java/org/baeldung/batch/service/RecordFieldSetMapper.java
@@ -0,0 +1,35 @@
+package org.baeldung.batch.service;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+
+import org.baeldung.batch.model.Transaction;
+import org.springframework.batch.item.file.mapping.FieldSetMapper;
+import org.springframework.batch.item.file.transform.FieldSet;
+import org.springframework.validation.BindException;
+
+public class RecordFieldSetMapper implements FieldSetMapper {
+
+ public Transaction mapFieldSet(FieldSet fieldSet) throws BindException {
+
+ SimpleDateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy");
+ Transaction transaction = new Transaction();
+ // you can either use the indices or custom names
+ // I personally prefer the custom names easy for debugging and
+ // validating the pipelines
+ transaction.setUsername(fieldSet.readString("username"));
+ transaction.setUserId(fieldSet.readInt("userid"));
+ transaction.setAmount(fieldSet.readDouble(3));
+ // Converting the date
+ String dateString = fieldSet.readString(2);
+ try {
+ transaction.setTransactionDate(dateFormat.parse(dateString));
+ } catch (ParseException e) {
+ e.printStackTrace();
+ }
+
+ return transaction;
+
+ }
+
+}
diff --git a/spring-batch/src/main/java/org/baeldung/batch/service/SkippingItemProcessor.java b/spring-batch/src/main/java/org/baeldung/batch/service/SkippingItemProcessor.java
new file mode 100644
index 0000000000..307a8213e2
--- /dev/null
+++ b/spring-batch/src/main/java/org/baeldung/batch/service/SkippingItemProcessor.java
@@ -0,0 +1,24 @@
+package org.baeldung.batch.service;
+
+import org.baeldung.batch.model.Transaction;
+import org.springframework.batch.item.ItemProcessor;
+
+public class SkippingItemProcessor implements ItemProcessor {
+
+ @Override
+ public Transaction process(Transaction transaction) {
+
+ System.out.println("SkippingItemProcessor: " + transaction);
+
+ if (transaction.getUsername() == null || transaction.getUsername().isEmpty()) {
+ throw new MissingUsernameException();
+ }
+
+ double txAmount = transaction.getAmount();
+ if (txAmount < 0) {
+ throw new NegativeAmountException(txAmount);
+ }
+
+ return transaction;
+ }
+}
diff --git a/spring-batch/src/main/java/org/baeldung/batchscheduler/SpringBatchScheduler.java b/spring-batch/src/main/java/org/baeldung/batchscheduler/SpringBatchScheduler.java
new file mode 100644
index 0000000000..1beeb6b2bf
--- /dev/null
+++ b/spring-batch/src/main/java/org/baeldung/batchscheduler/SpringBatchScheduler.java
@@ -0,0 +1,172 @@
+package org.baeldung.batchscheduler;
+
+import java.util.Date;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import org.baeldung.batchscheduler.model.Book;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.batch.core.Job;
+import org.springframework.batch.core.JobExecution;
+import org.springframework.batch.core.JobParametersBuilder;
+import org.springframework.batch.core.Step;
+import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
+import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
+import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
+import org.springframework.batch.core.launch.JobLauncher;
+import org.springframework.batch.core.launch.support.SimpleJobLauncher;
+import org.springframework.batch.core.repository.JobRepository;
+import org.springframework.batch.core.repository.support.MapJobRepositoryFactoryBean;
+import org.springframework.batch.item.ItemWriter;
+import org.springframework.batch.item.file.FlatFileItemReader;
+import org.springframework.batch.item.file.builder.FlatFileItemReaderBuilder;
+import org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper;
+import org.springframework.batch.support.transaction.ResourcelessTransactionManager;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.scheduling.TaskScheduler;
+import org.springframework.scheduling.annotation.EnableScheduling;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
+import org.springframework.scheduling.support.ScheduledMethodRunnable;
+
+@Configuration
+@EnableBatchProcessing
+@EnableScheduling
+public class SpringBatchScheduler {
+
+ private final Logger logger = LoggerFactory.getLogger(SpringBatchScheduler.class);
+
+ private AtomicBoolean enabled = new AtomicBoolean(true);
+
+ private AtomicInteger batchRunCounter = new AtomicInteger(0);
+
+ private final Map