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
platformConfigurationat 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:
| # | Change | Impact |
|---|---|---|
| 1 | UserAuthenticationConfiguration now takes a userAuthenticationType parameter | Configure biometric authentication behavior. initialize may throw HolderException.UserAuthenticationNotSupportedException if you use BiometryCurrentSet with OnInitialize. |
| 2 | Pre-authorized code flow now uses the application's client_id instead of a default identifier | Ensure your application has a valid configured client_id and issuers recognize it. |
| 3 | Credential retrieval result shape updated to explicitly identify success or failure with guaranteed fields | Update result parsing logic to use success/failure branching. |
| 4 | OfferedCredential.doctype renamed to docType | Rename all usages of doctype to docType. |
| 5 | AuthenticationOption renamed to DeviceAuthenticationOption (and the authenticationOption parameter on createProximityPresentationSession renamed to deviceAuthenticationOption) | Update all imports, type references, and the session-creation argument name. |
| 6 | OfferedCredential.claims is now optional | Handle the case where claims is null, indicating no claim data is included in the offer. |
| 7 | RetrieveCredentialResult is now a sealed interface with Success and Failure variants | Replace field-based branching with when/is pattern matching. |
| 8 | OnlinePresentationSession.matchedCredentials is now the getMatchedCredentials() method | Replace property access with the method call. |
| 9 | sessionStatus parameter removed from ProximityPresentationSession.terminateSession | Listen for SessionStatus.SessionTerminated in your presentation session callback instead of passing a custom status. |
| 10 | Required authorizationServerIssuer and tokenEndpointAuthMethodsSupported fields added to DiscoveredCredentialOffer | Supply the new arguments if you construct this type directly. |
| 11 | ConnectivityError and InvalidWalletAttestation added to RetrieveCredentialError | Handle the new cases in exhaustive when statements. |
| 12 | Stricter JWT request validation for online presentations: enforces expected typ, validates iat, rejects expired exp | Where possible, ensure verifiers generate compliant JWT authorization request objects. |
| 13 | Oversized state values in VP online presentation requests are now rejected | Where possible, ensure verifier-generated state values remain within supported limits. |
| 14 | Oversized credential fields in pre-authorized issuance are now rejected | Where possible, validate issuer payload sizes and test with realistic credential data. |
| 15 | mdocIacasUri is now optional | Review 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:
POST /v1/holder/applications{
"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.0client_idthat identifies your wallet application. This value is included in attestation JWTs and must match theclient_idconfigured on the issuer's Authorization Server.type: Must beandroidfor 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:
- Pass as the
clientIdparameter when callingretrieveCredentials. - 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: Theidof 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 noplatformConfigurationwas 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:
- Coordinate with the issuer to register your wallet application as a trusted wallet provider.
The issuer will provide you with a
client_idthat identifies your application. - Pass the issuer-provided
client_idwhen callingretrieveCredentials.
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:
OfferedCredentialreturned bydiscoverCredentialOffer
Update all references from .doctype to .docType.
- val documentType = credential.doctype
+ val documentType = credential.docTypeRename 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
typheader 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