flowable/flowable-engine · error · ELException

Builder ${clazz} is missing constructor (can't pass features

Error message

Builder ${clazz} is missing constructor (can't pass features)

What it means

createTreeBuilder() checks whether a custom TreeBuilder class implements the Builder interface and, if so, requires a constructor taking Feature[] so configured features can be passed. If getConstructor(Feature[].class) yields no such constructor while features were supplied, it throws ELException 'Builder X is missing constructor (can't pass features)'.

Source

Thrown at modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/de/odysseus/el/ExpressionFactoryImpl.java:397

	 * <code>de.odysseus.el.tree.TreeBuilder</code> interface. If the property is not set, a plain
	 * <code>de.odysseus.el.tree.impl.Builder</code> is used. If the configured class is a subclass
	 * of <code>de.odysseus.el.tree.impl.Builder</code> and which provides a constructor taking an
	 * array of <code>Builder.Feature</code>, this constructor will be invoked. Otherwise, the
	 * default constructor will be used.
	 */
	protected TreeBuilder createTreeBuilder(Properties properties, Feature... features) {
		Class<?> clazz = load(TreeBuilder.class, properties);
		if (clazz == null) {
			return createDefaultTreeBuilder(features);
		}
		try {
			if (Builder.class.isAssignableFrom(clazz)) {
				Constructor<?> constructor = clazz.getConstructor(Feature[].class);
				if (constructor == null) {
					if (features == null || features.length == 0) {
						return TreeBuilder.class.cast(clazz.newInstance());
					} else {
						throw new ELException("Builder " + clazz + " is missing constructor (can't pass features)");
					}
				} else {
					return TreeBuilder.class.cast(constructor.newInstance((Object) features));
				}
			} else {
				return TreeBuilder.class.cast(clazz.newInstance());
			}
		} catch (Exception e) {
			throw new ELException("TreeBuilder " + clazz + " could not be instantiated", e);
		}
	}

	protected TreeBuilder createDefaultTreeBuilder(Feature... features) {
		return new Builder(features);
	}

	private Class<?> load(Class<?> clazz, Properties properties) {
		if (properties != null) {

View on GitHub (pinned to d6d39ce1c6)

Solutions

  1. Add a public constructor accepting Feature[] to the custom Builder implementation
  2. Construct the factory without features if the builder only supports a no-arg constructor
  3. Use the default Builder (de.odysseus.el.tree.impl.Builder) which supports features

Example fix

// before
public class MyBuilder implements Builder {
    public MyBuilder() { }
}

// after
public class MyBuilder implements Builder {
    public MyBuilder() { }
    public MyBuilder(Feature[] features) { ... }
}
Defensive patterns

Strategy: validation

Validate before calling

Class<?> c = Class.forName("com.acme.MyBuilder");
boolean hasFeatureCtor = Arrays.stream(c.getConstructors())
    .anyMatch(k -> Arrays.equals(k.getParameterTypes(), new Class[]{Feature[].class}));
if (hasFeaturesRequested && !hasFeatureCtor) {
    throw new IllegalStateException("Builder implementing Builder interface needs a public Feature[] constructor");
}

Try / catch

try {
    ExpressionFactory.newInstance(props, features);
} catch (ELException e) {
    if (e.getMessage().contains("is missing constructor")) {
        log.error("Add Feature[] constructor to custom Builder or drop features", e);
    }
    throw e;
}

Prevention

When it happens

Trigger: Constructing ExpressionFactoryImpl (e.g. ExpressionFactory.newInstance with Feature options) where the configured TreeBuilder class implements Builder but only declares a no-arg constructor, and at least one Feature is passed.

Common situations: Plugging in a custom tree builder written for an older Juel version without the Feature[] constructor; calling newInstance with features (like MethodInvocations or VarArgs) against an incompatible custom builder.

Related errors


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