Spring BootSpring Boot Actuator
Spring Boot

Spring Boot Actuator

Spring Boot Actuator adds production-ready management endpoints to any Spring Boot application. It exposes information about health, metrics, environment, configuration, beans, HTTP traces, and more over HTTP or JMX. Actuator endpoints are the foundation of observability — they feed health checks to Kubernetes, metrics to Prometheus, and configuration data to support teams.

Setup and Configuration

Adding the Actuator starter exposes a set of built-in endpoints immediately. By default only /actuator/health and /actuator/info are exposed over HTTP — all others must be explicitly enabled. Actuator endpoints should be secured so that only internal infrastructure (Kubernetes, Prometheus, ops tooling) can access them.
XML
<!-- pom.xml: -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

<!-- Micrometer Prometheus registry — required for /actuator/prometheus: -->
<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-registry-prometheus</artifactId>
</dependency>

Exposing and Securing Endpoints

Actuator endpoints are powerful — they expose environment variables, bean graphs, configuration properties, and live metrics. Never expose all endpoints on the public-facing port without authentication. The recommended pattern is to run Actuator on a separate management port (e.g. 9090) reachable only from within the cluster, keeping it completely off the public network.
yaml
# application.yml
management:
  server:
    port: 9090                          # separate port — not exposed publicly

  endpoints:
    web:
      base-path: /actuator
      exposure:
        include:                        # whitelist only what is needed
          - health
          - info
          - metrics
          - prometheus
          - loggers                     # change log levels at runtime
          - env                         # view resolved config (ops use)
          - beans                       # view bean graph (ops use)
          - mappings                    # view URL → handler mappings
          - threaddump                  # JVM thread dump
          - heapdump                    # JVM heap dump (on demand)
        exclude:
          - shutdown                    # NEVER expose — kills the JVM

  endpoint:
    health:
      show-details: when-authorized     # full details only to ACTUATOR_ADMIN
      show-components: always
      probes:
        enabled: true                   # /health/liveness, /health/readiness
    loggers:
      enabled: true
    env:
      show-values: when-authorized      # hide secret values by default

  # ── Kubernetes probe paths (Spring Boot 2.3+): ──────────────────────
  health:
    livenessstate:
      enabled: true
    readinessstate:
      enabled: true

# ── Security config — protect actuator endpoints: ────────────────────
# (in SecurityConfig.java)
# http.authorizeHttpRequests(auth -> auth
#     .requestMatchers("/actuator/health/**",
#                      "/actuator/info").permitAll()
#     .requestMatchers("/actuator/**")
#         .hasRole("ACTUATOR_ADMIN")
#     ...

Key Built-in Endpoints

Actuator ships with over twenty built-in endpoints. Each serves a specific operational need. Understanding which endpoint to use for which task prevents wasted time during incidents.
Java
// ── Built-in endpoint reference: ─────────────────────────────────────
//
// HEALTH & READINESS
// GET /actuator/health              → aggregate health status (UP/DOWN)
// GET /actuator/health/liveness     → Kubernetes liveness probe
// GET /actuator/health/readiness    → Kubernetes readiness probe
// GET /actuator/health/{component}  → single component (db, redis, kafka)
//
// METRICS
// GET /actuator/metrics             → list all metric names
// GET /actuator/metrics/{name}      → metric value + tags
//   ?tag=uri:/api/users             → filter by tag
// GET /actuator/prometheus          → all metrics in Prometheus format
//
// CONFIGURATION
// GET /actuator/env                 → all resolved environment properties
// GET /actuator/env/{property}      → single property value
// GET /actuator/configprops         → all @ConfigurationProperties values
// GET /actuator/beans               → all Spring beans in the context
// GET /actuator/mappings            → all URL → controller mappings
//
// LOGGING
// GET  /actuator/loggers            → all logger names and levels
// GET  /actuator/loggers/{name}     → specific logger level
// POST /actuator/loggers/{name}     → change level at runtime (no restart)
//   body: {"configuredLevel":"DEBUG"}
//
// JVM DIAGNOSTICS
// GET  /actuator/threaddump         → all JVM threads and their state
// GET  /actuator/heapdump           → download heap dump (HPROF format)
// GET  /actuator/info               → app version, git commit, build info
//
// HTTP TRACING (requires HttpExchangeRepository bean)
// GET  /actuator/httpexchanges      → last N HTTP request/response pairs

// ── Change log level at runtime (no restart needed): ─────────────────
// curl -X POST http://localhost:9090/actuator/loggers/com.example.order
//      -H "Content-Type: application/json"
//      -d '{"configuredLevel":"DEBUG"}'
//
// → OrderService now logs at DEBUG level until next restart.
// To reset: POST with {"configuredLevel": null}

