Skip to content

Commit 74c0b27

Browse files
injae-kimfmbenhassine
authored andcommitted
Update missing information about error handling in ChunkListener
Resolves #4384
1 parent 89db774 commit 74c0b27

File tree

4 files changed

+91
-7
lines changed

4 files changed

+91
-7
lines changed

spring-batch-core/src/main/java/org/springframework/batch/core/ChunkListener.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2006-2022 the original author or authors.
2+
* Copyright 2006-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -20,11 +20,15 @@
2020
/**
2121
* Listener interface for the lifecycle of a chunk. A chunk can be thought of as a
2222
* collection of items that are committed together.
23+
* <p>
24+
* {@link ChunkListener} shouldn't throw exceptions and expect continued processing, they
25+
* must be handled in the implementation or the step will terminate.
2326
*
2427
* @author Lucas Ward
2528
* @author Michael Minella
2629
* @author Mahmoud Ben Hassine
2730
* @author Parikshit Dutta
31+
* @author Injae Kim
2832
*/
2933
public interface ChunkListener extends StepListener {
3034

spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/FaultTolerantStepBuilder.java

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2006-2023 the original author or authors.
2+
* Copyright 2006-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -689,10 +689,6 @@ private void addNonRetryableExceptionIfMissing(Class<? extends Throwable>... cls
689689
/**
690690
* ChunkListener that wraps exceptions thrown from the ChunkListener in
691691
* {@link FatalStepExecutionException} to force termination of StepExecution
692-
* <p>
693-
* ChunkListeners shoulnd't throw exceptions and expect continued processing, they
694-
* must be handled in the implementation or the step will terminate
695-
*
696692
*/
697693
private static class TerminateOnExceptionChunkListenerDelegate implements ChunkListener {
698694

spring-batch-core/src/test/java/org/springframework/batch/core/step/item/SimpleStepFactoryBeanTests.java

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2006-2023 the original author or authors.
2+
* Copyright 2006-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -328,6 +328,87 @@ public void afterChunkError(ChunkContext context) {
328328
assertTrue(writeListener.trail.startsWith("1234"), "Listener order not as expected: " + writeListener.trail);
329329
}
330330

331+
@Test
332+
void testChunkListenersThrowException() throws Exception {
333+
String[] items = new String[] { "1", "2", "3", "4", "5", "6", "7" };
334+
int commitInterval = 3;
335+
336+
SimpleStepFactoryBean<String, String> factory = getStepFactory(items);
337+
class AssertingWriteListener extends StepListenerSupport<Object, Object> {
338+
339+
String trail = "";
340+
341+
@Override
342+
public void beforeWrite(Chunk<?> chunk) {
343+
trail = trail + "2";
344+
}
345+
346+
@Override
347+
public void afterWrite(Chunk<?> items) {
348+
trail = trail + "3";
349+
}
350+
351+
}
352+
class CountingChunkListener implements ChunkListener {
353+
354+
int beforeCount = 0;
355+
356+
int afterCount = 0;
357+
358+
int failedCount = 0;
359+
360+
private final AssertingWriteListener writeListener;
361+
362+
public CountingChunkListener(AssertingWriteListener writeListener) {
363+
super();
364+
this.writeListener = writeListener;
365+
}
366+
367+
@Override
368+
public void afterChunk(ChunkContext context) {
369+
writeListener.trail = writeListener.trail + "4";
370+
afterCount++;
371+
throw new RuntimeException("Step will be terminated when ChunkListener throws exceptions.");
372+
}
373+
374+
@Override
375+
public void beforeChunk(ChunkContext context) {
376+
writeListener.trail = writeListener.trail + "1";
377+
beforeCount++;
378+
throw new RuntimeException("Step will be terminated when ChunkListener throws exceptions.");
379+
}
380+
381+
@Override
382+
public void afterChunkError(ChunkContext context) {
383+
writeListener.trail = writeListener.trail + "5";
384+
failedCount++;
385+
throw new RuntimeException("Step will be terminated when ChunkListener throws exceptions.");
386+
}
387+
388+
}
389+
AssertingWriteListener writeListener = new AssertingWriteListener();
390+
CountingChunkListener chunkListener = new CountingChunkListener(writeListener);
391+
factory.setListeners(new StepListener[] { chunkListener, writeListener });
392+
factory.setCommitInterval(commitInterval);
393+
394+
AbstractStep step = (AbstractStep) factory.getObject();
395+
396+
job.setSteps(Collections.singletonList((Step) step));
397+
398+
JobExecution jobExecution = repository.createJobExecution(job.getName(), new JobParameters());
399+
job.execute(jobExecution);
400+
401+
assertEquals(BatchStatus.FAILED, jobExecution.getStatus());
402+
assertEquals("1", reader.read());
403+
assertEquals(0, written.size());
404+
405+
assertEquals(0, chunkListener.afterCount);
406+
assertEquals(1, chunkListener.beforeCount);
407+
assertEquals(1, chunkListener.failedCount);
408+
assertEquals("15", writeListener.trail);
409+
assertTrue(writeListener.trail.startsWith("15"), "Listener order not as expected: " + writeListener.trail);
410+
}
411+
331412
/*
332413
* Commit interval specified is not allowed to be zero or negative.
333414
*/

spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/intercepting-execution.adoc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,9 @@ You can apply a `ChunkListener` when there is no chunk declaration. The `Tasklet
130130
responsible for calling the `ChunkListener`, so it applies to a non-item-oriented tasklet
131131
as well (it is called before and after the tasklet).
132132

133+
A `ChunkListener` is not designed to throw checked exceptions. Errors must be handled in the
134+
implementation or the step will terminate.
135+
133136
[[itemReadListener]]
134137
== `ItemReadListener`
135138

0 commit comments

Comments
 (0)