Symptom
You switched an operation to blocking mode with BlockingMessageGateway, and now the coroutine never resumes on failure — it hangs until it throws TimeoutCancellationException. Successful operations work fine; only failures hang. A login or payment that should return an error instead just times out.
Cause
sendBlocking resumes when a message of the exact type you asked it to await arrives. If you await the success subtype (e.g. SuccessRetailerLoginResponse) and the operation fails, the failure arrives as a different type (ErrorRetailerLoginResponse) — which never matches what you're waiting for. The gateway keeps waiting, and your coroutine hangs until the timeout fires.
The failure response was delivered. You just weren't listening for its type.
Fix
Await the sealed super-type, not a subtype, then branch with when:
// WRONG — resumes only on success; every failure becomes a timeout
val r: SuccessRetailerLoginResponse = gateway.sendBlocking { clientSDK.sendLoginRequest() }
// RIGHT — resumes on success OR error
val r: RetailerLoginResponse = gateway.sendBlocking(timeoutMillis = 30000) {
clientSDK.sendLoginRequest()
}
when (r) {
is SuccessRetailerLoginResponse -> { /* ok */ }
is ErrorRetailerLoginResponse -> { /* handle the failure */ }
}
The same rule applies to every operation: await RetailerPaymentResponse, RetailerReversalResponse, etc. — the super-type — and branch afterwards. The super-type covers all outcomes, so both success and failure resume your coroutine.
Remember payment has a third outcome,
PartialRetailerPaymentResponse. AwaitingRetailerPaymentResponsecatches it; awaiting only the success subtype would miss both the error and the partial cases.
Still timing out after fixing the type?
If you await the super-type and it still times out, a response genuinely isn't arriving — check that the gateway is being fed every message (gateway.onNewMessage(message) in your interceptor), that you have a live session (see Session and login lifecycle), and that the timeout is long enough for the operation. Always handle TimeoutCancellationException regardless — a real timeout is itself a condition your app must react to.
Related
- How the message model works — the full explanation of the super-type pattern.
- Session and login lifecycle — a missing session also produces no response.