Error Handling Patterns: Exceptions vs Sealed Results
TrustWeave uses a hybrid error handling approach: exceptions for programming errors and sealed results for expected failures. This guide explains when to use each pattern and why.
Sealed Results → Expected failures that are part of normal operation
Exception-Based Operations
Operations that throw exceptions are those where failure indicates a programming error or invalid configuration:
DID Creation (createDid())
Why Exceptions? Creating a DID should always succeed if configuration is correct. Failure means:
Invalid configuration (programming error)
Missing required components (KMS not configured)
Method not registered (should be registered at startup)
1
2
3
4
5
6
7
8
9
10
11
try{valdid=trustweave.createDid()// Success - DID created}catch(error:DidException.DidMethodNotRegistered){// Programming error: Method should be registeredprintln("Method not registered: ${error.method}")println("Available: ${error.availableMethods}")}catch(error:DidException){// Configuration errorprintln("Failed to create DID: ${error.message}")}
Credential Issuance (trustWeave.issue { })
Why a sealed result? Issuance can fail for resolver, KMS, schema, or adapter reasons—handle them with when (or getOrThrow() only in tests).
importorg.trustweave.credential.results.IssuanceResult// issuerDid: Did, issuerKeyId: String (e.g. from verificationMethod.extractKeyId())when(valissued=trustWeave.issue{credential{type("PersonCredential")issuer(issuerDid)subject{"name"to"Alice"}}signedBy(issuerDid=issuerDid,keyId=issuerKeyId)}){isIssuanceResult.Success->{valcredential=issued.credential// …}isIssuanceResult.Failure.UnsupportedFormat->println(issued.allErrors.joinToString())isIssuanceResult.Failure.AdapterNotReady->println(issued.allErrors.joinToString())isIssuanceResult.Failure.InvalidRequest->println("Invalid field '${issued.field}': ${issued.reason}")isIssuanceResult.Failure.AdapterError->println("Adapter error: ${issued.reason}")isIssuanceResult.Failure.MultipleFailures->println(issued.allErrors.joinToString())}
Wallet Creation (trustWeave.wallet { })
Why a sealed result? Misconfigured factories, invalid holder DIDs, or storage errors surface as WalletCreationResult.Failure—handle with when or getOrThrow() in tests.
Operations that return sealed results are those where failure is expected and normal:
DID Resolution (resolveDid())
Why Sealed Result? DID resolution can fail for expected reasons:
DID doesn’t exist (normal - DIDs are created by others)
Network unavailable (expected transient failure)
Method not available (expected - methods are optional)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
when(valresult=trustWeave.resolveDid("did:key:z6Mk...")){isDidResolutionResult.Success->{println("Resolved: ${result.document.id}")}isDidResolutionResult.Failure.NotFound->{println("DID not found - may be created later")}isDidResolutionResult.Failure.InvalidFormat->{println("Invalid DID: ${result.reason}")}isDidResolutionResult.Failure.MethodNotRegistered->{println("Method not available: ${result.method}")}isDidResolutionResult.Failure.ResolutionError->{println("Resolution error - retry later")}}
Credential Verification (trustWeave.verify)
Why Sealed Result? Verification can fail for expected reasons:
// ❌ Bad: Using sealed result for programming errorvaldidResult=trustweave.createDid()when(didResult){isSuccess->{...}isFailure->{...}// This is a programming error, not expected}// ✅ Good: Exception for programming errortry{valdid=trustweave.createDid()// Should succeed if configured}catch(error:DidException.DidMethodNotRegistered){// Fix configurationthrowIllegalStateException("Missing DID method registration",error)}
Pattern 2: Sealed Result for Expected Failures
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// ❌ Bad: Using exception for expected failuretry{valresolution=trustweave.resolveDid("did:unknown:test")}catch(error:DidException.DidNotFound){// This is expected - DID may not exist}// ✅ Good: Sealed result for expected failurewhen(valresolution=trustweave.resolveDid("did:unknown:test")){isDidResolutionResult.Success->{// DID exists}isDidResolutionResult.Failure.NotFound->{// Expected: DID doesn't existprintln("DID not found - this is normal")}}
When to Use Each Pattern
Use Exceptions When:
Programming Error
Method not registered (should be registered at startup)
// ✅ Good: Handle exceptions for configuration errorstry{valdid=trustweave.createDid()// Exception on config error}catch(error:DidException){// Log and fix configurationlogger.error("DID creation failed",error)throwIllegalStateException("Configuration error",error)}// ✅ Good: Handle sealed results for expected failureswhen(valverification=trustWeave.verify(credential)){isVerificationResult.Valid->{// Proceed}isVerificationResult.Invalid->{// Handle expected failure gracefullylogger.warn("Credential invalid: ${verification.allErrors.joinToString()}")// Show user-friendly message}}
2. Don’t Mix Patterns Incorrectly
1
2
3
4
5
6
7
8
9
10
11
12
13
// ❌ Bad: Treating expected failure as exceptiontry{valresolution=trustweave.resolveDid("did:unknown:test")// This can fail normally - shouldn't be exception}catch(error:Exception){// Wrong pattern}// ✅ Good: Using sealed result for expected failurewhen(valresolution=trustweave.resolveDid("did:unknown:test")){isDidResolutionResult.Success->{...}isDidResolutionResult.Failure->{...}// Expected}
3. Use Exhaustive Handling for Sealed Results
1
2
3
4
5
6
7
8
9
// ✅ Good: Compiler ensures all cases handledwhen(valresult=trustWeave.resolveDid(did)){isDidResolutionResult.Success->{...}isDidResolutionResult.Failure.NotFound->{...}isDidResolutionResult.Failure.MethodNotRegistered->{...}isDidResolutionResult.Failure.InvalidFormat->{...}isDidResolutionResult.Failure.ResolutionError->{...}// Compiler error if case missing}