Spring Boot Actuator provides production-ready monitoring and management features including built-in endpoints for application information, environment properties, auditing, and application introspection.
—
// Core Web Types
org.springframework.boot.actuate.endpoint.web.WebEndpointResponse
org.springframework.boot.actuate.endpoint.web.WebOperation
org.springframework.boot.actuate.endpoint.web.WebOperationRequestPredicate
org.springframework.boot.actuate.endpoint.web.WebEndpointHttpMethod
org.springframework.boot.actuate.endpoint.web.annotation.EndpointWebExtension
// Web Endpoint Discovery
org.springframework.boot.actuate.endpoint.web.annotation.WebEndpointDiscoverer
org.springframework.boot.actuate.endpoint.web.WebEndpointsSupplier
org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint
// Hypermedia
org.springframework.boot.actuate.endpoint.web.Link
org.springframework.boot.actuate.endpoint.web.EndpointLinksResolver
// Servlet Support (Spring MVC)
org.springframework.boot.actuate.endpoint.web.servlet.WebMvcEndpointHandlerMapping
org.springframework.boot.actuate.endpoint.web.servlet.ServletEndpointRegistrar
// Reactive Support (Spring WebFlux)
org.springframework.boot.actuate.endpoint.web.reactive.WebFluxEndpointHandlerMapping| Status Code | Constant | Use When |
|---|---|---|
| 200 | STATUS_OK | Successful operation with body |
| 204 | STATUS_NO_CONTENT | Successful operation without body |
| 400 | STATUS_BAD_REQUEST | Invalid input parameters |
| 404 | STATUS_NOT_FOUND | Resource not found |
| 429 | STATUS_TOO_MANY_REQUESTS | Rate limit exceeded |
| 500 | STATUS_INTERNAL_SERVER_ERROR | Internal error |
| 503 | STATUS_SERVICE_UNAVAILABLE | Service temporarily unavailable |
Does operation need HTTP-specific features?
│
├─ YES: Use WebEndpointResponse
│ ├─ Custom status codes (404, 503, etc.)
│ ├─ Custom content types
│ └─ Conditional responses based on request
│
└─ NO: Use plain return type
└─ Simple data retrieval (always 200 OK)@ReadOperation → GET
@WriteOperation → POST
@DeleteOperation → DELETE
// Path parameters via @Selector
@ReadOperation
public Data get(@Selector String id) { }
// Maps to: GET /actuator/endpoint/{id}
// Query parameters via method parameters
@ReadOperation
public Data search(String query, Integer limit) { }
// Maps to: GET /actuator/endpoint?query=foo&limit=10IMPORTANT: When using @WebEndpoint, operation annotations (@ReadOperation, @WriteOperation, @DeleteOperation) must be imported from the base annotation package, NOT from the web.annotation package:
// CORRECT imports for @WebEndpoint
import org.springframework.boot.actuate.endpoint.web.annotation.WebEndpoint;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; // ✓ Base package
import org.springframework.boot.actuate.endpoint.annotation.WriteOperation; // ✓ Base package
import org.springframework.boot.actuate.endpoint.annotation.DeleteOperation; // ✓ Base package
// INCORRECT - these do not exist
import org.springframework.boot.actuate.endpoint.web.annotation.ReadOperation; // ✗ Does not exist
import org.springframework.boot.actuate.endpoint.web.annotation.WriteOperation; // ✗ Does not exist
import org.springframework.boot.actuate.endpoint.web.annotation.DeleteOperation; // ✗ Does not existThe web.annotation package contains only @WebEndpoint and @EndpointWebExtension. All operation annotations are in the base endpoint.annotation package and work with both technology-agnostic and web-specific endpoints.
Use WebEndpointResponse when:
Use plain return type when:
PATTERN: Conditional status codes
@ReadOperation
public WebEndpointResponse<Data> getData(@Selector String id) {
Data data = repository.findById(id);
if (data == null) {
return new WebEndpointResponse<>(
WebEndpointResponse.STATUS_NOT_FOUND
);
}
return new WebEndpointResponse<>(data, WebEndpointResponse.STATUS_OK);
}ANTI-PATTERN: Throwing exceptions for flow control
@ReadOperation
public Data getData(@Selector String id) {
Data data = repository.findById(id);
if (data == null) {
throw new RuntimeException("Not found"); // ❌ Wrong status code
}
return data;
}PATTERN: Custom content type
@ReadOperation
public WebEndpointResponse<String> getCsv() {
String csv = generateCsv();
return new WebEndpointResponse<>(
csv,
WebEndpointResponse.STATUS_OK,
MimeTypeUtils.parseMimeType("text/csv")
);
}PATTERN: Service unavailable response
@ReadOperation
public WebEndpointResponse<Map<String, Object>> getStatus() {
if (!service.isAvailable()) {
return new WebEndpointResponse<>(
Map.of("status", "unavailable"),
WebEndpointResponse.STATUS_SERVICE_UNAVAILABLE
);
}
return new WebEndpointResponse<>(
Map.of("status", "available"),
WebEndpointResponse.STATUS_OK
);
}Web-specific endpoint support with HTTP method mapping, status code handling, content negotiation, hypermedia links, and servlet endpoint registration for Spring MVC and WebFlux.
Wrapper for HTTP-specific endpoint responses with status codes and content types.
/**
* HTTP response wrapper for web endpoints.
* Allows returning custom status codes and content types.
*
* Thread-safe: Immutable after construction
* Nullability: Body can be null for empty responses
* Since: Spring Boot 2.0+
*
* @param <T> Response body type
*/
public final class WebEndpointResponse<T> {
/** HTTP 200 OK - Successful response with body */
public static final int STATUS_OK = 200;
/** HTTP 204 No Content - Successful response without body */
public static final int STATUS_NO_CONTENT = 204;
/** HTTP 400 Bad Request - Invalid input parameters */
public static final int STATUS_BAD_REQUEST = 400;
/** HTTP 404 Not Found - Resource not found */
public static final int STATUS_NOT_FOUND = 404;
/** HTTP 429 Too Many Requests - Rate limit exceeded */
public static final int STATUS_TOO_MANY_REQUESTS = 429;
/** HTTP 500 Internal Server Error - Server error */
public static final int STATUS_INTERNAL_SERVER_ERROR = 500;
/** HTTP 503 Service Unavailable - Service temporarily unavailable */
public static final int STATUS_SERVICE_UNAVAILABLE = 503;
/**
* Create response with 200 OK status and default content type.
*/
public WebEndpointResponse();
/**
* Create response with custom status code and no body.
*
* @param status HTTP status code
*/
public WebEndpointResponse(int status);
/**
* Create response with body and 200 OK status.
*
* @param body Response body (nullable)
*/
public WebEndpointResponse(@Nullable T body);
/**
* Create response with body, status, and producible content type.
*
* @param body Response body (nullable)
* @param producible Content type producer
*/
public WebEndpointResponse(@Nullable T body, Producible<?> producible);
/**
* Create response with body and custom content type.
*
* @param body Response body (nullable)
* @param contentType MIME type
*/
public WebEndpointResponse(@Nullable T body, MimeType contentType);
/**
* Create response with body and custom status.
*
* @param body Response body (nullable)
* @param status HTTP status code
*/
public WebEndpointResponse(@Nullable T body, int status);
/**
* Create response with body, status, and content type.
*
* @param body Response body (nullable)
* @param status HTTP status code
* @param contentType MIME type
*/
public WebEndpointResponse(@Nullable T body, int status, MimeType contentType);
/**
* Get the content type.
*
* @return MIME type or null for default
*/
public @Nullable MimeType getContentType();
/**
* Get the response body.
*
* @return Response body (nullable)
*/
public @Nullable T getBody();
/**
* Get the HTTP status code.
*
* @return Status code
*/
public int getStatus();
}Discovery and supplier interfaces for finding and providing web endpoints.
/**
* Discovers and creates web endpoints from annotated classes.
* Scans the application context for @WebEndpoint and @EndpointWebExtension annotations.
*
* Thread-safe: Yes (immutable after construction)
* Since: Spring Boot 2.0.0
*/
public class WebEndpointDiscoverer extends EndpointDiscoverer<ExposableWebEndpoint, WebOperation>
implements WebEndpointsSupplier {
/**
* Create a new WebEndpointDiscoverer instance.
*
* @param applicationContext Source application context to scan
* @param parameterValueMapper Maps HTTP parameters to operation parameters
* @param endpointMediaTypes Media types for endpoints
* @param endpointPathMappers Custom path mappers for endpoint IDs
* @param additionalPathsMappers Mappers for additional endpoint paths
* @param invokerAdvisors Advisors to apply to operation invokers
* @param endpointFilters Filters to apply to discovered endpoints
* @param operationFilters Filters to apply to discovered operations
* @since 3.4.0
*/
public WebEndpointDiscoverer(ApplicationContext applicationContext,
ParameterValueMapper parameterValueMapper,
EndpointMediaTypes endpointMediaTypes,
@Nullable List<PathMapper> endpointPathMappers,
@Nullable List<AdditionalPathsMapper> additionalPathsMappers,
Collection<OperationInvokerAdvisor> invokerAdvisors,
Collection<EndpointFilter<ExposableWebEndpoint>> endpointFilters,
Collection<OperationFilter<WebOperation>> operationFilters);
/**
* Get the discovered web endpoints.
* Inherited from WebEndpointsSupplier.
*
* @return Collection of discovered web endpoints
*/
@Override
Collection<ExposableWebEndpoint> getEndpoints();
}
/**
* Supplier interface for providing web endpoints.
* Functional interface for components that provide web endpoints.
*
* Thread-safe: Implementation dependent
* Since: Spring Boot 2.0.0
*/
@FunctionalInterface
public interface WebEndpointsSupplier extends EndpointsSupplier<ExposableWebEndpoint> {
/**
* Get the web endpoints.
*
* @return Collection of web endpoints
*/
@Override
Collection<ExposableWebEndpoint> getEndpoints();
}
/**
* Information about a web endpoint that can be exposed over HTTP.
* Combines endpoint metadata with path mapping information.
*
* Thread-safe: Implementation dependent
* Since: Spring Boot 2.0.0
*/
public interface ExposableWebEndpoint extends ExposableEndpoint<WebOperation>, PathMappedEndpoint {
/**
* Get the endpoint ID.
* Inherited from ExposableEndpoint.
*
* @return Endpoint ID
*/
EndpointId getEndpointId();
/**
* Get the operations for this endpoint.
* Inherited from ExposableEndpoint.
*
* @return Collection of web operations
*/
Collection<WebOperation> getOperations();
/**
* Get the root path for the endpoint.
* Inherited from PathMappedEndpoint.
*
* @return Root path
*/
String getRootPath();
}Interface and classes for web-specific endpoint operations with HTTP request predicates.
/**
* An operation on a web endpoint.
*
* Thread-safe: Implementation dependent
* Since: Spring Boot 2.0.0
*/
public interface WebOperation extends Operation {
/**
* Get the ID of the operation that uniquely identifies it within its endpoint.
*
* @return Operation ID
*/
String getId();
/**
* Check if the underlying operation is blocking.
*
* @return true if the operation is blocking
*/
boolean isBlocking();
/**
* Get the predicate for requests that can be handled by this operation.
*
* @return Request predicate
*/
WebOperationRequestPredicate getRequestPredicate();
}
/**
* A predicate for a request to an operation on a web endpoint.
* Encapsulates HTTP method, path, and content negotiation requirements.
*
* Thread-safe: Yes (immutable)
* Since: Spring Boot 2.0.0
*/
public final class WebOperationRequestPredicate {
/**
* Create a new operation request predicate.
*
* @param path Path for the operation
* @param httpMethod HTTP method that the operation supports
* @param consumes Media types that the operation consumes
* @param produces Media types that the operation produces
*/
public WebOperationRequestPredicate(String path, WebEndpointHttpMethod httpMethod,
Collection<String> consumes, Collection<String> produces);
/**
* Get the path for the operation.
*
* @return Path
*/
public String getPath();
/**
* Get the name of the variable used to catch all remaining path segments.
*
* @return Variable name or null
* @since 2.2.0
*/
public @Nullable String getMatchAllRemainingPathSegmentsVariable();
/**
* Get the HTTP method for the operation.
*
* @return HTTP method
*/
public WebEndpointHttpMethod getHttpMethod();
/**
* Get the media types that the operation consumes.
*
* @return Consumed media types
*/
public Collection<String> getConsumes();
/**
* Get the media types that the operation produces.
*
* @return Produced media types
*/
public Collection<String> getProduces();
}Interfaces and classes for customizing endpoint path mappings and URL structure.
/**
* Represents a web server namespace for managing different web server contexts.
* Spring Boot can run management endpoints on a separate port from the main application server,
* and this class distinguishes between those contexts.
*
* Thread-safe: Yes (immutable)
* Package: org.springframework.boot.actuate.endpoint.web
* @since 2.6.0
*/
public final class WebServerNamespace {
/**
* The main server namespace (application endpoints).
*/
public static final WebServerNamespace SERVER;
/**
* The management server namespace (actuator endpoints on separate port).
*/
public static final WebServerNamespace MANAGEMENT;
/**
* Get the string value of this namespace.
*
* @return the namespace value (never null)
*/
public String getValue();
/**
* Create a WebServerNamespace from a string value.
* Returns SERVER or MANAGEMENT for recognized values, or a custom instance otherwise.
*
* @param value the namespace value (may be null for SERVER)
* @return WebServerNamespace instance (never null)
*/
public static WebServerNamespace from(@Nullable String value);
/**
* Check equality based on the namespace value.
*
* @param obj the object to compare (may be null)
* @return true if equal, false otherwise
*/
@Override
public boolean equals(Object obj);
/**
* Return hash code based on the namespace value.
*
* @return the hash code
*/
@Override
public int hashCode();
/**
* Return the string representation (same as getValue()).
*
* @return the namespace value (never null)
*/
@Override
public String toString();
}
/**
* Interface implemented by endpoints that are mapped to a root web path.
*
* @since 2.0.0
*/
@FunctionalInterface
public interface PathMappedEndpoint {
/**
* Get the root path of the endpoint (relative to the actuator base path).
* For example, a root path of "example" would be exposed at "/actuator/example".
*
* @return Root path for the endpoint
*/
String getRootPath();
/**
* Get any additional paths for the given web server namespace.
*
* @param webServerNamespace Web server namespace
* @return List of additional paths (empty by default)
* @since 3.4.0
*/
default List<String> getAdditionalPaths(WebServerNamespace webServerNamespace);
}
/**
* Strategy interface for providing custom endpoint ID to path mappings.
*
* @since 2.0.0
*/
@FunctionalInterface
public interface PathMapper {
/**
* Resolve the root path for the specified endpoint ID.
* Return null if this mapper doesn't support the given endpoint ID.
*
* @param endpointId Endpoint ID to map
* @return Root path or null
*/
@Nullable String getRootPath(EndpointId endpointId);
/**
* Resolve the root path for an endpoint ID using the given path mappers.
* Falls back to the endpoint ID itself if no mapper matches.
*
* @param pathMappers Path mappers (may be null)
* @param endpointId Endpoint ID
* @return Root path
*/
static String getRootPath(@Nullable List<PathMapper> pathMappers, EndpointId endpointId);
}
/**
* Base path configuration for endpoints.
*
* @since 2.0.0
*/
public class EndpointMapping {
/**
* Create endpoint mapping with the given base path.
*
* @param path Base path
*/
public EndpointMapping(String path);
/**
* Get the base path to which endpoints are mapped.
*
* @return Base path
*/
public String getPath();
/**
* Create a sub-path under this mapping.
*
* @param path Sub-path to append
* @return Full path
*/
public String createSubPath(String path);
}
/**
* Collection of path-mapped endpoints with convenience methods for path resolution.
*
* @since 2.0.0
*/
public class PathMappedEndpoints implements Iterable<PathMappedEndpoint> {
/**
* Create instance from a single endpoint supplier.
*
* @param basePath Base path for all endpoints
* @param supplier Endpoint supplier
*/
public PathMappedEndpoints(@Nullable String basePath, EndpointsSupplier<?> supplier);
/**
* Create instance from multiple endpoint suppliers.
*
* @param basePath Base path for all endpoints
* @param suppliers Endpoint suppliers
*/
public PathMappedEndpoints(@Nullable String basePath, Collection<EndpointsSupplier<?>> suppliers);
/**
* Get the base path for all endpoints.
*
* @return Base path
*/
public String getBasePath();
/**
* Get the root path for an endpoint (relative to base path).
*
* @param endpointId Endpoint ID
* @return Root path or null if not found
*/
public @Nullable String getRootPath(EndpointId endpointId);
/**
* Get the full path for an endpoint (including base path).
*
* @param endpointId Endpoint ID
* @return Full path or null if not found
*/
public @Nullable String getPath(EndpointId endpointId);
/**
* Get all root paths (excluding additional paths).
*
* @return Collection of root paths
*/
public Collection<String> getAllRootPaths();
/**
* Get all full paths (excluding additional paths).
*
* @return Collection of full paths
*/
public Collection<String> getAllPaths();
/**
* Get additional paths for an endpoint in the given namespace.
*
* @param webServerNamespace Web server namespace
* @param endpointId Endpoint ID
* @return Collection of additional paths
* @since 3.4.0
*/
public Collection<String> getAdditionalPaths(WebServerNamespace webServerNamespace, EndpointId endpointId);
/**
* Get a specific endpoint by ID.
*
* @param endpointId Endpoint ID
* @return PathMappedEndpoint or null if not found
*/
public @Nullable PathMappedEndpoint getEndpoint(EndpointId endpointId);
/**
* Stream all path-mapped endpoints.
*
* @return Stream of endpoints
*/
public Stream<PathMappedEndpoint> stream();
@Override
public Iterator<PathMappedEndpoint> iterator();
}HAL-formatted links and link resolution for endpoint discovery.
/**
* Details for a link in a HAL-formatted response.
* Represents a hyperlink with an href and templated flag.
*
* Thread-safe: Yes (immutable)
* Package: org.springframework.boot.actuate.endpoint.web
* @since 2.0.0
*/
public class Link {
/**
* Creates a new Link with the given href.
* Automatically sets templated to true if href contains "{".
*
* @param href Link href (must not be null)
*/
public Link(String href);
/**
* Returns the href of the link.
*
* @return Link href (never null)
*/
public String getHref();
/**
* Returns whether the href is templated.
* A link is considered templated if its href contains "{".
*
* @return true if the href is templated, false otherwise
*/
public boolean isTemplated();
@Override
public String toString();
}
/**
* A resolver for Links to web endpoints.
* Resolves endpoint links based on request URLs for HAL-formatted responses.
*
* Thread-safe: Yes (immutable after construction)
* Package: org.springframework.boot.actuate.endpoint.web
* @since 2.0.0
*/
public class EndpointLinksResolver {
/**
* Creates a new EndpointLinksResolver that will resolve links to the given endpoints.
*
* @param endpoints Endpoints to resolve links for
*/
public EndpointLinksResolver(Collection<? extends ExposableEndpoint<?>> endpoints);
/**
* Creates a new EndpointLinksResolver that will resolve links to the given endpoints
* that are exposed beneath the given basePath.
* Logs the number of endpoints being exposed at INFO level.
*
* @param endpoints Endpoints to resolve links for
* @param basePath Base path of the endpoints
*/
public EndpointLinksResolver(Collection<? extends ExposableEndpoint<?>> endpoints, String basePath);
/**
* Resolves links to the known endpoints based on a request with the given requestUrl.
* Includes a "self" link to the requestUrl and links to each endpoint.
*
* @param requestUrl URL of the request for the endpoint links
* @return Map of endpoint IDs to Links
*/
public Map<String, Link> resolveLinks(String requestUrl);
}Namespace for disambiguating between multiple web servers in the same application.
/**
* Web server namespace used for disambiguation when multiple web servers
* are running in the same application (e.g., management context on different port).
*
* @since 2.6.0
*/
public final class WebServerNamespace {
/**
* Namespace that represents the main server.
*/
public static final WebServerNamespace SERVER;
/**
* Namespace that represents the management server.
*/
public static final WebServerNamespace MANAGEMENT;
/**
* Get the value of the namespace.
*
* @return Namespace value
*/
public String getValue();
/**
* Factory method to create a new WebServerNamespace from a value.
* If the value is empty or null then SERVER is returned.
*
* @param value Namespace value or null
* @return Web server namespace
*/
public static WebServerNamespace from(@Nullable String value);
}Strategy interface for providing additional paths where endpoints are exposed.
/**
* Strategy interface used to provide a mapping between an endpoint ID
* and any additional paths where it will be exposed.
*
* @since 3.4.0
*/
@FunctionalInterface
public interface AdditionalPathsMapper {
/**
* Resolve the additional paths for the specified endpoint ID and web server namespace.
*
* @param endpointId Endpoint ID
* @param webServerNamespace Web server namespace
* @return Additional paths or null if this mapper doesn't support the given endpoint ID
*/
@Nullable List<String> getAdditionalPaths(EndpointId endpointId, WebServerNamespace webServerNamespace);
}Configuration for endpoint content types.
/**
* Media types that are by default produced and consumed by endpoints.
*
* @since 2.0.0
*/
public class EndpointMediaTypes {
/**
* Default media types for this version of Spring Boot.
*/
public static final EndpointMediaTypes DEFAULT;
/**
* Create with the same media types for produced and consumed.
*
* @param producedAndConsumed Media types
*/
public EndpointMediaTypes(String... producedAndConsumed);
/**
* Create with the same media types for produced and consumed.
*
* @param producedAndConsumed Media types
*/
public EndpointMediaTypes(List<String> producedAndConsumed);
/**
* Create with different media types for produced and consumed.
*
* @param produced Media types produced by endpoints
* @param consumed Media types consumed by endpoints
*/
public EndpointMediaTypes(List<String> produced, List<String> consumed);
/**
* Get the media types produced by endpoints.
*
* @return Produced media types
*/
public List<String> getProduced();
/**
* Get the media types consumed by endpoints.
*
* @return Consumed media types
*/
public List<String> getConsumed();
}Handler mappings that register web endpoints with Spring MVC and WebFlux.
/**
* Custom HandlerMapping that makes web endpoints available over HTTP using Spring MVC.
* Registers discovered endpoints as controller methods and handles request routing.
*
* Thread-safe: Yes
* Framework: Spring MVC (Servlet)
* Since: Spring Boot 4.0.0
*/
public class WebMvcEndpointHandlerMapping extends AbstractWebMvcEndpointHandlerMapping {
/**
* Create a new WebMvcEndpointHandlerMapping instance.
*
* @param endpointMapping Base mapping for all endpoints (e.g., "/actuator")
* @param endpoints Web endpoints to expose
* @param endpointMediaTypes Media types consumed and produced by endpoints
* @param corsConfiguration CORS configuration for endpoints or null
* @param linksResolver Resolver for determining links to available endpoints
* @param shouldRegisterLinksMapping Whether to register the root links endpoint
*/
public WebMvcEndpointHandlerMapping(EndpointMapping endpointMapping,
Collection<ExposableWebEndpoint> endpoints,
EndpointMediaTypes endpointMediaTypes,
@Nullable CorsConfiguration corsConfiguration,
EndpointLinksResolver linksResolver,
boolean shouldRegisterLinksMapping);
/**
* Get the web endpoints being mapped.
* Inherited from AbstractWebMvcEndpointHandlerMapping.
*
* @return Collection of web endpoints
*/
public Collection<ExposableWebEndpoint> getEndpoints();
}
/**
* Custom HandlerMapping that makes web endpoints available over HTTP using Spring WebFlux.
* Registers discovered endpoints as reactive handlers with non-blocking request handling.
*
* Thread-safe: Yes
* Framework: Spring WebFlux (Reactive)
* Since: Spring Boot 4.0.0
*/
public class WebFluxEndpointHandlerMapping extends AbstractWebFluxEndpointHandlerMapping
implements InitializingBean {
/**
* Create a new WebFluxEndpointHandlerMapping instance.
*
* @param endpointMapping Base mapping for all endpoints (e.g., "/actuator")
* @param endpoints Web endpoints to expose
* @param endpointMediaTypes Media types consumed and produced by endpoints
* @param corsConfiguration CORS configuration for endpoints or null
* @param linksResolver Resolver for determining links to available endpoints
* @param shouldRegisterLinksMapping Whether to register the root links endpoint
*/
public WebFluxEndpointHandlerMapping(EndpointMapping endpointMapping,
Collection<ExposableWebEndpoint> endpoints,
EndpointMediaTypes endpointMediaTypes,
@Nullable CorsConfiguration corsConfiguration,
EndpointLinksResolver linksResolver,
boolean shouldRegisterLinksMapping);
/**
* Get the web endpoints being mapped.
* Inherited from AbstractWebFluxEndpointHandlerMapping.
*
* @return Collection of web endpoints
*/
public Collection<ExposableWebEndpoint> getEndpoints();
/**
* Initialize the handler mapping after properties are set.
* Inherited from InitializingBean.
*/
@Override
void afterPropertiesSet();
}DEPRECATED: Servlet endpoint support is deprecated since 3.3.0 and scheduled for removal. Use @Endpoint and @WebEndpoint instead for new development.
Framework for registering servlet-based actuator endpoints (deprecated since 3.3.0). These APIs allow exposing servlets as actuator endpoints but are being phased out in favor of the modern endpoint framework.
/**
* Contains details of a servlet that is exposed as an actuator endpoint.
*
* Package: org.springframework.boot.actuate.endpoint.web
* Thread-safe: Yes (immutable after construction)
* @since 2.0.0
* @deprecated since 3.3.0 in favor of {@code @Endpoint} and {@code @WebEndpoint}
*/
@Deprecated(since = "3.3.0", forRemoval = true)
public final class EndpointServlet {
/**
* Create an endpoint servlet from a servlet class.
*
* @param servlet Servlet class to instantiate (never null)
*/
public EndpointServlet(Class<? extends Servlet> servlet);
/**
* Create an endpoint servlet from a servlet instance.
*
* @param servlet Servlet instance (never null)
*/
public EndpointServlet(Servlet servlet);
/**
* Add a single initialization parameter.
* Returns a new instance with the parameter added.
*
* @param name Parameter name (must not be empty)
* @param value Parameter value
* @return New EndpointServlet instance with parameter
*/
public EndpointServlet withInitParameter(String name, String value);
/**
* Add multiple initialization parameters.
* Returns a new instance with the parameters added.
*
* @param initParameters Map of parameter name-value pairs (never null)
* @return New EndpointServlet instance with parameters
*/
public EndpointServlet withInitParameters(Map<String, String> initParameters);
/**
* Set the loadOnStartup priority for servlet registration.
* Default value is -1 (load on first request).
*
* @param loadOnStartup Initialization priority
* @return New EndpointServlet instance with loadOnStartup value
* @since 2.2.0
*/
public EndpointServlet withLoadOnStartup(int loadOnStartup);
}
/**
* Information describing an endpoint that can be exposed by registering a servlet.
*
* Package: org.springframework.boot.actuate.endpoint.web
* @since 2.0.0
* @deprecated since 3.3.0 in favor of {@code @Endpoint} and {@code @WebEndpoint}
*/
@Deprecated(since = "3.3.0", forRemoval = true)
public interface ExposableServletEndpoint extends ExposableEndpoint<Operation>, PathMappedEndpoint {
/**
* Return details of the servlet that should be registered.
*
* @return The endpoint servlet (never null)
*/
EndpointServlet getEndpointServlet();
}
/**
* ServletContextInitializer to register servlet endpoints.
*
* Package: org.springframework.boot.actuate.endpoint.web
* @since 2.0.0
* @deprecated since 3.3.0 in favor of {@code @Endpoint} and {@code @WebEndpoint} support
*/
@Deprecated(since = "3.3.0", forRemoval = true)
public class ServletEndpointRegistrar implements ServletContextInitializer {
/**
* Create a new ServletEndpointRegistrar.
*
* @param basePath Base path for servlet endpoints (nullable)
* @param servletEndpoints Collection of servlet endpoints to register (never null)
*/
public ServletEndpointRegistrar(@Nullable String basePath,
Collection<ExposableServletEndpoint> servletEndpoints);
/**
* Create a new ServletEndpointRegistrar with access resolver.
*
* @param basePath Base path for servlet endpoints (nullable)
* @param servletEndpoints Collection of servlet endpoints to register (never null)
* @param endpointAccessResolver Resolver for endpoint access control (never null)
*/
public ServletEndpointRegistrar(@Nullable String basePath,
Collection<ExposableServletEndpoint> servletEndpoints,
EndpointAccessResolver endpointAccessResolver);
}Migration Guide: Replace servlet endpoints with modern @WebEndpoint:
// OLD (deprecated):
@ServletEndpoint(id = "legacy")
public class LegacyServletEndpoint {
public EndpointServlet endpoint() {
return new EndpointServlet(MyServlet.class)
.withInitParameter("param", "value");
}
}
// NEW (recommended):
@WebEndpoint(id = "modern")
@Component
public class ModernWebEndpoint {
@ReadOperation
public WebEndpointResponse<Data> getData() {
return new WebEndpointResponse<>(data, 200);
}
}package com.example.actuator;
import org.springframework.boot.actuate.endpoint.web.annotation.EndpointWebExtension;
import org.springframework.boot.actuate.endpoint.annotation.*;
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
import org.springframework.stereotype.Component;
import org.jspecify.annotations.Nullable;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* Resource management endpoint with full CRUD operations.
*
* Thread-safe: Yes (uses ConcurrentHashMap)
* HTTP-specific: Yes (uses status codes)
* Since: Application 1.0
*/
@EndpointWebExtension(endpoint = ResourceEndpoint.class)
@Component
public class ResourceWebEndpoint {
private final Map<String, Resource> resources = new ConcurrentHashMap<>();
/**
* List all resources.
*
* Returns 200 OK with resource list.
*
* @return Web response with resources
*/
@ReadOperation
public WebEndpointResponse<Map<String, Resource>> listResources() {
return new WebEndpointResponse<>(
Map.copyOf(resources),
WebEndpointResponse.STATUS_OK
);
}
/**
* Get specific resource.
*
* Returns:
* - 200 OK if found
* - 404 Not Found if missing
*
* @param id Resource ID
* @return Web response with resource or error
*/
@ReadOperation
public WebEndpointResponse<Resource> getResource(@Selector String id) {
Resource resource = resources.get(id);
if (resource == null) {
return new WebEndpointResponse<>(
WebEndpointResponse.STATUS_NOT_FOUND
);
}
return new WebEndpointResponse<>(
resource,
WebEndpointResponse.STATUS_OK
);
}
/**
* Create or update resource.
*
* Returns:
* - 200 OK if updated
* - 400 Bad Request if invalid input
*
* @param id Resource ID
* @param name Resource name
* @param value Resource value
* @return Web response with result
*/
@WriteOperation
public WebEndpointResponse<Map<String, String>> saveResource(
@Selector String id,
String name,
String value) {
// Validation
if (name == null || name.isBlank()) {
return new WebEndpointResponse<>(
Map.of("error", "Name is required"),
WebEndpointResponse.STATUS_BAD_REQUEST
);
}
Resource resource = new Resource(id, name, value);
resources.put(id, resource);
return new WebEndpointResponse<>(
Map.of("status", "saved", "id", id),
WebEndpointResponse.STATUS_OK
);
}
/**
* Delete resource.
*
* Returns:
* - 204 No Content if deleted
* - 404 Not Found if not exists
*
* @param id Resource ID
* @return Web response with status
*/
@DeleteOperation
public WebEndpointResponse<Void> deleteResource(@Selector String id) {
Resource removed = resources.remove(id);
if (removed == null) {
return new WebEndpointResponse<>(
WebEndpointResponse.STATUS_NOT_FOUND
);
}
return new WebEndpointResponse<>(
WebEndpointResponse.STATUS_NO_CONTENT
);
}
public record Resource(String id, String name, String value) {}
}
// Base endpoint (technology-agnostic)
@Endpoint(id = "resources")
@Component
class ResourceEndpoint {
// Minimal implementation, extended by web extension
}package com.example.actuator;
import org.springframework.boot.actuate.endpoint.web.annotation.WebEndpoint;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
import org.springframework.boot.actuate.endpoint.annotation.Selector;
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
import org.springframework.boot.actuate.endpoint.Access;
import org.springframework.core.io.Resource;
import org.springframework.core.io.FileSystemResource;
import org.springframework.util.MimeTypeUtils;
import org.springframework.stereotype.Component;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* File download endpoint with content type detection.
*
* Thread-safe: Yes
* HTTP-only: Yes (files don't make sense over JMX)
* Since: Application 1.0
*/
@WebEndpoint(id = "downloads", defaultAccess = Access.READ_ONLY)
@Component
public class FileDownloadEndpoint {
private final Path downloadsDirectory;
public FileDownloadEndpoint() {
this.downloadsDirectory = Path.of("/var/app/downloads");
}
/**
* List available files.
*
* @return Web response with file list
*/
@ReadOperation
public WebEndpointResponse<Map<String, Object>> listFiles() {
try {
List<String> files = Files.list(downloadsDirectory)
.filter(Files::isRegularFile)
.map(Path::getFileName)
.map(Path::toString)
.collect(Collectors.toList());
return new WebEndpointResponse<>(
Map.of("files", files),
WebEndpointResponse.STATUS_OK
);
} catch (IOException e) {
return new WebEndpointResponse<>(
Map.of("error", "Failed to list files"),
WebEndpointResponse.STATUS_INTERNAL_SERVER_ERROR
);
}
}
/**
* Download file with appropriate content type.
*
* Returns:
* - 200 OK with file content
* - 400 Bad Request if path traversal detected
* - 404 Not Found if file doesn't exist
* - 500 Internal Server Error if read fails
*
* @param filename File name to download
* @return Web response with file or error
*/
@ReadOperation
public WebEndpointResponse<Resource> downloadFile(@Selector String filename) {
Path filePath = downloadsDirectory.resolve(filename).normalize();
// Security check: prevent path traversal
if (!filePath.startsWith(downloadsDirectory)) {
return new WebEndpointResponse<>(
WebEndpointResponse.STATUS_BAD_REQUEST
);
}
File file = filePath.toFile();
// Check existence
if (!file.exists() || !file.isFile()) {
return new WebEndpointResponse<>(
WebEndpointResponse.STATUS_NOT_FOUND
);
}
// Detect content type
String contentType;
try {
contentType = Files.probeContentType(filePath);
if (contentType == null) {
contentType = "application/octet-stream";
}
} catch (IOException e) {
contentType = "application/octet-stream";
}
return new WebEndpointResponse<>(
new FileSystemResource(file),
WebEndpointResponse.STATUS_OK,
MimeTypeUtils.parseMimeType(contentType)
);
}
}package com.example.actuator;
import org.springframework.boot.actuate.endpoint.web.annotation.EndpointWebExtension;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
import org.springframework.boot.actuate.endpoint.Producible;
import org.springframework.util.MimeType;
import org.springframework.util.MimeTypeUtils;
import org.springframework.stereotype.Component;
import java.util.Map;
/**
* Report endpoint with multiple output formats.
*
* Thread-safe: Yes
* Content negotiation: Yes (JSON, CSV, XML)
* Since: Application 1.0
*/
@EndpointWebExtension(endpoint = ReportEndpoint.class)
@Component
public class ReportWebExtension {
private final ReportEndpoint delegate;
public ReportWebExtension(ReportEndpoint delegate) {
this.delegate = delegate;
}
/**
* Get report in requested format.
*
* Supports:
* - application/json (default)
* - text/csv
* - application/xml
*
* @param format Output format
* @return Web response in requested format
*/
@ReadOperation
public WebEndpointResponse<?> getReport(ReportFormat format) {
Map<String, Object> data = delegate.generateReport();
switch (format) {
case CSV:
String csv = convertToCsv(data);
return new WebEndpointResponse<>(
csv,
WebEndpointResponse.STATUS_OK,
MimeTypeUtils.parseMimeType("text/csv")
);
case XML:
String xml = convertToXml(data);
return new WebEndpointResponse<>(
xml,
WebEndpointResponse.STATUS_OK,
MimeTypeUtils.APPLICATION_XML
);
case JSON:
default:
return new WebEndpointResponse<>(
data,
WebEndpointResponse.STATUS_OK,
MimeTypeUtils.APPLICATION_JSON
);
}
}
private String convertToCsv(Map<String, Object> data) {
// CSV conversion logic
return "key,value\n" + data.entrySet().stream()
.map(e -> e.getKey() + "," + e.getValue())
.reduce((a, b) -> a + "\n" + b)
.orElse("");
}
private String convertToXml(Map<String, Object> data) {
// XML conversion logic
return "<?xml version=\"1.0\"?><report>" +
data.entrySet().stream()
.map(e -> "<" + e.getKey() + ">" + e.getValue() + "</" + e.getKey() + ">")
.reduce(String::concat)
.orElse("") +
"</report>";
}
public enum ReportFormat implements Producible<ReportFormat> {
JSON(MimeTypeUtils.APPLICATION_JSON),
CSV(MimeTypeUtils.parseMimeType("text/csv")),
XML(MimeTypeUtils.APPLICATION_XML);
private final MimeType mimeType;
ReportFormat(MimeType mimeType) {
this.mimeType = mimeType;
}
@Override
public MimeType getProducedMimeType() {
return mimeType;
}
}
}
@Endpoint(id = "report")
@Component
class ReportEndpoint {
public Map<String, Object> generateReport() {
return Map.of("total", 100, "active", 85);
}
}package com.example.actuator;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.BeforeEach;
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
import static org.assertj.core.api.Assertions.*;
class ResourceWebEndpointTest {
private ResourceWebEndpoint endpoint;
@BeforeEach
void setUp() {
endpoint = new ResourceWebEndpoint();
}
@Test
void getResource_WhenExists_Returns200() {
// Create resource first
endpoint.saveResource("test", "Test Resource", "value");
WebEndpointResponse<ResourceWebEndpoint.Resource> response =
endpoint.getResource("test");
assertThat(response.getStatus()).isEqualTo(200);
assertThat(response.getBody()).isNotNull();
assertThat(response.getBody().name()).isEqualTo("Test Resource");
}
@Test
void getResource_WhenNotExists_Returns404() {
WebEndpointResponse<ResourceWebEndpoint.Resource> response =
endpoint.getResource("nonexistent");
assertThat(response.getStatus()).isEqualTo(404);
assertThat(response.getBody()).isNull();
}
@Test
void saveResource_WithInvalidName_Returns400() {
WebEndpointResponse<Map<String, String>> response =
endpoint.saveResource("test", "", "value");
assertThat(response.getStatus()).isEqualTo(400);
assertThat(response.getBody()).containsKey("error");
}
@Test
void deleteResource_WhenExists_Returns204() {
endpoint.saveResource("test", "Test", "value");
WebEndpointResponse<Void> response = endpoint.deleteResource("test");
assertThat(response.getStatus()).isEqualTo(204);
assertThat(response.getBody()).isNull();
}
@Test
void deleteResource_WhenNotExists_Returns404() {
WebEndpointResponse<Void> response = endpoint.deleteResource("nonexistent");
assertThat(response.getStatus()).isEqualTo(404);
}
}package com.example.actuator;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.BeforeEach;
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
import org.springframework.util.MimeTypeUtils;
import static org.assertj.core.api.Assertions.*;
class ReportWebExtensionTest {
private ReportEndpoint endpoint;
private ReportWebExtension extension;
@BeforeEach
void setUp() {
endpoint = new ReportEndpoint();
extension = new ReportWebExtension(endpoint);
}
@Test
void getReport_AsJson_ReturnsJsonContentType() {
WebEndpointResponse<?> response =
extension.getReport(ReportWebExtension.ReportFormat.JSON);
assertThat(response.getStatus()).isEqualTo(200);
assertThat(response.getContentType()).isEqualTo(MimeTypeUtils.APPLICATION_JSON);
assertThat(response.getBody()).isInstanceOf(Map.class);
}
@Test
void getReport_AsCsv_ReturnsCsvContentType() {
WebEndpointResponse<?> response =
extension.getReport(ReportWebExtension.ReportFormat.CSV);
assertThat(response.getStatus()).isEqualTo(200);
assertThat(response.getContentType())
.isEqualTo(MimeTypeUtils.parseMimeType("text/csv"));
assertThat(response.getBody()).isInstanceOf(String.class);
assertThat((String) response.getBody()).contains("key,value");
}
@Test
void getReport_AsXml_ReturnsXmlContentType() {
WebEndpointResponse<?> response =
extension.getReport(ReportWebExtension.ReportFormat.XML);
assertThat(response.getStatus()).isEqualTo(200);
assertThat(response.getContentType()).isEqualTo(MimeTypeUtils.APPLICATION_XML);
assertThat((String) response.getBody()).startsWith("<?xml");
}
}Problem: Endpoint returns 500 when should return 404 or 400
Cause: Not using WebEndpointResponse
Solution:
// ❌ Wrong - throws exception
@ReadOperation
public Data getData(@Selector String id) {
Data data = repository.findById(id);
if (data == null) {
throw new RuntimeException("Not found"); // Returns 500
}
return data;
}
// ✓ Correct - returns 404
@ReadOperation
public WebEndpointResponse<Data> getData(@Selector String id) {
Data data = repository.findById(id);
if (data == null) {
return new WebEndpointResponse<>(
WebEndpointResponse.STATUS_NOT_FOUND
);
}
return new WebEndpointResponse<>(data);
}Problem: Browser downloads JSON instead of displaying it
Cause: Missing or wrong content type
Solution:
// Set explicit content type
@ReadOperation
public WebEndpointResponse<String> getData() {
return new WebEndpointResponse<>(
jsonString,
WebEndpointResponse.STATUS_OK,
MimeTypeUtils.APPLICATION_JSON // <-- Explicit content type
);
}Problem: File download returns as JSON or fails
Cause: Wrong return type or content type
Solution:
// ✓ Correct file download
@ReadOperation
public WebEndpointResponse<Resource> downloadFile(@Selector String filename) {
File file = new File("/path/to/" + filename);
return new WebEndpointResponse<>(
new FileSystemResource(file),
WebEndpointResponse.STATUS_OK,
MimeTypeUtils.APPLICATION_OCTET_STREAM
);
}// WebEndpointResponse results are cacheable
@ReadOperation
public WebEndpointResponse<Data> getData() {
// This will be cached if caching configured
return new WebEndpointResponse<>(expensiveOperation());
}
// Conditional responses disable caching
@ReadOperation
public WebEndpointResponse<Data> getData(SecurityContext ctx) {
// Not cached (has parameters)
if (ctx.isUserInRole("ADMIN")) {
return new WebEndpointResponse<>(fullData);
}
return new WebEndpointResponse<>(limitedData);
}// For large responses, use streaming
@ReadOperation
public WebEndpointResponse<Resource> getLargeFile() {
// Resource supports streaming
return new WebEndpointResponse<>(
new FileSystemResource(largeFile),
WebEndpointResponse.STATUS_OK
);
}
// Avoid building large objects in memory
@ReadOperation
public WebEndpointResponse<List<Data>> getAllData() {
// ❌ Bad - loads everything
List<Data> all = repository.findAll();
// ✓ Better - paginate
List<Data> page = repository.findPage(0, 100);
return new WebEndpointResponse<>(page);
}Install with Tessl CLI
npx tessl i tessl/maven-org-springframework-boot--spring-boot-actuator