Jolokia Protocol

Jolokia uses a JSON-over-HTTP protocol which is described in this chapter. The communication is based on a request-response paradigm, where each request results in a single response.

GET URLs are chatty

Keep in mind that many web servers log the requested path of every request, including parameters passed to a GET request, so sending messages over GET often bloats server logs.

Another important aspect is that some ObjectNames may be complex (for example in Camel or ActiveMQ/Artemis) and POST request is much more suitable when working with such MBeans.

Jolokia requests can be sent in two ways: Either as a HTTP GET request, in which case the request parameters are encoded completely in the URL. Or as a POST request where the request is put into a JSON payload in the HTTP request’s body. GET based requests are mostly suitable for simple use cases and for testing the agent via a browser. The focus here is on simplicity. POST based requests uses a JSON representation of the request within the HTTP body. They are more appropriate for complex requests and provide some additional features (e.g. bulk requests are only possible with POST).

The response returned by the agent always uses JSON for its data representation. It has the same format regardless whether GET or POST requests are used.

The rest of this chapter is divided into two parts: First, the general structure of requests and responses is explained after which the representation of Jolokia supported operations is defined.

Unfortunately the term operation is used in different contexts which should be distinguished from one another. Jolokia operations denote the various kind of Jolokia requests (read, exec, …​), whereas JMX operations are methods which can be invoked on an JMX MBean. Whenever the context requires it, this documents uses Jolokia or JMX as prefix.

Requests and Responses

Jolokia knows about two different styles of handling requests, which are distinguished by the HTTP method used: GET or POST. Regardless of which method is used, the agent doesn’t keep any state on the server side (except of course that MBeans are obviously stateful most of the time). So in this aspect, the communication can be considered REST like[1].

Some Servlet API concepts

Before proceeding, let’s review some important concepts from Jakarta Servlet Specification 5.

Chapter 3.5. Request Path Elements splits full URI used to access Java Servlet container into three parts (query string is not included here):

requestURI = contextPath + servletPath + pathInfo

These 4 elements are accessed using these API calls:

  • jakarta.servlet.http.HttpServletRequest.getRequestURI()

  • jakarta.servlet.http.HttpServletRequest.getContextPath()

  • jakarta.servlet.http.HttpServletRequest.getServletPath()

  • jakarta.servlet.http.HttpServletRequest.getPathInfo()

    Request URI

    this is simply full URI without query string

    Context path

    this fragment selects the web application (one of potentially many running in single Servlet container). On Tomcat it is derived from the file name of WAR archive in webapps/ directory. It may be / (for special ROOT.war on Tomcat).

    Servlet path

    this is more complicated, as it indicates part of the URI that is used to select single servlet within single web application. When servlet mapping (<servlet-mapping>/<url-pattern> element in WEB-INF/web.xml) is /path or /path/*, servlet path is /path. Jolokia Agent WAR uses /* mapping, so servlet path is empty.

    Path info

    this is the URI part after servlet path, so for Jolokia, it is simply everything after /jolokia (when WAR agent is deployed to Tomcat as webapps/jolokia.war).

GET requests

The simplest way to access the Jolokia agent is by sending HTTP GET requests. These requests encode all their parameters within the access URL. Jolokia uses the path info part of an URL to extract the parameters. Within the path info, each part is separated by a slash (/). In general, the request URL looks like

<base-url>/<type>/<arg1>/<arg2>/..../

The <base-url> specifies the URL under which the agent is accessible and consists of protocol (http or https), host, port and a context path.

It normally looks like http://localhost:8080/jolokia, but depends on your deployment setup. The last part of such URL is the context root of the deployed agent, which by default is based on the agent’s filename (e.g. jolokia.war).

<type> specifies one of the supported Jolokia operations (described in the next section), followed by one or more operation-specific parameters separated by slashes.

For example, the following URL executes a read Jolokia operation on the MBean java.lang:type=Memory for reading the attribute HeapMemoryUsage (see Reading attributes (read)). It is assumed, that the agent is reachable under the base URL http://localhost:8080/jolokia:

http://localhost:8080/jolokia/read/java.lang:type=Memory/HeapMemoryUsage

Escaping rules in GET requests

GET requests are easy and straightforward - you can easily type them in browser address bar, bookmark or use with curl. However, there are limitations.

RFC 2396 and newer RFC 3986 specify what is and what is not allowed in the URL (which is a subset of URI). RFC 7230 specifies usage of URIs within HTTP protocol.

As mentioned in Some Servlet API concepts, Jolokia uses path info to identify the type of operation to perform and its specific arguments (mbean names, attribute names for read/write operations or method name and arguments for exec operations).

RFC 3986 mentions that a URI:

  • path component with data organized in hierarchical form, where segments are separated by / character (implicitly stating that / is forbidden as part of the segment)

  • query component with data organized in non-hierarchical form

Also (and RFC 2396 says this explictly, while newer RFC 3986 only highlights the convention), some characters in the segments may have special meaning (usually to implement the path parameters). These are ;, , and =. Finally, by the definition, ? separates the path part from the query part, so ? can’t be part of the path (and any segment of the path).

The problem is that mbean names or attribute values may use various characters which are illegal from the point of view of URI or HTTP specifications.

That’s where Jolokia URI encoding steps in. In theory URI encoding should be sufficient, but it simply is not.

You might wonder why simple URI encoding isn’t enough for escaping slashes. The reason is simple - security. For example, Tomcat returns HTTP error 400 with Invalid URI: [The encoded slash character is not allowed] message if you encode / as %2F. Jetty returns Bad Message 400, reason: Ambiguous URI empty segment when two slashes are used (//). Other appservers might exhibit a similar behaviour, so Jolokia uses an own escaping mechanism.

If one of the Jolokia request parts contain a slash (/) (e.g. as part of you bean’s name, which is often the case with Camel or ActiveMQ) it needs to be escaped. An exclamation mark (!) is used as escape character [2]. An exclamation mark itself needs to be doubled for escaping. Any other character preceded by an exclamation mark is taken literally. Table Table 1, “Escaping rules” illustrates the escape rules as used in GET requests. Also, if quotes are part of an GET request the need to be escaped with !".

Table 1. Escaping rules
Escaped Unescaped

!/

/

!!

!

!"

"

!(anything else)

(anything else)

For example, to read the attribute State on the MBean named jboss.jmx:alias=jmx/rmi/RMIAdaptor, an access URL like this has to be constructed:

.../read/jboss.jmx:alias=jmx!/rmi!/RMIAdaptor/State

Client libraries like JMX::Jmx4Perl do this sort of escaping transparently.

Escaping can be avoided altogether if a slightly different variant for a request is used (which doesn’t look that REST-stylish, though). Instead of providing the information as path-info, a query parameter p can be used instead. This should be URL encoded, though. For the example above, the alternative is

http://localhost:8080/jolokia?p=/read/jboss.jmx:alias=jmx%2Frmi%2FRMIAdaptor/State

This format must be used for GET requests containing backslashes (\) since backslashes can not be sent as part of an URL at all.

Summarizing, the recommended approach is to use POST requests if possible. If GET has to be used and escaping may be confusing with the server you used, use p query parameter. For GET requests with path segments, be careful about the escaping rules.

POST requests

POST requests are the most powerful way to communicate with the Jolokia agent. There are fewer escaping issues and it allows for features which are not available with GET requests. POST requests use a single Jolokia URL and put their payload within the HTTP request’s body. This payload is represented in JSON, a data serialization format originating from the JavaScript world.

The JSON format for a single request is a JSON object, which is essentially a map with keys (or attributes) and values. All requests have a common mandatory attribute, type, which specifies the kind of JMX operation to perform. The other attributes are either operation specific as described in Jolokia operations or are processing parameters which influence the overall behaviour and can be mixed in to any request. See Processing parameters for details. Operation specific attributes can be either mandatory or optional and depend on the operation type. In the following, if not mentioned otherwise, attributes are mandatory. Processing parameters are always optional, though.

A sample read request in JSON format looks like the following example. It has a type read (case doesn’t matter) and the three attributes mbean, attribute and path which are specific to a read request.

JSON Request
{
  "type" : "read",
  "mbean" : "java.lang:type=Memory",
  "attribute" : "HeapMemoryUsage",
  "path" : "used"
}

Each request JSON object results in a single JSON response object contained in the HTTP answer’s body. A bulk request contains multiple Jolokia requests within a single HTTP request. This is done by putting individual Jolokia requests into a JSON array:

[
  {
    "type" : "read",
    "attribute" : "HeapMemoryUsage",
    "mbean" : "java.lang:type=Memory",
    "path" : "used"
  },
  {
    "type" : "search",
    "mbean" : "*:type=Memory,*"
  }
]

This request will result in a JSON array containing multiple JSON responses within the HTTP response. They are returned in same order as the requests in the initial bulk request.

Responses

Responses are always encoded in UTF-8 JSON, regardless whether the request was a GET or POST request. In general, two kinds of responses can be classified: In the normal case, an HTTP Response with response code 200 is returned, containing the result of the operation as a JSON payload. In case of an error, a 4xx or 5xx code will be returned and the JSON payload contains details about the error occurred (e.g. 404 means "not found"). (See this page for more information about HTTP error codes..). If the processing option ifModifiedSince is given and the requested value as been not changed since then, a response code of 304 is returned. This option is currently only supported by the LIST request, for other request types the value is always fetched.

In the non-error case a JSON response looks mostly the same for each request type except for the value attribute which is request type specific.

The format of a single Jolokia response is:

JSON Response
{
  "value": .... ,
  "status" : 200,
  "timestamp" : 1702391068,
  "request": {
    "type": ...,
    ....
  },
  "history": [
    {
      "value": ... ,
      "timestamp" : 1702391069
    },
    ....
  ]
}

For successful requests, the status is always 200 (the HTTP success code). The timestamp contains the epoch time[3] when the request has been handled. The request leading to this response can be found under the attribute request. Finally and optionally, if history tracking is switched on (see Tracking historical values), an entry with key history contains a list of historical values along with their timestamps. History tracking is only available for certain type of requests (read, write and exec). The value is specific for the type of request, it can be a single scalar value or a monster JSON structure.

If an error occurs, the status will be a number different from 200. An error response looks like

{
  "error_type": "java.lang.UnsupportedOperationException",
  "error": "java.lang.UnsupportedOperationException : No type with name 'java.lang:type=Memory' exists",
  "status": 400
}

For status codes it is important to distinguish status codes as they appear in Jolokia JSON response objects and the HTTP status code of the (outer) HTTP response. There can be many Jolokia status codes, one for each Jolokia request contained in the single HTTP request. The HTTP status code merely reflect the status of agent itself (i.e. whether it could perform the operation at all), whereas the Jolokia response status reflects the result of the operation (e.g. whether the performed operation throws an exception). So it is not uncommon to have an HTTP status code of 200, but the contained JSON response(s) indicate some errors.

I.e. the status has a code in the range 400 .. 499 or 500 .. 599 as it is specified for HTTP return codes. The error member contains an error description. This is typically the message of an exception occurred on the agent side[4]. Finally, error_type contains the Java class name of the exception occurred. The stacktrace contains a Java stacktrace occurred on the server side (if any stacktrace is available and includeStackTrace option is set to true).

For each type of operation, the format of the value entry is explained in Jolokia operations

Paths

An inner path points to a certain substructure (plain value, array, hash) within a a complex JSON value. Think of it as something like "XPath lite". This is best explained by an example:

The attribute HeapMemoryUsage of the MBean java.lang:type=Memory can be requested with the URL http://localhost:8080/jolokia/read/java.lang:type=Memory/HeapMemoryUsage which returns a complex JSON structure like

{
  "request": {
    "mbean": "java.lang:type=Memory",
    "attribute": "HeapMemoryUsage",
    "type": "read"
  },
  "value": {
    "init": 524288000,
    "committed": 532676608,
    "max": 8334082048,
    "used": 27145000
  },
  "status": 200,
  "timestamp": 1702392020
}

In order to get to the value for used heap memory you should specify an inner path used, so that the request http://localhost:8080/jolokia/read/java.lang:type=Memory/HeapMemoryUsage/used results in a response of 27145000 for the value:

{
  "request": {
    "path": "used",
    "mbean": "java.lang:type=Memory",
    "attribute": "HeapMemoryUsage",
    "type": "read"
  },
  "value": 27145000,
  "status": 200,
  "timestamp": 1702392075
}

If the attribute contains arrays at some level, use a numeric index (0 based) as part of the inner path if you want to traverse into this array.

For both, GET and POST requests, paths must be escaped as described in Table 1, “Escaping rules” when they contain slashes (/) or exclamation marks (!).

Paths support wildcards * in a simple form. If given as a path part exclusively, it matches any entry and path matching continues on the next level. This feature is especially useful when using pattern read request together with paths. See Reading attributes (read) for details. A * mixed with other characters in a path part has no special meaning and is used literally.

Jolokia operations

Reading attributes (read)

Reading MBean attributes is probably the most used JMX method, especially when it comes to monitoring. Concerning Jolokia, it is also the most powerful one with the richest semantics. Obviously the value of a single attribute can be fetched, but Jolokia supports also fetching of a list of given attributes on a single MBean or even on multiple MBeans matching a certain pattern.

Reading attributes are supported by both kinds of requests, GET and POST.

Don’t confuse fetching multiple attributes on possibly multiple MBeans with bulk requests. A single read request will always result in a single read response, even when multiple attribute values are fetched. Only the single response’s structure of the value will differ depending on what kind of read request was performed.

A read request for multiple attributes on the same MBean is initiated by giving a list of attributes to the request. For a POST request this is an JSON array, for a GET request it is a comma separated list of attribute names (where slashes and exclamation marks must be escaped as described in Escaping rules). If no attribute is provided, then all attributes are fetched. The MBean name can be given as a pattern in which case the attributes are read on all matching MBeans. If a MBean pattern and multiple attributes are requested, then only the value of attributes which matches both are returned, the others are ignored.

Paths can be used with pattern and multiple attribute read as well. In order to skip the extra value levels introduced by a pattern read, the wildcard * can be used. For example, a read request for the MBean Pattern java.lang:type=GarbageCollector,* for the Attribute LastGcInfo returns a complex structure holding information about the last garbage collection. If one is looking only for the used memory during garbage collection, a path used could be used if this request wouldn’t be a pattern request (i.e. refers a specific, single MBean). But in this case since a nested map with MBean and Attribute names is returned, the path */*/*/*/used has to be used in order to skip the extra levels (for different heaps/spaces) for applying the path. Note that in the following example the final value is not the full GC-Info but only the value of its used entry for different spaces:

