Spring Boot Exit Codes Examples with Exception Mapping

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

Overview

An Exit Code (aka return code) is a small number that is 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 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.

In this tutorial, we will focus on returning specific exit codes from a Spring Boot Application. We will cover the default Spring Boot exit codes and then learn to return exception specific exit codes or to listen exit code events.

Spring Boot Exit Codes

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

However, sometimes we may want our Spring Boot application to return more specific exit codes when something goes wrong. This is required especially 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(). In order to return a custom exit code, we need to execute SpringApplication#exit(applicationContext, exitCodeGenerator) method. As the ExitCodeGenerator is a functional interface, we can use 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 a static exit code of 11.

Application Runner

Alternatively, if 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:run
Code language: Bash (bash)

Now, 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, like we did in the previous example, we can also return specific codes that are based on exceptions. So that each exit code denotes an occurrence of a particular exception. In order to map exit codes with application exceptions, we can use 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)

As can be seen in the snippet, the runner implementation throws IllegalArgumentException if the arguments are not provided. Also, it throws NumberFormatException, if the given argument cannot be parsed 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=abc
Code language: Bash (bash)

Here we are using 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]

As expected, the application failed with exit code 30, which is mapped to NumberFormatException.

Exceptions with their own 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 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. Now, we can simply 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=22
Code language: Bash (bash)

Let’s execute the application and pass a valid number, which is 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 exit code of 40.

Note, if you have been following the examples in 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 the exit code events. The Exit Code Event Listeners are a place to execute some code before the application is exited.

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

In order to listen 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 was a detailed introduction to Spring Boot application exit codes. Exit Codes are returned from every application and commands that are used to determine whether the execution was successful 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 let them return their own exit code. Spring Boot also provides a way of listen to the application exit events where we can perform all the necessary cleanup before the application exits.

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