Services

HiveMQ Registries provide a convenient way for extensions to register extension callbacks with the HiveMQ core.

They can be accessed through the Services class.

The following table lists all available HiveMQ Registries.

Table 1. Available HiveMQ Services
Registry Description

Initializer Registry

Allows extensions to set a client initializer for every mqtt client.

Cluster Service

Enables extensions to dynamically discover HiveMQ cluster nodes.

Security Registry

Allows extensions to define the authentication and authorization of MQTT clients.

Event Registry

Allows extensions to set a client lifecycle event listener for MQTT clients.

Metric Registry

Enables the extensions to access the metrics of HiveMQ and add custom metrics.



Initializer Registry

The initializer registry allows every extension to set a ClientInitializer.


Client Initializer

With the client initializer set up in the extension start method, it is possible to add:

  • Interceptors

  • Client lifecycle listeners

  • Default topic permissions

To use a client initializer, it must be implemented by the extension developer. See example below.

Multiple extensions can set a client initializer. Those initializers are called sequentially, starting with the client initializer of the extension with the highest priority. Extensions with the same priority have an undefined order of calling initialize.

Every client calls the same initialize method once.

The context of the client initializer will be added to every connected and connecting mqtt client after extension start.

When the extension stops, the context of its client initializer will be removed from every client automatically.

Example Usage

This section illustrates examples on how to set a ClientInitializer via the InitializerRegistry.


Accessing Initializer Registry

This example shows how to access InitializerRegistry via the Services class.

final InitializerRegistry initializerRegistry = Services.initializerRegistry();


Add Interceptors And Set Initializer

This example shows how to create a ClientInitializer, add an Interceptor and set the ClientInitializer to the registry.

ClientInitializer initializer = new ClientInitializer() {
    @Override
    public void initialize(InitializerInput initializerInput, ClientContext clientContext) {
        clientContext.addPublishInboundInterceptor(interceptor);
    }
}

Services.initializerRegistry().setClientInitializer(initializer);
Full Example Code
...

@Override
public void extensionStart(final ExtensionStartInput extensionStartInput, final ExtensionStartOutput extensionStartOutput) {

    //create shareable interceptor
    final PublishInboundInterceptor shareAbleInterceptor = new PublishInboundInterceptor() {
        @Override
        public void onInboundPublish(PublishInboundInput publishInboundInput, PublishInboundOutput publishInboundOutput) {
            System.out.println("Publish incoming for topic: " + publishInboundInput.getPublishPacket().getTopic());
        }
    };

    //create client initializer
    final ClientInitializer initializer = new ClientInitializer() {

        @Override
        public void initialize(InitializerInput initializerInput, ClientContext clientContext) {

            //add shareable interceptor
            clientContext.addPublishInboundInterceptor(shareAbleInterceptor);

            //add backend client only interceptor
            if(initializerInput.getClientInformation().getClientId().equals("backend")) {
                clientContext.addSubscribeInboundInterceptor(
                        (subscribeInboundInput, subscribeInboundOutput) -> System.out.println("Inbound backend subscribe")
                );
            }
        }

    };

    //get registry from Services
    final InitializerRegistry initializerRegistry = Services.initializerRegistry();

    //set client initializer
    initializerRegistry.setClientInitializer(initializer);

}

...



Cluster Service

The Cluster Service enables extensions to dynamically discover HiveMQ cluster nodes.

The ClusterService object can be accessed by extensions through Services.clusterService().

Example usage of the ClusterService
public class MyExtensionMain implements ExtensionMain {

    private final MyClusterDiscoveryCallback myCallback;

    public MyExtensionMain() {
        myCallback = new MyClusterDiscoveryCallback();
    }

    @Override
    public void extensionStart(
            final @NotNull ExtensionStartInput input, final @NotNull ExtensionStartOutput output) {

        Services.clusterService().addDiscoveryCallback(myCallback);
    }

    @Override
    public void extensionStop(
            final @NotNull ExtensionStopInput input, final @NotNull ExtensionStopOutput output) {

        Services.clusterService().removeDiscoveryCallback(myCallback);
    }
}

Cluster Discovery

