management.endpoints.web.exposure.include

Chooses which Actuator endpoints are published over HTTP; only health is exposed by default, and * exposes everything.

Signature
management.endpoints.web.exposure.include=health,info,metrics
  • Added in Spring Boot Spring Boot 4.1 (verified present)

Parameters

ParameterTypeDescription
$valueoptionalSet<String>= healthComma-separated endpoint IDs such as health, info, metrics, prometheus, loggers, or * for all.
$management.endpoints.web.exposure.excludeoptionalSet<String>Endpoint IDs to leave out. Exclusion wins over inclusion.
$management.endpoint.<id>.accessoptionalAccessnone, read-only or unrestricted: whether the endpoint exists and which operations it allows.
$management.endpoints.web.base-pathoptionalString= /actuatorPrefix for all exposed endpoints.

Return value

n/aThe set of endpoint IDs that are mapped to HTTP under the Actuator base path (/actuator by default). An endpoint must also be accessible for it to answer.

Errors

An endpoint that is not exposed returns 404, which looks the same as a typo in the path.

Actuator ships many endpoints, but adding the starter publishes almost none of them over HTTP. Only health is exposed by default. management.endpoints.web.exposure.include lists the endpoint IDs to map under /actuator; anything not listed answers 404.

management.endpoints.web.exposure.include=health,info,metrics,prometheus

Exposure is not access

Two settings decide whether an endpoint answers. Access (management.endpoint.<id>.access) says whether the endpoint exists and what it may do: none, read-only or unrestricted. Exposure says which technology publishes it, HTTP or JMX. An endpoint must be both accessible and exposed. That is why heapdump and shutdown stay unreachable after include=*: their access is none by default.

Why * is dangerous

include=* is handy locally and a leak in production. env and configprops describe your configuration (values are masked by default, but names and structure still help an attacker), mappings lists every route, threaddump reveals internals, and loggers accepts writes. List the endpoints you use by name and treat the list like a firewall rule. If you start from *, at least add an exclude, which always wins:

management.endpoints.web.exposure.include=*
management.endpoints.web.exposure.exclude=env,configprops,threaddump,heapdump

In YAML, quote the star

* has a meaning in YAML, so write include: "*". An unquoted star is a parse error rather than a wildcard.

Where the endpoints should live

Exposure only controls the mapping. Who can reach the URL is a network and security question. The simplest production setup combines a short exposure list with management.server.port, so the endpoints listen on a port the public load balancer never routes. When they share the application port and Spring Security is present, write rules with EndpointRequest so health stays open for probes and everything else requires an operator role.

A frequent support question, "metrics returns 404", is almost always this property: the endpoint exists but was never exposed.

Example

Example · properties
# Only what operations use.
management.endpoints.web.exposure.include=health,info,metrics,prometheus,loggers

# Never exposed, even if someone widens the list to * later.
management.endpoints.web.exposure.exclude=env,configprops,heapdump,threaddump

# Keep Actuator off the public port.
management.server.port=9090
management.endpoint.health.show-details=when-authorized

When to use it

  • Publishing a Prometheus scrape endpoint without exposing configuration or thread dumps
  • Giving developers every endpoint locally through a dev profile while production stays minimal
  • Exposing a custom operational endpoint, such as feature flags, to an internal admin tool

Frequently asked questions

Why does /actuator/metrics return 404?

Only health is exposed over HTTP by default. Add metrics to management.endpoints.web.exposure.include.

Is management.endpoints.web.exposure.include=* safe?

Not on a public port. It publishes configuration, mappings, thread dumps and writable endpoints. List the endpoints you need, use exclude for the dangerous ones, and keep Actuator on a private management port or behind authentication.

Why can I not reach heapdump even with include=*?

Exposure and access are separate. heapdump and shutdown have access none by default, so they are not available until management.endpoint.<id>.access is changed.

More examples

Everything locally, little in production

The default document is safe; the dev profile opens everything and even shows configuration values. The quotes around * are required in YAML.

Example · yaml
management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics
---
spring:
  config:
    activate:
      on-profile: dev
management:
  endpoints:
    web:
      exposure:
        include: "*"
  endpoint:
    env:
      show-values: always

A custom endpoint that must be exposed to be reachable

The endpoint exists as soon as the bean does, but /actuator/features answers only after features is added to the exposure list. The write operation is a POST, so secure it like the loggers endpoint.

Example · java
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
import org.springframework.boot.actuate.endpoint.annotation.Selector;
import org.springframework.boot.actuate.endpoint.annotation.WriteOperation;
import org.springframework.stereotype.Component;

@Component
@Endpoint(id = "features")
public class FeatureFlagsEndpoint {

    private final Map<String, Boolean> flags = new ConcurrentHashMap<>(Map.of("new-checkout", false));

    @ReadOperation
    Map<String, Boolean> all() {
        return Map.copyOf(flags);
    }

    @WriteOperation
    void set(@Selector String name, boolean enabled) {
        flags.put(name, enabled);
    }
}

Health open, everything else for operators

EndpointRequest matches by endpoint ID, so the rules keep working if the base path or management port changes. Exposure decides what is mapped; these chains decide who may call it. Bearer-token requests are exempt from CSRF checks, so operators can POST to loggers with a token carrying the ops scope. The second chain matters: once you declare a SecurityFilterChain, Boot's default one backs off and any request no chain matches is not secured at all.

Example · java
import org.springframework.boot.security.autoconfigure.actuate.web.servlet.EndpointRequest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
public class ActuatorSecurity {

    // Tokens are validated against spring.security.oauth2.resourceserver.jwt.issuer-uri.
    @Bean
    @Order(1)
    SecurityFilterChain actuator(HttpSecurity http) throws Exception {
        http
            .securityMatcher(EndpointRequest.toAnyEndpoint())
            .authorizeHttpRequests(auth -> auth
                .requestMatchers(EndpointRequest.to("health", "info")).permitAll()
                .anyRequest().hasAuthority("SCOPE_ops"))
            .oauth2ResourceServer(oauth -> oauth.jwt(Customizer.withDefaults()));
        return http.build();
    }

    // Declaring any SecurityFilterChain switches off Boot's default one,
    // so the API needs its own chain or its endpoints would be unprotected.
    @Bean
    @Order(2)
    SecurityFilterChain api(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
            .oauth2ResourceServer(oauth -> oauth.jwt(Customizer.withDefaults()));
        return http.build();
    }
}

Discussion

  • Be the first to comment on this lesson.