Spring Boot Actuator Deep Dive: Custom Metrics and Health Indicators

Deep dive into Spring Boot Actuator: expose custom metrics with Micrometer, create custom HealthIndicators, and secure actuator endpoints properly.

published: reading time: 25 min read author: GeekWorkBench
Quick Summary

Deep dive into Spring Boot Actuator: expose custom metrics with Micrometer, create custom HealthIndicators, and secure actuator endpoints properly. The guide uses practical examples to explain when to use actuator endpoints and when to avoid them, actuator architecture with micrometer and shows how to apply the ideas in a Spring Boot project.

Spring Boot Actuator Deep Dive: Custom Metrics and Health Indicators

Most Spring Boot projects have the Actuator dependency in pom.xml or build.gradle without anyone touching it. Then something breaks in production and you need visibility into the JVM. That is when you either have a useful tool or a mystery box.

Spring Boot Actuator gives you a set of HTTP endpoints (and JMX MBeans) that expose the internals of your running application. Out of the box you get /health, /info, /metrics, and with a few lines of configuration you can unlock /env, /loggers, /heapdump, and more. The key constraint is that Spring Boot deliberately exposes only /health over HTTP by default. Everything else stays hidden until you explicitly whitelist it, which is the right security posture.

The four main concepts to understand are:

  • Endpoints – individual operational units identified by an id (like health, info, metrics, prometheus)
  • Operations – HTTP methods on an endpoint: @ReadOperation (GET), @WriteOperation (POST), @DeleteOperation (DELETE)
  • Health Indicators – components that report the health of a specific subsystem (database, queue, disk)
  • Metrics – numerical measurements exported via Micrometer and surfaced through the /metrics endpoint

Actuator is also the reason Spring Boot can integrate with Kubernetes probes, Prometheus scraping, and Datadog auto-discovery without you writing a single line of configuration code. If you are building microservices, this sits naturally alongside an API gateway in your operational infrastructure.

When to Use Actuator Endpoints and When to Avoid Them

Introduction

Spring Boot Actuator exposes operational information that helps teams understand whether an application is healthy and how it is behaving in production. This deep dive covers endpoint exposure and security, custom health indicators, Micrometer metrics, Kubernetes probes, and integrations with monitoring systems such as Prometheus and Datadog.

Actuator Architecture with Micrometer

Here is how the components connect. Micrometer sits in the middle as the metrics facade, bridging your application code with whatever monitoring backend you have running.

graph TD
    App["Your Spring Boot Application"]
    Actuator["Spring Boot Actuator"]
    Endpoints["Actuator Endpoints<br/>/health /metrics /info<br/>/prometheus /loggers"]
    HealthIndicators["Health Indicators<br/>DB, Redis, Disk, Custom"]
    Micrometer["Micrometer<br/>MeterRegistry"]
    Metrics["Micrometer Metrics<br/>@Timed @Gauge @Counter<br/>@Metered"]
    Backends["Monitoring Backends<br/>Prometheus Datadog<br/>Elastic InfluxDB"]

    App --> Actuator
    App --> Metrics
    Actuator --> Endpoints
    Actuator --> HealthIndicators
    Metrics --> Micrometer
    Micrometer --> Backends
    HealthIndicators --> Actuator

You instrument application code with Micrometer annotations (@Timed, @Gauge, @Counter) or by registering meters directly with a MeterRegistry bean. Actuator aggregates health status from every registered HealthIndicator and exposes it through its endpoints. The MeterRegistry collects metrics and forwards them to whichever exporter you have configured – Prometheus, Datadog, Elastic, InfluxDB, or one of the many others Micrometer supports.

Failure Scenarios

Actuator behaves well in most cases, but a few misconfigurations can turn it into a liability.

Endpoint Exposure Risks

Publishing actuator endpoints publicly without authentication is handing your operational manual to anyone who asks. An attacker hitting /env might walk away with database credentials. /heapdump can dump your entire JVM heap, creating multi-gigabyte files on disk or exposing sensitive in-memory data in the process. /threaddump reveals thread names, which occasionally encode business logic details or user identifiers you did not intend to expose.

Health Endpoint Performance Degradation

If a HealthIndicator makes network calls to a database or remote cache, /health blocks until those calls succeed or timeout. During a genuine upstream outage, a slow health check tells your load balancer the instance is unhealthy even when it would recover on its own. This cascades. Keep health checks fast, and use circuit breakers if you must check external systems at all.

