Skip to main content
The Portal Android SDK uses a structured 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..599 range are reported as null for the same reason.
  • The request returned 200 OK but 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.
Treat 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 of 9.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.
Treat a null result as “this device has no usable share” rather than strictly “no share exists”. Code that relied on the throw to detect “no wallet on this device” will now take the success path with a null value instead of entering its catch block, so check the result for null explicitly.
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 generic Error, 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(...) and Exception(...) throws have been replaced with specific PortalException subclasses.
  • Flat exception classes (SendAssetArgumentError, PortalHttpUnauthorizedException, PasskeyNotSupportedException, PasskeyStorageNotConfiguredException, MpcError, InvalidWalletStateError, StorageAuthError) now extend their corresponding PortalException subcategory. Your existing catch blocks will still work, but you should migrate to the new types.

Before and after

Deprecated classes mapping