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

operation-invocation.mddocs/

Operation Invocation

QUICK REFERENCE

Key Classes and Packages

// Core Invocation
org.springframework.boot.actuate.endpoint.invoke.OperationInvoker
org.springframework.boot.actuate.endpoint.invoke.reflect.ReflectiveOperationInvoker
org.springframework.boot.actuate.endpoint.InvocationContext

// Parameter Handling
org.springframework.boot.actuate.endpoint.invoke.ParameterValueMapper
org.springframework.boot.actuate.endpoint.invoke.convert.ConversionServiceParameterValueMapper
org.springframework.boot.actuate.endpoint.invoke.OperationParameter
org.springframework.boot.actuate.endpoint.invoke.OperationParameters

// Caching
org.springframework.boot.actuate.endpoint.invoker.cache.CachingOperationInvoker
org.springframework.boot.actuate.endpoint.invoker.cache.CachingOperationInvokerAdvisor

// Argument Resolution
org.springframework.boot.actuate.endpoint.invoke.OperationArgumentResolver

Invocation Flow Diagram

HTTP Request
    ↓
Extract Parameters (URL params, path variables, body)
    ↓
Create InvocationContext (security + arguments)
    ↓
Apply Advisors (logging, caching, etc.)
    ↓
Parameter Value Mapping (String → typed values)
    ↓
Argument Resolution (SecurityContext, etc.)
    ↓
ReflectiveOperationInvoker (method.invoke())
    ↓
Response

Caching Decision Guide

Should operation be cached?
│
├─ Has parameters?
│  └─ NO → Cacheable if configured
│
├─ Is READ operation?
│  └─ NO (WRITE/DELETE) → Never cached
│
├─ Parameters are ApiVersion, SecurityContext, or WebServerNamespace?
│  └─ YES → Cacheable
│
└─ Other parameters?
   └─ NO → Not cacheable

Parameter Type Conversion Support

Source TypeTarget TypesConverter
StringPrimitives (int, long, boolean)Default
StringEnumsDefault
StringOffsetDateTimeIsoOffsetDateTimeConverter
StringLogLevelCustom
AnySame typePassthrough

AGENT GUIDANCE

When to Use Caching

Cache read operations when:

  • No parameters (pure read)
  • Result doesn't change frequently
  • Computation is expensive
  • High request volume
  • Examples: /health, /info, /metrics

Don't cache when:

  • Has query parameters (filtering, pagination)
  • Result changes frequently
  • Write/Delete operations (never!)
  • Real-time data required
  • Examples: /loggers/{name}, /env/{property}

Pattern vs Anti-Pattern

PATTERN: Configure caching per endpoint

// ✓ Selective caching based on endpoint
@Bean
public CachingOperationInvokerAdvisor cachingAdvisor() {
    return new CachingOperationInvokerAdvisor(endpointId -> {
        return switch (endpointId.toLowerCaseString()) {
            case "health" -> 10_000L;  // 10 seconds
            case "info" -> 60_000L;     // 60 seconds
            case "metrics" -> 5_000L;   // 5 seconds
            default -> 0L;              // No caching
        };
    });
}

ANTI-PATTERN: Cache everything

// ❌ Wrong - caches operations that shouldn't be cached
@Bean
public CachingOperationInvokerAdvisor cachingAdvisor() {
    return new CachingOperationInvokerAdvisor(endpointId -> 60_000L);
    // Caches write operations, parameterized reads, etc!
}

PATTERN: Custom parameter converter

// ✓ Add domain-specific converters
@Configuration
public class ConverterConfiguration {
    @Bean
    public ParameterValueMapper parameterValueMapper() {
        DefaultConversionService service = new DefaultConversionService();

        // Add custom converter
        service.addConverter(new StringToLogLevelConverter());

        return new ConversionServiceParameterValueMapper(service);
    }
}

public class StringToLogLevelConverter implements Converter<String, LogLevel> {
    @Override
    public LogLevel convert(String source) {
        return LogLevel.valueOf(source.toUpperCase());
    }
}

ANTI-PATTERN: Manual parameter mapping