Metric Cardinality Explosion

This is the mistake I see most often with teams new to Micrometer. If you register request.latency with a userId tag and your app has thousands of users, you now have thousands of distinct time series. Session IDs, request IDs, and any other high-cardinality dimension cause the same problem. Your metrics backend chokes, your application slows as the meter registry grows, and you spend an afternoon removing tags you should never have added.

Endpoint ID Collisions

Defining @Endpoint(id = “metrics”) silently conflicts with the built-in metrics endpoint. Spring Boot does not warn you. The behavior becomes unpredictable, with your custom endpoint winning some of the time and the built-in winning the rest.

Built-in vs Custom Health Indicators and Metrics

The tradeoffs here are straightforward.

Aspect Built-in Indicators Custom Indicators
Effort Zero configuration needed Requires writing a Spring bean
Coverage Common subsystems only (DB, disk, Redis) Any business logic or infrastructure you control
Maintenance Handled by Spring Boot updates Your responsibility to keep current
Performance Optimized and fast by default Risk of slowing down /health if poorly implemented
Testability Covered by Spring Boot’s own test suite Must write your own tests
Observability Standard format, integrates with all backends Same format if you extend AbstractHealthIndicator

For metrics, the built-in auto-configuration already gives you JVM memory, GC, thread counts, HTTP request timings, and database connection pool stats without any code. Custom metrics make sense for business-specific measurements: orders processed per minute, internal cache size, how long a multi-step workflow takes. The built-in ones cover the infrastructure layer; custom ones cover your specific domain.

Implementation Examples

Time to look at actual code. If you are following the Spring Boot learning roadmap, this is the level where you move beyond configuration and start instrumenting your own application behavior.

Custom Endpoint with @Endpoint

import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
import org.springframework.boot.actuate.endpoint.annotation.WriteOperation;
import org.springframework.stereotype.Component;

import java.time.Instant;
import java.util.HashMap;
import java.util.Map;

@Component
@Endpoint(id = "application")
public class ApplicationInfoEndpoint {

    private final Instant startTime = Instant.now();

    @ReadOperation
    public Map<String, Object> info() {
        Map<String, Object> info = new HashMap<>();
        info.put("uptimeSeconds", Instant.now().getEpochSecond() - startTime.getEpochSecond());
        info.put("timestamp", Instant.now().toString());
        return info;
    }

    @WriteOperation
    public String restart() {
        // In a real application, this would trigger a graceful restart
        return "Restart initiated";
    }
}

The @Endpoint annotation marks this class as an Actuator endpoint. Methods annotated with @ReadOperation respond to GET requests, @WriteOperation to POST, and @DeleteOperation to DELETE. The id attribute sets the URL path – so this becomes /actuator/application.

Custom Health Indicator

import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.stereotype.Component;

@Component
public class DatabaseHealthIndicator implements HealthIndicator {

    @Override
    public Health health() {
        try {
            // Perform actual database connectivity check
            boolean isConnected = checkDatabaseConnection();
            if (isConnected) {
                return Health.up()
                    .withDetail("database", "PrimaryDB")
                    .withDetail("status", "Connected")
                    .build();
            }
        } catch (Exception e) {
            return Health.down()
                .withDetail("error", e.getMessage())
                .build();
        }
        return Health.unknown().build();
    }

    private boolean checkDatabaseConnection() {
        // Actual DB check implementation
        return true;
    }
}

If you want exception handling handled for you, extend AbstractHealthIndicator instead. One rule that never changes: keep health() fast. No long timeouts here, ever.

Recording Metrics with Micrometer

import io.micrometer.core.annotation.Timed;
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.Gauge;
import io.micrometer.core.instrument.MeterRegistry;
import org.springframework.stereotype.Component;

import java.util.concurrent.atomic.AtomicInteger;

@Component
public class OrderMetrics {

    private final Counter orderCounter;
    private final AtomicInteger pendingOrders;

    public OrderMetrics(MeterRegistry registry) {
        this.orderCounter = Counter.builder("orders.processed")
            .description("Total number of processed orders")
            .tag("service", "order-service")
            .register(registry);

        this.pendingOrders = new AtomicInteger(0);
        Gauge.builder("orders.pending", pendingOrders, AtomicInteger::get)
            .description("Number of pending orders")
            .register(registry);
    }

