What must a compiled custom JMeter function declare for JMeter to discover it?
answer
- One class, one name, no GUI needed
- Registration beats being scanned for
- The fallback path only looks in certain directories
- One method's list is described as not optional
basics
~20 sIt must implement JMeter's Function interface, normally by extending AbstractFunction, and return its name from getReferenceKey. Register it in META-INF/services so JMeter's service lookup finds it; otherwise it is only found by a scan of lib/ext and the directories search_paths names.
solid answer
~60 sA custom function is a class implementing `org.apache.jmeter.functions.Function`, in practice by extending `AbstractFunction`. It must supply `getReferenceKey()` - the name, by convention prefixed with `__` - plus `setParameters(Collection<CompoundVariable>)`, `execute(SampleResult, Sampler)` and `getArgumentDesc()`, which returns one description string per argument and is *not* optional. Discovery is the part people miss. `Function` is annotated `@JMeterService`, so the fast path is to register the class in `META-INF/services/org.apache.jmeter.functions.Function`; JMeter's own functions do it with `@AutoService(Function.class)`. Without that entry JMeter falls back to scanning the jars on its component search path - `lib/ext` plus anything `search_paths` adds - so the jar has to sit in one of those directories or the class is never seen at all. Do not rely on the old package-naming rule: `bin/jmeter.properties` still ships `classfinder.functions.contain=.functions.` and `classfinder.functions.notContain=.gui.`, and JMeter still logs them at startup, but in 6.0.0 nothing applies them - `CompoundVariable` reads the two values only to print those notes, and the lookup it then runs hands `ClassFinder` null filters. Package name no longer decides whether a function is found; directory and registration do.
go deeper
Know that a custom function is one Java class implementing JMeter's Function interface, usually by extending AbstractFunction, and that its name comes from getReferenceKey.
Explain the four methods and where argument validation belongs, and that getArgumentDesc must return one entry per argument even if the entries are blank.
Cover discovery properly: register through META-INF/services, and know that the scanning fallback is bounded by lib/ext plus search_paths - not by the classfinder.functions.* naming rule, which 6.0.0 only logs.
Judge when a shared function jar is worth its release cycle against inline scripting, and who owns the compatibility of that jar across JMeter upgrades.
Writing a JMeter function is the smallest possible plugin: no GUI class, no test element, one class and a jar. It is also the extension point where discovery, rather than the code, is what usually goes wrong. Everything below is as of Apache JMeter 6.0.0. ## The interface `org.apache.jmeter.functions.Function` declares four methods, and `AbstractFunction` implements the plumbing around three of them: - **`getReferenceKey()`** - the function's name. The interface's own javadoc states the convention: prepend `__`, as in `__regexFunction`. This string is the key JMeter registers the class under, so it is what a plan writes. - **`setParameters(Collection<CompoundVariable>)`** - called once with the parsed arguments, and called *even when no parameters were supplied*. This is where you validate arity. `AbstractFunction` gives you `checkParameterCount`, which throws `InvalidVariableException` naming the reference key and the expected count. - **`execute(SampleResult previousResult, Sampler currentSampler)`** - produce the replacement string. The javadoc is blunt about the constraint: this method must be thread-safe, because multiple threads will be using the same object. JMeter guarantees that `setParameters` happens-before `execute`, since parameters are set on the main thread and worker threads start afterwards; if your function touches something that is not thread-safe, such as a file, you must synchronise it yourself. - **`getArgumentDesc()`** - a list of short descriptions, one per argument, used by the Function Helper dialog. The javadoc calls this list "not optional": if you have no help to write, return a list of the right length containing blank strings. ```java @AutoService(Function.class) public class Nonce extends AbstractFunction { private static final String KEY = "__nonce"; private static final List<String> DESC = List.of("Length in characters"); private static final String ALPHABET = "abcdefghijklmnopqrstuvwxyz0123456789"; private CompoundVariable lengthParam; @Override public String getReferenceKey() { return KEY; } @Override public List<String> getArgumentDesc() { return DESC; } @Override public void setParameters(Collection<CompoundVariable> params) throws InvalidVariableException { checkParameterCount(params, 1); lengthParam = params.iterator().next(); } @Override public String execute(SampleResult previousResult, Sampler currentSampler) throws InvalidVariableException { int length = Integer.parseInt(lengthParam.execute().trim()); ThreadLocalRandom rnd = ThreadLocalRandom.current(); StringBuilder out = new StringBuilder(length); for (int i = 0; i < length; i++) { out.append(ALPHABET.charAt(rnd.nextInt(ALPHABET.length()))); } return out.toString(); } } ``` ## Two ways to be found, and one of them has rules JMeter looks for functions twice over. It loads every implementation registered through Java's `ServiceLoader`, then it scans the jars on its component search path - `lib/ext` plus anything `search_paths` adds - and adds whatever the scan turns up that the service lookup did not already provide. The service route is the one to use. `Function` carries the `@JMeterService` annotation precisely to mark it as service-loadable, and JMeter's own functions declare themselves with `@AutoService(Function.class)`, which generates the `META-INF/services/org.apache.jmeter.functions.Function` file at build time. A jar that registers everything this way can also carry the manifest attribute `JMeter-Skip-Class-Scanning: true`, telling JMeter there is nothing in it worth scanning - useful once an installation carries many plugins, because the scan has to load classes to inspect them. The scan route carries a trap, though not the one the file advertises. `bin/jmeter.properties` really does ship two properties set rather than commented out: - `classfinder.functions.contain=.functions.` - `classfinder.functions.notContain=.gui.` JMeter really does echo them at startup - "Note: Function class names must contain the string: '.functions.'". At 6.0.0 nothing acts on them. `CompoundVariable`'s static initialiser reads the two values only to write those log lines, then calls the combined service-plus-scan lookup, which runs the `ServiceLoader` and then the class finder through the overload that hard-codes both name filters to `null` - so the filter's two `contains` tests are skipped outright. `com.example.jmeter.util.Nonce` is therefore found and registered, provided its jar sits in `lib/ext` or a `search_paths` directory, exposes a public no-arg constructor, and does not carry `JMeter-Skip-Class-Scanning: true`. The properties, their comment, and the manual page describing them are stale. What bounds the scan is the directory, not the package name - and a `META-INF/services` entry lifts even that, since the service lookup finds the class anywhere on the loader's classpath, `lib` included. ## Packaging and deployment 1. Build a jar containing the class and its `META-INF/services` entry. 2. Put the jar in `JMETER_HOME/lib/ext`, or in a directory named by `search_paths`. Its own dependencies go in `lib`, or a directory named by `plugin_dependency_paths`. 3. Restart JMeter. Discovery runs at startup only. 4. Confirm it registered: the function should appear in the Function Helper dialog's list, which is built from the same registry. ## Failure modes worth recognising - **The name renders literally.** An unresolved reference is not blanked out - JMeter renders the literal text of the reference, so a field containing the raw `${__nonce(8)}` text in a request is the signature of a function that never registered. - **The wrong argument count aborts the whole plan.** `checkParameterCount` throws `InvalidVariableException`, and a bad function call is caught while the test tree is compiled: the engine logs `Error occurred compiling the tree:` and returns, so no thread ever starts and there is no results file. A malformed function call is loud, not silent. - **A thread-unsafe function corrupts under load and looks fine at one thread.** Test it with the thread count you intend to run, not with one.
- Your custom function's jar is in JMETER_HOME/lib and JMeter never registers the function. Why?The scan walks only the component search path - `lib/ext` plus anything `search_paths` names - so a `lib` jar is never scanned, whatever the class is called. `lib` *is* on the loader's classpath, though, so a `META-INF/services/org.apache.jmeter.functions.Function` entry in that same jar would be picked up by the service lookup. Move the jar to `lib/ext`, or register the class as a service.
- What does the JMeter-Skip-Class-Scanning manifest attribute do for a plugin jar?It tells JMeter's class finder there is nothing in the jar to scan for, because the jar declares all its plugins through `META-INF/services`. Only the lookups that combine services with a scan honour it - the GUI's menu scan reads every jar regardless - but where it applies it saves startup time, since scanning has to load classes to inspect them, and the effect grows with the number of plugins an installation carries.
saying these in an interview costs you the question
- Thinks any class on the classpath is picked up as a function
- Returns an empty list from getArgumentDesc when there are arguments
- Assumes each thread gets its own function instance
- Forgets the double-underscore convention on the reference key
- Expects an unresolved function reference to render as empty text