{"record":{"id":"08ed59cb5061ed75","repo":"bazelbuild/bazel","slug":"the-converter-type-s-must-be-a-concrete-type","errorCode":null,"errorMessage":"The converter type %s must be a concrete type","messagePattern":"The converter type (.+?) must be a concrete type","errorType":"validation","errorClass":"OptionProcessorException","httpStatus":null,"severity":"error","filePath":"src/main/java/com/google/devtools/common/options/processor/OptionsClassProcessor.java","lineNumber":658,"sourceCode":"  @Nullable\n  private Converter<?> findDefaultConverter(TypeMirror type) {\n    // According to the documentation of TypeMirror, equality check is not how one checks whether\n    // two instances reference the same type but Types.isSameType().\n    for (Map.Entry<TypeMirror, Converter<?>> entry : defaultConverters.entrySet()) {\n      if (typeUtils.isSameType(type, entry.getKey())) {\n        return entry.getValue();\n      }\n    }\n    return null;\n  }\n\n  private void checkProvidedConverter(\n      ExecutableElement method,\n      ImmutableList<TypeMirror> acceptedConverterReturnTypes,\n      TypeElement converterElement)\n      throws OptionProcessorException {\n    if (converterElement.getModifiers().contains(Modifier.ABSTRACT)) {\n      throw new OptionProcessorException(\n          method, \"The converter type %s must be a concrete type\", converterElement.asType());\n    }\n\n    DeclaredType converterType = (DeclaredType) converterElement.asType();\n    List<ExecutableElement> methodList =\n        elementUtils.getAllMembers(converterElement).stream()\n            .filter(element -> element.getKind() == ElementKind.METHOD)\n            .map(methodElement -> (ExecutableElement) methodElement)\n            .filter(methodElement -> methodElement.getSimpleName().contentEquals(\"convert\"))\n            .filter(\n                methodElement ->\n                    methodElement.getParameters().size() == 2\n                        && typeUtils.isSameType(\n                            methodElement.getParameters().get(0).asType(),\n                            elementUtils.getTypeElement(String.class.getCanonicalName()).asType())\n                        && typeUtils.isSameType(\n                            methodElement.getParameters().get(1).asType(),\n                            elementUtils.getTypeElement(Object.class.getCanonicalName()).asType()))","sourceCodeStart":640,"sourceCodeEnd":676,"githubUrl":"https://github.com/bazelbuild/bazel/blob/e6e199d0601a244511b4cf18c8b2828aa73db1fd/src/main/java/com/google/devtools/common/options/processor/OptionsClassProcessor.java#L640-L676","documentation":"When an @Option specifies a custom converter class, Bazel's options annotation processor verifies the converter is instantiable. This error fires when the supplied converter class is abstract: the processor cannot instantiate it to call convert(), so the option definition is rejected at compile time.","triggerScenarios":"@Option(..., converter = SomeAbstractConverter.class) where SomeAbstractConverter is declared abstract (or is an interface referenced as a converter).","commonSituations":"Pointing at a converter base class instead of a concrete subclass; introducing a generic converter hierarchy and wiring the base type by mistake; refactoring a converter into abstract+concrete pair and forgetting to update the @Option reference.","solutions":["Reference the concrete subclass in the converter attribute: converter = ConcreteConverter.class.","If the abstract class is the only thing that exists, implement it as a concrete class (or make it a final utility class implementing Converter directly).","Recompile to confirm."],"exampleFix":"// before\nabstract class TimeoutConverter implements Converter<Duration> { ... }\n\n@Option(\n  name = \"timeout\",\n  defaultValue = \"30s\",\n  converter = TimeoutConverter.class  // abstract -> error\n)\n// after\nfinal class TimeoutConverter implements Converter<Duration> { ... }\n\n@Option(\n  name = \"timeout\",\n  defaultValue = \"30s\",\n  converter = TimeoutConverter.class\n)","handlingStrategy":"type-guard","validationCode":"static void assertConverterConcrete(Class<? extends Converter<?>> converter) {\n  Preconditions.checkState(!Modifier.isAbstract(converter.getModifiers()),\n        \"Converter %s must be concrete (non-abstract)\", converter.getName());\n}","typeGuard":"static boolean isInstantiableConverter(Class<?> c) {\n  return Converter.class.isAssignableFrom(c)\n      && !c.isInterface()\n      && !Modifier.isAbstract(c.getModifiers());\n}","tryCatchPattern":null,"preventionTips":["Make converter classes final and concrete; avoid abstract converter bases referenced from @Option.","When introducing a converter hierarchy, update every @Option converter reference to the leaf class."],"tags":["java","bazel","annotation-processing","options","compile-time","converter","abstract-class"],"backgroundTag":null,"analyzedSha":"e6e199d0601a244511b4cf18c8b2828aa73db1fd","analyzedAt":"2026-08-14T10:24:27.848Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}