    @Timed(value = "order.processing.time", description = "Time taken to process an order")
    public void processOrder() {
        pendingOrders.incrementAndGet();
        try {
            // Order processing logic
        } finally {
            pendingOrders.decrementAndGet();
            orderCounter.increment();
        }
    }
}

@Timed records how long the method takes and exports it as a timer metric automatically. @Gauge tracks values that go up and down. @Counter increments in one direction only.

Exporting Health Status as a Metric

import io.micrometer.core.instrument.Gauge;
import io.micrometer.core.instrument.MeterRegistry;
import org.springframework.boot.actuate.health.HealthEndpoint;
import org.springframework.boot.actuate.health.Status;
import org.springframework.context.annotation.Configuration;

@Configuration
public class HealthMetricsExport {

    public HealthMetricsExport(MeterRegistry registry, HealthEndpoint healthEndpoint) {
        Gauge.builder("health.status", healthEndpoint, ep -> getStatusCode(ep.health().getStatus()))
            .strongReference(true)
            .register(registry);
    }

    private int getStatusCode(Status status) {
        return switch (status.getCode()) {
            case "UP" -> 3;
            case "OUT_OF_SERVICE" -> 2;
            case "DOWN" -> 1;
            default -> 0;
        };
    }
}

This bridges health and metrics worlds, so you can alert on health status changes using your existing metrics tooling.

Observability Checklist

A Spring Boot app is properly observable when it covers all three pillars.

Health Checks

  • /actuator/health returns UP when all critical dependencies are reachable
  • Each HealthIndicator is fast (under 100ms) and handles timeouts gracefully
  • Health endpoint is included in Kubernetes readiness probes
  • Liveness probe does not depend on external systems

Metrics

  • JVM memory, GC, and thread metrics are available at /actuator/metrics
  • HTTP request latency and throughput are recorded by default
  • Custom business metrics are registered for key operations
  • No high-cardinality tags are used on metrics (avoid userId, sessionId, requestId as tags)
  • Metrics are exported to your monitoring backend (Prometheus, Datadog, etc.)
  • Alerts are configured for metric thresholds

Traces

  • Distributed tracing is enabled if your service calls other microservices (see the Microservices roadmap for the full picture)
  • Trace IDs are propagated through HTTP headers and message queue metadata
  • Slow requests are traceable back to specific operations

Securing Actuator Endpoints

Spring Security integrates with Actuator through the EndpointRequest matcher. Here is a config that requires authentication for all actuator endpoints and assigns role-based restrictions to specific endpoints.

import org.springframework.boot.actuate.autoconfigure.security.servlet.EndpointRequest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;

import static org.springframework.security.config.Customizer.withDefaults;

@Configuration
@EnableWebSecurity
public class ActuatorSecurityConfig {

    @Bean
    public SecurityFilterChain actuatorSecurityFilterChain(HttpSecurity http) throws Exception {
        http.securityMatcher(EndpointRequest.toAnyEndpoint());
        http.authorizeHttpRequests(requests -> requests
            .requestMatchers(EndpointRequest.to("/health", "/info")).permitAll()
            .requestMatchers(EndpointRequest.to("/prometheus")).hasRole("METRICS_VIEWER")
            .requestMatchers(EndpointRequest.toAnyEndpoint()).hasRole("ENDPOINT_ADMIN")
        );
        http.httpBasic(withDefaults());
        return http.build();
    }
}

A few things worth remembering:

  • /health and /info are publicly accessible by default. This is intentional – load balancers and orchestrators need to reach them without authentication.
  • Always audit /env, /configprops, /heapdump, and /threaddump before enabling them.
  • Use management.endpoints.web.exposure.include and .exclude to explicitly list which endpoints should be reachable over HTTP.
  • Set management.endpoint.env.show-values to never or when-authorized to control whether sensitive values appear in responses.
  • In air-gapped environments, disable actuator web exposure entirely and rely only on JMX. Pair this with a solid API versioning strategy so your services remain manageable without external monitoring.

Common Pitfalls / Anti-Patterns

Metric Naming Inconsistencies. Micrometer recommends dot-separated names: orders.processed, orders.pending, order.processing.time. Mixing camelCase and underscores across your metrics makes dashboards and alerts harder to maintain.

