or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

audit.mdbuiltin-endpoints.mdendpoint-framework.mdhttp-exchanges.mdindex.mdinfo-contributors.mdjmx-integration.mdmanagement-operations.mdoperation-invocation.mdsanitization.mdsecurity.mdweb-integration.md
tile.json

info-contributors.mddocs/

Info Contributors

QUICK REFERENCE

Key Classes and Packages

// Core Info Types
org.springframework.boot.actuate.info.Info
org.springframework.boot.actuate.info.Info.Builder
org.springframework.boot.actuate.info.InfoContributor
org.springframework.boot.actuate.info.InfoPropertiesInfoContributor
org.springframework.boot.actuate.info.InfoEndpoint

// Built-in Contributors
org.springframework.boot.actuate.info.BuildInfoContributor
org.springframework.boot.actuate.info.GitInfoContributor
org.springframework.boot.actuate.info.JavaInfoContributor
org.springframework.boot.actuate.info.OsInfoContributor
org.springframework.boot.actuate.info.ProcessInfoContributor
org.springframework.boot.actuate.info.EnvironmentInfoContributor
org.springframework.boot.actuate.info.SslInfoContributor

// Helper Contributors
org.springframework.boot.actuate.info.SimpleInfoContributor
org.springframework.boot.actuate.info.MapInfoContributor

Contributor Selection Guide

What information needs exposure?
│
├─ Build metadata (version, artifact, timestamp)?
│  └─ BuildInfoContributor (auto-configured)
│
├─ Git commit info?
│  └─ GitInfoContributor (auto-configured)
│
├─ Runtime environment?
│  ├─ Java version → JavaInfoContributor (auto-configured)
│  ├─ OS details → OsInfoContributor (opt-in)
│  └─ Process PID/uptime → ProcessInfoContributor (opt-in)
│
├─ SSL certificates?
│  └─ SslInfoContributor
│
├─ Static properties?
│  └─ EnvironmentInfoContributor (info.* prefix)
│
└─ Custom data?
   ├─ Single value → SimpleInfoContributor
   ├─ Multiple values → MapInfoContributor
   └─ Dynamic/computed → Custom InfoContributor

Auto-Configuration Warning Matrix

ContributorAuto-Configured?Manual Bean?Configuration Property
JavaInfoContributorYesNOmanagement.info.java.enabled
OsInfoContributorYesNOmanagement.info.os.enabled
ProcessInfoContributorYesNOmanagement.info.process.enabled
EnvironmentInfoContributorYesNOmanagement.info.env.enabled
BuildInfoContributorConditionalNONeeds build-info.properties
GitInfoContributorConditionalNONeeds git.properties
Custom InfoContributorNoYESN/A

Common Pattern Summary

// ✓ Custom contributor
@Component
public class MyInfoContributor implements InfoContributor {
    @Override
    public void contribute(Info.Builder builder) {
        builder.withDetail("custom", data);
    }
}

// ❌ DON'T manually create auto-configured contributors
@Bean
public JavaInfoContributor javaInfoContributor() {  // DON'T DO THIS
    return new JavaInfoContributor();
}

AGENT GUIDANCE

When to Create Custom Contributors

Create custom InfoContributor for:

  • Application-specific metadata
  • Feature flags or configuration
  • Custom version information
  • Dynamic system status
  • Integration health checks
  • License information
  • Team/contact information

Use built-in contributors for:

  • Build information (Maven/Gradle)
  • Git commit details
  • Java runtime information
  • OS and process details
  • Environment properties

Pattern vs Anti-Pattern

PATTERN: Custom contributor

// ✓ Custom data requires custom contributor
@Component
public class FeatureFlagContributor implements InfoContributor {
    private final FeatureFlags features;

    @Override
    public void contribute(Info.Builder builder) {
        builder.withDetail("features", Map.of(
            "newUI", features.isNewUIEnabled(),
            "betaFeatures", features.isBetaEnabled()
        ));
    }
}

ANTI-PATTERN: Manually creating auto-configured contributors

// ❌ WRONG - JavaInfoContributor is auto-configured
@Configuration
public class WrongConfiguration {
    @Bean
    public JavaInfoContributor javaInfoContributor() {
        return new JavaInfoContributor(); // Creates duplicate!
    }
}

// ✓ CORRECT - Use configuration property
management.info.java.enabled=true

PATTERN: Using environment properties

# ✓ Static data via properties
info:
  app:
    name: My Application
    description: Customer Management System
  company:
    name: ACME Corp
    website: https://acme.com

ANTI-PATTERN: Custom contributor for static data

// ❌ Overkill for static data
@Component
public class StaticInfoContributor implements InfoContributor {
    @Override
    public void contribute(Info.Builder builder) {
        builder.withDetail("app", Map.of(
            "name", "My Application" // Should be in application.yml
        ));
    }
}

PATTERN: Structured nested data

// ✓ Well-organized hierarchical structure
builder.withDetail("database", Map.of(
    "vendor", "PostgreSQL",
    "version", "15.2",
    "pool", Map.of(
        "max", 20,
        "active", 5
    )
));

The info contributor framework provides an extensible system for contributing application information to the info endpoint. It includes built-in contributors for build metadata, git information, Java runtime details, OS information, SSL certificates, and custom data.

IMPORTANT: Auto-Configuration

CRITICAL: Most built-in InfoContributors are automatically configured by Spring Boot Actuator and should NOT be manually registered as beans.

Auto-Configured Contributors

The following contributors are automatically registered by Spring Boot:

  • JavaInfoContributor - management.info.java.enabled=true (default: true)
  • OsInfoContributor - management.info.os.enabled=true (default: false)
  • ProcessInfoContributor - management.info.process.enabled=true (default: false)
  • EnvironmentInfoContributor - Always enabled, exposes info.* properties
  • BuildInfoContributor - Auto-configured when META-INF/build-info.properties exists
  • GitInfoContributor - Auto-configured when git.properties exists

Custom Contributors

To add custom information, implement InfoContributor and register as Spring bean:

@Component
public class CustomInfoContributor implements InfoContributor {
    @Override
    public void contribute(Info.Builder builder) {
        builder.withDetail("custom", Map.of("key", "value"));
    }
}

Capabilities

Core Info Types

/**
 * Application information container.
 *
 * Thread-safe: Immutable after build
 * Nullability: Details values can be null
 * Since: Spring Boot 1.4+
 */
public final class Info {

    /**
     * Get all details as map.
     *
     * @return Immutable map of details (never null)
     */
    public Map<String, Object> getDetails();

    /**
     * Get specific detail.
     *
     * @param id Detail identifier
     * @return Detail value or null if not present
     */
    public @Nullable Object get(String id);

    /**
     * Get typed detail.
     *
     * @param id Detail identifier
     * @param type Expected type
     * @return Typed value or null
     * @throws IllegalStateException if value is wrong type
     */
    public <T> @Nullable T get(String id, @Nullable Class<T> type);

    /**
     * Builder for Info instances.
     */
    public static class Builder {

        /**
         * Add single detail.
         *
         * @param key Detail key (non-null)
         * @param value Detail value (nullable)
         * @return This builder
         */
        public Builder withDetail(String key, Object value);

        /**
         * Add multiple details.
         *
         * @param details Map of details (non-null)
         * @return This builder
         */
        public Builder withDetails(Map<String, Object> details);

        /**
         * Build immutable Info.
         *
         * @return Info instance
         */
        public Info build();
    }
}

/**
 * Strategy for contributing info details.
 *
 * Thread-safe: Implementations should be thread-safe
 * Since: Spring Boot 1.4+
 */
@FunctionalInterface
public interface InfoContributor {

    /**
     * Contribute details to Info.Builder.
     *
     * @param builder Builder to contribute to (non-null)
     */
    void contribute(Info.Builder builder);
}

/**
 * Base class for InfoContributors that expose InfoProperties.
 *
 * Provides infrastructure for exposing build metadata and git information
 * with support for SIMPLE and FULL exposure modes. This is an abstract base
 * class that BuildInfoContributor and GitInfoContributor extend to provide
 * consistent property exposure semantics.
 *
 * The class handles property extraction, mode-based filtering, and content
 * post-processing. Subclasses must implement toSimplePropertySource() to
 * define which properties are exposed in SIMPLE mode.
 *
 * Thread-safe: Yes
 * Since: Spring Boot 1.4.0
 *
 * @param <T> the type of InfoProperties to expose (BuildProperties or GitProperties)
 *
 * Example Usage:
 * <pre>
 * // BuildInfoContributor extends this class
 * public class BuildInfoContributor extends InfoPropertiesInfoContributor&lt;BuildProperties&gt; {
 *     public BuildInfoContributor(BuildProperties properties) {
 *         super(properties, Mode.FULL);
 *     }
 *
 *     &#64;Override
 *     protected PropertySource&lt;?&gt; toSimplePropertySource() {
 *         Properties props = new Properties();
 *         copyIfSet(props, "group");
 *         copyIfSet(props, "artifact");
 *         copyIfSet(props, "version");
 *         return new PropertiesPropertySource("build", props);
 *     }
 * }
 * </pre>
 */
public abstract class InfoPropertiesInfoContributor<T extends InfoProperties> implements InfoContributor {

    /**
     * Construct InfoPropertiesInfoContributor.
     *
     * @param properties Properties to expose (non-null)
     * @param mode Exposure mode (non-null) - SIMPLE or FULL
     * @throws IllegalArgumentException if properties or mode is null
     */
    protected InfoPropertiesInfoContributor(T properties, Mode mode);

    /**
     * Get the managed properties.
     *
     * @return Properties instance (never null)
     */
    protected final T getProperties();

    /**
     * Get the exposure mode.
     *
     * @return Mode (never null) - either SIMPLE or FULL
     */
    protected final Mode getMode();

    /**
     * Return PropertySource for SIMPLE mode.
     * Subclasses must implement this to define simple mode behavior.
     *
     * In SIMPLE mode, only a predefined set of core properties should be exposed.
     * For example, BuildInfoContributor exposes: group, artifact, name, version, time.
     * GitInfoContributor exposes: branch, commit.id (short), commit.time.
     *
     * @return PropertySource for simple mode (non-null)
     */
    protected abstract PropertySource<?> toSimplePropertySource();

    /**
     * Generate content to contribute.
     * Extracts and post-processes property content based on mode.
     *
     * This method orchestrates the content generation by:
     * 1. Getting the appropriate PropertySource (simple or full)
     * 2. Extracting content from PropertySource
     * 3. Post-processing the content
     *
     * @return Content map (never null, may be empty)
     */
    protected Map<String, Object> generateContent();

    /**
     * Extract raw content from PropertySource.
     *
     * Uses Spring Boot's Binder to convert PropertySource into a nested map structure.
     * The resulting map can contain nested maps for hierarchical properties.
     *
     * @param propertySource Source to extract from (non-null)
     * @return Extracted content (never null, may be empty)
     */
    protected Map<String, Object> extractContent(PropertySource<?> propertySource);

    /**
     * Post-process extracted content.
     * Override to customize content before exposure.
     *
     * Common use cases:
     * - Converting string timestamps to Instant objects
     * - Formatting values for display
     * - Removing or masking sensitive data
     *
     * Default implementation does nothing.
     *
     * @param content Content to modify (non-null) - modified in place
     */
    protected void postProcessContent(Map<String, Object> content);

    /**
     * Get PropertySource based on mode.
     *
     * Returns full PropertySource if mode is FULL, otherwise returns the
     * simple PropertySource from toSimplePropertySource().
     *
     * @return PropertySource (never null)
     */
    protected PropertySource<?> toPropertySource();

    /**
     * Copy property to target if set.
     *
     * Utility method to copy a property from the managed properties to a
     * target Properties object, but only if the property has a non-empty value.
     *
     * @param target Target properties (non-null)
     * @param key Property key (non-null)
     */
    protected void copyIfSet(Properties target, String key);

    /**
     * Replace content value if present and non-null.
     *
     * Utility method to replace a value in the content map only if:
     * 1. The key already exists in the content
     * 2. The new value is not null
     *
     * This is useful in postProcessContent() to replace string values with
     * typed objects (e.g., replacing timestamp strings with Instant objects).
     *
     * @param content Content map (non-null)
     * @param key Key to replace (non-null)
     * @param value New value (nullable) - no replacement if null
     */
    protected void replaceValue(Map<String, Object> content, String key, @Nullable Object value);

    /**
     * Get nested map or empty map.
     *
     * Utility method to safely access nested maps in the content structure.
     * Returns an empty map if the key doesn't exist or the value is null.
     *
     * @param map Parent map (non-null)
     * @param key Nested map key (non-null)
     * @return Nested map or empty (never null)
     * @throws ClassCastException if value exists but is not a Map
     */
    protected Map<String, Object> getNestedMap(Map<String, Object> map, String key);

    /**
     * Defines property exposure mode.
     *
     * The mode controls how much information from InfoProperties is exposed
     * in the info endpoint response.
     */
    public enum Mode {
        /**
         * Expose all properties including custom ones.
         *
         * In FULL mode, all properties from the underlying InfoProperties are exposed,
         * including any custom properties added to build-info.properties or git.properties.
         * This provides complete transparency but may expose more data than desired.
         *
         * GitInfoContributor uses FULL mode when configured with:
         * management.info.git.mode=full
         */
        FULL,

        /**
         * Expose only core predefined properties.
         *
         * In SIMPLE mode, only a well-defined subset of core properties is exposed.
         * This provides a consistent, predictable info structure and avoids exposing
         * potentially sensitive custom properties.
         *
         * GitInfoContributor uses SIMPLE mode by default:
         * management.info.git.mode=simple (default)
         */
        SIMPLE
    }
}

Built-in Info Contributors

Spring Boot Actuator provides several built-in InfoContributor implementations for exposing runtime information.

Property-Based Contributors

These contributors extend InfoPropertiesInfoContributor to expose build and git metadata.

/**
 * Exposes BuildProperties from build-info.properties.
 *
 * Auto-configured when META-INF/build-info.properties exists on the classpath.
 * The build-info.properties file is generated by Spring Boot Maven/Gradle plugins.
 *
 * Exposes information under the "build" key in the info endpoint:
 * - group: Maven/Gradle group ID
 * - artifact: Artifact ID
 * - name: Project name
 * - version: Project version
 * - time: Build timestamp (as Instant)
 * - Any additional custom properties
 *
 * Thread-safe: Yes
 * Auto-configured: Yes (when build-info.properties exists)
 * Configuration: N/A (always uses FULL mode)
 * Since: Spring Boot 1.4.0
 *
 * Example Response:
 * {
 *   "build": {
 *     "group": "com.example",
 *     "artifact": "my-app",
 *     "name": "My Application",
 *     "version": "1.0.0",
 *     "time": "2025-12-19T10:30:00Z"
 *   }
 * }
 */
public class BuildInfoContributor extends InfoPropertiesInfoContributor<BuildProperties> {
    /**
     * Create BuildInfoContributor.
     *
     * @param properties Build properties loaded from build-info.properties (non-null)
     */
    public BuildInfoContributor(BuildProperties properties);

    /**
     * Contribute build information to builder.
     * Adds a "build" detail with all build properties.
     *
     * @param builder Builder to contribute to (non-null)
     */
    @Override
    public void contribute(Info.Builder builder);

    /**
     * Return PropertySource for SIMPLE mode.
     * Includes: group, artifact, name, version, time.
     *
     * @return PropertySource with core build properties (non-null)
     */
    @Override
    protected PropertySource<?> toSimplePropertySource();

    /**
     * Post-process content to convert time string to Instant.
     *
     * @param content Content map to modify (non-null)
     */
    @Override
    protected void postProcessContent(Map<String, Object> content);
}

/**
 * Exposes GitProperties from git.properties.
 *
 * Auto-configured when git.properties exists on the classpath.
 * The git.properties file is generated by git-commit-id-plugin.
 *
 * Supports two modes:
 * - SIMPLE (default): Exposes branch, commit.id (short), commit.time
 * - FULL: Exposes all git properties including custom ones
 *
 * Exposes information under the "git" key in the info endpoint.
 *
 * Thread-safe: Yes
 * Auto-configured: Yes (when git.properties exists)
 * Configuration: management.info.git.mode (simple or full)
 * Since: Spring Boot 1.4.0
 *
 * Example Response (SIMPLE mode):
 * {
 *   "git": {
 *     "branch": "main",
 *     "commit": {
 *       "id": "abc1234",
 *       "time": "2025-12-19T10:15:00Z"
 *     }
 *   }
 * }
 *
 * Example Response (FULL mode):
 * {
 *   "git": {
 *     "branch": "main",
 *     "commit": {
 *       "id": "abc1234567890def",
 *       "id.abbrev": "abc1234",
 *       "time": "2025-12-19T10:15:00Z",
 *       "message": {
 *         "full": "Add new feature",
 *         "short": "Add new feature"
 *       },
 *       "user": {
 *         "name": "John Doe",
 *         "email": "john@example.com"
 *       }
 *     },
 *     "build": {
 *       "time": "2025-12-19T10:30:00Z",
 *       "version": "1.0.0"
 *     }
 *   }
 * }
 */
public class GitInfoContributor extends InfoPropertiesInfoContributor<GitProperties> {
    /**
     * Create GitInfoContributor with default SIMPLE mode.
     *
     * @param properties Git properties loaded from git.properties (non-null)
     */
    public GitInfoContributor(GitProperties properties);

    /**
     * Create GitInfoContributor with specified mode.
     *
     * @param properties Git properties loaded from git.properties (non-null)
     * @param mode Exposure mode (non-null) - SIMPLE or FULL
     */
    public GitInfoContributor(GitProperties properties, Mode mode);

    /**
     * Contribute git information to builder.
     * Adds a "git" detail with git properties based on mode.
     *
     * @param builder Builder to contribute to (non-null)
     */
    @Override
    public void contribute(Info.Builder builder);

    /**
     * Return PropertySource for SIMPLE mode.
     * Includes: branch, commit.id (short), commit.time.
     *
     * @return PropertySource with core git properties (non-null)
     */
    @Override
    protected PropertySource<?> toSimplePropertySource();

    /**
     * Post-process content to convert time strings to Instant objects.
     * Converts commit.time and build.time to Instant.
     *
     * @param content Content map to modify (non-null)
     */
    @Override
    protected void postProcessContent(Map<String, Object> content);
}

Runtime Information Contributors

These contributors expose information about the runtime environment.

/**
 * Contributes Java runtime information.
 *
 * Auto-configured by Spring Boot. Exposes Java version, vendor, and VM details
 * under the "java" key in the info endpoint.
 *
 * Thread-safe: Yes
 * Auto-configured: Yes
 * Configuration: management.info.java.enabled (default: true)
 * Since: Spring Boot 2.6.0
 *
 * Example Response:
 * {
 *   "java": {
 *     "version": "17.0.5",
 *     "vendor": {
 *       "name": "Oracle Corporation",
 *       "version": "17.0.5+9-LTS"
 *     },
 *     "runtime": {
 *       "name": "Java(TM) SE Runtime Environment",
 *       "version": "17.0.5+9-LTS"
 *     },
 *     "jvm": {
 *       "name": "Java HotSpot(TM) 64-Bit Server VM",
 *       "vendor": "Oracle Corporation",
 *       "version": "17.0.5+9-LTS"
 *     }
 *   }
 * }
 */
public class JavaInfoContributor implements InfoContributor {
    /**
     * Create JavaInfoContributor.
     * Automatically discovers Java system properties.
     */
    public JavaInfoContributor();

