In this post we will take a closer look at Spring Actuator and highlight some changes of it in Spring Boot 2.0. We will discuss some of the endpoints and will create a custom endpoint to our application. The sources can be found at GitHub.

What is Spring Boot Actuator?

Spring Boot Actuator supplies several endpoints in order to monitor and interact with your application. It does so by providing built-in endpoints, but you are also able to build your own endpoints. In the next sections we will create a dummy application, enable the Actuator endpoints, provide the version and build information to it, add security to the endpoint and customize an endpoint to our needs.

Create the project

First of all, we will create our Spring project. As always, we do so by means of the Spring Initialzr. We select the following dependencies:

  • Spring Boot 2.0.0
  • Java 9
  • Web for using Spring MVC
  • Actuator in order to add the dependency for Spring Actuator, what this post is all about of course

When we take a closer look to the pom, we notice that the following dependency for Spring Actuator is added:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

In order to have some functionality in our application, we create a simple RestController which simply returns a string ‘Hello Spring Boot Actuator’. The controller is the following:

@RestController
public class HelloActuatorController {

  @RequestMapping(value = "/helloactuator", method= GET)
  public String helloActuator() {
    return "Hello Spring Boot Actuator";
  }

}

Run the Spring Boot application and invoke the url http://localhost:8080/helloactuator  which returns the string in the browser.

Actuator endpoint

Now that we have setup our simple application, it is time to take a look at what Spring Actuator has to offer us. In the browser, go to the url http://localhost:8080/actuator/. This shows us an overview of the exposed Actuator endpoints. Compared to Spring Boot 1, the Actuator endpoints all reside after this actuator endpoint. This should prevent naming collisions with your own endpoints whenever they would have the same name as Actuator endpoints. Invoking the url returns us the following:

{
  "_links": {
    "self": {
      "href": "http://localhost:8080/actuator",
      "templated": false
    },
    "health": {
      "href": "http://localhost:8080/actuator/health",
      "templated": false
    },
    "info": {
      "href": "http://localhost:8080/actuator/info",
      "templated": false
    }
  }
}

As you can see, only 3 Actuator endpoints are exposed by default. In order to expose more endpoints, we need to add include or exclude configuration to the application.properties file. We will add all endpoints to the configuration but you can also limit the exposed endpoints by means of a comma separated list. In order to expose all endpoints, we add the following configuration to the application.properties file:

management.endpoints.web.exposure.include=*

Restart the application and invoke the url again: all Actuator endpoints except shutdown are available now.

All available endpoints can be found in the Spring documentation: https://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-endpoints.html

Add version information

In one of the previous posts, we introduced the git commit id plugin which provided us version information. We add the plugin to our pom just as we did in the previous post. Make sure that the format is set to properties instead of json. Whenever there is a git.properties file into the root of the output directory, the info Actuator endpoint will provide version information about your application. Invoke the url http://localhost:8080/actuator/info and by default the following information is shown:

{
  "git": {
    "branch": "master",
    "commit": {
      "id": "d7d7202",
      "time": "2018-03-24T13:24:51Z"
    }
  }
}

As you can see, this is not the complete contents of our git.properties file. In order to add the complete contents, we add the following property to our application.properties file and restart the application:

management.info.git.mode=full

At this point, we can see the complete output.

Add build information

Similar to the version information, we can add build information. The info Actuator endpoint will show the build information whenever a build-info.properties file is present in the META-INF directory. In order to generate the build-info.properties file, we add the build-info goal to our spring-boot-maven-plugin in the pom:

<plugin>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-maven-plugin</artifactId>
  <executions>
    <execution>
      <goals>
        <goal>build-info</goal>
      </goals>
    </execution>
  </executions>
</plugin>

When we re-run the application, the build information is available at the info Actuator endpoint and looks like the following:

{
  "git": {...}, // 7 items
  "build": {
    "artifact": "myspringactuatorplanet",
    "name": "myspringactuatorplanet",
    "time": "2018-03-24T13:40:03.907109600Z",
    "version": "0.0.1-SNAPSHOT",
    "group": "com.mydeveloperplanet"
  }
}

Secure the endpoints

Most of the time, we do not want this kind of information to be accessible by everyone. In that case, we can secure the Actuator endpoints. First of all, we will add Spring Security to the pom:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-security</artifactId>
</dependency>

