{"record":{"id":"254bb6537adb3ca6","repo":"apache/beam","slug":"unable-to-infer-sql-type-from-type-variable-this-usually","errorCode":null,"errorMessage":"Unable to infer SQL type from type variable . This usually means you are trying to use a generic type whose type information is not known at runtime. You can wrap your CombineFn into typed subclass by 'new TypedCombineFnDelegate<...>(combineFn) {}'","messagePattern":"Unable to infer SQL type from type variable \\. This usually means you are trying to use a generic type whose type information is not known at runtime\\. You can wrap your CombineFn into typed subclass by 'new TypedCombineFnDelegate<\\.\\.\\.>\\(combineFn\\) (.+?)'","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"sdks/java/extensions/sql/src/main/java/org/apache/beam/sdk/extensions/sql/impl/UdafImpl.java","lineNumber":75,"sourceCode":"    List<FunctionParameter> para = new ArrayList<>();\n    para.add(\n        new FunctionParameter() {\n          @Override\n          public int getOrdinal() {\n            return 0; // up to one parameter is supported in UDAF.\n          }\n\n          @Override\n          public String getName() {\n            // not used as Beam SQL uses its own execution engine\n            return null;\n          }\n\n          @Override\n          public RelDataType getType(RelDataTypeFactory typeFactory) {\n            Type inputType = getInputType();\n            if (inputType instanceof TypeVariable) {\n              throw new IllegalArgumentException(\n                  \"Unable to infer SQL type from type variable \"\n                      + inputType\n                      + \". This usually means you are trying to use a generic type whose type information \"\n                      + \"is not known at runtime. You can wrap your CombineFn into typed subclass\"\n                      + \" by 'new TypedCombineFnDelegate<...>(combineFn) {}'\");\n            }\n            return CalciteUtils.sqlTypeWithAutoCast(typeFactory, inputType);\n          }\n\n          @Override\n          public boolean isOptional() {\n            // not used as Beam SQL uses its own execution engine\n            return false;\n          }\n        });\n    return para;\n  }\n","sourceCodeStart":57,"sourceCodeEnd":93,"githubUrl":"https://github.com/apache/beam/blob/12126d8942aaf848030c478b4c6a28c6af861c66/sdks/java/extensions/sql/src/main/java/org/apache/beam/sdk/extensions/sql/impl/UdafImpl.java#L57-L93","documentation":"Beam SQL cannot map a UDAF's input type to a SQL type when the CombineFn's generic type is a TypeVariable (unknown at runtime due to Java type erasure). Calcite needs a concrete RelDataType, and a raw generic CombineFn gives it nothing to infer from. The library throws to force you to supply a typed subclass (TypedCombineFnDelegate) that captures the type parameters in the anonymous class's superclass.","triggerScenarios":"Registering a UDAF via UdfUtils/ReflectiveSchema whose CombineFn is a generic class or was created through a generic method, so getInputType() returns a TypeVariable instead of a concrete Class/ParameterizedType.","commonSituations":"Defining a CombineFn inside a generic helper method or generic class and passing it to Beam SQL UDAF registration; using a lambda or raw CombineFn reference whose type parameters were erased; registering the same generic CombineFn for multiple SQL types.","solutions":["Wrap the CombineFn in a typed anonymous subclass: new TypedCombineFnDelegate<InputT,AccumT,OutputT>(combineFn) {} so the concrete type parameters are preserved in the superclass signature","Define the CombineFn as a concrete (non-generic) named or anonymous subclass with literal type parameters instead of passing a raw generic instance","Check the class with clazz.getGenericSuperclass() instanceof ParameterizedType before registering it as a UDAF"],"exampleFix":"// before\nCombineFn<Long, long[], Long> sumFn = new SumLongFn(); // raw/generic instance\nsqlEnv.registerUdaf(\"SUM_LONG\", sumFn);\n// after\nsqlEnv.registerUdaf(\"SUM_LONG\", new TypedCombineFnDelegate<Long, long[], Long>(sumFn) {});","handlingStrategy":"type-guard","validationCode":"java.util.reflect.Type t = getInputType();\nif (t instanceof TypeVariable) throw new IllegalArgumentException(\"Wrap in TypedCombineFnDelegate<...>(fn) {}\");","typeGuard":"static boolean hasConcreteType(CombineFn<?, ?, ?> fn) {\n  return fn.getClass().getGenericSuperclass() instanceof ParameterizedType\n      && Arrays.stream(fn.getClass().getSuperclass().getTypeParameters())\n          .noneMatch(p -> p instanceof TypeVariable);\n}","tryCatchPattern":"try {\n  sqlEnv.registerUdaf(name, combineFn);\n} catch (IllegalArgumentException e) {\n  if (e.getMessage().contains(\"Unable to infer SQL type\")) {\n    sqlEnv.registerUdaf(name, new TypedCombineFnDelegate<I, A, O>(combineFn) {});\n  } else throw e;\n}","preventionTips":["Always register CombineFns via TypedCombineFnDelegate<...>(fn) {} or concrete subclasses","Avoid passing CombineFns created inside generic methods/classes directly to Beam SQL","Unit-test UDAF registration at startup so erasure failures surface early"],"tags":["java","beam-sql","generics","type-erasure","udaf"],"backgroundTag":"type-mismatch","analyzedSha":"12126d8942aaf848030c478b4c6a28c6af861c66","analyzedAt":"2026-09-13T01:50:10.254Z","contentChangedAt":"2026-09-13T01:50:10.254Z","schemaVersion":2},"datasetVersion":"2026-09-14T21:17:11.552Z"}