k6 vs JMeter: The Same 100-User Load Test, Side by Side
One small endpoint, two tools, real numbers, and the one-character k6 typo that stops your test with exit code 104.
Most "JMeter vs k6" articles compare feature lists. I wanted to see the difference in the files themselves, so I wrote the same load test twice: 100 virtual users hitting one endpoint for 30 seconds, once as a k6 script and once as a JMeter plan. Both files, plus the tiny demo app they hit, are in a public repo so you can run them yourself: zainbasra/aitg-test-kits.
A quick honesty note before any numbers: the server and the load generator ran on the same machine, against a trivial JSON endpoint. That shows how each tool behaves. It is not a capacity benchmark of anything.
The test
- Target:
GET http://127.0.0.1:3000/userson a small Node.js demo server (no dependencies). - Load: 100 virtual users, 30 seconds, 1 second of think time between requests.
- Pass rule: 95% of requests faster than 200 ms.
Version 1: k6 (a JavaScript file)
import http from 'k6/http';
import { check, sleep } from 'k6';
export const options = {
vus: 100,
duration: '30s',
thresholds: { http_req_duration: ['p(95)<200'] },
};
export default function () {
const res = http.get('http://127.0.0.1:3000/users');
check(res, { 'status is 200': (r) => r.status === 200 });
sleep(1);
}
That's the whole test. The load profile, the assertion and the pass/fail rule live in one file you can review in a pull request. Run it with k6 run k6-users.js. If the threshold fails, k6 exits with a non-zero code, so your CI job fails without any extra plugin.
Version 2: JMeter (an XML test plan)
The same load in JMeter is a .jmx file: a Thread Group with 100 threads, a 30-second duration and a 1-second ramp-up, an HTTP Request sampler for /users on port 3000, and a Constant Timer of 1,000 ms for think time. You normally build it in the JMeter GUI, then run it headless:
jmeter -n -t jmeter-users.jmx -l result.jtl
The plan works fine, but it's XML generated by a GUI. A reviewer can't easily see what changed between two versions, and the 200 ms pass rule needs an extra assertion or a post-processing step in your pipeline rather than a single line.
Results (10 Nov 2026, one Windows PC, everything local)
| k6 2.3.0 | JMeter 5.6.3 | |
|---|---|---|
| Requests | 3,000 | 2,874 |
| Errors | 0 | 0 |
| Average response | 0.99 ms | 2 ms |
| Max response | 24.54 ms | 98 ms |
| Throughput | ~99.8 req/s | ~95.6 req/s |
Both tools handled this load without errors, and both landed close to the expected 100 requests per second for 100 users with 1 second of think time. The small gaps come from how each tool schedules threads and counts samples. On a toy endpoint like this, they say nothing about which tool is "faster" for your system. Your numbers will differ with hardware, versions and background load.
The k6 typo worth knowing about
The repo includes a second k6 script with one change: the threshold is written p95<200 instead of p(95)<200.
I re-ran it today with k6 2.3.0. k6 refuses to start the test at all:
level=error msg="failed parsing threshold expression's \"p95<200\" left hand side; reason: failed parsing method from expression"
and exits with code 104. The good news is that it fails loudly instead of silently ignoring the rule. The catch: in CI this looks like a broken pipeline, not a failed performance check, so it's worth knowing what exit code 104 means before you go hunting through your app for a regression. The correct syntax wraps the percentile in brackets: p(95), p(99), avg, max.
So which should you learn?
- k6 if your tests should live in Git next to the code, run in CI on every merge and be reviewed like any other code. It suits developers and SDETs who are comfortable with JavaScript.
- JMeter if you need its very wide protocol support and plugin ecosystem, your team prefers a GUI, or you're joining a company with years of existing
.jmxplans. It's still everywhere in enterprise QA. - Both is a realistic answer for an SDET: k6 for new, code-first tests; enough JMeter to maintain what already exists.
The full comparison, covering scripting, CI/CD, resource use, protocol support, AI-assisted scripting and what job postings ask for, is on my site: JMeter vs k6 2026: Which Should You Learn as an SDET?
Have you moved a JMeter suite to k6, or the other way around? I'd like to hear what made the switch worth it.
About the author
- QA Automation Engineer & SDET | Founder of aitestingguide.com | Honest reviews of AI testing tools — written by a real tester, not a marketer.
Comments
Join the discussionThe detail that stood out to me was the request count — 3,000 from k6 against 2,874 from JMeter. With a 1-second ramp-up on the JMeter side, that gap is more likely timing than tool performance, which makes the near-identical response times the more honest headline here.
I also liked that the k6 typo fails loudly with exit code 104. A test that silently ignored a broken threshold would be far worse in CI than one that refuses to run. That is a good argument for keeping thresholds in version control and reviewing them like code, rather than treating them as a setting you tweak in a GUI and forget.
For a next run, I would be curious to see the same test with a gradual ramp and a less trivial endpoint, so the tools are stressed on connection handling rather than just returning a tiny JSON payload.
Good catch on the ramp — that 126-request gap is almost certainly timing, not the tools, so the matching response times are the real takeaway. For the next run I'd keep the ramp identical on both sides and use a DB-backed endpoint, so connection handling gets stressed instead of a tiny JSON payload. And you're right about exit code 104 — a test that fails loudly in CI beats one that silently passes with a broken threshold.
Running both against the same local endpoint keeps the comparison honest, and the repo makes it easy to reproduce. The next step I would try is the same test from a separate machine against a deployed endpoint, since localhost hides network latency and connection limits. A staged ramp would also show more than a flat 100 VUs — how p95 degrades as the load builds, not just where it lands.


