Creating Custom MBeans in Java

MBeans (Managed Beans) are Java objects that represent resources for management and monitoring in JMX (Java Management Extensions). They expose attributes and operations that can be managed at runtime.

1. JMX and MBean Overview

What are MBeans?

  • MBeans = Managed Beans
  • Java objects that follow specific design patterns
  • Expose management interface (attributes, operations, notifications)
  • Can be monitored and managed through JMX clients

Types of MBeans

  1. Standard MBeans - Interface-based, simplest
  2. Dynamic MBeans - Runtime metadata, flexible
  3. Model MBeans - Fully configurable at runtime
  4. MXBeans - Simplified with open types

2. Standard MBeans

Step 1: Define MBean Interface

// MBean interface - must follow naming convention: ClassNameMBean
public interface SystemConfigMBean {
// Attributes (getter/setter methods)
int getThreadCount();
void setThreadCount(int count);
String getSchemaName();
void setSchemaName(String schemaName);
// Read-only attribute (only getter)
long getStartTime();
// Operations (methods)
void doCacheRefresh();
String displayConfig();
// Operation with parameters
boolean updateConfig(String configName, String value);
}

Step 2: Implement the MBean

public class SystemConfig implements SystemConfigMBean {
private int threadCount = 10;
private String schemaName = "default";
private final long startTime;
public SystemConfig() {
this.startTime = System.currentTimeMillis();
}
// Attribute getters and setters
@Override
public int getThreadCount() {
return threadCount;
}
@Override
public void setThreadCount(int threadCount) {
if (threadCount < 1 || threadCount > 100) {
throw new IllegalArgumentException("Thread count must be between 1 and 100");
}
this.threadCount = threadCount;
System.out.println("Thread count set to: " + threadCount);
}
@Override
public String getSchemaName() {
return schemaName;
}
@Override
public void setSchemaName(String schemaName) {
if (schemaName == null || schemaName.trim().isEmpty()) {
throw new IllegalArgumentException("Schema name cannot be null or empty");
}
this.schemaName = schemaName;
System.out.println("Schema name set to: " + schemaName);
}
@Override
public long getStartTime() {
return startTime;
}
// Operations
@Override
public void doCacheRefresh() {
System.out.println("Refreshing cache...");
// Simulate cache refresh
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println("Cache refresh completed");
}
@Override
public String displayConfig() {
return String.format("SystemConfig{threadCount=%d, schemaName='%s', uptime=%d ms}",
threadCount, schemaName, System.currentTimeMillis() - startTime);
}
@Override
public boolean updateConfig(String configName, String value) {
switch (configName) {
case "threadCount":
setThreadCount(Integer.parseInt(value));
return true;
case "schemaName":
setSchemaName(value);
return true;
default:
System.out.println("Unknown config: " + configName);
return false;
}
}
}

Step 3: Register and Use MBean

import javax.management.*;
import java.lang.management.ManagementFactory;
public class StandardMBeanServer {
public static void main(String[] args) throws Exception {
// Get the Platform MBean Server
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
// Create object name
ObjectName objectName = new ObjectName("com.example:type=SystemConfig,name=SystemConfig1");
// Create and register MBean
SystemConfig mbean = new SystemConfig();
mbs.registerMBean(mbean, objectName);
System.out.println("SystemConfig MBean registered successfully!");
System.out.println("Connect with JConsole or JVisualVM to manage the MBean");
System.out.println("Domain: com.example, Type: SystemConfig, Name: SystemConfig1");
// Keep application running
Thread.sleep(Long.MAX_VALUE);
}
}

3. MXBeans (Simplified MBeans)

MXBean Interface

// MXBean interface - name ends with MXBean
public interface CacheManagerMXBean {
// Simple types (automatically converted)
int getCacheSize();
void setCacheSize(int size);
long getHitCount();
long getMissCount();
double getHitRatio();
// Complex types (must be compatible types)
Map<String, String> getCacheStats();
List<String> getCachedKeys();
// Operations
void clearCache();
boolean containsKey(String key);
String getCacheInfo();
}

