Skip to main content

Auto-configuration and Actuator

Source: Spring Boot in Practice — Chapter 4 by Somnath Musib (Manning). Concepts rewritten for this course; companion samples: spring-boot-in-practice/repo ch04.

Auto-configuration is why Boot feels “magical.” Actuator is how you observe a running app. This lesson covers conditionals, DevTools, custom failure analysis, and production-ready endpoints and metrics.

Pattern-check challenges

These editors do not compile or run Spring Boot. Check validates patterns (sourceChecks) only. Annotated snippets above each exercise show the usual shape — inline comments explain what each part does; type it yourself. Paste is disabled on Spring Boot lessons.

What this lesson covers​

  • Auto-configuration motivation and @Conditional / Boot’s high-level conditionals
  • How EnableAutoConfiguration loads configs (historically spring.factories; Boot 3 uses META-INF/spring/…imports)
  • DevTools for faster local feedback
  • Custom FailureAnalyzer
  • Actuator endpoints, health/info customization, metrics overview

1. Auto-configuration​

Boot guesses what you need from the classpath and properties, then registers beans — unless you already defined them. Example: spring-boot-starter-web → embedded Tomcat. Prefer Jetty? Exclude Tomcat and add the Jetty starter; Boot backs off.

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId> <!-- drop default server -->
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId> <!-- use Jetty instead -->
</dependency>

Motivation: shared configs and opting out​

Teams often extract shared @Configuration beans into a library and @Import them. That breaks when one consumer needs most beans but not one of them (e.g. no relational DataSource). Spring’s @Conditional (and Boot’s specialized variants) gate bean creation on classpath, properties, or existing beans.

@Conditional​

Place @Conditional(MyCondition.class) on @Bean / @Component / @Configuration. Implement Condition.matches(...) — true → create; false → skip. Typical checks: class present on classpath, or a property set via ConditionContext.

Boot’s high-level conditionals​

AnnotationTrue when…
@ConditionalOnClassNamed class is on the classpath
@ConditionalOnMissingBeanNo bean of that type already exists
@ConditionalOnBeanA bean of that type exists
@ConditionalOnPropertyProperty is present / matches
@ConditionalOnWebApplicationApp is a web app
@ConditionalOnMissingClassClass is not on the classpath

These power auto-config: e.g. create a pooled DataSource only if the classes exist and you did not define one yourself (@ConditionalOnMissingBean).

@Bean
@ConditionalOnMissingBean(DataSource.class) // only if user did not define one
@ConditionalOnClass(DataSource.class)
public DataSource dataSource() { … }

Deep dive: DataSourceAutoConfiguration (idea)​

Boot’s JDBC auto-config is a @Configuration that roughly:

  1. @ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
  2. @EnableConfigurationProperties(DataSourceProperties.class) for spring.datasource.*
  3. Nested configs for embedded vs pooled datasources, each with @ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
  4. Imports Hikari / Tomcat / DBCP2 configuration classes when the pool condition matches

Same pattern everywhere: classpath + missing user bean ⇒ Boot supplies a default.

Registration: older Boot listed auto-configs under EnableAutoConfiguration in META-INF/spring.factories. Boot 3 prefers META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports.

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
Quiz

Auto-configuration quiz

1 / 2

Why does Boot often skip creating a DataSource bean?

Pick an answer to see if you are right.


2. DevTools​

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional> <!-- do not leak to dependents -->
</dependency>

DevTools (development only):

  • Property defaults — turns off caches that hurt local edit/refresh (templates, etc.)
  • Automatic restart — classpath changes reload app code via a restart classloader (third-party jars stay in a base loader)
  • LiveReload — browser refresh when static resources change (browser extension required)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>

3. Custom FailureAnalyzer​

Boot formats startup failures (e.g. port in use) via FailureAnalyzer. You can plug in domain checks:

  1. Throw a custom exception (e.g. UrlNotAccessibleException)
  2. Extend AbstractFailureAnalyzer<YourException> and return FailureAnalysis(description, action, cause)
  3. Register in META-INF/spring.factories (or Boot 3 imports) under org.springframework.boot.diagnostics.FailureAnalyzer
  4. Trigger validation on a lifecycle event (ContextRefreshedEvent, etc.)
