skip to content

How do you add custom data to /actuator/info by writing your own InfoContributor?

level: seniorimportance: should knowfreq 45%

answer

  1. implement InfoContributor, register as @Component bean
  2. builder.withDetail(key, value) / withDetails(map)
  3. InfoEndpoint auto-aggregates all beans
  4. runs on every request — keep cheap, no throws
  5. MapInfoContributor for a static map

basics

~10 s

Create a Spring bean implementing InfoContributor and override contribute(Info.Builder builder), calling builder.withDetail(key, value) to add fields. Because InfoEndpoint aggregates all such beans, your block is merged into the response automatically.

solid answer

~40 s

For dynamic or computed info that static info.* properties can't express, implement InfoContributor. Register the class as a Spring bean (e.g. @Component) and implement contribute(Info.Builder builder), calling builder.withDetail("key", value) — values can be nested Maps/objects and are serialized to JSON. Since InfoEndpoint iterates every InfoContributor bean and merges their Info.Builder output, your contribution appears alongside build/git/env automatically; no registration wiring is needed. Order rarely matters because keys are merged, though colliding top-level keys can overwrite. Keep contribute() cheap and side-effect-free — it runs on every request to /actuator/info — and never expose secrets. Typical uses: feature-flag snapshot, active region, cache stats, or a computed 'healthy since' timestamp.

code

java · 23 lines
java
import org.springframework.boot.actuate.info.Info;
import org.springframework.boot.actuate.info.InfoContributor;
import org.springframework.stereotype.Component;
import java.util.Map;

@Component
public class FeatureFlagInfoContributor implements InfoContributor {

    private final FeatureFlags flags;

    FeatureFlagInfoContributor(FeatureFlags flags) {
        this.flags = flags;
    }

    @Override
    public void contribute(Info.Builder builder) {
        // Merged into /actuator/info under a "features" key
        builder.withDetail("features", Map.of(
            "enabled", flags.enabledNames(),
            "count", flags.enabledNames().size()
        ));
    }
}

go deeper

for a junior

Know a custom class implementing InfoContributor can add fields.

for a middle

Write one with @Component and withDetail, and know it's auto-aggregated.

for a senior

Reason about per-request execution cost, exception safety, key collisions, and MapInfoContributor.

for a principal

Set conventions for what belongs in info, caching strategy, and guardrails against leaking sensitive data.

## Why write one The env contributor handles **static** metadata via `info.*` properties. When you need **dynamic or computed** data — a value read from a bean, a live count, a feature-flag snapshot — you implement `org.springframework.boot.actuate.info.InfoContributor` yourself. ## The mechanics 1. Implement the interface and register it as a bean: ```java @Component public class RegionInfoContributor implements InfoContributor { private final RegionService regionService; RegionInfoContributor(RegionService regionService) { this.regionService = regionService; } @Override public void contribute(Info.Builder builder) { builder.withDetail("deployment", Map.of( "region", regionService.currentRegion(), "activeFeatures", regionService.enabledFeatureFlags() )); } } ``` 2. `InfoEndpoint` (auto-configured by Actuator) collects **all** `InfoContributor` beans and calls `contribute` on each, accumulating into a single `Info.Builder`. Your `deployment` block is merged with `build`, `git`, etc. No extra registration is needed — being a bean is enough. ## Info.Builder API - `withDetail(String key, Object value)` — add one entry (value serialized via Jackson, so nested `Map`/POJO works). - `withDetails(Map<String, Object>)` — add many at once. The builder is per-request; contributions do not persist between calls. ## Execution semantics & gotchas - **Called on every request** to `/actuator/info`. Keep `contribute()` fast, non-blocking, and free of heavy I/O; cache expensive values. - **No exceptions, please** — a contributor that throws can break the whole endpoint response; guard external calls. - **Key collisions** — if two contributors add the same top-level key, the later one can overwrite; namespace your keys. - **Ordering** — contributors implement no guaranteed order by default; if you need deterministic override behavior you can order beans, but prefer unique keys instead. - **Security** — the response is often exposed to operators/monitoring; never place credentials, tokens, or PII in `withDetail`. ## Alternative: MapInfoContributor For a fixed map you can register a `MapInfoContributor(Map<String, Object>)` bean instead of writing a class — handy for simple static-but-code-provided data. ## When to use Reach for a custom `InfoContributor` when the metadata is computed at runtime or sourced from application beans. For plain constants, prefer `info.*` properties (less code, same result).

  • Your custom contributor makes a slow DB call in contribute(). What's the risk and the fix?
    contribute() runs on every /actuator/info request, so the slow call is repeated and can hang the endpoint under load. Cache the value (e.g. refresh periodically) and keep contribute() cheap.
  • How does Spring know to call your contributor without you registering it anywhere special?
    InfoEndpoint is auto-configured with all InfoContributor beans injected as a list; being a Spring bean is sufficient — it iterates them and merges their Info.Builder output.

saying these in an interview costs you the question

  • Manually wiring the contributor into InfoEndpoint (unnecessary — bean injection is automatic)
  • Doing heavy I/O or blocking calls in contribute() without caching
  • Letting contribute() throw and break the endpoint
  • Putting secrets/credentials into withDetail

context