Forgetting the Unit in the Name. Micrometer encodes units in its metadata separately. Do not encode the unit in the metric name itself. orderProcessingTimeMillis redundantly puts the unit in the name. Just use order.processing.time and Micrometer handles the rest.

Slow Health Indicators. The /health endpoint waits for every registered health indicator to respond. If one is slow, the entire endpoint is slow. Set timeouts on any network call inside a HealthIndicator.

Enabling Too Many Endpoints in Production. Setting management.endpoints.web.exposure.include=* in production is an open invitation for information disclosure. Name exactly the endpoints you need.

Missing Metric Tags for Context. http.requests with no tags tells you nothing. Add uri, method, status tags so you can actually filter and group in your dashboards.

Production Failure Scenarios

Actuator endpoints misbehave in ways that are hard to predict until production traffic hits them.

Metrics Endpoint Overload from High Cardinality Tags

Attaching high-cardinality tags like userId, sessionId, or requestId to metrics creates an unbounded explosion of time series in Prometheus. Each unique tag combination becomes a separate metric. Under load with thousands of users, this overwhelms Prometheus storage and can slow your application as the MeterRegistry grows without bound. The fix is strict tag cardinality control: only tag with values that have a small fixed set of possibilities like environment, region, status_code, or endpoint. For user or session-level tracking, use distributed traces or structured logs instead.

Health Endpoint Blocking During Dependency Outage

If a HealthIndicator makes a network call to an external service with a long timeout, /health blocks until that call succeeds or times out. During a genuine outage of that dependency, the health check itself becomes the problem. Your load balancer sees the health endpoint as slow and marks the instance as unhealthy even when the application would recover on its own. The cascade follows: healthy instances get traffic, fail health checks, more traffic shifts to remaining healthy instances, those fail under load, and the system rolls over. Keep health indicators fast and local. Set timeouts in the hundreds of milliseconds. For external dependency checks, use circuit breakers that fail fast rather than waiting.

Endpoint ID Collision Silently Breaking Monitoring

Defining a custom @Endpoint(id = “metrics”) that conflicts with the built-in metrics endpoint does not produce a warning at startup. Spring Boot silently lets your endpoint win some of the time and the built-in win the rest, depending on registration order. You may not notice until your dashboards go blank or your custom endpoint starts receiving traffic meant for the built-in one. Avoid endpoint IDs that overlap with built-ins: health, info, metrics, prometheus, loggers, env, beans, caches, threaddump, heapdump. If you need custom metrics exposure, register them through MeterRegistry rather than as a separate endpoint.

Security Misconfiguration Exposing Sensitive Data

Publishing /env or /configprops without auditing what they contain is an information disclosure waiting to happen. Spring Boot sanitizes known sensitive property names by default, but custom properties with names that do not match the sanitization patterns slip through. An attacker who retrieves the full environment can often find database passwords, API keys, or internal hostnames. Always set management.endpoint.env.show-values to never or when-authorized. Audit /env and /configprops before enabling them in any environment that external traffic can reach. In production, restrict these endpoints to internal networks or disable their web exposure entirely.

Quick Recap Checklist

The essential points about Spring Boot Actuator:

  • Only /health is exposed over HTTP by default. Use management.endpoints.web.exposure.include to whitelist the others.
  • Build custom endpoints with @Endpoint, @ReadOperation, @WriteOperation, and @DeleteOperation.
  • Health indicators implement HealthIndicator – keep them fast and resilient.
  • Micrometer is the facade between your code and your monitoring backend.
  • @Timed, @Gauge, and @Counter let you instrument code with minimal boilerplate.
  • High-cardinality tag values cause memory leaks and can overwhelm your metrics backend. Keep tags bounded.
  • Secure actuator endpoints with Spring Security or network controls before exposing them over HTTP.
  • /env and /configprops can leak sensitive config. Audit them before enabling.

Interview Questions

1. What is the difference between @ReadOperation, @WriteOperation, and @DeleteOperation in Spring Boot Actuator?

These annotations define the HTTP method that maps to a method on an Actuator endpoint. @ReadOperation corresponds to GET requests, making the method callable via HTTP GET at the endpoint URL. @WriteOperation corresponds to POST and is used for operations that modify state, such as clearing a cache or triggering an action. @DeleteOperation corresponds to DELETE and handles removal operations. All three annotations make the method automatically available over both HTTP and JMX, with Actuator handling the adaptation between the two transport mechanisms.

