// Core JMX Annotations
org.springframework.boot.actuate.endpoint.jmx.annotation.JmxEndpoint
org.springframework.boot.actuate.endpoint.jmx.annotation.EndpointJmxExtension
// JMX Endpoint Discovery
org.springframework.boot.actuate.endpoint.jmx.annotation.JmxEndpointDiscoverer
org.springframework.boot.actuate.endpoint.jmx.annotation.JmxEndpointFilter
// JMX Endpoints
org.springframework.boot.actuate.endpoint.jmx.ExposableJmxEndpoint
org.springframework.boot.actuate.endpoint.jmx.JmxEndpointsSupplier
// JMX Operations
org.springframework.boot.actuate.endpoint.jmx.JmxOperation
org.springframework.boot.actuate.endpoint.jmx.JmxOperationParameter
// JMX Export
org.springframework.boot.actuate.endpoint.jmx.JmxEndpointExporter
org.springframework.boot.actuate.endpoint.jmx.JmxOperationResponseMapper
org.springframework.boot.actuate.endpoint.jmx.EndpointObjectNameFactory
org.springframework.boot.actuate.endpoint.jmx.EndpointMBean
// JMX Core
javax.management.MBeanServer
javax.management.ObjectName
javax.management.DynamicMBeanNeed JMX exposure?
│
├─ Only JMX (no HTTP)?
│ └─ Use @JmxEndpoint
│
├─ Both JMX and HTTP?
│ └─ Use @Endpoint (technology-agnostic)
│
└─ JMX-specific behavior for existing endpoint?
└─ Use @EndpointJmxExtension# JMX enabled by default
management.endpoints.jmx.exposure.include=*
# Domain and naming
management.endpoints.jmx.domain=org.springframework.boot
spring.jmx.enabled=true| Tool | Purpose | Connection |
|---|---|---|
| JConsole | Built-in JVM monitoring | JDK bundled |
| VisualVM | Advanced profiling | Download separately |
| JMC (Mission Control) | Enterprise monitoring | Oracle JDK |
| jmxterm | Command-line client | Standalone tool |
Use JMX when:
Don't use JMX when:
PATTERN: Technology-agnostic endpoint (works with both)
@Endpoint(id = "cache") // ✓ Available via HTTP and JMX
@Component
public class CacheEndpoint {
@ReadOperation
public CacheStats getStats() {
return new CacheStats(...);
}
}ANTI-PATTERN: JMX-only when HTTP would work
@JmxEndpoint(id = "cache") // ❌ Limits to JMX only
@Component
public class CacheEndpoint {
// Unless you specifically need JMX-only, use @Endpoint
}PATTERN: JMX extension for JMX-specific features
@EndpointJmxExtension(endpoint = CacheEndpoint.class)
@Component
public class CacheJmxExtension {
// Add JMX-specific operations here
@WriteOperation
public void clearAllCaches() {
// JMX-only operation
}
}JMX-specific endpoint support with MBean export, object name generation, and operation response mapping for JMX management tools like JConsole and VisualVM.
/**
* Identifies an endpoint exposed only over JMX.
* Use when endpoint is designed specifically for JMX management tools.
*
* Thread-safe: Endpoint beans must be thread-safe
* Since: Spring Boot 2.0+
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Endpoint
@FilteredEndpoint(JmxEndpointFilter.class)
public @interface JmxEndpoint {
/**
* The ID of the endpoint. Used in ObjectName.
*
* @return endpoint ID (non-null)
*/
@AliasFor(annotation = Endpoint.class)
String id() default "";
/**
* Default access level for the endpoint.
*
* @return access level
*/
@AliasFor(annotation = Endpoint.class)
Access defaultAccess() default Access.UNRESTRICTED;
}
/**
* Identifies a JMX-specific extension of an existing endpoint.
* Adds JMX-specific operations to technology-agnostic endpoint.
*
* Since: Spring Boot 2.0+
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@EndpointExtension(filter = JmxEndpointFilter.class)
public @interface EndpointJmxExtension {
/**
* The class of the endpoint to extend.
*
* @return endpoint class
*/
Class<?> endpoint();
}/**
* Information describing an endpoint that can be exposed over JMX.
* Extends ExposableEndpoint with JMX-specific operations.
*
* Thread-safe: Implementations should be thread-safe
* Since: Spring Boot 2.0+
*
* @see ExposableEndpoint
* @see JmxOperation
*/
public interface ExposableJmxEndpoint extends ExposableEndpoint<JmxOperation> {
// Inherits methods:
// - EndpointId getEndpointId()
// - Access getDefaultAccess()
// - Collection<JmxOperation> getOperations()
}
/**
* Supplier for JMX endpoints.
* Functional interface to provide collection of JMX endpoints.
*
* Thread-safe: Implementations should be thread-safe
* Since: Spring Boot 2.0+
*
* @see EndpointsSupplier
* @see ExposableJmxEndpoint
*/
@FunctionalInterface
public interface JmxEndpointsSupplier extends EndpointsSupplier<ExposableJmxEndpoint> {
// Inherits method:
// - Collection<ExposableJmxEndpoint> getEndpoints()
}/**
* An operation on a JMX endpoint.
* Represents a single MBean operation exposed via JMX.
*
* Thread-safe: Implementations should be thread-safe
* Since: Spring Boot 2.0+
*
* @see Operation
* @see JmxOperationParameter
*/
public interface JmxOperation extends Operation {
/**
* Returns the name of the operation.
* This name is used in the MBean operation signature.
*
* @return the operation name (non-null)
*/
String getName();
/**
* Returns the type of the output of the operation.
* Determines the return type in the MBean operation signature.
*
* @return the output type (non-null)
*/
Class<?> getOutputType();
/**
* Returns the description of the operation.
* Provides documentation visible in JMX clients.
*
* @return the operation description (non-null)
*/
String getDescription();
/**
* Returns the parameters the operation expects in the order that
* they should be provided.
* Empty list for operations with no parameters.
*
* @return the operation parameters (non-null, may be empty)
*/
List<JmxOperationParameter> getParameters();
// Inherited from Operation:
// - OperationType getType()
// - Object invoke(InvocationContext context)
}
/**
* Describes the parameters of an operation on a JMX endpoint.
* Used to build MBean operation parameter metadata.
*
* Thread-safe: Implementations should be thread-safe
* Since: Spring Boot 2.0+
*
* @see JmxOperation
*/
public interface JmxOperationParameter {
/**
* Return the name of the operation parameter.
* This name is used in the MBean operation parameter signature.
*
* @return the name of the parameter (non-null)
*/
String getName();
/**
* Return the type of the operation parameter.
* Determines the parameter type in the MBean operation signature.
*
* @return the type (non-null)
*/
Class<?> getType();
/**
* Return the description of the parameter or null if none is available.
* Provides documentation visible in JMX clients.
*
* @return the description or null
*/
String getDescription();
}/**
* Adapter to expose a JMX endpoint as a DynamicMBean.
* Implements the JMX DynamicMBean interface to allow endpoints
* to be registered as MBeans without requiring MBean interfaces.
*
* Thread-safe: Yes
* Since: Spring Boot 2.0+
*
* Key responsibilities:
* - Exposes ExposableJmxEndpoint operations as MBean operations
* - Maps operation invocations to endpoint methods
* - Handles response mapping and reactive types (Mono/Flux)
* - Manages thread context class loader for operation execution
* - Does NOT support MBean attributes (operations only)
*
* @see DynamicMBean
* @see ExposableJmxEndpoint
* @see JmxOperationResponseMapper
*/
public class EndpointMBean implements DynamicMBean {
/**
* Get MBean metadata.
* Returns information about exposed operations.
*
* @return MBean info (non-null)
*/
@Override
public MBeanInfo getMBeanInfo();
/**
* Invoke an MBean operation.
* Delegates to the underlying endpoint operation.
*
* Reactive handling:
* - Flux results are collected to List
* - Mono results are blocked until complete
*
* @param actionName the operation name
* @param params the operation parameters
* @param signature the operation signature
* @return the operation result (may be null)
* @throws MBeanException if the operation fails
* @throws ReflectionException if the operation cannot be found
*/
@Override
public Object invoke(String actionName, Object[] params, String[] signature)
throws MBeanException, ReflectionException;
/**
* Get an attribute value.
* NOT SUPPORTED - EndpointMBeans do not support attributes.
*
* @param attribute the attribute name
* @return never returns normally
* @throws AttributeNotFoundException always thrown
*/
@Override
public Object getAttribute(String attribute)
throws AttributeNotFoundException, MBeanException, ReflectionException;
/**
* Set an attribute value.
* NOT SUPPORTED - EndpointMBeans do not support attributes.
*
* @param attribute the attribute to set
* @throws AttributeNotFoundException always thrown
*/
@Override
public void setAttribute(Attribute attribute)
throws AttributeNotFoundException, InvalidAttributeValueException,
MBeanException, ReflectionException;
/**
* Get multiple attribute values.
* Returns empty list - EndpointMBeans do not support attributes.
*
* @param attributes the attribute names
* @return empty AttributeList
*/
@Override
public AttributeList getAttributes(String[] attributes);
/**
* Set multiple attribute values.
* Returns empty list - EndpointMBeans do not support attributes.
*
* @param attributes the attributes to set
* @return empty AttributeList
*/
@Override
public AttributeList setAttributes(AttributeList attributes);
}/**
* EndpointDiscoverer for JMX endpoints.
* Discovers and creates ExposableJmxEndpoint instances from endpoint beans.
*
* Thread-safe: Yes
* Since: Spring Boot 2.0+
*
* Discovery process:
* 1. Scans for @JmxEndpoint and @Endpoint annotated beans
* 2. Applies endpoint filters to determine which endpoints to expose
* 3. Discovers operations from @ReadOperation, @WriteOperation, @DeleteOperation
* 4. Applies operation filters to determine which operations to expose
* 5. Creates DiscoveredJmxEndpoint instances with JmxOperation objects
*
* @see EndpointDiscoverer
* @see JmxEndpointsSupplier
* @see ExposableJmxEndpoint
*/
public class JmxEndpointDiscoverer
extends EndpointDiscoverer<ExposableJmxEndpoint, JmxOperation>
implements JmxEndpointsSupplier {
/**
* Create a new JmxEndpointDiscoverer instance.
*
* @param applicationContext the source application context
* @param parameterValueMapper the parameter value mapper
* @param invokerAdvisors invoker advisors to apply
* @param endpointFilters endpoint filters to apply
* @param operationFilters operation filters to apply
* @since 3.4.0
*/
public JmxEndpointDiscoverer(
ApplicationContext applicationContext,
ParameterValueMapper parameterValueMapper,
Collection<OperationInvokerAdvisor> invokerAdvisors,
Collection<EndpointFilter<ExposableJmxEndpoint>> endpointFilters,
Collection<OperationFilter<JmxOperation>> operationFilters);
// Inherited from JmxEndpointsSupplier:
// - Collection<ExposableJmxEndpoint> getEndpoints()
}
/**
* EndpointFilter for endpoints discovered by JmxEndpointDiscoverer.
* Ensures only JMX-compatible endpoints are processed.
*
* Thread-safe: Yes
* Since: Spring Boot 2.0+
*
* Used by:
* - @JmxEndpoint annotation filtering
* - @EndpointJmxExtension annotation filtering
*
* @see EndpointFilter
* @see DiscovererEndpointFilter
*/
class JmxEndpointFilter extends DiscovererEndpointFilter {
// Package-private - used internally by framework
// Constructor associates with JmxEndpointDiscoverer
}Classes for exporting endpoints as JMX MBeans and mapping responses to JMX-compatible types.
/**
* Exports ExposableJmxEndpoint instances to an MBeanServer.
* Automatically registers discovered JMX endpoints as MBeans on initialization
* and unregisters them on destruction.
*
* Thread-safe: Yes (immutable after construction)
* Package: org.springframework.boot.actuate.endpoint.jmx
* @since 2.0.0
*/
public class JmxEndpointExporter implements InitializingBean, DisposableBean, BeanClassLoaderAware {
/**
* Creates a new JmxEndpointExporter instance.
*
* @param mBeanServer MBeanServer to register endpoints with
* @param objectNameFactory Factory for creating ObjectNames for endpoints
* @param responseMapper Mapper for converting operation responses to JMX-compatible types
* @param endpoints Collection of JMX endpoints to export
*/
public JmxEndpointExporter(
MBeanServer mBeanServer,
EndpointObjectNameFactory objectNameFactory,
JmxOperationResponseMapper responseMapper,
Collection<? extends ExposableJmxEndpoint> endpoints);
/**
* Set the bean class loader for MBean registration.
*
* @param classLoader Class loader to use
*/
public void setBeanClassLoader(ClassLoader classLoader);
/**
* Called after properties are set.
* Registers all endpoints as MBeans with the MBeanServer.
*/
public void afterPropertiesSet();
/**
* Called on bean destruction.
* Unregisters all MBeans from the MBeanServer.
*
* @throws Exception if unregistration fails
*/
public void destroy() throws Exception;
}
/**
* A factory to create an ObjectName for an EndpointMBean.
* Strategy interface for customizing how endpoint ObjectNames are generated.
*
* Thread-safe: Implementation dependent
* Package: org.springframework.boot.actuate.endpoint.jmx
* @since 2.0.0
*/
@FunctionalInterface
public interface EndpointObjectNameFactory {
/**
* Get the ObjectName for the given endpoint.
*
* @param endpoint JMX endpoint to create an ObjectName for
* @return ObjectName for the endpoint
* @throws MalformedObjectNameException if the ObjectName is invalid
*/
ObjectName getObjectName(ExposableJmxEndpoint endpoint) throws MalformedObjectNameException;
}
/**
* Maps an operation's response to a JMX-friendly form.
* Strategy interface for converting endpoint operation responses into types
* that can be returned from JMX MBean operations.
*
* Thread-safe: Implementation dependent
* Package: org.springframework.boot.actuate.endpoint.jmx
* @since 2.0.0
*/
public interface JmxOperationResponseMapper {
/**
* Map the response type to a JMX-compatible type.
* Determines the return type that will be advertised in the MBeanOperationInfo.
*
* @param responseType Original response type
* @return JMX-compatible type (e.g., String for JSON responses)
*/
Class<?> mapResponseType(Class<?> responseType);
/**
* Map a response value to a JMX-compatible value.
* Converts the operation result to a format suitable for JMX.
*
* @param response Original response value (may be null)
* @return JMX-compatible value (may be null)
*/
@Nullable Object mapResponse(@Nullable Object response);
}
/**
* JmxOperationResponseMapper that delegates to a Jackson JsonMapper to return a JSON response.
* Converts operation responses to JSON strings for JMX transport.
*
* Thread-safe: Yes (immutable after construction)
* Package: org.springframework.boot.actuate.endpoint.jmx
* @since 2.0.0
*/
public class JacksonJmxOperationResponseMapper implements JmxOperationResponseMapper {
/**
* Creates a new JacksonJmxOperationResponseMapper.
*
* @param jsonMapper Jackson JsonMapper to use for JSON serialization (may be null for default)
*/
public JacksonJmxOperationResponseMapper(@Nullable JsonMapper jsonMapper);
/**
* Map the response type to String.class (for JSON representation).
*
* @param responseType Original response type
* @return String.class
*/
@Override
public Class<?> mapResponseType(Class<?> responseType);
/**
* Map a response value to its JSON string representation.
*
* @param response Original response value (may be null)
* @return JSON string or null if response is null
*/
@Override
@Contract("!null -> !null")
public @Nullable Object mapResponse(@Nullable Object response);
}
/**
* JmxOperationResponseMapper that delegates to a Jackson 2 ObjectMapper to return a JSON response.
* Legacy mapper for Jackson 2.x compatibility.
*
* Thread-safe: Yes (immutable after construction)
* Package: org.springframework.boot.actuate.endpoint.jmx
* @since 4.0.0
* @deprecated since 4.0.0 for removal in 4.2.0 in favor of JacksonJmxOperationResponseMapper
*/
@Deprecated(since = "4.0.0", forRemoval = true)
public class Jackson2JmxOperationResponseMapper implements JmxOperationResponseMapper {
/**
* Creates a new Jackson2JmxOperationResponseMapper.
* Use JacksonJmxOperationResponseMapper for new code.
*
* @param objectMapper Jackson 2 ObjectMapper to use for JSON serialization (may be null for default)
*/
public Jackson2JmxOperationResponseMapper(@Nullable ObjectMapper objectMapper);
/**
* Map the response type to String.class (for JSON representation).
*
* @param responseType Original response type
* @return String.class
*/
@Override
public Class<?> mapResponseType(Class<?> responseType);
/**
* Map a response value to its JSON string representation.
*
* @param response Original response value (may be null)
* @return JSON string or null if response is null
*/
@Override
@Contract("!null -> !null")
public @Nullable Object mapResponse(@Nullable Object response);
}package com.example.actuator;
import org.springframework.boot.actuate.endpoint.jmx.annotation.JmxEndpoint;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
import org.springframework.boot.actuate.endpoint.annotation.WriteOperation;
import org.springframework.boot.actuate.endpoint.Access;
import org.springframework.stereotype.Component;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.lang.management.MemoryUsage;
import java.util.Map;
/**
* JMX-only monitoring endpoint for memory management.
*
* Thread-safe: Yes
* Exposure: JMX only (not HTTP)
* ObjectName: org.springframework.boot:type=Endpoint,name=JvmMemory
* Since: Application 1.0
*
* Access via JConsole:
* - Connect to JVM
* - Navigate to MBeans tab
* - Find org.springframework.boot → Endpoint → JvmMemory
*/
@JmxEndpoint(id = "jvmMemory", defaultAccess = Access.UNRESTRICTED)
@Component
public class JvmMemoryEndpoint {
private final MemoryMXBean memoryMXBean;
public JvmMemoryEndpoint() {
this.memoryMXBean = ManagementFactory.getMemoryMXBean();
}
/**
* Get heap memory usage.
* JMX operation: getHeapMemory()
*
* @return Map with heap memory statistics
*/
@ReadOperation
public Map<String, Object> getHeapMemory() {
MemoryUsage heap = memoryMXBean.getHeapMemoryUsage();
return Map.of(
"init", formatBytes(heap.getInit()),
"used", formatBytes(heap.getUsed()),
"committed", formatBytes(heap.getCommitted()),
"max", formatBytes(heap.getMax()),
"usagePercent", (heap.getUsed() * 100.0) / heap.getMax()
);
}
/**
* Get non-heap memory usage.
* JMX operation: getNonHeapMemory()
*
* @return Map with non-heap memory statistics
*/
@ReadOperation
public Map<String, Object> getNonHeapMemory() {
MemoryUsage nonHeap = memoryMXBean.getNonHeapMemoryUsage();
return Map.of(
"init", formatBytes(nonHeap.getInit()),
"used", formatBytes(nonHeap.getUsed()),
"committed", formatBytes(nonHeap.getCommitted()),
"max", formatBytes(nonHeap.getMax())
);
}
/**
* Trigger garbage collection.
* JMX operation: triggerGc()
*
* WARNING: Should be used sparingly as it impacts performance.
*/
@WriteOperation
public void triggerGc() {
memoryMXBean.gc();
}
private String formatBytes(long bytes) {
if (bytes < 1024) return bytes + " B";
int exp = (int) (Math.log(bytes) / Math.log(1024));
char pre = "KMGTPE".charAt(exp - 1);
return String.format("%.1f %sB", bytes / Math.pow(1024, exp), pre);
}
}package com.example.actuator;
import org.springframework.boot.actuate.endpoint.annotation.*;
import org.springframework.boot.actuate.endpoint.jmx.annotation.EndpointJmxExtension;
import org.springframework.stereotype.Component;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* Technology-agnostic cache endpoint (HTTP + JMX).
*/
@Endpoint(id = "cacheManagement")
@Component
public class CacheEndpoint {
private final Map<String, CacheData> caches = new ConcurrentHashMap<>();
/**
* Get cache statistics.
* Available via both HTTP and JMX.
*
* @return Map of cache statistics
*/
@ReadOperation
public Map<String, CacheData> getCacheStats() {
return Map.copyOf(caches);
}
/**
* Clear specific cache.
* Available via both HTTP and JMX.
*
* @param name Cache name
*/
@DeleteOperation
public void clearCache(@Selector String name) {
caches.remove(name);
}
public void updateCache(String name, CacheData data) {
caches.put(name, data);
}
public record CacheData(long hits, long misses, long size) {}
}
/**
* JMX-specific extension with additional operations.
*
* Thread-safe: Yes
* ObjectName: org.springframework.boot:type=Endpoint,name=CacheManagement
* Since: Application 1.0
*
* Adds JMX-only operations for bulk cache management.
*/
@EndpointJmxExtension(endpoint = CacheEndpoint.class)
@Component
public class CacheJmxExtension {
private final CacheEndpoint delegate;
public CacheJmxExtension(CacheEndpoint delegate) {
this.delegate = delegate;
}
/**
* Get cache statistics (delegates to base endpoint).
* JMX operation: getCacheStats()
*/
@ReadOperation
public Map<String, CacheEndpoint.CacheData> getCacheStats() {
return delegate.getCacheStats();
}
/**
* Clear all caches (JMX-only operation).
* JMX operation: clearAllCaches()
*
* This operation is only available via JMX to prevent
* accidental HTTP-based cache clearing.
*/
@WriteOperation
public void clearAllCaches() {
Map<String, CacheEndpoint.CacheData> stats = delegate.getCacheStats();
for (String cacheName : stats.keySet()) {
delegate.clearCache(cacheName);
}
}
/**
* Get cache hit ratio (JMX-only convenience operation).
* JMX operation: getCacheHitRatio(String)
*
* @param name Cache name
* @return Hit ratio as percentage
*/
@ReadOperation
public double getCacheHitRatio(@Selector String name) {
Map<String, CacheEndpoint.CacheData> stats = delegate.getCacheStats();
CacheEndpoint.CacheData data = stats.get(name);
if (data == null) {
return 0.0;
}
long total = data.hits() + data.misses();
return total == 0 ? 0.0 : (data.hits() * 100.0) / total;
}
/**
* Warm up cache with test data (JMX-only operation).
* JMX operation: warmUpCache(String, long)
*
* @param name Cache name
* @param size Number of entries to pre-load
*/
@WriteOperation
public void warmUpCache(String name, long size) {
// Simulate cache warm-up
delegate.updateCache(name, new CacheEndpoint.CacheData(0, 0, size));
}
}package com.example.actuator;
import org.springframework.boot.actuate.endpoint.jmx.EndpointObjectNameFactory;
import org.springframework.boot.actuate.endpoint.jmx.ExposableJmxEndpoint;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jmx.support.ObjectNameManager;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
/**
* Configuration for custom JMX object naming.
*
* By default, endpoints are registered as:
* org.springframework.boot:type=Endpoint,name=<EndpointId>
*
* This factory allows customizing the naming scheme.
*/
@Configuration
public class JmxConfiguration {
/**
* Custom ObjectName factory using application-specific domain.
*
* Registers endpoints as:
* com.example.actuator:type=Management,component=<EndpointId>
*/
@Bean
public EndpointObjectNameFactory customObjectNameFactory() {
return new EndpointObjectNameFactory() {
@Override
public ObjectName getObjectName(ExposableJmxEndpoint endpoint)
throws MalformedObjectNameException {
String endpointId = endpoint.getEndpointId().toString();
return ObjectNameManager.getInstance(
String.format(
"com.example.actuator:type=Management,component=%s",
endpointId
)
);
}
};
}
/**
* Example of hierarchical ObjectName structure.
*
* Registers endpoints as:
* com.example:category=Actuator,type=Endpoint,name=<EndpointId>
*/
@Bean
public EndpointObjectNameFactory hierarchicalObjectNameFactory() {
return endpoint -> {
String endpointId = endpoint.getEndpointId().toString();
return ObjectNameManager.getInstance(
String.format(
"com.example:category=Actuator,type=Endpoint,name=%s",
endpointId
)
);
};
}
}package com.example.actuator;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.BeforeEach;
import java.util.Map;
import static org.assertj.core.api.Assertions.*;
class JvmMemoryEndpointTest {
private JvmMemoryEndpoint endpoint;
@BeforeEach
void setUp() {
endpoint = new JvmMemoryEndpoint();
}
@Test
void getHeapMemory_ReturnsValidStatistics() {
Map<String, Object> heap = endpoint.getHeapMemory();
assertThat(heap).containsKeys("init", "used", "committed", "max", "usagePercent");
assertThat(heap.get("usagePercent")).isInstanceOf(Double.class);
double usage = (Double) heap.get("usagePercent");
assertThat(usage).isBetween(0.0, 100.0);
}
@Test
void getNonHeapMemory_ReturnsValidStatistics() {
Map<String, Object> nonHeap = endpoint.getNonHeapMemory();
assertThat(nonHeap).containsKeys("init", "used", "committed", "max");
}
@Test
void triggerGc_DoesNotThrowException() {
// Should complete without error
assertThatNoException().isThrownBy(() -> endpoint.triggerGc());
}
}package com.example.actuator;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.BeforeEach;
import java.util.Map;
import static org.assertj.core.api.Assertions.*;
class CacheJmxExtensionTest {
private CacheEndpoint endpoint;
private CacheJmxExtension extension;
@BeforeEach
void setUp() {
endpoint = new CacheEndpoint();
extension = new CacheJmxExtension(endpoint);
// Add test data
endpoint.updateCache("cache1", new CacheEndpoint.CacheData(100, 10, 500));
endpoint.updateCache("cache2", new CacheEndpoint.CacheData(200, 50, 1000));
}
@Test
void getCacheHitRatio_CalculatesCorrectly() {
double ratio = extension.getCacheHitRatio("cache1");
assertThat(ratio).isEqualTo(90.909, within(0.01));
}
@Test
void getCacheHitRatio_WithNonexistentCache_ReturnsZero() {
double ratio = extension.getCacheHitRatio("nonexistent");
assertThat(ratio).isZero();
}
@Test
void clearAllCaches_RemovesAllCaches() {
extension.clearAllCaches();
Map<String, CacheEndpoint.CacheData> stats = endpoint.getCacheStats();
assertThat(stats).isEmpty();
}
@Test
void warmUpCache_CreatesCache() {
extension.warmUpCache("cache3", 5000);
Map<String, CacheEndpoint.CacheData> stats = endpoint.getCacheStats();
assertThat(stats).containsKey("cache3");
assertThat(stats.get("cache3").size()).isEqualTo(5000);
}
}Problem: Cannot find endpoint MBean in JConsole
Causes:
Solutions:
# Solution 1: Enable JMX
spring.jmx.enabled=true
# Solution 2: Expose endpoint
management.endpoints.jmx.exposure.include=*
# Or specific: management.endpoints.jmx.exposure.include=cacheManagement
# Solution 3: Check domain
management.endpoints.jmx.domain=org.springframework.bootProblem: JConsole connection refused
Causes:
Solutions:
# Solution 1: Start JVM with JMX port
java -Dcom.sun.management.jmxremote.port=9010 \
-Dcom.sun.management.jmxremote.authenticate=false \
-Dcom.sun.management.jmxremote.ssl=false \
-jar application.jar
# Solution 2: For production, enable authentication
java -Dcom.sun.management.jmxremote.port=9010 \
-Dcom.sun.management.jmxremote.authenticate=true \
-Dcom.sun.management.jmxremote.ssl=true \
-Dcom.sun.management.jmxremote.password.file=/path/to/password.file \
-jar application.jar
# Solution 3: Check firewall allows JMX portProblem: MBean registration exception on startup
Causes:
Solutions:
// Solution 1: Use unique ObjectNames
@Bean
public EndpointObjectNameFactory uniqueObjectNameFactory() {
return endpoint -> {
String id = endpoint.getEndpointId().toString();
// Add timestamp or instance ID for uniqueness
return ObjectNameManager.getInstance(
String.format(
"com.example:type=Endpoint,name=%s,instance=%d",
id, System.currentTimeMillis()
)
);
};
}
// Solution 2: Validate ObjectName format
String objectName = "com.example:type=Endpoint,name=Test";
ObjectNameManager.getInstance(objectName); // Validates format// JMX operations have slightly higher overhead than direct calls
// due to JMX infrastructure (serialization, invocation, etc.)
@ReadOperation
public Map<String, Object> getStats() {
// Keep operations fast - they're called via JMX
// Avoid expensive operations in JMX read operations
return cachedStats; // ✓ Fast
// return computeExpensiveStats(); // ❌ Slow
}# For remote JMX, consider:
# 1. Network latency
# - JMX calls are synchronous
# - High latency impacts operation time
# 2. Security
# - Always use SSL in production
com.sun.management.jmxremote.ssl=true
# 3. Authentication
# - Enable authentication
com.sun.management.jmxremote.authenticate=true
# 4. Port configuration
# - May need both RMI registry and RMI server ports
com.sun.management.jmxremote.port=9010
com.sun.management.jmxremote.rmi.port=9010management:
endpoints:
jmx:
# Expose all endpoints via JMX
exposure:
include: "*"
# Or specific endpoints
# include: cacheManagement,jvmMemory
# JMX domain for endpoints
domain: org.springframework.boot
# Endpoint naming
unique-names: false
# Enable/disable JMX entirely
enabled: true
spring:
# JMX support
jmx:
enabled: true
# Default domain for Spring beans
default-domain: com.example
# MBean server
server: mbeanServer# 1. Start application with JMX
java -Dcom.sun.management.jmxremote.port=9010 \
-Dcom.sun.management.jmxremote.authenticate=false \
-Dcom.sun.management.jmxremote.ssl=false \
-jar application.jar
# 2. Connect JConsole
jconsole localhost:9010
# 3. Navigate to MBeans tab
# 4. Find actuator endpoints at:
# org.springframework.boot → Endpoint → <EndpointId>
# 5. View operations and attributes
# - Attributes: Read operations without parameters
# - Operations: Write operations or read with parameters
# 6. Invoke operations
# - Select operation
# - Enter parameters if required
# - Click "Execute"