    /**
     * Contribute Java information to builder.
     * Adds a "java" detail with runtime information.
     *
     * @param builder Builder to contribute to (non-null)
     */
    @Override
    public void contribute(Info.Builder builder);
}

/**
 * Contributes operating system information.
 *
 * Auto-configured by Spring Boot. Exposes OS name, version, and architecture
 * under the "os" key in the info endpoint.
 *
 * Thread-safe: Yes
 * Auto-configured: Yes
 * Configuration: management.info.os.enabled (default: false)
 * Since: Spring Boot 2.7.0
 *
 * Example Response:
 * {
 *   "os": {
 *     "name": "Linux",
 *     "version": "5.15.0-58-generic",
 *     "arch": "amd64"
 *   }
 * }
 */
public class OsInfoContributor implements InfoContributor {
    /**
     * Create OsInfoContributor.
     * Automatically discovers OS system properties.
     */
    public OsInfoContributor();

    /**
     * Contribute OS information to builder.
     * Adds an "os" detail with operating system information.
     *
     * @param builder Builder to contribute to (non-null)
     */
    @Override
    public void contribute(Info.Builder builder);
}

/**
 * Contributes process information including PID and uptime.
 *
 * Auto-configured by Spring Boot. Exposes current process ID and uptime
 * under the "process" key in the info endpoint.
 *
 * Thread-safe: Yes
 * Auto-configured: Yes
 * Configuration: management.info.process.enabled (default: false)
 * Since: Spring Boot 3.3.0
 *
 * Example Response:
 * {
 *   "process": {
 *     "pid": 12345,
 *     "uptime": 3600000
 *   }
 * }
 */
public class ProcessInfoContributor implements InfoContributor {
    /**
     * Create ProcessInfoContributor.
     * Automatically captures current process information.
     */
    public ProcessInfoContributor();

    /**
     * Contribute process information to builder.
     * Adds a "process" detail with PID and uptime.
     *
     * @param builder Builder to contribute to (non-null)
     */
    @Override
    public void contribute(Info.Builder builder);
}

/**
 * Contributes SSL certificate information from configured SSL bundles.
 *
 * Exposes SSL certificate details under the "ssl" key in the info endpoint.
 * This includes certificate validity dates, issuer, and subject information.
 *
 * Thread-safe: Yes
 * Auto-configured: Yes (when SSL bundles are configured)
 * Configuration: Requires spring.ssl.bundle.* configuration
 * Since: Spring Boot 3.4.0
 *
 * Example Response:
 * {
 *   "ssl": {
 *     "bundles": {
 *       "my-bundle": {
 *         "validFrom": "2025-01-01T00:00:00Z",
 *         "validUntil": "2026-01-01T00:00:00Z",
 *         "issuer": "CN=Example CA",
 *         "subject": "CN=example.com"
 *       }
 *     }
 *   }
 * }
 */
public class SslInfoContributor implements InfoContributor {
    /**
     * Create SslInfoContributor with SSL information.
     *
     * @param sslInfo SSL information to expose (non-null)
     * @throws IllegalArgumentException if sslInfo is null
     */
    public SslInfoContributor(SslInfo sslInfo);

    /**
     * Contribute SSL information to builder.
     * Adds an "ssl" detail with SSL bundle information.
     *
     * @param builder Builder to contribute to (non-null)
     */
    @Override
    public void contribute(Info.Builder builder);
}

/**
 * Contributes environment properties prefixed with "info.".
 *
 * Auto-configured by Spring Boot. Reads all properties starting with "info."
 * from the environment and exposes them in the info endpoint. This is the
 * primary mechanism for adding static application information.
 *
 * Properties are exposed without the "info." prefix. Nested properties
 * are converted to nested maps.
 *
 * Thread-safe: Yes
 * Auto-configured: Yes (always enabled)
 * Configuration: management.info.env.enabled (default: true)
 * Since: Spring Boot 1.4.0
 *
 * Example Configuration (application.yml):
 * info:
 *   app:
 *     name: My Application
 *     description: Customer Management System
 *     version: 1.0.0
 *   company:
 *     name: ACME Corp
 *     website: https://acme.com
 *
 * Example Response:
 * {
 *   "app": {
 *     "name": "My Application",
 *     "description": "Customer Management System",
 *     "version": "1.0.0"
 *   },
 *   "company": {
 *     "name": "ACME Corp",
 *     "website": "https://acme.com"
 *   }
 * }
 */
public class EnvironmentInfoContributor implements InfoContributor {
    /**
     * Create EnvironmentInfoContributor.
     *
     * @param environment Environment to read properties from (non-null)
     * @throws IllegalArgumentException if environment is null
     */
    public EnvironmentInfoContributor(ConfigurableEnvironment environment);

    /**
     * Contribute environment properties to builder.
     * Reads all "info.*" properties and adds them as details.
     *
     * @param builder Builder to contribute to (non-null)
     */
    @Override
    public void contribute(Info.Builder builder);
}

Helper Contributors

Utility contributors for simple use cases.