2. How do you prevent a slow HealthIndicator from blocking the entire /health endpoint?

The key is to make the health check fast and resilient. Never call an external service with a long timeout inside a HealthIndicator. Instead, use a short timeout (in the hundreds of milliseconds), catch any exceptions, and return an appropriate status. You can also use a circuit breaker pattern: if an upstream system has failed recently, return DOWN immediately without making a network call. Consider running expensive health checks in a separate thread pool or asynchronously so they do not block the aggregated response from /health.

3. What causes metric cardinality explosion and how do you avoid it?

Cardinality explosion happens when you use tags with many distinct values, such as a userId, sessionId, or requestId tag on a metric. Each unique combination of tag values creates a distinct time series in your metrics backend. If you have 10,000 users and create a metric with a userId tag, you now have 10,000 time series for that one metric. This overwhelms storage systems, slows down queries, and can crash your monitoring backend. To avoid it, only use tags with a small, bounded set of values like environment, service name, region, or HTTP status code. For tracking individual user or request data, use structured logging or distributed traces instead of metrics.

4. How does Micrometer integrate with Spring Boot Actuator, and why is it the preferred metrics library?

Micrometer serves as a facade or abstraction layer over various monitoring backends. Instead of coupling your code to Prometheus, Datadog, or InfluxDB directly, you write against the Micrometer API. Spring Boot Actuator automatically exposes all Micrometer meters through the /actuator/metrics endpoint. Micrometer then binds to whichever backing registry you include on your classpath. This means you can switch monitoring backends by changing dependencies and configuration, without touching your instrumented code. Spring Boot auto-configures a MeterRegistry bean for you, and annotations like @Timed, @Gauge, and @Counted work automatically when Micrometer is present.

5. What security considerations apply to Spring Boot Actuator endpoints in production?

The default security posture is to expose only the /health endpoint over HTTP, which is the right starting point. Before exposing any additional endpoint, ask what information it reveals and who should have access. The /env, /configprops, and /heapdump endpoints are particularly dangerous because they can expose database passwords, API keys, and in-memory data. Always use Spring Security to restrict access with proper authentication and role-based authorization. Consider network-level controls so only internal monitoring systems can reach actuator endpoints. Audit endpoint exposure periodically as new endpoints are added in Spring Boot updates. Use the show-values property on sensitive endpoints to control whether actual values or sanitized placeholders are returned.

6. How does the /health endpoint aggregate results from multiple HealthIndicator beans?

The /health endpoint calls every bean that implements HealthIndicator and aggregates the results into a single response. The overall status is determined by a hierarchical rule: if any indicator returns DOWN, the aggregate is DOWN; if any returns OUT_OF_SERVICE, the aggregate is OUT_OF_SERVICE; otherwise, it is UP. Unknown status items are generally ignored in the aggregate calculation. You can configure this aggregation behavior by implementing HealthAggregator or by using health groups. Spring Boot also exposes sub-statuses in the response body under components, so you can see each indicator individually even though the HTTP status code reflects only the aggregate.

7. What is the difference between building a custom endpoint with @Endpoint versus extending EndpointResponse?

@Endpoint is the standard declarative approach. You annotate a class with @Endpoint(id = "...") and its methods with @ReadOperation, @WriteOperation, or @DeleteOperation. Spring Boot automatically exposes it over both HTTP and JMX. EndpointResponse is a lower-level type used inside operation methods to control the HTTP response status code, headers, and body more precisely. For most use cases, @Endpoint is sufficient. Use EndpointResponse when you need fine-grained control over the HTTP response, such as returning different status codes for different conditions within a single operation method.

8. How do Kubernetes liveness and readiness probes map to Spring Boot Actuator endpoints?

Kubernetes has two probe types that map directly to Actuator endpoints. The liveness probe checks if the process is alive and should not depend on external dependencies -- configure it with management.endpoint.health.probes.enabled=true and map it to /actuator/health/liveness. A failing liveness probe triggers a container restart. The readiness probe checks if the application can handle traffic and should verify critical dependencies -- map it to /actuator/health/readiness. A failing readiness probe removes the pod from Service endpoints. Spring Boot auto-configures both when management.endpoint.health.probes.enabled=true is set. Never make the liveness probe check databases or remote services, because an upstream outage would trigger unnecessary restarts.

9. When should you use @Timed versus manually recording a Timer through the MeterRegistry?

