or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

audit.mdbuiltin-endpoints.mdendpoint-framework.mdhttp-exchanges.mdindex.mdinfo-contributors.mdjmx-integration.mdmanagement-operations.mdoperation-invocation.mdsanitization.mdsecurity.mdweb-integration.md
tile.json

jmx-integration.mddocs/

JMX Integration

QUICK REFERENCE

Key Classes and Packages

// 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.DynamicMBean

Quick Decision Guide

Need JMX exposure?
│
├─ Only JMX (no HTTP)?
│  └─ Use @JmxEndpoint
│
├─ Both JMX and HTTP?
│  └─ Use @Endpoint (technology-agnostic)
│
└─ JMX-specific behavior for existing endpoint?
   └─ Use @EndpointJmxExtension

Default JMX Configuration

# JMX enabled by default
management.endpoints.jmx.exposure.include=*

# Domain and naming
management.endpoints.jmx.domain=org.springframework.boot
spring.jmx.enabled=true

Common JMX Tools

ToolPurposeConnection
JConsoleBuilt-in JVM monitoringJDK bundled
VisualVMAdvanced profilingDownload separately
JMC (Mission Control)Enterprise monitoringOracle JDK
jmxtermCommand-line clientStandalone tool

AGENT GUIDANCE

When to Use JMX

Use JMX when:

  • Operations team prefers JMX tools (JConsole, VisualVM)
  • Need integration with existing JMX infrastructure
  • Monitoring multiple JVMs from single console
  • Enterprise monitoring systems expect JMX
  • Network policy blocks HTTP but allows JMX

Don't use JMX when:

  • Cloud-native environment (use HTTP/metrics)
  • Kubernetes deployment (use HTTP endpoints)
  • Modern monitoring (Prometheus, Grafana)
  • Firewall blocks JMX ports

Pattern vs Anti-Pattern

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.

Capabilities

JMX Endpoint Annotations

/**
 * 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();
}

JMX Endpoint Interfaces

/**
 * 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()
}

JMX Operation Types

/**
 * 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();
}

JMX MBean Infrastructure

/**
 * 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);
}

JMX Endpoint Discovery

/**
 * 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
}

JMX Export

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);
}

COMPLETE WORKING EXAMPLES

Example 1: JMX-Only Monitoring Endpoint

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);
    }
}

Example 2: JMX Extension for Cache Endpoint

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));
    }
}

Example 3: Custom ObjectName Factory

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
                )
            );
        };
    }
}

TESTING EXAMPLES

Test 1: JMX Endpoint Operations

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());
    }
}

Test 2: JMX Extension

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);
    }
}

TROUBLESHOOTING

Common Error: Endpoint Not Visible in JConsole

Problem: Cannot find endpoint MBean in JConsole

Causes:

  1. JMX not enabled
  2. Endpoint not exposed
  3. Wrong ObjectName

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.boot

Common Error: Cannot Connect to JMX

Problem: JConsole connection refused

Causes:

  1. JMX port not configured
  2. Authentication required
  3. Firewall blocking connection

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 port

Common Error: MBean Registration Failed

Problem: MBean registration exception on startup

Causes:

  1. Duplicate ObjectName
  2. Invalid ObjectName format
  3. MBeanServer conflict

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

PERFORMANCE NOTES

JMX Operation Overhead

// 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
}

Remote JMX Considerations

# 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=9010

Configuration Reference

management:
  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

JConsole Usage Guide

# 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"

Cross-References

  • For endpoint basics: Endpoint Framework
  • For HTTP alternative: Web Integration
  • For built-in endpoints: Built-in Endpoints
  • For security: Security Integration