MXBean Implementation

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
public class CacheManager implements CacheManagerMXBean {
private final Map<String, String> cache = new ConcurrentHashMap<>();
private int maxCacheSize = 1000;
private long hitCount = 0;
private long missCount = 0;
public void put(String key, String value) {
if (cache.size() >= maxCacheSize) {
// Simple eviction - remove first entry
Iterator<String> iterator = cache.keySet().iterator();
if (iterator.hasNext()) {
cache.remove(iterator.next());
}
}
cache.put(key, value);
}
public String get(String key) {
String value = cache.get(key);
if (value != null) {
hitCount++;
} else {
missCount++;
}
return value;
}
// Attribute implementations
@Override
public int getCacheSize() {
return cache.size();
}
@Override
public void setCacheSize(int size) {
if (size < 1) {
throw new IllegalArgumentException("Cache size must be positive");
}
this.maxCacheSize = size;
// Evict excess entries if necessary
while (cache.size() > maxCacheSize) {
Iterator<String> iterator = cache.keySet().iterator();
if (iterator.hasNext()) {
cache.remove(iterator.next());
}
}
}
@Override
public long getHitCount() {
return hitCount;
}
@Override
public long getMissCount() {
return missCount;
}
@Override
public double getHitRatio() {
long total = hitCount + missCount;
return total == 0 ? 0.0 : (double) hitCount / total;
}
@Override
public Map<String, String> getCacheStats() {
Map<String, String> stats = new HashMap<>();
stats.put("size", String.valueOf(cache.size()));
stats.put("maxSize", String.valueOf(maxCacheSize));
stats.put("hitCount", String.valueOf(hitCount));
stats.put("missCount", String.valueOf(missCount));
stats.put("hitRatio", String.format("%.2f", getHitRatio()));
return stats;
}
@Override
public List<String> getCachedKeys() {
return new ArrayList<>(cache.keySet());
}
// Operation implementations
@Override
public void clearCache() {
cache.clear();
hitCount = 0;
missCount = 0;
System.out.println("Cache cleared");
}
@Override
public boolean containsKey(String key) {
return cache.containsKey(key);
}
@Override
public String getCacheInfo() {
return String.format(
"CacheManager{size=%d, maxSize=%d, hits=%d, misses=%d, ratio=%.2f}",
cache.size(), maxCacheSize, hitCount, missCount, getHitRatio());
}
}

Registering MXBean

