light-mode-image
Learn
Management & OperationsMigration Guides

Android Holder SDK v7.0.0 Migration Guide

A comprehensive guide to migrating to Android Holder SDK v7.0.0, covering breaking changes, new features, and step-by-step migration instructions.

Overview

This guide provides a comprehensive overview of the changes introduced in the Android Holder SDK v7.0.0, including breaking changes, new features, and migration steps.

This release focuses on strengthening trust at issuance, improving predictability in credential handling, and increasing consistency across platforms. It introduces optional SDK Tethering to connect the SDK to your MATTR VII tenant, adds optional Wallet Attestation support so issuers can verify wallet integrity before issuing credentials, and strengthens validation and resilience in credential flows.

SDK Tethering (and Wallet Attestation, which builds on it) is optional in this release. You opt in by passing a platformConfiguration when initializing the SDK. If you omit it, the SDK skips registration, tethering, and wallet attestation, and existing integrations continue to work. We expect to make SDK Tethering required in an upcoming release, so we recommend adopting it now to prepare.

Key Features

  • SDK Tethering (optional): The Android Holder SDK can now be tethered to a MATTR VII tenant, tying each SDK/app instance to your tenant. This allows you to view details about registered and active app instances directly from your tenant for operational insights. SDK Tethering also establishes a remote management channel that we expect to extend in the future with additional features, such as remote syncing of trusted issuer lists and eventing. Tethering is enabled by providing a platformConfiguration at initialization.
  • Wallet Attestation support (optional): Building on the SDK Tethering channel, the Android Holder SDK now supports Wallet Attestation, allowing your holder application to prove to issuers that it is a trusted wallet before credentials are issued. When an issuer requires it, the SDK uses the tethering connection to obtain wallet attestation tokens from your MATTR VII tenant. Wallet Attestation requires SDK Tethering to be configured.
  • Stronger application identity during issuance: Pre-authorized credential issuance flows now pass the application's client_id, ensuring the holder is accurately represented when interacting with issuers. This improves compatibility with issuers applying stricter controls.
  • More predictable credential retrieval results: Credential retrieval responses are now more structured and deterministic, explicitly identifying success or failure with guaranteed fields per result type.
  • Stronger validation in presentation flows: Stricter JWT request validation, state size validation, and certificate chain validation improve how the SDK handles malformed or incomplete requests.
  • Improved resilience in issuance flows: Oversized credential payload validation prevents failure modes that could lead to degraded user experiences.
  • General stability and performance improvements: Multiple refinements reduce integration friction, increase consistency, and improve overall reliability.

Breaking Changes

This section outlines the breaking changes introduced in v7.0.0 that require updates to your existing implementation:

#ChangeImpact
1UserAuthenticationConfiguration now takes a userAuthenticationType parameterConfigure biometric authentication behavior. initialize may throw HolderException.UserAuthenticationNotSupportedException if you use BiometryCurrentSet with OnInitialize.
2Pre-authorized code flow now uses the application's client_id instead of a default identifierEnsure your application has a valid configured client_id and issuers recognize it.
3Credential retrieval result shape updated to explicitly identify success or failure with guaranteed fieldsUpdate result parsing logic to use success/failure branching.
4OfferedCredential.doctype renamed to docTypeRename all usages of doctype to docType.
5AuthenticationOption renamed to DeviceAuthenticationOption (and the authenticationOption parameter on createProximityPresentationSession renamed to deviceAuthenticationOption)Update all imports, type references, and the session-creation argument name.
6OfferedCredential.claims is now optionalHandle the case where claims is null, indicating no claim data is included in the offer.
7RetrieveCredentialResult is now a sealed interface with Success and Failure variantsReplace field-based branching with when/is pattern matching.
8OnlinePresentationSession.matchedCredentials is now the getMatchedCredentials() methodReplace property access with the method call.
9sessionStatus parameter removed from ProximityPresentationSession.terminateSessionListen for SessionStatus.SessionTerminated in your presentation session callback instead of passing a custom status.
10Required authorizationServerIssuer and tokenEndpointAuthMethodsSupported fields added to DiscoveredCredentialOfferSupply the new arguments if you construct this type directly.
11ConnectivityError and InvalidWalletAttestation added to RetrieveCredentialErrorHandle the new cases in exhaustive when statements.
12Stricter JWT request validation for online presentations: enforces expected typ, validates iat, rejects expired expWhere possible, ensure verifiers generate compliant JWT authorization request objects.
13Oversized state values in VP online presentation requests are now rejectedWhere possible, ensure verifier-generated state values remain within supported limits.
14Oversized credential fields in pre-authorized issuance are now rejectedWhere possible, validate issuer payload sizes and test with realistic credential data.
15mdocIacasUri is now optionalReview assumptions that this field is always present.

Migration Steps

Configure userAuthenticationType

