apache/hadoop · error · DelegationTokenIOException

Delegation token is wrong class; expected a token identifier

Error message

Delegation token is wrong class; expected a token identifier of type {expectedClass} but got {identifierClass} and kind {kind}

What it means

When an S3A delegation token is bound, AbstractDelegationTokenBinding.convertTokenIdentifier requires the decoded token identifier's class to be exactly the binding's expected class (stricter than instanceof, rejecting subclasses too). A mismatch means a token issued by a different delegation-token binding is being fed to this one, so it throws DelegationTokenIOException with the TOKEN_WRONG_CLASS prefix showing expected vs actual class and the token kind.

Source

Thrown at hadoop-tools/hadoop-aws/src/main/java/org/apache/hadoop/fs/s3a/auth/delegation/AbstractDelegationTokenBinding.java:190

      Optional<RoleModel.Policy> policy,
      EncryptionSecrets encryptionSecrets,
      Text renewer) throws IOException;

  /**
   * Verify that a token identifier is of a specific class.
   * This will reject subclasses (i.e. it is stricter than
   * {@code instanceof}, then cast it to that type.
   * @param <T> type of S3A delegation ttoken identifier.
   * @param identifier identifier to validate
   * @param expectedClass class of the expected token identifier.
   * @return token identifier.
   * @throws DelegationTokenIOException If the wrong class was found.
   */
  protected <T extends AbstractS3ATokenIdentifier> T convertTokenIdentifier(
      final AbstractS3ATokenIdentifier identifier,
      final Class<T> expectedClass) throws DelegationTokenIOException {
    if (!identifier.getClass().equals(expectedClass)) {
      throw new DelegationTokenIOException(
          DelegationTokenIOException.TOKEN_WRONG_CLASS
              + "; expected a token identifier of type "
              + expectedClass
              + " but got "
              + identifier.getClass()
              + " and kind " + identifier.getKind());
    }
    return (T) identifier;
  }

  /**
   * Deploy, returning the binding information.
   * The base implementation calls
   *
   * @param retrievedIdentifier any identifier -null if deployed unbonded.
   * @return binding information
   * @throws IOException any failure.
   */

View on GitHub (pinned to 2add963021)

Solutions

  1. Remove the stale token from the user's credentials (cancel it or start the job with fresh Credentials) so the filesystem rebinds or deploys unbonded
  2. Keep fs.s3a.delegation.token.binding identical on the issuing service and every consuming job
  3. Do not reuse persisted credential files across binding changes; re-fetch tokens after config changes

Example fix

# before: credentials file holds a token from the old binding
hadoop --loglevel INFO fs -Dfs.s3a.delegation.token.binding=org.apache.hadoop.fs.s3a.auth.delegation.S3ATokenBinding -put local dest

# after: drop stale tokens for the s3a service, then re-fetch
# (cancel the old token with its renewer, or delete/recreate the credentials file)
hadoop credential list
hadoop fetchdt -rename s3a://bucket -- renewer
Defensive patterns

Strategy: type-guard

Validate before calling

Token<?> t = ugi.getCredentials().getToken(new Text("s3a://bucket"));
if (t != null && !expectedKind.equals(t.getKind())) {
  ugi.getCredentials().removeToken(t.getService()); // drop token from wrong binding
  LOG.warn("Dropped token kind {} (expected {})", t.getKind(), expectedKind);
}

Type guard

static boolean tokenMatchesBinding(Token<?> t, Text expectedKind,
    Class<? extends AbstractS3ATokenIdentifier> expectedClass) {
  return t != null
      && expectedKind.equals(t.getKind())
      && expectedClass.equals(t.decodeIdentifier().getClass()); // exact class, like the binding
}

Try / catch

try {
  tokens.bindToDelegationToken(token);
} catch (DelegationTokenIOException e) {
  if (e.getMessage().contains(DelegationTokenIOException.TOKEN_WRONG_CLASS)) {
    // stale token from another binding: discard and rebind/deploy unbonded
    credentials.removeToken(token.getService());
  } else {
    throw e;
  }
}

Prevention

When it happens

Trigger: The filesystem is configured with binding X (e.g. S3ATokenBinding expecting S3ATokenIdentifier) but the user's credentials contain a token for the same s3a:// URI whose identifier class belongs to binding Y (e.g. SessionTokenBinding's SessionTokenIdentifier). Occurs during bindToDelegationToken when deserializeToken converts the identifier.

Common situations: fs.s3a.delegation.token.binding changed between the run that fetched the token and the run that consumes it; tokens persisted in a credentials file across a binding change; mixed clusters where one service issues session tokens and another expects full-credential tokens.

Related errors


AI-assisted analysis of apache/hadoop@2add963021 (2026-08-22). Data as JSON: /api/errors/7fc5009ecdba77f4. Report an issue: GitHub.