// ❌ Manual mapping in operation (should use converter)
@ReadOperation
public void setLogLevel(@Selector String name, String level) {
    LogLevel logLevel = LogLevel.valueOf(level.toUpperCase());
    // Should let framework convert String → LogLevel
}

// ✓ Let framework handle conversion
@ReadOperation
public void setLogLevel(@Selector String name, LogLevel level) {
    // level already converted by framework
}

PATTERN: Argument resolver for custom types

// ✓ Resolve custom types via argument resolver
@Configuration
public class ResolverConfiguration {
    @Bean
    public OperationArgumentResolver requestInfoResolver(HttpServletRequest request) {
        return OperationArgumentResolver.of(
            RequestInfo.class,
            () -> new RequestInfo(request.getRemoteAddr())
        );
    }
}

@ReadOperation
public Data getData(RequestInfo requestInfo) {
    // requestInfo automatically resolved
}

The operation invocation framework provides the infrastructure for invoking endpoint operations with parameter mapping, reflection-based invocation, caching support, and argument resolution.

Capabilities

Core Invocation Types

/**
 * Invokes endpoint operations.
 *
 * Thread-safe: Implementations must be thread-safe
 * Since: Spring Boot 2.0+
 */
@FunctionalInterface
public interface OperationInvoker {

    /**
     * Invoke operation with context.
     *
     * @param context Invocation context (non-null)
     * @return Operation result (nullable)
     * @throws MissingParametersException if required parameters missing
     * @throws ParameterMappingException if parameter mapping fails
     */
    Object invoke(InvocationContext context);
}

/**
 * Advisor that wraps an OperationInvoker to add cross-cutting concerns.
 * Advisors can add functionality like caching, logging, metrics, or security checks.
 *
 * Thread-safe: Implementations must be thread-safe
 * Package: org.springframework.boot.actuate.endpoint.invoke
 * @since 2.0.0
 */
@FunctionalInterface
public interface OperationInvokerAdvisor {

    /**
     * Apply this advisor to an operation invoker.
     * Returns the original invoker if no advice is needed, or a wrapped invoker.
     *
     * @param endpointId the endpoint ID (never null)
     * @param operationType the operation type (never null)
     * @param parameters the operation parameters (never null)
     * @param invoker the operation invoker to wrap (never null)
     * @return the original or wrapped invoker (never null)
     */
    OperationInvoker apply(EndpointId endpointId, OperationType operationType,
                          OperationParameters parameters, OperationInvoker invoker);
}

/**
 * Context for operation invocation.
 *
 * Thread-safe: Immutable after construction
 * Since: Spring Boot 2.0+
 */
public class InvocationContext {

    /**
     * Create invocation context.
     *
     * @param securityContext Security context (non-null)
     * @param arguments Operation arguments (non-null)
     * @param argumentResolvers Additional resolvers (varargs)
     */
    public InvocationContext(SecurityContext securityContext,
                            Map<String, Object> arguments,
                            OperationArgumentResolver... argumentResolvers);

    /**
     * Get operation arguments.
     *
     * @return Immutable map of arguments
     */
    public Map<String, Object> getArguments();

    /**
     * Resolve argument by type.
     *
     * @param argumentType Type to resolve
     * @return Resolved argument or null
     */
    public <T> @Nullable T resolveArgument(Class<T> argumentType);

    /**
     * Check if type can be resolved.
     *
     * @param type Type to check
     * @return true if resolvable
     */
    public boolean canResolve(Class<?> type);
}

Parameter Mapping

/**
 * A collection of {@link OperationParameter operation parameters}.
 *
 * Thread-safe: Implementations should be thread-safe
 * Since: Spring Boot 2.0+
 */
public interface OperationParameters extends Iterable<OperationParameter> {

    /**
     * Return {@code true} if there is at least one parameter.
     *
     * @return if there are parameters
     */
    default boolean hasParameters() {
        return getParameterCount() > 0;
    }

    /**
     * Return the total number of parameters.
     *
     * @return the total number of parameters
     */
    int getParameterCount();

