microg/GmsCore · error · IllegalStateException

Only one extension per type may be added

Error message

Only one extension per type may be added

What it means

GoogleSignInOptions.Builder.addExtension throws this IllegalStateException when an extension of the same extension type (e.g. Games.GamesOptions) is added twice. Each sign-in option type may only appear once because the options are stored in a map keyed by extension type.

Source

Thrown at play-services-base/src/main/java/com/google/android/gms/auth/api/signin/GoogleSignInOptions.java:207

            this.serverClientId = options.serverClientId;
            this.account = options.account;
            this.hostedDomain = options.hostedDomain;
            if (options.extensions != null) {
                for (GoogleSignInOptionsExtensionParcelable extension : options.extensions) {
                    extensionMap.put(extension.getType(), extension);
                }
            }
        }

        /**
         * Specifies additional sign-in options via the given extension.
         *
         * @param extension A sign-in extension used to further configure API specific sign-in options. Supported values include: {@link Games.GamesOptions}.
         */
        @NonNull
        public Builder addExtension(GoogleSignInOptionsExtension extension) {
            if (this.extensionMap.containsKey(extension.getExtensionType())) {
                throw new IllegalStateException("Only one extension per type may be added");
            }
            List<Scope> scopes = extension.getImpliedScopes();
            if (scopes != null) {
                this.scopes.addAll(scopes);
            }
            this.extensionMap.put(extension.getExtensionType(), new GoogleSignInOptionsExtensionParcelable(extension));
            return this;
        }

        /**
         * Specifies that email info is requested by your application. Note that we don't recommend keying user by email address since email address might
         * change. Keying user by ID is the preferable approach.
         */
        @NonNull
        public Builder requestEmail() {
            this.scopes.add(new Scope(Scopes.EMAIL));
            return this;
        }

View on GitHub (pinned to 157c9d86ac)

Solutions

  1. Call addExtension at most once per extension type per builder; check your builder construction path for duplicate calls.
  2. Create a new GoogleSignInOptions.Builder instead of reusing one that already has the extension.
  3. Track added extension types (or wrap addExtension in a contains-type check against your own set) before adding.
  4. If custom merging is needed, replace rather than append: rebuild the extension options into a single GoogleSignInOptionsExtension instance.

Example fix

// before
builder.addExtension(Games.GamesOptions.DEFAULT).addExtension(gamesOptions); // IllegalStateException

// after
GoogleSignInOptions.Builder builder = new GoogleSignInOptions.Builder(...);
builder.addExtension(gamesOptions != null ? gamesOptions : Games.GamesOptions.DEFAULT); // exactly once
Defensive patterns

Strategy: validation

Validate before calling

private final Set<Integer> addedExtTypes = new HashSet<>();
void safeAddExtension(GoogleSignInOptions.Builder b, GoogleSignInOptionsExtension ext) {
    if (addedExtTypes.add(ext.getExtensionType())) b.addExtension(ext);
}

Type guard

boolean canAddExtension(Set<Integer> seen, GoogleSignInOptionsExtension ext) {
    return !seen.contains(ext.getExtensionType());
}

Try / catch

try {
    builder.addExtension(extension);
} catch (IllegalStateException e) {
    Log.w(TAG, "Extension of this type already added; keeping first", e);
}

Prevention

When it happens

Trigger: Calling addExtension(new Games.GamesOptions.Builder()...) twice on the same GoogleSignInOptions.Builder; building options in a loop or a reused builder where the same extension is appended on each iteration.

Common situations: Reusing a single Builder across multiple sign-in configurations; conditional code paths that each add the Games extension; merging default and custom option sets that both contain the extension.

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 microg/GmsCore@157c9d86ac (2026-09-06). Data as JSON: /api/errors/03e8aa9de6e66188. Report an issue: GitHub.