Search

Dark theme | Light theme
Showing posts with label Spring Boot 1.5.2.RELEASE. Show all posts
Showing posts with label Spring Boot 1.5.2.RELEASE. Show all posts

April 12, 2017

Spring Sweets: Hiding Sensitive Environment Or Configuration Values From Actuator Endpoints

We can use Spring Boot Actuator to add endpoints to our application that can expose information about our application. For example we can request the /env endpoint to see which Spring environment properties are available. Or use /configprops to see the values of properties defined using @ConfigurationProperties. Sensitive information like passwords and keys are replaced with ******. Spring Boot Actuator has a list of properties that have sensitive information and therefore should be replaced with ******. The default list of keys that have their value hidden is defined as password,secret,key,token,.*credentials.*,vcap_services. A value is either what the property name ends with or a regular expression. We can define our own list of property names from which the values should be hidden or sanitized and replaced with ******. We define the key we want to be hidden using the application properties endpoints.env.keys-to-sanatize and endpoints.configprops.keys-to-sanatize.

In the following example Spring application YAML configuration we define new values for keys we want to be sanitized. Properties in our Spring environment that end with username or password should be sanatized. For properties set via @ConfigurationProperties we want to hide values for keys that end with port and key:

# File: src/main/resources/application.yml
endpoints:
  env:
    # Hide properties that end with password and username:
    keys-to-sanitize: password,username
  configprops:
    # Also hide port and key values from the output:
    keys-to-sanitize: port,key
---
# Extra properties will be exposed
# via /env endpoint.
sample:
  username: test
  password: test

When we request the /env we see in the output that values of properties that end with username and password are hidden:

...
    "applicationConfig: [classpath:/application.yml]": {
        ...
        "sample.password": "******",
        "sample.username": "******"
    },
...

When we request the /configprops we see in the output that for example key and port properties are sanitized:

...
    "spring.metrics.export-org.springframework.boot.actuate.metrics.export.MetricExportProperties": {
        "prefix": "spring.metrics.export",
        "properties": {
            ...
            "redis": {
                "key": "******",
                "prefix": "spring.metrics.application.f2325e314fc8223e6bb8ee6ddebbbd79"
            },
            "statsd": {
                "host": null,
                "port": "******",
                "prefix": null
            }
        }
    },
...

Written with Spring Boot 1.5.2.RELEASE.

March 23, 2017

Ratpacked: Add Ratpack To Spring Boot Application

In a previous post we saw how we can use Spring Boot in a Ratpack application. But the integration can also be the other way around: using Ratpack in a Spring Boot application. This way we can use Ratpack's power to handle requests sent to our Spring Boot application and still use all Spring Boot features in our application. The easiest way to add Ratpack to a Spring Boot application is adding a Ratpack dependency and use the @EnableRatpack annotation. With this annotation a RatpackServer instance is created and started along with configuration options.

Let's see an example Spring Boot application with Ratpack enabled. First we add Ratpack as dependency to our Spring Boot application. In our example we also add Ratpack's Dropwizard module as dependency. We use Gradle in our example:

// File: build.gradle
plugins {
    id 'groovy'
    id 'idea'
    id 'org.springframework.boot' version '1.5.2.RELEASE'
}

repositories {
    jcenter()
}

ext {
    ratpackVersion = '1.4.5'
}
dependencies {
    compile 'org.springframework.boot:spring-boot-starter'
    compile 'org.springframework.boot:spring-boot-devtools'
    
    // Add Ratpack for Spring Boot dependency.
    compile "io.ratpack:ratpack-spring-boot-starter:$ratpackVersion"
    // Add Dropwizard for Ratpack dependency.
    compile "io.ratpack:ratpack-dropwizard-metrics:$ratpackVersion"
    
    runtime 'ch.qos.logback:logback-classic:1.2.2'
    
    testCompile "org.spockframework:spock-core:1.0-groovy-2.4" 
}

springBoot {
    mainClass = 'mrhaki.sample.SampleApp'    
}

Now we look at our example application. We use the annotation @EnableRatpack to have Ratpack in our Spring Boot application. We add a Spring bean that implements Action<Chain>. Beans of this type are recognised by Spring Boot and are added to the Ratpack configuration. We also add a Spring bean that is a Ratpack module. This bean is also automatically added to the Ratpack configuration.

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

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import ratpack.dropwizard.metrics.DropwizardMetricsConfig;
import ratpack.dropwizard.metrics.DropwizardMetricsModule;
import ratpack.func.Action;
import ratpack.handling.Chain;
import ratpack.handling.RequestLogger;
import ratpack.spring.config.EnableRatpack;
import ratpack.spring.config.RatpackServerCustomizer;

