Testland
Browse all skills & agents

jmeter-load-testing

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).

jmeter-load-testing

Overview

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.

When to use

  • The project already has .jmx test plans.
  • The team is on the JVM and prefers JMeter's mature ecosystem (BlazeMeter, Taurus, plugin manager, distributed mode).
  • A specific protocol is needed that JMeter has first-class support for: JDBC, JMS, MQTT, FTP, LDAP, SOAP - k6's plugin landscape is weaker for niche protocols.

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.

Install

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.

Running

Canonical CLI invocation

Per jmeter-getstarted:

jmeter -n -t test.jmx -l results.jtl
FlagPurpose
-nNon-GUI mode. Required for load tests.
-tPath to the .jmx test plan.
-lOutput file for raw sample results (JTL - CSV-shaped).

With HTML dashboard

Per jmeter-getstarted:

jmeter -n -t test.jmx -l results.jtl -e -o report_folder
FlagPurpose
-eGenerate the HTML dashboard report automatically.
-oOutput 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.

Other useful flags

FlagPurpose
-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=csvForce CSV JTL (vs. XML).
-q <props-file>Additional properties file.
-rStart the test on remote slaves (distributed mode).
-XExit 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_TOKEN

Parsing results

The 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.jtl

For richer parsing, use the JMeter HTML report's statistics.json under the report folder - it contains the percentiles per sampler in JSON form.

CI integration

# .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: 14

The Docker invocation pattern keeps the CI runner clean - no Java / JMeter install on the runner.

Anti-patterns

Anti-patternWhy it failsFix
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 XMLTest plan binds to one environment.Parameterize: ${__P(api.base.url, ...)}; override with -J.
Saving JTL in XML formatXML JTL files are 5-10x larger than CSV; slow to parse.Force CSV with -Jjmeter.save.saveservice.output_format=csv.
Listeners enabled in CI runsUI 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 samplersFailure 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 rateA 30-second response that succeeds passes the rate gate but breaks UX.Pair error-rate with percentile gates parsed from statistics.json.

Limitations

  • XML authoring. .jmx files are not human-friendly; reviews of test-plan changes are awkward.
  • Memory hungry. JMeter's per-sample memory overhead is high vs. k6 / Gatling.
  • Single-machine VU limits. ~1000 threads per machine before JVM contention; for higher loads, use distributed mode (master + remote slaves) or move to a tool with a smaller per-VU footprint.
  • Plugin ecosystem fragmentation. Many JMeter plugins are abandoned; vet plugin maintenance status before depending on one.

References