Documentation Index
Fetch the complete documentation index at: https://mintlify.com/hivemq/hivemq-community-edition/llms.txt
Use this file to discover all available pages before exploring further.
Overview
Packet Interceptors allow you to intercept, inspect, and modify MQTT packets as they flow through HiveMQ. Interceptors enable:
- Packet Inspection - Examine packet contents for logging or monitoring
- Packet Modification - Change packet properties, payloads, or user properties
- Message Enrichment - Add metadata or transform message payloads
- Protocol Translation - Convert between different data formats
- Filtering and Validation - Block or modify invalid packets
Interceptor Types
HiveMQ supports interceptors for all MQTT packet types:
Inbound Interceptors
- CONNECT - Client connection requests
- PUBLISH - Inbound messages from clients
- SUBSCRIBE - Subscription requests
- UNSUBSCRIBE - Unsubscription requests
- DISCONNECT - Client disconnection
- PINGREQ - Ping requests
- AUTH - Authentication packets (MQTT 5.0)
Outbound Interceptors
- CONNACK - Connection acknowledgments
- PUBLISH - Outbound messages to clients
- SUBACK - Subscribe acknowledgments
- UNSUBACK - Unsubscribe acknowledgments
- DISCONNECT - Server-initiated disconnects
- PINGRESP - Ping responses
- PUBACK, PUBREC, PUBREL, PUBCOMP - QoS acknowledgments
See Interceptors.java:27 for interceptor service interface.
Implementing Interceptors
Step 1: Register Interceptor Provider
import com.hivemq.extension.sdk.api.ExtensionMain;
import com.hivemq.extension.sdk.api.annotations.NotNull;
import com.hivemq.extension.sdk.api.parameter.*;
import com.hivemq.extension.sdk.api.services.Services;
import com.hivemq.extension.sdk.api.interceptor.publish.PublishInboundInterceptor;
public class MyInterceptorExtension implements ExtensionMain {
@Override
public void extensionStart(
@NotNull ExtensionStartInput input,
@NotNull ExtensionStartOutput output) {
Services services = input.getServices();
// Register global publish inbound interceptor
services.interceptorRegistry().setPublishInboundInterceptorProvider(
interceptorInput -> new MyPublishInterceptor()
);
// Register global connect interceptor
services.interceptorRegistry().setConnectInboundInterceptorProvider(
interceptorInput -> new MyConnectInterceptor()
);
}
@Override
public void extensionStop(
@NotNull ExtensionStopInput input,
@NotNull ExtensionStopOutput output) {
// Cleanup
}
}
Step 2: Implement Publish Inbound Interceptor
import com.hivemq.extension.sdk.api.interceptor.publish.PublishInboundInterceptor;
import com.hivemq.extension.sdk.api.interceptor.publish.parameter.PublishInboundInput;
import com.hivemq.extension.sdk.api.interceptor.publish.parameter.PublishInboundOutput;
import com.hivemq.extension.sdk.api.annotations.NotNull;
import com.hivemq.extension.sdk.api.packets.publish.ModifiablePublishPacket;
import com.hivemq.extension.sdk.api.packets.publish.PublishPacket;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
public class MyPublishInterceptor implements PublishInboundInterceptor {
@Override
public void onInboundPublish(
@NotNull PublishInboundInput input,
@NotNull PublishInboundOutput output) {
PublishPacket packet = input.getPublishPacket();
String clientId = input.getClientInformation().getClientId();
System.out.println("Client " + clientId + " published to " + packet.getTopic());
// Get modifiable packet
ModifiablePublishPacket modifiable = output.getPublishPacket();
// Add user property with client ID
modifiable.getUserProperties().addUserProperty("client-id", clientId);
// Add timestamp
modifiable.getUserProperties().addUserProperty(
"timestamp",
String.valueOf(System.currentTimeMillis())
);
}
}
See interceptor implementations in src/main/java/com/hivemq/extensions/interceptor/publish/.
Publish Interceptors
Inbound Publish Interceptor
Intercept messages from clients before routing:
import com.hivemq.extension.sdk.api.packets.publish.PayloadFormatIndicator;
import com.hivemq.extension.sdk.api.packets.general.Qos;
public class PublishInboundInterceptor implements PublishInboundInterceptor {
@Override
public void onInboundPublish(
@NotNull PublishInboundInput input,
@NotNull PublishInboundOutput output) {
PublishPacket packet = input.getPublishPacket();
ModifiablePublishPacket modifiable = output.getPublishPacket();
// Read packet properties
String topic = packet.getTopic();
Qos qos = packet.getQos();
boolean retain = packet.getRetain();
ByteBuffer payload = packet.getPayload().orElse(null);
// Modify topic
if (topic.startsWith("v1/")) {
modifiable.setTopic("legacy/" + topic);
}
// Transform payload
if (payload != null) {
String payloadStr = StandardCharsets.UTF_8.decode(payload).toString();
String transformed = payloadStr.toUpperCase();
modifiable.setPayload(
ByteBuffer.wrap(transformed.getBytes(StandardCharsets.UTF_8))
);
}
// Set payload format indicator
modifiable.setPayloadFormatIndicator(
PayloadFormatIndicator.UTF_8
);
// Modify QoS
if (qos == Qos.AT_MOST_ONCE) {
modifiable.setQos(Qos.AT_LEAST_ONCE); // Upgrade QoS
}
}
}
Outbound Publish Interceptor
Intercept messages before delivery to clients:
import com.hivemq.extension.sdk.api.interceptor.publish.PublishOutboundInterceptor;
import com.hivemq.extension.sdk.api.interceptor.publish.parameter.PublishOutboundInput;
import com.hivemq.extension.sdk.api.interceptor.publish.parameter.PublishOutboundOutput;
public class PublishOutboundInterceptor implements PublishOutboundInterceptor {
@Override
public void onOutboundPublish(
@NotNull PublishOutboundInput input,
@NotNull PublishOutboundOutput output) {
String clientId = input.getClientInformation().getClientId();
ModifiablePublishPacket packet = output.getPublishPacket();
// Filter sensitive data for non-admin clients
if (!clientId.startsWith("admin-")) {
ByteBuffer payload = packet.getPayload().orElse(null);
if (payload != null) {
String data = StandardCharsets.UTF_8.decode(payload).toString();
String filtered = data.replaceAll("password=\\w+", "password=***");
packet.setPayload(
ByteBuffer.wrap(filtered.getBytes(StandardCharsets.UTF_8))
);
}
}
// Add delivery metadata
packet.getUserProperties().addUserProperty(
"delivered-to",
clientId
);
}
}
Connect Interceptors
Inbound Connect Interceptor
Intercept client connection requests:
import com.hivemq.extension.sdk.api.interceptor.connect.ConnectInboundInterceptor;
import com.hivemq.extension.sdk.api.interceptor.connect.parameter.ConnectInboundInput;
import com.hivemq.extension.sdk.api.interceptor.connect.parameter.ConnectInboundOutput;
import com.hivemq.extension.sdk.api.packets.connect.ModifiableConnectPacket;
public class ConnectInboundInterceptor implements ConnectInboundInterceptor {
@Override
public void onInboundConnect(
@NotNull ConnectInboundInput input,
@NotNull ConnectInboundOutput output) {
ModifiableConnectPacket connect = output.getConnectPacket();
// Enforce minimum keep alive
if (connect.getKeepAlive() < 30) {
connect.setKeepAlive(30);
}
// Force clean start for certain clients
String clientId = connect.getClientId();
if (clientId.startsWith("temp-")) {
connect.setCleanStart(true);
}
// Modify session expiry interval (MQTT 5.0)
connect.setSessionExpiryInterval(3600); // 1 hour
}
}
See ConnectInboundInterceptorHandler.java for internal implementation.
Outbound Connack Interceptor
Intercept connection acknowledgments:
import com.hivemq.extension.sdk.api.interceptor.connack.ConnackOutboundInterceptor;
import com.hivemq.extension.sdk.api.interceptor.connack.parameter.ConnackOutboundInput;
import com.hivemq.extension.sdk.api.interceptor.connack.parameter.ConnackOutboundOutput;
import com.hivemq.extension.sdk.api.packets.connack.ModifiableConnackPacket;
public class ConnackOutboundInterceptor implements ConnackOutboundInterceptor {
@Override
public void onOutboundConnack(
@NotNull ConnackOutboundInput input,
@NotNull ConnackOutboundOutput output) {
ModifiableConnackPacket connack = output.getConnackPacket();
String clientId = input.getClientInformation().getClientId();
// Add server information
connack.getUserProperties().addUserProperty(
"server-version",
"1.0.0"
);
// Add client-specific metadata
connack.getUserProperties().addUserProperty(
"assigned-client-id",
clientId
);
}
}
See ConnackOutboundInterceptorHandler.java for internal implementation.
Subscribe Interceptors
Inbound Subscribe Interceptor
Intercept subscription requests:
import com.hivemq.extension.sdk.api.interceptor.subscribe.SubscribeInboundInterceptor;
import com.hivemq.extension.sdk.api.interceptor.subscribe.parameter.SubscribeInboundInput;
import com.hivemq.extension.sdk.api.interceptor.subscribe.parameter.SubscribeInboundOutput;
import com.hivemq.extension.sdk.api.packets.subscribe.ModifiableSubscribePacket;
import com.hivemq.extension.sdk.api.packets.subscribe.ModifiableSubscription;
public class SubscribeInboundInterceptor implements SubscribeInboundInterceptor {
@Override
public void onInboundSubscribe(
@NotNull SubscribeInboundInput input,
@NotNull SubscribeInboundOutput output) {
ModifiableSubscribePacket subscribe = output.getSubscribePacket();
String clientId = input.getClientInformation().getClientId();
// Modify subscriptions
for (ModifiableSubscription subscription : subscribe.getSubscriptions()) {
String topicFilter = subscription.getTopicFilter();
// Downgrade QoS for non-premium clients
if (!clientId.startsWith("premium-")) {
subscription.setQos(Qos.AT_MOST_ONCE);
}
// Modify topic filters
if (topicFilter.equals("#")) {
// Prevent wildcard subscriptions
subscription.setTopicFilter("public/#");
}
System.out.println("Client " + clientId + " subscribing to " +
subscription.getTopicFilter());
}
}
}
See interceptor implementations in src/main/java/com/hivemq/extensions/interceptor/subscribe/.
Disconnect Interceptors
Inbound Disconnect Interceptor
Intercept client-initiated disconnects:
import com.hivemq.extension.sdk.api.interceptor.disconnect.DisconnectInboundInterceptor;
import com.hivemq.extension.sdk.api.interceptor.disconnect.parameter.DisconnectInboundInput;
import com.hivemq.extension.sdk.api.interceptor.disconnect.parameter.DisconnectInboundOutput;
public class DisconnectInboundInterceptor implements DisconnectInboundInterceptor {
@Override
public void onInboundDisconnect(
@NotNull DisconnectInboundInput input,
@NotNull DisconnectInboundOutput output) {
String clientId = input.getClientInformation().getClientId();
var disconnect = input.getDisconnectPacket();
System.out.println("Client " + clientId + " disconnecting. Reason: " +
disconnect.getReasonCode());
// Log session expiry
disconnect.getSessionExpiryInterval().ifPresent(expiry -> {
System.out.println("Session expiry interval: " + expiry + " seconds");
});
}
}
See DisconnectInterceptorHandler.java for internal implementation.
Outbound Disconnect Interceptor
Intercept server-initiated disconnects:
import com.hivemq.extension.sdk.api.interceptor.disconnect.DisconnectOutboundInterceptor;
import com.hivemq.extension.sdk.api.interceptor.disconnect.parameter.DisconnectOutboundInput;
import com.hivemq.extension.sdk.api.interceptor.disconnect.parameter.DisconnectOutboundOutput;
import com.hivemq.extension.sdk.api.packets.disconnect.ModifiableDisconnectPacket;
public class DisconnectOutboundInterceptor implements DisconnectOutboundInterceptor {
@Override
public void onOutboundDisconnect(
@NotNull DisconnectOutboundInput input,
@NotNull DisconnectOutboundOutput output) {
ModifiableDisconnectPacket disconnect = output.getDisconnectPacket();
String clientId = input.getClientInformation().getClientId();
// Add reason string for MQTT 5.0 clients
disconnect.setReasonString(
"Disconnected by server policy"
);
// Add custom metadata
disconnect.getUserProperties().addUserProperty(
"disconnected-client",
clientId
);
System.out.println("Disconnecting client: " + clientId);
}
}
Message Enrichment Example
Add metadata to all published messages:
import java.time.Instant;
import java.util.UUID;
public class MessageEnrichmentInterceptor implements PublishInboundInterceptor {
@Override
public void onInboundPublish(
@NotNull PublishInboundInput input,
@NotNull PublishInboundOutput output) {
ModifiablePublishPacket packet = output.getPublishPacket();
var userProps = packet.getUserProperties();
// Add message ID
userProps.addUserProperty(
"message-id",
UUID.randomUUID().toString()
);
// Add timestamp
userProps.addUserProperty(
"timestamp",
Instant.now().toString()
);
// Add client information
String clientId = input.getClientInformation().getClientId();
userProps.addUserProperty("publisher", clientId);
// Add connection metadata
input.getConnectionInformation().getInetAddress().ifPresent(addr -> {
userProps.addUserProperty(
"source-ip",
addr.getHostAddress()
);
});
}
}
Protocol Translation Example
Convert between JSON and Protocol Buffers:
import com.google.protobuf.Message;
import com.google.gson.Gson;
public class ProtocolTranslationInterceptor implements PublishInboundInterceptor {
private final Gson gson = new Gson();
@Override
public void onInboundPublish(
@NotNull PublishInboundInput input,
@NotNull PublishInboundOutput output) {
PublishPacket packet = input.getPublishPacket();
String topic = packet.getTopic();
// Convert JSON to Protobuf for legacy topics
if (topic.startsWith("legacy/json/")) {
ByteBuffer payload = packet.getPayload().orElse(null);
if (payload != null) {
String json = StandardCharsets.UTF_8.decode(payload).toString();
// Convert JSON to Protobuf
byte[] protobuf = jsonToProtobuf(json);
ModifiablePublishPacket modifiable = output.getPublishPacket();
modifiable.setPayload(ByteBuffer.wrap(protobuf));
modifiable.setTopic(topic.replace("legacy/json/", "protobuf/"));
modifiable.setContentType("application/protobuf");
}
}
}
private byte[] jsonToProtobuf(String json) {
// Convert JSON to Protocol Buffers
// Implementation depends on your protobuf schema
return new byte[0];
}
}
Async Interceptors
Perform async operations in interceptors:
import com.hivemq.extension.sdk.api.async.Async;
import com.hivemq.extension.sdk.api.async.TimeoutFallback;
import java.time.Duration;
public class AsyncPublishInterceptor implements PublishInboundInterceptor {
private final ValidationService validationService;
@Override
public void onInboundPublish(
@NotNull PublishInboundInput input,
@NotNull PublishInboundOutput output) {
// Enable async mode
Async<PublishInboundOutput> async = output.async(
Duration.ofSeconds(2),
TimeoutFallback.SUCCESS // Allow publish on timeout
);
PublishPacket packet = input.getPublishPacket();
ByteBuffer payload = packet.getPayload().orElse(null);
if (payload != null) {
// Async validation
validationService.validateAsync(payload)
.thenAccept(valid -> {
if (valid) {
async.resume(); // Continue with packet
} else {
// Prevent delivery
async.resume().preventPublishDelivery();
}
});
} else {
async.resume();
}
}
}
Preventing Packet Delivery
Block packets from being processed:
public class FilteringInterceptor implements PublishInboundInterceptor {
@Override
public void onInboundPublish(
@NotNull PublishInboundInput input,
@NotNull PublishInboundOutput output) {
PublishPacket packet = input.getPublishPacket();
// Block messages with offensive content
ByteBuffer payload = packet.getPayload().orElse(null);
if (payload != null) {
String content = StandardCharsets.UTF_8.decode(payload).toString();
if (containsOffensiveContent(content)) {
// Prevent message delivery
output.preventPublishDelivery();
return;
}
}
// Block based on rate limiting
String clientId = input.getClientInformation().getClientId();
if (isRateLimited(clientId)) {
output.preventPublishDelivery();
}
}
private boolean containsOffensiveContent(String content) {
// Content filtering logic
return false;
}
private boolean isRateLimited(String clientId) {
// Rate limiting logic
return false;
}
}
Best Practices
- Minimize Processing - Keep interceptor logic fast and lightweight
- Use Async Mode - Don’t block packet processing with I/O
- Avoid Unnecessary Modifications - Only modify packets when needed
- Cache Lookups - Cache frequently accessed data
- Batch Operations - Process multiple packets together when possible
Security
- Validate Payloads - Check payload size and format
- Sanitize Topics - Validate topic structure and content
- Filter Sensitive Data - Remove credentials from logs and metrics
- Implement Rate Limiting - Prevent abuse via interceptors
Reliability
- Handle Errors Gracefully - Don’t crash on malformed packets
- Set Appropriate Timeouts - Don’t block indefinitely
- Log Important Events - Track interceptor actions for debugging
- Test Edge Cases - Test with malformed and edge-case packets
Troubleshooting
Interceptor Not Called
- Verify interceptor provider is registered in
extensionStart()
- Check extension is loaded and started
- Review packet type (inbound vs outbound)
- Enable debug logging for interceptor system
Packet Modifications Not Applied
- Use
output.getPublishPacket() to get modifiable packet
- Verify modifications occur before async timeout
- Check that packet hasn’t been prevented from delivery
- Review interceptor execution order (extension priority)
- Profile interceptor execution time
- Use async mode for I/O operations
- Reduce number of registered interceptors
- Optimize packet modification logic
See Interceptors.java:35 and Interceptors.java:51 for interceptor provider registration.
Next Steps
Authentication
Authenticate clients before packet processing
Authorization
Control packet authorization
Client Initializers
Register client-specific interceptors
Extension SDK
Learn more about the Extension SDK