Credential Exchange Protocols - Error Handling
Complete guide to error handling for credential exchange protocols.
Overview
All credential exchange operations throw structured exceptions from the ExchangeException hierarchy. These exceptions extend TrustWeaveException and provide:
- Structured error codes for programmatic handling
- Rich context with relevant information
- Type-safe error handling with sealed classes
- Consistent error format across all TrustWeave modules
Exception Hierarchy
All exchange-related exceptions extend ExchangeException, which extends TrustWeaveException. Plugin-specific exceptions are located in their respective plugin modules:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import com.trustweave.credential.exchange.exception.ExchangeException
import com.trustweave.credential.didcomm.exception.DidCommException
import com.trustweave.credential.oidc4vci.exception.Oidc4VciException
import com.trustweave.credential.chapi.exception.ChapiException
try {
val offer = registry.offerCredential("didcomm", request)
} catch (e: ExchangeException) {
when (e) {
is ExchangeException.ProtocolNotRegistered -> {
println("Protocol: ${e.protocolName}")
println("Available: ${e.availableProtocols}")
}
is ExchangeException.OperationNotSupported -> {
println("Operation: ${e.operation}")
println("Supported: ${e.supportedOperations}")
}
// Plugin-specific exceptions also extend ExchangeException
is DidCommException.EncryptionFailed -> {
println("DIDComm encryption failed: ${e.reason}")
}
is Oidc4VciException.HttpRequestFailed -> {
println("OIDC4VCI HTTP request failed: ${e.statusCode}")
}
is ChapiException.BrowserNotAvailable -> {
println("CHAPI requires browser: ${e.reason}")
}
// ... handle other exceptions
}
}
Exception Module Structure
- Core exceptions (
ExchangeException): Located incredentials/credential-core- Registry errors
- Request validation errors
- Resource not found errors
- Generic/unknown errors
- Plugin-specific exceptions: Located in their respective plugin modules
DidCommException:credentials/plugins/didcommOidc4VciException:credentials/plugins/oidc4vciChapiException:credentials/plugins/chapi
All plugin exceptions extend ExchangeException, ensuring consistent error handling across all protocols.
Error Types
Registry Errors
ExchangeException.ProtocolNotRegistered
When it occurs:
- Calling any registry method with an unregistered protocol name
Error code: PROTOCOL_NOT_REGISTERED
Properties:
protocolName: String- The requested protocol nameavailableProtocols: List<String>- List of available protocol names
Code example:
1
2
3
4
5
6
7
8
9
10
11
try {
val offer = registry.offerCredential("didcomm", request)
} catch (e: ExchangeException.ProtocolNotRegistered) {
println("Error code: ${e.code}")
println("Protocol: ${e.protocolName}")
println("Available: ${e.availableProtocols}")
// Output:
// Error code: PROTOCOL_NOT_REGISTERED
// Protocol: didcomm
// Available: []
}
Solutions:
- Register the protocol before use:
1 2 3 4 5 6
val registry = CredentialExchangeProtocolRegistry() val didCommService = DidCommFactory.createInMemoryService(kms, resolveDid) registry.register(DidCommExchangeProtocol(didCommService)) // Now safe to use val offer = registry.offerCredential("didcomm", request)
- Check available protocols:
1 2 3 4 5
val available = registry.getAllProtocolNames() if (!available.contains("didcomm")) { println("Protocol not available. Available: $available") // Register protocol or use different protocol }
- Use isRegistered() to check:
1 2 3 4
if (!registry.isRegistered("didcomm")) { // Register protocol registry.register(DidCommExchangeProtocol(didCommService)) }
Prevention:
- Always register protocols before use
- Check
registry.isRegistered()before calling methods - Use
registry.getAllProtocolNames()to see available protocols
Request Validation Errors
ExchangeException.MissingRequiredOption
When it occurs:
- A required option is missing from the request
Error code: MISSING_REQUIRED_OPTION
Properties:
optionName: String- The name of the missing optionprotocolName: String?- The protocol name (if applicable)
Code example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
try {
val request = CredentialOfferRequest(
issuerDid = "did:key:issuer",
holderDid = "did:key:holder",
credentialPreview = preview,
options = emptyMap() // Missing 'fromKeyId' and 'toKeyId'
)
val offer = registry.offerCredential("didcomm", request)
} catch (e: ExchangeException.MissingRequiredOption) {
println("Error code: ${e.code}")
println("Missing option: ${e.optionName}")
println("Protocol: ${e.protocolName}")
// Output:
// Error code: MISSING_REQUIRED_OPTION
// Missing option: fromKeyId
// Protocol: didcomm
}
Solutions:
- Add the missing option:
1 2 3 4 5 6 7 8 9
val request = CredentialOfferRequest( issuerDid = "did:key:issuer", holderDid = "did:key:holder", credentialPreview = preview, options = mapOf( "fromKeyId" to "did:key:issuer#key-1", "toKeyId" to "did:key:holder#key-1" ) )
- Check protocol requirements:
- DIDComm requires:
fromKeyId,toKeyId - OIDC4VCI requires:
credentialIssuer
- DIDComm requires:
ExchangeException.OfferNotFound
When it occurs:
- Requesting a credential using a non-existent offer ID
Error code: OFFER_NOT_FOUND
Properties:
offerId: String- The offer ID that was not found
Code example:
1
2
3
4
5
6
7
8
9
10
11
12
13
try {
val request = CredentialRequestRequest(
holderDid = "did:key:holder",
offerId = "non-existent-offer-id"
)
val response = registry.requestCredential("didcomm", request)
} catch (e: ExchangeException.OfferNotFound) {
println("Error code: ${e.code}")
println("Offer ID: ${e.offerId}")
// Output:
// Error code: OFFER_NOT_FOUND
// Offer ID: non-existent-offer-id
}
Solutions:
- Use a valid offer ID:
1 2 3 4 5 6 7 8
// First create an offer val offer = registry.offerCredential("didcomm", offerRequest) // Then use the offer ID val request = CredentialRequestRequest( holderDid = "did:key:holder", offerId = offer.offerId // Use the actual offer ID )
- Store offer IDs:
- Store offer IDs when creating offers
- Use a database or cache to track offers
ExchangeException.RequestNotFound
When it occurs:
- Issuing a credential using a non-existent request ID
Error code: REQUEST_NOT_FOUND
Properties:
requestId: String- The request ID that was not found
Code example:
1
2
3
4
5
6
7
8
9
10
11
12
try {
val request = CredentialIssueRequest(
issuerDid = "did:key:issuer",
holderDid = "did:key:holder",
credential = credential,
requestId = "non-existent-request-id"
)
val response = registry.issueCredential("didcomm", request)
} catch (e: ExchangeException.RequestNotFound) {
println("Error code: ${e.code}")
println("Request ID: ${e.requestId}")
}
DIDComm-Specific Errors
DIDComm-specific exceptions are located in the didcomm plugin module and extend ExchangeException:
1
import com.trustweave.credential.didcomm.exception.DidCommException
DidCommException.EncryptionFailed
When it occurs:
- DIDComm message encryption fails
Error code: DIDCOMM_ENCRYPTION_FAILED
Properties:
reason: String- The reason encryption failedfromDid: String?- The sender DID (if available)toDid: String?- The recipient DID (if available)cause: Throwable?- The underlying exception
Code example:
1
2
3
4
5
6
7
8
9
10
11
import com.trustweave.credential.didcomm.exception.DidCommException
try {
val offer = registry.offerCredential("didcomm", request)
} catch (e: DidCommException.EncryptionFailed) {
println("Error code: ${e.code}")
println("Reason: ${e.reason}")
println("From: ${e.fromDid}")
println("To: ${e.toDid}")
println("Cause: ${e.cause?.message}")
}
Common causes:
- Missing or invalid keys
- Key resolution failure
- Cryptographic operation failure
Solutions:
- Verify keys exist:
1 2
val fromKey = kms.getPublicKey(fromKeyId) val toKey = kms.getPublicKey(toKeyId)
- Check DID resolution:
1 2
val fromDoc = resolveDid(fromDid) val toDoc = resolveDid(toDid)
DidCommException.DecryptionFailed
When it occurs:
- DIDComm message decryption fails
Error code: DIDCOMM_DECRYPTION_FAILED
Properties:
reason: String- The reason decryption failedmessageId: String?- The message ID (if available)cause: Throwable?- The underlying exception
Code example:
1
2
3
4
5
6
7
8
9
import com.trustweave.credential.didcomm.exception.DidCommException
try {
val message = didCommService.unpack(encryptedMessage)
} catch (e: DidCommException.DecryptionFailed) {
println("Error code: ${e.code}")
println("Reason: ${e.reason}")
println("Message ID: ${e.messageId}")
}
DidCommException.PackingFailed
When it occurs:
- DIDComm message packing fails
Error code: DIDCOMM_PACKING_FAILED
Properties:
reason: String- The reason packing failedmessageId: String?- The message ID (if available)cause: Throwable?- The underlying exception
DidCommException.UnpackingFailed
When it occurs:
- DIDComm message unpacking fails
Error code: DIDCOMM_UNPACKING_FAILED
Properties:
reason: String- The reason unpacking failedmessageId: String?- The message ID (if available)cause: Throwable?- The underlying exception
DidCommException.ProtocolError
When it occurs:
- DIDComm protocol error occurs
Error code: DIDCOMM_PROTOCOL_ERROR
Properties:
reason: String- The reason for the errorfield: String?- The field that caused the error (if applicable)cause: Throwable?- The underlying exception
OIDC4VCI-Specific Errors
OIDC4VCI-specific exceptions are located in the oidc4vci plugin module and extend ExchangeException:
1
import com.trustweave.credential.oidc4vci.exception.Oidc4VciException
Oidc4VciException.HttpRequestFailed
When it occurs:
- OIDC4VCI HTTP request fails
Error code: OIDC4VCI_HTTP_REQUEST_FAILED
Properties:
url: String- The URL that was requestedstatusCode: Int?- The HTTP status code (if available)reason: String- The reason the request failedcause: Throwable?- The underlying exception
Code example:
1
2
3
4
5
6
7
8
9
10
import com.trustweave.credential.oidc4vci.exception.Oidc4VciException
try {
val offer = registry.offerCredential("oidc4vci", request)
} catch (e: Oidc4VciException.HttpRequestFailed) {
println("Error code: ${e.code}")
println("URL: ${e.url}")
println("Status: ${e.statusCode}")
println("Reason: ${e.reason}")
}
Common causes:
- Network connectivity issues
- Invalid credential issuer URL
- Server errors (5xx)
- Client errors (4xx)
Solutions:
- Check network connectivity:
1 2
// Verify URL is reachable val response = httpClient.newCall(Request.Builder().url(url).build()).execute()
- Verify credential issuer URL:
1
val metadata = oidc4vciService.fetchCredentialIssuerMetadata(credentialIssuerUrl)
Oidc4VciException.TokenExchangeFailed
When it occurs:
- OIDC4VCI token exchange fails
Error code: OIDC4VCI_TOKEN_EXCHANGE_FAILED
Properties:
reason: String- The reason token exchange failedcredentialIssuer: String?- The credential issuer URL (if available)cause: Throwable?- The underlying exception
Code example:
1
2
3
4
5
6
7
8
9
import com.trustweave.credential.oidc4vci.exception.Oidc4VciException
try {
val token = oidc4vciService.exchangeToken(authorizationCode)
} catch (e: Oidc4VciException.TokenExchangeFailed) {
println("Error code: ${e.code}")
println("Reason: ${e.reason}")
println("Issuer: ${e.credentialIssuer}")
}
Oidc4VciException.MetadataFetchFailed
When it occurs:
- OIDC4VCI metadata fetch fails
Error code: OIDC4VCI_METADATA_FETCH_FAILED
Properties:
credentialIssuer: String- The credential issuer URLreason: String- The reason metadata fetch failedcause: Throwable?- The underlying exception
Code example:
1
2
3
4
5
6
7
8
9
import com.trustweave.credential.oidc4vci.exception.Oidc4VciException
try {
val metadata = oidc4vciService.fetchCredentialIssuerMetadata(issuerUrl)
} catch (e: Oidc4VciException.MetadataFetchFailed) {
println("Error code: ${e.code}")
println("Issuer: ${e.credentialIssuer}")
println("Reason: ${e.reason}")
}
Oidc4VciException.CredentialRequestFailed
When it occurs:
- OIDC4VCI credential request fails
Error code: OIDC4VCI_CREDENTIAL_REQUEST_FAILED
Properties:
reason: String- The reason credential request failedcredentialIssuer: String?- The credential issuer URL (if available)cause: Throwable?- The underlying exception
Code example:
1
2
3
4
5
6
7
8
9
import com.trustweave.credential.oidc4vci.exception.Oidc4VciException
try {
val credential = oidc4vciService.requestCredential(accessToken, credentialOffer)
} catch (e: Oidc4VciException.CredentialRequestFailed) {
println("Error code: ${e.code}")
println("Reason: ${e.reason}")
println("Issuer: ${e.credentialIssuer}")
}
ExchangeException.OperationNotSupported
When it occurs:
- Protocol doesn’t support the requested operation
Error code: OPERATION_NOT_SUPPORTED
Properties:
protocolName: String- The protocol nameoperation: String- The requested operationsupportedOperations: List<String>- List of supported operations
Code example:
1
2
3
4
5
6
7
8
9
10
11
12
13
try {
val proofRequest = registry.requestProof("oidc4vci", request)
} catch (e: ExchangeException.OperationNotSupported) {
println("Error code: ${e.code}")
println("Protocol: ${e.protocolName}")
println("Operation: ${e.operation}")
println("Supported: ${e.supportedOperations}")
// Output:
// Error code: OPERATION_NOT_SUPPORTED
// Protocol: oidc4vci
// Operation: REQUEST_PROOF
// Supported: [OFFER_CREDENTIAL, REQUEST_CREDENTIAL, ISSUE_CREDENTIAL]
}
Solutions:
- Check supported operations:
1 2 3 4 5
val protocol = registry.get("oidc4vci") if (protocol != null) { println("Supported operations: ${protocol.supportedOperations}") // Output: Supported operations: [OFFER_CREDENTIAL, REQUEST_CREDENTIAL, ISSUE_CREDENTIAL] }
- Use a different protocol:
1 2
// OIDC4VCI doesn't support proof requests, use DIDComm instead val proofRequest = registry.requestProof("didcomm", request)
- Use a different operation:
1 2 3 4 5 6
// If you need proof functionality, use DIDComm or CHAPI if (registry.isRegistered("didcomm")) { val proofRequest = registry.requestProof("didcomm", request) } else { println("No protocol available for proof requests") }
Prevention:
- Check
protocol.supportedOperationsbefore calling methods - Use protocol comparison table to choose the right protocol
- Handle
UnsupportedOperationExceptiongracefully
Protocol-Specific Errors
DIDComm Errors
Missing Required Options
When it occurs:
- Missing
fromKeyIdortoKeyIdin options
Error message:
1
Missing required option: fromKeyId
Code example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
try {
val offer = registry.offerCredential(
protocolName = "didcomm",
request = CredentialOfferRequest(
issuerDid = "did:key:issuer",
holderDid = "did:key:holder",
credentialPreview = preview,
options = mapOf(
// Missing fromKeyId and toKeyId
)
)
)
} catch (e: IllegalArgumentException) {
println("Error: ${e.message}")
}
Solution:
1
2
3
4
5
6
7
8
9
10
11
12
val offer = registry.offerCredential(
protocolName = "didcomm",
request = CredentialOfferRequest(
issuerDid = "did:key:issuer",
holderDid = "did:key:holder",
credentialPreview = preview,
options = mapOf(
"fromKeyId" to "did:key:issuer#key-1", // Required
"toKeyId" to "did:key:holder#key-1" // Required
)
)
)
Invalid Key ID Format
When it occurs:
- Key ID doesn’t match expected format
Error message:
1
Invalid key ID format: <key-id>
Solution:
1
2
// Key ID must be in format: did:key:...#key-1
val keyId = "$issuerDid#key-1" // Correct format
DID Resolution Failure
When it occurs:
- Cannot resolve DID to DID document
- DID document doesn’t contain required keys
Error message:
1
Failed to resolve DID: did:key:issuer
Solution:
1
2
3
4
5
6
7
8
9
10
11
// Ensure DID resolver is properly configured
val resolveDid: suspend (String) -> DidDocument? = { did ->
// Implement real DID resolution
yourDidResolver.resolve(did)
}
// Ensure DID document contains required keys
val document = resolveDid("did:key:issuer")
if (document?.verificationMethod?.isEmpty() == true) {
println("DID document has no verification methods")
}
Key Not Found in KMS
When it occurs:
- Key ID references a key that doesn’t exist in KMS
Error message:
1
Key not found: did:key:issuer#key-1
Solution:
1
2
3
4
5
6
// Ensure key exists in KMS before use
val keyId = "did:key:issuer#key-1"
if (!kms.keyExists(keyId)) {
// Create key or use existing key
kms.createKey(keyId, algorithm = "Ed25519")
}
OIDC4VCI Errors
Missing Credential Issuer URL
When it occurs:
- Missing
credentialIssuerin options
Error message:
1
Missing required option: credentialIssuer
Solution:
1
2
3
4
5
6
7
8
9
10
11
val offer = registry.offerCredential(
protocolName = "oidc4vci",
request = CredentialOfferRequest(
issuerDid = "did:key:issuer",
holderDid = "did:key:holder",
credentialPreview = preview,
options = mapOf(
"credentialIssuer" to "https://issuer.example.com" // Required
)
)
)
HTTP Request Failure
When it occurs:
- Network error connecting to credential issuer
- Credential issuer returns error response
Error message:
1
HTTP request failed: 404 Not Found
Solution:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
try {
val offer = registry.offerCredential("oidc4vci", request)
} catch (e: Exception) {
when {
e.message?.contains("404") == true -> {
println("Credential issuer not found. Check URL.")
}
e.message?.contains("401") == true -> {
println("Authentication failed. Check credentials.")
}
e.message?.contains("timeout") == true -> {
println("Request timed out. Retry later.")
}
else -> {
println("HTTP error: ${e.message}")
}
}
}
Token Exchange Failure
When it occurs:
- Authorization code is invalid
- Token exchange endpoint returns error
Error message:
1
Token exchange failed: invalid_grant
Solution:
1
2
3
4
5
// Ensure authorization code is valid and not expired
val options = mapOf(
"credentialIssuer" to "https://issuer.example.com",
"authorizationCode" to validAuthorizationCode // Must be valid and not expired
)
Invalid Credential Issuer Metadata
When it occurs:
- Credential issuer metadata is invalid
- Required endpoints are missing
Error message:
1
Invalid credential issuer metadata: missing token_endpoint
Solution:
1
2
// Verify credential issuer metadata is valid
// Check that all required endpoints are present
CHAPI Errors
Browser Not Available
When it occurs:
- CHAPI requires browser environment
- Running in non-browser context
Error message:
1
CHAPI requires browser environment
Solution:
1
2
3
4
5
6
7
8
// CHAPI only works in browser
// Use different protocol for server-side operations
if (isBrowserEnvironment()) {
val offer = registry.offerCredential("chapi", request)
} else {
// Use DIDComm or OIDC4VCI for server-side
val offer = registry.offerCredential("didcomm", request)
}
Validation Errors
Invalid DID Format
When it occurs:
- DID doesn’t match format:
did:<method>:<identifier>
Error message:
1
Invalid DID format: invalid-did
Solution:
1
2
3
4
5
6
7
8
9
10
// Validate DID format before use
fun isValidDid(did: String): Boolean {
return did.matches(Regex("^did:[a-z0-9]+:.+$"))
}
val issuerDid = "did:key:issuer"
if (!isValidDid(issuerDid)) {
println("Invalid DID format")
return
}
Empty Credential Preview
When it occurs:
credentialPreview.attributesis empty
Error message:
1
Credential preview attributes must not be empty
Solution:
1
2
3
4
5
val preview = CredentialPreview(
attributes = listOf(
CredentialAttribute("name", "Alice") // Must have at least one attribute
)
)
Invalid Credential
When it occurs:
- Credential is missing required fields
- Credential structure is invalid
Error message:
1
Invalid credential: missing issuer
Solution:
1
2
3
4
5
6
7
// Ensure credential has all required fields
val credential = VerifiableCredential(
type = listOf("VerifiableCredential"), // Required
issuer = "did:key:issuer", // Required
credentialSubject = buildJsonObject { }, // Required
issuanceDate = Instant.now().toString() // Required
)
Error Handling Patterns
Pattern 1: Check Before Use
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Check protocol is registered
if (!registry.isRegistered("didcomm")) {
println("Protocol not registered")
return
}
// Check operation is supported
val protocol = registry.get("didcomm")
if (protocol?.supportedOperations?.contains(ExchangeOperation.OFFER_CREDENTIAL) != true) {
println("Operation not supported")
return
}
// Now safe to use
val offer = registry.offerCredential("didcomm", request)
Pattern 2: Try-Catch with Specific Handling
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import com.trustweave.credential.exchange.exception.ExchangeException
import com.trustweave.credential.didcomm.exception.DidCommException
try {
val offer = registry.offerCredential("didcomm", request)
println("Offer created: ${offer.offerId}")
} catch (e: ExchangeException.ProtocolNotRegistered) {
println("Protocol not registered. Register it first.")
registry.register(DidCommExchangeProtocol(didCommService))
// Retry
val offer = registry.offerCredential("didcomm", request)
} catch (e: ExchangeException.MissingRequiredOption) {
println("Missing required option: ${e.optionName}")
println("Protocol: ${e.protocolName}")
} catch (e: ExchangeException.OperationNotSupported) {
println("Operation not supported. Use different protocol or operation.")
println("Supported operations: ${e.supportedOperations}")
} catch (e: DidCommException) {
when (e) {
is DidCommException.EncryptionFailed -> {
println("DIDComm encryption failed: ${e.reason}")
}
is DidCommException.DecryptionFailed -> {
println("DIDComm decryption failed: ${e.reason}")
}
// ... handle other DIDComm exceptions
}
} catch (e: ExchangeException) {
println("Exchange error: ${e.message}")
println("Error code: ${e.code}")
} catch (e: Throwable) {
println("Unexpected error: ${e.message}")
e.printStackTrace()
}
Pattern 3: Fallback to Alternative Protocol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
suspend fun offerCredentialWithFallback(
preferredProtocol: String,
fallbackProtocols: List<String>,
request: CredentialOfferRequest
): CredentialOfferResponse? {
// Try preferred protocol first
if (registry.isRegistered(preferredProtocol)) {
try {
return registry.offerCredential(preferredProtocol, request)
} catch (e: Exception) {
println("Preferred protocol failed: ${e.message}")
}
}
// Try fallback protocols
for (protocol in fallbackProtocols) {
if (registry.isRegistered(protocol)) {
try {
return registry.offerCredential(protocol, request)
} catch (e: Exception) {
println("Fallback protocol $protocol failed: ${e.message}")
}
}
}
return null
}
// Usage
val offer = offerCredentialWithFallback(
preferredProtocol = "didcomm",
fallbackProtocols = listOf("oidc4vci", "chapi"),
request = request
) ?: throw IllegalStateException("All protocols failed")
Pattern 4: Validate Before Operation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
fun validateOfferRequest(request: CredentialOfferRequest): ValidationResult {
val errors = mutableListOf<String>()
// Validate DIDs
if (!isValidDid(request.issuerDid)) {
errors.add("Invalid issuer DID format")
}
if (!isValidDid(request.holderDid)) {
errors.add("Invalid holder DID format")
}
// Validate preview
if (request.credentialPreview.attributes.isEmpty()) {
errors.add("Credential preview attributes must not be empty")
}
// Validate protocol-specific options
// (Implementation depends on protocol)
return if (errors.isEmpty()) {
ValidationResult.Valid
} else {
ValidationResult.Invalid(errors)
}
}
// Use before operation
val validation = validateOfferRequest(request)
if (validation is ValidationResult.Invalid) {
println("Validation failed: ${validation.errors}")
return
}
val offer = registry.offerCredential("didcomm", request)
Common Error Scenarios
Scenario 1: Protocol Not Registered
Problem:
1
2
val registry = CredentialExchangeProtocolRegistry()
val offer = registry.offerCredential("didcomm", request) // Throws ExchangeException.MissingRequiredOption
Solution:
1
2
3
4
val registry = CredentialExchangeProtocolRegistry()
val didCommService = DidCommFactory.createInMemoryService(kms, resolveDid)
registry.register(DidCommExchangeProtocol(didCommService))
val offer = registry.offerCredential("didcomm", request) // Now works
Scenario 2: Missing Required Options
Problem:
1
2
3
4
5
6
7
8
9
val offer = registry.offerCredential(
protocolName = "didcomm",
request = CredentialOfferRequest(
issuerDid = "did:key:issuer",
holderDid = "did:key:holder",
credentialPreview = preview,
options = emptyMap() // Missing fromKeyId and toKeyId
)
) // Throws ExchangeException.MissingRequiredOption
Solution:
1
2
3
4
5
6
7
8
9
10
11
12
val offer = registry.offerCredential(
protocolName = "didcomm",
request = CredentialOfferRequest(
issuerDid = "did:key:issuer",
holderDid = "did:key:holder",
credentialPreview = preview,
options = mapOf(
"fromKeyId" to "did:key:issuer#key-1", // Added
"toKeyId" to "did:key:holder#key-1" // Added
)
)
) // Now works
Scenario 3: Operation Not Supported
Problem:
1
val proofRequest = registry.requestProof("oidc4vci", request) // Throws ExchangeException.OperationNotSupported
Solution:
1
2
3
4
5
6
7
8
// Check supported operations first
val protocol = registry.get("oidc4vci")
if (protocol?.supportedOperations?.contains(ExchangeOperation.REQUEST_PROOF) == true) {
val proofRequest = registry.requestProof("oidc4vci", request)
} else {
// Use different protocol
val proofRequest = registry.requestProof("didcomm", request)
}
Error Recovery Utilities
The ExchangeExceptionRecovery object provides comprehensive error recovery utilities:
Automatic Retry with Exponential Backoff
1
2
3
4
5
6
import com.trustweave.credential.exchange.exception.retryExchangeOperation
// Automatically retries on transient errors
val offer = retryExchangeOperation(maxRetries = 3) {
registry.offerCredential("didcomm", request)
}
Error Classification
1
2
3
4
5
6
7
8
9
10
11
12
13
import com.trustweave.credential.exchange.exception.ExchangeExceptionRecovery
val exception: ExchangeException = // ... get exception
// Check if error is retryable
if (ExchangeExceptionRecovery.isRetryable(exception)) {
// Retry the operation
}
// Check if error is transient
if (ExchangeExceptionRecovery.isTransient(exception)) {
// Error might resolve on its own
}
User-Friendly Error Messages
1
2
val message = ExchangeExceptionRecovery.getUserFriendlyMessage(exception)
println(message) // Displays user-friendly error message
Alternative Protocol Fallback
1
2
3
4
5
6
val result = ExchangeExceptionRecovery.tryAlternativeProtocol(
exception = exception,
availableProtocols = listOf("oidc4vci", "chapi")
) { protocol ->
registry.offerCredential(protocol, request)
}
Companion Object Helpers
1
2
3
4
5
6
7
8
import com.trustweave.credential.exchange.exception.ExchangeException
// Use companion object for convenience
if (ExchangeException.isRetryable(exception)) {
// Retry logic
}
val message = ExchangeException.getUserFriendlyMessage(exception)
Error Recovery Strategies
Strategy 1: Retry with Different Protocol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
suspend fun offerCredentialWithRetry(
protocols: List<String>,
request: CredentialOfferRequest
): CredentialOfferResponse? {
for (protocol in protocols) {
try {
if (registry.isRegistered(protocol)) {
return registry.offerCredential(protocol, request)
}
} catch (e: Exception) {
println("Protocol $protocol failed: ${e.message}")
continue
}
}
return null
}
Strategy 2: Register Missing Protocol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
suspend fun offerCredentialWithAutoRegister(
protocolName: String,
request: CredentialOfferRequest
): CredentialOfferResponse {
if (!registry.isRegistered(protocolName)) {
// Auto-register protocol
when (protocolName) {
"didcomm" -> {
val service = DidCommFactory.createInMemoryService(kms, resolveDid)
registry.register(DidCommExchangeProtocol(service))
}
"oidc4vci" -> {
val service = Oidc4VciService(credentialIssuerUrl, kms)
registry.register(Oidc4VciExchangeProtocol(service))
}
// ... other protocols
}
}
return registry.offerCredential(protocolName, request)
}
Best Practices
- Always check protocol registration:
1 2 3
if (!registry.isRegistered("didcomm")) { // Register or use different protocol }
- Check supported operations:
1 2 3 4
val protocol = registry.get("oidc4vci") if (protocol?.supportedOperations?.contains(ExchangeOperation.OFFER_CREDENTIAL) != true) { // Use different protocol }
- Validate inputs before operations:
1 2 3
if (!isValidDid(issuerDid)) { return // Handle error }
- Use try-catch for all operations:
1 2 3 4 5
try { val offer = registry.offerCredential("didcomm", request) } catch (e: Exception) { // Handle error appropriately }
- Provide helpful error messages:
1 2 3 4
catch (e: ExchangeException) { logger.error("Invalid argument: ${e.message}") // Provide user-friendly message }
Related Documentation
- Quick Start - Get started quickly (5 minutes)
- API Reference - Complete API documentation
- Troubleshooting - Common issues and solutions
- Workflows - Step-by-step workflows
- Examples - Code examples with error handling
- Best Practices - Error handling best practices
- Glossary - Terms and concepts