A extension can realize discovery of HiveMQ cluster nodes by implementing a ClusterDiscoveryCallback and adding it via the ClusterService.

Extension discovery is only used by HiveMQ if <discovery> is set to <extension> in the <cluster> section of the HiveMQ config file.
HiveMQ config for extension cluster discovery
<?xml version="1.0"?>
<hivemq xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    ...
    <cluster>
        ...
        <discovery>
            <extension></extension>
        </discovery>
        ...
    </cluster>
    ...
</hivemq>

The lifecycle of a ClusterDiscoveryCallback consists of 3 methods:

  • init: called once by HiveMQ when the callback is added. It can be used to register the HiveMQ instance (where the extension is running on) with some kind of central registry or to save it to a database or file server.

  • reload: called regularly by HiveMQ to discover all currently available HiveMQ cluster nodes. The interval between calls to this method is 60 seconds by default and can be overwritten by an individual ClusterDiscoveryCallback.

  • destroy: called once by HiveMQ in one of the following cases:

    • the callback is removed

    • the extension which added the callback is stopped

    • the HiveMQ instance is shut down.

    It can be used to unregister the HiveMQ instance (where the extension is running on) from a central registry.

If an exception is thrown inside one of these methods, the provided output is ignored.

Example implementation of a ClusterDiscoveryCallback
public class MyClusterDiscoveryCallback implements ClusterDiscoveryCallback {

    private final MyClusterNodesService myService = ...

    @Override
    public void init(
            final @NotNull ClusterDiscoveryInput clusterDiscoveryInput,
            final @NotNull ClusterDiscoveryOutput clusterDiscoveryOutput) {

        myService.registerHiveMQNode(clusterDiscoveryInput.getOwnAddress());
        final List<ClusterNodeAddress> hiveMQNodes = myService.getHiveMQNodes();
        clusterDiscoveryOutput.provideCurrentNodes(hiveMQNodes);
        final int nextReloadInterval = myService.getNextReloadInterval();
        clusterDiscoveryOutput.setReloadInterval(nextReloadInterval);
    }

    @Override
    public void reload(
            final @NotNull ClusterDiscoveryInput clusterDiscoveryInput,
            final @NotNull ClusterDiscoveryOutput clusterDiscoveryOutput) {

        final List<ClusterNodeAddress> hiveMQNodes = myService.getHiveMQNodes();
        clusterDiscoveryOutput.provideCurrentNodes(hiveMQNodes);
        final int nextReloadInterval = myService.getNextReloadInterval();
        clusterDiscoveryOutput.setReloadInterval(nextReloadInterval);
    }

    @Override
    public void destroy(final @NotNull ClusterDiscoveryInput clusterDiscoveryInput) {
        myService.unregisterHiveMQNode(clusterDiscoveryInput.getOwnAddress());
    }
}



Security Registry

The Security Registry allows extensions to define the authentication and authorization of MQTT clients.

The SecurityRegistry object can be accessed by extensions through Services.securityRegistry().

Its functionality is the setting of AuthenticatorProvider and AuthorizerProvider on a one of each per extension basis.

The Providers will be removed at extension stop.


Authenticator Provider

The AuthenticatorProvider provides Authenticators that are used for authenticating connecting MQTT clients. An AuthenticatorProvider is given a client specific AuthenticatorProviderInput object and should return an Authenticator. The AuthenticatorProviderInput contains information about the MQTT client, its connection and the state of the broker. The returned authenticator is valid for the duration of the client’s connection.

An AuthenticatorProvider must return an implementation of a SimpleAuthenticator or null. See example.

Authorizer Provider

The AuthorizerProvider provides Authorizers that are used for authorizing MQTT PUBLISH and SUBSCRIBE packets. An AuthenticatorProvider is given a client specific AuthenticatorProviderInput object and should return an Authorizer. The AuthorizerProviderInput contains information about the MQTT client, its connection and the state of the broker. The returned authorizer is valid for the duration of the client’s connection.

An AuthorizerProvider must return an implementation of a PublishAuthorizer, a SubscriptionAuthorizer or null. See example.


Authenticator

There is one sub-interface of Authenticator available for the development of HiveMQ extensions, the SimpleAuthenticator.


