Spring Batch — Date Reading

Prateek
4 min readAug 6, 2024

--

In this example, we’ll see how to read the date from csv (with the different type) and convert it to another format and finally write. Here I’ve keep things simple to understand, however we can refactor it based on need to need basis.

In this example, I’ve used XML based beans, it should not be so hard to convert it into Annotation based approached.

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>passing-tasklet-failure-message-to-next-step</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>passing-tasklet-failure-message-to-next-step</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-batch</artifactId>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.batch</groupId>
<artifactId>spring-batch-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>

DaoUser.java

package com.example;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class DaoUser {
private String email, fullname, matricule, password, username;
private java.sql.Date dateIntegration;
}

Writer.java

package com.example;

import org.springframework.batch.item.ItemWriter;

import java.util.List;

public class MyWriter implements ItemWriter<DaoUser> {
@Override
public void write(List<? extends DaoUser> items) throws Exception {
items.forEach(System.out::println);
}
}

MyConverter.java

package com.example;

import org.springframework.core.convert.converter.Converter;

import java.sql.Date;
import java.time.LocalDate;

public class MyConverter implements Converter<String, LocalDate> {
@Override
public LocalDate convert(String s) {
Date oldDate = Date.valueOf(s);
return oldDate.toLocalDate();
}
}

MyConversionService.java

package com.example;

import lombok.Setter;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.support.DefaultConversionService;

@Setter
public class MyConversionService extends DefaultConversionService {
private MyConverter myConverter;

@Override
public void addConverter(Converter<?, ?> converter) {
super.addConverter(converter);
}
}

MainApp.java

package com.example;

import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobExecutionException;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

@EnableBatchProcessing
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class DateConversionServiceApplication implements CommandLineRunner {

public static void main(String[] args) {
SpringApplication.run(DateConversionServiceApplication.class, args);
}

@Override
public void run(String... args) throws Exception {
ApplicationContext context = new ClassPathXmlApplicationContext("spring-batch.xml");

JobLauncher jobLauncher = (JobLauncher) context.getBean("jobLauncher");
Job job = (Job) context.getBean("readDateJob");

try {
JobExecution execution = jobLauncher.run(job, new JobParameters());
System.out.println("Job Exit Status : " + execution.getStatus());

} catch (JobExecutionException e) {
System.out.println("Job ExamResult failed");
e.printStackTrace();
}
}
}

spring-batch-context.xml

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:batch="http://www.springframework.org/schema/batch"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/batch http://www.springframework.org/schema/batch/spring-batch-3.0.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-2.5.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd">

<bean id="transactionManager" class="org.springframework.batch.support.transaction.ResourcelessTransactionManager"/>

<bean id="jobRepository" class="org.springframework.batch.core.repository.support.MapJobRepositoryFactoryBean"/>

<bean id="jobLauncher" class="org.springframework.batch.core.launch.support.SimpleJobLauncher">
<property name="jobRepository" ref="jobRepository"/>
</bean>

<bean id="myConverter" class="com.example.MyConverter" />

<bean id="conversionService" class="com.example.MyConversionService">
<property name="myConverter" ref="myConverter" />
</bean>

<batch:job id="readDateJob">
<batch:step id="step1">
<batch:tasklet transaction-manager="transactionManager">
<batch:chunk reader="flatFileItemReader"
writer="myWriter"
commit-interval="10"/>
</batch:tasklet>
</batch:step>
</batch:job>

<bean id="flatFileItemReader" class="org.springframework.batch.item.file.FlatFileItemReader" scope="step">
<property name="resource" value="classpath:users.csv" />
<property name="linesToSkip" value="0" />
<property name="lineMapper">
<bean class="org.springframework.batch.item.file.mapping.DefaultLineMapper">
<property name="fieldSetMapper">
<bean class="org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper">
<property name="prototypeBeanName" value="domain" />
<property name="conversionService" ref="conversionService" />
</bean>
</property>
<property name="lineTokenizer">
<!-- A tokenizer class to be used when items in input record are separated by specific characters -->
<bean class="org.springframework.batch.item.file.transform.DelimitedLineTokenizer">
<property name="delimiter" value=","/>
<property name="names" value="dateIntegration,email, fullname, matricule, password, username" />
</bean>
</property>
</bean>
</property>
</bean>

