Running JMeter in non-GUI mode is the only way to get accurate, scalable load test results.

Let’s see JMeter spin up a few thousand threads to hit an API endpoint.

# First, make sure you have a JMeter test plan saved as a .jmx file.
# For this example, let's assume it's named 'my_test_plan.jmx'.
# We'll also need a JMeter properties file to configure logging,
# otherwise, you'll get a ton of output. Let's create 'jmeter.properties'
# with just one line:
#
# log_level.jmeter=INFO
#
# Now, let's run it. We'll simulate 1000 concurrent users hitting a dummy API
# for 5 minutes (300 seconds), saving results to 'results.csv'.

./jmeter -n -t my_test_plan.jmx -l results.csv -e -o ./dashboard-report -Jusers=1000 -Jduration=300 -p jmeter.properties

This command kicks off a test with 1000 users for 300 seconds, spitting out a CSV of raw results and generating an HTML dashboard report. The -n flag tells JMeter to go headless, -t points to your test plan, -l specifies the log file, -e generates the report after the test, -o directs the report output, and -J injects properties directly into the test plan.

The core problem JMeter solves is simulating many users interacting with a system simultaneously. The GUI mode, while great for building tests, introduces overhead: it’s busy rendering threads, listeners, and managing the UI itself. This overhead consumes CPU and memory that should be dedicated to simulating user load. In non-GUI mode, JMeter strips away all graphical elements, focusing solely on executing test samplers, managing thread groups, and writing results. This dramatically reduces resource consumption, allowing you to simulate far more users from a single machine and getting results that more closely reflect real-world performance.

Internally, JMeter uses a thread-per-user model. Each virtual user is a Java thread. In non-GUI mode, JMeter efficiently manages these threads, allocating system resources without the graphical baggage. The test plan itself (.jmx file) is essentially an XML configuration that JMeter parses. When running non-GUI, it parses this configuration and then executes the defined samplers (like HTTP requests), timers, assertions, and listeners. The results listener, configured with -l, writes transaction data (response time, success/failure, etc.) to a specified file in real-time. The -e flag triggers the generation of a comprehensive HTML dashboard report from this CSV data after the test completes.

The most surprising thing most people miss is how much more you can achieve by tuning the JVM and JMeter’s own settings when running non-GUI. It’s not just about not using the GUI; it’s about optimizing the engine itself. For instance, increasing the Java heap size (-Xmx) for the JMeter process can prevent OutOfMemory errors under heavy load, allowing more threads to run. You can also adjust JMeter’s own properties, like jmeter.save.saveservice.bytes_per_request, to false if you don’t need that data, reducing the size of your results file and I/O overhead.

If you try to run a very large test with many threads (e.g., 10,000+ on a single machine), you’ll likely hit a java.lang.OutOfMemoryError: Metaspace even with a large heap.

Want structured learning?

Take the full Jmeter course →