{"record":{"id":"e57c443ff05a5e33","repo":"bazelbuild/bazel","slug":"default-values-for-multiple-options-are-not-allowe","errorCode":null,"errorMessage":"Default values for multiple options are not allowed - use \"null\" special value","messagePattern":"Default values for multiple options are not allowed - use \"null\" special value","errorType":"validation","errorClass":"OptionProcessorException","httpStatus":null,"severity":"error","filePath":"src/main/java/com/google/devtools/common/options/processor/OptionsClassProcessor.java","lineNumber":458,"sourceCode":"          \"Can't set an option to be both an expansion option and have implicit requirements.\");\n    }\n\n    if (isExpansion || hasImplicitRequirements) {\n      if (annotation.allowMultiple()) {\n        throw new OptionProcessorException(\n            method,\n            \"Can't set an option to accumulate multiple values and let it expand to other flags.\");\n      }\n    }\n  }\n\n  private void checkNoDefaultValueForMultipleOption(ExecutableElement method)\n      throws OptionProcessorException {\n    Option annotation = method.getAnnotation(Option.class);\n    if (annotation.allowMultiple()\n        && !annotation.defaultValue().equals(\"null\")\n        && !ImmutableList.of(\"runs_per_test\", \"flaky_test_attempts\").contains(annotation.name())) {\n      throw new OptionProcessorException(\n          method,\n          \"Default values for multiple options are not allowed - use \\\"null\\\" special value\");\n    }\n  }\n\n  // TODO(Silic0nS0ldier): Remove this allowlist once all tests have been fixed.\n  private static final ImmutableList<String> NO_OP_OPTION_ALLOWLIST =\n      ImmutableList.of(\n          \"com.google.devtools.build.lib.analysis.AnalysisCachingTest.\",\n          \"com.google.devtools.build.lib.analysis.config.BuildOptionDetailsTest.\",\n          \"com.google.devtools.build.lib.analysis.config.BuildOptionsTest.\",\n          \"com.google.devtools.build.lib.analysis.LateBoundSplitUtil.\",\n          \"com.google.devtools.build.lib.analysis.producers.BuildConfigurationKeyMapProducerTest.\",\n          \"com.google.devtools.build.lib.analysis.producers.BuildConfigurationKeyProducerTest.\",\n          \"com.google.devtools.build.lib.analysis.RequiredConfigFragmentsTest.\",\n          \"com.google.devtools.build.lib.analysis.starlark.StarlarkTransitionTest.\",\n          \"com.google.devtools.build.lib.analysis.starlark.StarlarkTransitionTest.\",\n          \"com.google.devtools.build.lib.analysis.util.ConfigurationTestCase.\",","sourceCodeStart":440,"sourceCodeEnd":476,"githubUrl":"https://github.com/bazelbuild/bazel/blob/e6e199d0601a244511b4cf18c8b2828aa73db1fd/src/main/java/com/google/devtools/common/options/processor/OptionsClassProcessor.java#L440-L476","documentation":"Bazel's options annotation processor forbids default values on options with allowMultiple = true. List-valued options start empty by construction; their defaultValue must be the special string \"null\" (meaning 'no default'). The only exceptions are the two grandfathered options named runs_per_test and flaky_test_attempts.","triggerScenarios":"Declaring @Option(allowMultiple = true, defaultValue = \"something\") where the option name is not exactly \"runs_per_test\" or \"flaky_test_attempts\". Any defaultValue string other than \"null\" triggers it.","commonSituations":"Writing a new repeatable flag and habitually filling in a default; migrating an old option to allowMultiple while keeping its previous default; misunderstanding that \"null\" is a sentinel literal, not a Java null reference.","solutions":["Set defaultValue = \"null\" (the literal string) on the allowMultiple option; the list is then initialized empty by the options parser.","If an initial non-empty value is genuinely required, implement it in the options class's converter or in code that consumes the option, not via defaultValue.","Do not rename your option to runs_per_test/flaky_test_attempts to sneak past the allowlist — those names are hardcoded exceptions pending cleanup.","Recompile to confirm."],"exampleFix":"// before\n@Option(\n  name = \"extra_actions\",\n  defaultValue = \"[]\",\n  allowMultiple = true,\n  documentationCategory = OptionDocumentationCategory.UNDOCUMENTED,\n  effectTags = {OptionEffectTag.EAGER}\n)\n// after\n@Option(\n  name = \"extra_actions\",\n  defaultValue = \"null\",\n  allowMultiple = true,\n  documentationCategory = OptionDocumentationCategory.UNDOCUMENTED,\n  effectTags = {OptionEffectTag.EAGER}\n)","handlingStrategy":"validation","validationCode":"static String checkMultipleDefault(String optionName, boolean allowMultiple, String defaultValue) {\n  if (allowMultiple && !\"null\".equals(defaultValue)\n      && !List.of(\"runs_per_test\", \"flaky_test_attempts\").contains(optionName)) {\n    throw new IllegalArgumentException(\n        \"allowMultiple option '\" + optionName + \"' must use defaultValue = \\\"null\\\"\");\n  }\n  return defaultValue;\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Remember \"null\" is a literal string sentinel meaning 'no default' for repeatable options.","If initial values are needed, populate them in consuming code, not defaultValue."],"tags":["java","bazel","annotation-processing","options","compile-time","allow-multiple","default-value"],"backgroundTag":null,"analyzedSha":"e6e199d0601a244511b4cf18c8b2828aa73db1fd","analyzedAt":"2026-08-14T10:24:27.848Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}