Authors Apache JMeter `.jmx` test plans (Thread Groups + HTTP samplers + assertions + listeners) in the JMeter GUI, runs them headlessly via `jmeter -n -t plan.jmx -l results.jtl`, generates an HTML dashboard with `-e -o`, and gates CI on JTL parsing. Use when the project has an existing JMeter investment, needs JVM-native load tooling, or works in domains with strong JMeter community support (banking, telecom, enterprise).
80
100%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
High
Do not use without reviewing
Apache JMeter is the long-running JVM-native load-testing tool. Tests
are XML .jmx files authored in JMeter's GUI; CI runs them headless
via the jmeter CLI (jmeter-getstarted).
The official guidance is explicit:
"GUI mode should only be used for creating the test script, CLI mode (NON GUI) must be used for load testing." (jmeter-getstarted)
This skill covers the CLI / CI side. Authoring is GUI-driven and out of scope here - see the JMeter user manual for the Thread Group / HTTP Sampler / Assertion authoring flow.
.jmx test plans.If the team is starting fresh, evaluate
k6-load-testing (developer-friendly
JS), gatling-load-testing (JVM
DSL), or locust-load-testing
(Python) before adopting JMeter - XML authoring has a steep learning
curve.
JMeter requires Java 8 or higher with JAVA_HOME set. Download
the latest release from
jmeter.apache.org and extract - there
is no installer (jmeter-getstarted).
For Docker-based CI, official images are at apache/jmeter. Pin to
a specific tag rather than latest.
Per jmeter-getstarted:
jmeter -n -t test.jmx -l results.jtl| Flag | Purpose |
|---|---|
-n | Non-GUI mode. Required for load tests. |
-t | Path to the .jmx test plan. |
-l | Output file for raw sample results (JTL - CSV-shaped). |
Per jmeter-getstarted:
jmeter -n -t test.jmx -l results.jtl -e -o report_folder| Flag | Purpose |
|---|---|
-e | Generate the HTML dashboard report automatically. |
-o | Output folder (must be empty or non-existent). |
The dashboard shows percentiles, throughput, error rates, response- time graphs, and per-sampler breakdowns - the canonical JMeter output for human review.
| Flag | Purpose |
|---|---|
-J<property>=<value> | Override a JMeter property at the JVM level. |
-G<property>=<value> | Override a property in distributed (remote) mode. |
-Jjmeter.save.saveservice.output_format=csv | Force CSV JTL (vs. XML). |
-q <props-file> | Additional properties file. |
-r | Start the test on remote slaves (distributed mode). |
-X | Exit JMeter when the test finishes. |
For multi-environment runs, parameterize the test plan with
${__P(api.base.url, default)} and override at the CLI:
jmeter -n -t orders.jmx -l results.jtl \
-Japi.base.url=https://staging.example.com \
-Japi.token=$API_TOKENThe JTL file is the structured artifact for CI gating. Default JTL
columns (CSV): timeStamp, elapsed, label, responseCode,
responseMessage, threadName, dataType, success, failureMessage,
bytes, sentBytes, grpThreads, allThreads, URL, Latency,
IdleTime, Connect.
Quick-and-dirty pass/fail with awk:
# Count errors
awk -F',' 'NR>1 && $8=="false"' results.jtl | wc -l
# Compute mean response time
awk -F',' 'NR>1 { sum+=$2; n++ } END { print sum/n }' results.jtlFor richer parsing, use the JMeter HTML report's
statistics.json under the report folder - it contains the
percentiles per sampler in JSON form.
# .github/workflows/jmeter.yml
name: load-test
on:
pull_request:
paths: ['tests/load/**.jmx']
schedule:
- cron: '0 4 * * *'
jobs:
jmeter:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- name: Run JMeter via Docker
env:
API_TOKEN: ${{ secrets.STAGING_API_TOKEN }}
run: |
docker run --rm \
-v "$PWD:/work" \
-w /work \
apache/jmeter \
-n -t tests/load/orders.jmx \
-l results.jtl \
-e -o report \
-Japi.token=$API_TOKEN \
-Japi.base.url=https://staging.example.com
- name: Pass/fail gate
run: |
ERRORS=$(awk -F',' 'NR>1 && $8=="false"' results.jtl | wc -l)
if [ "$ERRORS" -gt 10 ]; then
echo "::error::Got $ERRORS errors (>10 threshold)"
exit 1
fi
- name: Upload report
if: always()
uses: actions/upload-artifact@v4
with:
name: jmeter-report
path: |
report/
results.jtl
retention-days: 14The Docker invocation pattern keeps the CI runner clean - no Java / JMeter install on the runner.
| Anti-pattern | Why it fails | Fix |
|---|---|---|
| Running tests in GUI mode "just for this one quick run" | GUI runner adds overhead; metrics are skewed; per jmeter-getstarted this is explicitly forbidden for load tests. | Always -n. GUI is for authoring only. |
Hard-coding URLs / tokens in the .jmx XML | Test plan binds to one environment. | Parameterize: ${__P(api.base.url, ...)}; override with -J. |
| Saving JTL in XML format | XML JTL files are 5-10x larger than CSV; slow to parse. | Force CSV with -Jjmeter.save.saveservice.output_format=csv. |
| Listeners enabled in CI runs | UI listeners (View Results Tree, Aggregate Report) consume RAM proportional to sample count; OOMs at scale. | Disable all listeners in .jmx; emit JTL only; generate HTML report post-run via -e -o. |
| One mega-test-plan with 100 samplers | Failure attribution is hard; runtime dominated by one slow endpoint. | Split into per-domain .jmx files; run as separate CI jobs. |
| Threshold gates only on error rate | A 30-second response that succeeds passes the rate gate but breaks UX. | Pair error-rate with percentile gates parsed from statistics.json. |
.jmx files are not human-friendly; reviews of
test-plan changes are awkward.k6-load-testing,
gatling-load-testing,
locust-load-testing -
alternatives by stack.perf-budget-gate - downstream
gate aggregating multiple runner verdicts.