Simple Authenticator

The SimpleAuthenticator interface is used to perform authentication at the moment of receiving an MQTT CONNECT message from a newly connecting client. For this task a SimpleAuthInput and a SimpleAuthOutput are provided by HiveMQ. Extension input and extension output principles apply. The SimpleAuthOutput itself is blocking but can easily be used to create an asynchronous SimpleAuthOutput object.

The SimpleAuthenticatorOutput provides three important methods:

  • authenticationSuccessful() finishes the authentication process for the client and allows it to connect to HiveMQ. Authenticators of extensions with a lower priority will not be called.

  • failAuthentication() finishes the authentication process for the client and disconnects the client. Authenticators of extensions with a lower priority will not be called.

  • nextExtensionOrDefault() does not decide the authentication state of the client. Authenticators of extensions with a lower priority are called or default behaviour is applied.

The default behavior is disconnecting the client with reason code "NOT_AUTHORIZED" and reason string: "authentication failed by extension". This behaviour also occurs, when none of the methods above are called.

The difference between MQTT 3 and MQTT 5 is, that an MQTT 3 client does not receive a reason string and is capable of receiving significantly less CONNACK reason codes than an MQTT 5 client after failed authentication.

Table 2. Connack Reason Codes
Mqtt5 Reason Code Mqtt3 Reason Code

NOT_AUTHORIZED

REFUSED_NOT_AUTHORIZED

BAD_USER_NAME_OR_PASSWORD

REFUSED_BAD_USERNAME_OR_PASSWORD

UNSUPPORTED_PROTOCOL_VERSION

REFUSED_UNACCEPTABLE_PROTOCOL_VERSION

CLIENT_IDENTIFIER_NOT_VALID

REFUSED_IDENTIFIER_REJECTED

SERVER_UNAVAILABLE

REFUSED_SERVER_UNAVAILABLE

UNSPECIFIED_ERROR

-

MALFORMED_PACKET

-

PROTOCOL_ERROR

-

IMPLEMENTATION_SPECIFIC_ERROR

-

SERVER_BUSY

-

BANNED

-

BAD_AUTHENTICATION_METHOD

-

TOPIC_NAME_INVALID

-

PACKET_TOO_LARGE

-

QUOTA_EXCEEDED

-

PAYLOAD_FORMAT_INVALID

-

RETAIN_NOT_SUPPORTED

-

QOS_NOT_SUPPORTED

-

USE_ANOTHER_SERVER

-

SERVER_MOVED

-

CONNECTION_RATE_EXCEEDED

-

In the output of the Authenticator it is possible to set the Default Permissions for the client.

Example Usage


Accessing Security Registry

final SecurityRegistry securityRegistry = Services.securityRegistry();


Implement Authenticator Provider

This example shows how to implement an AuthenticatorProvider.

You only need to return an Authenticator in the getAuthenticator method.

public class MyAuthenticatorProvider implements AuthenticatorProvider {

    final @NotNull Map<String, String> usernamePasswordMap;

    public MyAuthenticatorProvider(final @NotNull Map<String, String> usernamePasswordMap) {
        this.usernamePasswordMap = usernamePasswordMap;
    }

    @Nullable
    @Override
    public Authenticator getAuthenticator(final @NotNull AuthenticatorProviderInput authenticatorProviderInput) {

        //A new instance must be returned, because the authenticator has state.
        return new MySimpleAuthenticator(usernamePasswordMap);

    }
}


Implement Authenticator Provider With Shareable Authenticator

This example shows how to implement an AuthenticatorProvider with a shareable authenticator.

This authenticator must either be stateless or thread-safe.

You only need to return an Authenticator in the getAuthenticator method.

public class MyAuthenticatorProvider implements AuthenticatorProvider {

    final @NotNull SimpleAuthenticator authenticator;

    public MyAuthenticatorProvider(final @NotNull SimpleAuthenticator authenticator) {
        this.authenticator = authenticator;
    }

    @Nullable
    @Override
    public Authenticator getAuthenticator(final @NotNull AuthenticatorProviderInput authenticatorProviderInput) {

        //return a shareable authenticator which must be thread-safe / stateless
        return authenticator;

    }
}


