Skip to content

Live JMeter properties

MaxoPerf can set JMeter properties on a test that is already running. Anything your plan reads through ${__P(key, default)} picks the new value up on its next sample — no restart, no new run, no changes to the plan for the properties feature itself.

This is the mechanism behind the virtual-user and throughput controls too, and it is available to you directly for your own keys.

MaxoPerf starts JMeter with the BeanShell server listening on 127.0.0.1:9000 inside the runner container. A live change is delivered to the runner over the control stream and applied with setprop("key", "value"), which is exactly the call the property functions read from.

  1. Open the run, select the Live tab, and find the Properties editor.

  2. Add or edit keys, then commit. Every commit is one revision on the run’s timeline: who, when, and which keys changed.

  3. Read it in the plan with ${__P(feature.flag.checkout, v1)}. The next sample sees the new value.

From the API:

Terminal window
curl -X PUT "$MAXOPERF_API/v1/runs/$RUN_ID/live-controls/properties" \
-H "Authorization: Bearer $MAXOPERF_API_KEY" \
-H "X-Account-Id: $ACCOUNT_ID" \
-H 'Content-Type: application/json' \
-d '{"scope":{"type":"all"},"properties":{"feature.flag.checkout":"v2","think.time.ms":"250"}}'

Read properties with the built-in function, always with a default so a run started without the property still works:

<stringProp name="HTTPSampler.path">/checkout?variant=${__P(feature.flag.checkout,v1)}</stringProp>

For a number that must be re-evaluated per sample, wrap it in __P directly rather than caching it in a User Defined Variable — UDVs are evaluated once, at plan start, and will never see your change:

${__P(think.time.ms,500)}
RuleLimit
Key charactersaz, 09, _, ., - — up to 64 characters
Keys per request100
Value size4 KiB
Reserved namespacemaxoperf_* is rejected with 422 PROPERTY_RESERVED

The maxoperf_ namespace is reserved because MaxoPerf drives the virtual-user and throughput controls through it: maxoperf_vus_target (threads) and maxoperf_throughput_rpm_total — samples per minute, the unit JMeter’s Constant Throughput Timer field is in, converted for you from the requests per second you type. (The per-second number is also stated, as maxoperf_throughput_rps_total, in the run’s runtime file for scripts that are not JMeter timers.) Letting a plan write those directly would mean two authorities for one number.

Values are framed as string literals when they reach the engine, so a value containing quotes, semicolons or code is data, not code. Values longer than 256 characters are redacted in logs.

Both are live on JMeter, and both have a precondition — because JMeter itself has one.

ControlWorks whenOtherwise
Virtual usersthe plan uses a Concurrency Thread Groupdisabled with jmeter.concurrency-thread-group; a PUT returns 422 CONTROL_PRECONDITION_UNMET
Throughputthe plan contains a Constant Throughput Timer (a Throughput Shaping Timer keeps its schedule in a table and cannot be steered)disabled with jmeter.throughput-timer
Pause / resumesame as virtual users — pause is the VU target moved to 0, reversiblydisabled with jmeter.concurrency-thread-group

Classic, Stepping and Ultimate thread groups read their thread count only when the plan loads. A Concurrency Thread Group re-reads it continuously, which is why it is the one that can follow a live change. MaxoPerf detects which one your plan uses when the run is created, and disables what cannot work rather than accepting a change that would do nothing.

MaxoPerf rewrites the thread group’s target and the throughput timer’s rate to read from its own properties when the bundle is built, so you do not have to edit your plan to use the sliders. The step-by-step version of that — which elements to use, and the same recipe for k6, Locust and Gatling — is Make your test controllable mid-run.