or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

conditional-annotations.mdcore-infrastructure.mdendpoint-filtering.mdendpoint-properties.mdindex.mdjmx-endpoints.mdmanagement-endpoints.mdmanagement-server.mdweb-endpoints.md
tile.json

conditional-annotations.mddocs/

Conditional Annotations

Specialized conditional annotations for controlling actuator endpoint and component registration based on endpoint availability, management port configuration, and info contributor enablement.

Imports

import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnAvailableEndpoint;
import org.springframework.boot.actuate.autoconfigure.info.ConditionalOnEnabledInfoContributor;
import org.springframework.boot.actuate.autoconfigure.logging.ConditionalOnEnabledLoggingExport;
import org.springframework.boot.actuate.autoconfigure.web.server.ConditionalOnManagementPort;
import org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration;
import org.springframework.boot.actuate.autoconfigure.web.ManagementContextType;
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementPortType;

Quick Reference

Annotation Quick Lookup

AnnotationChecksPrimary Use
@ConditionalOnAvailableEndpointEndpoint enabled + exposedEndpoint auto-configurations
@ConditionalOnEnabledInfoContributorInfo contributor enabledInfo contributors
@ConditionalOnEnabledLoggingExportLogging export enabledLogging exporters (since 3.4.0)
@ConditionalOnManagementPortManagement port typePort-specific beans
@ManagementContextConfigurationManagement context typeManagement context beans

Most Common Usage

// Endpoint auto-configuration
@AutoConfiguration
@ConditionalOnAvailableEndpoint(endpoint = CustomEndpoint.class)
public class CustomEndpointAutoConfiguration {
    @Bean
    @ConditionalOnMissingBean
    public CustomEndpoint customEndpoint() {
        return new CustomEndpoint();
    }
}

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

// Management port specific
@Bean
@ConditionalOnManagementPort(ManagementPortType.DIFFERENT)
public ManagementSecurityFilter managementSecurityFilter() {
    return new ManagementSecurityFilter();
}

API Reference

@ConditionalOnAvailableEndpoint

/**
 * Condition that checks if endpoint is available (enabled AND exposed)
 * Used extensively in endpoint auto-configurations
 *
 * Evaluation:
 * 1. Endpoint enabled: management.endpoint.<id>.access != NONE
 * 2. Endpoint exposed: management.endpoints.{web|jmx}.exposure.include contains <id>
 */
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Conditional(OnAvailableEndpointCondition.class)
public @interface ConditionalOnAvailableEndpoint {
    Class<?> endpoint() default Void.class;      // Endpoint class to check
    Class<?> value() default Void.class;         // Alias for endpoint
    EndpointExposure[] exposure() default {};    // Technologies to check (WEB, JMX, or both)
}

Properties Checked:

# Endpoint enabled
management.endpoint.<id>.access=UNRESTRICTED|READ_ONLY|NONE

# Endpoint exposed
management.endpoints.web.exposure.include=<id>
management.endpoints.jmx.exposure.include=<id>

Usage:

@AutoConfiguration
@ConditionalOnAvailableEndpoint(endpoint = BeansEndpoint.class)
public class BeansEndpointAutoConfiguration {
    // Only registered if beans endpoint enabled + exposed
}

@Bean
@ConditionalOnAvailableEndpoint(endpoint = InfoEndpoint.class, exposure = EndpointExposure.WEB)
public InfoWebFilter infoWebFilter() {
    // Only if info exposed via web
}

@ConditionalOnEnabledInfoContributor

/**
 * Condition for info contributors
 * Checks: management.info.<name>.enabled
 * Fallback: management.info.defaults.enabled (if USE_DEFAULTS_PROPERTY)
 */
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Conditional(OnEnabledInfoContributorCondition.class)
public @interface ConditionalOnEnabledInfoContributor {
    String value();                                                        // Contributor name
    InfoContributorFallback fallback() default USE_DEFAULTS_PROPERTY;     // Fallback behavior
}

public enum InfoContributorFallback {
    USE_DEFAULTS_PROPERTY,  // Check management.info.defaults.enabled
    DISABLE                 // Disable if not explicitly enabled
}

Property Resolution:

1. Check management.info.<name>.enabled
   ↓ (if not set)
