Broker Testing

MQTT Load Testing with JMeter: Plugin Setup, Thread Groups and Results

Load test an MQTT broker with Apache JMeter: install the MQTT plugin, build publisher and subscriber thread groups, run headless and read the results.

Updated · 9 min read

MQTT Load Testing with JMeter: Plugin Setup, Thread Groups and Results

Apache JMeter can load test an MQTT broker once you add an MQTT plugin: each JMeter thread becomes one MQTT client that connects, publishes or subscribes, and JMeter's timers, listeners and HTML report do the rest. If your team already uses JMeter for HTTP, this lets you keep one tool, one report format and one CI job for your MQTT tests too.

This guide installs the plugin, builds a test plan with publishers and subscribers, runs it headless, and explains what the numbers actually measure.

Choosing an MQTT plugin

JMeter has no built-in MQTT sampler. The most widely used option is emqx/mqtt-jmeter (originally published by XMeter). It adds four samplers:

SamplerWhat it does
MQTT ConnectOpens a connection (TCP, SSL, WS or WSS) and keeps it for the thread
MQTT Pub SamplerPublishes to one topic with a chosen QoS, retain flag and payload
MQTT Sub SamplerSubscribes and samples received messages for a time window or a message count
MQTT DisConnectCloses the thread's connection

Pub and Sub samplers reuse the connection created by the Connect sampler in the same thread. If no connection exists yet, they fail immediately — which is exactly what you want to see in the report.

Install JMeter and the plugin

JMeter needs a Java runtime (Java 8 or newer for JMeter 5.6). Download JMeter, then copy the plugin jar from the project's releases page into lib/ext:

Install JMeter 5.6.3 and the MQTT plugin
curl -LO https://archive.apache.org/dist/jmeter/binaries/apache-jmeter-5.6.3.tgz
tar xzf apache-jmeter-5.6.3.tgz

# download the latest jar from https://github.com/emqx/mqtt-jmeter/releases
cp ~/Downloads/mqtt-xmeter-*.jar apache-jmeter-5.6.3/lib/ext/

apache-jmeter-5.6.3/bin/jmeter   # start the GUI to build the plan

After a restart, right-click a Thread Group → Add → Sampler and the four MQTT samplers appear. Use the GUI only to build and debug the plan; run real load from the command line.

Before any load, confirm the host, port and credentials work with a single client in the online MQTT client. A wrong password at 500 threads produces 500 identical errors and no insight. The examples use broker.connectmq.com, a shared endpoint meant for quick functional checks; for the load run itself, point -Jhost and the credentials at your own private broker instance.

Build the test plan

A realistic telemetry test has two groups of clients: publishers that send device data and subscribers that consume it. This layout gives each publisher its own topic and its own subscriber (one-to-one), so you measure routing rather than fan-out:

mqtt-load.jmx — test plan tree
Test Plan
├── User Defined Variables: HOST, PORT, THREADS, RATE
├── Thread Group "subscribers"   threads=${THREADS}  ramp-up=30 s
│   ├── MQTT Connect           clientId prefix "jm_sub_" + random suffix
│   └── MQTT Sub Sampler       topic testmqtt/jmeter/${__threadNum}/data
├── Thread Group "publishers"    threads=${THREADS}  ramp-up=60 s  duration=600 s
│   ├── Once Only Controller
│   │   └── MQTT Connect       clientId prefix "jm_pub_" + random suffix
│   └── MQTT Pub Sampler       topic testmqtt/jmeter/${__threadNum}/data
│       └── Constant Throughput Timer   ${RATE} samples/min, all active threads
├── tearDown Thread Group → MQTT DisConnect
└── Summary Report (GUI debugging only)