Implement Authorizer Provider

This example shows how to implement an AuthorizerProvider.

You need to return an SubscriptionAuthorizer or PublishAuthorizer in the getAuthorizer() method.

It is possible to implement both a SubscriptionAuthorizer and a PublishAuthorizer in the same class by implementing both interfaces.

public class MyAuthorizerProvider implements AuthorizerProvider {

        //create a shared instance of SubcsriptionAuthorizer
        private final MySubscriptionAuthorizer subscriptionAuthorizer = new MySubscriptionAuthorizer();

        @Override
        public @Nullable Authorizer getAuthorizer(@NotNull final AuthorizerProviderInput authorizerProviderInput) {
            //always return the shared instance.
            //It is also possible to create a new instance here for each client
            return subscriptionAuthorizer;
        }

    }


Implement Simple Authenticator

This example shows an implementation of a SimpleAuthenticator with username and password validation.

public class MySimpleAuthenticator implements SimpleAuthenticator {

    final @NotNull Map<String, String> usernamePasswordMap;

    private MySimpleAuthenticator(final @NotNull Map<String, String> usernamePasswordMap) {
        this.usernamePasswordMap = usernamePasswordMap;
    }

    @Override
    public void onConnect(final @NotNull SimpleAuthInput input, final @NotNull SimpleAuthOutput output) {

        //get connect packet from input object
        final ConnectPacket connectPacket = input.getConnectPacket();

        //validate the username and password combination
        if (validate(connectPacket.getUserName(), connectPacket.getPassword())) {

            //successful authentication if username and password are correct
            output.authenticateSuccessfully();

        } else {

            //failed authenticattion if username or password are incorrect
            output.failAuthentication(BAD_USER_NAME_OR_PASSWORD, "wrong password");

        }
    }


    private boolean validate(final @NotNull Optional<String> usernameOptional, final @NotNull Optional<ByteBuffer> passwordOptional) {

        //if no username or password is set, valdation fails
        if(!usernameOptional.isPresent() || !passwordOptional.isPresent()){
            return false;
        }

        final String username = usernameOptional.get();
        final byte[] bytes = getBytesFromBuffer(passwordOptional.get());
        final String password = new String(bytes, StandardCharsets.UTF_8);

        //get password for username from map.
        final String passwordFromMap = usernamePasswordMap.get(username);

        //return true if passwords are equal, else false
        return password.equals(passwordFromMap);

    }

    private byte[] getBytesFromBuffer(final ByteBuffer byteBuffer) {
        final byte[] bytes = new byte[byteBuffer.remaining()];
        byteBuffer.get(bytes);
        return bytes;
    }

}


Implement Async Simple Authenticator

This example shows an async implementation of a SimpleAuthenticator with external validation.

public class MyAsyncSimpleAuthenticator implements SimpleAuthenticator {

    @Override
    public void onConnect(final @NotNull SimpleAuthInput input, final @NotNull SimpleAuthOutput output) {


        //access managed extension executor service
        final ManagedExtensionExecutorService extensionExecutorService = Services.extensionExecutorService();

        //make output async with timeout of 10 seconds and when operation timed out, auth will fail
        final Async<SimpleAuthOutput> asyncOutput = output.async(Duration.of(10, SECONDS), TimeoutFallback.FAILURE);

        //submit external task to managed extension executor service
        extensionExecutorService.submit(new Runnable() {
            @Override
            public void run() {

                //validate client via external service call. This method is not provided by the HiveMQ extension system
                final boolean successful = externalServiceCallForValidation();

                if(successful){
                    asyncOutput.getOutput().authenticateSuccessfully();
                } else {
                    asyncOutput.getOutput().failAuthentication();
                }

                //resume output to tell HiveMQ auth is complete
                asyncOutput.resume();
            }
        });

    }

}


Set Authenticator Provider

This example shows how to set an AuthenticatorProvider to the security registry.

public class AuthExtensionMain implements ExtensionMain{

