Synchronized ItemStreamWriter example in Spring Batch 4.3

Published
Updated

In Spring Batch, some item writers are not safe to use in a multi-threaded step. For example, Jaxb2Marshaller, StaxEventItemWriter, JsonItemWriter, and many others will encounter issues should you attempt to write to them with multiple threads.

This can be solved with Spring Batch 4.3 as it now contains a SynchronizedItemStreamWriter which can wrap your underlying itemWriter, making it safe for concurrent access by multiple threads.

The Problem

Take the following example for consideration:

@Bean
public StaxEventItemWriter<Vehicle> itemWriter() {
    Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
    marshaller.setClassesToBeBound(Vehicle.class);
    return new StaxEventItemWriterBuilder<Vehicle>()
        .name("vehicleItemWriter")
        .resource(new FileSystemResource("vehicles.xml"))
        .marshaller(marshaller)
        .rootTagName("vehicles")
        .build();
}

One would expect the output to be properly formatted and be nested correctly. However, if there are more than one step threads accessing the same ItemWriter, it will create a concurrency issue, resulting in improperly formatted XML.

Expected Output

<vehicles>
    <vehicle>
        <id>1</id>
        <make>Ford</make>
        <model>Bronco</model>
    </vehicle>
    <vehicle>
        <id>2</id>
        <make>Chevy</make>
        <model>Corvette</model>
    </vehicle>
</vehicles>

Actual Output

<vehicles>
    <make>Ford</make>
    <vehicle>
        <id>1</id>
    </vehicle>
    <model>Bronco</model>
    <id>2<vehicle>    
    <make>Chevy</make>
    </id>
        <model>Corvette</model>
    </vehicle>
</vehicles>

Solution: Use a SynchronizedItemStreamWriter

We need to wrap our Jaxb2Marshaller within a SynchronizedItemStreamWriter. This will result in our items being written to the marshaller in a thread-safe context, preventing concurrency issues. This solution does not requires any crafty synchronized blocks or locks.

import com.codetinkering.example.Vehicle;
import org.springframework.batch.item.support.SynchronizedItemStreamWriter;
import org.springframework.batch.item.xml.builder.StaxEventItemWriterBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.core.io.FileSystemResource;
import org.springframework.oxm.jaxb.Jaxb2Marshaller;

public class SynchronizedItemStreamWriterExample {

    @Bean
    public SynchronizedItemStreamWriter<Vehicle> itemWriter() {

        Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
        marshaller.setClassesToBeBound(Vehicle.class);

        StaxEventItemWriter<Vehicle> writer = new StaxEventItemWriterBuilder<Vehicle>()
                .name("vehicleItemWriter")
                .resource(new FileSystemResource("vehicles.xml"))
                .marshaller(marshaller)
                .rootTagName("vehicles")
                .build();

        SynchronizedItemStreamWriter<Vechile> synchronizedWriter = new SynchronizedItemStreamWriter<Vechile>();
        synchronizedWriter.setDelegate(writer);

        return synchronizedWriter;
    }
}

Maven Dependencies

Here are the Maven dependencies used for this example:

<dependencies>
    <dependency>
        <groupId>org.springframework.batch</groupId>
        <artifactId>spring-batch-core</artifactId>
        <version>4.3.0</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-oxm</artifactId>
        <version>5.3.1</version>
    </dependency>
</dependencies>