stanfordnlp/CoreNLP · error · IllegalArgumentException

Multiple declarations of option

Error message

Multiple declarations of option 

What it means

ArgumentParser.fillOptionsImpl builds a map of fillable option names from @Option-annotated fields. When two fields (or an alternate name of two fields) declare the same option name, this IllegalArgumentException is thrown, naming the duplicated option and both declaring fields. It prevents ambiguous option binding at program startup.

Solutions

  1. Read the message: rename one of the two fields listed so each @Option name/alt is unique.
  2. Adjust the alt() lists to remove the colliding alternate name.
  3. If composing classes, ensure each contributes distinct option names before calling fillOptions.
  4. Add a startup self-test that calls fillOptions once to surface collisions early.

Example fix

// before
@Option(name = "verbose", alt = "v") public boolean verbose;
@Option(name = "verbose") public boolean debugMode;
// after
@Option(name = "verbose", alt = "v") public boolean verbose;
@Option(name = "debug-mode") public boolean debugMode;
Defensive patterns

Strategy: validation

Validate before calling

Set<String> seen = new HashSet<>();
for (Field f : clazz.getDeclaredFields()) {
  Option o = f.getAnnotation(Option.class);
  if (o == null) continue;
  if (!seen.add(o.name())) throw new IllegalStateException("Duplicate option: " + o.name());
}

Try / catch

try { ArgumentParser.fillOptions(opts, props); } catch (IllegalArgumentException e) { if (e.getMessage().startsWith("Multiple declarations of option")) { throw new IllegalStateException("Fix duplicate @Option in " + opts.getClass(), e); } throw e; }

Prevention

When it happens

Trigger: Two @Option fields with the same name, or two classes' fields whose alt names collide, passed to fillOptions/fillOptionsImpl via bootstrapMap — detected when canFill already contains the alt name and it isn't the same field's own name.

Common situations: Refactoring a class so two fields end up with the same option name; composing multiple option-holder classes that share an option name; renaming a field without updating alt names so an alt collides with another field's name.

Understand the failure class

Background: Conflicting config options: "cannot be used together" — configuration validation errors across open-source libraries — this error's family across 162 libraries.

Related errors


AI-assisted analysis of stanfordnlp/CoreNLP@1b7edd19c4 (2026-09-10). Data as JSON: /api/errors/22570243ecb339b9. Report an issue: GitHub.

Appendix: source

Thrown at src/edu/stanford/nlp/util/ArgumentParser.java:404

          }
          if (canFill.containsKey(name)) {
            String name1 = canFill.get(name).getDeclaringClass().getCanonicalName() + '.' + canFill.get(name).getName();
            String name2 = f.getDeclaringClass().getCanonicalName() + '.' + f.getName();
            if (!name1.equals(name2)) {
              runtimeException("Multiple declarations of option " + name + ": " + name1 + " and " + name2);
            } else {
              err("Class is in classpath multiple times: " + canFill.get(name).getDeclaringClass().getCanonicalName());
            }
          }
          canFill.put(name, f);
          required.put(name, mark);
          interner.put(name, name);
          //(add alternate names)
          if ( ! o.alt().isEmpty()) {
            for (String alt : o.alt().split(" *, *")) {
              alt = alt.toLowerCase();
              if (canFill.containsKey(alt) && !alt.equals(name))
                throw new IllegalArgumentException("Multiple declarations of option " + alt + ": " + canFill.get(alt) + " and " + f);
              canFill.put(alt, f);
              if (mark.first) required.put(alt, mark);
              interner.put(alt, name);
            }
          }
        }
      }
      //(check to ensure that something got filled, if any @Option annotation was found)
      if (someOptionFound && !someOptionFilled) {
        warn("found @Option annotations in class " + c + ", but didn't set any of them (all options were instance variables and no instance given?)");
      }
    }

    //--Fill Options
    for (Map.Entry<Object, Object> entry : options.entrySet()) {
      String rawKeyStr = entry.getKey().toString();
      String key = rawKeyStr.toLowerCase();
      // (get values)

View on GitHub (pinned to 1b7edd19c4)