    /**
     * Return if any of the contained parameters are
     * {@link OperationParameter#isMandatory() mandatory}.
     *
     * @return if any parameters are mandatory
     */
    default boolean hasMandatoryParameter() {
        return stream().anyMatch(OperationParameter::isMandatory);
    }

    /**
     * Return the parameter at the specified index.
     *
     * @param index the parameter index
     * @return the parameter (never null)
     */
    OperationParameter get(int index);

    /**
     * Return a stream of the contained parameters.
     *
     * @return a stream of the parameters (never null)
     */
    Stream<OperationParameter> stream();
}

/**
 * Maps parameter values to target types.
 *
 * Thread-safe: Implementations should be thread-safe
 * Since: Spring Boot 2.0+
 */
public interface ParameterValueMapper {

    /**
     * A {@link ParameterValueMapper} that does nothing.
     */
    ParameterValueMapper NONE = (parameter, value) -> value;

    /**
     * Map parameter value to target type.
     *
     * @param parameter Parameter definition (non-null)
     * @param value Value to map (nullable)
     * @return Mapped value
     * @throws ParameterMappingException if mapping fails
     */
    Object mapParameterValue(OperationParameter parameter, @Nullable Object value);
}

/**
 * Parameter mapper using Spring ConversionService.
 *
 * Thread-safe: Yes
 * Since: Spring Boot 2.0+
 */
public class ConversionServiceParameterValueMapper implements ParameterValueMapper {

    /**
     * Create with shared ApplicationConversionService.
     */
    public ConversionServiceParameterValueMapper();

    /**
     * Create with custom conversion service.
     *
     * @param conversionService Conversion service (non-null)
     */
    public ConversionServiceParameterValueMapper(ConversionService conversionService);

    @Override
    public Object mapParameterValue(OperationParameter parameter, @Nullable Object value);
}

Reflective Invocation

/**
 * {@link OperationInvoker} that uses reflection to invoke the underlying operation method.
 * Standard implementation used by the framework for invoking endpoint operations.
 *
 * Thread-safe: Yes (immutable after construction)
 * Since: Spring Boot 2.0+
 */
public class ReflectiveOperationInvoker implements OperationInvoker {

    /**
     * Create a new {@link ReflectiveOperationInvoker} instance.
     *
     * @param target the target object containing the operation method (non-null)
     * @param operationMethod the operation method to invoke (non-null)
     * @param parameterValueMapper the parameter value mapper (non-null)
     */
    public ReflectiveOperationInvoker(Object target,
                                     OperationMethod operationMethod,
                                     ParameterValueMapper parameterValueMapper);

    @Override
    public Object invoke(InvocationContext context);
}

/**
 * A method on an endpoint that can be used as an operation.
 * Wraps a Java {@link Method} with additional metadata about the operation type and parameters.
 *
 * Thread-safe: Immutable
 * Since: Spring Boot 2.0+
 */
public class OperationMethod {

    /**
     * Create a new {@link OperationMethod} instance.
     *
     * @param method the Java method (non-null)
     * @param operationType the type of operation (non-null)
     */
    public OperationMethod(Method method, OperationType operationType);

    /**
     * Return the Java method.
     *
     * @return the method (never null)
     */
    public Method getMethod();

    /**
     * Return the operation type.
     *
     * @return the operation type (never null)
     */
    public OperationType getOperationType();

    /**
     * Return the operation parameters.
     *
     * @return the parameters (never null)
     */
    public OperationParameters getParameters();
}

Caching Support

/**
 * Advisor that adds caching to read operations.
 *
 * Thread-safe: Yes
 * Caching Rules:
 * - Only READ operations
 * - No parameters OR only ApiVersion/SecurityContext/WebServerNamespace
 * - TTL > 0
 *
 * Since: Spring Boot 2.0+
 */
public class CachingOperationInvokerAdvisor implements OperationInvokerAdvisor {

    /**
     * Create caching advisor.
     *
     * @param endpointIdTimeToLive Function mapping endpoint ID to TTL (ms)
     *                            Returns null or 0 for no caching
     */
    public CachingOperationInvokerAdvisor(
            Function<EndpointId, @Nullable Long> endpointIdTimeToLive);