UserAuthenticationConfiguration now takes a userAuthenticationType parameter to configure biometric authentication behavior, aligning with iOS. The default authentication type is UserPresence. Review your configuration and note that initialize may now throw HolderException.UserAuthenticationNotSupportedException if you attempt to use BiometryCurrentSet with OnInitialize:

  val userAuthConfig = UserAuthenticationConfiguration(
      userAuthenticationBehavior = OnDeviceKeyAccess,
+     userAuthenticationType = UserPresence
  )

UserPresence matches the implicit behavior of v6.x, so existing integrations are unaffected if you keep it. Opt in to BiometryCurrentSet only if you require that stricter behavior.

(Optional) Create a holder application on your MATTR VII tenant

SDK Tethering is optional in this release. If you do not need it, you can skip this step and the next one. The SDK continues to function without a platformConfiguration.

To enable SDK Tethering, the Android Holder SDK connects to a MATTR VII tenant. Tethering gives you access to a centralized view of registered and active app instances in MATTR VII, the optional Wallet Attestation feature, and other centralized management capabilities we plan to deliver over this channel.

We expect SDK Tethering to become required in an upcoming release. We recommend adopting it now to prepare, even if you do not yet use Wallet Attestation.

To register your Android application, make a request to create a holder application:

Request
POST /v1/holder/applications
Request body
{
    "name": "My Android Holder Application",
    "clientId": "your-wallet-client-id",
    "type": "android",
    "packageName": "com.yourcompany.holderapp",
    "packageSigningCertificateThumbprints": [
        "1232584b6f6a892d356899fb9576c5f226a179e6199f2b7a1d837b5c234c5a8e"
    ]
}
  • name: A unique name to identify your holder application.
  • clientId: The OAuth 2.0 client_id that identifies your wallet application. This value is included in attestation JWTs and must match the client_id configured on the issuer's Authorization Server.
  • type: Must be android for an Android application.
  • packageName: The package name of your Android application.
  • packageSigningCertificateThumbprints: SHA-256 hex-encoded fingerprints of the signing key certificates used to sign your APK or app bundle. This ensures the tenant only accepts requests from known and trusted applications. Refer to Android app signing for more information.

Once created, your holder application will be able to use the SDK and interact with the MATTR VII platform (for example, to obtain attestation tokens).

The response will include a unique id for your application, which must be used when initializing the SDK so that it can correctly identify and authenticate your application.

The clientId you configure here is the same value you must:

  1. Pass as the clientId parameter when calling retrieveCredentials.
  2. Register with each issuer you intend to interact with, so the issuer can identify and trust requests coming from your wallet application.

(Optional) Enable tethering at SDK initialization

To enable SDK Tethering, pass a platformConfiguration when initializing the SDK. This parameter is optional. Omit it to initialize the SDK without tethering:

- MobileCredentialHolder.initialize(context, instanceId)
+ val platformConfig = PlatformConfiguration(
+     tenantHost = URL("https://your-tenant.vii.mattr.global"),
+     applicationId = "1ef1f867-20b4-48ea-aec1-bea7aff4964c"
+ )
+ MobileCredentialHolder.initialize(context, instanceId, platformConfiguration = platformConfig)
  • tenantHost: The URL of your MATTR VII tenant where your holder application is configured.
  • applicationId: The id of your configured MATTR VII holder application.

When platformConfiguration is omitted, the SDK skips registration, tethering, and wallet attestation.

Handle new error cases

This release adds two new values to RetrieveCredentialError, ConnectivityError and InvalidWalletAttestation. This is a breaking change that requires updates to exhaustive when blocks over this enum regardless of whether you enable SDK Tethering.

If you enable SDK Tethering and Wallet Attestation, the following exceptions on HolderException also become relevant during credential issuance:

  • HolderException.InvalidCredentialOfferException: The offer requires attestation but no platformConfiguration was provided, or the SDK does not support any of the client authentication methods advertised by the authorization server.
  • HolderException.WalletAttestationFailedException: The SDK could not obtain an attestation token from the MATTR VII tenant.
  • HolderException.InvalidWalletAttestationException: The authorization server rejected the attestation token.

The retrieveCredentials method may now throw these exceptions. Wallet attestation errors only occur when the SDK is tethered and an issuer requires attestation.

Update your error handling, logging, analytics, and support diagnostics to account for these new error cases.

Update client_id configuration

Previously, the client_id passed to retrieveCredentials was not shared with the issuer during the pre-authorized code flow, so any value would work. This is no longer the case. The SDK now presents the client_id to the issuer as part of wallet attestation, and the issuer validates it against its list of trusted wallet providers.

To prepare for this change:

  1. Coordinate with the issuer to register your wallet application as a trusted wallet provider. The issuer will provide you with a client_id that identifies your application.
  2. Pass the issuer-provided client_id when calling retrieveCredentials.
  val results = holder.retrieveCredentials(
      activity = activity,
      credentialOffer = offer,
-     clientId = "any-value",
+     clientId = "issuer-provided-client-id"
  )