import java.time.Duration;

// Add Ratpack configuration for Spring Boot
@EnableRatpack
@EnableConfigurationProperties
@SpringBootApplication
public class SampleApp {

    /**
     * Start application.
     * 
     * @param args
     */
    public static void main(String[] args) {
        SpringApplication.run(SampleApp.class, args);
    }

    /**
     * Implementation for {@link MessageService} with pirate accent.
     * 
     * @return {@link MessageService} Pirate speak.
     */
    @Bean
    MessageService pirateMessage() {
        return name -> String.format("Arr, matey %s", name);
    }

    /**
     * Create Ratpack chain to handle requests to {@code /message} endpoint.
     * 
     * @return Ratpack chain.
     */
    @Bean
    Action<Chain> messageHandler() {
        return chain -> chain
                // Add logging for requests.
                .all(RequestLogger.ncsa())
                .get("message/:name?", ctx -> {
                    final String name = ctx.getPathTokens().getOrDefault("name", "mrhaki");
                    // Use MessageService implementation added to Spring context.
                    final String message = ctx.get(MessageService.class).message(name);
                    ctx.render(message);
                });
    }

    /**
     * Configuration properties to configure {@link DropwizardMetricsModule}.
     * Properties can be set via default Spring Boot mechanism like
     * environment variables, system properties, configuration files, etc.
     * 
     * @return Configuration for {@link DropwizardMetricsModule}
     */
    @Bean
    MetricsProperties metricsProperties() {
        return new MetricsProperties();
    }

    /**
     * Spring beans that are {@link com.google.inject.Module} objects are
     * automatically added to Ratpack's registry.
     * 
     * @param metricsProperties Configuration for module.
     * @return Module to add Dropwizard to Ratpack.
     */
    @Bean
    DropwizardMetricsModule metricsModule(final MetricsProperties metricsProperties) {
        // Create Dropwizard configuration.
        final DropwizardMetricsConfig config = new DropwizardMetricsConfig();
        if (metricsProperties.isJmx()) {
            config.jmx();
        }
        if (metricsProperties.getSlf4j().isEnabled()) {
            config.slf4j(slf4jConfig -> slf4jConfig
                    .enable(true)
                    .reporterInterval(Duration.ofSeconds(metricsProperties.getSlf4j().getInterval())));
        }

        // Create Dropwizard module.
        final DropwizardMetricsModule metricsModule = new DropwizardMetricsModule();
        metricsModule.setConfig(config);

        return metricsModule;
    }
}

We create a bean pirateMessage that implements the following interface:

package mrhaki.sample;

public interface MessageService {
    String message(final String name);
}

We also need the supporting class MetricsProperties to allow for configuration of the Dropwizard module.

// File: src/main/java/mrhaki/sample/MetricsProperties.java
package mrhaki.sample;

import org.springframework.boot.context.properties.ConfigurationProperties;

/**
 * Configuration for {@link ratpack.dropwizard.metrics.DropwizardMetricsModule}.
 */
@ConfigurationProperties(prefix = "dropwizard")
public class MetricsProperties {
    private boolean jmx;
    private Slf4Config slf4j = new Slf4Config();

    public boolean isJmx() {
        return jmx;
    }

    public void setJmx(final boolean jmx) {
        this.jmx = jmx;
    }

    public Slf4Config getSlf4j() {
        return slf4j;
    }

    public static class Slf4Config {
        private boolean enabled;
        private long interval = 30;

        public boolean isEnabled() {
            return enabled;
        }

        public void setEnabled(final boolean enabled) {
            this.enabled = enabled;
        }

        public long getInterval() {
            return interval;
        }

        public void setInterval(final long interval) {
            this.interval = interval;
        }
    }
}

To complete our application we also add a configuration file where we can change several aspects of our application:

# File: src/main/resources/application.yml
ratpack:
  port: 9000
---
dropwizard:
  jmx: true
  slf4j:
    enabled: true
    interval: 10

Let's start the application and we can see already in the logging output Ratpack is started:

$ ./gradlew bootRun
:compileJava
:compileGroovy NO-SOURCE
:processResources UP-TO-DATE
:classes
:findMainClass
:bootRun
06:38:45.137 [main] DEBUG org.springframework.boot.devtools.settings.DevToolsSettings - Included patterns for restart : []
06:38:45.139 [main] DEBUG org.springframework.boot.devtools.settings.DevToolsSettings - Excluded patterns for restart : [/spring-boot-starter/target/classes/, /spring-boot-autoconfigure/target/classes/, /spring-boot-starter-[\w-]+/, /spring-boot/target/classes/, /spring-boot-actuator/target/classes/, /spring-boot-devtools/target/classes/]
06:38:45.140 [main] DEBUG org.springframework.boot.devtools.restart.ChangeableUrls - Matching URLs for reloading : [file:/Users/mrhaki/Projects/mrhaki.com/blog/posts/samples/ratpack/springboot/build/classes/main/, file:/Users/mrhaki/Projects/mrhaki.com/blog/posts/samples/ratpack/springboot/build/resources/main/]

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.5.2.RELEASE)

2017-03-23 06:38:45.531  INFO 25302 --- [  restartedMain] mrhaki.sample.SampleApp                  : Starting SampleApp on mrhaki-laptop-2015.fritz.box with PID 25302 (/Users/mrhaki/Projects/mrhaki.com/blog/posts/samples/ratpack/springboot/build/classes/main started by mrhaki in /Users/mrhaki/Projects/mrhaki.com/blog/posts/samples/ratpack/springboot)
2017-03-23 06:38:45.532  INFO 25302 --- [  restartedMain] mrhaki.sample.SampleApp                  : No active profile set, falling back to default profiles: default
2017-03-23 06:38:45.609  INFO 25302 --- [  restartedMain] s.c.a.AnnotationConfigApplicationContext : Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@701a7feb: startup date [Thu Mar 23 06:38:45 CET 2017]; root of context hierarchy
2017-03-23 06:38:46.023  INFO 25302 --- [  restartedMain] f.a.AutowiredAnnotationBeanPostProcessor : JSR-330 'javax.inject.Inject' annotation found and supported for autowiring
2017-03-23 06:38:46.687  INFO 25302 --- [  restartedMain] o.s.b.d.a.OptionalLiveReloadServer       : LiveReload server is running on port 35729
2017-03-23 06:38:46.714  INFO 25302 --- [  restartedMain] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2017-03-23 06:38:46.726  INFO 25302 --- [  restartedMain] ratpack.server.RatpackServer             : Starting server...
2017-03-23 06:38:46.963  INFO 25302 --- [  restartedMain] ratpack.server.RatpackServer             : Building registry...
2017-03-23 06:38:47.618  INFO 25302 --- [  restartedMain] ratpack.server.RatpackServer             : Initializing 1 services...
2017-03-23 06:38:47.711  INFO 25302 --- [  restartedMain] ratpack.server.RatpackServer             : Ratpack started for http://localhost:9000
2017-03-23 06:38:47.716  INFO 25302 --- [  restartedMain] mrhaki.sample.SampleApp                  : Started SampleApp in 2.556 seconds (JVM running for 2.992)

To add extra server configuration properties we must add a Spring bean that implements the ratpack.spring.config.RatpackServerCustomizer interface. The Spring Boot Ratpack configuration uses all beans found in the context that implement this interface. The interface has three methods we need to implement: getHandlers, getBindings and getServerConfig. The easiest way to implement the interface is by extending the class RatpackServerCustomizerAdapter. This class already provides empty implementations for the three methods. We only need to override the method we need in our application.

We rewrite our previous example application. We create a new class RatpackServerConfig that extends RatpackServerCustomizerAdapter. We override the method getServerConfig to set the development mode property of our Ratpack server configuration:

// File: src/main/java/mrhaki/sample/RatpackServerConfig.java
package mrhaki.sample;

import org.springframework.beans.factory.annotation.Autowired;
import ratpack.func.Action;
import ratpack.server.ServerConfigBuilder;
import ratpack.spring.config.RatpackProperties;
import ratpack.spring.config.RatpackServerCustomizerAdapter;

/**
 * Spring beans that implement {@link ratpack.spring.config.RatpackServerCustomizer}
 * interface our used for configuring Ratpack. The class
 * {@linly onk RatpackServerCustomizerAdapter} is a convenience class we can 
 * extend and only override the methods we need to.
 */
public class RatpackServerConfig extends RatpackServerCustomizerAdapter {

    /**
     * {@link RatpackProperties} configuration properties 
     * for Ratpack configuration.
     */
    private final RatpackProperties ratpack;

    public RatpackServerConfig(final RatpackProperties ratpack) {
        this.ratpack = ratpack;
    }

    /**
     * Extra configuration for the default Ratpack server configuration.
     * 
     * @return Extra server configuration.
     */
    @Override
    public Action<ServerConfigBuilder> getServerConfig() {
        return serverConfigBuilder -> serverConfigBuilder
                .development(ratpack.isDevelopment());
    }
    
}

