Skip to content

Spring Boot · Actuator & Observability

Spring Boot Actuator Endpoints: Production-Ready Monitoring

7 min read Updated 2026-06-26 Share:

Practice Actuator Endpoints interview questions

The gap between "it compiles" and "it runs in production"

A Spring Boot app that passes its tests still tells you nothing once it's live. Is it healthy? What version is actually deployed? Why is one instance slow at 3 a.m.? Answering those questions is the job of Spring Boot Actuator — a module that bolts production-ready endpoints onto your application so you can monitor and manage it without writing the plumbing yourself. Health checks, metrics, configuration inspection, runtime log control: it's all there, behind conventions Boot already wired up.

The best part is how little it costs to adopt. One dependency and you have an operable service.

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

Auto-configuration registers every endpoint and its infrastructure the moment the starter hits the classpath. You don't annotate anything; you only touch properties when you want to change what's exposed, secure it, or move it around.

The endpoints worth knowing

Actuator publishes a family of endpoints under the /actuator base path. A handful do the heavy lifting in day-to-day operations, and the rest are diagnostics you reach for when something's wrong.

/health        overall status + component health (DB, disk, Redis)
/info          build, git, and custom app metadata
/metrics       Micrometer metrics — JVM, HTTP, datasource, custom
/loggers       view and change log levels at runtime
/env           Environment properties and their sources
/beans         every bean in the context
/mappings      all request mappings
/threaddump    a JVM thread dump
/heapdump      downloads a heap dump file
/httpexchanges the last N HTTP exchanges
/shutdown      graceful shutdown (disabled by default)

/health and /metrics are the two you'll wire into monitoring. /metrics is backed by Micrometer, the vendor-neutral metrics facade that feeds Prometheus, Datadog, CloudWatch, and friends — but that's a topic of its own. For now, just know /actuator/metrics is your window into JVM memory, request latency, and connection-pool gauges.

Enabling versus exposing — two different switches

This trips up almost everyone the first time. Actuator has two independent gates. Enabling decides whether an endpoint exists in the application; exposing decides whether an enabled endpoint is reachable over a transport — HTTP or JMX. An endpoint must be both before you can curl it.

Most endpoints are enabled by default but not web-exposed. In fact, out of the box only /health is reachable over HTTP. That's deliberate: endpoints like /env and /heapdump leak sensitive internals, so Boot ships secure by default and makes you opt in to anything more.

management:
  endpoint:
    shutdown:
      enabled: true            # ENABLE: /shutdown is off by default, switch it on
  endpoints:
    web:
      exposure:
        include: health,info,metrics,loggers   # EXPOSE these over HTTP
        exclude: env                            # exclude always wins over include

You can use include: "*" to expose everything, and it's tempting in development. In production it's a liability unless those endpoints sit behind authentication or on a locked-down port. Prefer an explicit whitelist of the endpoints you actually monitor.

If the default /actuator prefix collides with your app's routes or clashes with a platform convention, rename it:

management:
  endpoints:
    web:
      base-path: /manage       # now /manage/health, /manage/metrics, ...

The /health endpoint and health groups

/health aggregates health indicators — datasource, disk space, message broker — into one overall status: UP, DOWN, OUT_OF_SERVICE, or UNKNOWN. That status maps to an HTTP code (200 for UP, 503 for DOWN), which is exactly what a load balancer or orchestrator probe wants. By default the endpoint shows only the top-level status; the component breakdown is gated by show-details.

management:
  endpoint:
    health:
      show-details: when-authorized   # never (default) | when-authorized | always

Keep it at never or when-authorized in production — a public probe has no business seeing which internal dependency is degraded.

Where health gets genuinely powerful is groups, which bundle a subset of indicators under their own sub-endpoint. The classic use is Kubernetes, where liveness and readiness mean different things. Liveness answers "is the process broken, restart me?"; readiness answers "can I take traffic right now?".

management:
  endpoint:
    health:
      group:
        liveness:
          include: livenessState                  # /actuator/health/liveness
        readiness:
          include: readinessState,db,redis        # /actuator/health/readiness

