Skip to content

Commit c4f740f

Browse files
committed
Propagate step exit description from FlowJob to JobExecution
Issue: gh-3740 When a step inside a FlowJob fails, the StepExecution's exit status already contains the failure description (typically the stack trace recorded by AbstractStep). However, JobFlowExecutor.executeStep only returned the exit code, and the final JobFlowExecutor.updateJobExecutionStatus combined the executor's tracked ExitStatus (which had no description) with an ExitStatus built from the FlowExecutionStatus name. As a result the step's exit description was discarded and the JobExecution ended with an empty exit description, in contrast to SimpleJob which assigns stepExecution.getExitStatus() directly. Accumulate the step's exit description into the executor's tracked ExitStatus in executeStep so it survives the final updateJobExecutionStatus call. ExitStatus.addExitDescription already deduplicates equal descriptions and concatenates distinct ones with a semicolon, so multiple step failures along a flow path are preserved without duplication. Signed-off-by: Seonwoo Jung <laborlawseon@kap.kr>
1 parent d8447d8 commit c4f740f

3 files changed

Lines changed: 38 additions & 2 deletions

File tree

spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/JobFlowExecutor.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,12 @@ public String executeStep(Step step)
8282
stepExecution.getExecutionContext().put("batch.restart", true);
8383
}
8484

85+
// Preserve the step's exit description (e.g., the stack trace recorded by
86+
// AbstractStep when a step fails) so it survives the flow's final
87+
// updateJobExecutionStatus() call, which only carries the FlowExecutionStatus
88+
// name and would otherwise discard it.
89+
exitStatus = exitStatus.addExitDescription(stepExecution.getExitStatus().getExitDescription());
90+
8591
return stepExecution.getExitStatus().getExitCode();
8692
}
8793

spring-batch-core/src/test/java/org/springframework/batch/core/job/flow/FlowJobFailureTests.java

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2010-2023 the original author or authors.
2+
* Copyright 2010-2026 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.
@@ -16,6 +16,7 @@
1616
package org.springframework.batch.core.job.flow;
1717

1818
import static org.junit.jupiter.api.Assertions.assertEquals;
19+
import static org.junit.jupiter.api.Assertions.assertTrue;
1920

2021
import java.util.ArrayList;
2122
import java.util.List;
@@ -91,6 +92,35 @@ void testStepFailure() throws Exception {
9192
assertEquals(BatchStatus.FAILED, execution.getStatus());
9293
}
9394

95+
@Test // gh-3740
96+
void testStepFailureExitDescriptionPropagatedToJobExecution() throws Exception {
97+
final String failureDescription = "boom: simulated step failure";
98+
SimpleFlow flow = new SimpleFlow("job");
99+
List<StateTransition> transitions = new ArrayList<>();
100+
StepState step = new StepState(new StepSupport("step") {
101+
@Override
102+
public void execute(StepExecution stepExecution)
103+
throws JobInterruptedException, UnexpectedJobExecutionException {
104+
stepExecution.setExitStatus(ExitStatus.FAILED.addExitDescription(failureDescription));
105+
stepExecution.setStatus(BatchStatus.FAILED);
106+
}
107+
});
108+
transitions.add(StateTransition.createStateTransition(step, ExitStatus.FAILED.getExitCode(), "end0"));
109+
transitions.add(StateTransition.createStateTransition(step, ExitStatus.COMPLETED.getExitCode(), "end1"));
110+
transitions.add(StateTransition.createEndStateTransition(new EndState(FlowExecutionStatus.FAILED, "end0")));
111+
transitions.add(StateTransition.createEndStateTransition(new EndState(FlowExecutionStatus.COMPLETED, "end1")));
112+
flow.setStateTransitions(transitions);
113+
job.setFlow(flow);
114+
job.afterPropertiesSet();
115+
job.execute(execution);
116+
117+
assertEquals(BatchStatus.FAILED, execution.getStatus());
118+
assertEquals(ExitStatus.FAILED.getExitCode(), execution.getExitStatus().getExitCode());
119+
assertTrue(execution.getExitStatus().getExitDescription().contains(failureDescription),
120+
"Expected JobExecution exit description to contain step failure description but was: '"
121+
+ execution.getExitStatus().getExitDescription() + "'");
122+
}
123+
94124
@Test
95125
void testStepStatusUnknown() throws Exception {
96126
SimpleFlow flow = new SimpleFlow("job");

spring-batch-integration/src/test/java/org/springframework/batch/integration/step/StepGatewayIntegrationTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ void testLaunchFailedJob() throws Exception {
6565
JobExecution jobExecution = jobOperator.start(job,
6666
new JobParametersBuilder().addLong("run.id", 2L).toJobParameters());
6767
assertEquals(BatchStatus.FAILED, jobExecution.getStatus());
68-
assertEquals(ExitStatus.FAILED, jobExecution.getExitStatus());
68+
assertEquals(ExitStatus.FAILED.getExitCode(), jobExecution.getExitStatus().getExitCode());
6969
}
7070

7171
}

0 commit comments

Comments
 (0)