Issuance flows that previously worked with an arbitrary client_id will fail if the issuer requires a trusted wallet provider. Ensure you have coordinated with each issuer and obtained the correct client_id before upgrading. Test direct issuance flows to confirm credentials are issued successfully.

Update credential retrieval result handling

RetrieveCredentialResult has been converted from a data class with nullable fields to a sealed interface with explicit Success and Failure subtypes. Each subtype carries guaranteed properties, removing ambiguity from result handling.

The retrieveCredentials function returns List<RetrieveCredentialResult>, a list with one result per offered credential. Update your iteration logic to use when matching:

  val results = holder.retrieveCredentials(options)
  for (result in results) {
-     val credentialId = result.credentialId
-     if (credentialId != null) {
-         // Use result.docType and credentialId
-     } else {
-         // Use result.docType and result.error
-     }
+     when (result) {
+         is RetrieveCredentialResult.Success -> {
+             // result.docType and result.credentialId are guaranteed
+         }
+         is RetrieveCredentialResult.Failure -> {
+             // result.docType and result.error are guaranteed
+         }
+     }
  }

Update tests and any downstream logic that checked for null fields.

doctype renamed to docType

The doctype field has been renamed to docType (camelCase). This affects:

Update all references from .doctype to .docType.

- val documentType = credential.doctype
+ val documentType = credential.docType

Rename AuthenticationOption to DeviceAuthenticationOption

AuthenticationOption has been renamed to DeviceAuthenticationOption. Update all imports and type references, and rename the authenticationOption argument on createProximityPresentationSession to deviceAuthenticationOption:

- import global.mattr.mobilecredential.holder.AuthenticationOption
+ import global.mattr.mobilecredential.holder.DeviceAuthenticationOption

- val authOption: AuthenticationOption = ...
+ val authOption: DeviceAuthenticationOption = ...

- holder.createProximityPresentationSession(authenticationOption = authOption)
+ holder.createProximityPresentationSession(deviceAuthenticationOption = authOption)

Handle optional OfferedCredential.claims

OfferedCredential.claims is now optional and only returned for offers that contain claim data. Handle the case where claims is null:

- val claims = offeredCredential.claims
+ val claims = offeredCredential.claims // may be null when the offer contains no claim data
+ if (claims != null) {
+     // Use the claims
+ }

Update matchedCredentials handling for online presentation

OnlinePresentationSession.matchedCredentials has been changed from a property to a getMatchedCredentials() method to support Just In Time issuance flows. Replace property access with the method call:

- val matched = session.matchedCredentials
+ val matched = session.getMatchedCredentials()

Update terminateSession calls

The sessionStatus parameter has been removed from ProximityPresentationSession.terminateSession. Sessions terminated this way send SessionStatus.SessionTerminated to the verifier. If you previously passed a custom status, listen for SessionStatus.SessionTerminated in your presentation session callback and handle it as needed:

- session.terminateSession(sessionStatus = customStatus)
+ session.terminateSession()

Supply new DiscoveredCredentialOffer fields

The required fields authorizationServerIssuer and tokenEndpointAuthMethodsSupported have been added to DiscoveredCredentialOffer. If you construct this type directly, supply the new arguments. Most callers receive this type from discoverCredentialOffer and are not affected.

Update verifier JWT request objects

The SDK now enforces stricter JWT request validation for online presentations:

  • The typ header must match the expected value.
  • The iat (issued at) claim is now validated.
  • Expired exp (expiration) values are now rejected.

Where possible, ensure all verifiers in your ecosystem generate compliant JWT authorization request objects. Update test fixtures and any non-compliant verifier integrations.

Validate state values in presentation requests

Oversized state values in VP online presentation requests are now rejected. Where possible, ensure verifier-generated state values remain within supported limits. Update tests that use large state payloads.

Ensure x5c certificate chains are present

Missing x5c certificate chains in validity-signed JWT request objects are now safely rejected. Where possible, ensure verifier request objects include required certificate chains where applicable. Update negative-path handling for rejected presentation requests.

Validate credential payload sizes

Oversized credential fields in pre-authorized issuance are now rejected. Where possible, validate issuer payload sizes and test issuance with realistic credential data. Update error handling for rejected oversized credentials:

  val result = holder.retrieveCredentials(options)
  when (result) {
      is RetrieveCredentialsResult.Failure -> {
          when (result.error) {
              // ... existing error cases
+             is OversizedCredentialPayload -> {
+                 // Handle oversized credential rejection
+             }
          }
      }
  }

Handle optional mdocIacasUri

The mdocIacasUri field is now optional. Review any app-side assumptions that this field is always present and confirm handling of empty string values:

- val iacasUri = credential.mdocIacasUri // assumed non-null
+ val iacasUri = credential.mdocIacasUri // may be empty string
+ if (iacasUri.isNotEmpty()) {
+     // Use the URI
+ }

How would you rate this page?

Last updated on

On this page