gavenkoa
gavenkoa

Reputation: 48733

Spring batch continue job flow in case of unchecked exception

I send email in Spring Batch Tasklet.

SMTP server is down so unchecked MailSendException exception occurred.

Next step in transition is declared as (from email sending):

FlowBuilder<Flow> flowBuilder = new FlowBuilder<Flow>("myFlow")
        .from(sendNotificationStep()).next(nextStep());

and nextStep() is executed even in case of unchecked exception.

Is that normal behavior of Spring Batch Framework to ignore unchecked exceptions?

The problem is that this exception is silently ignored and is not logged (I set root logger to WARN).

Somewhat opposite behavior reported in why does transaction roll back on RuntimeException but not SQLException

UPDATE After stepping with debugger I end inside:

public class SimpleFlow implements Flow, InitializingBean {

    public FlowExecution resume(String stateName, FlowExecutor executor) throws FlowExecutionException {

         state = nextState(stateName, status, stepExecution);

status is FAILED, state is sendNotificationStep and nextState() return nextStep.

There is catch in resume:

catch (Exception e) {
    executor.close(new FlowExecution(stateName, status));
    throw new FlowExecutionException(String.format("Ended flow=%s at state=%s with exception", name,
                                                  stateName), e);
}

but exception handled previously by:

public abstract class AbstractStep implements Step, InitializingBean, BeanNameAware {
    public final void execute(StepExecution stepExecution) throws JobInterruptedException,

    catch (Throwable e) {
        stepExecution.upgradeStatus(determineBatchStatus(e));
        exitStatus = exitStatus.and(getDefaultExitStatusForFailure(e));
        stepExecution.addFailureException(e);
        if (stepExecution.getStatus() == BatchStatus.STOPPED) {
            logger.info(String.format("Encountered interruption executing step %s in job %s : %s", name, stepExecution.getJobExecution().getJobInstance().getJobName(), e.getMessage()));
            if (logger.isDebugEnabled()) {
                logger.debug("Full exception", e);
            }
        }
        else {
            logger.error(String.format("Encountered an error executing step %s in job %s", name, stepExecution.getJobExecution().getJobInstance().getJobName()), e);
        }
    }

Batch admin lists problem step as ABANDONED.

UPDATE 3 Fully functional example to reproduce behavior (thanks to Sabir Khan for providing stab!):

@SpringBootApplication
@Configuration
@EnableBatchProcessing
public class X {

    private static final Logger logger = LoggerFactory.getLogger(X.class);

    @Autowired
    private JobBuilderFactory jobs;

    @Autowired
    private StepBuilderFactory steps;

    @Bean
    protected Tasklet tasklet1() {
        return (StepContribution contribution, ChunkContext context) -> {
            logger.warn("Inside tasklet1");
            throw new IllegalStateException("xxx");
            //return RepeatStatus.FINISHED;
        };
    }

    @Bean
    protected Tasklet tasklet2() {
        return (StepContribution contribution, ChunkContext context) -> {
            logger.warn("Inside tasklet2");
            return RepeatStatus.FINISHED;
        };
    }

    @Bean
    public Job job() throws Exception {
        Flow flow = new FlowBuilder<Flow>("myFlow").from(firstStep()).on("*").to(nextStep()).end();
        return this.jobs.get("job").start(flow).end().build();
    }

    @Bean
    protected Step firstStep() {
        return this.steps.get("firstStep").tasklet(tasklet1()).build();
    }

    @Bean
    protected Step nextStep() {
        return this.steps.get("nextStep").tasklet(tasklet2()).build();
    }

    public static void main(String[] args) throws Exception {
        System.exit(SpringApplication.exit(SpringApplication.run(X.class, args)));
    }
}

Upvotes: 0

Views: 8406

Answers (1)

Sabir Khan
Sabir Khan

Reputation: 10132

No, that is not normal Spring Batch behavior and I have never seen what you are describing.

I think, Spring Batch doesn't make a distinction in exception thrown being checked or unchecked - flow will stop at that very point when exception is thrown for the case of sequential execution.

Obviously, executions of other parts will continue if parallel steps or executions are going on.

Somewhere down the line, exception might have been handled in your code (eaten silently) and that is why execution might have continued to next step.

I have seen both categories of exceptions being thrown in my jobs and job behaves the way I coded skip-retry and other exception handling mechanisms and it has nothing to do with Spring Batch - that is pure Java.

I am not sure if I misread your question but this below sample code doesn't work the way you described. Step-1 throws unchecked exception and execution stopped right there since I am not handling it in anyway.

@SpringBootApplication(exclude = { DataSource.class,
        DataSourceAutoConfiguration.class })
@Configuration
@EnableBatchProcessing
public class AppConfiguration {

    private static final Logger logger = LoggerFactory.getLogger(AppConfiguration.class);

    @Autowired
    private JobBuilderFactory jobs;

    @Autowired
    private StepBuilderFactory steps;

    @Autowired
    private JavaMailSender javaMailSender;

    @Bean
    protected Tasklet tasklet() {

        return new Tasklet() {
            @Override
            public RepeatStatus execute(StepContribution contribution,
                    ChunkContext context) {


                MimeMessage message = javaMailSender.createMimeMessage();
                javaMailSender.send(message);

                return RepeatStatus.FINISHED;

            }
        };

    }

    @Bean
    protected Tasklet tasklet2() {

        return new Tasklet() {
            @Override
            public RepeatStatus execute(StepContribution contribution,
                    ChunkContext context) {

                return RepeatStatus.FINISHED;
            }
        };

    }

    @Bean
    public Job job() throws Exception {

        Flow flow = new FlowBuilder<Flow>("myFlow").from(step1()).next(nextStep()).end();
        return this.jobs.get("job").start(flow).end().build();

    }

    @Bean
    protected Step step1() {
        return this.steps.get("step1").tasklet(tasklet()).build();
    }

    @Bean
    protected Step nextStep() {
        return this.steps.get("nextStep").tasklet(tasklet2()).build();
    }

    public static void main(String[] args) throws Exception {
        System.exit(SpringApplication.exit(SpringApplication.run(AppConfiguration.class, args)));
    }

}

Hope it helps !!

Upvotes: 2

Related Questions