{"record":{"id":"03e8aa9de6e66188","repo":"microg/GmsCore","slug":"only-one-extension-per-type-may-be-added","errorCode":null,"errorMessage":"Only one extension per type may be added","messagePattern":"Only one extension per type may be added","errorType":"validation","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"play-services-base/src/main/java/com/google/android/gms/auth/api/signin/GoogleSignInOptions.java","lineNumber":207,"sourceCode":"            this.serverClientId = options.serverClientId;\n            this.account = options.account;\n            this.hostedDomain = options.hostedDomain;\n            if (options.extensions != null) {\n                for (GoogleSignInOptionsExtensionParcelable extension : options.extensions) {\n                    extensionMap.put(extension.getType(), extension);\n                }\n            }\n        }\n\n        /**\n         * Specifies additional sign-in options via the given extension.\n         *\n         * @param extension A sign-in extension used to further configure API specific sign-in options. Supported values include: {@link Games.GamesOptions}.\n         */\n        @NonNull\n        public Builder addExtension(GoogleSignInOptionsExtension extension) {\n            if (this.extensionMap.containsKey(extension.getExtensionType())) {\n                throw new IllegalStateException(\"Only one extension per type may be added\");\n            }\n            List<Scope> scopes = extension.getImpliedScopes();\n            if (scopes != null) {\n                this.scopes.addAll(scopes);\n            }\n            this.extensionMap.put(extension.getExtensionType(), new GoogleSignInOptionsExtensionParcelable(extension));\n            return this;\n        }\n\n        /**\n         * Specifies that email info is requested by your application. Note that we don't recommend keying user by email address since email address might\n         * change. Keying user by ID is the preferable approach.\n         */\n        @NonNull\n        public Builder requestEmail() {\n            this.scopes.add(new Scope(Scopes.EMAIL));\n            return this;\n        }","sourceCodeStart":189,"sourceCodeEnd":225,"githubUrl":"https://github.com/microg/GmsCore/blob/157c9d86ac46c195a86c2f15ab55c84036223f95/play-services-base/src/main/java/com/google/android/gms/auth/api/signin/GoogleSignInOptions.java#L189-L225","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nbuilder.addExtension(Games.GamesOptions.DEFAULT).addExtension(gamesOptions); // IllegalStateException\n\n// after\nGoogleSignInOptions.Builder builder = new GoogleSignInOptions.Builder(...);\nbuilder.addExtension(gamesOptions != null ? gamesOptions : Games.GamesOptions.DEFAULT); // exactly once","handlingStrategy":"validation","validationCode":"private final Set<Integer> addedExtTypes = new HashSet<>();\nvoid safeAddExtension(GoogleSignInOptions.Builder b, GoogleSignInOptionsExtension ext) {\n    if (addedExtTypes.add(ext.getExtensionType())) b.addExtension(ext);\n}","typeGuard":"boolean canAddExtension(Set<Integer> seen, GoogleSignInOptionsExtension ext) {\n    return !seen.contains(ext.getExtensionType());\n}","tryCatchPattern":"try {\n    builder.addExtension(extension);\n} catch (IllegalStateException e) {\n    Log.w(TAG, \"Extension of this type already added; keeping first\", e);\n}","preventionTips":["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."],"tags":["android","google-sign-in","illegal-state","builder"],"backgroundTag":"invalid-state-transition","analyzedSha":"157c9d86ac46c195a86c2f15ab55c84036223f95","analyzedAt":"2026-09-06T17:27:33.892Z","contentChangedAt":"2026-09-06T17:27:33.892Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}