public class MXBeanServer {
public static void main(String[] args) throws Exception {
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
// Create object name for MXBean
ObjectName mxbeanName = new ObjectName("com.example:type=CacheManager,name=CacheManager1");
// Create and register MXBean
CacheManager cacheManager = new CacheManager();
mbs.registerMBean(cacheManager, mxbeanName);
// Populate some data
for (int i = 0; i < 100; i++) {
cacheManager.put("key" + i, "value" + i);
}
// Simulate some cache access
Thread accessSimulator = new Thread(() -> {
Random random = new Random();
while (!Thread.currentThread().isInterrupted()) {
try {
cacheManager.get("key" + random.nextInt(150)); // Some misses
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
});
accessSimulator.setDaemon(true);
accessSimulator.start();
System.out.println("CacheManager MXBean registered!");
System.out.println("Connect with JConsole to monitor cache statistics");
Thread.sleep(Long.MAX_VALUE);
}
}

4. Dynamic MBeans

Custom Dynamic MBean Implementation

import javax.management.*;
import java.util.*;
public class DatabaseConnectionPool implements DynamicMBean {
private final Map<String, Object> attributes = new HashMap<>();
private final MBeanInfo mbeanInfo;
private int maxConnections = 50;
private int activeConnections = 0;
private int idleConnections = 10;
private boolean poolEnabled = true;
public DatabaseConnectionPool() {
initializeMBeanInfo();
updateAttributes();
}
private void updateAttributes() {
attributes.put("MaxConnections", maxConnections);
attributes.put("ActiveConnections", activeConnections);
attributes.put("IdleConnections", idleConnections);
attributes.put("PoolEnabled", poolEnabled);
attributes.put("TotalConnections", activeConnections + idleConnections);
attributes.put("Utilization", maxConnections == 0 ? 0 : 
(double) activeConnections / maxConnections);
}
private void initializeMBeanInfo() {
// Define attributes
MBeanAttributeInfo[] attributes = {
new MBeanAttributeInfo(
"MaxConnections", 
"int", 
"Maximum number of connections", 
true,   // isReadable
true,   // isWritable
false   // isIs
),
new MBeanAttributeInfo(
"ActiveConnections", 
"int", 
"Number of active connections", 
true, 
false, 
false
),
new MBeanAttributeInfo(
"IdleConnections", 
"int", 
"Number of idle connections", 
true, 
false, 
false
),
new MBeanAttributeInfo(
"TotalConnections", 
"int", 
"Total connections (active + idle)", 
true, 
false, 
false
),
new MBeanAttributeInfo(
"PoolEnabled", 
"boolean", 
"Whether connection pool is enabled", 
true, 
true, 
false
),
new MBeanAttributeInfo(
"Utilization", 
"double", 
"Pool utilization ratio", 
true, 
false, 
false
)
};
// Define operations
MBeanOperationInfo[] operations = {
new MBeanOperationInfo(
"getPoolStatus",
"Get detailed pool status",
null, // no parameters
"String",
MBeanOperationInfo.INFO
),
new MBeanOperationInfo(
"resetPool",
"Reset the connection pool",
null,
"void",
MBeanOperationInfo.ACTION
),
new MBeanOperationInfo(
"borrowConnection",
"Borrow a connection from pool",
null,
"boolean",
MBeanOperationInfo.ACTION
),
new MBeanOperationInfo(
"returnConnection",
"Return a connection to pool",
null,
"void",
MBeanOperationInfo.ACTION
)
};
// Construct MBeanInfo
mbeanInfo = new MBeanInfo(
this.getClass().getName(),
"Database Connection Pool MBean",
attributes,
null, // constructors
operations,
null  // notifications
);
}
// DynamicMBean interface implementation
@Override
public Object getAttribute(String attribute) throws AttributeNotFoundException, MBeanException, ReflectionException {
if (!attributes.containsKey(attribute)) {
throw new AttributeNotFoundException("Attribute not found: " + attribute);
}
return attributes.get(attribute);
}
@Override
public void setAttribute(Attribute attribute) throws AttributeNotFoundException, InvalidAttributeValueException, MBeanException, ReflectionException {
String name = attribute.getName();
Object value = attribute.getValue();
switch (name) {
case "MaxConnections":
if (value instanceof Integer) {
maxConnections = (Integer) value;
System.out.println("Max connections set to: " + maxConnections);
} else {
throw new InvalidAttributeValueException("MaxConnections must be an integer");
}
break;
case "PoolEnabled":
if (value instanceof Boolean) {
poolEnabled = (Boolean) value;
System.out.println("Pool enabled set to: " + poolEnabled);
} else {
throw new InvalidAttributeValueException("PoolEnabled must be a boolean");
}
break;
default:
throw new AttributeNotFoundException("Attribute not writable: " + name);
}
updateAttributes();
}
@Override
public AttributeList getAttributes(String[] attributes) {
AttributeList result = new AttributeList();
for (String attribute : attributes) {
try {
result.add(new Attribute(attribute, getAttribute(attribute)));
} catch (Exception e) {
// Skip attributes that can't be retrieved
}
}
return result;
}
@Override
public AttributeList setAttributes(AttributeList attributes) {
AttributeList result = new AttributeList();
for (Object attribute : attributes) {
if (attribute instanceof Attribute) {
Attribute attr = (Attribute) attribute;
try {
setAttribute(attr);
result.add(new Attribute(attr.getName(), getAttribute(attr.getName())));
} catch (Exception e) {
// Skip attributes that can't be set
}
}
}
return result;
}
@Override
public Object invoke(String actionName, Object[] params, String[] signature) throws MBeanException, ReflectionException {
switch (actionName) {
case "getPoolStatus":
return getPoolStatus();
case "resetPool":
resetPool();
return null;
case "borrowConnection":
return borrowConnection();
case "returnConnection":
returnConnection();
return null;
default:
throw new ReflectionException(new NoSuchMethodException("Operation not found: " + actionName));
}
}
@Override
public MBeanInfo getMBeanInfo() {
return mbeanInfo;
}
// Operation implementations
private String getPoolStatus() {
return String.format(
"DatabaseConnectionPool{max=%d, active=%d, idle=%d, enabled=%s, utilization=%.2f}",
maxConnections, activeConnections, idleConnections, poolEnabled,
(double) activeConnections / maxConnections);
}
private void resetPool() {
activeConnections = 0;
idleConnections = Math.min(10, maxConnections);
System.out.println("Connection pool reset");
updateAttributes();
}
private boolean borrowConnection() {
if (!poolEnabled || idleConnections == 0) {
return false;
}
idleConnections--;
activeConnections++;
updateAttributes();
return true;
}
private void returnConnection() {
if (activeConnections > 0) {
activeConnections--;
idleConnections++;
updateAttributes();
}
}
// Simulate connection usage
public void simulateUsage() {
Thread simulator = new Thread(() -> {
Random random = new Random();
while (!Thread.currentThread().isInterrupted()) {
try {
// Randomly borrow and return connections
if (random.nextBoolean() && borrowConnection()) {
Thread.sleep(random.nextInt(1000));
returnConnection();
}
Thread.sleep(500);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
});
simulator.setDaemon(true);
simulator.start();
}
}

Registering Dynamic MBean

public class DynamicMBeanServer {
public static void main(String[] args) throws Exception {
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
ObjectName objectName = new ObjectName("com.example:type=DatabaseConnectionPool,name=Pool1");
DatabaseConnectionPool poolMBean = new DatabaseConnectionPool();
mbs.registerMBean(poolMBean, objectName);
// Start simulation
poolMBean.simulateUsage();
System.out.println("Dynamic MBean registered successfully!");
System.out.println("Available attributes: MaxConnections, ActiveConnections, IdleConnections, PoolEnabled, Utilization");
System.out.println("Available operations: getPoolStatus, resetPool, borrowConnection, returnConnection");
Thread.sleep(Long.MAX_VALUE);
}
}

5. Notifications in MBeans

MBean with Notifications

import javax.management.*;
import java.util.concurrent.atomic.AtomicLong;
public class ServiceMonitor extends NotificationBroadcasterSupport implements ServiceMonitorMBean {
private final AtomicLong sequenceNumber = new AtomicLong(1);
private boolean serviceRunning = false;
private int requestCount = 0;
private int errorCount = 0;
private double responseTimeThreshold = 1000.0; // milliseconds
// Notification types
public static final String SERVICE_STARTED = "service.started";
public static final String SERVICE_STOPPED = "service.stopped";
public static final String HIGH_RESPONSE_TIME = "response.time.high";
public static final String ERROR_THRESHOLD_EXCEEDED = "error.threshold.exceeded";
@Override
public void startService() {
if (!serviceRunning) {
serviceRunning = true;
sendNotification(SERVICE_STARTED, "Service started successfully", null);
System.out.println("Service started");
}
}
@Override
public void stopService() {
if (serviceRunning) {
serviceRunning = false;
sendNotification(SERVICE_STOPPED, "Service stopped", null);
System.out.println("Service stopped");
}
}
@Override
public boolean isServiceRunning() {
return serviceRunning;
}
@Override
public int getRequestCount() {
return requestCount;
}
@Override
public int getErrorCount() {
return errorCount;
}
@Override
public double getErrorRate() {
return requestCount == 0 ? 0.0 : (double) errorCount / requestCount;
}
@Override
public double getResponseTimeThreshold() {
return responseTimeThreshold;
}
@Override
public void setResponseTimeThreshold(double threshold) {
this.responseTimeThreshold = threshold;
}
// Simulate service operations
public void simulateRequest(double responseTime) {
if (!serviceRunning) {
return;
}
requestCount++;
// Check for high response time
if (responseTime > responseTimeThreshold) {
String message = String.format("High response time detected: %.2f ms", responseTime);
sendNotification(HIGH_RESPONSE_TIME, message, responseTime);
}
// Simulate occasional errors (5% error rate)
if (Math.random() < 0.05) {
errorCount++;
// Check error threshold
if (getErrorRate() > 0.1) { // 10% error rate threshold
String message = String.format("Error rate exceeded: %.2f", getErrorRate());
sendNotification(ERROR_THRESHOLD_EXCEEDED, message, getErrorRate());
}
}
}
private void sendNotification(String type, String message, Object userData) {
Notification notification = new Notification(
type,
this,
sequenceNumber.getAndIncrement(),
System.currentTimeMillis(),
message
);
notification.setUserData(userData);
sendNotification(notification);
}
// Simulate service activity
public void startSimulation() {
Thread simulator = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
if (serviceRunning) {
// Simulate variable response times (50ms to 1500ms)
double responseTime = 50 + Math.random() * 1450;
simulateRequest(responseTime);
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
});
simulator.setDaemon(true);
simulator.start();
}
}
// MBean interface for ServiceMonitor
interface ServiceMonitorMBean {
void startService();
void stopService();
boolean isServiceRunning();
int getRequestCount();
int getErrorCount();
double getErrorRate();
double getResponseTimeThreshold();
void setResponseTimeThreshold(double threshold);
}

Notification Listener

import javax.management.*;
public class ServiceMonitorListener implements NotificationListener {
@Override
public void handleNotification(Notification notification, Object handback) {
System.out.printf("[NOTIFICATION] Type: %s, Time: %tT, Message: %s%n",
notification.getType(),
notification.getTimeStamp(),
notification.getMessage());
// Handle specific notification types
switch (notification.getType()) {
case ServiceMonitor.SERVICE_STARTED:
System.out.println(">>> Service started notification received");
break;
case ServiceMonitor.SERVICE_STOPPED:
System.out.println(">>> Service stopped notification received");
break;
case ServiceMonitor.HIGH_RESPONSE_TIME:
System.out.printf(">>> High response time: %.2f ms%n", notification.getUserData());
break;
case ServiceMonitor.ERROR_THRESHOLD_EXCEEDED:
System.out.printf(">>> Error threshold exceeded: %.2f%n", notification.getUserData());
break;
}
}
}

Registering MBean with Notifications

public class NotificationMBeanServer {
public static void main(String[] args) throws Exception {
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
ObjectName objectName = new ObjectName("com.example:type=ServiceMonitor,name=Monitor1");
ServiceMonitor monitor = new ServiceMonitor();
mbs.registerMBean(monitor, objectName);
// Add notification listener
ServiceMonitorListener listener = new ServiceMonitorListener();
monitor.addNotificationListener(listener, null, null);
// Start simulation
monitor.startSimulation();
System.out.println("ServiceMonitor MBean with notifications registered!");
System.out.println("Start/stop service and monitor notifications");
// Keep running
Thread.sleep(Long.MAX_VALUE);
}
}

6. Advanced: Custom MBean with Complex Attributes

Complex Attribute MBean

import javax.management.*;
import java.util.*;
public class ApplicationStats implements ApplicationStatsMBean {
private final Map<String, Object> stats = new HashMap<>();
private final List<String> activeUsers = new ArrayList<>();
private final Map<String, Long> endpointHits = new HashMap<>();
public ApplicationStats() {
initializeStats();
}
private void initializeStats() {
stats.put("startTime", System.currentTimeMillis());
stats.put("uptime", 0L);
stats.put("memoryUsed", 0L);
stats.put("cpuUsage", 0.0);
stats.put("activeSessions", 0);
stats.put("throughput", 0.0);
}
@Override
public long getUptime() {
return System.currentTimeMillis() - (Long) stats.get("startTime");
}
@Override
public CompositeData getMemoryUsage() {
try {
Runtime runtime = Runtime.getRuntime();
long total = runtime.totalMemory();
long free = runtime.freeMemory();
long used = total - free;
long max = runtime.maxMemory();
return new CompositeDataSupport(
new CompositeType(
"MemoryUsage",
"Memory usage statistics",
new String[]{"used", "total", "free", "max", "usagePercentage"},
new String[]{
"Used memory in bytes",
"Total memory in bytes", 
"Free memory in bytes",
"Max memory in bytes",
"Memory usage percentage"
},
new OpenType[]{
SimpleType.LONG,
SimpleType.LONG,
SimpleType.LONG,
SimpleType.LONG,
SimpleType.DOUBLE
}
),
new HashMap<String, Object>() {{
put("used", used);
put("total", total);
put("free", free);
put("max", max);
put("usagePercentage", total == 0 ? 0.0 : (double) used / total * 100);
}}
);
} catch (Exception e) {
throw new RuntimeException("Error creating memory usage composite data", e);
}
}
@Override
public String[] getActiveUsers() {
return activeUsers.toArray(new String[0]);
}
@Override
public TabularData getEndpointStatistics() {
try {
TabularType tabularType = new TabularType(
"EndpointStatistics",
"Statistics for API endpoints",
new CompositeType(
"EndpointStat",
"Single endpoint statistic",
new String[]{"endpoint", "hits", "lastAccess"},
new String[]{"Endpoint path", "Number of hits", "Last access time"},
new OpenType[]{SimpleType.STRING, SimpleType.LONG, SimpleType.LONG}
),
new String[]{"endpoint"}
);
TabularDataSupport tabularData = new TabularDataSupport(tabularType);
for (Map.Entry<String, Long> entry : endpointHits.entrySet()) {
CompositeData compositeData = new CompositeDataSupport(
new CompositeType(
"EndpointStat",
"Single endpoint statistic",
new String[]{"endpoint", "hits", "lastAccess"},
new String[]{"Endpoint path", "Number of hits", "Last access time"},
new OpenType[]{SimpleType.STRING, SimpleType.LONG, SimpleType.LONG}
),
new HashMap<String, Object>() {{
put("endpoint", entry.getKey());
put("hits", entry.getValue());
put("lastAccess", System.currentTimeMillis());
}}
);
tabularData.put(compositeData);
}
return tabularData;
} catch (Exception e) {
throw new RuntimeException("Error creating endpoint statistics", e);
}
}
@Override
public void recordEndpointHit(String endpoint) {
endpointHits.merge(endpoint, 1L, Long::sum);
}
@Override
public void addActiveUser(String username) {
if (!activeUsers.contains(username)) {
activeUsers.add(username);
}
}
@Override
public void removeActiveUser(String username) {
activeUsers.remove(username);
}
@Override
public void resetStatistics() {
endpointHits.clear();
activeUsers.clear();
initializeStats();
System.out.println("Statistics reset");
}
// Simulate application activity
public void simulateActivity() {
Thread simulator = new Thread(() -> {
String[] endpoints = {"/api/users", "/api/products", "/api/orders", "/api/auth"};
String[] users = {"alice", "bob", "charlie", "diana"};
Random random = new Random();
while (!Thread.currentThread().isInterrupted()) {
try {
// Simulate endpoint hits
recordEndpointHit(endpoints[random.nextInt(endpoints.length)]);
// Simulate user activity
if (random.nextDouble() < 0.3) {
String user = users[random.nextInt(users.length)];
if (!activeUsers.contains(user)) {
addActiveUser(user);
}
}
// Occasionally remove users
if (random.nextDouble() < 0.2 && !activeUsers.isEmpty()) {
String user = activeUsers.get(random.nextInt(activeUsers.size()));
removeActiveUser(user);
}
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
});
simulator.setDaemon(true);
simulator.start();
}
}
// MBean interface for complex attributes
interface ApplicationStatsMBean {
// Simple attributes
long getUptime();
// Complex attributes
CompositeData getMemoryUsage();
String[] getActiveUsers();
TabularData getEndpointStatistics();
// Operations
void recordEndpointHit(String endpoint);
void addActiveUser(String username);
void removeActiveUser(String username);
void resetStatistics();
}

7. JMX Client Example

Programmatic JMX Client

import javax.management.*;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;
import java.util.*;
public class JMXClient {
public static void main(String[] args) throws Exception {
// Connect to local MBeanServer
MBeanServerConnection mbsc = ManagementFactory.getPlatformMBeanServer();
// Or connect to remote server
// JMXServiceURL url = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://localhost:9999/jmxrmi");
// JMXConnector jmxc = JMXConnectorFactory.connect(url, null);
// MBeanServerConnection mbsc = jmxc.getMBeanServerConnection();
ObjectName objectName = new ObjectName("com.example:type=SystemConfig,name=SystemConfig1");
// Get attribute
Integer threadCount = (Integer) mbsc.getAttribute(objectName, "ThreadCount");
System.out.println("Current thread count: " + threadCount);
// Set attribute
mbsc.setAttribute(objectName, new Attribute("ThreadCount", 25));
System.out.println("Thread count set to 25");
// Invoke operation
String config = (String) mbsc.invoke(objectName, "displayConfig", null, null);
System.out.println("Config: " + config);
// Get MBean info
MBeanInfo info = mbsc.getMBeanInfo(objectName);
System.out.println("MBean Description: " + info.getDescription());
// List all attributes
for (MBeanAttributeInfo attr : info.getAttributes()) {
System.out.println("Attribute: " + attr.getName() + " (" + attr.getType() + ")");
}
// List all operations
for (MBeanOperationInfo op : info.getOperations()) {
System.out.println("Operation: " + op.getName() + " - " + op.getDescription());
}
// Query MBeans
Set<ObjectName> mbeans = mbsc.queryNames(null, null);
System.out.println("\nRegistered MBeans:");
for (ObjectName name : mbeans) {
System.out.println(" - " + name);
}
}
}

8. Best Practices

1. Use Meaningful ObjectNames

// Good - descriptive and organized
ObjectName name = new ObjectName("com.mycompany:type=Database,pool=ReadPool,name=ConnectionPool1");
// Avoid - too generic
ObjectName badName = new ObjectName("com.mycompany:name=Pool1");

2. Handle Exceptions Properly

public class SafeMBean implements SafeMBeanMBean {
@Override
public void riskyOperation() {
try {
// Risky operation
performRiskyWork();
} catch (Exception e) {
throw new RuntimeOperationsException(
new RuntimeException("Operation failed", e),
"Error in riskyOperation"
);
}
}
}

3. Use MXBeans for Complex Data

// MXBean automatically handles complex type conversion
public interface MyServiceMXBean {
Map<String, String> getStatistics();  // Automatically converted
List<String> getRecentEvents();       // Automatically converted
}

4. Cache MBeanServer Reference

public class MBeanManager {
private static final MBeanServer MBEAN_SERVER = ManagementFactory.getPlatformMBeanServer();
public static void registerMBean(Object mbean, String name) throws Exception {
ObjectName objectName = new ObjectName(name);
MBEAN_SERVER.registerMBean(mbean, objectName);
}
}

Summary

Custom MBeans provide powerful management capabilities for Java applications:

  • Standard MBeans - Simple interface-based approach
  • MXBeans - Automatic type conversion for complex data
  • Dynamic MBeans - Flexible runtime metadata
  • Notifications - Event-driven monitoring
  • Complex Attributes - Composite and tabular data

MBeans enable runtime monitoring, configuration changes, and operational control without restarting applications, making them essential for enterprise Java applications and microservices.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper