0

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)));
    }
}
Community
  • 1
  • 1
gavenkoa
  • 45,285
  • 19
  • 251
  • 303
  • please show full code of `sendNotificationStep()`. – Sabir Khan Apr 15 '17 at 18:25
  • Are other exceptions not ignored? You're not accidentally skipping exceptions using skippable-exception-classes (or FaultTolerantStepBuilder.skip()) in your spring batch config? – qtips Apr 17 '17 at 17:43

1 Answers1

2

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 !!

Sabir Khan
  • 9,826
  • 7
  • 45
  • 98
  • Changes of `from(step1()).next(nextStep())` in your example to `from(step1()).on("*").to(nextStep())` causes of `nextStep()` execution! As I debugged `AbstractJob#getDefaultExitStatusForFailure()` set `ExitStatus.FAILED` and `.on("*")` matches this string. – gavenkoa Apr 19 '17 at 09:43
  • `on("*")` will cause execution of step -2 to begin irrespective of what happened on previous step. That has nothing to do with Spring Batch Exception handling as you asked in your question. – Sabir Khan Apr 19 '17 at 10:09
  • 1
    That was surprising behavior. Official Spring Batch docs does not explicitly say what should happen in case of exception. Stepping in debugger showed that such task receives status `ExitStatus.FAILED`. That also is not explicitly documented. – gavenkoa Apr 19 '17 at 11:05