    @Override
    public void extensionStart(final @NotNull ExtensionStartInput extensionStartInput, final @NotNull ExtensionStartOutput extensionStartOutput) {

        //access security registry
        final SecurityRegistry securityRegistry = Services.securityRegistry();

        //create username_password map
        final Map<String, String> usernamePasswordMap = new HashMap<>();
        usernamePasswordMap.put("test_name_1", "password_1");
        usernamePasswordMap.put("test_name_2", "password_2");
        usernamePasswordMap.put("test_name_3", "password_3");

        //create provider
        final MyAuthenticatorProvider provider = new MyAuthenticatorProvider(usernamePasswordMap);

        //set provider
        securityRegistry.setAuthenticatorProvider(provider);

    }

    @Override
    public void extensionStop(final @NotNull ExtensionStopInput extensionStopInput, final @NotNull ExtensionStopOutput extensionStopOutput) {

    }
}

Set Authorizer Provider

This example shows how to set an AuthorizerProvider to the security registry.

public class AuthorizerExtensionMain implements ExtensionMain{

    @Override
    public void extensionStart(final @NotNull ExtensionStartInput extensionStartInput, final @NotNull ExtensionStartOutput extensionStartOutput) {

        //access security registry
        final SecurityRegistry securityRegistry = Services.securityRegistry();

        //create provider
        final MyAuthorizerProvider provider = new MyAuthorizerProvider();

        //set provider
        securityRegistry.setAuthorizerProvider(provider);
    }

    @Override
    public void extensionStop(final @NotNull ExtensionStopInput extensionStopInput, final @NotNull ExtensionStopOutput extensionStopOutput) {

    }
}



Event Registry

The event registry allows every extension to register a ClientLifecycleEventListenerProvider, which must return an implementation of a ClientLifecycleEventListener.

The ClientLifecycleEventListenerProvider.getClientLifecycleEventListener() method is called once for every client, as soon as the first event is fired.

The EventRegistry can be accessed by the Services class.

Client Lifecycle Event Listener

With the ClientLifecycleEventListener set up in a extension, it is possible to listen to these client lifecycle events:

Table 3. Available ClientLifecycleEventListener Methods
Method Input Description

onMqttConnectionStart

ConnectionStartInput

This event is fired as soon as HiveMQ receives a valid MQTT connect packet.

onAuthenticationSuccessful

AuthenticationSuccessfulInput

This event is fired when a client’s authentication succeeds.

onAuthenticationFailedDisconnect

AuthenticationFailedInput

This event is fired when a client’s authentication failed.

onClientInitiatedDisconnect

ClientInitiatedDisconnectInput

This event is fired when a client disconnects gracefully (sends a disconnect packet).

onConnectionLost

ConnectionLostInput

This event is fired when a client disconnects ungracefully (closed connection without disconnect packet sent).

onServerInitiatedDisconnect

ServerInitiatedDisconnectInput

This event is fired when HiveMQ disconnects the client.

onDisconnect

DisconnectEventInput

This event is fired when any kind of disconnect occurs.

To use a ClientLifecycleEventListener, it must be implemented by the extension developer. See example below.

Multiple extensions can set individual ClientLifecycleEventListener. These listeners are called sequentially, starting with the listener of the extension with the highest priority. Extensions with the same priority have an undefined order of calling a listener.

As soon as a extension is started, every client uses the listener to fire its lifecycle events.

When a extension stops, the listener will be removed from every client automatically.


Client Lifecycle Events

Client lifecycle event methods are called by HiveMQ when a client connects, authenticates or disconnects.

The input parameters of these methods are all immutable and for information purpose only.


onMqttConnectionStart

As soon as a connect packet is decoded and validated by HiveMQ the ClientLifecycleEventListener.onMqttConnectionStart method is called with a ConnectionStartInput.

This method must be overridden in the implementation of the ClientLifecycleEventListener.

The input contains the following information:

  • MQTT CONNECT Packet

  • MQTT Version

  • Client ID

  • IP Address

  • Listener with port, bind address and type

  • ConnectionAttributeStore

  • TLS


onAuthenticationSuccessful

When a client’s authentication succeeds the ClientLifecycleEventListener.onAuthenticationSuccessful method is called with an AuthenticationSuccessfulInput.