Use @Timed when the timing scope matches a single method and you want zero-boilerplate instrumentation. The annotation is declarative and self-documenting. Use manual Timer.record() calls when timing a multi-step operation where you need to start and stop the timer across different method boundaries, when the metric name or tags are dynamic and cannot be known at compile time, or when you need to record multiple related measurements from a single operation. @Timed also automatically adds method and class name tags, whereas manual recording gives you full control over tag values.

10. How do you configure Actuator health groups for different monitoring scenarios?

Health groups let you define subsets of health indicators for different purposes. Configure them using management.endpoint.health.group.<group-name> properties. For example, management.endpoint.health.group.readiness.include=db,redis creates a group that only checks database and Redis. You can then access /actuator/health/readiness for Kubernetes readiness probes. Groups can also set their own status mapping rules, show-details policy, and rollback rules. This is useful when different consumers of the health endpoint need different subsets of indicators checked.

11. What are the trade-offs between exposing actuator endpoints over HTTP versus JMX only?

HTTP exposure is convenient for external monitoring tools like Prometheus, Datadog agents, or load balancers that make HTTP requests. It works naturally with Kubernetes probes and cloud-native monitoring. The trade-off is that HTTP endpoints are network-reachable and must be secured. JMX is ideal for air-gapped environments or when you only need internal JVM visibility. JMX MBeans work without any network exposure and integrate with JConsole and JVisualVM. The downside is that JMX requires JVM tooling access and does not integrate with standard HTTP-based monitoring pipelines. In practice, many production systems expose a minimal set of HTTP endpoints for monitoring while keeping JMX available for deep diagnostics.

12. How does the Micrometer naming convention work and why should you follow it?

Micrometer recommends lowercase dot-separated names following the pattern <domain>.<subsystem>.<measure>, for example orders.processed.total or http.server.requests.latency. This convention maps correctly to the dimensional data model used by Prometheus, Datadog, and most other backends. Mixing naming styles (camelCase, underscores, Hungarian notation) across a codebase makes dashboard queries error-prone and alert definitions fragile. Micrometer also encodes the measurement unit separately in metadata rather than embedding it in the name, so order.processing.time stores milliseconds as metadata rather than encoding Millis in the name itself.

13. What is the difference between a Counter, a Gauge, and a Timer in Micrometer?

A Counter tracks a value that only increments, useful for counting events like orders placed or errors encountered. A Gauge samples an arbitrary value on demand, tracking something that goes up and down like queue depth, cache size, or active connections -- you provide a callback that Micrometer calls when the meter is sampled. A Timer records duration distributions, computing percentiles, counts, and throughput over a time window for operations like request latency or processing time. Micrometer also has a LongTaskTimer for operations that outlive a single call, useful for tracking batch job progress.

14. How do you handle health indicator ordering and dependencies between subsystems?

By default, Spring Boot calls health indicators in no guaranteed order. Use @Order or implement Ordered on your HealthIndicator beans when the sequence matters. More importantly, model dependency chains carefully: if service A depends on B, do not have A make a live network call to B inside its health check. Instead, track B's status in a local variable updated by events (cache invalidation, message consumption) and check that local state. This prevents a cascade where every health check times out simultaneously during a B outage. Use health group composition to model layered dependencies: a top-level group includes sub-groups for each dependency tier.

15. How does Actuator integrate with distributed tracing systems like Zipkin or Jaeger?

Micrometer integrates with distributed tracing by automatically propagating trace context through HTTP requests and message queue operations when a tracing library like Brave (Zipkin) or OpenTelemetry is on the classpath. Actuator does not directly handle tracing, but Micrometer metrics emitted inside traced operations are automatically tagged with trace and span IDs. This lets you correlate metrics anomalies in your monitoring backend with specific traces in Zipkin or Jaeger. The @Timed annotation is particularly useful here because the recorded duration becomes a span in the trace, giving you latency distributions per endpoint in both the metrics dashboard and the trace explorer.

16. What configuration properties control which actuator endpoints are visible and in what format?

The primary control is management.endpoints.web.exposure.include and .exclude, which whitelist or blacklist endpoints for HTTP exposure. Use management.endpoints.web.base-path to change the URL prefix from /actuator to something else. The management.endpoint.<id>.enabled property enables or disables individual endpoints. For health specifically, management.endpoint.health.show-details controls whether details are always shown, never shown, or only shown to authorized users. management.endpoint.health.probes.enabled activates the liveness and readiness probe endpoints. management.endpoints.enabled-by-default sets the default enabled state for all endpoints if not explicitly overridden.

17. How do you test a custom HealthIndicator or metric without deploying to a live environment?

Spring Boot Test provides @AutoConfigureHealthIndicator for unit testing individual health indicators in isolation. Use @MockBean to mock the external dependency your indicator checks, then verify it returns UP when the mock is healthy and DOWN when it is not. For metrics testing, MicrometerMetric assertions through MeterRegistry tests let you assert meter values directly. @WebMvcTest with @AutoConfigureMockMvc can be used to test that actuator endpoints respond with expected payloads, including custom endpoint responses serialized as JSON. For integration testing, Testcontainers is the standard approach to verify health indicators against real databases or Redis during CI runs.

18. What is the role of management.endpoint.env.show-values and when should you change it?

management.endpoint.env.show-values controls whether Spring Boot returns actual property values or sanitized placeholders in the /env endpoint. The default is when-authorized, meaning only authenticated users with ENDPOINT_ADMIN role see real values. Set it to never to always sanitize, even for authenticated users, as a defense-in-depth measure. Set it to always only in fully trusted internal environments where you want to see real values during debugging. Never set it to always if the /env endpoint is reachable from any network beyond your strict internal monitoring VLAN.

19. How does the @ConditionalOnEnabledEndpoint annotation work and when would you use it?

@ConditionalOnEnabledEndpoint is a Spring Boot annotation that makes a custom endpoint or health indicator conditional on the target endpoint being enabled. For example, if you are building a health indicator for an optional database feature and want it to be registered only when management.endpoint.health.enabled=true, you would annotate the @Bean method with @ConditionalOnEnabledEndpoint(endpoint = HealthEndpoint.class). This follows the same pattern as other Spring Boot conditional annotations and ensures your custom components respect the enabled/disabled configuration for their parent endpoints, rather than being registered independently of the endpoint's configuration state.

20. What are the performance implications of registering many custom metrics and how do you keep the overhead low?

Every meter registered with Micrometer has a small but non-zero overhead at registration time and during metric emission. Registering thousands of metrics with high-cardinality tags (userId, sessionId) amplifies this into a measurable problem because each unique tag combination is a separate time series that must be updated and exported. To keep overhead low, pre-register meters at startup rather than dynamically. Use static tag values with bounded cardinality. Reuse MeterFilter to deny or rename high-cardinality tag combinations before they hit the registry. Prefer a small number of high-information metrics (throughput, latency percentiles, error rate) over a large number of low-information ones. Export only the metrics you actually query in dashboards.

Further Reading

Conclusion

Spring Boot Actuator and Micrometer together form the observability backbone of production Spring Boot applications. Actuator exposes operational endpoints that reveal the internal state of your running application, while Micrometer provides a vendor-neutral API for instrumenting your code with meaningful metrics. The default security posture is conservative – only /health is exposed publicly – which is the right starting point, but every production deployment requires deliberate configuration of which additional endpoints to expose and how to secure them.

The most impactful habits to build: instrument your custom business operations with Micrometer counters, timers, and gauges before production breaks, keep health indicators fast and local to prevent cascading failures during upstream outages, and enforce tag cardinality discipline from the start. High-cardinality tags do not show up in development – they surface as Prometheus OOM events at 3 AM. The investment in proper observability infrastructure pays back immediately when incidents become short and well-understood rather than long and mysterious.

Category

Related Posts

Spring Boot Build Tools: Maven & Gradle

Configure Maven and Gradle for Spring Boot projects—plugins, dependency management, packaging JARs and WARs, and build automation essentials.

#spring-boot #spring-boot-roadmap #learning-path

Embedded Web Servers in Spring Boot: Tomcat, Jetty, Undertow

Configure embedded servers in Spring Boot: compare Tomcat, Jetty, and Undertow, tune thread pools, enable access logs, and switch implementations.

#spring-boot #spring-boot-roadmap #learning-path

JUnit 5 & Jupiter: Lifecycle, Nested & Parameterized Tests

Explore JUnit 5 Jupiter features: master test lifecycle annotations, organize tests with @Nested, and parameterize tests with @CsvSource and @MethodSource.

#spring-boot #spring-boot-roadmap #learning-path