This final part gathers the tools that aren't part of the application's core runtime, yet no production project can do without: monitoring, logging, development acceleration, testing, packaging, and native compilation.
9.1. Actuator — Production-Ready Monitoring
Actuator adds ready-made HTTP/JMX endpoints for monitoring and management to your application.
9.1.1. Adding it
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
By default, only /actuator/health and /actuator/info are exposed. The rest are enabled via management.endpoints.web.exposure.include:
management:
endpoints:
web:
exposure:
include: health,info,metrics,env,beans
9.1.2. Built-in Endpoints
| ID | What it shows |
|---|---|
health |
Application status (UP/DOWN) |
info |
Arbitrary info (info.* properties) |
metrics |
Micrometer metrics (JVM, HTTP, DataSource) |
env |
All property sources |
beans |
The full list of beans |
mappings |
All @RequestMapping paths |
configprops |
All @ConfigurationProperties
|
threaddump |
JVM thread dump |
heapdump |
Heap dump (hprof) |
loggers |
View/change logging levels |
shutdown |
Graceful shutdown — removed in Boot 3.4; use server.shutdown=graceful instead |
9.1.3. Your Own @Endpoint
@Component
@Endpoint(id = "featureFlags")
public class FeatureFlagsEndpoint {
@ReadOperation
public Map<String, Boolean> getFlags() {
return Map.of("newCheckout", true, "darkMode", false);
}
@WriteOperation
public void setFlag(@Selector String name, boolean value) {
// set the flag
}
}
-
@ReadOperation→ HTTP GET (available athttp://localhost:8080/actuator/featureFlags) -
@WriteOperation→ HTTP POST -
@DeleteOperation→ HTTP DELETE
9.1.4. HealthIndicators
@Component
public class ExternalServiceHealthIndicator implements HealthIndicator {
@Override
public Health health() {
if (isServiceUp()) {
return Health.up().withDetail("responseTime", "45ms").build();
}
return Health.down().withDetail("error", "Connection refused").build();
}
}
Actuator collects all HealthIndicator beans and aggregates them into the overall /actuator/health status. The possible statuses are UP, DOWN, OUT_OF_SERVICE, and UNKNOWN. The overall status is the "worst" of them all.
9.1.5. Micrometer
Micrometer is a metrics facade. Actuator automatically registers a MeterRegistry and collects JVM metrics (heap, threads, GC).
@Component
public class OrderMetrics {
private final Counter orderCounter;
private final Timer orderTimer;
public OrderMetrics(MeterRegistry registry) {
this.orderCounter = Counter.builder("orders.created")
.description("Number of created orders")
.register(registry);
this.orderTimer = Timer.builder("orders.processing.time")
.register(registry);
}
public void onOrderCreated() { orderCounter.increment(); }
public void recordTime(Runnable task) { orderTimer.record(task); }
}
Prometheus integration:
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
The /actuator/prometheus endpoint serves metrics in the Prometheus format.
9.1.6. Boot 4: Actuator Changes
- Packages relocated:
org.springframework.boot.actuate.health.Health→org.springframework.boot.health.contributor.Health. - Modularization: Actuator is split into many modules (actuator-web, actuator-jmx, actuator-sbom, etc.).
-
/actuator/infohas been extended: process information is now included.
9.2. Logging
9.2.1. The Defaults
Spring Boot uses SLF4J as the facade and Logback as the implementation. At startup, LoggingApplicationListener (registered in spring.factories) initializes logging before the ApplicationContext is even created.
9.2.2. Configuration
| System | File |
|---|---|
| Logback |
logback-spring.xml (recommended), logback.xml
|
| Log4j2 |
log4j2-spring.xml, log4j2.xml
|
| JUL | logging.properties |
The -spring variants are recommended: they support <springProfile> and <springProperty>.
<!-- logback-spring.xml -->
<configuration>
<springProfile name="dev">
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
</springProfile>
<springProfile name="prod">
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_FILE}</file>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger - %msg%n</pattern>
</encoder>
</appender>
</springProfile>
</configuration>
9.2.3. Levels via Properties
logging:
level:
root: INFO
com.example.myapp: DEBUG
org.springframework.web: WARN
9.2.4. Structured Logging (Boot 3.4+)
Spring Boot 3.4 added native structured logging support with no extra dependencies:
logging:
structured:
format:
console: ecs # Elastic Common Schema
file: logstash
Formats: ecs, logstash, gelf.
9.2.5. Boot 4: Changes
- The default encoding is UTF-8 for Logback, matching Log4j2.
- No automatic trace/span ID correlation — you need to configure the appender manually.
- Logback has been upgraded to a new version; some appenders were removed.
9.3. DevTools — Speeding Up Development
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
9.3.1. Automatic Restart
DevTools uses two ClassLoaders:
| ClassLoader | What it loads |
|---|---|
| Base ClassLoader | Immutable JARs (Spring, third-party libraries) |
| Restart ClassLoader | Your application's classes (the frequently changing ones) |
When a classpath file changes, DevTools re-creates only the Restart ClassLoader while the Base one stays. That's what makes the restart several times faster than a full one.
9.3.2. LiveReload
The built-in LiveReload server automatically refreshes the browser when resources change (HTML, CSS, JS). Requires a LiveReload browser extension. Disabled via spring.devtools.livereload.enabled=false.
9.3.3. include/exclude Configuration
# META-INF/spring-devtools.properties
restart.exclude.companycommonlibs=/mycorp-common-[\\w-]+\.jar
restart.include.projectcommon=/mycorp-myproj-[\\w-]+\.jar
exclude — JARs loaded by the Base ClassLoader. include — the opposite, loaded by the Restart ClassLoader.
9.3.4. Global Settings
A .spring-boot-devtools.properties file in $HOME applies to all projects.
Important: DevTools must never make it into production.
optional=trueplus Maven/Gradle excludes it from the final JAR.
9.4. Testing
9.4.1. @SpringBootTest — the Full Context
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class MyApplicationTests {
@Autowired
private TestRestTemplate restTemplate;
@Test
void contextLoads() {
ResponseEntity<String> response = restTemplate.getForEntity("/api/hello", String.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
}
}
webEnvironment:
-
MOCK(default) — no real server,MockMvc -
RANDOM_PORT— a real server on a random port -
DEFINED_PORT— on the configured port -
NONE— no web environment
9.4.2. Slice Tests — Narrow Contexts
| Annotation | What it loads | What to mock |
|---|---|---|
@WebMvcTest |
Only MVC: controllers, @ControllerAdvice, filters |
Services via @MockitoBean
|
@DataJpaTest |
Only JPA: repositories, @Entity, DataSource |
Usually H2 in-memory |
@JsonTest |
Only Jackson/Gson | — |
@RestClientTest |
Only the REST client (RestTemplate, WebClient) |
The server via MockWebServer |
@WebMvcTest(UserController.class)
class UserControllerTest {
@Autowired
private MockMvc mockMvc;
@MockitoBean
private UserService userService;
@Test
void shouldReturnUser() throws Exception {
when(userService.findById(1L)).thenReturn(new User(1L, "Alice"));
mockMvc.perform(get("/api/users/1"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.name").value("Alice"));
}
}
The key advantage of slice tests: the context loads faster because only the relevant slice of auto-configuration is enabled.
9.4.3. @MockitoBean — Replacing @MockBean in Boot 4
A critical breaking change in Boot 4:
// Boot 3.x (deprecated in 3.4, removed in 4.0)
@MockBean
private UserService userService;
// Boot 4.x
import org.springframework.test.context.bean.override.mockito.MockitoBean;
@MockitoBean
private UserService userService;
@MockBean and @SpyBean are gone in Boot 4. The replacements are @MockitoBean and @MockitoSpyBean from Spring Framework.
9.4.4. ApplicationContextRunner — Testing Auto-configurations
For testing your own auto-configurations:
class MyAutoConfigurationTests {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(MyServiceAutoConfiguration.class))
.withPropertyValues("my.service.endpoint=http://localhost:9090");
@Test
void shouldCreateMyService() {
contextRunner.run(context -> {
assertThat(context).hasSingleBean(MyService.class);
});
}
@Test
void shouldBackOffWhenCustomBeanPresent() {
contextRunner
.withUserConfiguration(CustomConfig.class)
.run(context -> {
assertThat(context).hasSingleBean(MyService.class);
assertThat(context.getBean(MyService.class)).isInstanceOf(CustomMyService.class);
});
}
}
ApplicationContextRunner lets you test auto-configuration conditions in isolation, without loading the whole application.
9.5. Build and Packaging
9.5.1. spring-boot-maven-plugin
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
The repackage goal creates an executable JAR (a fat jar):
mvn clean package
java -jar target/myapp-0.0.1-SNAPSHOT.jar
9.5.2. Layered JAR
Since Boot 2.3, the JAR can be split into layers for efficient Docker caching:
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<layers>
<enabled>true</enabled>
</layers>
</configuration>
</plugin>
Layers (from rarely changing to frequently changing):
-
dependencies— stable dependencies -
spring-boot-loader— the loader -
snapshot-dependencies— snapshots -
application— your code
Dockerfile:
FROM eclipse-temurin:21-jre AS builder
WORKDIR /app
COPY target/myapp-*.jar app.jar
RUN java -Djarmode=tools -jar app.jar extract --layers --launcher
FROM eclipse-temurin:21-jre
WORKDIR /app
COPY --from=builder /app/dependencies/ ./
COPY --from=builder /app/spring-boot-loader/ ./
COPY --from=builder /app/snapshot-dependencies/ ./
COPY --from=builder /app/application/ ./
ENTRYPOINT ["java", "org.springframework.boot.loader.launch.JarLauncher"]
9.5.3. JarLauncher
JarLauncher is the fat jar's entry point. It lives in spring-boot-loader and is responsible for:
- Reading
MANIFEST.MF(Main-Class: org.springframework.boot.loader.launch.JarLauncher) - Building a
URLClassLoaderwith all the nested JARs - Delegating to the
Start-Class(yourmainclass)
There's also WarLauncher and PropertiesLauncher (for external configurations).
9.5.4. Buildpacks
mvn spring-boot:build-image
Spring Boot uses Paketo Buildpacks to create an OCI image without a Dockerfile:
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<image>
<name>myregistry/myapp:${project.version}</name>
</image>
</configuration>
</plugin>
9.5.5. Boot 4: Changes
- Starters renamed:
spring-boot-starter-web→spring-boot-starter-webmvc. - Undertow removed (incompatible with Servlet 6.1).
-
spring-boot-loaderremains, but the packages have been relocated.
9.6. AOT and GraalVM Native Image (an Overview)
9.6.1. What Is AOT
Ahead-of-Time — processing the application at build time rather than at runtime. Spring Boot 3+ performs AOT processing that:
- Scans all
@Configuration,@Bean,@Componentclasses. - Generates Java code for bean registration (instead of reflection).
- Registers
RuntimeHints— hints for GraalVM.
Why: GraalVM Native Image compiles Java into a native executable. Reflection, dynamic proxies, and resource loading only work in a native image if you declare them at build time.
9.6.2. RuntimeHintsRegistrar
public class MyRuntimeHints implements RuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
hints.reflection().registerType(MyDto.class, MemberCategory.values());
hints.resources().registerPattern("my-config/*.json");
}
}
Registration:
@ImportRuntimeHints(MyRuntimeHints.class)
@Configuration
public class MyConfig { }
9.6.3. @RegisterReflectionForBinding — a Shortcut
@Configuration
@RegisterReflectionForBinding({User.class, UserDto.class})
public class JacksonConfig { }
The annotation registers reflection hints for DTOs that get serialized by Jackson.
9.6.4. Building a Native Image
mvn -Pnative native:compile
The result is a native executable:
./target/myapp
Advantages:
- Instant startup (milliseconds instead of seconds)
- Lower memory consumption
- Smaller image size
Limitations:
- No dynamic class loading
- No JIT (lower peak performance)
- Longer builds
- Not all libraries are supported
9.6.5. Boot 4: AOT by Default
In Boot 4, AOT processing is more aggressive: reflection-free by default for most scenarios. BeanRegistrar (introduced in Spring Framework 7) is the primary way to register beans for AOT, replacing reflection-based @Bean methods.
9.7. Summary Table: Boot 3 vs Boot 4
| Aspect | Spring Boot 3.x | Spring Boot 4.x |
|---|---|---|
| Actuator packages | org.springframework.boot.actuate.health |
org.springframework.boot.health.contributor |
| Actuator modules | A single spring-boot-actuator
|
Many modules (web, jmx, sbom...) |
| Default logging | Logback, charset depends on the OS | Logback, UTF-8 always |
| Structured logging | Introduced in 3.4 | Fully integrated |
| Test annotations |
@MockBean, @SpyBean
|
Removed; replaced by @MockitoBean, @MockitoSpyBean
|
| Starters | spring-boot-starter-web |
spring-boot-starter-webmvc |
| AOT | Opt-in, -Pnative
|
More aggressive, reflection-free |
| Buildpacks | Supported | Supported, improved |
| Layered JAR | Present | Present, improved |
9.8. Key Takeaways
- Actuator — not just
/health. Custom endpoints via@Endpoint+@ReadOperation/@WriteOperation.HealthIndicator— for checking external services. Micrometer — for metrics. - Logging is initialized before the
ApplicationContext. Uselogback-spring.xml, notlogback.xml. Since Boot 3.4 — structured logging out of the box. - DevTools uses two ClassLoaders: Base (stable JARs) and Restart (your code). It restarts only the latter.
- Testing:
@SpringBootTest— the full context; slice tests — narrow contexts.ApplicationContextRunner— for testing auto-configurations. -
@MockBean→@MockitoBean— a critical breaking change in Boot 4. - Layered JAR — splitting into layers for Docker caching.
JarLauncher— the fat jar's entry point. - Buildpacks — an OCI image without a Dockerfile:
mvn spring-boot:build-image. - AOT — build-time processing for GraalVM.
@RegisterReflectionForBinding— for DTOs. Native image: instant startup, but with limitations.
The End of the Series
Top comments (0)