This method must be overridden in the implementation of the ClientLifecycleEventListener.

The input contains the following information:

  • MQTT Version

  • Client ID

  • IP Address

  • Listener with port, bind address and type

  • ConnectionAttributeStore

  • TLS


onAuthenticationFailedDisconnect

When a client’s authentication fails the ClientLifecycleEventListener.onAuthenticationFailedDisconnect method is called with a AuthenticationFailedInput.

If this method was called no other disconnect method will be called.

When this method is not overridden in the ClientLifecycleEventListener, onDisconnect is called.

The input contains the following information:

  • Optional disconnect reason code

  • Optional reason string

  • Optional user properties

  • MQTT version

  • Client ID

  • IP address

  • Listener with port, bind address and type

  • ConnectionAttributeStore

  • TLS


onClientInitiatedDisconnect

When a client sends a disconnect packet (disconnects gracefully) the ClientLifecycleEventListener.onClientInitiatedDisconnect method is called with a ClientInitiatedDisconnectInput.

If this method was called no other disconnect method will be called.

When this method is not overridden in the ClientLifecycleEventListener, onDisconnect is called.

The input contains the following information:

  • Optional disconnect reason code

  • Optional reason string

  • Optional user properties

  • MQTT version

  • Client ID

  • IP address

  • Listener with port, bind address and type

  • ConnectionAttributeStore

  • TLS


onConnectionLost

When a client disconnects without sending any disconnect packet (disconnects ungracefully) the ClientLifecycleEventListener.onConnectionLost method is called with a ConnectionLostInput.

If this method was called no other disconnect method will be called.

When this method is not overridden in the ClientLifecycleEventListener, onDisconnect is called.

The input contains the following information:

  • Optional disconnect reason code

  • Optional reason string

  • Optional user properties

  • MQTT version

  • Client ID

  • IP address

  • Listener with port, bind address and type

  • ConnectionAttributeStore

  • TLS


onServerInitiatedDisconnect

When HiveMQ disconnects a client the ClientLifecycleEventListener.onServerInitiatedDisconnect method is called with a ServerInitiatedDisconnectInput.

If this method was called no other disconnect method will be called.

When this method is not overridden in the ClientLifecycleEventListener, onDisconnect is called.

The input contains the following information:

  • Optional disconnect reason code

  • Optional reason string

  • Optional user properties

  • MQTT version

  • Client ID

  • IP address

  • Listener with port, bind address and type

  • ConnectionAttributeStore

  • TLS


onDisconnect

For every kind of specific disconnect method which is not overridden in the ClientLifecycleEventListener implementation the ClientLifecycleEventListener.onDisconnect method is called with a DisconnectEventInput.

This method will never be called, when every specific disconnect method is overridden in the ClientLifecycleEventListener.

The specific methods are:

This method must be overridden in the implementation of the ClientLifecycleEventListener.

The input contains the following information:

  • Optional disconnect reason code

  • Optional reason string

  • Optional user properties

  • MQTT version

  • Client ID

  • IP address

  • Listener with port, bind address and type

  • ConnectionAttributeStore

  • TLS


Example Usage

This section illustrates examples on how to set a ClientLifecycleEventListenerProvider via the EventRegistry.


Accessing Event Registry

This example shows how to access EventRegistry via the Services class.

final EventRegistry eventRegistry = Services.eventRegistry();


Implement A Simple ClientLifecycleEventListener

This example shows how to implement a very simple ClientLifecycleEventListener.

Only mandatory methods have been overridden in this example.

//create a class implementing ClientLifecycleEventListener
public class SimpleEventListener implements ClientLifecycleEventListener {

    //override mandatory onMqttConnectionStart method
    @Override
    public void onMqttConnectionStart(final @NotNull ConnectionStartInput connectionStartInput) {
        final MqttVersion version = connectionStartInput.getConnectionInformation().getMqttVersion();
        switch (version){
            case V_5:
                log.info("MQTT 5 client connected with id: {} ", connectionStartInput.getClientInformation().getClientId());
                break;
            case V_3_1_1:
                log.info("MQTT 3.1.1 client connected with id: {} ", connectionStartInput.getClientInformation().getClientId());
                break;
            case V_3_1:
                log.info("MQTT 3.1 client connected with id: {} ", connectionStartInput.getClientInformation().getClientId());
                break;
        }
    }