"value": {
  "java.lang:name=G1 Young Generation,type=GarbageCollector": {
    "LastGcInfo": {
      "duration": 3,
      "memoryUsageBeforeGc": {
        "CodeHeap 'profiled nmethods'": 4780288,
        "G1 Old Gen": 4934656,
        "CodeHeap 'non-profiled nmethods'": 928256,
        "G1 Survivor Space": 4194304,
        "Compressed Class Space": 1331528,
        "Metaspace": 12812816,
        "G1 Eden Space": 20971520,
        "CodeHeap 'non-nmethods'": 1271296
      },
      "GcThreadCount": 5,
      "startTime": 786,
      "endTime": 789,
      "id": 2,
      "memoryUsageAfterGc": {
        "CodeHeap 'profiled nmethods'": 4780288,
        "G1 Old Gen": 9082880,
        "CodeHeap 'non-profiled nmethods'": 928256,
        "G1 Survivor Space": 2597960,
        "Compressed Class Space": 1331528,
        "Metaspace": 12812816,
        "G1 Eden Space": 0,
        "CodeHeap 'non-nmethods'": 1271296
      }
    }
  }
}

The following rule of thumb applies:

  • If a wildcard is used, everything at that point in the path is matched. The next path parts are used to match from there on. All the values on this level are included.

  • Every other path part is literally compared against the values on that level. If there is a match, this value is removed in the answer so that at the end you get back a structure with the values on the wildcard levels and the leaves of the matched parts.

  • If used with wildcards, paths behave also like filters. E.g. you can use a path */*/*/*/used on the MBean pattern java.lang:* and get back only that portions which contains "used" as key, all others are ignored.

GET read request

The GET URL for a read request has the following format:

<base-url>/read/<mbean name>/<attribute name>/<inner path>
Table 2. GET Read Request
Part Description Example

<mbean name>

The ObjectName of the MBean for which the attribute should be fetched. It contains two parts: A domain part and a list of properties which are separated by :. Properties themselves are combined in a comma separated list of key-value pairs. This name can be a pattern in which case multiple MBeans are queried for the attribute value.

java.lang:type=Memory

<attribute name>

Name of attribute to read. This can be a list of Attribute names separated by comma. According to URI compliance, some special characters need to be escaped as described in Escaping rules. If no attribute is given, all attributes are read.

HeapMemoryUsage

<inner path>

This optional part describes an inner path as described in Paths