    @Override
    public OperationInvoker apply(EndpointId endpointId,
                                  OperationType operationType,
                                  OperationParameters parameters,
                                  OperationInvoker invoker);
}

/**
 * Advisor for operation invokers.
 *
 * Since: Spring Boot 2.0+
 */
public interface OperationInvokerAdvisor {

    /**
     * Apply advice to invoker.
     *
     * @param endpointId Endpoint ID
     * @param operationType Operation type
     * @param parameters Operation parameters
     * @param invoker Invoker to advise
     * @return Advised invoker (may wrap original)
     */
    OperationInvoker apply(EndpointId endpointId,
                          OperationType operationType,
                          OperationParameters parameters,
                          OperationInvoker invoker);
}

/**
 * {@link OperationInvoker} that caches the result of an operation invocation.
 * Wraps another invoker and caches its result for a configurable time-to-live period.
 * Only caches READ operations without parameters (or with special parameters like ApiVersion).
 * Supports reactive types (Mono, Flux) when Project Reactor is present.
 *
 * Thread-safe: Yes
 * Package: org.springframework.boot.actuate.endpoint.invoker.cache
 * Since: Spring Boot 2.0+
 *
 * Note: This class has a package-private constructor and is typically created by
 * {@link CachingOperationInvokerAdvisor} rather than being instantiated directly.
 */
public class CachingOperationInvoker implements OperationInvoker {

    /**
     * Create caching invoker (package-private constructor).
     * Note: Typically created by framework via CachingOperationInvokerAdvisor.
     *
     * @param invoker delegate invoker (non-null)
     * @param timeToLive cache TTL in milliseconds
     */
    CachingOperationInvoker(OperationInvoker invoker, long timeToLive);

    /**
     * Get the time-to-live for the cache.
     *
     * @return the time-to-live in milliseconds
     */
    public long getTimeToLive();

    /**
     * Determine if caching is applicable for operations with the given parameters.
     * Caching is only applicable if all parameters are PATH parameters (no query parameters).
     *
     * @param parameters Operation parameters to check
     * @return true if caching can be applied, false otherwise
     */
    static boolean isApplicable(OperationParameters parameters);

    @Override
    public Object invoke(InvocationContext context);
}

Argument Resolution

/**
 * Resolves additional arguments for operation invocation.
 * Allows custom argument types to be injected into operation methods beyond standard parameters.
 *
 * Thread-safe: Implementations should be thread-safe
 * Package: org.springframework.boot.actuate.endpoint
 * Since: Spring Boot 2.0+
 */
@FunctionalInterface
public interface OperationArgumentResolver {

    /**
     * Check if this resolver can resolve the given type.
     *
     * @param type Type to resolve
     * @return true if this resolver can resolve the type
     */
    default boolean canResolve(Class<?> type) {
        return false;
    }

    /**
     * Resolve argument of given type.
     *
     * @param type Type to resolve (non-null)
     * @return Resolved argument or null if not resolvable
     */
    <T> @Nullable T resolve(Class<T> type);

    /**
     * Factory method to create resolver for specific type.
     *
     * @param type Type this resolver handles
     * @param supplier Supplier providing the value
     * @return New resolver instance
     */
    static OperationArgumentResolver of(Class<?> type, Supplier<?> supplier);
}

/**
 * Argument resolver for {@link Producible} enum types used in content negotiation.
 * Resolves producible enums based on HTTP Accept header or defaults to the highest ordinal value.
 *
 * Thread-safe: Yes
 * Package: org.springframework.boot.actuate.endpoint
 * Since: Spring Boot 2.5.0
 *
 * Example: Resolving ApiVersion.V2 or ApiVersion.V3 based on client's Accept header.
 */
public class ProducibleOperationArgumentResolver implements OperationArgumentResolver {

    /**
     * Create resolver with supplier for accepted MIME types.
     *
     * @param accepts Supplier returning list of accepted MIME types from Accept header
     *                Returns null or empty list for wildcard (*\/*) or missing Accept header
     */
    public ProducibleOperationArgumentResolver(Supplier<@Nullable List<String>> accepts);

