Loading...

March 15, 2017

Spring Sweets: Custom Exit Code From Exception

When we write a Spring Boot application a lot of things are done for us. For example when an exception in the application occurs when we start our application, Spring Boot will exit the application with exit code 1. If everything goes well and the we stop the application the exit code is 0. When we use the run method of SpringApplication and an exception is not handled by our code, Spring Boot will catch it and will check if the exception implements the ExitCodeGenerator interface. The ExitCodeGenerator interface has one method getExitCode() which must return a exit code value. This value is used as input argument for the method System.exit() that is invoke by Spring Boot to stop the application.

In the following example application we write a Spring Boot command line application that can throw an exception on startup. The exception class implements the ExitCodeGenerator interface:

// File: src/main/java/mrhaki/springboot/SampleApp.java
package mrhaki.springboot;

import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.ExitCodeGenerator;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

import java.util.Random;

import static org.springframework.boot.SpringApplication.run;

@SpringBootApplication
public class SampleApp {

    /**
     * Run the application.
     */
    public static void main(String[] args) {
        run(SampleApp.class, args);
    }

    /**
     * Very simple {@code CommandLineRunner} to get a
     * random {@code Integer} value and throw exception if value
     * is higher than or equal to 5. This code is executed
     * when we run the application.
     *
     * @return Simple application logic.
     */
    @Bean
    CommandLineRunner randomException() {
        return args -> {
            final Integer value = new Random().nextInt(10);
            if (value >= 5) {
                throw new TooHighException(String.format("Value %d is too high", value));
            } else {
                System.out.println(value);
            }
        };
    }

    /**
     * Exception when a Integer value is too high.
     * We implement the {@code ExitCodeGenerator} interface and
     * annotate this class as a Spring component. Spring Boot
     * will look for classes that implement {@code ExitCodeGenerator}
     * and use them to get a exit code.
     */
    static class TooHighException extends Exception implements ExitCodeGenerator {
        TooHighException(final String message) {
            super(message);
        }

        /**
         * @return Always return 42 as exit code when this exception occurs.
         */
        @Override
        public int getExitCode() {
            return 42;
        }
    }

}

But there is also an exit method available in the SpringApplication class. This method determines the exit code value by looking up beans in the application context that implement the ExitCodeExceptionMapper interface. The ExitCodeExceptionMapper interface has the method getExitCode(Throwable). We can write an implementation that has logic to return different exit codes based on characteristics of the given Throwable. This is especially useful when we want to have an exit code for exceptions from third party libraries.

In the following example we use the exit method of SpringApplication and a Spring bean exitCodeExceptionMapper that implements the ExitCodeExceptionMapper interface using a Java 8 lambda expression:

// File: src/main/java/mrhaki/springboot/SampleApp.java
package mrhaki.springboot;

import org.jooq.DSLContext;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.ExitCodeExceptionMapper;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.dao.DataAccessResourceFailureException;

import static mrhaki.springboot.sql.Tables.USERS;
import static org.springframework.boot.SpringApplication.exit;
import static org.springframework.boot.SpringApplication.run;

@SpringBootApplication
public class SampleApp {

    /**
     * Run the application, let Spring Boot
     * decide the exit code and use the
     * exit code value for {@code System.exit()} method.
     */
    public static void main(String[] args) {
        System.exit(exit(run(SampleApp.class, args)));
    }

    /**
     * Very simple {@code CommandLineRunner} to get a
     * a list of users from a Postgresql database 
     * using JOOQ.
     *
     * @return Simple application logic.
     */
    @Bean
    CommandLineRunner showUsers(final DSLContext dslContext) {
        return args -> dslContext.select(USERS.NAME)
                                 .from(USERS)
                                 .fetch()
                                 .forEach(name -> System.out.println(name.get(USERS.NAME)));
    }

    /**
     * Spring bean that implements the {@code ExitCodeExceptionMapper}
     * interface using a lambda expression.
     */
    @Bean
    ExitCodeExceptionMapper exitCodeExceptionMapper() {
        return exception -> {
            // Using ExitCodeExceptionMapper we can set
            // the exit code ourselves, even if we didn't
            // write the exception ourselves.
            if (exception.getCause() instanceof DataAccessResourceFailureException) {
                return 42;
            }
            return 1;
        };
    }

}

Written with Spring Boot 1.5.2.RELEASE.