How to capture WildFly lifecycle notifications

This tutorial describes how to capture lifecycle events notifications in WildFly 11 and above.

Since WildFly 11 it’s much easier to capture JMX notifications of the server lifecycle events. Each time that an event like start/stop/resume etc. happens, the application server will send an AttributeChangeNotifications on the object named “jboss.root:type=state“. If you have subscribed to this notification you will be able to capture this event.

There are broadly two kinds of notifications that can be captured:

  • Notifications about the state of the model configuration, compared to the running configuration. For example if a restart/reload is required after a model change.
  • Notifications about the running state of a server. For example, if the server was stopped, started, suspended etc.

Let’s see in practice how this can be done:

package com.example;

import java.io.BufferedWriter;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;

import javax.management.AttributeChangeNotification;
import javax.management.Notification;
import javax.management.NotificationListener;

public class WildFlyListener implements NotificationListener {

    public static final String RUNTIME_CONFIGURATION_FILENAME = "runtime-configuration-notifications.txt";
    public static final String RUNNING_FILENAME = "notifications.txt";
    private final Path targetFile;

    public WildFlyListener() {
        this.targetFile = Paths.get(".").toAbsolutePath();
        init(targetFile);
    }

    protected Path getRuntimeConfigurationTargetFile() {
        return this.targetFile.resolve(RUNTIME_CONFIGURATION_FILENAME);
    }

    protected Path getRunningConfigurationTargetFile() {
        return this.targetFile.resolve(RUNNING_FILENAME);
    }

    protected final void init(Path targetFile) {
        try {
            Files.createDirectories(targetFile);

            if (!Files.exists(targetFile.resolve(RUNTIME_CONFIGURATION_FILENAME))) {
                Files.createFile(targetFile.resolve(RUNTIME_CONFIGURATION_FILENAME));
            }

            if (!Files.exists(targetFile.resolve(RUNNING_FILENAME))) {
                Files.createFile(targetFile.resolve(RUNNING_FILENAME));
            }
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }

    @Override
    public void handleNotification(Notification notification, Object handback) {
        AttributeChangeNotification attributeChangeNotification = (AttributeChangeNotification) notification;
        if ("RuntimeConfigurationState".equals(attributeChangeNotification.getAttributeName())) {
            writeNotification(attributeChangeNotification, getRuntimeConfigurationTargetFile());
        } else {
            writeNotification(attributeChangeNotification, getRunningConfigurationTargetFile());
        }
    }

    private void writeNotification(AttributeChangeNotification notification, Path path) {
        try (BufferedWriter in = Files.newBufferedWriter(path, StandardCharsets.UTF_8, StandardOpenOption.APPEND)) {
            in.write(String.format("%s %s %s %s", notification.getType(), notification.getSequenceNumber(),
                    notification.getSource().toString(), notification.getMessage()));
            in.newLine();
            in.flush();
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }
}

In order to install the listener in a managed environment, we can use any bootstrap module like a Singleton EJB with @Startup annotation:

package com.example;

import java.lang.management.ManagementFactory;

import javax.annotation.PostConstruct;
import javax.ejb.Singleton;
import javax.ejb.Startup;
import javax.management.InstanceNotFoundException;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;

@Startup
@Singleton
public class SingletonEJB {

    @PostConstruct
    public void init() {
        MBeanServer server = ManagementFactory.getPlatformMBeanServer();
        try {
            server.addNotificationListener(ObjectName.getInstance("jboss.root:type=state"), new WildFlyListener(), null,
                    null);
        } catch (InstanceNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (MalformedObjectNameException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (NullPointerException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

}

That’s all. Deploy the application and try to change the server status, for example:

~/jboss/wildfly-15.0.0.Final/bin:$ ./jboss-cli.sh -c

[standalone@localhost:9990 /] :suspend
{"outcome" => "success"}

You will see that all the Server status changes are logged in the RUNNING_FILENAME, that is named “notifications.txt” and by default it is configured to log into the current folder (“.”) where WildFly has started.

~/jboss/wildfly-15.0.0.Final:$ tail -f ./bin/running-notifications.txt 
jmx.attribute.change 5 jboss.root:type=state The attribute 'RunningState' has changed from 'normal' to 'suspending'
jmx.attribute.change 5 jboss.root:type=state The attribute 'RunningState' has changed from 'normal' to 'suspending'
jmx.attribute.change 6 jboss.root:type=state The attribute 'RunningState' has changed from 'suspending' to 'suspended'
jmx.attribute.change 6 jboss.root:type=state The attribute 'RunningState' has changed from 'suspending' to 'suspended'
jmx.attribute.change 7 jboss.root:type=state The attribute 'RunningState' has changed from 'suspended' to 'normal'
jmx.attribute.change 7 jboss.root:type=state The attribute 'RunningState' has changed from 'suspended' to 'normal'

Manage WildFly JMX notifications with JConsole

Obviously, you can use any tool that is able to Subscribe to JMX Notifications, like JConsole for example. Just pin into WildFly process and reach out the object named “jboss.root:type=state”.

You can see that if you Subscribe to the Object’s notifications, they will pop-up under the Notifications tree, like depicted in this picture:

WildFly lifecycle notifications

Found the article helpful? if so please follow us on Socials