Skip to main content

Adding Descriptors to MBeans in Tiger

Posted by emcmanus on June 8, 2006 at 9:34 AM PDT

Mustang (Java SE 6) includes the ability to give additional
information about MBeans to management clients via
Descriptors, as I described href="">previously.
But what if you are not yet able to migrate to the Mustang
platform? As I hinted in that previous entry, all is not
lost. You can still use Descriptors, though it's more work.

We often hear from developers that they'd dearly love to move
to the latest and greatest Java SE platform, but they can't.
Their app server isn't yet supported on that platform; or their
giant mission-critical financial application would require an
enormous test campaign on the new platform, that the QA
department doesn't have the resources to do; or their href="">pointy-haired
boss fears the unknown or doesn't see what the fuss is all
about. So if there's some way to make the shiny new features
available on older platforms, these developers are all for


Descriptors allow you to give additional information about
MBeans to management clients. For example, a Descriptor on an
MBean attribute might say what units it is measured in, or what
its minimum and maximum possible values are. As of Mustang,
Descriptors are a basic part of the JMX API and are available in
all types of MBeans. In particular, the Mustang version of the href="">jconsole
tool will display an MBean's descriptors, as the following
screenshot illustrates:

width="670" height="454"
alt="JConsole screenshot showing MBean with its Descriptor information" />

The descriptor here includes some href="">standard
descriptor fields, namely immutableInfo,
interfaceClassName, and mxbean; as well
as a custom field author.

You can perfectly well use Mustang jconsole to connect to a JMX
agent running on Tiger (J2SE 5.0).
(You can even connect to
an earlier J2SE version such as 1.4, though in that case you
will need to have the JMX and JMX Remote API classes in the
classpath of your application.) So even if you can't migrate your
application to Mustang, it's perfectly feasible to
install a second JDK, the Mustang one, and just use its jconsole
to connect to your application running on Tiger. Likewise you
could write your own management client running on Mustang, even
though your application is still running on Tiger.

So if you could somehow arrange for your MBean to contain
descriptors in Tiger, then you could show them with your Mustang
jconsole or access them in your Mustang management client. And
this is in fact possible.

Descriptors on Tiger

The trick is that descriptors have always existed in the JMX
API, but only in conjunction with Model MBeans. Model MBeans
are an advanced (not to say obscure) part of the API, so you
probably haven't paid much attention to them. But the key point
here is that href="">the
Model MBean package defines a set of subclasses of the
standard metadata classes such as href="">MBeanInfo
and href="">MBeanAttributeInfo.
For example, href="">ModelMBeanAttributeInfo
is just like MBeanAttributeInfo except that it adds
a descriptor. Prior to Mustang, MBeanAttributeInfo
did not have a descriptor, but
ModelMBeanAttributeInfo did. Since
ModelMBeanAttributeInfo is a subclass of
MBeanAttributeInfo, you can use a
ModelMBeanAttributeInfo wherever an
MBeanAttributeInfo is expected. So in particular
if you have a plain MBean, for example a Standard MBean, you can
change its MBeanInfo so that it contains an array
of ModelMBeanAttributeInfo rather than just

Suppose we want to create a Standard MBean, but with descriptor
fields as in the snapshot above. Here's how we might go about it.

First, we create a subclass of href="">StandardMBean
called DescriptorStandardMBean that allows us to
supply the extra descriptor fields when the MBean is constructed.
So the MBean registration might look like this:

MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
ObjectName name = new ObjectName("com.example.MyApp:type=Test");
Map<String, Object> descriptorMap = new HashMap<String, Object>();
descriptorMap.put("author", "Éamonn McManus");
descriptorMap.put("immutableInfo", "true");
descriptorMap.put("interfaceClassName", TestMBean.class.getName());
descriptorMap.put("mxbean", "false");
Test test = new Test();
Object mbean = new DescriptorStandardMBean(test, TestMBean.class, descriptorMap);
mbs.registerMBean(mbean, name);

Here's what the DescriptorStandardMBean class
might look like. It will have two constructors, corresponding to
the two constructors of the parent class
StandardMBean, but adding an extra Map
parameter that will contain the extra descriptor fields. So it
starts off like this:

public class DescriptorStandardMBean extends StandardMBean {

    private final Map<String, ?> mbeanDescriptorMap;

