Spring Boot Exit Codes Examples with Exception Mapping

A guide to Spring Boot ExitCodeGenerator and ExitCodeExceptionMapper interfaces used to return custom exit codes or exception-based exit codes from a Spring Boot application.

Overview

An Exit Code (aka return code) is a small number returned by executables to its parent process. Every application or an executing command returns such exit code. The parent processes or the scripts executing the command use exit codes to interpret whether the application executed successfully or failed because of something.

A zero exit code indicates the successful execution of an application. On the other hand, a non-zero exit code denotes unsuccessful execution. An application or a command can use specific exit codes to indicate what went wrong.

This tutorial will focus on returning specific exit codes from a Spring Boot Application. We will cover the default exit codes any Spring Boot application returns and then learn to return exception-specific exit codes or listen to exit code events.

Spring Boot Exit Codes

Like any other executables, Spring Boot applications return exit codes. Spring Boot returns an exit code of 0 (zero) when it finishes and exits successfully. On the other hand, if the application fails, it returns 1 (one) as the exit code.

However, sometimes we may want our Spring Boot application to return more specific exit codes when something goes wrong. That is required primarily for the Spring Boot non-web Command-Line Applications. Thankfully, Spring Boot provides ExitCodeGenerator and ExitCodeExceptionMapper interfaces that help us to customize the error codes.

Spring Boot Custom Exit Code

The ExitCodeGenerator interface has only one method – int getExitCode(). To return a custom exit code, we need to execute the SpringApplication#exit(applicationContext, exitCodeGenerator) method. As the ExitCodeGenerator is a functional interface, we can use a lambda expression.

We know that Spring Boot standalone application can be based CommandLineRunner or ApplicationRunner. Let’s see how to return exit codes using both of these methods. Before that, if you are new to the Spring Boot Application runners, please read Spring Boot Runners – Application Runner and Command-Line Runner tutorial.

Let’s create a @Configuration class to declare our runners.

@Configuration
public class ShutdownWithStaticExitCode {
    private final ApplicationContext applicationContext;
    
    // Constructor

}Code language: Java (java)

Note that the SpringApplication#exit(…) method needs application context. Thus, we have auto-wired it in our class.

Command Line Runner

Let’s add a factory method to return an implementation of CommandLineRunner.

@Bean
public CommandLineRunner commandLineRunner() {
  return args -> {
    System.exit(
      SpringApplication.exit(applicationContext, () -> 11));
  };
}Code language: Java (java)

Here, we are simply exiting our runner and providing it with a static exit code of 11.

Application Runner

Alternatively, we can also return a static exit code by using ApplicationRunner.

@Bean
public ApplicationRunner commandLineRunner() {
  return args -> {
    System.exit(
      SpringApplication.exit(applicationContext, () -> 11));
  };
}Code language: Java (java)
~ mvn spring-boot:runCode language: Bash (bash)

When we start our application using either of the runners, it will fail immediately with an exit code of 11.

[ERROR] Failed to execute goal org.springframework.boot:spring-boot-maven-plugin:2.5.4:run (default-cli) on project spring-boot-exit-codes: Application finished with exit code: 11 -> [Help 1] 

Exception Specific Exit Codes

Instead of returning static exit codes, as we did in the previous example, we can also return specific exit codes based on exceptions. Each exit code denotes an occurrence of a particular exception. To map exit codes with application exceptions, we can use the ExitCodeExceptionMapper interface.

First, we will have our runner throw a couple of exceptions.

@Bean
public CommandLineRunner commandLineRunner() {
  return args -> {
    if (args.length == 0) {
      throw new IllegalArgumentException("Illegal argument received");
    }

    long value = Long.parseLong(args[0]);
    // ...skipped

    SpringApplication.exit(applicationContext, () -> 11);
  };
}Code language: Java (java)

The snippet shows that the runner implementation throws IllegalArgumentException if the arguments are missing. Also, it throws NumberFormatException, if it cannot parse the given parameter to long type.

Next, we will provide an implementation of ExitCodeExceptionMapper that can map specific exceptions to their respective exit codes.

@Bean
public ExitCodeExceptionMapper exceptionBasedExitCode() {
  return exception -> {
    if (exception.getCause() instanceof NumberFormatException) {
      return 30;
    }
    
    if (exception.getCause() instanceof IllegalArgumentException) {
      return 20;
    }
    return 99;
  };
}Code language: Java (java)

The above method returns an instance of ExitCodeExceptionMapper that returns different exit codes for different exceptions and a default exit code.

Let’s launch our application with a text argument.

~ mvn spring-boot:run -Dspring-boot.run.arguments=abcCode language: Bash (bash)

We use the Spring Boot maven plugin to launch the application with a text argument.

[ERROR] Failed to execute goal org.springframework.boot:spring-boot-maven-plugin:2.5.4:run (default-cli) on project spring-boot-exit-codes: Application finished with exit code: 30 -> [Help 1]

The application failed with exit code 30, which denotes a NumberFormatException.

Exceptions with their Exit Codes

The ExitCodeExceptionMapper is good when we want more control over the exceptions and their exit codes. Alternatively, if your application throws custom exceptions only, the simplest way is to extend your custom exception classes from the ExitCodeGenerator interface.

Given that, when Spring Boot runner throws an exception of type ExitCodeGenerator, it automatically uses the exit code provided by the exception.

public class ValueTooSmallException
    extends RuntimeException
    implements ExitCodeGenerator {

  public ValueTooSmallException(String msg) {
    super(msg);
  }

  @Override
  public int getExitCode() {
    return 40;
  }
}Code language: Java (java)

Our custom exception is a RuntimeException, and it also implements ExitCodeGenerator. The implemented method getExitCode() returns an exit code of 40. We can throw this exception from our application without using ExitCodeExceptionMapper.

For example, we will throw ValueTooSmallException, if our argument value is less than 100.

@Bean
public CommandLineRunner commandLineRunner() {
  return args -> {
    if (args.length == 0) {
      throw new IllegalArgumentException("Illegal argument received");
    }

    long value = Long.parseLong(args[0]);
    if (value < 100) {
      throw new ValueTooSmallException("Value should be >= 100");
    }

    System.exit(
      SpringApplication.exit(applicationContext, () -> 11));
    };
}
Code language: Java (java)
~ mvn spring-boot:run -Dspring-boot.run.arguments=22Code language: Bash (bash)

Let’s execute the application and pass a valid number smaller than 100.

[ERROR] Failed to execute goal org.springframework.boot:spring-boot-maven-plugin:2.5.4:run (default-cli) on project spring-boot-exit-codes: Application finished with exit code: 40 -> [Help 1]

And, as expected, we got an exit code of 40.

Note, if you have been following the examples in the tutorial, you will need to comment out the ExitCodeExceptionMapper factory bean to get our custom exception-based exit code to work.

Listening to Exit Code Events

So far, we have learned a lot about Spring Boot application Exit Codes and different ways to customize them. On top of that, Spring Boot also allows us to listen to the exit code events. The Exit Code Event Listeners are a place to execute some code before the application exits.

@Component
public class ExitCodeListener {

  @EventListener
  public void listenExitCodes(ExitCodeEvent event) {
    log.info("Exiting with code: {}", event.getExitCode());
  }
}Code language: Java (java)

To listen to exit code events, we need to mark our method as @EventListener.

INFO  | [main] c.a.s.e.Application:61 - Started Application in 6.862 seconds (JVM running for 7.294)
INFO  | [main] c.a.s.e.ExitCodeListener:14 - Exiting with code: 40

As seen, the listener is executed right before the application exits.

Summary

This tutorial was a detailed introduction to Spring Boot application exit codes. Every application and command executions return exit codes used to determine whether the application execution was completed successfully or failed.

Spring Boot Provides ExitCodeGenerator and ExitCodeExceptionMapper interfaces that let us return custom exit codes or exception-based exit codes. Moreover, we can also extend our custom exceptions from ExitCodeGenerator that allow them to produce their exit code. Spring Boot also provides a way of listening to the application exit events where we can perform all the necessary cleanup before the application exits.

For the complete source of the examples used here, please visit our Github Repository.