flowable/flowable-engine · error · ELException

error.function.notfound

error.function.notfound

Error message

error.function.notfound

What it means

Tree.bind() asked the FunctionMapper to resolve a function named in the expression and got null back: no Method is registered under that prefix/local name. JUEL throws ELException error.function.notfound naming the function image (e.g. 'my:check').

Solutions

  1. Register the missing function with the FunctionMapper / Flowable function delegates so resolveFunction returns the Method.
  2. Fix the function name/prefix typo in the expression.
  3. Verify at startup that every function used in deployed expressions resolves via the mapper.
  4. Catch ELException, parse the function image from the message, and report which function is unregistered.

Example fix

// before
// expression uses ${acct:balance(id)} but no 'acct' functions registered -> ELException
// after
functionMapper.mapFunction("acct", "balance",
    AccountFunctions.class.getMethod("balance", Long.class));
Defensive patterns

Strategy: validation

Validate before calling

Set<String> registered = loadedFunctionNames(functionMapper); // names your mapper resolves
for (String fn : extractFunctionImages(expression)) { // regex: ([\w]+):([\w]+)\(
    if (!registered.contains(fn)) throw new IllegalStateException("Unregistered EL function: " + fn);
}

Try / catch

try {
    bindings = tree.bind(fnMapper, varMapper, converter);
} catch (ELException e) {
    log.error("Unknown EL function in '{}': {}", expression, e.getMessage(), e);
    throw new ConfigurationException(e.getMessage(), e);
}

Prevention

When it happens

Trigger: An expression contains ns:name(...) but the FunctionMapper.resolveFunction returns null — the function bean/method is not registered, the prefix is wrong, or the mapper used at bind time differs from the one configured with the functions.

Common situations: Flowable custom function beans not exposed via the function delegate; typo in function name or namespace in the expression; functions available in one engine/module but used in expressions of another; classpath/config changes dropping the function registration.

Related errors


AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11). Data as JSON: /api/errors/39186dc6003f8f63. Report an issue: GitHub.

Appendix: source

Thrown at modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/de/odysseus/el/tree/Tree.java:124

	public Bindings bind(FunctionMapper fnMapper, VariableMapper varMapper, TypeConverter converter) {
		Method[] methods = null;
		if (!functions.isEmpty()) {
			if (fnMapper == null) {
				throw new ELException(LocalMessages.get("error.function.nomapper"));
			}
			methods = new Method[functions.size()];
			for (int i = 0; i < functions.size(); i++) {
				FunctionNode node = functions.get(i);
				String image = node.getName();
				Method method = null;
				int colon = image.indexOf(':');
				if (colon < 0) {
					method = fnMapper.resolveFunction("", image);
				} else {
					method = fnMapper.resolveFunction(image.substring(0, colon), image.substring(colon + 1));
				}
				if (method == null) {
					throw new ELException(LocalMessages.get("error.function.notfound", image));
				}
				if (node.isVarArgs() && method.isVarArgs()) {
					if (method.getParameterTypes().length > node.getParamCount() + 1) {
						throw new ELException(LocalMessages.get("error.function.params", image));
					}
				} else {
					if (method.getParameterTypes().length != node.getParamCount()) {
						throw new ELException(LocalMessages.get("error.function.params", image));
					}
				}
				methods[node.getIndex()] = method;
			}
		}
		ValueExpression[] expressions = null;
		if (identifiers.size() > 0) {
			expressions = new ValueExpression[identifiers.size()];
			for (int i = 0; i < identifiers.size(); i++) {
				IdentifierNode node = identifiers.get(i);

View on GitHub (pinned to d6d39ce1c6)