{"record":{"id":"6cc2d24f08547dd2","repo":"apache/hadoop","slug":"unable-to-obtain-access-token-from-credential","errorCode":null,"errorMessage":"Unable to obtain access token from credential","messagePattern":"Unable to obtain access token from credential","errorType":"exception","errorClass":"IOException","httpStatus":null,"severity":"error","filePath":"hadoop-hdfs-project/hadoop-hdfs-client/src/main/java/org/apache/hadoop/hdfs/web/oauth2/CredentialBasedAccessTokenProvider.java","lineNumber":137,"sourceCode":"      httpPost.setEntity(new UrlEncodedFormEntity(pairs, StandardCharsets.UTF_8));\n      httpPost.setHeader(HttpHeaders.CONTENT_TYPE, URLENCODED);\n      try (CloseableHttpResponse response = client.execute(httpPost)) {\n        final int statusCode = response.getStatusLine().getStatusCode();\n        if (statusCode != HttpStatus.SC_OK) {\n          throw new IllegalArgumentException(\n              \"Received invalid http response: \" + statusCode + \", text = \" +\n                  EntityUtils.toString(response.getEntity()));\n        }\n        Map<?, ?> responseBody = JsonSerialization.mapReader().readValue(\n            EntityUtils.toString(response.getEntity()));\n\n        String newExpiresIn = responseBody.get(EXPIRES_IN).toString();\n        timer.setExpiresIn(newExpiresIn);\n\n        accessToken = responseBody.get(ACCESS_TOKEN).toString();\n      }\n    } catch (RuntimeException e) {\n      throw new IOException(\"Unable to obtain access token from credential\", e);\n    } catch (Exception e) {\n      throw new IOException(\"Unable to obtain access token from credential\", e);\n    }\n  }\n}\n","sourceCodeStart":119,"sourceCodeEnd":143,"githubUrl":"https://github.com/apache/hadoop/blob/2add9630210752f88ceb1bb74eb65e37bf41da8e/hadoop-hdfs-project/hadoop-hdfs-client/src/main/java/org/apache/hadoop/hdfs/web/oauth2/CredentialBasedAccessTokenProvider.java#L119-L143","documentation":"CredentialBasedAccessTokenProvider.refresh wraps any RuntimeException from the token exchange into IOException('Unable to obtain access token from credential') with the cause attached. It is the client-credentials counterpart of the refresh-token provider's wrapper: the HTTP call succeeded or failed, but a RuntimeException (NPE on a missing access_token/expires_in field, IllegalStateException from the timer on malformed expires_in, JSON mapping error) escaped before accessToken was set.","triggerScenarios":"refresh() where response handling blows up: 200 response whose JSON lacks access_token or expires_in (NPE at responseBody.get(...).toString()), malformed expires_in tripping timer.setExpiresIn, or unexpected response shape from the IdP.","commonSituations":"IdP changes its token response schema; endpoints returning error objects with HTTP 200; test stubs returning incomplete payloads; region/tenant URL changes altering response fields.","solutions":["Unwrap e.getCause() to identify the failing step (null field vs timer validation vs JSON parse)","Capture the actual token endpoint response with curl using the same client id/credential and compare against the expected access_token/expires_in shape","If the IdP legitimately omits expires_in, switch to a provider implementation or endpoint variant that returns it","Add a one-time integration test that performs the exchange during deployment so schema drift is caught early"],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  return fs.open(p);\n} catch (IOException e) {\n  if (\"Unable to obtain access token from credential\".equals(e.getMessage())\n      && isTransient(e.getCause())) {\n    return retryWithBackoff(() -> fs.open(p));\n  }\n  throw e;\n}","preventionTips":["Pin the expected token response schema in an integration test against the IdP","Log e.getCause() fully — NPE on a missing field points at response-shape drift","Prefer IdP endpoints that return both access_token and expires_in"],"tags":["oauth2","webhdfs","response-parsing","client-credentials"],"backgroundTag":"oauth2-token-refresh-failed","analyzedSha":"2add9630210752f88ceb1bb74eb65e37bf41da8e","analyzedAt":"2026-08-22T19:55:07.957Z","schemaVersion":2},"datasetVersion":"2026-08-22T20:17:22.307Z"}