grpc/grpc-java · error · IllegalStateException

Configurators are already set

Error message

Configurators are already set

What it means

ConfiguratorRegistry.setConfigurators() is a write-once API: it stores an unmodifiable snapshot of the configurators and flips a wasConfiguratorsSet flag. Any second call — even with an identical or empty list — throws IllegalStateException because reconfiguration after initialization would violate the registry's invariants.

Source

Thrown at api/src/main/java/io/grpc/ConfiguratorRegistry.java:60

  /**
   * Returns the default global instance of the configurator registry.
   */
  public static synchronized ConfiguratorRegistry getDefaultRegistry() {
    if (instance == null) {
      instance = new ConfiguratorRegistry();
    }
    return instance;
  }

  /**
   * Sets the configurators in this registry. This method can only be called once.
   *
   * @param configurators the configurators to set
   * @throws IllegalStateException if this method is called more than once
   */
  public synchronized void setConfigurators(List<? extends Configurator> configurators) {
    if (wasConfiguratorsSet) {
      throw new IllegalStateException("Configurators are already set");
    }
    this.configurators = Collections.unmodifiableList(new ArrayList<>(configurators));
    wasConfiguratorsSet = true;
  }

  /**
   * Returns a list of the configurators in this registry.
   */
  public synchronized List<Configurator> getConfigurators() {
    if (!wasConfiguratorsSet) {
      configuratorsCallCountBeforeSet++;
    }
    return configurators;
  }

  /**
   * Returns the number of times getConfigurators() was called before
   * setConfigurators() was successfully invoked.

View on GitHub (pinned to 64daddc1f3)

Solutions

  1. Call setConfigurators() exactly once per registry instance, before any read/use
  2. Wrap the call in an idempotency check or set a local boolean so retries don't re-invoke it
  3. If configurators must change, create a new registry instance instead of reconfiguring the existing one

Example fix

// before
registry.setConfigurators(configurators); // may run again on reload
// after
if (!initialized) {
  registry.setConfigurators(configurators);
  initialized = true;
}
Defensive patterns

Strategy: type-guard

Type guard

boolean canSetConfigurators(ConfiguratorRegistry registry) {
  try {
    java.lang.reflect.Field f = ConfiguratorRegistry.class.getDeclaredField("wasConfiguratorsSet");
    f.setAccessible(true);
    return !f.getBoolean(registry);
  } catch (ReflectiveOperationException e) {
    return false;
  }
}

Try / catch

try {
  registry.setConfigurators(configurators);
} catch (IllegalStateException e) {
  if ("Configurators are already set".equals(e.getMessage())) {
    // idempotent re-init: keep existing configurators
  } else throw e;
}

Prevention

When it happens

Trigger: Calling setConfigurators() twice on the same registry instance, e.g. re-running initialization code, re-invoking a setup method during retry or hot-reload, or two independent components both attempting to install their configurator lists into a shared registry.

Common situations: Application bootstrap code executed more than once (redeployment hooks, Spring bean post-processing duplicates, tests reusing a registry across cases); plugin systems where multiple modules each try to register configurators; accidental reconfiguration on config refresh.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of grpc/grpc-java@64daddc1f3 (2026-09-08). Data as JSON: /api/errors/3d21bd94d1e06691. Report an issue: GitHub.