    public <T> DescriptorStandardMBean(
            T impl, Class<T> intf,
            Map<String, ?> mbeanDescriptorMap)
            throws NotCompliantMBeanException {
        super(impl, intf);
    this.mbeanDescriptorMap = mbeanDescriptorMap;
    public DescriptorStandardMBean(
            Class<?> intf,
            Map<String, ?> mbeanDescriptorMap)
            throws NotCompliantMBeanException {
    this.mbeanDescriptorMap = mbeanDescriptorMap;

The <T> voodoo incantation on the first
constructor says that if the second parameter is
TestMBean.class, say, then the first parameter must
be an object of that type or a subtype. That is, the declared type
of the parameter must be TestMBean or a subtype (typically
a class that implements that interface). You can see that this is
true of our registration code above, assuming the
Test class implements the TestMBean
interface following the usual Standard MBean conventions. (The
StandardMBean constructor uses the same
generics voodoo in Mustang, though not in Tiger.)

The mbeanDescriptorMap is declared as a
Map<String, ?> rather than a
Map<String, Object> because the caller might
want to give it a Map<String, String>, say.
In fact, we could have used a

in the registration code, because all the
descriptor entries were in fact strings. One of the subtleties
of generics is that you can't assign a
List<String> to a
List<Object>, and likewise you can't assign a
Map<String, String> to a
. But you can assign if
Object is replaced by ?.

Now let's look at how we attach our descriptor to the MBean's

    public MBeanInfo getMBeanInfo() {
        MBeanInfo mbi = super.getMBeanInfo();
        if (mbi instanceof ModelMBeanInfoSupport)  // we already rewrote it
            return mbi;

        Descriptor d = new DescriptorSupport();
        for (Map.Entry<String, ?> entry : mbeanDescriptorMap.entrySet())
            d.setField(entry.getKey(), entry.getValue());
        d.setField("descriptorType", "mbean");
        if (d.getFieldValue("name") == null)
            d.setField("name", mbi.getClassName());
        mbi = new ModelMBeanInfoSupport(
                null,  // no ModelMBeanNotificationInfo[]
        return mbi;

We override getMBeanInfo so that it calls the
original getMBeanInfo from
StandardMBean, and then augments it. Because href="">StandardMBean.getMBeanInfo()
contains logic to cache the computed MBeanInfo,
we need to be careful. super.getMBeanInfo() may
return the augmented MBeanInfo that was cached
last time. The class we're using to include the descriptor is
ModelMBeanInfoSupport, a subclass of
MBeanInfo, so if
super.getMBeanInfo() returns one of these we know
it's one we already created.

To make the descriptor, we use the href="">DescriptorSupport
class, which is a concrete implementation of the
Descriptor interface. We create an empty descriptor,
then add all the entries in the Map to it.

Model MBeans impose a gratuitously annoying rule, which is that
every descriptor must have a name and
descriptorType field. We add a somewhat random value
for the name field, if it wasn't already provided by
the Map. The descriptorType field must
be equal to "mbean" so we set that.

Next we make the ModelMBeanInfoSupport. It's
going to contain the same fields as the original
MBeanInfo, but with an extra descriptor.
Unfortunately this requires us to take apart the
MBeanInfo that we started from, because the
MBeanAttributeInfo[] and other arrays it contains
are not good enough for ModelMBeanInfoSupport. It
wants ModelMBeanAttributeInfo[] etc. You might
guess seeing those makeModelMBeanAttributeInfos and
other calls that we are in for some tedious code, and you would
be right, as we'll see shortly.

Finally, we call href="">StandardMBean.cacheMBeanInfo,
so that the next time somebody calls getMBeanInfo()
on this MBean it will return the value we just constructed.

Now let's look at that tedious code I mentioned:

    private static ModelMBeanAttributeInfo[]
            makeModelMBeanAttributeInfos(MBeanAttributeInfo[] mbais) {
        ModelMBeanAttributeInfo[] mmbais = new ModelMBeanAttributeInfo[mbais.length];
        for (int i = 0; i < mbais.length; i++) {
            MBeanAttributeInfo mbai = mbais[i];
            ModelMBeanAttributeInfo mmbai = new ModelMBeanAttributeInfo(
            mmbais[i] = mmbai;
        return mmbais;
    private static ModelMBeanOperationInfo[]
            makeModelMBeanOperationInfos(MBeanOperationInfo[] mbois) {
        ModelMBeanOperationInfo[] mmbois = new ModelMBeanOperationInfo[mbois.length];
        for (int i = 0; i < mbois.length; i++) {
            MBeanOperationInfo mboi = mbois[i];
            ModelMBeanOperationInfo mmboi = new ModelMBeanOperationInfo(
            mmbois[i] = mmboi;
        return mmbois;
    private static ModelMBeanConstructorInfo[]
            makeModelMBeanConstructorInfos(MBeanConstructorInfo[] mbcis) {
        ModelMBeanConstructorInfo[] mmbcis = new ModelMBeanConstructorInfo[mbcis.length];
        for (int i = 0; i < mmbcis.length; i++) {
            MBeanConstructorInfo mbci = mbcis[i];
            ModelMBeanConstructorInfo mmbci = new ModelMBeanConstructorInfo(
            mmbcis[i] = mmbci;
        return mmbcis;

Each of these methods has the same outline. We start with the
original MBeanFooInfo array, and construct a
parallel ModelMBeanFooInfo array, where each
MBeanFooInfo from the original array has been
replaced by a ModelMBeanFooInfo with the same
information. In practice you might need to write a fourth
method, makeModelMBeanNotificationInfos. I haven't
done that here because StandardMBean always defines
an empty MBeanNotificationInfo[] array. (I said it
would be possible to add descriptors to your Tiger
MBeans; I didn't say it would be elegant.)

If you're like me, you'll be very annoyed at the fact that
these three methods look exactly the same (four if you add
makeModelMBeanNotificationInfos). I tried writing
the code this way instead:

    private static <I extends MBeanFeatureInfo, M extends I> M[]
            makeModelMBeanFeatureInfos(I[] mbfis, Class<M> modelClass) {
        M[] mmbfis = newArray(modelClass, mbfis.length);
        Converter<M> converter = getConverter(modelClass);
        for (int i = 0; i < mbfis.length; i++) {
            I mbfi = mbfis[i];
            M mmbfi = modelClass.cast(converter.convert(mbfi));
            mmbfis[i] = mmbfi;
        return mmbfis;

A static initializer then initializes a
Map<Class<?>, Converter<?>> with one entry
for each of the four ModelMBeanFooInfo classes, that
is an anonymous class implementing the
Converter<ModelMBeanFooInfo> interface. Well,
it works, and it avoids the boilerplate in the
makeModelMBeanFooInfos methods, but as explanatory
code goes it leaves something to be desired. So let's forget

So, OK, now we've defined DescriptorStandardMBean, right?
So does it work?

Wait a minute! We're missing something important:


Right. So now let's see what jconsole looks like connecting to
our test program on Tiger:

width="670" height="596"
alt="JConsole screenshot showing Tiger MBean with its Descriptor information">

Success! Well, mostly. You can see that the descriptor fields
we defined are indeed present: author,
immutableInfo, interfaceClassName, and
mxbean. Unfortunately,
ModelMBeanInfoSupport has seen fit to add its own
junk fields for us. We'll just have to ignore those.

You can use similar tricks to add descriptors to individual
attributes. In the makeModelMBeanAttributeInfos
method, for example, you could append a Descriptor
parameter to the ModelMBeanAttributeInfo constructor
call, with whatever you want in it. As before, you have to
remember to add the pointless but necessary name and
descriptorType fields; the name must be
the same as the name of the attribute, and the
descriptorType must be the string
"attribute". Also as before,
ModelMBeanAttributeInfo will want to add its own crud
to your descriptor.

width="670" height="596"
alt="JConsole screenshot showing Tiger MBean attribute with its Descriptor information" />

Finally, note that there is no
ModelMBeanParameterInfo class, so there is no way to
specify a descriptor for a parameter (to indicate its units, for
example). The best you can do is to put a
Descriptor[] in the enclosing
ModelMBeanOperationInfo or
ModelMBeanConstructorInfo descriptor, with something


Everything here will work on J2SE 1.4 as well, except you'll
have to add the JMX and JMX Remote APIs to the classpath of your application, and
create your own MBean Server and RMI Connector instead of the ones
built-in to Tiger.

Related Topics >>