2. If fallback == USE_DEFAULTS_PROPERTY:
      Check management.info.defaults.enabled (default: true)
   If fallback == DISABLE:
      Disable contributor

Properties:

# Per-contributor
management.info.git.enabled=true
management.info.env.enabled=true

# Default fallback
management.info.defaults.enabled=true

Usage:

@Bean
@ConditionalOnEnabledInfoContributor("git")
public GitInfoContributor gitInfoContributor(GitProperties properties) {
    return new GitInfoContributor(properties);
}

@Bean
@ConditionalOnEnabledInfoContributor(value = "custom", fallback = DISABLE)
public CustomInfoContributor customInfoContributor() {
    // Only if management.info.custom.enabled=true (no fallback)
}

@ConditionalOnEnabledLoggingExport

/**
 * Condition for logging exporters
 * Checks if logging export is enabled globally or for a specific exporter
 * Matches if management.logging.export.enabled is true or not configured
 *
 * @since 3.4.0
 */
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnEnabledLoggingExportCondition.class)
public @interface ConditionalOnEnabledLoggingExport {
    String value() default "";  // Name of the logging exporter (optional)
}

Property Resolution:

1. If value() is set (specific exporter name):
   - Check management.logging.export.<name>.enabled
   - If not set, fall back to management.logging.export.enabled

2. If value() is empty (global):
   - Check management.logging.export.enabled (default: true)

Properties:

# Global logging export (default: true)
management.logging.export.enabled=true

# Per-exporter configuration
management.logging.export.otlp.enabled=true
management.logging.export.zipkin.enabled=false

Usage:

// Global logging export check
@Bean
@ConditionalOnEnabledLoggingExport
public LoggingExportConfiguration loggingExportConfiguration() {
    return new LoggingExportConfiguration();
}

// Specific exporter check
@Bean
@ConditionalOnEnabledLoggingExport("otlp")
public OtlpLoggingExporter otlpLoggingExporter() {
    // Only if management.logging.export.otlp.enabled=true
    // Falls back to management.logging.export.enabled if not set
    return new OtlpLoggingExporter();
}

@ConditionalOnManagementPort

/**
 * Condition based on management port type
 */
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Conditional(OnManagementPortCondition.class)
public @interface ConditionalOnManagementPort {
    ManagementPortType value();  // Port type to match
}

public enum ManagementPortType {
    DISABLED,   // management.server.port=-1
    SAME,       // port not set or same as server.port
    DIFFERENT;  // separate port specified

    /**
     * Determines type from environment
     * Logic:
     * - DISABLED: if management.server.port < 0
     * - SAME: if port null OR (server.port null AND mgmt.port == 8080) OR (mgmt.port == server.port)
     * - DIFFERENT: otherwise
     */
    public static ManagementPortType get(Environment environment);
}

Properties:

# Not set or same
management.server.port=${server.port}  # SAME

# Different
management.server.port=8081            # DIFFERENT

# Disabled
management.server.port=-1              # DISABLED

Usage:

@Bean
@ConditionalOnManagementPort(ManagementPortType.SAME)
public SamePortFilter samePortFilter() {
    return new SamePortFilter();
}

@Bean
@ConditionalOnManagementPort(ManagementPortType.DIFFERENT)
public ChildContextInitializer childContextInitializer() {
    return new ChildContextInitializer();
}

@Bean
@ConditionalOnManagementPort(ManagementPortType.DISABLED)
public NoOpManagementConfig noOpConfig() {
    return new NoOpManagementConfig();
}

@ManagementContextConfiguration

