Using JMX and J2SE 5.0 to Securely Manage Web Applications Blog



    Setting Up the Sample Application
    Tracking Some Meaningful Data
    Creating JMX MBeans
    Creating and Securing a JMX Agent
       Creating aMBeanServer with MBeans
       Creating theJMXServiceURL
       Securing the Service
    Starting the RMI Registry
       Using the Command Line
       Programmatically Starting the RMI Registry
    Accessing our JMX Service
       Connecting using MC4J
       Connecting to a "Cluster" using jManage

    JMX (Java Management Extensions) supplies tools for managing local and remote applications, system objects, devices, and more. This article will explain how to remotely manage a web application using JMX (JSR 160). It will explain the code needed inside of the application to make it available to JMX clients and will demonstrate how to connect to your JMX-enabled application using different clients such as MC4J and jManage. Securing the communication layer using the RMI protocol and JNDI is also covered in detail.

    We will review a simple web application that monitors the number of users that have logged in and exposes that statistic via a secure JMX service. We will also run multiple instances of this application and track statistics from all running instances. The sample web application can be downloaded here. It requires you to have the J2SE 5.0 SDK installed and your JAVA_HOME environment variable pointing to the base installation directory. J2SE 5.0 implements the JMX API, version 1.2, and the JMX Remote API, version 1.0. A supporting servlet container is also required; I'm using Apache Tomcat 5.5.12. I'm also using Apache Ant to build the sample application.

    Setting up the Sample Application

    Download the sample application and create a WAR file with ant war(for more details, see the comments in build.xml). Copyjmxapp.war to Tomcat's webapps directory. Assuming Tomcat is running on your local machine on port 8080, the URL for the application will be:


    If you see a login screen that prompts you for your username and password, all is well.

    Tracking Some Meaningful Data

    The sample application uses the Struts framework to submit the login form. Upon submission, theLoginAction.execute(..) method is executed, which quite simply checks that the user ID is "hello" and the password is "world." If both are true, then the login was successful and control is forwarded to login_success.jsp; if not, then we go back to the login form. Depending on whether the login was successful or not, theincrementSuccessLogins(HttpServletRequest) method or the incrementFailedLogins(HttpServletRequest) method is called. Let's have a look atincrementFailedLogins(HttpServletRequest):

    private void incrementFailedLogins (HttpServletRequest request) { HttpSession session = request.getSession(); ServletContext context = session.getServletContext(); Integer num = (Integer) context.getAttribute( Constants.FAILED_LOGINS_KEY); int newValue = 1; if (num != null) { newValue = num.intValue() + 1; } context.setAttribute( Constants.FAILED_LOGINS_KEY, new Integer(newValue)); }

    The method increments a FAILED_LOGINS_KEY variable that is stored in application scope. TheincrementSuccessLogins(HttpServletRequest) method is implemented in a similar way. The application now keeps track of how many people successfully logged in and how many failed authentication. That's great, but how do we access this data? That's where JMX kicks in.

    Creating JMX MBeans

    The basics of MBeans and where they fit into the JMX architecture is beyond the scope of this article. We will simply create, implement, expose, and secure an MBean for our application. We are interested in exposing two pieces of data corresponding to the following two methods. Here is the our simple MBean interface:

    public interface LoginStatsMBean { public int getFailedLogins(); public int getSuccessLogins(); } 

    Quite simply, the two methods return the number of failed and successful logins. The LoginStatsMBean implementation,LoginStats, provides a concrete implementation for both methods. Let's have a look at thegetFailedLogins() implementation:

    public int getFailedLogins() { ServletContext context = Config.getServletContext(); Integer val = (Integer) context.getAttribute( Constants.FAILED_LOGINS_KEY); return (val == null) ? 0 : val.intValue(); }

    The method returns a value stored in theServletContext. The getSuccessLogins()method is implemented in a similar manner.

    Creating and Securing a JMX Agent

    The JMXAgent class that manages the JMX-related aspects of the application has a few responsibilities:

    1. Create an MBeanServer.
    2. Register LoginStatsMBean with theMBeanServer.
    3. Create a JMXConnector, allowing remote clients to connect.
      • Involves use of JNDI.
      • Must also have an RMI registry running.
    4. Securing the JMXConnector using a username and password.
    5. Starting and stopping the JMXConnector on application start and stop, respectively.

    The class outline for JMXAgent is:

    public class JMXAgent { public JMXAgent() { // Initialize JMX server } public void start() { // Start JMX server } // called at application end public void stop() { // Stop JMX server } }

    Let's understand the code in the constructor that will enable clients to remotely monitor the application.

    Creating aMBeanServer with MBeans

    We first create a MBeanServer object, which is the core component of the JMX infrastructure. It allows us to expose our MBeans as manageable objects. TheMBeanServerFactory.createMBeanServer(String) method makes this an easy task. The parameter supplied is the domain of the server. Think of this as the unique name for thisMBeanServer. Next, we register theLoginStatsMBean with the MBeanServer. TheMBeanServer.registerMBean(Object, ObjectName) method takes in as a parameter an instance of the MBean implementation and an object of type ObjectName that uniquely identifies the MBean; in this case, DOMAIN + ":name=LoginStats"suffices.

    MBeanServer server = MBeanServerFactory.createMBeanServer(DOMAIN); server.registerMBean( new LoginStats(), new ObjectName(DOMAIN + ":name=LoginStats")); 
    Creating theJMXServiceURL

    At this point, we have created an MBeanServer and registered LoginStatsMBean with it. The next step is to make the server available to clients. To do this, we must create a JMXServiceURL, which represents the URL that clients will use to access the JMX service:

    JMXServiceURL url = new JMXServiceURL( "rmi", null, Constants.MBEAN_SERVER_PORT, "/jndi/rmi://localhost:" + Constants.RMI_REGISTRY_PORT + "/jmxapp"); 

    Let's look closely at the above line of code. TheJMXServiceURL constructor takes four arguments:

    1. The protocol to be used when connecting (rmi,jmxmp, iiop, etc.).
    2. The host machine of the JMX service. Supplyinglocalhost as argument would also suffice however, supplying null forces the JMXServiceURLto find the best possible name for the host. For example, in this case, it would translate null to zarar, which is the name of my computer.
    3. The port used by the JMX service.
    4. Finally, we must supply the URL path that indicates how to find the JMX service. In this case, it would be/jndi/rmi://localhost:1099/jmxapp.

    The URL path warrants more explanation:


    The /jndi part is saying that the client must do a JNDI lookup for the JMX service. Thermi://localhost:1099 indicates that there is an RMI registry running on localhost at port 1099 (more on the RMI registry later). The jmxapp is the unique identifier of this JMX service in the RMI registry. AtoString() on the JMXServiceURL object yields the following:


    The above is the URL clients will eventually use to connect to the JMX service. The J2SE 5.0 documentation has more on the structure of this URL.

    Securing the Service

    J2SE 5.0 provides a mechanism for JMX to authenticate users in an easy manner. I have created a simple text file that stores username and password information. The contents of the file are:

    zarar siddiqi fyodor dostoevsky 

    The users zarar and fyodor are authenticated by the passwords siddiqi anddostoevsky, respectively. The next step is to create and secure a JMXConnectorServer that exposes theMBeanServer. The path of the username/password file is stored in a Map under the key,jmx.remote.x.password.file. This Map is later used when creating the JMXConnectorServer.

    ServletContext context = Config.getServletContext(); // Get file which stores jmx user information String userFile = context.getRealPath("/") + "/WEB-INF/classes/" + Constants.JMX_USERS_FILE; // Create authenticator and initialize RMI server Map<string> env = new HashMap<string>(); env.put("jmx.remote.x.password.file", userFile); 

    Now let's create the JMXConnectorServer. The following line of code does the trick.

    connectorServer = JMXConnectorServerFactory. newJMXConnectorServer(url, env, server); 

    TheJMXConnectorServerFactory.newJMXConnectorServer(JMXServiceURL, Map, MBeanServer) method takes in as arguments three objects we have just created: the JMXServiceURL, theMap that stores authentication information, and theMBeanServer. The connectorServer instance variable allows us to start() and stop()the JMXConnectorServer on application start and stop, respectively.

    Although the J2SE 5.0 implementation of JSR 160 is quite powerful, other implementations, such as MX4J, provide classes that offer convenient features such as obfuscating of passwords, namely the PasswordAuthenticatorclass.

    Starting the RMI Registry

    Earlier, I alluded to a RMI registry and said that a JNDI lookup is done when accessing the service. However, right now we don't have a RMI registry running, so a JNDI lookup will fail. A RMI registry started may be started manually or programmatically.

    Using the Command Line

    On your Windows or Linux command line, type the following to start a RMI registry:

    rmiregistry & 

    This will start the RMI registry at your default host and port,localhost and 1109, respectively. However, for our web application we cannot rely on an RMI registry being available on application start and would rather take care of this in our code.

    Programmatically Starting the RMI Registry

    To programmatically start the RMI Registry, you can use theLocateRegistry.createRegistry(int port) method. The method returns an object of the type Registry. We store this reference, as we would like to stop the registry on application end. Right before we start ourJMXConnectorServer in JMXAgent.start(), we first start an RMI registry using the following line of code:

     registry = LocateRegistry.createRegistry( Constants.RMI_REGISTRY_PORT);

    On application end, after stopping theJMXConnectorServer in JMXAgent.stop(), the following method is called to stop the registry:

     UnicastRemoteObject.unexportObject(registry, true);

    Note that the StartupListener class triggers application start and end tasks.

    Accessing our JMX Service

    There are a number of ways we can access JSR 160 services. We may do so programmatically or by using a GUI.

    Connecting using MC4J

    Deploy the application by copying jmxapp.war to Tomcat'swebapps directory. Download and install MC4J. Once installed, create a new Server Connection of the typeJSR 160 and specify the Server URL that was printed in the application server logs on application startup. In my case, it was:


    Supply the username and password, which MC4J refers to as "Principle" and "Credentials," respectively. Clicking Next takes you to a screen where you can customize your classpath. Default settings should work fine, and you can click on Finish to connect to the JMX service. Once connected, browse the MC4J tree structure as shown in Figure 1 until you reach the Properties option of theLoginStats MBean implementation.

    MC4J View
    Figure 1. MC4J view

    Clicking on the Properties option displays the statistics, as shown in Figure 2:

    Properties Window
    Figure 2. Properties window

    Connecting to a "Cluster" using jManage

    Deploy the application by copying jmxapp.war to Tomcat'swebapps directory. Note the URL that is printed on application startup. Next, deploy another instance of this application by changing the RMI_REGISTRY_PORT andMBEAN_SERVER_PORT variables in theConstants class so that the second instance of the application will not try to use ports that are already in use. Change the property in the build.xmlfile so that the new instance will be deployed in a different context (e.g., jmxapp2). Do an ant clean war, which will create jmxapp2.war in the base directory. Copy jmxapp2.war to Tomcat's webappsdirectory. The application will deploy and now you have two instances of the same application running. Again, note the URL that is printed on startup.

    Download and install jManage. Once installed, use jManage's web interface to create a JSR 160 application by following the Add New Application link found on the home page. The Add Application page is shown in Figure 3:

    Add Application page
    Figure 3. Add Application page

    Once again, use the appropriate username, password, and URL. Repeat the steps for the second application that is deployed. Once you have created the two applications, you must create a cluster by following the Add New Application Cluster link found on the home page. Simply add the two applications you have already created to your cluster, as shown in Figure 4:

    Add Application Cluster page
    Figure 4. Add Application Cluster page

    That's it, we are done! From the home page, click on one of the applications in the cluster and then click on the Find More Objects button. You will see the name=LoginStats MBean; click on it, and you will see the FailedLogins andSuccessLogins attributes that we have exposed. Clicking on the Cluster View link on the same page will display a page similar to Figure 5, where a running count of statistics from both applications can be seen:

    Cluster view for jmxapp and jmxapp2
    Figure 5. Cluster view for jmxapp andjmxapp2

    Try a few logins on both applications (http://localhost:8080/jmxapp andhttp://localhost:8080/jmxapp2) and see how the numbers change.


    You now know how to "JMX enable" your new and existing web applications and securely manage them using MC4J and jManage. Although J2SE 5.0 provides a powerful implementation of the JMX specification, other open source projects such as XMOJO andMX4J provide additional features, such as connecting via web interfaces and more. Interested readers who want to learn more about JMX will find Java Management Extensions by J. Steven Perry a very useful book. For those interested in remote application management, Connecting JMX Clients and Serversby Jeff Hanson is a valuable resource that provides real-world examples.