Dart Tutorial

Dart Lesson 63 of 102 4 min read

Exception Handling in Dart: try, catch, on and finally

Learn exception handling in Dart: throw, try, catch, on, finally and rethrow, the difference between Exception and Error, and best practices.

On this page

An exception is Dart’s way of signalling that something went wrong while the program was running. If nobody handles it, the program stops.

An unhandled exception #

void main() {
  var number = int.parse('twelve');
  print(number); // never reached
}
Unhandled exception:
FormatException: Invalid radix-10 number (at character 1)
twelve
^

try and catch #

Put the risky code in a try block. If it throws, control jumps to catch and the program carries on.

void main() {
  try {
    var number = int.parse('twelve');
    print(number);
  } catch (e) {
    print('That was not a number.');
  }
  print('Program continues.');
}
That was not a number.
Program continues.

Catching specific types with on #

A bare catch catches everything, which can hide real bugs. Use on to handle only the problems you expect.

void process(String input, List<int> data) {
  try {
    var index = int.parse(input);
    print('Value: ${data[index]}');
  } on FormatException {
    print('"$input" is not a number');
  } on RangeError {
    print('There is no item at position $input');
  }
}

void main() {
  var data = [10, 20, 30];
  process('1', data);
  process('one', data);
  process('7', data);
}
Value: 20
"one" is not a number
There is no item at position 7

Use on Type catch (e) when you also need the exception object. Clauses are checked in order, so put the most specific type first.

void main() {
  try {
    int.parse('3.7');
  } on FormatException catch (e) {
    print('Bad format: ${e.source}');
  } catch (e) {
    print('Something else: $e');
  }
}
Bad format: 3.7

finally: always runs #

Code in finally runs whether or not an exception was thrown. Use it for clean-up such as closing a file or hiding a loading spinner.

void loadData(bool fail) {
  print('Opening connection');
  try {
    if (fail) throw Exception('Server did not respond');
    print('Data loaded');
  } catch (e) {
    print('Failed: $e');
  } finally {
    print('Closing connection');
  }
}

void main() {
  loadData(false);
  print('---');
  loadData(true);
}
Opening connection
Data loaded
Closing connection
---
Opening connection
Failed: Exception: Server did not respond
Closing connection

Throwing your own #

Use throw when your code detects a situation it cannot handle.

double divide(double a, double b) {
  if (b == 0) {
    throw ArgumentError('Cannot divide by zero');
  }
  return a / b;
}

void main() {
  try {
    print(divide(10, 2));
    print(divide(5, 0));
  } on ArgumentError catch (e) {
    print('Error: ${e.message}');
  }
}
5.0
Error: Cannot divide by zero

throw is an expression, so it can be used after ?? and in arrow functions: final name = input ?? (throw ArgumentError('name is required'));.

The stack trace #

catch can take a second parameter: the stack trace, which lists the calls that led to the problem. Log it; do not show it to users.

void main() {
  try {
    [1, 2, 3].firstWhere((n) => n > 5);
  } catch (e, stackTrace) {
    print('Caught: $e');
    print(stackTrace.toString().split('\n').first);
  }
}

rethrow: handle a little, then pass it on #

Sometimes a function wants to note that something failed but leave the decision to its caller.

int parsePort(String text) {
  try {
    return int.parse(text);
  } on FormatException {
    print('[log] invalid port "$text"');
    rethrow; // same exception, original stack trace
  }
}

void main() {
  try {
    parsePort('80a');
  } on FormatException {
    print('Using default port 8080');
  }
}
[log] invalid port "80a"
Using default port 8080

Exception or Error? #

Dart has two families of throwable objects.

ExceptionError
MeansSomething outside your control failedThere is a bug in the code
ExamplesFormatException, TimeoutException, HttpExceptionRangeError, TypeError, StateError, ArgumentError
What to doCatch it and recoverFix the code so it never happens

A list index out of range is an Error: the right response is to check the length, not to wrap the code in try.

Common built-in types #

TypeThrown when
FormatExceptionText is not in the expected format
RangeErrorAn index or value is outside the allowed range
ArgumentErrorA function received an unacceptable argument
StateErrorAn object is used in the wrong state, such as first on an empty list
TypeErrorA cast with as fails
UnsupportedErrorThe operation is not allowed, such as adding to a fixed-length list

Best practices #

  • Catch what you can handle. If all you can do is ignore it, let it travel up to code that can respond.
  • Be specific. Prefer on FormatException to a bare catch.
  • Never leave a catch block empty. At least log the problem.
  • Prefer checking to catching when it is cheap: tryParse over parse, isEmpty before first.
  • Use finally for clean-up, so it happens on every path.
  • Write messages for the reader. A user needs “Check your internet connection”, not a stack trace.

Try it yourself #

Write double safeDivide(String a, String b) that parses both strings and divides them. Handle text that is not a number and division by zero with separate, friendly messages, and always print Calculation finished at the end.

Practise in the playground Updated by Santosh Adhikari