    /**
     * Check if type is a Producible enum.
     *
     * @param type Type to check
     * @return true if type implements both Producible and Enum
     */
    @Override
    public boolean canResolve(Class<?> type);

    /**
     * Resolve Producible enum value based on Accept header.
     *
     * Resolution rules:
     * 1. If Accept header is *\/* or missing: return default value (or highest ordinal)
     * 2. Parse Accept header MIME types
     * 3. Match against enum's getProducedMimeType()
     * 4. Return most recent (highest ordinal) match
     * 5. Return null if no match found
     *
     * @param type Producible enum type
     * @return Resolved enum value or null
     */
    @Override
    public <T> @Nullable T resolve(Class<T> type);
}

COMPLETE WORKING EXAMPLES

Example 1: Custom Caching Configuration

package com.example.actuator;

import org.springframework.boot.actuate.endpoint.invoker.cache.CachingOperationInvokerAdvisor;
import org.springframework.boot.actuate.endpoint.EndpointId;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.Map;

/**
 * Caching configuration for endpoints.
 *
 * Thread-safe: Yes
 * Performance: Reduces computation for cached operations
 * Since: Application 1.0
 */
@Configuration
public class EndpointCachingConfiguration {

    /**
     * Configure caching per endpoint.
     *
     * Caching rules:
     * - Only READ operations without parameters
     * - Cached for configured TTL
     * - Cache key includes SecurityContext if present
     *
     * @return Caching advisor
     */
    @Bean
    public CachingOperationInvokerAdvisor cachingAdvisor() {
        // Define cache TTLs per endpoint (in milliseconds)
        Map<String, Long> cacheTtls = Map.of(
            "health", 10_000L,      // 10 seconds
            "info", 60_000L,        // 60 seconds
            "metrics", 5_000L,      // 5 seconds
            "beans", 30_000L,       // 30 seconds
            "configprops", 60_000L  // 60 seconds
        );

        return new CachingOperationInvokerAdvisor(endpointId ->
            cacheTtls.getOrDefault(endpointId.toLowerCaseString(), 0L)
        );
    }
}

// Configuration in application.yml
/*
management:
  endpoint:
    health:
      cache:
        time-to-live: 10s
    info:
      cache:
        time-to-live: 60s
    metrics:
      cache:
        time-to-live: 5s
*/

Example 2: Custom Parameter Converters

package com.example.actuator;

import org.springframework.boot.actuate.endpoint.invoke.ParameterValueMapper;
import org.springframework.boot.actuate.endpoint.invoke.convert.ConversionServiceParameterValueMapper;
import org.springframework.boot.logging.LogLevel;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.support.DefaultConversionService;

import java.time.OffsetDateTime;
import java.time.format.DateTimeFormatter;

/**
 * Configuration for custom parameter converters.
 *
 * Thread-safe: Yes
 * Since: Application 1.0
 */
@Configuration
public class ParameterMappingConfiguration {

    /**
     * Configure parameter value mapper with custom converters.
     *
     * @return Configured parameter mapper
     */
    @Bean
    public ParameterValueMapper parameterValueMapper() {
        DefaultConversionService conversionService = new DefaultConversionService();

        // Add custom converters
        conversionService.addConverter(new StringToLogLevelConverter());
        conversionService.addConverter(new StringToOffsetDateTimeConverter());
        conversionService.addConverter(new StringToSeverityConverter());

        return new ConversionServiceParameterValueMapper(conversionService);
    }

    /**
     * Converter for log levels (case-insensitive).
     */
    public static class StringToLogLevelConverter implements Converter<String, LogLevel> {

        @Override
        public LogLevel convert(String source) {
            if (source == null || source.isEmpty()) {
                return null;
            }

            try {
                return LogLevel.valueOf(source.toUpperCase());
            } catch (IllegalArgumentException e) {
                throw new IllegalArgumentException(
                    "Invalid log level: " + source +
                    ". Valid values: TRACE, DEBUG, INFO, WARN, ERROR, OFF"
                );
            }
        }
    }

