Exceptions and error handling

Dart · Essentials · 10 min

Parsing, I/O and network calls all throw, and catching the right type keeps apps from crashing or hiding bugs.

Dart can throw any non-null object, but by convention you throw:

  • `Exception` subtypes for expected, recoverable failures (bad input, network down). FormatException, IOException and TimeoutException are common built-ins.
  • `Error` subtypes for programming bugs (RangeError, StateError, ArgumentError, TypeError). Generally don't catch these; fix the code.

Handling:

try {
  final n = int.parse(input);
} on FormatException catch (e) {
  print('bad number: ${e.message}');
} catch (e, stackTrace) {
  rethrow;
} finally {
  cleanup();
}

on Type selects by type, catch (e, s) binds the exception and stack trace, and rethrow passes it on unchanged. finally always runs. Order clauses from most specific to most general.

Dart functions don't declare what they throw, so read API docs. Many APIs offer non-throwing variants: int.tryParse returns null instead of throwing.

Define your own exceptions by implementing Exception: class PaymentDeclined implements Exception { ... } with a useful toString.

In async code, errors from awaited futures are caught by the same try/catch. An uncaught exception terminates a command-line program; in Flutter it is reported and the frame continues, but your state may be inconsistent.

Example

class InsufficientFunds implements Exception {
  final int missing;
  InsufficientFunds(this.missing);
  @override String toString() => 'InsufficientFunds: need $missing more';
}
int withdraw(int balance, String input) {
  final amount = int.parse(input);            // may throw FormatException
  if (amount > balance) throw InsufficientFunds(amount - balance);
  return balance - amount;
}
void main() {
  for (final input in ['30', 'abc', '500']) {
    try {
      print('left: ${withdraw(100, input)}');
    } on FormatException catch (e) {
      print('not a number: ${e.source}');
    } on InsufficientFunds catch (e) {
      print(e);
    } finally {
      print('done $input');
    }
  }
  // left: 70 / done 30 / not a number: abc / done abc / InsufficientFunds: need 400 more / done 500
  print(int.tryParse('x') ?? -1);             // -1
}

Remember

  • Throw Exceptions for recoverable problems; Errors mean bugs
  • on Type catch (e, s) catches selectively; rethrow passes it on
  • Prefer non-throwing variants like tryParse for expected bad input

Watch out for

  • A bare catch (e) that swallows everything, including real bugs.

Check yourself

  1. Which clause catches only FormatException and gives access to it?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: dart.dev/language/error-handling