<bean id="myWriter" class="com.example.MyWriter" />

<bean id="domain" class="com.example.DaoUser" scope="prototype" />
</beans>

users.csv

2021-06-22,test1@gmail.com,testFullname1,testMatricule1,1234,testUsername1
2021-06-22,test2@gmail.com,testFullname2,testMatricule2,0000,testUsername2

Output

/Library/Java/JavaVirtualMachines/jdk-11.jdk/Contents/Home/bin/java -javaagent:/Applications/IntelliJ IDEA CE.app/Contents/lib/idea_rt.jar=50409:/Applications/IntelliJ IDEA CE.app/Contents/bin -Dfile.encoding=UTF-8 -classpath /Users/prats/Downloads/date-conversion-service/target/classes:/Users/prats/.m2/repository/org/springframework/boot/spring-boot-starter-batch/2.7.0/spring-boot-starter-batch-2.7.0.jar:/Users/prats/.m2/repository/org/springframework/boot/spring-boot-starter/2.7.0/spring-boot-starter-2.7.0.jar:/Users/prats/.m2/repository/org/springframework/boot/spring-boot/2.7.0/spring-boot-2.7.0.jar:/Users/prats/.m2/repository/org/springframework/boot/spring-boot-autoconfigure/2.7.0/spring-boot-autoconfigure-2.7.0.jar:/Users/prats/.m2/repository/org/springframework/boot/spring-boot-starter-logging/2.7.0/spring-boot-starter-logging-2.7.0.jar:/Users/prats/.m2/repository/ch/qos/logback/logback-classic/1.2.11/logback-classic-1.2.11.jar:/Users/prats/.m2/repository/ch/qos/logback/logback-core/1.2.11/logback-core-1.2.11.jar:/Users/prats/.m2/repository/org/apache/logging/log4j/log4j-to-slf4j/2.17.2/log4j-to-slf4j-2.17.2.jar:/Users/prats/.m2/repository/org/apache/logging/log4j/log4j-api/2.17.2/log4j-api-2.17.2.jar:/Users/prats/.m2/repository/org/slf4j/jul-to-slf4j/1.7.36/jul-to-slf4j-1.7.36.jar:/Users/prats/.m2/repository/jakarta/annotation/jakarta.annotation-api/1.3.5/jakarta.annotation-api-1.3.5.jar:/Users/prats/.m2/repository/org/yaml/snakeyaml/1.30/snakeyaml-1.30.jar:/Users/prats/.m2/repository/org/springframework/boot/spring-boot-starter-jdbc/2.7.0/spring-boot-starter-jdbc-2.7.0.jar:/Users/prats/.m2/repository/com/zaxxer/HikariCP/4.0.3/HikariCP-4.0.3.jar:/Users/prats/.m2/repository/org/springframework/batch/spring-batch-core/4.3.6/spring-batch-core-4.3.6.jar:/Users/prats/.m2/repository/com/fasterxml/jackson/core/jackson-databind/2.13.3/jackson-databind-2.13.3.jar:/Users/prats/.m2/repository/com/fasterxml/jackson/core/jackson-annotations/2.13.3/jackson-annotations-2.13.3.jar:/Users/prats/.m2/repository/com/fasterxml/jackson/core/jackson-core/2.13.3/jackson-core-2.13.3.jar:/Users/prats/.m2/repository/io/micrometer/micrometer-core/1.9.0/micrometer-core-1.9.0.jar:/Users/prats/.m2/repository/org/hdrhistogram/HdrHistogram/2.1.12/HdrHistogram-2.1.12.jar:/Users/prats/.m2/repository/org/latencyutils/LatencyUtils/2.0.3/LatencyUtils-2.0.3.jar:/Users/prats/.m2/repository/javax/batch/javax.batch-api/1.0/javax.batch-api-1.0.jar:/Users/prats/.m2/repository/org/codehaus/jettison/jettison/1.2/jettison-1.2.jar:/Users/prats/.m2/repository/org/springframework/batch/spring-batch-infrastructure/4.3.6/spring-batch-infrastructure-4.3.6.jar:/Users/prats/.m2/repository/org/springframework/retry/spring-retry/1.3.3/spring-retry-1.3.3.jar:/Users/prats/.m2/repository/org/springframework/spring-aop/5.3.20/spring-aop-5.3.20.jar:/Users/prats/.m2/repository/org/springframework/spring-beans/5.3.20/spring-beans-5.3.20.jar:/Users/prats/.m2/repository/org/springframework/spring-context/5.3.20/spring-context-5.3.20.jar:/Users/prats/.m2/repository/org/springframework/spring-expression/5.3.20/spring-expression-5.3.20.jar:/Users/prats/.m2/repository/org/springframework/spring-tx/5.3.20/spring-tx-5.3.20.jar:/Users/prats/.m2/repository/org/projectlombok/lombok/1.18.24/lombok-1.18.24.jar:/Users/prats/.m2/repository/org/slf4j/slf4j-api/1.7.36/slf4j-api-1.7.36.jar:/Users/prats/.m2/repository/org/springframework/spring-core/5.3.20/spring-core-5.3.20.jar:/Users/prats/.m2/repository/org/springframework/spring-jcl/5.3.20/spring-jcl-5.3.20.jar:/Users/prats/.m2/repository/org/springframework/spring-jdbc/5.3.20/spring-jdbc-5.3.20.jar com.example.DateConversionServiceApplication

. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.7.0)

2024-08-06 18:26:30.342 INFO 2055 --- [ main] c.e.DateConversionServiceApplication : Starting DateConversionServiceApplication using Java 11.0.22 on Prateeks-MacBook-Pro.local with PID 2055 (/Users/prats/Downloads/date-conversion-service/target/classes started by prateekashtikar in /Users/prats/Downloads/date-conversion-service)
2024-08-06 18:26:30.344 INFO 2055 --- [ main] c.e.DateConversionServiceApplication : No active profile set, falling back to 1 default profile: "default"
2024-08-06 18:26:30.730 INFO 2055 --- [ main] c.e.DateConversionServiceApplication : Started DateConversionServiceApplication in 0.609 seconds (JVM running for 0.833)
2024-08-06 18:26:30.732 INFO 2055 --- [ main] o.s.b.a.b.JobLauncherApplicationRunner : Running default command line with: []
2024-08-06 18:26:30.840 INFO 2055 --- [ main] o.s.b.c.l.support.SimpleJobLauncher : No TaskExecutor has been set, defaulting to synchronous executor.
2024-08-06 18:26:30.903 INFO 2055 --- [ main] o.s.b.c.l.support.SimpleJobLauncher : Job: [FlowJob: [name=readDateJob]] launched with the following parameters: [{}]
2024-08-06 18:26:30.919 INFO 2055 --- [ main] o.s.batch.core.job.SimpleStepHandler : Executing step: [step1]
DaoUser(email=test1@gmail.com, fullname=testFullname1, matricule=testMatricule1, password=1234, username=testUsername1, dateIntegration=2021-06-22)
DaoUser(email=test2@gmail.com, fullname=testFullname2, matricule=testMatricule2, password=0000, username=testUsername2, dateIntegration=2021-06-22)
2024-08-06 18:26:30.957 INFO 2055 --- [ main] o.s.batch.core.step.AbstractStep : Step: [step1] executed in 38ms
2024-08-06 18:26:30.963 INFO 2055 --- [ main] o.s.b.c.l.support.SimpleJobLauncher : Job: [FlowJob: [name=readDateJob]] completed with the following parameters: [{}] and the following status: [COMPLETED] in 47ms
Job Exit Status : COMPLETED

Process finished with exit code 0
  • References

https://mkyong.com/spring-batch/how-to-convert-date-in-beanwrapperfieldsetmapper/

--

--

Prateek
Prateek

Written by Prateek

Java Developer and enthusiast

No responses yet