Just by adding Spring Security already gives us a login page when trying to access the Actuator endpoints and also our own helloActuator endpoint. We can login with user user and the password which is available in the Spring Boot log. This looks like the following (of course the hash changes after each startup):

Using generated security password: 8da882c3-4bbb-4e71-88c2-13399d9f0724

Now let’s assume that we only want to secure the Actuator endpoints and not our own hellActuator endpoint. According to the Spring documentation we need to add the following configuration class:

@Configuration
public class ActuatorSecurity extends WebSecurityConfigurerAdapter {

  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http.requestMatcher(EndpointRequest.toAnyEndpoint()).authorizeRequests()
      .anyRequest().hasRole("ADMIN")
      .and()
      .httpBasic();
  }

}

At this point, only the Actuator endpoints have been secured. It took me some time to find out that it were only the Actuator endpoints and not the Actuator base path. I assumed that EndpointRequest.toAnyEndpoint would also secure the url http://localhost:8080/actuator, but that is not the case. The idea behind this, is that it isn’t an actual endpoint. An issue for Spring Boot exists for this: https://github.com/spring-projects/spring-boot/issues/12353

We are still stuck with the generated password. We can overcome this by adding a userDetailsService to the ActuatorSecurity class. The way we are doing this is only allowed for demonstrating purposes and should not be used in a real production environment.

@Bean
@Override
public UserDetailsService userDetailsService() {
  UserDetails user =
    User.withDefaultPasswordEncoder()
        .username("user")
        .password("password")
        .roles("ADMIN")
        .build();

  return new InMemoryUserDetailsManager(user);
}

The Actuator endpoints are now accessible with user user and password password.

The Actuator health endpoint

The Actuator health endpoint by default shows no detail. Several built-in healthindicators are present and the information to show is collected from these healthindicators. But by default, you will only see the following:

{
  "status": "UP"
}

In order to see the information of the built-in healthindicators, you will need to add the following line to your application.properties.  This way, all information from the retrieved healthindicators is being shown. The default value is never, never showing the health indicators information. We choose to show the health indicator information when an authorized user is requesting the information.

management.endpoint.health.show-details=when-authorized

Re-run the application and invoke the Actuator health endpoint, the following information is shown in our case:

{
  "status": "UP",
  "details": {
    "diskSpace": {
      "status": "UP",
      "details": {
        "total": 408943587328,
        "free": 75006865408,
        "threshold": 10485760
      }
    }
  }
}

Most likely, you will also want to create custom health indicators to be used in your monitoring software. In that case, you can create custom health indicators by implementing the HealthIndicator interface. In the following example, we will check whether we are in an odd or an even minute when invoking the request. This way, we can easily test the change of status of the health indicator. When we are in an even minute, we will return the UP status, when we are in an odd minute, we will return the DOWN status. Our custom class OddOrEvenMinuteHealthCheck becomes the following:

@Component
public class OddOrEvenMinuteHealthIndicator implements HealthIndicator {

  @Override
  public Health health() {
    int errorCode = 0;
    LocalTime now = LocalTime.now();
    if (now.getMinute() % 2 != 0) {
      errorCode = 1;
    }

    if (errorCode != 0) {
      return Health.down().withDetail("Error Code", errorCode).build();
    }
    return Health.up().build();
  }

}

Re-run the application and wait for an even minute. Invoking the Actuator health endpoint shows the following:

"oddOrEvenMinute": {
  "status": "UP"
},

Invoking the Actuator health endpoint when we are in an odd minute, shows the following:

"oddOrEvenMinute": {
  "status": "DOWN",
  "details": {
    "Error Code": 1
  }
},

It is also possible to add custom status codes. Let’s assume we want to have the status ODD and EVEN in our health check. We need to set them into the sequence of standard status codes in the application.properties, we will do so just below the UPP status:

management.health.status.order=DOWN, OUT_OF_SERVICE, UNKNOWN, ODD, EVEN, UP

And the last thing we need to do is to change the return status in our ActuatorHealth class:

if (errorCode != 0) {
  return Health.status("ODD").withDetail("Error Code", errorCode).build();
}
return Health.status("EVEN").build();

Summary

In this post we took a look at Spring Actuator. We saw how we can enable the Actuator endpoints, how we can add version and build information to the info Actuator endpoint, how we can secure the Actuator endpoints and finally, how we can customize some health indicators to the health Actuator endpoint.