public class UrlNotAccessibleFailureAnalyzer
extends AbstractFailureAnalyzer<UrlNotAccessibleException> {
@Override
protected FailureAnalysis analyze(Throwable root, UrlNotAccessibleException cause) {
return new FailureAnalysis(
"Unable to access the URL " + cause.getUrl(), // what failed
"Validate the URL and ensure it is accessible", // what to do
cause);
}
}
org.springframework.boot.diagnostics.FailureAnalyzer=\
com.example.demo.UrlNotAccessibleFailureAnalyzer
public class UrlNotAccessibleFailureAnalyzer
extends AbstractFailureAnalyzer<UrlNotAccessibleException> {
@Override
protected FailureAnalysis analyze(Throwable rootFailure, UrlNotAccessibleException cause) {
return new FailureAnalysis(
"Unable to access the URL " + cause.getUrl(),
"Validate the URL and ensure it is accessible",
cause);
}
}
# META-INF/spring.factories (or Boot 3 FailureAnalyzer.imports)
org.springframework.boot.diagnostics.FailureAnalyzer=\
com.example.demo.UrlNotAccessibleFailureAnalyzer

4. Spring Boot Actuator​

Enable​

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

Brings actuator auto-config + Micrometer for metrics. Discovery page: /actuator. Default HTTP exposure is mainly health and info; many other IDs are on JMX by default.

Common endpoints​

IdPurpose
healthAggregated / component health
infoApp info from properties / contributors
metricsMicrometer meters
env, beans, conditionsEnvironment, beans, auto-config report
loggers, threaddump, heapdumpOps diagnostics
shutdownStop the app (disabled by default)

Expose over HTTP:

management.endpoints.web.exposure.include=health,info,metrics
# or * then exclude sensitive ones
management.endpoints.web.exposure.exclude=shutdown,heapdump
management.endpoints.web.base-path=/actuator
management.server.port=8081 # optional separate management port
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
management.endpoints.web.exposure.include=health,info,metrics # expose these over HTTP
management.endpoint.health.show-details=always # include diskSpace, db, custom indicators

Health deep dive​

Statuses: UP, DOWN, OUT_OF_SERVICE, UNKNOWN (custom via Health.status("FATAL")). Aggregation uses priority (DOWN wins by default). Map statuses to HTTP codes with management.endpoint.health.status.http-mapping.*.

Built-in indicators: disk space, ping; DataSource appears when JDBC is on the classpath.

Custom HealthIndicator: @Component class implementing health() → Health.up() / down(), optionally withDetails / withException. Convention: name ends with HealthIndicator.

@Component
public class DogsApiHealthIndicator implements HealthIndicator {
@Override
public Health health() {
// call remote API; return Health.up() or Health.down()
return Health.up().withDetail("api", "reachable").build();
}
}
@Component
public class DogsApiHealthIndicator implements HealthIndicator {
@Override
public Health health() {
return Health.up().withDetail("api", "reachable").build();
}
}

Info deep dive​

Default /info is empty. Fill it with:

  • info.* properties in application.properties
  • Maven/@project.*@ expansion or build-info goal → build-info.properties
  • git.properties via git-commit-id plugin
  • Custom InfoContributor @Component implementing contribute(Info.Builder)
info.app.name=Course Tracker
info.app.version=1.0.0
@Component
public class CourseInfoContributor implements InfoContributor {
@Override
public void contribute(Info.Builder builder) {
builder.withDetail("courses", List.of()); // attach custom payload
}
}

Custom actuator endpoint​

@Component
@Endpoint(id = "courses") // appears as /actuator/courses when exposed
public class CourseEndpoint {
@ReadOperation
public Iterable<Course> courses() { … }

@WriteOperation
public void updateRating(@Selector Long id, int newRating) { … }

@DeleteOperation
public void deleteAll() { … }
}

Expose with management.endpoints.web.exposure.include=courses (or *). Prefer not to put core business mutations only on Actuator in production — use REST APIs; Actuator is for ops.

Metrics (Micrometer)​

/actuator/metrics lists meters; /actuator/metrics/{name} shows one. Custom meters: Counter, Gauge, Timer, DistributionSummary registered on MeterRegistry. Export to Prometheus with micrometer-registry-prometheus → /actuator/prometheus; visualize in Grafana.

@Bean
public Counter createCourseCounter(MeterRegistry registry) {
return Counter.builder("api.courses.created.count") // metric name
.description("Total number of courses created")
.register(registry);
}
@Bean
public Counter createCourseCounter(MeterRegistry meterRegistry) {
return Counter.builder("api.courses.created.count")
.description("Total number of courses created")
.register(meterRegistry);
}
Quiz

Actuator quiz

1 / 3

By default over HTTP, Spring Boot typically exposes…

Pick an answer to see if you are right.


Chapter checklist​

TopicTakeaway
Auto-configClasspath + @Conditional* + back off if user beans exist
DevToolsOptional dependency; restart + LiveReload + cache defaults
FailureAnalyzerCustom exception → analyzer → register → lifecycle trigger
ActuatorStarter + controlled HTTP exposure; health/info/metrics
ExtensionsHealthIndicator, InfoContributor, @Endpoint, Micrometer meters

Next in the book: securing Boot apps with Spring Security (advanced topics in Lesson 4).