// Information Endpoints
org.springframework.boot.actuate.info.InfoEndpoint // /actuator/info
org.springframework.boot.actuate.env.EnvironmentEndpoint // /actuator/env
org.springframework.boot.actuate.logging.LoggersEndpoint // /actuator/loggers
org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpoint // /actuator/configprops
// Introspection Endpoints
org.springframework.boot.actuate.beans.BeansEndpoint // /actuator/beans
org.springframework.boot.actuate.scheduling.ScheduledTasksEndpoint // /actuator/scheduledtasks
org.springframework.boot.actuate.web.mappings.MappingsEndpoint // /actuator/mappings
// Audit and Monitoring
org.springframework.boot.actuate.audit.AuditEventsEndpoint // /actuator/auditevents
org.springframework.boot.actuate.web.exchanges.HttpExchangesEndpoint // /actuator/httpexchanges
// Operations
org.springframework.boot.actuate.management.ThreadDumpEndpoint // /actuator/threaddump
org.springframework.boot.actuate.management.HeapDumpWebEndpoint // /actuator/heapdump
org.springframework.boot.actuate.context.ShutdownEndpoint // /actuator/shutdown┌─ What information do you need?
│
├─ Application Metadata
│ ├─ Build info, git commit → InfoEndpoint
│ ├─ Configuration properties → ConfigurationPropertiesReportEndpoint
│ └─ Environment variables → EnvironmentEndpoint
│
├─ Runtime Information
│ ├─ All Spring beans → BeansEndpoint
│ ├─ Request mappings → MappingsEndpoint
│ └─ Scheduled tasks → ScheduledTasksEndpoint
│
├─ Logging
│ ├─ View/change log levels → LoggersEndpoint
│ └─ Download log file → LogFileWebEndpoint
│
├─ Monitoring
│ ├─ Recent HTTP requests → HttpExchangesEndpoint
│ └─ Security events → AuditEventsEndpoint
│
└─ Diagnostics
├─ Thread dump → ThreadDumpEndpoint
├─ Heap dump → HeapDumpWebEndpoint
└─ Startup timeline → StartupEndpoint| Endpoint | Default Enabled | Default Exposed | Sensitive | Use Case |
|---|---|---|---|---|
| info | Yes | Yes | No | Build/version info |
| env | Yes | No | Yes | Config values |
| loggers | Yes | No | Yes | Log level control |
| configprops | Yes | No | Yes | @ConfigurationProperties |
| beans | Yes | No | No | Bean introspection |
| auditevents | Yes | No | Yes | Security auditing |
| httpexchanges | Yes | No | Yes | HTTP monitoring |
| threaddump | Yes | No | Yes | Thread diagnostics |
| heapdump | Yes | No | Yes | Memory analysis |
| shutdown | No | No | Yes | Graceful shutdown |
InfoEndpoint - Use when:
EnvironmentEndpoint - Use when:
LoggersEndpoint - Use when:
ConfigurationPropertiesReportEndpoint - Use when:
BeansEndpoint - Use when:
AuditEventsEndpoint - Use when:
HttpExchangesEndpoint - Use when:
PATTERN: Enable selective exposure
management:
endpoints:
web:
exposure:
include: info,health # ✓ Only expose what's neededANTI-PATTERN: Expose all endpoints
management:
endpoints:
web:
exposure:
include: "*" # ❌ Security riskPATTERN: Sanitize sensitive data
management:
endpoint:
env:
show-values: when-authorized # ✓ Protect credentials
roles: ADMINANTI-PATTERN: Always show values
management:
endpoint:
env:
show-values: always # ❌ Exposes secretsPATTERN: Secure shutdown endpoint
management:
endpoint:
shutdown:
enabled: true # Only if explicitly needed
spring:
security:
user:
roles: ADMIN # ✓ Require authenticationANTI-PATTERN: Unsecured shutdown
management:
endpoint:
shutdown:
enabled: true # ❌ Anyone can shut down app
endpoints:
web:
exposure:
include: shutdown
# No security configured!Production-ready endpoints providing monitoring and management capabilities including application information, environment properties, logger configuration, log file access, audit events, beans, configuration properties, scheduled tasks, thread dumps, heap dumps, HTTP exchanges, request mappings, SBOM, and shutdown. (Note: Health endpoints are in the separate spring-boot-health module.)
@Endpoint(id = "info")
class InfoEndpoint {
InfoEndpoint(List<InfoContributor> infoContributors);
@ReadOperation
Map<String, Object> info();
}@Endpoint(id = "env")
class EnvironmentEndpoint {
EnvironmentEndpoint(Environment environment,
Iterable<SanitizingFunction> sanitizingFunctions,
Show showValues);
@ReadOperation
EnvironmentDescriptor environment(@Nullable String pattern);
@ReadOperation
EnvironmentEntryDescriptor environmentEntry(@Selector String toMatch);
// Descriptor classes
static final class EnvironmentDescriptor implements OperationResponseBody {
List<String> getActiveProfiles();
List<String> getDefaultProfiles();
List<PropertySourceDescriptor> getPropertySources();
}
static final class EnvironmentEntryDescriptor {
@Nullable PropertySummaryDescriptor getProperty();
List<String> getActiveProfiles();
List<String> getDefaultProfiles();
List<PropertySourceEntryDescriptor> getPropertySources();
}
static final class PropertySummaryDescriptor {
PropertySummaryDescriptor(String source, @Nullable Object value);
String getSource();
@Nullable Object getValue();
}
static final class PropertySourceDescriptor {
String getName();
Map<String, PropertyValueDescriptor> getProperties();
}
static final class PropertySourceEntryDescriptor {
String getName();
@Nullable PropertyValueDescriptor getProperty();
}
static final class PropertyValueDescriptor {
@Nullable Object getValue();
@Nullable String getOrigin();
String @Nullable [] getOriginParents();
}
}
@EndpointWebExtension(endpoint = EnvironmentEndpoint.class)
class EnvironmentEndpointWebExtension {
EnvironmentEndpointWebExtension(EnvironmentEndpoint delegate,
Show showValues,
Set<String> roles);
@ReadOperation
EnvironmentDescriptor environment(SecurityContext securityContext,
@Nullable String pattern);
@ReadOperation
WebEndpointResponse<EnvironmentEntryDescriptor> environmentEntry(SecurityContext securityContext,
@Selector String toMatch);
}Note: LogLevel enum referenced below is from org.springframework.boot.logging package (spring-boot-core module), not the actuator module. It defines standard log levels: TRACE, DEBUG, INFO, WARN, ERROR, FATAL, OFF.
@Endpoint(id = "loggers")
class LoggersEndpoint {
LoggersEndpoint(LoggingSystem loggingSystem, LoggerGroups loggerGroups);
@ReadOperation
LoggersDescriptor loggers();
@ReadOperation
@Nullable LoggerLevelsDescriptor loggerLevels(@Selector String name);
@WriteOperation
void configureLogLevel(@Selector String name, @Nullable LogLevel configuredLevel);
// Descriptor classes
static class LoggersDescriptor implements OperationResponseBody {
LoggersDescriptor(@Nullable NavigableSet<LogLevel> levels,
@Nullable Map<String, LoggerLevelsDescriptor> loggers,
@Nullable Map<String, GroupLoggerLevelsDescriptor> groups);
@Nullable NavigableSet<LogLevel> getLevels();
@Nullable Map<String, LoggerLevelsDescriptor> getLoggers();
@Nullable Map<String, GroupLoggerLevelsDescriptor> getGroups();
}
static class LoggerLevelsDescriptor implements OperationResponseBody {
LoggerLevelsDescriptor(@Nullable LogLevel configuredLevel);
/**
* Returns the configured log level as a String.
* Note: While the constructor accepts LogLevel enum, the getter returns the level name as String.
* This allows for custom log level implementations beyond the standard LogLevel enum values.
*
* @return the configured level name, or null if not configured
*/
@Nullable String getConfiguredLevel();
}
static class GroupLoggerLevelsDescriptor extends LoggerLevelsDescriptor {
GroupLoggerLevelsDescriptor(@Nullable LogLevel configuredLevel, List<String> members);
List<String> getMembers();
}
static class SingleLoggerLevelsDescriptor extends LoggerLevelsDescriptor {
SingleLoggerLevelsDescriptor(LoggerConfiguration configuration);
String getEffectiveLevel();
}
}
// Note: LogLevel enum is from org.springframework.boot.logging package (spring-boot-core module)
// The configureLogLevel() method accepts LogLevel values: TRACE, DEBUG, INFO, WARN, ERROR, FATAL, OFF
// However, getConfiguredLevel() returns the level as a String to support custom logging implementations@WebEndpoint(id = "logfile")
class LogFileWebEndpoint {
LogFileWebEndpoint(@Nullable LogFile logFile, @Nullable File externalFile);
@ReadOperation(produces = "text/plain; charset=UTF-8")
@Nullable Resource logFile();
}@Endpoint(id = "beans")
class BeansEndpoint {
BeansEndpoint(ConfigurableApplicationContext context);
@ReadOperation
BeansDescriptor beans();
// Descriptor classes
static final class BeansDescriptor implements OperationResponseBody {
Map<@Nullable String, ContextBeansDescriptor> getContexts();
}
static final class ContextBeansDescriptor {
@Nullable String getParentId();
Map<String, BeanDescriptor> getBeans();
}
static final class BeanDescriptor {
String[] getAliases();
@Nullable String getScope();
@Nullable Class<?> getType();
@Nullable String getResource();
String[] getDependencies();
}
}@Endpoint(id = "configprops")
class ConfigurationPropertiesReportEndpoint implements ApplicationContextAware {
ConfigurationPropertiesReportEndpoint(Iterable<SanitizingFunction> sanitizingFunctions,
Show showValues);
void setApplicationContext(ApplicationContext context) throws BeansException;
@ReadOperation
ConfigurationPropertiesDescriptor configurationProperties();
@ReadOperation
ConfigurationPropertiesDescriptor configurationPropertiesWithPrefix(@Selector String prefix);
// Descriptor classes
static final class ConfigurationPropertiesDescriptor implements OperationResponseBody {
Map<@Nullable String, ContextConfigurationPropertiesDescriptor> getContexts();
}
static final class ContextConfigurationPropertiesDescriptor {
Map<String, ConfigurationPropertiesBeanDescriptor> getBeans();
@Nullable String getParentId();
}
static final class ConfigurationPropertiesBeanDescriptor {
String getPrefix();
Map<String, @Nullable Object> getProperties();
Map<String, Object> getInputs();
}
}
@EndpointWebExtension(endpoint = ConfigurationPropertiesReportEndpoint.class)
class ConfigurationPropertiesReportEndpointWebExtension {
ConfigurationPropertiesReportEndpointWebExtension(ConfigurationPropertiesReportEndpoint delegate,
Show showValues,
Set<String> roles);
@ReadOperation
ConfigurationPropertiesDescriptor configurationProperties(SecurityContext securityContext);
@ReadOperation
WebEndpointResponse<ConfigurationPropertiesDescriptor> configurationPropertiesWithPrefix(
SecurityContext securityContext,
@Selector String prefix);
}
// Bean serialization strategy (package: org.springframework.boot.actuate.context.properties)
@FunctionalInterface
interface BeanSerializer {
/**
* Serialize a bean into a Map for JSON representation.
* Used by ConfigurationPropertiesReportEndpoint to serialize @ConfigurationProperties beans.
*
* @param bean the bean to serialize (may be null)
* @return Map representation of the bean (never null)
*/
Map<String, @Nullable Object> serialize(@Nullable Object bean);
}
class JacksonBeanSerializer implements BeanSerializer {
/**
* Default constructor using Jackson for serialization.
* Automatically configured to:
* - Hide properties starting with '$$' (CGLIB artifacts)
* - Skip self-referential properties
* - Filter to only bindable properties
* - Handle DataSize and other Spring Boot types
*/
JacksonBeanSerializer();
Map<String, @Nullable Object> serialize(@Nullable Object bean);
}@Endpoint(id = "auditevents")
class AuditEventsEndpoint {
AuditEventsEndpoint(AuditEventRepository auditEventRepository);
@ReadOperation
AuditEventsDescriptor events(@Nullable String principal,
@Nullable OffsetDateTime after,
@Nullable String type);
}@Endpoint(id = "httpexchanges")
class HttpExchangesEndpoint {
HttpExchangesEndpoint(HttpExchangeRepository repository);
@ReadOperation
HttpExchangesDescriptor httpExchanges();
static final class HttpExchangesDescriptor implements OperationResponseBody {
List<HttpExchange> getExchanges();
}
}@Endpoint(id = "scheduledtasks")
class ScheduledTasksEndpoint {
ScheduledTasksEndpoint(Collection<ScheduledTaskHolder> scheduledTaskHolders);
@ReadOperation
ScheduledTasksDescriptor scheduledTasks();
}@Endpoint(id = "threaddump")
class ThreadDumpEndpoint {
@ReadOperation
ThreadDumpDescriptor threadDump();
}@WebEndpoint(id = "heapdump", defaultAccess = Access.NONE)
class HeapDumpWebEndpoint {
HeapDumpWebEndpoint();
protected HeapDumpWebEndpoint(long timeout);
@ReadOperation
WebEndpointResponse<Resource> heapDump(@Nullable Boolean live);
}@Endpoint(id = "startup")
class StartupEndpoint {
StartupEndpoint(BufferingApplicationStartup applicationStartup);
@ReadOperation
StartupDescriptor startupSnapshot();
@WriteOperation
StartupDescriptor startup();
}@Endpoint(id = "sbom")
class SbomEndpoint {
SbomEndpoint(SbomProperties properties, ResourceLoader resourceLoader);
@ReadOperation
Sboms sboms();
@ReadOperation
@Nullable Resource sbom(@Selector String id);
}@Endpoint(id = "shutdown", defaultAccess = Access.NONE)
class ShutdownEndpoint implements ApplicationContextAware {
@WriteOperation
ShutdownDescriptor shutdown();
}@Endpoint(id = "mappings")
class MappingsEndpoint {
MappingsEndpoint(Collection<MappingDescriptionProvider> descriptionProviders,
ApplicationContext context);
@ReadOperation
ApplicationMappingsDescriptor mappings();
}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.util.Map;
/**
* Custom info contributor for application metadata.
*
* Thread-safe: Yes
* Auto-discovered: Yes (via @Component)
* Since: Application 1.0
*/
@Component
public class ApplicationInfoContributor implements InfoContributor {
private final String applicationName;
private final String environment;
private final Instant startupTime;
public ApplicationInfoContributor(
@Value("${spring.application.name}") String applicationName,
@Value("${spring.profiles.active:default}") String environment) {
this.applicationName = applicationName;
this.environment = environment;
this.startupTime = Instant.now();
}
@Override
public void contribute(Info.Builder builder) {
builder.withDetail("application", Map.of(
"name", applicationName,
"environment", environment,
"startupTime", startupTime.toString(),
"uptime", java.time.Duration.between(startupTime, Instant.now()).toString()
));
}
}
// Configuration
@Configuration
public class InfoEndpointConfiguration {
/**
* Configure which built-in info contributors to enable.
*/
@Bean
public InfoEndpointAutoConfiguration infoConfig() {
return new InfoEndpointAutoConfiguration();
}
}
// application.yml
/*
management:
endpoints:
web:
exposure:
include: info
info:
java:
enabled: true
os:
enabled: true
info:
app:
name: My Application
description: Customer Management System
*/
// Response: GET /actuator/info
/*
{
"application": {
"name": "customer-service",
"environment": "production",
"startupTime": "2024-01-15T10:00:00Z",
"uptime": "PT2H30M"
},
"java": {
"version": "21.0.1",
"vendor": {
"name": "Oracle Corporation"
}
},
"os": {
"name": "Linux",
"version": "5.15.0-1028-aws",
"arch": "amd64"
},
"app": {
"name": "My Application",
"description": "Customer Management System"
}
}
*/package com.example.actuator;
import org.springframework.boot.actuate.logging.LoggersEndpoint;
import org.springframework.boot.logging.LogLevel;
import org.springframework.stereotype.Service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Service for dynamic log level management.
*
* Thread-safe: Yes (delegates to LoggersEndpoint)
* Nullability: Methods handle null log levels appropriately
* Since: Application 1.0
*/
@Service
public class LogManagementService {
private static final Logger logger = LoggerFactory.getLogger(LogManagementService.class);
private final LoggersEndpoint loggersEndpoint;
public LogManagementService(LoggersEndpoint loggersEndpoint) {
this.loggersEndpoint = loggersEndpoint;
}
/**
* Enable debug logging for specific package temporarily.
*
* @param packageName Package name (e.g., "com.example.service")
* @param durationSeconds Duration in seconds
*/
public void enableDebugLogging(String packageName, long durationSeconds) {
logger.info("Enabling DEBUG logging for {} for {} seconds", packageName, durationSeconds);
// Get current level (returns String)
LoggersEndpoint.LoggerLevelsDescriptor current = loggersEndpoint.loggerLevels(packageName);
String originalLevel = current.getConfiguredLevel();
// Set to DEBUG
loggersEndpoint.configureLogLevel(packageName, LogLevel.DEBUG);
// Schedule restoration (convert String back to LogLevel)
scheduleLogLevelRestoration(packageName,
originalLevel != null ? LogLevel.valueOf(originalLevel) : null,
durationSeconds);
}
/**
* Get current log level for logger.
*
* @param loggerName Logger name
* @return Current log level name as String, or null if not configured
*/
public String getLogLevel(String loggerName) {
LoggersEndpoint.LoggerLevelsDescriptor descriptor = loggersEndpoint.loggerLevels(loggerName);
return descriptor.getConfiguredLevel();
}
/**
* Set log level for logger.
*
* @param loggerName Logger name
* @param level Log level to set
*/
public void setLogLevel(String loggerName, LogLevel level) {
logger.info("Setting log level for {} to {}", loggerName, level);
loggersEndpoint.configureLogLevel(loggerName, level);
}
/**
* Reset logger to default level.
*
* @param loggerName Logger name
*/
public void resetLogLevel(String loggerName) {
logger.info("Resetting log level for {}", loggerName);
loggersEndpoint.configureLogLevel(loggerName, null);
}
private void scheduleLogLevelRestoration(String packageName, LogLevel originalLevel, long durationSeconds) {
// Use ScheduledExecutorService in real implementation
new Thread(() -> {
try {
Thread.sleep(durationSeconds * 1000);
loggersEndpoint.configureLogLevel(packageName, originalLevel);
logger.info("Restored log level for {} to {}", packageName, originalLevel);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}).start();
}
}package com.example.actuator;
import org.springframework.boot.actuate.web.exchanges.HttpExchange;
import org.springframework.boot.actuate.web.exchanges.HttpExchangeRepository;
import org.springframework.stereotype.Service;
import java.time.Duration;
import java.util.*;
import java.util.stream.Collectors;
/**
* Service for analyzing HTTP traffic patterns.
*
* Thread-safe: Yes
* Since: Application 1.0
*/
@Service
public class HttpAnalyticsService {
private final HttpExchangeRepository exchangeRepository;
public HttpAnalyticsService(HttpExchangeRepository exchangeRepository) {
this.exchangeRepository = exchangeRepository;
}
/**
* Get average response time.
*
* @return Average duration in milliseconds
*/
public double getAverageResponseTime() {
List<HttpExchange> exchanges = exchangeRepository.findAll();
return exchanges.stream()
.map(HttpExchange::getTimeTaken)
.filter(Objects::nonNull)
.mapToLong(Duration::toMillis)
.average()
.orElse(0.0);
}
/**
* Get endpoint call counts.
*
* @return Map of endpoints to call counts
*/
public Map<String, Long> getEndpointCallCounts() {
List<HttpExchange> exchanges = exchangeRepository.findAll();
return exchanges.stream()
.collect(Collectors.groupingBy(
ex -> ex.getRequest().getUri().getPath(),
Collectors.counting()
));
}
/**
* Find slow requests (> 1 second).
*
* @return List of slow requests
*/
public List<SlowRequest> findSlowRequests() {
List<HttpExchange> exchanges = exchangeRepository.findAll();
return exchanges.stream()
.filter(ex -> ex.getTimeTaken() != null)
.filter(ex -> ex.getTimeTaken().toMillis() > 1000)
.map(ex -> new SlowRequest(
ex.getRequest().getUri().toString(),
ex.getTimeTaken().toMillis(),
ex.getResponse().getStatus()
))
.collect(Collectors.toList());
}
/**
* Get error rate by status code.
*
* @return Map of status codes to counts
*/
public Map<Integer, Long> getStatusCodeDistribution() {
List<HttpExchange> exchanges = exchangeRepository.findAll();
return exchanges.stream()
.collect(Collectors.groupingBy(
ex -> ex.getResponse().getStatus(),
Collectors.counting()
));
}
/**
* Get requests by remote address.
*
* @return Map of IP addresses to request counts
*/
public Map<String, Long> getRequestsByIpAddress() {
List<HttpExchange> exchanges = exchangeRepository.findAll();
return exchanges.stream()
.filter(ex -> ex.getRequest().getRemoteAddress() != null)
.collect(Collectors.groupingBy(
ex -> ex.getRequest().getRemoteAddress(),
Collectors.counting()
));
}
public record SlowRequest(String uri, long durationMs, int status) {}
}package com.example.actuator;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.BeforeEach;
import org.springframework.boot.actuate.info.Info;
import org.springframework.boot.actuate.info.InfoEndpoint;
import java.util.Map;
import java.util.List;
import static org.assertj.core.api.Assertions.*;
class InfoEndpointTest {
private InfoEndpoint infoEndpoint;
private ApplicationInfoContributor contributor;
@BeforeEach
void setUp() {
contributor = new ApplicationInfoContributor("test-app", "test");
infoEndpoint = new InfoEndpoint(List.of(contributor));
}
@Test
void info_ContainsCustomContribution() {
Map<String, Object> info = infoEndpoint.info();
assertThat(info).containsKey("application");
@SuppressWarnings("unchecked")
Map<String, Object> appInfo = (Map<String, Object>) info.get("application");
assertThat(appInfo).containsEntry("name", "test-app");
assertThat(appInfo).containsEntry("environment", "test");
assertThat(appInfo).containsKey("startupTime");
assertThat(appInfo).containsKey("uptime");
}
}package com.example.actuator;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.BeforeEach;
import org.springframework.boot.actuate.logging.LoggersEndpoint;
import org.springframework.boot.logging.LogLevel;
import org.springframework.boot.logging.LoggingSystem;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
class LogManagementServiceTest {
private LoggersEndpoint loggersEndpoint;
private LogManagementService logManagementService;
@BeforeEach
void setUp() {
LoggingSystem loggingSystem = mock(LoggingSystem.class);
loggersEndpoint = new LoggersEndpoint(loggingSystem, null);
logManagementService = new LogManagementService(loggersEndpoint);
}
@Test
void setLogLevel_ChangesLevel() {
logManagementService.setLogLevel("com.example", LogLevel.DEBUG);
// Verify through endpoint
LoggersEndpoint.LoggerLevelsDescriptor descriptor =
loggersEndpoint.loggerLevels("com.example");
assertThat(descriptor.getConfiguredLevel()).isEqualTo("DEBUG");
}
@Test
void resetLogLevel_ClearsConfiguredLevel() {
// Set level first
logManagementService.setLogLevel("com.example", LogLevel.DEBUG);
// Reset
logManagementService.resetLogLevel("com.example");
// Verify cleared
LoggersEndpoint.LoggerLevelsDescriptor descriptor =
loggersEndpoint.loggerLevels("com.example");
assertThat(descriptor.getConfiguredLevel()).isNull();
}
}package com.example.actuator;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.BeforeEach;
import org.springframework.boot.actuate.web.exchanges.HttpExchange;
import org.springframework.boot.actuate.web.exchanges.InMemoryHttpExchangeRepository;
import java.net.URI;
import java.time.Duration;
import java.time.Instant;
import java.util.List;
import java.util.Map;
import static org.assertj.core.api.Assertions.*;
class HttpAnalyticsServiceTest {
private InMemoryHttpExchangeRepository repository;
private HttpAnalyticsService analyticsService;
@BeforeEach
void setUp() {
repository = new InMemoryHttpExchangeRepository();
analyticsService = new HttpAnalyticsService(repository);
// Add test data
addExchange("/api/users", 200, Duration.ofMillis(100));
addExchange("/api/users", 200, Duration.ofMillis(150));
addExchange("/api/orders", 200, Duration.ofMillis(2000)); // Slow
addExchange("/api/orders", 500, Duration.ofMillis(50));
}
@Test
void getAverageResponseTime_CalculatesCorrectly() {
double average = analyticsService.getAverageResponseTime();
assertThat(average).isEqualTo(575.0); // (100+150+2000+50)/4
}
@Test
void getEndpointCallCounts_CountsCorrectly() {
Map<String, Long> counts = analyticsService.getEndpointCallCounts();
assertThat(counts).containsEntry("/api/users", 2L);
assertThat(counts).containsEntry("/api/orders", 2L);
}
@Test
void findSlowRequests_FindsRequestsOver1Second() {
List<HttpAnalyticsService.SlowRequest> slowRequests =
analyticsService.findSlowRequests();
assertThat(slowRequests).hasSize(1);
assertThat(slowRequests.get(0).uri()).contains("/api/orders");
assertThat(slowRequests.get(0).durationMs()).isEqualTo(2000);
}
@Test
void getStatusCodeDistribution_CountsByStatus() {
Map<Integer, Long> distribution =
analyticsService.getStatusCodeDistribution();
assertThat(distribution).containsEntry(200, 3L);
assertThat(distribution).containsEntry(500, 1L);
}
private void addExchange(String path, int status, Duration timeTaken) {
HttpExchange.Request request = new HttpExchange.Request(
URI.create("http://localhost:8080" + path),
"127.0.0.1",
"GET",
Map.of()
);
HttpExchange.Response response = new HttpExchange.Response(
status,
Map.of()
);
HttpExchange exchange = new HttpExchange(
Instant.now(),
request,
response,
null,
null,
timeTaken
);
repository.add(exchange);
}
}Problem: Endpoint returns empty or null data
Causes:
Solutions:
// Solution 1: Ensure contributors are Spring beans
@Component // <-- Add this
public class MyInfoContributor implements InfoContributor { }
// Solution 2: Verify component scanning
@SpringBootApplication
@ComponentScan(basePackages = {"com.example", "com.example.actuator"})
public class Application { }
// Solution 3: Check auto-configuration
@Configuration
@EnableConfigurationProperties
public class ActuatorConfiguration {
// Ensure actuator auto-configuration is enabled
}Problem: Configuration properties show wrong or missing values
Causes:
Solutions:
// Ensure class is annotated correctly
@ConfigurationProperties(prefix = "app") // <-- Correct prefix
@Component // <-- Or enable via @EnableConfigurationProperties
public class AppProperties {
private String name;
// getters/setters required for binding
}
// Verify in application.yml
app:
name: My Application # Must match prefixProblem: Log level changes don't take effect
Causes:
Solutions:
// Use correct logger name
loggersEndpoint.configureLogLevel("com.example.service", LogLevel.DEBUG);
// Not "ServiceClass" - use fully qualified package/class name
// Check logging system is supported
@Bean
public LoggingSystem loggingSystem() {
// Should auto-configure for Logback, Log4j2, Java Util Logging
}
// For custom logging configs, ensure runtime changes are allowed
// In logback-spring.xml:
// <configuration scan="true" scanPeriod="30 seconds">// In-memory repositories have limits
@Bean
public InMemoryHttpExchangeRepository httpExchangeRepository() {
InMemoryHttpExchangeRepository repository =
new InMemoryHttpExchangeRepository();
// Default is 100, consider impact:
// - Low value: Less memory, lose older data
// - High value: More memory, better history
repository.setCapacity(1000); // Adjust based on traffic
return repository;
}
// For high-traffic apps, implement persistent repository
@Bean
public HttpExchangeRepository persistentRepository(DataSource dataSource) {
return new JdbcHttpExchangeRepository(dataSource);
}// Thread dumps can be expensive
@Configuration
public class ThreadDumpConfiguration {
// Consider rate limiting for thread dump endpoint
@Bean
public ThreadDumpEndpoint threadDumpEndpoint() {
// Thread dumps lock all threads briefly
// Can impact performance if called frequently
return new ThreadDumpEndpoint();
}
}
// In production, restrict access
management:
endpoint:
threaddump:
enabled: true
# Only allow authorized users// Heap dumps are VERY expensive
@Bean
public HeapDumpWebEndpoint heapDumpWebEndpoint() {
// Default timeout is 10 seconds
// For large heaps, increase timeout
return new HeapDumpWebEndpoint(30_000); // 30 seconds
}
// WARNING: Heap dumps can:
// - Pause application for seconds/minutes
// - Use significant disk space
// - Expose sensitive data in memory
// - Impact other applications on shared hosts