/**
 * Simple InfoContributor that exposes a single detail with a key-value pair.
 *
 * Useful for programmatically adding static or simple dynamic information
 * without creating a custom InfoContributor implementation.
 *
 * If the detail value is null, no information is contributed (the builder
 * is not modified).
 *
 * Thread-safe: Yes (if detail object is immutable)
 * Auto-configured: No (manual bean registration required)
 * Since: Spring Boot 1.4.0
 *
 * Example Usage:
 * <pre>
 * &#64;Bean
 * public SimpleInfoContributor versionContributor() {
 *     return new SimpleInfoContributor("version", "1.0.0");
 * }
 * </pre>
 *
 * Example Response:
 * {
 *   "version": "1.0.0"
 * }
 */
public class SimpleInfoContributor implements InfoContributor {
    /**
     * Create SimpleInfoContributor.
     *
     * @param prefix Detail key (non-null) - the key under which the detail is exposed
     * @param detail Detail value (nullable) - won't be added if null
     * @throws IllegalArgumentException if prefix is null
     */
    public SimpleInfoContributor(String prefix, @Nullable Object detail);

    /**
     * Contribute single detail to builder.
     * Adds the detail only if the detail value is not null.
     *
     * @param builder Builder to contribute to (non-null)
     */
    @Override
    public void contribute(Info.Builder builder);
}

/**
 * InfoContributor that exposes multiple details from a map.
 *
 * Useful for programmatically adding multiple related pieces of information
 * at once without creating a custom InfoContributor implementation.
 *
 * The map is defensively copied during construction to prevent external
 * modifications. All entries from the map are added as top-level details.
 *
 * Thread-safe: Yes (map is copied on construction)
 * Auto-configured: No (manual bean registration required)
 * Since: Spring Boot 1.4.0
 *
 * Example Usage:
 * <pre>
 * &#64;Bean
 * public MapInfoContributor appInfoContributor() {
 *     Map&lt;String, Object&gt; info = new HashMap&lt;&gt;();
 *     info.put("name", "My Application");
 *     info.put("version", "1.0.0");
 *     info.put("description", "Customer Management");
 *     return new MapInfoContributor(info);
 * }
 * </pre>
 *
 * Example Response:
 * {
 *   "name": "My Application",
 *   "version": "1.0.0",
 *   "description": "Customer Management"
 * }
 */
public class MapInfoContributor implements InfoContributor {
    /**
     * Create MapInfoContributor from existing map.
     * The map is defensively copied to ensure immutability.
     *
     * @param info Map of details to expose (non-null) - copied on construction
     * @throws IllegalArgumentException if info is null
     */
    public MapInfoContributor(Map<String, Object> info);

    /**
     * Contribute all map entries to builder.
     * Adds each map entry as a separate detail.
     *
     * @param builder Builder to contribute to (non-null)
     */
    @Override
    public void contribute(Info.Builder builder);
}

COMPLETE WORKING EXAMPLES

Example 1: Application Metadata Contributor

package com.example.actuator;

import org.springframework.boot.actuate.info.Info;
import org.springframework.boot.actuate.info.InfoContributor;
import org.springframework.stereotype.Component;
import org.springframework.beans.factory.annotation.Value;

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

/**
 * Contributes application runtime metadata.
 *
 * Thread-safe: Yes
 * Auto-discovered: Yes (via @Component)
 * Since: Application 1.0
 */
@Component
public class ApplicationMetadataContributor implements InfoContributor {

    private final String applicationName;
    private final String profile;
    private final Instant startupTime;

    public ApplicationMetadataContributor(
            @Value("${spring.application.name}") String applicationName,
            @Value("${spring.profiles.active:default}") String profile) {
        this.applicationName = applicationName;
        this.profile = profile;
        this.startupTime = Instant.now();
    }

    @Override
    public void contribute(Info.Builder builder) {
        Duration uptime = Duration.between(startupTime, Instant.now());

        builder.withDetail("runtime", Map.of(
            "applicationName", applicationName,
            "profile", profile,
            "startupTime", startupTime.toString(),
            "uptime", formatDuration(uptime)
        ));
    }

    private String formatDuration(Duration duration) {
        long hours = duration.toHours();
        long minutes = duration.toMinutes() % 60;
        long seconds = duration.getSeconds() % 60;
        return String.format("%dh %dm %ds", hours, minutes, seconds);
    }
}

Example 2: Feature Flags Contributor

package com.example.actuator;

import org.springframework.boot.actuate.info.Info;
import org.springframework.boot.actuate.info.InfoContributor;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;

/**
 * Feature flags configuration properties.
 */
@ConfigurationProperties(prefix = "features")
@Component
public class FeatureFlags {

    private boolean newUI = false;
    private boolean betaFeatures = false;
    private boolean experimentalApi = false;
    private Map<String, Boolean> custom = new HashMap<>();

    // Getters and setters
    public boolean isNewUI() { return newUI; }
    public void setNewUI(boolean newUI) { this.newUI = newUI; }

    public boolean isBetaFeatures() { return betaFeatures; }
    public void setBetaFeatures(boolean betaFeatures) { this.betaFeatures = betaFeatures; }

    public boolean isExperimentalApi() { return experimentalApi; }
    public void setExperimentalApi(boolean experimentalApi) { this.experimentalApi = experimentalApi; }

    public Map<String, Boolean> getCustom() { return custom; }
    public void setCustom(Map<String, Boolean> custom) { this.custom = custom; }
}

/**
 * Contributes feature flag status.
 *
 * Thread-safe: Yes
 * Auto-discovered: Yes
 * Since: Application 1.0
 */
@Component
public class FeatureFlagsContributor implements InfoContributor {

    private final FeatureFlags features;

    public FeatureFlagsContributor(FeatureFlags features) {
        this.features = features;
    }

    @Override
    public void contribute(Info.Builder builder) {
        Map<String, Object> featureMap = new HashMap<>();
        featureMap.put("newUI", features.isNewUI());
        featureMap.put("betaFeatures", features.isBetaFeatures());
        featureMap.put("experimentalApi", features.isExperimentalApi());
        featureMap.putAll(features.getCustom());

        builder.withDetail("features", featureMap);
    }
}

// Configuration in application.yml
/*
features:
  new-ui: true
  beta-features: false
  experimental-api: false
  custom:
    darkMode: true
    advancedSearch: false
*/

Example 3: Dynamic Dependencies Status

package com.example.actuator;

import org.springframework.boot.actuate.info.Info;
import org.springframework.boot.actuate.info.InfoContributor;
import org.springframework.stereotype.Component;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.data.redis.core.RedisTemplate;

import java.util.Map;
import java.util.HashMap;

/**
 * Contributes external dependencies status.
 *
 * Thread-safe: Yes
 * Performance: Checks are fast (single query each)
 * Since: Application 1.0
 */
@Component
public class DependenciesInfoContributor implements InfoContributor {

    private final JdbcTemplate jdbcTemplate;
    private final RedisTemplate<String, String> redisTemplate;

    public DependenciesInfoContributor(
            JdbcTemplate jdbcTemplate,
            RedisTemplate<String, String> redisTemplate) {
        this.jdbcTemplate = jdbcTemplate;
        this.redisTemplate = redisTemplate;
    }

    @Override
    public void contribute(Info.Builder builder) {
        Map<String, Object> dependencies = new HashMap<>();

        // Database status
        dependencies.put("database", checkDatabase());

        // Redis status
        dependencies.put("cache", checkRedis());

        builder.withDetail("dependencies", dependencies);
    }

    private Map<String, Object> checkDatabase() {
        try {
            String version = jdbcTemplate.queryForObject(
                "SELECT version()",
                String.class
            );

            return Map.of(
                "status", "UP",
                "version", version != null ? version : "unknown"
            );
        } catch (Exception e) {
            return Map.of(
                "status", "DOWN",
                "error", e.getMessage()
            );
        }
    }

    private Map<String, Object> checkRedis() {
        try {
            String pong = redisTemplate.getConnectionFactory()
                .getConnection()
                .ping();

            return Map.of(
                "status", "UP",
                "response", pong != null ? pong : "unknown"
            );
        } catch (Exception e) {
            return Map.of(
                "status", "DOWN",
                "error", e.getMessage()
            );
        }
    }
}

Example 4: Team Contact Information

package com.example.actuator;

import org.springframework.boot.actuate.info.Info;
import org.springframework.boot.actuate.info.InfoContributor;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.Map;

/**
 * Team contact configuration.
 */
@ConfigurationProperties(prefix = "team")
@Component
public class TeamConfiguration {

    private String name;
    private String email;
    private String slackChannel;
    private List<String> oncall;

    // Getters and setters
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }

    public String getEmail() { return email; }
    public void setEmail(String email) { this.email = email; }

    public String getSlackChannel() { return slackChannel; }
    public void setSlackChannel(String slackChannel) { this.slackChannel = slackChannel; }

    public List<String> getOncall() { return oncall; }
    public void setOncall(List<String> oncall) { this.oncall = oncall; }
}

/**
 * Contributes team contact information.
 *
 * Thread-safe: Yes
 * Since: Application 1.0
 */
@Component
public class TeamInfoContributor implements InfoContributor {

    private final TeamConfiguration team;

    public TeamInfoContributor(TeamConfiguration team) {
        this.team = team;
    }

    @Override
    public void contribute(Info.Builder builder) {
        builder.withDetail("team", Map.of(
            "name", team.getName(),
            "email", team.getEmail(),
            "slackChannel", team.getSlackChannel(),
            "oncall", team.getOncall()
        ));
    }
}

// Configuration in application.yml
/*
team:
  name: Platform Team
  email: platform@example.com
  slack-channel: "#platform-support"
  oncall:
    - alice@example.com
    - bob@example.com
*/

TESTING EXAMPLES

Test 1: Custom Info Contributor

package com.example.actuator;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.BeforeEach;
import org.springframework.boot.actuate.info.Info;
import java.util.Map;
import static org.assertj.core.api.Assertions.*;

class ApplicationMetadataContributorTest {

    private ApplicationMetadataContributor contributor;

    @BeforeEach
    void setUp() {
        contributor = new ApplicationMetadataContributor("test-app", "test");
    }

    @Test
    void contribute_AddsRuntimeInformation() {
        Info.Builder builder = new Info.Builder();

        contributor.contribute(builder);

        Info info = builder.build();
        Map<String, Object> runtime = (Map<String, Object>) info.get("runtime");

        assertThat(runtime).isNotNull();
        assertThat(runtime).containsEntry("applicationName", "test-app");
        assertThat(runtime).containsEntry("profile", "test");
        assertThat(runtime).containsKeys("startupTime", "uptime");
    }
}

Test 2: Feature Flags Contributor

package com.example.actuator;

import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.info.Info;
import java.util.Map;
import static org.assertj.core.api.Assertions.*;

class FeatureFlagsContributorTest {

    @Test
    void contribute_AddsFeatureFlags() {
        FeatureFlags features = new FeatureFlags();
        features.setNewUI(true);
        features.setBetaFeatures(false);

        FeatureFlagsContributor contributor = new FeatureFlagsContributor(features);
        Info.Builder builder = new Info.Builder();

        contributor.contribute(builder);

        Info info = builder.build();
        Map<String, Object> featureMap = (Map<String, Object>) info.get("features");

        assertThat(featureMap).containsEntry("newUI", true);
        assertThat(featureMap).containsEntry("betaFeatures", false);
    }
}

TROUBLESHOOTING

Common Error: Duplicate Information

Problem: Same information appears twice in /actuator/info

Causes:

  1. Manual bean creation for auto-configured contributor
  2. Duplicate property configuration
  3. Multiple contributors with same key

Solutions:

// ❌ WRONG - Don't manually create auto-configured beans
@Bean
public JavaInfoContributor javaInfoContributor() {
    return new JavaInfoContributor(); // Creates duplicate!
}

// ✓ CORRECT - Use configuration property
management.info.java.enabled=true

// Avoid duplicate keys in custom contributors
builder.withDetail("app", ...); // First contributor
builder.withDetail("app", ...); // Second contributor - overwrites!

Common Error: Information Not Showing

Problem: Custom contributor data not in response

Causes:

  1. Contributor not registered as bean
  2. Exception in contribute() method
  3. Endpoint not exposed

Solutions:

// Solution 1: Add @Component
@Component  // <-- Required
public class MyInfoContributor implements InfoContributor { }

// Solution 2: Handle exceptions
@Override
public void contribute(Info.Builder builder) {
    try {
        builder.withDetail("data", computeData());
    } catch (Exception e) {
        builder.withDetail("data", Map.of("error", e.getMessage()));
    }
}

// Solution 3: Expose endpoint
management:
  endpoints:
    web:
      exposure:
        include: info

Common Error: Build/Git Info Missing

Problem: Build or git information not appearing

Causes:

  1. Properties file not generated
  2. File not in classpath
  3. Wrong file location

Solutions:

<!-- Maven: Generate build-info.properties -->
<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <executions>
        <execution>
            <goals>
                <goal>build-info</goal>
            </goals>
        </execution>
    </executions>
</plugin>

<!-- Maven: Generate git.properties -->
<plugin>
    <groupId>pl.project13.maven</groupId>
    <artifactId>git-commit-id-plugin</artifactId>
</plugin>

<!-- Verify files exist:
  - target/classes/META-INF/build-info.properties
  - target/classes/git.properties
-->

PERFORMANCE NOTES

Avoid Expensive Operations

// ❌ Bad - expensive operation on every info request
@Override
public void contribute(Info.Builder builder) {
    // Scans entire database!
    long count = repository.count();
    builder.withDetail("recordCount", count);
}

// ✓ Good - cached or lightweight
@Override
public void contribute(Info.Builder builder) {
    // Use cached value or approximate
    builder.withDetail("recordCount", "~1M");
}

Consider Caching

// For expensive computations, cache the result
@Component
public class CachedInfoContributor implements InfoContributor {

    private volatile Map<String, Object> cachedInfo;
    private volatile Instant lastUpdate;

    @Override
    public void contribute(Info.Builder builder) {
        if (shouldRefresh()) {
            cachedInfo = computeExpensiveInfo();
            lastUpdate = Instant.now();
        }
        builder.withDetail("cached", cachedInfo);
    }

    private boolean shouldRefresh() {
        return cachedInfo == null ||
               lastUpdate.isBefore(Instant.now().minusSeconds(60));
    }

    private Map<String, Object> computeExpensiveInfo() {
        // Expensive operation
        return Map.of("key", "value");
    }
}

Cross-References

  • For endpoint exposure: Built-in Endpoints
  • For endpoint framework: Endpoint Framework
  • For environment properties: Data Sanitization
  • For build configuration: Built-in Endpoints