/**
 * Configuration specific to management context
 * Registered via META-INF/spring/...ManagementContextConfiguration.imports
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Configuration
public @interface ManagementContextConfiguration {
    ManagementContextType value() default ManagementContextType.ANY;
    boolean proxyBeanMethods() default true;
}

public enum ManagementContextType {
    SAME,   // Same context as application
    CHILD,  // Separate child context (separate port)
    ANY     // Applies to both
}

Registration: META-INF/spring/org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration.imports

com.example.CommonManagementConfiguration
com.example.ChildManagementConfiguration

Usage:

// Applies to both same and child contexts
@ManagementContextConfiguration
public class CommonConfiguration {
    @Bean
    public CommonBean commonBean() {
        return new CommonBean();
    }
}

// Only for child context (separate port)
@ManagementContextConfiguration(ManagementContextType.CHILD)
public class ChildConfiguration {
    @Bean
    public ChildOnlyBean childOnlyBean() {
        return new ChildOnlyBean();
    }
}

// Only for same context
@ManagementContextConfiguration(ManagementContextType.SAME)
public class SameConfiguration {
    @Bean
    public SameOnlyBean sameOnlyBean() {
        return new SameOnlyBean();
    }
}

Extension Points

EndpointExposureOutcomeContributor

/**
 * SPI interface for contributing to endpoint exposure determination
 * Loaded from spring.factories and used by @ConditionalOnAvailableEndpoint
 *
 * If any contributor returns a matching ConditionOutcome, the endpoint is considered exposed
 * Implementations may declare a constructor that accepts an Environment argument
 *
 * @since 3.4.0
 */
public interface EndpointExposureOutcomeContributor {

    /**
     * Determines if the given endpoint is exposed for the given exposure technologies
     * @param endpointId the endpoint ID
     * @param exposures the exposure technologies to check (WEB, JMX, or both)
     * @param message the condition message builder for logging
     * @return a matching ConditionOutcome if endpoint is exposed, or null if contributor does not apply
     */
    @Nullable ConditionOutcome getExposureOutcome(
        EndpointId endpointId,
        Set<EndpointExposure> exposures,
        ConditionMessage.Builder message
    );
}

Registration: META-INF/spring/org.springframework.boot.actuate.autoconfigure.endpoint.condition.EndpointExposureOutcomeContributor.imports

Example Implementation:

public class CustomEndpointExposureContributor implements EndpointExposureOutcomeContributor {

    private final Environment environment;

    // Constructor with Environment is optional but supported
    public CustomEndpointExposureContributor(Environment environment) {
        this.environment = environment;
    }

    @Override
    public ConditionOutcome getExposureOutcome(
            EndpointId endpointId,
            Set<EndpointExposure> exposures,
            ConditionMessage.Builder message) {

        // Custom logic to determine if endpoint should be exposed
        if (endpointId.toString().equals("custom")) {
            boolean exposed = environment.getProperty("custom.endpoint.exposed", Boolean.class, false);
            if (exposed) {
                return ConditionOutcome.match(message.found("custom endpoint").items("exposed"));
            }
            return ConditionOutcome.noMatch(message.didNotFind("custom endpoint").items("not exposed"));
        }

        // Return null if this contributor doesn't apply to this endpoint
        return null;
    }
}

Use Case: Custom exposure logic beyond standard include/exclude patterns, such as:

  • Dynamic exposure based on environment variables
  • Role-based endpoint exposure
  • License-based feature exposure
  • Runtime configuration-based exposure

Base Condition: OnEndpointElementCondition

/**
 * Base class for custom endpoint element conditions
 * Supports per-element and default enablement
 *
 * Property resolution order:
 * 1. <prefix><element-name>.enabled
 * 2. <prefix>defaults.enabled (default: true)
 *
 * @since 2.0.0
 */
public abstract class OnEndpointElementCondition extends SpringBootCondition {

    protected OnEndpointElementCondition(String prefix, Class<? extends Annotation> annotationType);

    protected @Nullable ConditionOutcome getEndpointOutcome(ConditionContext context, String endpointName);
    protected ConditionOutcome getDefaultOutcome(ConditionContext context, AnnotationAttributes annotationAttributes);

    @Override
    public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata);
}

Example: Creating custom condition

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Conditional(OnEnabledMyElementCondition.class)
public @interface ConditionalOnEnabledMyElement {
    String value();
}

class OnEnabledMyElementCondition extends OnEndpointElementCondition {
    OnEnabledMyElementCondition() {
        super("management.myelement.", ConditionalOnEnabledMyElement.class);
    }
}

Properties:

# Specific element
management.myelement.foo.enabled=true

# Default for all
management.myelement.defaults.enabled=true

Conditional Evaluation Patterns

Pattern 1: Endpoint with Auto-Configuration

// Endpoint class
@Endpoint(id = "custom")
public class CustomEndpoint {
    @ReadOperation
    public Map<String, Object> info() {
        return Map.of("status", "ok");
    }
}

// Auto-configuration
@AutoConfiguration
@ConditionalOnAvailableEndpoint(endpoint = CustomEndpoint.class)
public class CustomEndpointAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean
    public CustomEndpoint customEndpoint() {
        return new CustomEndpoint();
    }
}

Properties:

management.endpoint.custom.access=UNRESTRICTED
management.endpoints.web.exposure.include=custom

Pattern 2: Info Contributor

@Component
@ConditionalOnEnabledInfoContributor("app")
public class AppInfoContributor implements InfoContributor {

    @Override
    public void contribute(Info.Builder builder) {
        builder.withDetail("app", Map.of(
            "name", "My App",
            "version", "1.0.0"
        ));
    }
}

Properties:

management.info.app.enabled=true

Pattern 3: Port-Specific Security

@Configuration
public class SecurityConfiguration {

    @Bean
    @ConditionalOnManagementPort(ManagementPortType.SAME)
    public SecurityFilterChain combinedSecurityFilterChain(HttpSecurity http) {
        http.authorizeHttpRequests(auth -> auth
            .requestMatchers("/actuator/**").hasRole("ACTUATOR")
            .anyRequest().authenticated());
        return http.build();
    }

    @Bean
    @ConditionalOnManagementPort(ManagementPortType.DIFFERENT)
    @Order(1)
    public SecurityFilterChain managementSecurityFilterChain(HttpSecurity http) {
        http.securityMatcher(request -> request.getServerPort() == 8081)
            .authorizeHttpRequests(auth -> auth.anyRequest().hasRole("ACTUATOR"));
        return http.build();
    }
}

Combining Conditions

@Configuration
@ConditionalOnClass(SomeLibrary.class)
public class ConditionalConfiguration {

    // Only when metrics endpoint available AND using same port
    @Bean
    @ConditionalOnAvailableEndpoint(endpoint = MetricsEndpoint.class)
    @ConditionalOnManagementPort(ManagementPortType.SAME)
    public MetricsIntegration metricsIntegration() {
        return new MetricsIntegration();
    }

    // Only when using different port AND web exposure
    @Bean
    @ConditionalOnManagementPort(ManagementPortType.DIFFERENT)
    @ConditionalOnAvailableEndpoint(endpoint = MetricsEndpoint.class, exposure = EndpointExposure.WEB)
    public ManagementMetricsFilter managementMetricsFilter() {
        return new ManagementMetricsFilter();
    }
}

Troubleshooting

Issue: Bean Not Registered Despite Conditions

Debug:

logging.level.org.springframework.boot.autoconfigure=DEBUG
logging.level.org.springframework.boot.actuate.autoconfigure=DEBUG

Check:

  1. Endpoint access: management.endpoint.<id>.access
  2. Endpoint exposure: management.endpoints.web.exposure.include
  3. Port configuration: management.server.port
  4. Contributor enablement: management.info.<name>.enabled

Issue: Condition Evaluation Order

Conditions evaluated in this order:

  1. @ConditionalOnClass, @ConditionalOnBean
  2. @ConditionalOnAvailableEndpoint
  3. @ConditionalOnManagementPort
  4. @ConditionalOnProperty
  5. Custom conditions

Issue: Management Context Configuration Not Loaded

Cause: Not registered in imports file

Solution: Add to META-INF/spring/org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration.imports

Testing Conditions

@SpringBootTest(properties = {
    "management.endpoint.custom.access=UNRESTRICTED",
    "management.endpoints.web.exposure.include=custom"
})
class ConditionalTest {

    @Autowired
    private ApplicationContext context;

    @Test
    void customEndpointIsRegistered() {
        assertThat(context.containsBean("customEndpoint")).isTrue();
    }
}

@SpringBootTest(properties = {
    "management.server.port=8081"
})
class ManagementPortTest {

    @Autowired(required = false)
    @Qualifier("managementSecurityFilter")
    private SecurityFilter managementFilter;

    @Test
    void managementFilterIsRegistered() {
        assertThat(managementFilter).isNotNull();
    }
}

Related Documentation

  • Core Infrastructure - Access control
  • Management Endpoints - Individual endpoints
  • Management Server - Port configuration
  • Endpoint Filtering - Exposure filtering