diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/support/CompositeItemWriter.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/support/CompositeItemWriter.java index d76d5c33e0..8666112769 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/support/CompositeItemWriter.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/support/CompositeItemWriter.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. @@ -25,6 +25,7 @@ import org.springframework.beans.factory.InitializingBean; import org.springframework.util.Assert; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -37,6 +38,7 @@ * @author Robert Kasanicky * @author Dave Syer * @author Mahmoud Ben Hassine + * @author Elimelec Burghelea */ public class CompositeItemWriter implements ItemStreamWriter, InitializingBean { @@ -105,11 +107,25 @@ public void setDelegates(List> delegates) { @Override public void close() throws ItemStreamException { + List exceptions = new ArrayList<>(); + for (ItemWriter writer : delegates) { if (!ignoreItemStream && (writer instanceof ItemStream)) { - ((ItemStream) writer).close(); + try { + ((ItemStream) writer).close(); + } + catch (Exception e) { + exceptions.add(e); + } } } + + if (!exceptions.isEmpty()) { + String message = String.format("Failed to close %d delegate(s) due to exceptions", exceptions.size()); + ItemStreamException holder = new ItemStreamException(message); + exceptions.forEach(holder::addSuppressed); + throw holder; + } } @Override diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/support/CompositeItemWriterTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/support/CompositeItemWriterTests.java index 8d5d3f7b62..89db324007 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/support/CompositeItemWriterTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/support/CompositeItemWriterTests.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. @@ -18,14 +18,18 @@ import java.util.ArrayList; import java.util.List; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.springframework.batch.item.Chunk; import org.springframework.batch.item.ExecutionContext; +import org.springframework.batch.item.ItemStreamException; import org.springframework.batch.item.ItemStreamWriter; import org.springframework.batch.item.ItemWriter; +import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; /** * Tests for {@link CompositeItemWriter} @@ -33,6 +37,7 @@ * @author Robert Kasanicky * @author Will Schipp * @author Mahmoud Ben Hassine + * @author Elimelec Burghelea */ class CompositeItemWriterTests { @@ -94,4 +99,36 @@ private void doTestItemStream(boolean expectOpen) throws Exception { itemWriter.write(data); } + @Test + void testCloseWithMultipleDelegate() { + AbstractFileItemWriter delegate1 = mock(); + AbstractFileItemWriter delegate2 = mock(); + CompositeItemWriter itemWriter = new CompositeItemWriter<>(List.of(delegate1, delegate2)); + + itemWriter.close(); + + verify(delegate1).close(); + verify(delegate2).close(); + } + + @Test + void testCloseWithMultipleDelegatesThatThrow() { + AbstractFileItemWriter delegate1 = mock(); + AbstractFileItemWriter delegate2 = mock(); + CompositeItemWriter itemWriter = new CompositeItemWriter<>(List.of(delegate1, delegate2)); + + doThrow(new ItemStreamException("A failure")).when(delegate1).close(); + + try { + itemWriter.close(); + Assertions.fail("Expected an ItemStreamException"); + } + catch (ItemStreamException ignored) { + + } + + verify(delegate1).close(); + verify(delegate2).close(); + } + }