PortalException hierarchy so you can catch errors at the granularity you need — from individual error cases up to broad categories. Every exception thrown by the SDK is a subclass of PortalException, which itself extends Exception.
Exception hierarchy
PortalException is a sealed class with eight category-level sealed subclasses:
Catching exceptions
Because the hierarchy is sealed, you can catch at any level of specificity.Catch a specific exception
Catch an entire category
Catch any Portal exception
Handle errors from Result-returning methods
Some methods like sendAsset return a Result instead of throwing. Use onFailure or exceptionOrNull() to inspect the error:
Branch on the HTTP status code
PortalException.Api.HttpRequestFailed exposes the status code that produced the failure, so you can separate a transient server-side error from a terminal one without matching on the message string.
HttpRequestFailed only covers status codes that aren’t already routed to a more specific exception: 4xx codes of 402 and above, every 5xx, and unrecognized codes. A 400 throws PortalException.Api.HttpBadRequest and a 401 throws PortalException.Api.HttpUnauthorized, both of which are siblings of HttpRequestFailed rather than subclasses. statusCode is therefore never 400 or 401 — catch those two types directly.
statusCode is null in two situations, and neither one means “unknown error”:
- No HTTP response was received. A socket timeout, a DNS failure, or a malformed status line never produces a status code. Codes outside the
100..599range are reported asnullfor the same reason. - The request returned
200 OKbut the body carried an error envelope. The failure is real, but the HTTP status was a success, so there is no error code to report.
null as “there is no status code to branch on” and fall through to your default handling:
statusCode was added in 9.1.0. On earlier versions the only way to identify the status code was to parse it out of message, which is formatted as "<statusCode> - <responseBody>".Absent shares versus unreadable shares
As of9.1.0, having no share on the device is no longer an error. getShares() on a Keychain instance returns null in that case; before 9.1.0 it threw IllegalArgumentException("No user share found on device storage").
getShares() reads through a fallback chain: the current shares key first, then three legacy key formats kept for backward compatibility. An entry that can’t be parsed doesn’t fail the call — it moves on to the next format in the chain. So an unreadable entry under the current shares key resolves to null too, as long as no legacy entry is readable either.
The
"Unable to parse share from device storage" message is reserved for the last link in that chain — the misspelled deprecated key from an older SDK version — so in practice you’ll only see it on a device mid-migration. A parse failure elsewhere in the chain surfaces as whatever the JSON parser threw, not as an IllegalArgumentException.
This is what makes portal.isWalletOnDeviceOrThrow() return false rather than throw when the user has no usable wallet on the current device. See manage wallet lifecycle states for the migration.
Exception reference
MPC (PortalException.Mpc)
Storage (PortalException.Storage)
Wallet (PortalException.Wallet)
Transaction (PortalException.Transaction)
API (PortalException.Api)
Connect (PortalException.Connect)
Provider (PortalException.Provider)
Blockchain (PortalException.Blockchain)
Migration from previous versions
In previous versions of the Android SDK, errors were thrown as genericError, Exception, or flat PortalException subclasses like SendAssetArgumentError and PortalHttpUnauthorizedException. These legacy classes are now deprecated and will be removed in a future major version.
What changed
- All generic
Error(...)andException(...)throws have been replaced with specificPortalExceptionsubclasses. - Flat exception classes (
SendAssetArgumentError,PortalHttpUnauthorizedException,PasskeyNotSupportedException,PasskeyStorageNotConfiguredException,MpcError,InvalidWalletStateError,StorageAuthError) now extend their correspondingPortalExceptionsubcategory. Your existingcatchblocks will still work, but you should migrate to the new types.