bazelbuild/bazel · error · OptionProcessorException

Options with metadata tag DEPRECATED must be annotated with

Error message

Options with metadata tag DEPRECATED must be annotated with @Deprecated.

What it means

Bazel's options annotation processor keeps the DEPRECATED metadata tag and the Java @Deprecated annotation in lockstep. This error fires when an @Option method declares OptionMetadataTag.DEPRECATED in metadataTags but is not annotated with java.lang.@Deprecated.

Source

Thrown at src/main/java/com/google/devtools/common/options/processor/OptionsClassProcessor.java:527

    if (effectTags.contains(OptionEffectTag.NO_OP)
        && !metadataTags.contains(OptionMetadataTag.HIDDEN)
        && !metadataTags.contains(OptionMetadataTag.INTERNAL)
        && !hasDeprecatedAnnotation) {
      // Allowlist for tests - these are in the process of being fixed.
      String enclosingClassName = method.getEnclosingElement().toString();
      boolean allowlisted =
          NO_OP_OPTION_ALLOWLIST.stream().anyMatch(enclosingClassName::startsWith);
      if (!allowlisted) {
        throw new OptionProcessorException(
            method,
            "No-op options must be annotated with @Deprecated, or have metadata tag HIDDEN or"
                + " INTERNAL. Alternatively add %s to the allowlist.",
            enclosingClassName);
      }
    }

    if (hasDeprecatedMetadataTag && !hasDeprecatedAnnotation) {
      throw new OptionProcessorException(
          method, "Options with metadata tag DEPRECATED must be annotated with @Deprecated.");
    }
    if (hasDeprecatedAnnotation && !hasDeprecatedMetadataTag) {
      throw new OptionProcessorException(
          method, "Options annotated with @Deprecated must have metadata tag DEPRECATED.");
    }
  }

  private void checkConverter(ExecutableElement method) throws OptionProcessorException {
    TypeMirror optionType = method.getReturnType();
    Option annotation = method.getAnnotation(Option.class);
    ImmutableList<TypeMirror> acceptedConverterReturnTypes =
        getAcceptedConverterReturnTypes(method);

    // For simple, static expansions, don't accept non-Void types.
    if (annotation.expansion().length != 0
        && !typeUtils.isSameType(
            optionType, elementUtils.getTypeElement(Void.class.getCanonicalName()).asType())) {

View on GitHub (pinned to e6e199d060)

Solutions

  1. Add @Deprecated directly to the flagged @Option method (its paired check will then also pass).
  2. If the option is not actually deprecated, remove OptionMetadataTag.DEPRECATED from metadataTags instead.
  3. Recompile to confirm.

Example fix

// before
@Option(
  name = "old_way",
  defaultValue = "true",
  effectTags = {OptionEffectTag.NO_OP},
  metadataTags = {OptionMetadataTag.DEPRECATED}
)
// after
@Deprecated
@Option(
  name = "old_way",
  defaultValue = "true",
  effectTags = {OptionEffectTag.NO_OP},
  metadataTags = {OptionMetadataTag.DEPRECATED}
)
Defensive patterns

Strategy: validation

Validate before calling

static void checkDeprecatedTagMatchesAnnotation(boolean hasDeprecatedTag,
                                                 boolean hasDeprecatedAnnotation) {
  Preconditions.checkState(!hasDeprecatedTag || hasDeprecatedAnnotation,
      "metadataTags DEPRECATED requires the @Deprecated annotation");
}

Prevention

When it happens

Trigger: @Option(..., metadataTags = {OptionMetadataTag.DEPRECATED}) on a method without @Deprecated.

Common situations: Marking a flag deprecated in Bazel's generated documentation but forgetting the language-level annotation; copying an option definition that includes the tag; tooling or templates that add metadataTags automatically.

Related errors


AI-assisted analysis of bazelbuild/bazel@e6e199d060 (2026-08-14). Data as JSON: /api/errors/088efdb05301f26b. Report an issue: GitHub.