used

With this URL the used heap memory can be obtained:

http://localhost:8080/jolokia/read/java.lang:type=Memory/HeapMemoryUsage/used

POST read request

A the keys available for read POST requests are shown in the following table.

Table 3. POST Read Request
Key Description Example

type

read

mbean

MBean’s ObjectName which can be a pattern

java.lang:type=Memory

attribute

Attribute name to read or a JSON array containing a list of attributes to read. No attribute is given, then all attributes are read.

HeapMemoryUsage

[ "HeapMemoryUsage", "NonHeapMemoryUsage" ]

path

Inner path for accessing the value of a complex value (Paths)

used

The following request fetches the number of active threads:

{
  "type": "read",
  "mbean": "java.lang:type=Threading",
  "attribute": "ThreadCount"
}

Read response

The general format of the JSON response is described in Responses in detail. A typical response for an attribute read operation for a GET request with URL like:

http://localhost:8080/jolokia/read/java.lang:type=Memory/HeapMemoryUsage
{
  "request": {
    "mbean": "java.lang:type=Memory",
    "attribute": "HeapMemoryUsage",
    "type": "read"
  },
  "history": [
    {
      "value": {
        "init": 524288000,
        "committed": 532676608,
        "max": 8334082048,
        "used": 78027104
      },
      "timestamp": 1702454713
    },
    ...
  ],
  "value": {
    "init": 524288000,
    "committed": 532676608,
    "max": 8334082048,
    "used": 86415712
  },
  "status": 200,
  "timestamp": 1702454822
}

The value contains the response’s value. For simple data types it is a scalar value, more complex types are serialized into a JSON object. See Object serialization for detail on object serialization.

For a read request of a single MBean with multiple attributes, the returned value is a JSON object with the attribute names as keys and their values as values. For example a request to http://localhost:8080/jolokia/read/java.lang:type=Memory leads to

{
  "request": {
    "mbean": "java.lang:type=Memory",
    "type": "read"
  },
  "value": {
    "ObjectPendingFinalizationCount": 0,
    "Verbose": false,
    "HeapMemoryUsage": {
      "init": 524288000,
      "committed": 532676608,
      "max": 8334082048,
      "used": 94804320
    },
    "NonHeapMemoryUsage": {
      "init": 7667712,
      "committed": 38928384,
      "max": -1,
      "used": 36905512
    },
    "ObjectName": {
      "objectName": "java.lang:type=Memory"
    }
  },
  "status": 200,
  "timestamp": 1702454894
}

A request to a MBean pattern returns as value a JSON object, with the MBean names as keys and as value another JSON object with the attribute name as keys and the attribute values as values. For example a request http://localhost:8080/jolokia/read/java.lang:type=*/HeapMemoryUsage returns something like

{
  "request": {
    "mbean": "java.lang:type=*",
    "attribute": "HeapMemoryUsage",
    "type": "read"
  },
  "value": {
    "java.lang:type=Memory": {
      "HeapMemoryUsage": {
        "init": 524288000,
        "committed": 532676608,
        "max": 8334082048,
        "used": 103192928
      }
    }
  },
  "status": 200,
  "timestamp": 1702454978
}

Writing attributes (write)

Writing an attribute is quite similar to reading one, except that the request takes an additional value element.

GET write request

Writing an attribute with a GET request, an URL with the following format has to be used:

<base url>/write/<mbean name>/<attribute name>/<value>/<inner path>
Table 4. GET Write Request
Part Description Example

<mbean name>

MBean’s ObjectName

java.lang:type=ClassLoading

<attribute name>

Name of attribute to set

Verbose

<value>

The attribute name to value. The value must be serializable as described in Request parameter serialization.

true

<inner path>

Inner path for accessing the parent object on which to set the value. (See also Paths). Note, that this is not the path to the attribute itself, but to the object carrying this attribute. With a given path it is possible to deeply set a value on a complex object.

For example, you can set the garbage collector to verbose mode by using something like

http://localhost:8080/jolokia/write/java.lang:type=Memory/Verbose/true

POST write request

The keys which are evaluated for a POST write request are:

Table 5. POST Write Request
Key Description Example

type

write

mbean

MBean’s ObjectName

java.lang:type=ClassLoading

attribute

Name of attribute to set

Verbose

value

The attribute name to value. The value must be serializable as described in Request parameter serialization.

true

path

An optional inner path for specifying an inner object on which to set the value. See Paths for more on inner paths.

Write response

As response for a write operation the old attribute’s value is returned. For a request

http://localhost:8080/jolokia/write/java.lang:type=ClassLoading/Verbose/true

you get the answer (supposed that verbose mode was switched off for class loading at the time this request was sent)

{
  "request": {
    "mbean": "java.lang:type=ClassLoading",
    "attribute": "Verbose",
    "type": "write",
    "value": "true"
  },
  "value": false,
  "status": 200,
  "timestamp": 1702455595
}

The response is quite similar to the read operation except for the additional value element in the request (and of course, the different type).

Executing JMX operations (exec)

With Jolokia we can also execute exposed JMX operations with optional arguments. Just as when writing attributes, Jolokia must be able to serialize the operation arguments. See Object serialization for details. Execution of overloaded methods is supported. The JMX specifications recommends to avoid overloaded methods when exposing them via JMX, though.

GET exec request

The format of an GET exec request is

<base url>/exec/<mbean name>/<operation name>/<arg1>/<arg2>/....
Table 6. GET Exec Request
Part Description Example

<mbean name>

MBean’s ObjectName

java.lang:type=Threading

<operation name>

Name of the operation to execute. If this is an overloaded method, it is mandatory to provide a method signature as well. A signature consist the fully qualified argument class names or native types, separated by commas and enclosed with parentheses. For calling a non-argument overloaded method use () as signature.

loadUsers(java.lang.String, int)

<arg1>, <arg2>, …​

String representation for the arguments required to execute this operation. Only certain data types can be used here as described in Request parameter serialization.

/true/true/

The following request will trigger a garbage collection:

http://localhost:8080/jolokia/exec/java.lang:type=Memory/gc

POST exec request

Table 7. POST Exec Request
Key Description Example

type

exec

mbean

MBean’s ObjectName

java.lang:type=Threading

operation

The operation to execute, optionally with a signature as described above.

dumpAllThreads

arguments

An array of arguments for invoking this operation. The value must be serializable as described in Request parameter serialization.

[true, true]

The following request dumps all threads (along with locked monitors and locked synchronizers, thats what the boolean arguments are for):

{
  "type": "exec",
  "mbean": "java.lang:type=Threading",
  "operation": "dumpAllThreads(boolean, boolean)",
  "arguments": [ true, true ]
}

Exec response

