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
- Call addExtension at most once per extension type per builder; check your builder construction path for duplicate calls.
- Create a new GoogleSignInOptions.Builder instead of reusing one that already has the extension.
- Track added extension types (or wrap addExtension in a contains-type check against your own set) before adding.
- 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
- Build GoogleSignInOptions in a single function with one addExtension call per type.
- Never reuse a Builder across sign-in flows.
- Deduplicate option sets before merging.
- Add an assertion/log when the same extension type is added twice in debug builds.
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
- deleteAll=true but keys are provided
- Missing required properties: token
- Missing required properties: scopes
- Missing required properties: account
- Cannot add twice the same OnSwitchChangeListener
AI-assisted analysis of microg/GmsCore@157c9d86ac (2026-09-06).
Data as JSON: /api/errors/03e8aa9de6e66188.
Report an issue: GitHub.