// ── Query a specific metric with tag filter: ──────────────────────────
// GET /actuator/metrics/http.server.requests
//     ?tag=uri:/api/orders
//     &tag=status:200
//     &tag=method:POST
//
// Response:
// {
//   "name": "http.server.requests",
//   "measurements": [
//     { "statistic": "COUNT",      "value": 1523 },
//     { "statistic": "TOTAL_TIME", "value": 45.2 },
//     { "statistic": "MAX",        "value": 0.834 }
//   ]
// }

/actuator/info — Build and Git Metadata

The /info endpoint is the canonical way to expose service version, build time, and the exact Git commit deployed. This is invaluable during incidents — it tells support teams exactly which version is running without SSH access to the server. Spring Boot auto-populates info from META-INF/build-info.properties and git.properties when the Maven plugins are configured.
XML
<!-- pom.xml — generate build-info.properties at build time: -->
<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <executions>
        <execution>
            <goals>
                <goal>build-info</goal>   <!-- generates build-info.properties -->
                <goal>repackage</goal>
            </goals>
        </execution>
    </executions>
</plugin>

<!-- Generate git.properties at build time: -->
<plugin>
    <groupId>io.github.git-commit-id</groupId>
    <artifactId>git-commit-id-maven-plugin</artifactId>
    <version>9.0.1</version>
    <executions>
        <execution>
            <goals><goal>revision</goal></goals>
        </execution>
    </executions>
    <configuration>
        <generateGitPropertiesFile>true</generateGitPropertiesFile>
        <includeOnlyProperties>
            <includeOnlyProperty>git.branch</includeOnlyProperty>
            <includeOnlyProperty>git.commit.id.abbrev</includeOnlyProperty>
            <includeOnlyProperty>git.commit.message.short</includeOnlyProperty>
            <includeOnlyProperty>git.commit.time</includeOnlyProperty>
        </includeOnlyProperties>
    </configuration>
</plugin>

Custom Actuator Endpoint

Custom endpoints expose operational data specific to your service — cache statistics, feature flag states, queue depths, or circuit breaker summaries. A custom endpoint is a Spring bean annotated with @Endpoint. Methods annotated with @ReadOperation, @WriteOperation, or @DeleteOperation map to HTTP GET, POST, and DELETE respectively.
Java
// ── Custom endpoint — expose feature flag states: ────────────────────
@Component
@Endpoint(id = "featureflags")     // accessible at /actuator/featureflags
public class FeatureFlagsEndpoint {

    private final FeatureFlagService featureFlagService;

    // ── @ReadOperation → HTTP GET /actuator/featureflags: ─────────────
    @ReadOperation
    public Map<String, Object> getAll() {
        return Map.of(
            "flags", featureFlagService.getAllFlags(),
            "totalEnabled", featureFlagService.countEnabled(),
            "checkedAt", Instant.now().toString()
        );
    }

    // ── @ReadOperation with selector → GET /actuator/featureflags/{name}: ─
    @ReadOperation
    public Map<String, Object> getByName(@Selector String name) {
        boolean enabled = featureFlagService.isEnabled(name);
        return Map.of(
            "name", name,
            "enabled", enabled,
            "checkedAt", Instant.now().toString()
        );
    }

    // ── @WriteOperation → POST /actuator/featureflags/{name}: ─────────
    @WriteOperation
    public Map<String, String> toggle(@Selector String name,
                                       boolean enabled) {
        featureFlagService.setEnabled(name, enabled);
        return Map.of(
            "name", name,
            "enabled", String.valueOf(enabled),
            "result", "updated"
        );
    }
}

// ── Custom endpoint — cache statistics: ──────────────────────────────
@Component
@Endpoint(id = "cacheinfo")
@RequiredArgsConstructor
public class CacheInfoEndpoint {

    private final CacheManager cacheManager;

    @ReadOperation
    public Map<String, Object> cacheInfo() {
        Map<String, Object> info = new LinkedHashMap<>();
        cacheManager.getCacheNames().forEach(name -> {
            Cache cache = cacheManager.getCache(name);
            if (cache instanceof CaffeineCache caffeineCache) {
                com.github.benmanes.caffeine.cache.Cache<?, ?> nativeCache =
                    caffeineCache.getNativeCache();
                info.put(name, Map.of(
                    "size",       nativeCache.estimatedSize(),
                    "hitRate",    nativeCache.stats().hitRate(),
                    "missRate",   nativeCache.stats().missRate(),
                    "evictions",  nativeCache.stats().evictionCount()
                ));
            }
        });
        return Map.of("caches", info, "checkedAt", Instant.now());
    }
}