In a build step, why does declaring a JMeter plugin jar as a build dependency not make its element available?
answer
- The launcher line matters more than the build
- Two directories, and they are not interchangeable
- One java flag makes the classpath moot
- There is a property that adds lookup directories
basics
~10 sJMeter is launched with java -jar ApacheJMeter.jar, and java ignores CLASSPATH and -cp whenever -jar is used. Components are found only under the installation's lib/ext directory, or wherever the search_paths property points.
solid answer
~40 sA JMeter run is not a program on your build's classpath — it is a separate installation tree that finds its own components. `bin/jmeter` ends in `java ... -jar "$PRGDIR/ApacheJMeter.jar"`, and the `java` command silently ignores both the `CLASSPATH` environment variable and `-classpath`/`-cp` when `-jar` is used, so whatever your build resolved never reaches the process. JMeter picks up components from `JMETER_HOME/lib/ext` and utility or dependency jars from `JMETER_HOME/lib`, and it only looks at files ending in `.jar`, not `.zip`. If you would rather not copy files into the installation, the `search_paths` property adds directories scanned for plugin jars, and `user.classpath` or `plugin_dependency_paths` adds directories of dependency jars. Whichever route you pick, the build step has to place the jar somewhere JMeter itself is told to look.
code
bash · 11 lines# provision the installation the step will actually run
tar xzf apache-jmeter-6.0.0.tgz -C "$AGENT_TOOLS"
JMETER_HOME="$AGENT_TOOLS/apache-jmeter-6.0.0"
# components go here; a .zip in this directory is ignored
cp build/deps/some-jmeter-component.jar "$JMETER_HOME/lib/ext/"
# what that component needs goes here, not in lib/ext
cp build/deps/postgresql.jar "$JMETER_HOME/lib/"
"$JMETER_HOME/bin/jmeter" -n -f -t plan.jmx -l results.jtl -e -o reportgo deeper
Recall that JMeter loads components from its own installation, and that lib/ext is where a component jar has to end up. A dependency declared in the build file does not travel to the JMeter process.
Explain the mechanism: bin/jmeter runs java -jar, which makes CLASSPATH and -cp irrelevant. Then split the two directories correctly, lib/ext for components and lib for the libraries they need.
Diagnose it on a real agent: print the installation actually used, list lib/ext, and separate a missing element from a missing dependency. Know the search_paths and plugin_dependency_paths route for a shared or read-only tree.
Set the convention for the fleet: is the provisioned installation immutable and shared, or unpacked per build? That choice decides whether components are copied in or discovered through properties, and who audits what ends up in lib/ext.
## The launcher decides what the classpath can be The distribution's `bin/jmeter` script does its environment setup and then runs a single line of the shape `"$JAVA_HOME/bin/java" $ARGS $JVM_ARGS $JMETER_OPTS -jar "$PRGDIR/ApacheJMeter.jar" "$@"`. That `-jar` is the whole story. When `java` is given `-jar`, it takes the application classpath from the jar's manifest and **silently ignores** the `CLASSPATH` environment variable and any `-classpath`/`-cp` you pass. This is a property of the `java` command, not of JMeter, and it applies to every program launched that way. So a build step that resolves a component jar into `~/.m2`, a Gradle cache or a `build/libs` directory has done nothing JMeter can see. The dependency graph and the JMeter installation are two separate worlds, and only the second one is consulted at run time. ## Where JMeter does look JMeter scans two directories inside its installation, and their roles are not interchangeable: - **`JMETER_HOME/lib/ext`** — JMeter *components and plugins*. Anything that contributes a test element goes here. The manual is explicit that this directory is not for utility or dependency jars. - **`JMETER_HOME/lib`** — utility and dependency jars: JDBC drivers, JMS client libraries, and whatever a plugin itself needs. Two details bite in automation. First, only files with a `.jar` extension are found — a `.zip` containing the same classes is ignored without a message. Second, `JMETER_HOME` is derived from the launcher's own location (the parent of the `bin` directory), so "the installation" means whichever tree your build step actually invoked, not whichever one a developer has at home. ## The property route, when copying is unattractive If you would rather leave the unpacked distribution pristine — because it is a shared, cached, or read-only directory on the agent — three properties move the lookup instead of the files: | Property | Adds directories of | Relationship to the default | |---|---|---| | `search_paths` | plugin/component jars | In addition to whatever is in `lib/ext` | | `plugin_dependency_paths` | dependency jars needed by plugins | The recommended one for plugin dependencies | | `user.classpath` | general utility jars | The older, blunter option | These are ordinary JMeter properties, so a build step can set them the same way it sets anything else, or write them into a properties file the run reads. The point is that JMeter must be *told*; it does not discover jars from the surrounding build. ## What this means for the build wrapper question This is precisely the work a third-party Maven or Gradle wrapper is doing for you when it looks convenient. Under the hood it must: 1. Obtain a JMeter distribution of some pinned version and unpack it into a working directory. 2. Resolve the extra component jars you declared, and copy them into that tree's `lib/ext`. 3. Resolve their dependencies and copy those into `lib` — or set the equivalent properties. 4. Assemble the `jmeter -n -t ... -l ...` command line and run it. Written by hand, that is a handful of lines in the build step and every one of them is visible. Written by a wrapper, it is a configuration block, and the copying rules become the wrapper's opinion rather than yours. Neither is wrong, but a candidate who thinks the wrapper is doing something more clever than staging files into a directory tree will be lost the first time an element is not found. ## Diagnosing it The symptom is usually a plan that loads in someone's GUI and fails in the build with an error about an unknown or missing class for a test element. The checklist is short: - Print the JMeter installation the step actually used and list its `lib/ext`. On a cached agent the tree may be older than you think. - Confirm the file is a `.jar` and is readable by the build user. - If the element loads but blows up at run time with a `NoClassDefFoundError`, the component is in `lib/ext` but its dependency is not in `lib`. - Remember that a nightly job on a reused agent may be running against a distribution some earlier build unpacked, complete with jars nobody remembers adding. The third-party Custom Thread Groups and the Plugins Manager are exactly this kind of jar: community components that must be staged into the installation, never merely declared.
- The element now loads but the run dies with NoClassDefFoundError. What did the step get wrong?The component jar reached `lib/ext` but the library it needs did not reach `lib`. JMeter separates the two roles deliberately: `lib/ext` is for JMeter components only, and utility or dependency jars belong in `lib`, or in a directory named by `plugin_dependency_paths`. Copy the dependency across and the class resolves.
- Why can setting CLASSPATH in the build step not fix this?Because `bin/jmeter` starts the JVM with `java -jar ApacheJMeter.jar`, and `java` ignores `CLASSPATH` and `-cp` whenever `-jar` is used. That is standard JVM behaviour for every program launched from a jar, so no amount of exporting the variable changes what the JMeter process sees.
saying these in an interview costs you the question
- Thinks the build's dependency graph is on JMeter's classpath
- Exports CLASSPATH and expects the plugin to load
- Puts a JDBC driver in lib/ext instead of lib
- Assumes a .zip of classes is scanned like a .jar
- Forgets a cached agent may hold an older installation tree