Skip to content

Vayu vs Apache JMeter - a native HTTP load tester without the JVM

Apache JMeter has been the default open source load tester for two decades, and that longevity cuts both ways. It tests almost every protocol an enterprise runs, and it does so through a Java Swing interface and a thread-per-user model that has to be tuned, heap-sized, and usually run headless to get honest numbers. Vayu covers the HTTP case with a native C++ engine, from the same app you build the request in.

Vayu Apache JMeter
Execution engine C++ (native) Java (JVM)
Concurrency model Multi-worker event loop Thread per virtual user
API client + load test Both, one app Load test only
Load test throughput Tens of thousands of req/s Moderate (thread-heavy)
UI Native desktop app Java Swing (dated)
UI responsiveness High (sidecar architecture) Laggy under load
Memory usage Low (direct memory) High (RAM-intensive)
Protocol breadth HTTP, GraphQL, SSE HTTP, JDBC, JMS, FTP, LDAP, SMTP and more
Scripting QuickJS (pm.* syntax) Groovy / BeanShell
Distributed execution No - single machine Yes (controller + workers)
MCP / agent control Built in, local, drives the load engine No
Proxy / custom CA / client certs In Settings - four proxy modes, additive CAs, per-host certs* JVM properties and the Java keystore
Postman collection import Yes (v2.0 + v2.1) No
OpenAPI import Yes (3.1 / 3.0 / 2.0) No
JMeter .jmx import Yes - samplers, extractors, assertions, timers and logic controllers, an unmapped class counted rather than dropped** N/A
Open source Yes (dual-license) Yes (Apache 2.0)

* One asterisk that row has earned. system proxy mode resolves a PAC script once, against a probe URL, applying that one answer engine-wide rather than per URL; a headless engine with nothing resolved falls back to environment-variable pickup rather than to no proxy. Client certificates carry no qualification any more - they are proven on a wire on all three platforms, in both PEM-pair and PKCS#12 form. Detail: proxy settings, TLS trust and client certificates.

** Common, not complete. JMeter's own class list is open-ended - plugins add more of them - so this is a mapping table, not a JMeter-compatibility claim: HTTP samplers, JSON/regex/boundary extractors, the five assertion kinds, all four timer kinds and the six logic controllers map onto Vayu's element model, and a class this importer has no mapping for (a listener, a cookie manager, a protocol Vayu's HTTP engine does not speak) is counted by its own class name rather than silently dropped. Detail: the JMeter import doc.

The engine, and what it does not cost you

JMeter's classic constraint is its concurrency model: one thread per virtual user, on a JVM, which means the load generator's own resource profile becomes something you tune before you can trust the numbers - heap size, thread ramp, and the standing advice to run without the GUI because the GUI itself distorts the result. Vayu's engine is a multi-worker libcurl event loop in C++23, so concurrency is not threads, and it runs as a sidecar process next to the UI rather than inside it. That is the architectural reason you can watch a live dashboard while the run saturates a target: the renderer never shares a thread with the request load. Measured on a loopback target it reached 56,880 req/s, matching wrk and edging past vegeta on the same machine - method and reproduction.

Build the request, then load it

In JMeter a test is a tree of samplers, config elements, controllers and listeners that you assemble before anything is sent, and the request you were debugging in an API client is rebuilt there by hand. Vayu starts from the other end: the saved request, with its environment variables, its auth resolved engine-side, and its pm.* scripts, is what the load run drives. Collections run as ordered scenarios with per-step results and threshold verdicts, driven from a CSV, TSV, JSON or JSONL file when you need each virtual user to send different data. As in JMeter, a failed assertion can fail the run, not just the sample it ran on: assert.* element outcomes and pm.test results share one failure-rate budget (maxAssertionFailureRatePct), and thresholds.failRun turns a missed budget into a Failed run. Existing work comes across too - Postman v2.0/v2.1, Insomnia v4, and OpenAPI 3.1/3.0 or Swagger 2.0 specs generate a ready-to-use collection. JMeter's setUp / tearDown thread groups, which run once before or once after the whole test, are script.setup / script.teardown here - elements on the collection itself, run once at the run's own start or end in either run mode, rather than a thread group of their own.

When to choose JMeter

JMeter is still the right answer for a large class of work, and none of these are things Vayu plans to catch up on:

  • You test more than HTTP. JDBC against a database, JMS queues, FTP, LDAP, SMTP, TCP - JMeter's sampler catalogue and plugin ecosystem covers protocols Vayu's HTTP engine does not speak at all.
  • You need distributed load. JMeter's controller-and-workers model drives a test from many machines. Vayu runs from one.
  • Your .jmx plans lean on what the importer does not map - a protocol other than HTTP, distributed execution, a plugin sampler, SwitchController, or a JSR223/BeanShell script (imported disabled, since Groovy and BeanShell are not JavaScript). A plan built from the common element set (HTTP samplers, the extractors, assertions, timers and logic controllers in the table above) imports and runs; a team fluent in JMeter's full plugin ecosystem is still better served staying there.
  • Your load model needs a load-run logic jump. Think time and pacing timers work under load now (timer.think, including a gaussian option; timer.pacing, including its shared-cadence case, perUser: false - one cadence held across every virtual user, not one per user; and timer.throughput, the analogue of JMeter's Constant Throughput Timer, which targets a rate - N per minute - shared across every virtual user by default rather than held per user), and logic controllers - conditionals, once-only, throughput, loops and named transactions - are elements (control.if / .once / .switch / .throughput / .loop / .transaction) that run under load too: control.switch's dispatch and control.loop's repeat jump a scenario load run's virtual users the same way they jump the sequential run, control.throughput can share one budget across every virtual user (perUser: false) instead of one per user, and control.transaction can fold a between-member timer.* wait into its own reported latency (includeTimers). Correlation across a scenario's steps - a login's token reaching the next step's header, per virtual user - does work under load now too: mark the extracting element or script inline (or set the run's elements.scripts override), or let it stay in the post-run replay by default.
  • It has to be JVM-native for your infrastructure, monitoring, or compliance reasons.

Try it

Windows:

winget install athrvk.Vayu

macOS and Linux:

bash -c "$(curl -fsSL https://athrvk.github.io/vayu/install.sh)"

Full install detail Compare with Postman