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.
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
EnableAutoConfigurationloads configs (historicallyspring.factories; Boot 3 usesMETA-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
| Annotation | True when… |
|---|---|
@ConditionalOnClass | Named class is on the classpath |
@ConditionalOnMissingBean | No bean of that type already exists |
@ConditionalOnBean | A bean of that type exists |
@ConditionalOnProperty | Property is present / matches |
@ConditionalOnWebApplication | App is a web app |
@ConditionalOnMissingClass | Class 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:
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })@EnableConfigurationProperties(DataSourceProperties.class)forspring.datasource.*- Nested configs for embedded vs pooled datasources, each with
@ConditionalOnMissingBean({ DataSource.class, XADataSource.class }) - 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>
Auto-configuration quiz
1 / 2Why 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:
- Throw a custom exception (e.g.
UrlNotAccessibleException) - Extend
AbstractFailureAnalyzer<YourException>and returnFailureAnalysis(description, action, cause) - Register in
META-INF/spring.factories(or Boot 3 imports) underorg.springframework.boot.diagnostics.FailureAnalyzer - 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
| Id | Purpose |
|---|---|
health | Aggregated / component health |
info | App info from properties / contributors |
metrics | Micrometer meters |
env, beans, conditions | Environment, beans, auto-config report |
loggers, threaddump, heapdump | Ops diagnostics |
shutdown | Stop 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 inapplication.properties- Maven/
@project.*@expansion orbuild-infogoal →build-info.properties git.propertiesvia git-commit-id plugin- Custom
InfoContributor@Componentimplementingcontribute(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);
}
Actuator quiz
1 / 3By default over HTTP, Spring Boot typically exposes…
Pick an answer to see if you are right.
Chapter checklist
| Topic | Takeaway |
|---|---|
| Auto-config | Classpath + @Conditional* + back off if user beans exist |
| DevTools | Optional dependency; restart + LiveReload + cache defaults |
| FailureAnalyzer | Custom exception → analyzer → register → lifecycle trigger |
| Actuator | Starter + controlled HTTP exposure; health/info/metrics |
| Extensions | HealthIndicator, InfoContributor, @Endpoint, Micrometer meters |
Next in the book: securing Boot apps with Spring Security (advanced topics in Lesson 4).