{"record":{"id":"d85cd64f7e96258f","repo":"apache/iceberg","slug":"failed-to-refresh-token","errorCode":null,"errorMessage":"Failed to refresh token","messagePattern":"Failed to refresh token","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"core/src/main/java/org/apache/iceberg/rest/auth/OAuth2Util.java","lineNumber":512,"sourceCode":"    /**\n     * Attempt to refresh the session token using the token exchange flow.\n     *\n     * @param client a RESTClient\n     * @return interval to wait before calling refresh again, or null if no refresh is needed\n     */\n    public Pair<Integer, TimeUnit> refresh(RESTClient client) {\n      if (token() != null && config.keepRefreshed()) {\n        AtomicReference<OAuthTokenResponse> ref = new AtomicReference<>(null);\n        boolean isSuccessful =\n            Tasks.foreach(ref)\n                .suppressFailureWhenFinished()\n                .retry(tokenRefreshNumRetries)\n                .onFailure(\n                    (holder, err) -> {\n                      // attempt to refresh using the client credential instead of the parent token\n                      holder.set(refreshExpiredToken(client));\n                      if (holder.get() == null) {\n                        LOG.warn(\"Failed to refresh token\", err);\n                      }\n                    })\n                .exponentialBackoff(\n                    COMMIT_MIN_RETRY_WAIT_MS_DEFAULT,\n                    COMMIT_MAX_RETRY_WAIT_MS_DEFAULT,\n                    COMMIT_TOTAL_RETRY_TIME_MS_DEFAULT,\n                    2.0 /* exponential */)\n                .run(holder -> holder.set(refreshCurrentToken(client)));\n\n        if (!isSuccessful || ref.get() == null) {\n          return null;\n        }\n\n        OAuthTokenResponse response = ref.get();\n        this.config =\n            AuthConfig.builder()\n                .from(config())\n                .token(response.token())","sourceCodeStart":494,"sourceCodeEnd":530,"githubUrl":"https://github.com/apache/iceberg/blob/86d9c8fc543e7c56c9f624eb725f76c9baff9570/core/src/main/java/org/apache/iceberg/rest/auth/OAuth2Util.java#L494-L530","documentation":"OAuth2Util.refresh() retried refreshing the OAuth2 token via the token endpoint and all attempts failed, so the new token holder stayed null and a warning is logged. The client cannot obtain a valid access token, so subsequent authenticated REST catalog requests will fail with 401.","triggerScenarios":"The token endpoint returns an error on every retry: expired/revoked refresh token that cannot be exchanged, wrong client credentials, network failure to the auth server, or the auth server rejecting the grant (e.g. token exchanged from a parent context token that is invalid).","commonSituations":"Expired refresh token after long downtime; rotated client secrets not updated in catalog properties; auth server outage or 5xx; clock skew causing premature expiry; refresh token single-use and consumed by another process.","solutions":["Re-obtain fresh credentials: fix client-credentials (client-id/client-secret) or refresh token in catalog properties and restart","Verify network/DNS/proxy connectivity to the token endpoint and that the OAuth2 server URL is correct","Check auth server logs/auditing for why the grant is rejected (invalid_grant, revoked token)","Increase tokenRefreshNumRetries / retry window if the failure is transient","Shorten session usage time so tokens are used before expiry; ensure a single token-refresh owner per process"],"exampleFix":"// before\n.loadCatalog(\"rest\", Map.of(\"uri\", uri, \"credential\", \"user:wrong-secret\"))\n// after\n.loadCatalog(\"rest\", Map.of(\"uri\", uri, \"credential\", \"user:correct-secret\"))","handlingStrategy":"retry","validationCode":"// Validate credential shape and endpoint before use\nPreconditions.checkArgument(credential.contains(\":\"), \"credential must be client-id:client-secret\");\nnew URL(oauth2ServerUri.toString()).toURI(); // reachable, well-formed","typeGuard":null,"tryCatchPattern":"try {\n  OAuth2Util.refresh(session);\n} catch (RuntimeException e) {\n  // re-authenticate from scratch with fresh credentials, not the stale token\n  session = catalog.newSessionWithFreshCredentials();\n}","preventionTips":["Rotate and verify client secrets in catalog properties before deployment","Monitor refresh-failure warnings and alert before tokens fully expire","Ensure only one process consumes a single-use refresh token"],"tags":["oauth2","authentication","token-refresh","retry"],"backgroundTag":"oauth-token-exchange-failed","analyzedSha":"86d9c8fc543e7c56c9f624eb725f76c9baff9570","analyzedAt":"2026-09-12T00:46:39.097Z","contentChangedAt":"2026-09-12T00:46:39.097Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}