Key settings, sampler by sampler:

  • Variables. Define HOST as ${__P(host,broker.connectmq.com)}, PORT as ${__P(port,1883)}, and so on. __P reads a property passed with -J on the command line and falls back to the default, so one .jmx file serves every environment.
  • MQTT Connect. Server ${HOST} (no tcp:// prefix), port ${PORT}, protocol TCP (or SSL with port 8883, WSS with 8084), username and password from properties, keep alive matching your devices. Enable the random client ID suffix — two threads with the same client ID would kick each other off the broker.
  • Once Only Controller. The publisher thread connects on its first iteration only, then loops the Pub sampler. Without it, every iteration would open a new connection and you would be testing connection rate instead of throughput.
  • MQTT Pub Sampler. QoS 1, retain off, payload type "random string with fixed length" sized like your real messages (for example 256 bytes), and "Add timestamp in payload" enabled so subscribers can compute latency.
  • MQTT Sub Sampler. Same QoS, "Payload includes timestamp" enabled, and sample on "number of received messages" (for example 100). Each sample then represents a batch of messages and reports their latency.
  • Constant Throughput Timer. Its target is in samples per minute. For 1,000 messages per second across all publishers, set 60,000 and choose "all active threads in current thread group". Put it as a child of the Pub sampler so it only paces publishing.

${__threadNum} is the thread number inside its own group, so publisher 17 and subscriber 17 share testmqtt/jmeter/17/data. Check the pattern with the topic matcher if you switch to wildcard subscriptions.

Run headless and generate the report

GUI mode distorts results: rendering listeners costs CPU on the load generator. Run in non-GUI mode, write raw samples to a .jtl file and let JMeter build its HTML dashboard at the end:

Run the plan from the command line
export MQTT_USER=username MQTT_PASS=password
HEAP="-Xms1g -Xmx4g" apache-jmeter-5.6.3/bin/jmeter -n \
  -t mqtt-load.jmx \
  -Jhost=broker.connectmq.com -Jport=1883 \
  -Juser="$MQTT_USER" -Jpass="$MQTT_PASS" \
  -Jthreads=200 -Jrate=60000 \
  -l results.jtl -e -o report/

-n is non-GUI mode, -l the results file, -e -o generates the dashboard into an empty folder. The HEAP variable is read by the jmeter start script; increase it when you raise the thread count, because every thread holds a full MQTT client.

Listeners: what to keep, what to disable

ListenerUse it forDuring load?
View Results TreeDebugging one thread, checking payloadsNo — disable it
Summary Report / Aggregate ReportQuick per-sampler counts, error %, throughputGUI dry runs only
.jtl file + HTML dashboard (-e -o)Final report: percentiles, throughput over timeYes
Backend Listener (InfluxDB)Live graphs in Grafana during long runsYes, if you need live data

The HTML dashboard groups results by sampler label, so give samplers clear names ("Pub QoS1 256B", "Sub batch 100") — the labels are your report headings.

What the numbers mean

JMeter reports "response time" for every sample, but each MQTT sampler measures something different:

SamplerElapsed time meansWatch for
MQTT ConnectCONNECT until CONNACK (including TLS handshake)Errors and slow connects during ramp-up
MQTT Pub SamplerTime to hand the message over; at QoS 1/2 this includes waiting for the broker's acknowledgementRising elapsed time = broker or network back-pressure
MQTT Sub SamplerThe sampling window you configured, not delivery latencyMessages received per sample and the latency computed from timestamps

The two figures that matter most are publish throughput (Pub samples per second, from the dashboard) and end-to-end latency (from the Sub sampler's timestamp calculation). If received messages fall behind sent messages, something is dropping or queueing; repeat at QoS 1 to separate capacity problems from QoS 0 loss. A deeper walkthrough of percentiles and false bottlenecks is in interpreting MQTT benchmark results.

Connect errors that appear exactly at a round number of threads usually mean an operating-system limit on the load generator or a connection limit on the broker — see too many connections and look up the reason code with the MQTT reason code tool.

Scaling the load generator

Every JMeter thread is a Java thread with its own MQTT client, so JMeter is heavier per connection than Erlang- or Go-based tools. Practical limits:

  • Raise ulimit -n on the load generator and give JMeter enough heap.
  • Watch the generator's CPU. Above roughly 80 %, your measurements describe JMeter, not the broker.
  • For more connections, use JMeter's distributed mode (-R host1,host2) so several machines run the same plan, or move the pure connection test to emqtt-bench and keep JMeter for scenario-style tests.
  • The plugin's 2.x releases add batch connect/disconnect samplers that open several connections per thread; check the release notes if you need to reduce the thread count.

A repeatable JMeter workflow

  1. Verify one connection by hand in the online client.
  2. Run the plan in the GUI with 2 threads and View Results Tree enabled; confirm every sampler succeeds.
  3. Disable View Results Tree, save, and run headless at 10 %, 50 %, 100 % and 150 % of expected peak.
  4. Keep the .jtl files and dashboards of every run next to broker CPU, memory and connection metrics.
  5. Commit the .jmx file to your repository and re-run it after broker upgrades.

Watch your JMeter test traffic live

JMeter is not the lightest MQTT load generator, but it is a practical one when your team already knows it: parameterized plans, timers for exact message rates and a ready-made HTML report. Keep the samplers simple, run headless and read each sampler's timing for what it really measures.

Frequently asked questions

Does JMeter support MQTT out of the box?

No. JMeter has no built-in MQTT sampler. You add a plugin such as emqx/mqtt-jmeter (formerly XMeter), which provides Connect, Pub, Sub and DisConnect samplers. Copy its jar into lib/ext and restart JMeter.

Can JMeter test MQTT 5?

The emqx/mqtt-jmeter plugin documents MQTT 3.1 and 3.1.1. If your test depends on MQTT 5 features such as reason codes, user properties or session expiry, use a tool with MQTT 5 support such as emqtt-bench or k6 with xk6-mqtt.

How many MQTT connections can one JMeter machine open?

It depends on heap, CPU and OS limits, because every JMeter thread holds one MQTT connection. Raise the heap and ulimit -n, run headless, and use distributed mode or a lighter tool like emqtt-bench when you need tens of thousands of connections.