We change SampleApp and add RatpackServerConfig as Spring bean:

// File: src/main/java/mrhaki/sample/SampleApp.java
...
    /**
     * Extra Ratpack server configuration.
     * 
     * @param ratpackProperties Properties for Ratpack server configuration.
     * @return Bean with extra Ratpack server configuration.
     */
    @Bean
    RatpackServerCustomizer ratpackServerSpec(final RatpackProperties ratpackProperties) {
        return new RatpackServerConfig(ratpackProperties);
    }
...

Another rewrite of our application could be to move all Ratpack configuration like handlers and bindings in the RatpackServerConfig class. We simply need to override the other two methods: getHandlers and getBindings. This way we have all the configuration together.

// File: src/main/java/mrhaki/sample/RatpackServerConfig.java
package mrhaki.sample;

import ratpack.dropwizard.metrics.DropwizardMetricsConfig;
import ratpack.dropwizard.metrics.DropwizardMetricsModule;
import ratpack.func.Action;
import ratpack.guice.BindingsSpec;
import ratpack.handling.Chain;
import ratpack.handling.RequestLogger;
import ratpack.server.ServerConfigBuilder;
import ratpack.spring.config.RatpackProperties;
import ratpack.spring.config.RatpackServerCustomizerAdapter;

import java.time.Duration;
import java.util.Arrays;
import java.util.List;

/**
 * Spring beans that implement {@link ratpack.spring.config.RatpackServerCustomizer}
 * interface our used for configuring Ratpack. The class
 * {@linly onk RatpackServerCustomizerAdapter} is a convenience class we can 
 * extend and only override the methods we need to.
 */
public class RatpackServerConfig extends RatpackServerCustomizerAdapter {

    /**
     * {@link RatpackProperties} configuration properties 
     * for Ratpack configuration. 
     */
    private final RatpackProperties ratpack;

    /**
     * Configuration properties for {@link DropwizardMetricsModule}.
     */
    private final MetricsProperties metrics;

    public RatpackServerConfig(
            final RatpackProperties ratpack, 
            final MetricsProperties metrics) {
        this.ratpack = ratpack;
        this.metrics = metrics;
    }

    /**
     * Extra configuration for the default Ratpack server configuration.
     * 
     * @return Extra server configuration.
     */
    @Override
    public Action<ServerConfigBuilder> getServerConfig() {
        return serverConfigBuilder -> serverConfigBuilder
                .development(ratpack.isDevelopment());
    }

    /**
     * Configure Ratpack handlers.
     * 
     * @return List of Ratpack chain configurations.
     */
    @Override
    public List<Action<Chain>> getHandlers() {
        return Arrays.asList(messageHandler()); 
    }

    /**
     * Create Ratpack chain to handle requests to {@code /message} endpoint.
     *
     * @return Ratpack chain.
     */
    private Action<Chain> messageHandler() {
        return chain -> chain
                // Add logging for requests.
                .all(RequestLogger.ncsa())
                .get("message/:name?", ctx -> {
                    final String name = ctx.getPathTokens().getOrDefault("name", "mrhaki");
                    // Use MessageService implementation added to Spring context.
                    final String message = ctx.get(MessageService.class).message(name);
                    ctx.render(message);
                });
    }

    /**
     * Add {@link DropwizardMetricsModule} to the Ratpack bindings.
     * 
     * @return Ratpack bindings.
     */
    @Override
    public Action<BindingsSpec> getBindings() {
        return bindings -> {
            bindings.module(DropwizardMetricsModule.class, dropwizardMetricsConfig());
        };
    }

    /**
     * Configuration for {@link DropwizardMetricsModule}.
     * 
     * @return Configuration action for configuring {@link DropwizardMetricsModule}.
     */
    private Action<DropwizardMetricsConfig> dropwizardMetricsConfig() {
        return config -> {
            if (metrics.isJmx()) {
                config.jmx();
            }
            if (metrics.getSlf4j().isEnabled()) {
                config.slf4j(slf4jConfig -> slf4jConfig
                        .enable(true)
                        .reporterInterval(Duration.ofSeconds(metrics.getSlf4j().getInterval())));
            }
        };
    }
}

Written with Ratpack 1.4.5 and Spring Boot 1.5.2.RELEASE.

March 17, 2017

Spring Sweets: Access Application Arguments With ApplicationArguments Bean

