{"record":{"id":"823f968bda765572","repo":"flowable/flowable-engine","slug":"programmatic-error-could-not-find-method-on-clas","errorCode":null,"errorMessage":"Programmatic error: could not find method  on class ","messagePattern":"Programmatic error: could not find method  on class ","errorType":"exception","errorClass":"FlowableException","httpStatus":null,"severity":"error","filePath":"modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/el/function/AbstractFlowableVariableExpressionFunction.java","lineNumber":67,"sourceCode":"\n    public AbstractFlowableVariableExpressionFunction(String functionName) {\n        this(Collections.singletonList(functionName), functionName);\n    }\n    \n    public AbstractFlowableVariableExpressionFunction(List<String> functionNameOptions, String functionName) {\n        this.functionNamesOptions = new LinkedHashSet<>(functionNameOptions);\n        this.functionName = functionName;\n\n    }\n\n    protected Method findMethod(String functionName) {\n        Method[] methods = this.getClass().getMethods();\n        for (Method method : methods) {\n            if (Modifier.isStatic(method.getModifiers()) && method.getName().equals(functionName)) {\n                return method;\n            }\n        }\n        throw new FlowableException(\"Programmatic error: could not find method \" + functionName + \" on class \" + this.getClass());\n    }\n\n    @Override\n    public String prefix() {\n        throw new UnsupportedOperationException(\"Function has more than one prefix\");\n    }\n\n    @Override\n    public Collection<String> prefixes() {\n        return FUNCTION_PREFIXES;\n    }\n\n    @Override\n    public String localName() {\n        throw new UnsupportedOperationException(\"Function has more than one local name\");\n    }\n\n    @Override","sourceCodeStart":49,"sourceCodeEnd":85,"githubUrl":"https://github.com/flowable/flowable-engine/blob/d6d39ce1c69ff244f2d9dc6af756a9b95e865586/modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/el/function/AbstractFlowableVariableExpressionFunction.java#L49-L85","documentation":"AbstractFlowableVariableExpressionFunction resolves the static method backing a Flowable EL function (e.g. vars:get) by scanning the function class's public methods for one whose name matches the function's local name. If no matching public static method exists, the class was misconfigured/implemented incorrectly, so the engine throws this programmatic (internal) error instead of failing at expression evaluation time in a friendlier way.","triggerScenarios":"Creating a subclass of AbstractFlowableVariableExpressionFunction whose functionName/localName does not match any public static method declared on the subclass; renaming the implementing static method without updating the function's name constructor argument; calling functionMethod when the class declares the method as non-static or non-public.","commonSituations":"Writing a custom EL function class where the static method name and the name passed to the constructor diverge; copying an existing Flowable function and renaming the method but not the constructor argument; method made private/instance during refactoring.","solutions":["Ensure the subclass declares a public static method whose exact name equals the functionName passed to the constructor.","Fix mismatched names: constructor arg, static method name, and localName() must agree.","Make the implementing method public and static.","Call functionMethod only during/after proper initialization — it is an internal invariant; hitting it means the function class itself is buggy."],"exampleFix":"// before\npublic class MyVarFunction extends AbstractFlowableVariableExpressionFunction {\n    public MyVarFunction() { super(\"get\"); } // expects method 'get'\n    public static String fetch(VariableContainer c, String name) { ... } // mismatch\n}\n\n// after\npublic class MyVarFunction extends AbstractFlowableVariableExpressionFunction {\n    public MyVarFunction() { super(\"get\"); }\n    public static String get(VariableContainer c, String name) { ... }\n}","handlingStrategy":"validation","validationCode":"// at function class init time\nboolean methodExists = java.util.Arrays.stream(getClass().getMethods())\n    .anyMatch(m -> java.lang.reflect.Modifier.isStatic(m.getModifiers()) && m.getName().equals(functionName));\nif (!methodExists) {\n    throw new IllegalStateException(\"Function class \" + getClass() + \" lacks public static method \" + functionName);\n}","typeGuard":null,"tryCatchPattern":"try {\n    Method m = function.functionMethod();\n} catch (FlowableException e) {\n    throw new IllegalStateException(\"Misconfigured EL function class: \" + function.getClass(), e);\n}","preventionTips":["Keep the constructor's functionName, the public static method name, and localName(s) in sync.","Declare function methods public static, returning types compatible with EL.","Add a unit test that calls functionMethod()/localNames() for every custom function class."],"tags":["el","function","reflection","configuration"],"backgroundTag":"internal-invariant-violation","analyzedSha":"d6d39ce1c69ff244f2d9dc6af756a9b95e865586","analyzedAt":"2026-09-11T06:41:19.413Z","contentChangedAt":"2026-09-11T06:41:19.413Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}