{"record":{"id":"aa68c520e975bd82","repo":"apache/skywalking","slug":"malcontextfunction-list-parameters-must-b","errorCode":null,"errorMessage":"@MALContextFunction {}.{}() List parameters must be List<String>, got {}","messagePattern":"@MALContextFunction (.+?)\\.(.+?)\\(\\) List parameters must be List<String>, got (.+?)","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"oap-server/analyzer/meter-analyzer/src/main/java/org/apache/skywalking/oap/meter/analyzer/v2/compiler/rt/MalExtensionRegistry.java","lineNumber":106,"sourceCode":"            }\n            if (!SampleFamily.class.isAssignableFrom(m.getReturnType())) {\n                throw new IllegalArgumentException(\n                    \"@MALContextFunction \" + ext.getClass().getSimpleName()\n                        + \".\" + m.getName()\n                        + \"() return type must be SampleFamily\");\n            }\n            final Class<?>[] extraParamTypes = new Class<?>[paramTypes.length - 1];\n            System.arraycopy(paramTypes, 1, extraParamTypes, 0, extraParamTypes.length);\n            // Validate List params use List<String> (the only List type MAL supports)\n            final Type[] genericTypes = m.getGenericParameterTypes();\n            for (int i = 1; i < genericTypes.length; i++) {\n                if (List.class.isAssignableFrom(extraParamTypes[i - 1])\n                        && genericTypes[i] instanceof ParameterizedType) {\n                    final Type elementType =\n                        ((ParameterizedType) genericTypes[i])\n                            .getActualTypeArguments()[0];\n                    if (!String.class.equals(elementType)) {\n                        throw new IllegalArgumentException(\n                            \"@MALContextFunction \" + ext.getClass().getSimpleName()\n                                + \".\" + m.getName()\n                                + \"() List parameters must be List<String>, got \"\n                                + genericTypes[i]);\n                    }\n                }\n            }\n            final String methodName = m.getName();\n            if (methods.containsKey(methodName)) {\n                throw new IllegalArgumentException(\n                    \"Duplicate @MALContextFunction name '\" + methodName\n                        + \"' in namespace '\" + namespace\n                        + \"' from \" + ext.getClass().getName());\n            }\n            final String fqcn = m.getDeclaringClass().getName();\n            methods.put(methodName,\n                        new ExtensionMethod(fqcn, methodName, extraParamTypes));\n            log.info(\"Registered MAL extension function {}::{}() -> {}.{}()\",","sourceCodeStart":88,"sourceCodeEnd":124,"githubUrl":"https://github.com/apache/skywalking/blob/102af09b4a56064e22050dded10e2c52e490d040/oap-server/analyzer/meter-analyzer/src/main/java/org/apache/skywalking/oap/meter/analyzer/v2/compiler/rt/MalExtensionRegistry.java#L88-L124","documentation":"SPI validation error from MalExtensionRegistry: a @MALContextFunction method declares a List parameter whose generic element type is not String (e.g. List<Double>, List<Object>, or a raw/generic-wildcard type whose actual type argument is not exactly String.class). List<String> is the only list type the MAL argument codegen supports (StringListArgument emits Arrays.asList(new String[]{...})), so anything else is rejected at registration.","triggerScenarios":"A method like 'f(SampleFamily, List<Double> buckets)' — the registry inspects m.getGenericParameterTypes(), finds a ParameterizedType for a List param, and the first actual type argument is not String. Note: a raw List (no generic type) slips past this check but then fails argument codegen differently — declare List<String> explicitly.","commonSituations":"Wanting numeric bucket lists for histogram-like extensions; using raw List from older Java code; wildcard List<? extends String> also fails the String.class.equals check.","solutions":["Declare the parameter as exactly List<String>","If numbers are needed, take a String list and parse each element inside the method (Double.parseDouble), or take a double/int scalar","Rebuild the extension jar and restart OAP"],"exampleFix":"// before\n@MALContextFunction\npublic static SampleFamily f(SampleFamily sf, List<Double> buckets) { ... }\n\n// after\n@MALContextFunction\npublic static SampleFamily f(SampleFamily sf, List<String> buckets) {\n    List<Double> parsed = buckets.stream().map(Double::parseDouble).collect(java.util.stream.Collectors.toList());\n    ...\n}","handlingStrategy":"validation","validationCode":"Type[] gpts = m.getGenericParameterTypes();\nfor (int i = 1; i < gpts.length; i++) {\n    if (gpts[i] instanceof ParameterizedType\n            && ((ParameterizedType) gpts[i]).getRawType() == List.class\n            && ((ParameterizedType) gpts[i]).getActualTypeArguments()[0] != String.class) {\n        throw new IllegalArgumentException(\"List params must be List<String>: \" + m);\n    }\n}","typeGuard":null,"tryCatchPattern":"startup-time; fix the generic signature and redeploy","preventionTips":["Only List<String> is supported; parse numerics inside the method","Avoid raw List and wildcard generics on annotated methods"],"tags":["mal","extension","spi","generics","startup"],"backgroundTag":null,"analyzedSha":"102af09b4a56064e22050dded10e2c52e490d040","analyzedAt":"2026-08-14T10:47:52.647Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}