    //override mandatory onAuthenticationSuccessful method
    @Override
    public void onAuthenticationSuccessful(final @NotNull AuthenticationSuccessfulInput authenticationSuccessfulInput) {
        log.info("Authentication succeeded for client: {}", authenticationSuccessfulInput.getClientInformation().getClientId());
    }

    //override mandatory onDisconnect method
    @Override
    public void onDisconnect(final @NotNull DisconnectEventInput disconnectEventInput) {
        log.info("Client disconnected: {}", disconnectEventInput.getClientInformation().getClientId());
    }
}


Implement A Full ClientLifecycleEventListener

This example shows how to implement a ClientLifecycleEventListener with all methods overridden.

//create a class implementing ClientLifecycleEventListener
public class FullEventListener implements ClientLifecycleEventListener {

    //override mandatory onMqttConnectionStart method
    @Override
    public void onMqttConnectionStart(final @NotNull ConnectionStartInput connectionStartInput) {
        final MqttVersion version = connectionStartInput.getConnectPacket().getMqttVersion();
        switch (version){
            case V_5:
                log.info("MQTT 5 client connected with id: {} ", connectionStartInput.getClientInformation().getClientId());
                break;
            case V_3_1_1:
                log.info("MQTT 3.1.1 client connected with id: {} ", connectionStartInput.getClientInformation().getClientId());
                break;
            case V_3_1:
                log.info("MQTT 3.1 client connected with id: {} ", connectionStartInput.getClientInformation().getClientId());
                break;
        }
    }

    //override mandatory onAuthenticationSuccessful method
    @Override
    public void onAuthenticationSuccessful(final @NotNull AuthenticationSuccessfulInput authenticationSuccessfulInput) {
        log.info("Authentication succeeded for client: {}", authenticationSuccessfulInput.getClientInformation().getClientId());
    }

    //override optional onAuthenticationFailedDisconnect method
    @Override
    public void onAuthenticationFailedDisconnect(final  @NotNull AuthenticationFailedInput authenticationFailedInput) {
        log.info("Authentication failed for client: {}", authenticationFailedInput.getClientInformation().getClientId());
    }

    //override optional onConnectionLost method
    @Override
    public void onConnectionLost(final  @NotNull ConnectionLostInput connectionLostInput) {
        log.info("Ungraceful disconnect from client: {}", connectionLostInput.getClientInformation().getClientId());
    }

    //override optional onClientInitiatedDisconnect method
    @Override
    public void onClientInitiatedDisconnect(final  @NotNull ClientInitiatedDisconnectInput clientInitiatedDisconnectInput) {
        log.info("Graceful disconnect from client: {}", clientInitiatedDisconnectInput.getClientInformation().getClientId());
    }

    //override optional onServerInitiatedDisconnect method
    @Override
    public void onServerInitiatedDisconnect(final  @NotNull ServerInitiatedDisconnectInput serverInitiatedDisconnectInput) {
        log.info("Server disconnected client: {}", serverInitiatedDisconnectInput.getClientInformation().getClientId());
    }

    //override mandatory onDisconnect method
    @Override
    public void onDisconnect(final @NotNull DisconnectEventInput disconnectEventInput) {
        //as every other disconnect method is overridden this one will never be called from HiveMQ
        log.info("Client disconnected: {}", disconnectEventInput.getClientInformation().getClientId());
    }
}


Register A ClientLifecycleEventListenerProvider

This example shows how to implement and register a ClientLifecycleEventListenerProvider.

It uses the two different ClientLifecycleEventListener explained above.

...