    /**
     * Converter for ISO-8601 date-time strings.
     */
    public static class StringToOffsetDateTimeConverter
            implements Converter<String, OffsetDateTime> {

        @Override
        public OffsetDateTime convert(String source) {
            if (source == null || source.isEmpty()) {
                return null;
            }

            return OffsetDateTime.parse(source, DateTimeFormatter.ISO_OFFSET_DATE_TIME);
        }
    }

    /**
     * Converter for custom severity enum.
     */
    public static class StringToSeverityConverter implements Converter<String, Severity> {

        @Override
        public Severity convert(String source) {
            if (source == null || source.isEmpty()) {
                return Severity.MEDIUM;
            }

            return Severity.valueOf(source.toUpperCase());
        }
    }

    public enum Severity {
        LOW, MEDIUM, HIGH, CRITICAL
    }
}

// Usage in endpoint:
@ReadOperation
public void setLogLevel(
    @Selector String loggerName,
    LogLevel level  // Automatically converted from string
) {
    // level is already a LogLevel enum
}

Example 3: Custom Argument Resolvers

package com.example.actuator;

import org.springframework.boot.actuate.endpoint.invoke.OperationArgumentResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import jakarta.servlet.http.HttpServletRequest;

/**
 * Configuration for custom argument resolvers.
 *
 * Thread-safe: Yes
 * Since: Application 1.0
 */
@Configuration
public class ArgumentResolverConfiguration {

    /**
     * Resolver for RequestInfo (custom type).
     *
     * @return Argument resolver
     */
    @Bean
    public OperationArgumentResolver requestInfoResolver() {
        return OperationArgumentResolver.of(
            RequestInfo.class,
            () -> {
                ServletRequestAttributes attrs =
                    (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();

                if (attrs == null) {
                    return null;
                }

                HttpServletRequest request = attrs.getRequest();
                return new RequestInfo(
                    request.getRemoteAddr(),
                    request.getHeader("User-Agent")
                );
            }
        );
    }

    /**
     * Request information holder.
     */
    public record RequestInfo(String remoteAddress, String userAgent) {}
}

// Usage in endpoint:
@Endpoint(id = "custom")
@Component
public class CustomEndpoint {

    /**
     * Operation with custom argument type.
     *
     * @param requestInfo Automatically resolved by framework
     * @return Data with request info
     */
    @ReadOperation
    public Map<String, Object> getData(RequestInfo requestInfo) {
        return Map.of(
            "remoteAddress", requestInfo.remoteAddress(),
            "userAgent", requestInfo.userAgent(),
            "timestamp", Instant.now()
        );
    }
}

Example 4: Custom Logging Advisor

package com.example.actuator;

import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker;
import org.springframework.boot.actuate.endpoint.invoke.OperationInvokerAdvisor;
import org.springframework.boot.actuate.endpoint.EndpointId;
import org.springframework.boot.actuate.endpoint.OperationType;
import org.springframework.boot.actuate.endpoint.invoke.OperationParameters;
import org.springframework.boot.actuate.endpoint.InvocationContext;
import org.springframework.stereotype.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Advisor that adds logging to all operations.
 *
 * Thread-safe: Yes
 * Performance: Minimal overhead (~1ms)
 * Since: Application 1.0
 */
@Component
public class LoggingOperationInvokerAdvisor implements OperationInvokerAdvisor {

    private static final Logger logger =
        LoggerFactory.getLogger(LoggingOperationInvokerAdvisor.class);

    @Override
    public OperationInvoker apply(EndpointId endpointId,
                                  OperationType operationType,
                                  OperationParameters parameters,
                                  OperationInvoker invoker) {

        // Wrap invoker with logging
        return new LoggingOperationInvoker(invoker, endpointId, operationType);
    }

    /**
     * Invoker wrapper that adds logging.
     */
    private static class LoggingOperationInvoker implements OperationInvoker {

        private final OperationInvoker delegate;
        private final EndpointId endpointId;
        private final OperationType operationType;

        LoggingOperationInvoker(OperationInvoker delegate,
                               EndpointId endpointId,
                               OperationType operationType) {
            this.delegate = delegate;
            this.endpointId = endpointId;
            this.operationType = operationType;
        }

