diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/JobFlowExecutor.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/JobFlowExecutor.java index 89ace2f924..593a9bdb7d 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/JobFlowExecutor.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/JobFlowExecutor.java @@ -82,6 +82,12 @@ public String executeStep(Step step) stepExecution.getExecutionContext().put("batch.restart", true); } + // Preserve the step's exit description (e.g., the stack trace recorded by + // AbstractStep when a step fails) so it survives the flow's final + // updateJobExecutionStatus() call, which only carries the FlowExecutionStatus + // name and would otherwise discard it. + exitStatus = exitStatus.addExitDescription(stepExecution.getExitStatus().getExitDescription()); + return stepExecution.getExitStatus().getExitCode(); } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/job/flow/FlowJobFailureTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/job/flow/FlowJobFailureTests.java index a025c1cb69..81a43067fa 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/job/flow/FlowJobFailureTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/job/flow/FlowJobFailureTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2010-2023 the original author or authors. + * Copyright 2010-2026 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. @@ -16,6 +16,7 @@ package org.springframework.batch.core.job.flow; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.ArrayList; import java.util.List; @@ -91,6 +92,35 @@ void testStepFailure() throws Exception { assertEquals(BatchStatus.FAILED, execution.getStatus()); } + @Test // gh-3740 + void testStepFailureExitDescriptionPropagatedToJobExecution() throws Exception { + final String failureDescription = "boom: simulated step failure"; + SimpleFlow flow = new SimpleFlow("job"); + List transitions = new ArrayList<>(); + StepState step = new StepState(new StepSupport("step") { + @Override + public void execute(StepExecution stepExecution) + throws JobInterruptedException, UnexpectedJobExecutionException { + stepExecution.setExitStatus(ExitStatus.FAILED.addExitDescription(failureDescription)); + stepExecution.setStatus(BatchStatus.FAILED); + } + }); + transitions.add(StateTransition.createStateTransition(step, ExitStatus.FAILED.getExitCode(), "end0")); + transitions.add(StateTransition.createStateTransition(step, ExitStatus.COMPLETED.getExitCode(), "end1")); + transitions.add(StateTransition.createEndStateTransition(new EndState(FlowExecutionStatus.FAILED, "end0"))); + transitions.add(StateTransition.createEndStateTransition(new EndState(FlowExecutionStatus.COMPLETED, "end1"))); + flow.setStateTransitions(transitions); + job.setFlow(flow); + job.afterPropertiesSet(); + job.execute(execution); + + assertEquals(BatchStatus.FAILED, execution.getStatus()); + assertEquals(ExitStatus.FAILED.getExitCode(), execution.getExitStatus().getExitCode()); + assertTrue(execution.getExitStatus().getExitDescription().contains(failureDescription), + "Expected JobExecution exit description to contain step failure description but was: '" + + execution.getExitStatus().getExitDescription() + "'"); + } + @Test void testStepStatusUnknown() throws Exception { SimpleFlow flow = new SimpleFlow("job"); diff --git a/spring-batch-integration/src/test/java/org/springframework/batch/integration/step/StepGatewayIntegrationTests.java b/spring-batch-integration/src/test/java/org/springframework/batch/integration/step/StepGatewayIntegrationTests.java index 2f9b8e76c1..2c5adddaa6 100644 --- a/spring-batch-integration/src/test/java/org/springframework/batch/integration/step/StepGatewayIntegrationTests.java +++ b/spring-batch-integration/src/test/java/org/springframework/batch/integration/step/StepGatewayIntegrationTests.java @@ -65,7 +65,7 @@ void testLaunchFailedJob() throws Exception { JobExecution jobExecution = jobOperator.start(job, new JobParametersBuilder().addLong("run.id", 2L).toJobParameters()); assertEquals(BatchStatus.FAILED, jobExecution.getStatus()); - assertEquals(ExitStatus.FAILED, jobExecution.getExitStatus()); + assertEquals(ExitStatus.FAILED.getExitCode(), jobExecution.getExitStatus().getExitCode()); } }