When we start a Spring Boot application and pass arguments to the application, Spring Boot will capture those arguments and creates a Spring bean of type ApplicationArguments and puts it in the application context. We can use this Spring bean to access the arguments passed to the application. We could for example auto wire the bean in another bean and use the provided argument values. The ApplicationArguments interface has methods to get arguments values that are options and plain argument values. An option argument is prefixed with --, for example --format=xml is a valid option argument. If the argument value is not prefixed with -- it is a plain argument.

In the following example application we have a Spring Boot application with a Spring bean that implements CommandLineRunner. When we define the CommandLineRunner implementation we use the ApplicationArguments bean that is filled by Spring Boot:

package mrhaki.springboot;

import org.jooq.DSLContext;
import org.jooq.Record1;
import org.jooq.Result;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

import java.util.List;

import static mrhaki.springboot.sql.Tables.USERS;

@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) {
        SpringApplication.run(SampleApp.class, args);
    }

    /**
     * Find users based on user input given via application arguments.
     * Example usage: {@code java -jar sample.jar --format=csv mrhaki}
     * 
     * @param dslContext JOOQ context to access data.
     * @param applicationArguments Bean filled by Spring Boot 
     *                             with arguments used to start the application
     * @return Bean to run at startup
     */
    @Bean
    CommandLineRunner showUsers(
            final DSLContext dslContext, 
            final ApplicationArguments applicationArguments) {
        
        return (String... arguments) -> {
            // Get arguments passed to the application  that are
            // not option arguments from ApplicationArguments bean.
            // In the following example: java -jar sample.jar mrhaki
            // mrhaki is the non option argument.
            final String name = applicationArguments.getNonOptionArgs().get(0);
            final Result<Record1<String>> result =
                    dslContext.select(USERS.NAME)
                              .from(USERS)
                              .where(USERS.NAME.likeIgnoreCase(name))
                              .fetch();

            // Check if option argument --format is used.
            // In the following example: java -jar sample.jar --format=xml --format=csv
            // format is the option argument with two values: xml, csv.
            if (applicationArguments.containsOption("format")) {
                // Get values for format option argument.
                final List<String> format = applicationArguments.getOptionValues("format");
                if (format.contains("xml")) {
                    result.formatXML(System.out);
                }
                if (format.contains("json")) {
                    result.formatJSON(System.out);
                }
                if (format.contains("html")) {
                    result.formatHTML(System.out);
                }
                if (format.contains("html")) {
                    result.formatCSV(System.out);
                }
            } else {
                result.format(System.out);
            }
        };
    }

}

We have an alternative if we want to use the ApplicationArguments bean in a CommandlineRunner implementation. Spring Boot offers the ApplicationRunner interface. The interface has the method run(ApplicationArguments) we need to implement and gives us directly access to the ApplicationArguments bean. In the next example we refactor the application and use ApplicationRunner:

package mrhaki.springboot;

import org.jooq.DSLContext;
import org.jooq.Record1;
import org.jooq.Result;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

import java.util.List;

import static mrhaki.springboot.sql.Tables.USEaRS;

@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) {
        SpringApplication.run(SampleApp.class, args);
    }

    /**
     * Find users based on user input given via application arguments.
     * Example usage: {@code java -jar sample.jar --format=csv mrhaki}
     *
     * @param dslContext JOOQ context to access data.
     * @return Bean to run at startup
     */
    @Bean
    ApplicationRunner showUsers(final DSLContext dslContext) {
        return (ApplicationArguments arguments) -> {
            // Get arguments passed to the application that are
            // not option arguments. 
            // In the following example: java -jar sample.jar mrhaki
            // mrhaki is the non option argument.
            final String name = arguments.getNonOptionArgs().get(0);
            final Result<Record1<String>> result =
                    dslContext.select(USERS.NAME)
                              .from(USERS)
                              .where(USERS.NAME.likeIgnoreCase(name))
                              .fetch();

            // Check if option argument --format is used.
            // In the following example: java -jar sample.jar --format=xml --format=csv
            // format is the option argument with two values: xml, csv.
            if (arguments.containsOption("format")) {
                // Get values for format option argument.
                final List<String> format = arguments.getOptionValues("format");
                if (format.contains("xml")) {
                    result.formatXML(System.out);
                } 
                if (format.contains("json")) {
                    result.formatJSON(System.out);
                }
                if (format.contains("html")) {
                    result.formatHTML(System.out);
                }
                if (format.contains("html")) {
                    result.formatCSV(System.out);
                }
            } else {
                result.format(System.out);
            }
        };
    }

}

Written with Spring Boot 1.5.2.RELEASE.

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.