For an exec operation, the response contains the return value of the operation. null is returned if either the operation returns a null value or the operation is declared as void. A typical response for an URL like (mind that double quote (") has to be encoded with %22):

http://localhost:8080/jolokia/exec/java.util.logging:type=Logging/setLoggerLevel/%22%22/INFO

looks like

{
  "request": {
    "mbean": "java.util.logging:type=Logging",
    "arguments": [
      "",
      "INFO"
    ],
    "type": "exec",
    "operation": "setLoggerLevel"
  },
  "value": null,
  "status": 200,
  "timestamp": 1702456520
}

The return value get serialized as described in Request parameter serialization.

With the Jolokia search operation the agent can be queried for MBeans matching a given pattern. Searching will be performed on every MBeanServer found by the agent.

The format of the search GET URL is:

<base-url>/search/<pattern>

This mode is used to query for certain MBean. It takes a single argument pattern for specifying the search parameter like in

http://localhost:8080/jolokia/search/*:j2eeType=Servlet,*

You can use patterns as described here, i.e. it may contain wildcards like * and ?. The Mbean names matching the query are returned as a list within the response.

POST search request

A search POST request knows the following keys:

Table 8. POST Search Request
Key Description Example

type

search

mbean

The MBean pattern to search for

java.lang:*

The following request searches for all MBeans registered in the domain java.lang

{
  "type": "search",
  "mbean": "java.lang:*"
}

The answer is a list of MBean names which matches the pattern or an empty list if there was no match.

For example, the request

http://localhost:8888/jolokia/search/*:j2eeType=Servlet,*
{
  "request": {
    "mbean": "*:j2eeType=Servlet,*",
    "type": "search"
  },
  "value": [
    "Catalina:J2EEApplication=none,J2EEServer=none,WebModule=//localhost/manager,j2eeType=Servlet,name=Status",
    "Catalina:J2EEApplication=none,J2EEServer=none,WebModule=//localhost/manager,j2eeType=Servlet,name=JMXProxy",
    "Catalina:J2EEApplication=none,J2EEServer=none,WebModule=//localhost/manager,j2eeType=Servlet,name=jsp",
    "Catalina:J2EEApplication=none,J2EEServer=none,WebModule=//localhost/manager,j2eeType=Servlet,name=HTMLManager",
    "Catalina:J2EEApplication=none,J2EEServer=none,WebModule=//localhost/jolokia,j2eeType=Servlet,name=jsp",
    "Catalina:J2EEApplication=none,J2EEServer=none,WebModule=//localhost/,j2eeType=Servlet,name=default",
    "Catalina:J2EEApplication=none,J2EEServer=none,WebModule=//localhost/jolokia,j2eeType=Servlet,name=jolokia-agent",
    "Catalina:J2EEApplication=none,J2EEServer=none,WebModule=//localhost/,j2eeType=Servlet,name=jsp",
    "Catalina:J2EEApplication=none,J2EEServer=none,WebModule=//localhost/jolokia,j2eeType=Servlet,name=default",
    "Catalina:J2EEApplication=none,J2EEServer=none,WebModule=//localhost/manager,j2eeType=Servlet,name=Manager",
    "Catalina:J2EEApplication=none,J2EEServer=none,WebModule=//localhost/manager,j2eeType=Servlet,name=default"
  ],
  "status": 200,
  "timestamp": 1702458409
}

The returned MBean names are properly quoted so that they can be directly used as input for other requests.

Listing MBeans (list)

The list operation collects information about accessible MBeans. This information includes the MBean names, their attributes, operations and notifications along with type information and description (as far as they are provided by the MBean author which doesn’t seem to be often the case).

GET list request

The GET request format for a Jolokia list request is

<base-url>/list/<inner path>

The <inner path>, as described in Paths specifies a subset of the complete response. You can use this to select a specific domain, MBean or attribute/operation. See the next section for the format of the complete response.

POST list request

A list POST request has the following keys:

Table 9. POST list Request
Key Description Example

type

list

path

Inner path for accessing the value of a subset of the complete list Paths).

java.lang/type=Memory/attr

The following request fetches the information about the MBean java.lang:type=Memory

{
  "type": "list",
  "path": "java.lang/type=Memory"
}

List response

The value has the following format:

{
  "<domain>": {
    "<prop list>": {
      "attr": {
        "<attr name>": {
          "type": "<attribute type>",
          "desc": "<textual description of attribute>",
          "rw": "true|false"
        },
        ....
      },
      "op": {
        "<operation name>": {
          "args": [
            {
              "type": "<argument type>",
              "name": "<argument name>",
              "desc": "<textual description of argument>"
            },
            .....
          ],
          "ret": "<return type>",
          "desc": "<textual description of operation>"
        },
        .....
      },
      "not": {
        "name": "<name>",
        "desc": "<desc>",
        "types": [ "<type1>", "<type2>" ]
      }
    },
    ....
  },
  ....
}

The domain name and the property list together uniquely identify a single MBean. The property list is in the so called canonical order, i.e. in the form "<key1>=<val1>,<key2>=<val2>,.." where the keys are ordered alphabetically. Each MBean has zero or more attributes and operations which can be reached in an MBeans JSON object with the keys attr and op respectively. Within these groups the contained information is explained above in the schema and consist of Java types for attributes, arguments and return values, descriptive information and whether an attribute is writable (rw == true) or read-only.

As for reading attributes you can fetch a subset of this information using an path. E.g a path of domain/prop-list would return the value for a single bean only. For example, a request

http://localhost:8080/jolokia/list/java.lang/type=Memory

results in an answer

{
  "request": {
    "path": "java.lang/type=Memory",
    "type": "list"
  },
  "value": {
    "op": {
      "gc": {
        "args": [],
        "ret": "void",
        "desc": "gc"
      }
    },
    "notif": {
      "javax.management.Notification": {
        "types": [
          "java.management.memory.threshold.exceeded",
          "java.management.memory.collection.threshold.exceeded"
        ],
        "name": "javax.management.Notification",
        "desc": "Memory Notification"
      }
    },
    "attr": {
      "ObjectPendingFinalizationCount": {
        "rw": false,
        "type": "int",
        "desc": "ObjectPendingFinalizationCount"
      },
      "Verbose": {
        "rw": true,
        "type": "boolean",
        "desc": "Verbose"
      },
      "HeapMemoryUsage": {
        "rw": false,
        "type": "javax.management.openmbean.CompositeData",
        "desc": "HeapMemoryUsage"
      },
      "NonHeapMemoryUsage": {
        "rw": false,
        "type": "javax.management.openmbean.CompositeData",
        "desc": "NonHeapMemoryUsage"
      },
      "ObjectName": {
        "rw": false,
        "type": "javax.management.ObjectName",
        "desc": "ObjectName"
      }
    },
    "class": "sun.management.MemoryImpl",
    "desc": "Information on the management interface of the MBean"
  },
  "status": 200,
  "timestamp": 1702463340
}

Restrict depth of the returned tree

The optional parameter maxDepth can be used to restrict the depth of the return tree. Two value are possible: A maxDepth of 1 restricts the return value to a map with the JMX domains as keys, a maxDepth of 2 truncates the map returned to the domain names (first level) and the MBean’s properties (second level). The final values of the maps don’t have any meaning and are dummy values.

Using JMX notifications (notification) new in Jolokia 2

A new feature of Jolokia 2 is access to JMX notifications. While reading/writing attributes, executing operations or listing/searching MBeans is implemented as single request-response operation, with notifications the flow of messages is more complex.

There are 4 groups of subcommands for Jolokia notification operation:

  • Client registration/unregistration: register and unregister commands

  • Adding/removing/listing the listeners: add, remove and list commands

  • Ping (to refresh the registered client): ping command

  • Configuring a channel to a stream of notifications: open command

Client registration

In order to subscribe to JMX notification, a client has to be registered, so Jolokia agent can be aware of the entities for which notifications should be collected and returned.

The GET URL for client registration has the following format:

<base-url>/notification/register

The equivalend POST JSON payload is:

{
  "type": "notification",
  "command": "register"
}

There are no additional parameters in GET URL request or POST JSON payload.

The general format of the JSON response is described in Responses in detail. A typical response for client registration is:

{
  "request": {
    "type": "notification",
    "command": "register"
  },
  "value": {
    "backend": {
      "pull": {
        "maxEntries": 100,
        "store": "jolokia:type=NotificationStore,agent=192.168.0.221-21185-7e985ce9-servlet"
      },
      "sse": {
        "backChannel.contentType": "text/event-stream",
        "backChannel.encoding": "UTF-8"
      }
    },
    "id": "d77475dc-c7a7-4f71-b988-52b7f0252ca3"
  },
  "status": 200,
  "timestamp": 1702464211
}

The value field in the response contains two important fields:

  • id is an identifier of registered client, which is used in other notification-related Jolokia operations

  • backend is a collection of available backends (See more in Accessing notification stream). Jolokia 2 supports sse and pull backends. Other backends (like websocket) may be added in the future.

    pull backend

    In this implementation, notifications are collected within the Jolokia Agent and client has to fetch (pull) them by calling pull operation on jolokia:type=NotificationStore MBean.

    sse backend

    In this mode Server Sent Events are used (See WhatWG specification).

Client unregistration

In order to unsubscribe from Jolokia notification mechanism, an existing client has to be unregistered, by passing an existing client ID.

The GET URL for client registration has the following format:

<base-url>/notification/unregister/<client-id>
Table 10. GET Unregistration Request
Part Description Example

<client-id>

Client ID of previously registered client

d77475dc-c7a7-4f71-b988-52b7f0252ca3

The equivalend POST JSON payload is:

{
  "type": "notification",
  "command": "unregister",
  "client": "<client-id>"
}
Table 11. POST Unregistration Request
Key Description Example

type

notification

command

unregister

client

Client ID of previously registered client

d77475dc-c7a7-4f71-b988-52b7f0252ca3

A typical response for client unregistration is:

{
  "request": {
    "client": "d77475dc-c7a7-4f71-b988-52b7f0252ca3",
    "type": "notification",
    "command": "unregister"
  },
  "value": null,
  "status": 200,
  "timestamp": 1702464913
}

The returned value is simply null.

Adding a notification listener

Having registered a notification client, we can now use notification listeners. The underlying JMX call is javax.management.MBeanServerConnection.addNotificationListener() method which requires several parameters:

  • ObjectName: The name of the MBean on which the listener should be added

  • NotificationListener: The listener object which will handle the notifications emitted by the registered MBean.

  • NotificationFilter: The filter object. It can be used to filter notifications specific to a given MBean

  • Object: any handback object which will be passed to a listener when notification arrives - this is how Jolokia can get back to the client which added a JMX notification listener.

The GET URL for adding a notification listener has the following format:

<base-url>/notification/add/<client-id>/<mode>/<mbean name>/<filter1>,.../<config>/<handback>
Table 12. GET AddNotification Request
Part Description Example

<client-id>

Client ID of previously registered client

d77475dc-c7a7-4f71-b988-52b7f0252ca3

<mode>

One of supported modes of notification handling: pull or sse

pull

<mbean name>

The ObjectName of the MBean for which we’re registering a notification listener

JMImplementation:type=MBeanServerDelegate

<filter1>, <filter2>, …​

Comma-separated list notifications we’re interested in (and supported by given <mbean name>). If we want all notifications, a space (%20) or empty string (%22%22) can be passed. See filter rules in NotificationFilterSupport.enableType() Javadoc

JMX.mbean.registered

<config>

This optional part can be passed to a notification listener as JSON object

{"priority":"normal"}

<handback>

This optional part can be passed to a notification listener and will be returned for each related notification. In GET request it can only be a String value.

my-correlation-ID-1234

The equivalent POST JSON payload is:

{
  "type": "notification",
  "command": "add",
  "client": "1cddf91c-423e-46d8-ac9a-2eb6d8b213c7",
  "mode": "pull",
  "mbean": "JMImplementation:type=MBeanServerDelegate",
  "filter": [],
  "handback": "id-1234"
}
Table 13. POST AddNotification Request
Key Description Example

type

notification

command

add

client

Client ID of previously registered client

d77475dc-c7a7-4f71-b988-52b7f0252ca3

mode

One of supported modes of notification handling: pull or sse

pull

mbean

The ObjectName of the MBean for which we’re registering a notification listener

JMImplementation:type=MBeanServerDelegate

filter

A JSON array of notification notifications we’re interested in (and supported by given mbean). If we want all notifications, a space (%20) or empty string (%22%22) can be passed. See filter rules in NotificationFilterSupport.enableType() Javadoc

[ "JMX.mbean.registered", "JMX.mbean.unregistered" ]

config

This optional part can be passed to a notification listener as JSON object

{"priority":"normal"}

handback

This optional part can be passed to a notification listener and will be returned for each related notification.

{ "my-correlation-ID": "1234" }

A typical response for added notification listener is:

{
  "request": {
    "mode": "pull",
    "mbean": "JMImplementation:type=MBeanServerDelegate",
    "client": "1cddf91c-423e-46d8-ac9a-2eb6d8b213c7",
    "type": "notification",
    "handback": "id-1234",
    "command": "add"
  },
  "value": "2",
  "status": 200,
  "timestamp": 1702472334
}

The returned value is a handle to the added listener, required when removing the listener in the future.

Checking existing notification listeners

To check existing listener registrations for previously registered client, we can use list command of notification operation.

The GET URL for listing client listener registrations has the following format:

<base-url>/notification/list/<client-id>
Table 14. GET Unregistration Request
Part Description Example

<client-id>

Client ID of previously registered client

d77475dc-c7a7-4f71-b988-52b7f0252ca3

The equivalend POST JSON payload is:

{
  "type": "notification",
  "command": "list",
  "client": "<client-id>"
}
Table 15. POST Unregistration Request
Key Description Example

type

notification

command

list

client

Client ID of previously registered client

d77475dc-c7a7-4f71-b988-52b7f0252ca3

A typical response for listing the registrations is:

{
  "request": {
    "client": "1cddf91c-423e-46d8-ac9a-2eb6d8b213c7",
    "type": "notification",
    "command": "list"
  },
  "value": {
    "1": {
      "mbean": "JMImplementation:type=MBeanServerDelegate",
      "handback": "id-1234"
    },
    "2": {
      "filter": [
        "java.management.memory.threshold.exceeded"
      ],
      "mbean": "java.lang:type=Memory"
    }
  },
  "status": 200,
  "timestamp": 1702472848
}

The returned value is a collection of listener registrations with their details, keyed by handle id.

Removing a notification listener

When a notification listener for a given client is no longer needed, we can remove using remove command of notification operation.

The GET URL for removing client listener registrations has the following format:

<base-url>/notification/remove/<client-id>/<handle>
Table 16. GET Unregistration Request
Part Description Example

<client-id>

Client ID of previously registered client

d77475dc-c7a7-4f71-b988-52b7f0252ca3

<handle>

A handle of previously added listener

1

The equivalend POST JSON payload is:

{
  "type": "notification",
  "command": "remove",
  "client": "<client-id>",
  "handle": "<handle-id>"
}
Table 17. POST Unregistration Request
Key Description Example

type

notification

command

remove

client

Client ID of previously registered client

d77475dc-c7a7-4f71-b988-52b7f0252ca3

handle

A handle of previously added listener

1

A typical response for listing the registrations is:

{
  "request": {
    "client": "1cddf91c-423e-46d8-ac9a-2eb6d8b213c7",
    "handle": "1",
    "type": "notification",
    "command": "remove"
  },
  "value": null,
  "status": 200,
  "timestamp": 1702473703
}

The returned value is simply null.

Accessing notification stream

When a listener is added for a client (see Adding a notification listener), a mode indicates a desired notification backend.

For pull backend, there’s actually no back channel over which we can receive the notifications. Instead we should be calling an MBean operation on an MBean representing the backend. The details are available when client was first registered:

"backend": {
  "pull": {
    "maxEntries": 100,
    "store": "jolokia:type=NotificationStore,agent=192.168.0.221-21185-7e985ce9-servlet"
  },
  ...

If we want to access notifications collected in pull backend, we have to call org.jolokia.service.notif.pull.PullNotificationStoreMBean.pull(String pClientId, String pHandle) MBean operation on jolokia:type=NotificationStore,agent=<agent-id> MBean which can be done with Jolokia exec operation.

For example, having registered a notification listener for JMImplementation:type=MBeanServerDelegate MBean, we can get notified about MBean registrations/unregistrations. Accessing the pull notification store can be done with exec operation like this:

$ curl -s -u jolokia:jolokia 'http://localhost:8080/jolokia/exec/jolokia:type=NotificationStore,agent=192.168.0.221-21185-7e985ce9-servlet/pull(java.lang.String,java.lang.String)/1cddf91c-423e-46d8-ac9a-2eb6d8b213c7/2' | jq .
{
  "request": {
    "mbean": "jolokia:agent=192.168.0.221-21185-7e985ce9-servlet,type=NotificationStore",
    "arguments": [
      "1cddf91c-423e-46d8-ac9a-2eb6d8b213c7",
      "2"
    ],
    "type": "exec",
    "operation": "pull(java.lang.String,java.lang.String)"
  },
  "value": {
    "dropped": 0,
    "handle": "2",
    "handback": "id-1234",
    "notifications": [
      {
        "timeStamp": 1702473332222,
        "sequenceNumber": 248,
        "userData": null,
        "mBeanName": {
          "objectName": "Catalina:name=HttpRequest3,type=RequestProcessor,worker=\"http-nio-8080\""
        },
        "source": {
          "objectName": "JMImplementation:type=MBeanServerDelegate"
        },
        "message": "",
        "type": "JMX.mbean.registered"
      },
      {
        "timeStamp": 1702473390407,
        "sequenceNumber": 249,
        "userData": null,
        "mBeanName": {
          "objectName": "Catalina:J2EEApplication=none,J2EEServer=none,WebModule=//localhost/,name=jsp,type=JspMonitor"
        },
        "source": {
          "objectName": "JMImplementation:type=MBeanServerDelegate"
        },
        "message": "",
        "type": "JMX.mbean.unregistered"
      },
...

On the other hand, sse notification store works differently. Here’s the information received during client registration:

"sse": {
  "backChannel.contentType": "text/event-stream",
  "backChannel.encoding": "UTF-8"
}

Instead of providing us with Mbean name to access when needed (pull the notifications out if it by calling an MBean operation), sse backend needs a channel associated with client connection. This is where open command for notification operation comes into play.

When calling open command for sse backed notifications, the request (HttpServletRequest) is put into asynchronous mode and connection is not closed.

The GET URL for openning a backend channel for notification acces is:

<base-url>/notification/open/<client-id>/<mode>
Table 18. GET Open Notification Channel Request
Part Description Example

<client-id>

Client ID of previously registered client

d77475dc-c7a7-4f71-b988-52b7f0252ca3

<mode>

A mode of notification delivery. Only sse is useful.

sse

The equivalend POST JSON payload is:

{
  "type": "notification",
  "command": "open",
  "client": "<client-id>",
  "mode": "<mode>"
}
Table 19. POST Open Notification Channel Request
Key Description Example

type

notification

command

open

client

Client ID of previously registered client

d77475dc-c7a7-4f71-b988-52b7f0252ca3

mode

A mode of notification delivery. Only sse is useful.

sse

The notifications are returned to the client as they’re delivered from JMX. Here’s a sample interaction:

$ curl -i -u jolokia:jolokia 'http://localhost:8080/jolokia/notification/open/c72e2f07-e5ec-47a0-b9b4-3036b16614a0/sse'
HTTP/1.1 200
Cache-Control: private
Content-Type: text/event-stream;charset=UTF-8
Transfer-Encoding: chunked
Date: Wed, 13 Dec 2023 15:49:39 GMT

:
:
:
:
:
id: 363
data: {"dropped":0,"handle":"2","handback":null,"notifications":[{"timeStamp":1702482067031,"sequenceNumber":363,"userData":null,"mBeanName":{"objectName":"Catalina:J2EEApplication=none,J2EEServer=none,WebModule=\/\/localhost\/,name=jsp,type=JspMonitor"},"source":{"objectName":"JMImplementation:type=MBeanServerDelegate"},"message":"","type":"JMX.mbean.unregistered"}]}

id: 364
data: {"dropped":0,"handle":"2","handback":null,"notifications":[{"timeStamp":1702482067031,"sequenceNumber":364,"userData":null,"mBeanName":{"objectName":"Catalina:J2EEApplication=none,J2EEServer=none,WebModule=\/\/localhost\/,j2eeType=Filter,name=Tomcat WebSocket (JSR356) Filter"},"source":{"objectName":"JMImplementation:type=MBeanServerDelegate"},"message":"","type":"JMX.mbean.unregistered"}]}

The returned data is structured according to text/event-stream Mime type. id and data fields are used, where id matches the sequenceNumber from the JSON payload.

Getting the agent version (version)

The Jolokia command version returns the version of the Jolokia agent along with the protocol version.

GET version request

The GET URL for a version request has the following format:

<base-url>/version

For GET request the version part can be omitted since this is the default command if no command is provided as path info.

POST version request

A version POST request has only a single key type which has to be set to version.

Version response

The response value for a version request looks like:

{
  "request": {
    "type": "version"
  },
  "value": {
    "agent": "2.0.2",
    "protocol": "7.2",
    "details": {
      "agent_version": "2.0.2",
      "agent_id": "192.168.0.221-21185-5ce94d31-servlet",
      "server_product": "tomcat",
      "server_vendor": "Apache",
      "server_version": "10.1.16",
      "secured": true,
      "url": "http://192.168.0.221:8080/jolokia"
    },
    "id": "192.168.0.221-21185-5ce94d31-servlet",
    "config": {
      "maxDepth": "15",
      "discoveryEnabled": "false",
      "agentId": "192.168.0.221-21185-5ce94d31-servlet",
      "maxCollectionSize": "0",
      "debug": "false",
      "canonicalNaming": "true",
      "historyMaxEntries": "10",
      "serializeException": "false",
      "includeStackTrace": "false",
      "maxObjects": "0",
      "detectorOptions": "{}",
      "debugMaxEntries": "100"
    },
    "info": {
      "proxy": {},
      "jmx": {}
    }
  },
  "status": 200,
  "timestamp": 1702482830
}

protocol in the response value contains the protocol version used, agent is the version of the Jolokia agent. See Jolokia protocol versions for the various protocol versions and the interoperability. If the agent is able to detect the server, additional meta information about this server is returned (i.e. the product name, the vendor and optionally some extra information added by the server detector).

Processing parameters

Jolokia operations can be influenced by so-called processing parameters. These parameters are provided differently for POST and GET requests.

For GET request, the processing parameter are given as normal query parameters:

<GET request URL>?param1=value1&param2=value2&...

For example the request

http://localhost:8080/jolokia/list?maxObjects=100

will limit the response to at max 100 values.

POST request take the processing instructions within the JSON request below the key config:

{
  "type" : "list",
  "config" : {
    "maxObjects" : 100
  }
}

If a POST request carries also query parameters in the URL, these processing parameters are merged with the ones given within the request body. Configuration options given in the request body take precedence over the ones given as query parameters.

The list of known processing parameters is:

maxDepth

Maximum depth of the tree traversal into a bean’s properties. The maximum value as configured in the agent’s configuration is a hard limit and cannot be exceeded by a query parameter.

maxCollectionSize

For collections (lists, maps) this is the maximum size.

maxObjects

Number of objects to visit in total. A hard limit can be configured in the agent’s configuration.

ignoreErrors

If set to true, a Jolokia operation will not return an error if an JMX operation fails, but includes the exception message as value. This is useful for e.g. the read operation when requesting multiple attributes' values. Default: false

mimeType

The MIME type to return for the response. By default, this is text/plain, but it can be useful for some tools to change it to application/json. Init parameters can be used to change the default mime type. Only text/plain and application/json are allowed. For any other value Jolokia will fallback to text/plain.

canonicalNaming

Defaults to true to return the canonical format of property lists. If set to false then the default unsorted property list is returned.

includeStackTrace

If set to true, then in case of an error the stack trace is included. With false no stack trace will be returned, and when this parameter is set to runtime only for RuntimeExceptions a stack trace is put into the error response. Default is false if not set otherwise in the global agent configuration.

serializeException

If this parameter is set to true then a serialized version of the exception is included in an error response. This value is put under the key error_value in the response value. By default this is set to false except when the agent global configuration option is configured otherwise.

ifModifiedSince

If this parameter is given, its value is interpreted as epoch time (seconds since 1.1.1970) and if the requested value did not change since this time, an empty response (with no value) is returned and the response status code is set to 304 ("Not modified"). This option is currently only supported for list requests. The time value can be extracted from a previous' response timestamp.

Object serialization

Jolokia has some object serialization facilities in order to convert complex Java data types to JSON and vice versa. Serialization works in both ways in requests and responses, but the capabilities differ.

Complex data types returned from the agent can be serialized completely into a JSON value object. It can detect cycles in the object graph and provides a way to limit the depth of serialization. For certain types (like File or ObjectName) it uses a service org.jolokia.service.serializer.json.simplifier.SimplifierExtractor to not expose internal and redundant information.

Object values used for values in write operations and arguments in exec, type support is limited to a handful of data types.

Response value serialization

Jolokia can serialize any object into a JSON representation when generating the response. It uses some specific converters for certain well known data type with a generic bean converter as fallback.

The following types are directly supported:

  • Arrays and java.util.List are converted to JSON arrays

  • java.util.Map gets converted into a JSON object. Note, however, that JSON Object keys are always strings.

  • Enums are converted to their canonical name[5].

  • javax.management.openmbean.CompositeData is converted in a JSON object, with the keys taken from the `CompositeData’s key set and the value are its values.

  • javax.management.openmbean.TabularData is serialized differently depending on its internal structure. See below for a detailed explanation of this serialization mechanism including examples.

  • java.lang.Class is converted to a JSON object with keys name (the class name) and interfaces (the implemented interfaces, if any)

  • java.io.File becomes a JSON object with keys name (file name), modified (date of last modification), length (file size in bytes), directory (whether the file is a directory), canonicalPath (the canonical path) and exists.

  • javax.management.ObjectName is converted into a JSON object with the single key objectName.

  • java.net.URL becomes a JSON object with the key url containing the URL as String.

  • java.util.Date is represented in an ISO-8601 format. When used with a path time the milliseconds since 1.1.1970 00:00 UTC are returned.

  • org.w3c.dom.Element is translated into a JSON object with the properties name, value and hasChildNodes.

  • java.math.BigInteger becomes a JSON object with the key bigint containing the big integer value as String.

Primitive and simple types (like String) are directly converted into their string presentation. All objects not covered by the list above are serialized in JSON objects, where the keys are the public bean properties of the object and the values are serialized (recursively) as described.

TabularData serialization depends on the type of the index. It is serialized into one or multiple nested JSON objects where the keys are derived from its TabularType.indexNames(). If there is a single valued index with a simple type (i.e. an instance of javax.management.openmbean.SimpleType), the index’s value is the key and a TabularData's row (which in turn is a CompositeData) is a map. With multi valued, simple typed, keys, the map is nested (first level: first index’s value, second level: second index’s value and so on). For the serialization of TabularData resulting from a MXBean translation for maps, see Jolokia and MXBeans. If any of the declared index keys of a TabularData is a complex type (i.e. not a SimpleType), then this simple serialization into maps of maps is not possible anymore, since for JSON, map keys must be simple types. In this case, a more generic serialization is used in which case an JSON object with two keys is returned: indexNames is an array with the TabularData’s indexes as names and `values is the array containing the values as JSON object with the corresponding rows as values (including the indexes).

For example if there is a single valued key key, then the returned JSON looks like

{
  "mykey1" : { "key" : "mkey1", "item" : "value1", .... },
  "mykey2" : { "key" : "mkey2", "item" : "value2", .... },
  ...
}

For multi valued keys of simple open types (i.e. TabularType.getIndexNames()) is a list with more than one element but all of them are simple types), the returned JSON structure looks like (index names here are key and innerkey):

{
  "mykey1" : {
    "myinner1" : { "key" : "mkey1", "innerkey" : "myinner1", "item" : "value1", .... },
    "myinner2" : { "key" : "mkey1", "innerkey" : "myinner2", "item" : "value1", .... },
    ....
  },
  "mykey2" : {
    "second1" : { "key" : "mkey2", "innerkey" : "second1", "item" : "value1", .... },
    "second2" : { "key" : "mkey2", "innerkey" : "second2", "item" : "value1", .... },
    ....
  },
  ....
}

If keys are used, which themselves are complex objects (like CompositeData), this hierarchical map structure can not be used. In this case an object with two keys is returned: indexNames holds the name of the key index and values is an array of all rows which are represented as JSON objects:

{
  "indexNames" : [ "key", "innerkey" ],
  "values" : [
    { "key" : "mykey1", "innerkey" : { "name" : "a", "number" : 4711 }, "item" : "value1", .... },
    { "key" : "mykey2", "innerkey" : { "name" : "b", "number" : 815 }, "item" : "value2", .... },
    ...
  ]
}

Beside this special behaviour for TabularData, serialization can be influenced by certain processing parameters given with the request (see Processing parameters). I.e. the recursive process of JSON serialization can be stopped when the data set gets too large. Self and other circular references are detected, too. If this happen, special values indicate the truncation of the generated JSON object.

[this]

This label is used when a property contains a self reference

[Depth limit …​. ]

When a depth limit is used or the hard depth limit is exceeded, this label contains a string representation of the next object one level deeper. (see Processing parameters, parameter maxDepth)

[Reference …​. ]

If during the traversal an object is visited a second time, this label is used in order to break the cycle.

[Object limit exceeded]

The total limit of object has been exceeded and hence the object are not deserialized further. (see Processing parameters, parameters maxCollectionSize and maxObjects)

Request parameter serialization

Serialization in the upstream direction (i.e. when sending values for write operations or arguments for exec operations) differs from from the object serializaton as used as response values which is described in Response value serialization. Not all types are supported for upstream serialization [6] and the capabilities differ also for POST and GET requests. GET upstream serialization is limited to basic types and simple arrays. POST requests on the other support a much large set of types, including the serialization of Maps, Lists and all Open Types.

GET request values

Since parameters get encoded in the URL for GET request, only the following types can used for values and arguments in write and exec requests:

  • String

  • Integer / int

  • Long / long

  • Byte / byte

  • Short / short

  • Float / float

  • Double / double

  • BigDecimal / BigInteger

  • char

  • Boolean / boolean

  • Date

  • URL

  • Enums (whose type is accessible to the agent, see below)

  • Any type, that is accessible to the agent, and has a public constructor with one String parameter

The serialized value is simply the string representation of those types. Dates can be set either by an long value (epoch milliseconds) or with a string value (ISO-8601 format). Arrays of the given types are serialized as a comma separated list.

The array support is somewhat limited since it makes a native split on commas. It does not yet take into account any quoting or escaping. For a much safer way to transport arrays to the agent, please consider using POST requests.

Certain tag values are used to mark special values. A null value has to be serialized as [null], an empty String as "". Tag values are not required for POST requests.

POST request values

POST request take advantage of the JSON type of the value transfered. These are basic types for numbers (42 or 23.5), booleans (true or false) and strings ("habanero"). Also, JSON knows about null values so no special 'tags' like for GET requests are not required. Since JSON supports intrinsically key-value maps and array types, these can be used directly, too. I.e. if the JMX operation to execute takes a Map argument, the argument can be given as a JSON object. Be aware, however, that JSON maps (objects) only support strings as keys.

The agent knows how to convert a JSON array to Java Arrays (of a basic type) or Lists, depending on the requirement as dictated by the MBeans operation or attribute signature. Numbers in JSON are always transfered as long or double values and are as well tried to fit to the MBean’s signature. In case of an overflow (e.g. when trying to treat a long with a too large value as int), an exception is raised.

Enums can be converted from their canonical name. The prerequisite for this is, that the Jolokia agent has access to the Enum’s class. This is true for all Enums shipped with the JDK (like TimeUnit). Custom enums can not be used for upstream serialization by default since the Jolokia Agent is not able to construct an instance of it because of missing type information.

Upstream serialization also supports Open Types. If the signature of JMX exec operation or the value type of a JMX attribute is a OpenType, they are serialized as follows:

  • SimpleTypes are extracted from their corresponding JSON type.

  • ArrayType is extracted from a JSONArray where the elements are serialized recursively with this algorithms. Only ArrayTypes with element type CompositeType or SimpleType are supported.

  • CompositeType is extracted recursively from a JSONObject where there the string keys must fit to the CompositeType's item names and the values must be serializable as open types.

  • TabularType is converted from JSONObject. If it is single index (i.e. has only one single index name), the JSONObject must have the index values as string keys and the map values are other JSONObject's representing the row data. For TabularType's with more than one index name, the incoming JSONObject must be a nested object with each index as an additional layer. E.g. the following JSON object works for a TabularType with the two index names lastname and firstname, which are both of type SimpleType.STRING:

{
  "Mann": {
    "Thomas": {
      "lastname": "Mann",
      "firstname": "Thomas",
      "birth": 1875
    },
    "Heinrich": {
      "lastname": "Mann",
      "firstname": "Heinrich",
      "birth": 1871
    }
  }
}

TabularType used by the MXBean framework for serialization of Maps are translated directly from maps. More details are explained in the next section Jolokia and MXBeans.

TabularType's with index values which are not of type SimpleType can be used, too. However, in this case this simple nested map structure is not enough, since keys of complex types (e.g. CompositeData types) can not be represented as JSON map keys. Instead, a generic representation for TabularTypes must be used. A JSON object with two keys: indexNames with an array of the index names and values with an array of rows containing objects which include the index values plus any other values of the rows' CompositeType. E.g. if in the example above, the index would have been an User with first- and lastname, the JSON structure for setting the TabularData should look like

{
  "indexNames": [ "user" ],
  "values" : [
    { "user" : { "lastname": "Mann", "firstname": "Thomas" }, "birth": 1875 },
    { "user" : { "lastname": "Mann", "firstname": "Heinrich" }, "birth": 1871 }
  ]
}

Jolokia and MXBeans

The MXBean Framework is available in the JDK since version 6 and allows for easy creation and registration of own MBeans. MXBeans are some what the successor for standard MBeans and support an annotation driven as well as a naming convention driven programming model. The most important difference to standard MBeans it the restriction of MXBean to reference only open types.

Although to the outside only open types are exposed by the MXBean framework, MXBean themselves can use more complex data types. The framework will translate forth and back between the custom and open types according to certain rules as declared in the MXBean specification. Most of the translations to open types fits naturally to Jolokia’s serialization, except for the translation of Map.

When an MXBean references a map, the MXBean framework translates this map into a TabularData with a fixed internal structure, i.e. with an index key and rows with keys key and value. This leads directly to a JSON representation which is quite artificial. E.g a map with two keys kind and hotness will be converted by the MXBean framework to a TabularData object which in turn would be translated by Jolokia to the following JSON structure

{
  "kind" : {
    "key": "kind",
    "value": "Habanero"
  },
  "hotness" : {
    "key": "hotness",
    "value": 10
  }
}

Since this representation of a simple map is unnecessarily complicated, Jolokia treats TabularData of this kind (i.e. one index key and rows with properties key and value) specially in order to translate it back (and forth) to

{
  "kind" : "Habanero",
  "hotness" : 10
}

Tracking historical values

The Jolokia agents are able to keep requested values in memory along with a timestamp. If history tracking is switched on, then the agent will put the list of historical values specific for this request into the response. History tracking is toggled by an MBean operation on a Jolokia-owned MBean (see Jolokia MBeans). This has to be done individually for each attribute or JMX operation to be tracked.

A history entry is contained in every response for which history tracking was switched on. A certain JMX operation on an Jolokia specific MBean has to be executed to turn history tracking on for a specific attribute or operation. See Jolokia MBeans for details.The history property of the JSON response contains an array of json objects which have two attributes: value containing the historical value (which can be as complex as any other value) and timestamp indicating the time when this value was current (as measured by the server). JSON Response has an example of a response containing historical values.

For multi attribute read requests, the history entry in the response is a JSON object instead of an array, where this object’s attributes are the request’s attribute names and the values are the history arrays as described above.

Proxy requests

For proxy requests, POST must be used as HTTP method so that the given JSON request can contain an extra section for the target which should be finally reached via this proxy request. A typical proxy request looks like

{
  "type" : "read",
  "mbean" : "java.lang:type=Memory",
  "attribute" : "HeapMemoryUsage",
  "target" : {
    "url" : "service:jmx:rmi:///jndi/rmi://targethost:9999/jmxrmi",
    "user" : "jolokia",
    "password" : "s!cr!t"
  }
}

url within the target section is a JSR-160 service URL for the target server reachable from within the proxy agent. user and password are optional credentials used for the JSR-160 Remote communication.

Agent Discovery

Jolokia agents are able to respond to certain multicast requests in order to allow clients to detect automatically connection parameters. The agent URL to expose can be either manually configured for an agent or an agent can try to detect its URL automatically. This works fine for the JVM agent, for the WAR agent it only works after the first HTTP request has been processed by the agent. Due to limitations of the Servlet API the agent servlet has no clue about its own URL until this first request, which contains the request URL. Of course, the URL obtained that way can be bogus as well, since the agent might hide behind a proxy, too. So, if in doubt you should configure the agent URL from outside to allow external clients to be discovered. The configuration options for enabling multicast requests are described in the JVM agent configuration options and Servlet init parameters agent configuration sections.

A agent which is enabled for multicast discovery will only respond to a multicast request if the Policy based security allows connections from the source IP. Otherwise a multicast request will be simply ignored. For example, if you have configured your agent to only allow request from a central monitoring host, only this host is able to detect these agents. Beside security aspects it wouldn’t make sense to expose the URL as any other host is not able to connect anyways.

Starting with version 1.2.0 the Jolokia JVM agent has this discovery feature enabled by default which can be switched off via --discoveryEnabled=true command line parameter or the corresponding configuration option. For the WAR agent and OSGi agents this feature is switched off by default since auto detection doesn’t always work. It can be enabled with the init parameter discoveryEnabled (in which case the auto discovery described above is enabled) or better with discoveryAgentUrl with the URL. Alternatively, a system property can be used with a jolokia. prefix (e.g. jolokia.discoveryEnabled). More on the configuration options can be found in the agent’s configuration sections.

For sending a multicast request discovery message, an UDP message should be send to the address 239.192.48.84, port 24884 which contains a JSON message encoded in UTF-8 with the following format

{
  "type": "query"
}

We can use a tool like netcat to check the discovery:

$ nc -u localhost 24884
{"type":"query"}
{"agent_version":"2.0.2","agent_id":"192.168.0.221-67980-7e985ce9-servlet",
"server_product":"tomcat","type":"response","server_vendor":"Apache",
"server_version":"10.1.16","secured":true,"url":"http:\/\/192.168.0.221:8080\/jolokia"}

Any agent enabled for discovery will respond to requester on the same socket with an answer which looks like

{
  "agent_version": "2.0.2",
  "agent_id": "192.168.0.221-67980-7e985ce9-servlet",
  "server_product": "tomcat",
  "type": "response",
  "server_vendor": "Apache",
  "server_version": "10.1.16",
  "secured": true,
  "url": "http://192.168.0.221:8080/jolokia"
}

The response itself is a JSON object and is restricted to 8192 bytes maximum. The request type is either query or response. A query request is sent via multicast by any interested client and each agent responds with a response of type response. Query requests contain only the type as property. Responses are sent back to the address and port of the sender of the query request.

Please note, that IPv6 is currently not supported yet but likely in the future.

Table 20. Response properties
Property Description Example

type

Request type, either query or response.

query or response

agent_id

Each agent has a unique id which can be either provided during startup of the agent in form of a configuration parameter or being autodetected. If autodetected, the id has several parts: The IP, the process id, hashcode of the agent and its type. This field will be always provided.

192.168.0.221-67980-7e985ce9-servlet

agent_description

An optional description which can be used as a UI label if given.

ServiceMix ESB

url

The URL how this agent can be contacted. This URL is typically autodetected. For the JVM agent it should be highly accurate. For the servlet based agents, it depends. If configured via an initialisation parameter this URL is used. If autodetected it is taken from the first HTTP request processed by the servlet. Hence no URL is available until this first request was processed. This property might be empty.

http://192.168.0.221:8080/jolokia

secured

Whether the agent was configured for authentication or not.

false

server_vendor

The vendor of the container the agent is running in. This field is included if it could be automatically detected.

Apache

server_product

The container product if detected

tomcat

server_version

The container’s version (if detected)

10.1.16

Jolokia protocol versions

The protocol definition is versioned. It contains of a major and minor version. Changes in the minor version are backward compatible to other protocol with the same major version. Major version changes incorporate possibly backwards incompatible changes. This document describes the Jolokia protocol version 7.3.

7.3 (since 2.0.0)

Support for notification command and related JSON messages.

7.2 (since 1.2.2)

Paths can now be used with wildcards (*) which match everything in the selected level. They are especially useful with pattern read requests.

7.1 (since 1.2.0)

The version command returns now the configuration global information as well with the key config in the returned value.

7.0 (since 1.1.0)

The maxDepth parameter (either as processing parameter or as configuration value) is now 1 based. I.e. 0 means always "no limit" (be careful with this, though), 1 implies truncating the value on the first level for READ request. This was already true for LIST requests and the other limit values (maxCollectionSize and maxObjects) so this change is used in order to harmonize the overall behaviour with regard to limits.

Enums are now serialized downstream (full support) and upstream (for type accessible to the agent).

New query parameter options serializeException (for setting an error_value in case of an exception), canonicalNaming (influences how object names are returned) and includeStackTrace (for adding or omitting stacktraces in error responses).

6.1 (since 1.0.2)

Error responses contain now the original request as well, for single and bulk requests.

6.0 (since 1.0.0)

Escaping has been changed from /-/ to !/. This affects GET Urls and inner paths.

5.0 (since 0.95)

javax.management.openmbean.TabularData is serialized differently when generating the response. In fact, the serialization as an array in the former versions of this protocol is not correct, since TabularData in fact is a hash and not a list. It is now generated as map (or multiple maps), depending on the declared index. Also, access via path is now an access via key, not a list index. For the special case of MXBean map serialization, where the returned TabularData has a fixed format (i.e. with key and value columns), the TabularData is transformed to an appropriate map.

Removed JSON property modified from the serialized JSON representation of a File return value since it duplicated the lastModified property on the same object.

4.3 (since 0.91)

The list operation supports a maxDepth option for truncating the answer.

4.2 (since 0.90)

Response values are returned in the native JSON datatype, not always as strings as in previous versions of this protocol. Parameter serialization for writing attribute values or for arguments in exec operations has been enhanced for POST requests, which are now represented as native JSON types and not in a string representation as before. GET requests still use a simplified string representation.

4.0 (17.10.2010)

This is the initial version for Jolokia. Versions below 4 are implemented by jmx4perl.


1. This document will avoid the term REST as much as possible in order to avoid provoking any dogmatic resentments.
2. A backslash (\) can not be used, since most servlet container translate a backslash into a forward slash on the fly when given in an URL.
3. Seconds since 1.1.1970
4. If the server exception is a subtype of MBeanException, the wrapped exception’s message is used.
5. For JBoss older than version 7, there might be use cases when custom enums need to be serialized. In this case, the type information must be available to the agent, too. For the standard PlatformMBeanServer serialization should work always, regardless whether the customer enum type is accessible by the agent or not.
6. Conversion from a typed system to an untyped representation is obviously much easier than vice versa. Please note, that Jolokia does not replace a full blown JSON object serialization framework like Jackson. Nor does it use one in order to keep the agent small and simple with a low dependency count.
This page was built using the Antora default UI. The source code for this UI is licensed under the terms of the MPL-2.0 license. | Copyright © 2010 - 2023 Roland Huß