        @Override
        public Object invoke(InvocationContext context) {
            logger.debug("Invoking {} operation on endpoint {}",
                operationType, endpointId);

            long startTime = System.currentTimeMillis();

            try {
                Object result = delegate.invoke(context);

                long duration = System.currentTimeMillis() - startTime;
                logger.debug("Operation completed in {}ms", duration);

                if (duration > 1000) {
                    logger.warn("Slow operation: {} {} took {}ms",
                        operationType, endpointId, duration);
                }

                return result;

            } catch (Exception e) {
                logger.error("Operation failed: {} {} - {}",
                    operationType, endpointId, e.getMessage());
                throw e;
            }
        }
    }
}

TESTING EXAMPLES

Test 1: Parameter Conversion

package com.example.actuator;

import org.junit.jupiter.api.Test;
import org.springframework.boot.logging.LogLevel;
import static org.assertj.core.api.Assertions.*;

class StringToLogLevelConverterTest {

    private final ParameterMappingConfiguration.StringToLogLevelConverter converter =
        new ParameterMappingConfiguration.StringToLogLevelConverter();

    @Test
    void convert_ValidLevel_ReturnsLogLevel() {
        assertThat(converter.convert("DEBUG")).isEqualTo(LogLevel.DEBUG);
        assertThat(converter.convert("debug")).isEqualTo(LogLevel.DEBUG);
        assertThat(converter.convert("INFO")).isEqualTo(LogLevel.INFO);
    }

    @Test
    void convert_InvalidLevel_ThrowsException() {
        assertThatThrownBy(() -> converter.convert("INVALID"))
            .isInstanceOf(IllegalArgumentException.class)
            .hasMessageContaining("Invalid log level");
    }

    @Test
    void convert_Null_ReturnsNull() {
        assertThat(converter.convert(null)).isNull();
    }
}

TROUBLESHOOTING

Common Error: ParameterMappingException

Problem: Parameter conversion fails

Causes:

  1. No converter for parameter type
  2. Invalid parameter value
  3. Wrong parameter format

Solutions:

// Solution 1: Add custom converter
@Bean
public ParameterValueMapper parameterValueMapper() {
    DefaultConversionService service = new DefaultConversionService();
    service.addConverter(new MyCustomConverter());
    return new ConversionServiceParameterValueMapper(service);
}

// Solution 2: Validate input format
@ReadOperation
public void operation(
    @Selector String id,
    OffsetDateTime date  // Expects ISO-8601 format
) {
    // Use: 2024-01-15T10:30:00Z
}

// Solution 3: Make parameter nullable
@ReadOperation
public void operation(@Nullable String optionalParam) {
    // Handles missing parameter
}

Common Error: MissingParametersException

Problem: Required parameter not provided

Causes:

  1. Parameter not in request
  2. Wrong parameter name
  3. Missing @Selector annotation

Solutions:

// Ensure @Selector for path variables
@ReadOperation
public Data get(@Selector String id) {  // <-- @Selector required
    // Maps to /actuator/endpoint/{id}
}

// Make optional parameters nullable
@ReadOperation
public Data search(@Nullable String filter) {
    // filter can be null
}

PERFORMANCE NOTES

Caching Effectiveness

// Caching works for:
// ✓ READ operations without parameters
@ReadOperation
public Data getData() {
    return expensiveComputation(); // Cached
}

// ✓ READ operations with only special parameters
@ReadOperation
public Data getData(SecurityContext ctx) {
    return expensiveComputation(); // Cached per security context
}

// ✗ Not cached:
@ReadOperation
public Data search(String query) {
    return search(query); // Has query parameter - not cached
}

@WriteOperation
public void update(String value) {
    // Write operations never cached
}

Invocation Overhead

// Operation invocation overhead:
// - Parameter mapping: ~0.1-0.5ms
// - Reflection call: ~0.1ms
// - Advisor chain: ~0.1ms per advisor
// - Total: ~0.3-1ms typical

// For high-frequency endpoints, this is negligible
// compared to actual operation execution

Cross-References

  • For endpoint basics: Endpoint Framework
  • For security context: Security Integration
  • For web integration: Web Integration
  • For built-in endpoints: Built-in Endpoints