@Override
public void extensionStart(final ExtensionStartInput extensionStartInput, final ExtensionStartOutput extensionStartOutput) {

    //create new SimpleEventListener
    final ClientLifecycleEventListener simpleEventListener = new SimpleEventListener();

    //create new FullEventListener
    final ClientLifecycleEventListener fullEventListener = new FullEventListener();

    final ClientLifecycleEventListenerProvider eventListenerProvider = new ClientLifecycleEventListenerProvider() {

        //override mandatory getClientLifecycleEventListener method
        @Override
        public @Nullable ClientLifecycleEventListener getClientLifecycleEventListener(final @NotNull ClientLifecycleEventListenerProviderInput clientLifecycleEventListenerProviderInput) {

            //check client id for returning either simple or full listener
            if (clientLifecycleEventListenerProviderInput.getClientInformation().getClientId().contains("simple")) {
                return simpleEventListener;
            } else {
                return fullEventListener;
            }
        }
    };

    //call Services.eventRegistry() to set the provider.
    Services.eventRegistry().setClientLifecycleEventListener(eventListenerProvider);
}


...



Metric Registry

The MetricRegistry of HiveMQ can be used to gather information about pre-defined HiveMQ metrics or can be used to hook in custom metrics defined by your extension. There’s no artificial wrapper, so you can use the feature-rich Dropwizard Metrics library directly.

With the MetricRegistry it is possible to write extensions that allows the integration of HiveMQ with any monitoring system (that supports Dropwizard Metrics). Extensions for Prometheus, InfluxDB or others can be found in the marketplace.

HiveMQ uses the de-facto standard Java metrics library Dropwizard Metrics, so extension developers can benefit from the whole ecosystem about that library and writing custom integrations is a breeze.


Example Usage

This section illustrates examples on how to add a metric or fetch one with the MetricRegistry.


Accessing Metric Registry

This example shows how to access MetricRegistry via the Services class.

final MetricRegistry metricRegistry = Services.metricRegistry();


Register A Custom Metric

This example shows how to add a custom metric to the metric registry. Among other things custom metrics may be useful to monitor the extension itself. In the example the amount of failed client authentications is monitored.

...
@Override
public void extensionStart(final ExtensionStartInput extensionStartInput, final ExtensionStartOutput extensionStartOutput) {

    final MetricRegistry metricRegistry = Services.metricRegistry();

    // create the new metric, that is increased when a client isn't successfully authenticated
    final Counter failedAuthCounter = metricRegistry.counter("com.example.extension.authentication.failed");

    // log the amount of failed client authentications every 10 seconds
    Services.extensionExecutorService().scheduleAtFixedRate(() -> {
        log.info("Authentication failed {} times", failedAuthCounter.getCount());
    }, 10, 10, TimeUnit.SECONDS);

    // create authenticator where clients that start with id "obsolete" fail to authenticate
    final AuthenticatorProvider authenticatorProvider = new AuthenticatorProvider() {
        @Override
        public Authenticator getAuthenticator(final AuthenticatorProviderInput authenticatorProviderInput) {
            return new SimpleAuthenticator() {
                @Override
                public void onConnect(final SimpleAuthInput simpleAuthInput, final SimpleAuthOutput simpleAuthOutput) {
                    final String clientId = simpleAuthInput.getClientInformation().getClientId();

                    if (clientId.startsWith("obsolete")) {
                        failedAuthCounter.inc();
                        simpleAuthOutput.failAuthentication();
                    } else {
                        simpleAuthOutput.authenticateSuccessfully();
                    }
                }
            };
        }
    };

    Services.securityRegistry().setAuthenticatorProvider(authenticatorProvider);
}
...

Fetching A HiveMQ Metric

This examples shows how to get a HiveMQ metric via the MetricRegistry. A list of all available HiveMQ metrics can be found here.

...
@Override
public void extensionStart(final ExtensionStartInput extensionStartInput, final ExtensionStartOutput extensionStartOutput) {

    final MetricRegistry metricRegistry = Services.metricRegistry();

    final SortedMap<String, Gauge> gauges = metricRegistry.getGauges(
        MetricFilter.contains("com.hivemq.networking.connections.current"));

    //we expect a single result here
    final Gauge connectionsGauge = gauges.values().iterator().next();

    // every 10 seconds log the amount of clients that are connected
    Services.extensionExecutorService().scheduleAtFixedRate(() -> {
        log.info("Currently {} clients are connected", connectionsGauge.getValue());
    }, 10, 10, TimeUnit.SECONDS);
}
...