A flaky database should fail readiness — stop routing traffic until it recovers — but it should never fail liveness, because killing and restarting the pod won't fix the database. Getting this distinction right is the difference between a graceful degradation and a restart storm.

Securing what you expose

Here's the sharp edge: web-exposed actuator endpoints are not secured automatically. If Spring Security isn't on the classpath, an exposed /env, /heapdump, or /shutdown is wide open to anyone who can reach the port. Think about what that means — /env leaks datasource URLs and secrets, /heapdump hands over a snapshot of everything in memory, and /shutdown lets a stranger stop your application. Boot sanitizes known secret keys with ******, but that masking is pattern-based and not something to rely on.

The fix is to treat actuator like an admin console and guard it with Spring Security.

@Bean
SecurityFilterChain actuator(HttpSecurity http) throws Exception {
    http.securityMatcher(EndpointRequest.toAnyEndpoint())      // matches /actuator/**
        .authorizeHttpRequests(reg -> reg
            .requestMatchers(EndpointRequest.to("health")).permitAll() // health stays public
            .anyRequest().hasRole("ADMIN"))                     // everything else: ADMIN
        .httpBasic(withDefaults());
    return http;
}

Practice defense in depth: expose the minimum set, require authentication and roles, and — the simplest big win — put management traffic on its own port.

A separate management port

Set management.server.port and the actuator endpoints move to a different port from your application traffic. Now your network layer can block that port from the public internet entirely while the app's main listener keeps serving users.

server:
  port: 8080                   # application traffic
management:
  server:
    port: 8081                 # actuator endpoints only
    address: 127.0.0.1         # optionally bind to a private interface

Firewalling :8081 is a coarse but extremely effective control — sensitive endpoints simply aren't routable from outside, no matter what's exposed.

Changing log levels without a redeploy

One endpoint earns its keep during incidents more than any other: /loggers. It lists every logger's level and, via a POST, lets you change a level on a running instance. When a bug only reproduces in production, you can raise one package to DEBUG, capture the evidence, and set it back — no redeploy, no restart.

# Raise one package to DEBUG on the live app
curl -X POST localhost:8081/actuator/loggers/com.example.orders \
     -H 'Content-Type: application/json' \
     -d '{"configuredLevel":"DEBUG"}'

Because it mutates the running application, /loggers is a write endpoint — lock it down like any admin action.

When you need your own endpoint

Sometimes the built-ins don't cover an operational need specific to your app — toggling a feature flag, evicting a cache, kicking off a reconciliation job. Actuator lets you add a custom endpoint with @Endpoint and operation annotations, and it slots into the same exposure and security model as the built-ins.

@Component
@Endpoint(id = "features")                  // GET/POST /actuator/features
public class FeatureFlagsEndpoint {

    private final Map<String, Boolean> flags = new ConcurrentHashMap<>();

    @ReadOperation                           // HTTP GET
    public Map<String, Boolean> all() { return flags; }

    @WriteOperation                          // HTTP POST { name, enabled }
    public void set(String name, boolean enabled) { flags.put(name, enabled); }
}

It still has to be exposed via exposure.include to be reachable, and you'll want to secure any write operation. Use @WebEndpoint or @JmxEndpoint if you want to limit it to a single transport.

Recap

Actuator is what makes a Spring Boot service operable: add the starter and you get health, metrics, info, and a toolbox of diagnostics under /actuator. Internalize the two-gate model — enabled means the endpoint exists, exposed means you can reach it — and expose only the minimum you monitor. Lean on /health with groups for liveness and readiness, wire /metrics into Micrometer, and use /loggers to debug live without a redeploy. Above all, respect the security posture: nothing is locked down for you, so authenticate every endpoint but /health, keep /env, /heapdump, and /shutdown off the public internet, and put management on its own internal port. Do that, and your service is ready to run — and be run — in production.

More ways to practice

The self-quiz is live. Join our channel for updates, new content & tech tips.

Join our WhatsApp Channel