Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Dart (3.13)  Module 10 – Errors and exceptions

Dart exceptions and errors: try, catch, finally

Throw and catch Dart exceptions with try, on, catch and finally, tell an Exception from an Error, keep stack traces and write custom exceptions.

  • Beginner
  • 10 minutes
  • Examples run with dart 3.13.5 and Python 3.14.8
  • By MySmartCoPilot
Free lesson, Pro answers: 10 minutes
Free: the whole lesson. With a pass: the model answers to its 2 interview questions.

What you will learn

  • Catch the failures you expect with try, on, catch and finally
  • Distinguish an Exception, which a program handles, from an Error, which reports a bug
  • Read a stack trace and keep it intact when you rethrow or wrap an exception
  • Write a custom exception that names the field, the input and the reason

Before you start

What you’ll learn · Before you start

What you will learn

  • Catch the failures you expect with try, on, catch and finally
  • Distinguish an Exception, which a program handles, from an Error, which reports a bug
  • Read a stack trace and keep it intact when you rethrow or wrap an exception
  • Write a custom exception that names the field, the input and the reason

Before you start

On this page

An order file of 1,200 lines arrives with 12a typed in the quantity column of line 37. One importer stops at that line with a stack trace; a second skips it, says why and imports the other 1,199; a third imports everything with a wrong total and no message at all. The difference between them is a few lines of try, on, catch and finally.

This lesson follows one failure from the line that throws it to the line that handles it, in programs that ran on Dart 3.13.5. You will catch only the failures you expect, tell an Exception from an Error, read a stack trace and keep it when you pass a failure on, and write an exception that says exactly what was wrong.

A throw leaves every function until a catch takes it

Here is an importer with no error handling at all. Its second line says pads,12a:

crash.dart: an importer with no error handling Dart · crash.dart
// An order importer with no error handling. The second line's quantity is not a number.
const lines = ['pens,3', 'pads,12a', 'clips,5'];

int parseQuantity(String text) => int.parse(text);

int totalItems(List<String> lines) {
  var total = 0;
  for (final line in lines) {
    final [item, qty] = line.split(',');
    total += parseQuantity(qty);
    print('$item: $qty');
  }
  return total;
}

void main() {
  print('total: ${totalItems(lines)}');
}

Output (exit status 255)

pens: 3

Printed as an error (standard error)

Unhandled exception:
FormatException: Invalid radix-10 number (at character 1)
12a
^

#0      int._handleFormatError (dart:core-patch/integers_patch.dart:150:5)
#1      int._parseRadix (dart:core-patch/integers_patch.dart:179:14)
#2      int._parse (dart:core-patch/integers_patch.dart:121:12)
#3      int.parse (dart:core-patch/integers_patch.dart:81:12)
#4      parseQuantity (crash.dart:4:39)
#5      totalItems (crash.dart:10:14)
#6      main (crash.dart:17:19)
#7      _delayEntrypointInvocation.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:313:19)
#8      _RawReceivePort._handleMessage (dart:isolate-patch/isolate_patch.dart:192:12)

Recorded with Dart SDK 3.13.5 (stable) on macos_arm64 on macOS 26 arm64. To run it yourself: dart run crash.dart

The program printed pens: 3 and then stopped. int.parse('12a') could not read a whole number, so it threw an exception: An object a Dart program throws to report a failure it is expected to handle, such as text that does not parse or a file that is missing. Classes that implement Exception can be caught by type with an on clause., a FormatException. Nothing caught it, so it became an unhandled exception: An exception that no catch clause took. A Dart command line program then prints Unhandled exception, the message and the stack trace on standard error, and exits with code 255.: the runtime printed Unhandled exception:, the message with a caret under the character it could not read, and a stack trace: The list of calls that were running when a value was thrown, newest first, each with its file, line and column. Dart prints it for an unhandled exception and hands it to catch (e, s) as a StackTrace., all on standard error: A program's second output stream, meant for problems and diagnostics; > redirects standard output only, so standard error stays on the terminal. More in “Your first Dart program and project layout”. Then it ended the program with the exit status: The number a program hands to the shell when it ends: 0 for success, anything else for a problem. Dart sets it from exitCode, or from the value given to exit(). More in “Your first Dart program and project layout” 255, which the dart command uses for a runtime error (254 is its code for a compile-time error, the dart:io documentation says).

Read the trace from the top: frames #0 to #3 are inside int.parse, then come the program’s own frames, parseQuantity (crash.dart:4:39), totalItems (crash.dart:10:14) and main (crash.dart:17:19): file, line and column of each call on the way to the throw. Step through the same run and watch the throw leave each function:

Interactive · step by step A FormatException climbing out of crash.dart Step through the run of crash.dart. Watch the throw leave each function that has no catch, then end the program.
A FormatException climbing out of crash.dart, step 8 of 8. Showing main() (left, no catch), totalItems() (left at line 2), parseQuantity() (left, no catch), int.parse() (left), Dart runtime (exit code 255). In focus: Dart runtime.callscallscallsmain()left, no catchtotalItems()left at line 2parseQuantity()left, no catchint.parse()leftDart runtimeexit code 255

The drawing shows the whole scene; its 8 steps are listed below.

Every step as text
  1. main calls totalItems. main calls totalItems with the three order lines. Each call puts a frame on the call stack, which records the function that runs and where to go back to when it returns.
  2. Line 1 adds 3. For pens,3 the function parseQuantity returns 3, totalItems adds it to total and prints pens, 3. Nothing has been thrown yet, so every call returns normally.
  3. Line 2 reaches int.parse. For pads,12a totalItems calls parseQuantity, which calls int.parse with the text 12a. Three frames of this program are now on the stack, with int.parse on top.
  4. int.parse throws. 12a is not a whole number, so int.parse throws a FormatException. No later line of int.parse runs. The runtime now looks for a catch, starting with the function that threw.
  5. No catch in parseQuantity. parseQuantity has no try, so the exception leaves it at once, with no return value. Its frame comes off the stack.
  6. No catch in totalItems. totalItems has no try either. It leaves in the middle of line 2, so clips,5 is never read and total is never returned or printed.
  7. No catch in main. main has no try, so the exception leaves main as well. With no frame of the program left to catch it, the runtime takes it.
  8. The program ends with 255. The runtime prints Unhandled exception, the message and the stack trace on standard error, and ends the program with exit code 255. The trace lists the frames from int.parse down to main.

A throw never returns a value. Each function between the throw and a catch stops at the line it was running, which is why clips,5 was never read and total: was never printed. The search for a catch starts in the function that threw and moves to its caller, then to that caller’s caller, until it reaches main and the runtime.

Checkpoint

Check: A throw climbs the call stack

  1. Question 1 of 2 crash.dart reads pens,3 then pads,12a then clips,5 and has no try anywhere. What does it print on standard output?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: Only pens: 3

    A throw never returns. Every function between the throw and a catch stops at the line it was running, so the only line printed was the one before the throw.

  2. Question 2 of 2 With which exit code does the dart command end a program whose exception nobody caught?

    Type a number.

    Show the answer: Question 2 of 2

    Answer: 255

    The dart:io documentation lists 254 for a compile-time error and 255 for a runtime error, an unhandled exception. crash.dart ended with 255.

on chooses which failures a catch handles

The importer can do better at line 2: report the line and carry on. Put the call that may fail inside try, and add an on clause for the type of failure you expect:

import_lines.dart: skipping a line whose quantity is not a number Dart · import_lines.dart
// The same importer, now handling the one failure it expects: a quantity that is not a whole number.
const lines = ['pens,3', 'pads,12a', 'clips,5', 'tape,-'];

int parseQuantity(String text) => int.parse(text);

void main() {
  var total = 0;
  var skipped = 0;
  for (final (index, line) in lines.indexed) {
    final [item, qty] = line.split(',');
    try {
      total += parseQuantity(qty);
      print('line ${index + 1}: $item x $qty');
    } on FormatException catch (e) {
      skipped++;
      print('line ${index + 1}: skipped, "${e.source}" is not a whole number (${e.message})');
    }
  }
  print('total: $total items, $skipped lines skipped');
}

Output

line 1: pens x 3
line 2: skipped, "12a" is not a whole number (Invalid radix-10 number)
line 3: clips x 5
line 4: skipped, "-" is not a whole number (Invalid number)
total: 8 items, 2 lines skipped

Recorded with Dart SDK 3.13.5 (stable) on macos_arm64 on macOS 26 arm64. To run it yourself: dart run import_lines.dart

on FormatException catch (e) handles exactly that type and gives the caught object the name e, so the message can use e.source (the text that failed) and e.message. Both bad lines, 12a and -, were skipped with a reason, and the total of 8 counts only the lines that were read. on without catch handles the type without naming it, and catch (e, s) adds the stack trace as s.

A try can have several clauses. Dart tries them from the top and the first one whose type matches the thrown object handles it, so put specific types before general ones. Four actions go through the same clauses here:

which_clause.dart: the first matching clause wins Dart · which_clause.dart
// Four actions, one try statement: the first clause whose type matches the thrown object handles it.
import 'dart:async';

void attempt(String label, void Function() action) {
  try {
    action();
    print('$label: nothing thrown');
  } on FormatException catch (e) {
    print('$label: on FormatException, ${e.message}');
  } on Exception catch (e) {
    print('$label: on Exception, a ${e.runtimeType}');
  } catch (e) {
    print('$label: catch with no on, a ${e.runtimeType}');
  }
}

void main() {
  attempt('weight "7 kg"', () => int.parse('7 kg'));
  attempt('stock lookup', () => throw TimeoutException('stock lookup', const Duration(seconds: 2)));
  attempt('empty cart', () => <String>[].first);
  attempt('weight "42"', () => int.parse('42'));
}

Output

weight "7 kg": on FormatException, Invalid radix-10 number
stock lookup: on Exception, a TimeoutException
empty cart: catch with no on, a StateError
weight "42": nothing thrown

Recorded with Dart SDK 3.13.5 (stable) on macos_arm64 on macOS 26 arm64. To run it yourself: dart run which_clause.dart

7 kg matched on FormatException. The TimeoutException is not a FormatException, but it is an Exception, so the second clause took it. The empty cart threw a StateError, which is neither, and only the last clause, a catch with no on, takes anything at all. 42 threw nothing, so no clause ran.

A catch with no on around ordinary code

try { … } catch (e) { print('bad input'); } also catches the StateError of the empty cart above, a bug, and reports it as bad input. Write on FormatException catch (e) for the failure you expect, so a bug still stops the program where you will see it. Effective Dart lists this as “avoid catches without on clauses”.

Checkpoint

Check: Choosing failures with on and catch

  1. Question 1 of 2 A try has on FormatException, then on Exception, then catch (e). Which clause handles a TimeoutException?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: on Exception

    Dart tries the clauses from the top and the first one whose type matches the thrown object handles it, as which_clause.dart showed for the stock lookup.

  2. Question 2 of 2 Someone moves on Exception above on FormatException in which_clause.dart. What changes for weight "7 kg"?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: on Exception now handles it, and the FormatException clause never runs for anything

    Order clauses from specific to general. The analyzer warns about a clause that can never be reached, but the rule to remember is first match wins.

Exception means handle it, Error means fix the code

Dart throws two families of objects. An Exception reports a condition a program can expect and handle: bad input, a missing file, a slow network. The API documentation says an exception is “intended to be caught” and should carry useful data. An Error (programming error): An object of the Error class or a subtype, such as StateError, RangeError or TypeError, thrown when code does something its author should have avoided. It reports a bug to fix, not a condition to catch. reports a bug, a mistake the programmer should have avoided, and the Error class page of the API documentation says a caller should not expect or catch one. The two families side by side:

Exception Error
Common types FormatException, IOException, TimeoutException StateError, RangeError, ArgumentError, TypeError, UnsupportedError
Thrown when text cannot be parsed, a file or socket fails, a wait runs out of time the code asked for something impossible
What to do catch it where the program can recover or report it fix the code; let it reach the top in normal flow

These six bugs each throw an Error. The program catches them only to print them, so you can learn their messages:

messages.dart: six bugs and the Error each throws Dart · messages.dart
// Six bugs and the Error each one throws. The catch is here only to print them:
// real code lets an Error reach the top, where it ends the program and points at the bug.
class Cart {
  late int itemCount;
}

String kind(Error e) => switch (e) {
  RangeError() => 'RangeError',
  ArgumentError() => 'ArgumentError',
  StateError() => 'StateError',
  TypeError() => 'TypeError',
  UnsupportedError() => 'UnsupportedError',
  _ => 'Error subtype',
};

void show(String bug, Object? Function() code) {
  try {
    code();
  } on Error catch (e) {
    print('$bug\n  ${kind(e)}: $e');
  }
}

void main() {
  String? coupon;
  final Object id = 'A-17';
  show('first item of an empty list', () => <int>[].first);
  show('index 3 of a 3-item list', () => [10, 20, 30][3]);
  show('! on a null coupon', () => coupon!.length);
  show('reading a late field never set', () => Cart().itemCount);
  show('casting a String id to int', () => id as int);
  show('adding to an unmodifiable list', () => List.unmodifiable([1]).add(2));
}

Output

first item of an empty list
  StateError: Bad state: No element
index 3 of a 3-item list
  RangeError: RangeError (length): Invalid value: Not in inclusive range 0..2: 3
! on a null coupon
  TypeError: Null check operator used on a null value
reading a late field never set
  Error subtype: LateInitializationError: Field 'itemCount' has not been initialized.
casting a String id to int
  TypeError: type 'String' is not a subtype of type 'int' in type cast
adding to an unmodifiable list
  UnsupportedError: Unsupported operation: Cannot add to an unmodifiable list

Recorded with Dart SDK 3.13.5 (stable) on macos_arm64 on macOS 26 arm64. To run it yourself: dart run messages.dart

Learn to recognise these lines: Bad state: No element is .first on an empty list, RangeError (length) an index past the end, and Null check operator used on a null value a ! on null (see sound null safety). LateInitializationError is a late variable read before it was set, and the cast message names both types. The late one has no public type of its own: it is an internal subtype of Error, so the switch printed it as Error subtype.

Key idea

Catch an Exception where your code can do something sensible about it; let an Error reach the top, because its fix is a change to the code, not a catch.

Checkpoint

Check: Exception or Error

  1. Question 1 of 2 Which of these are Errors, bugs to fix rather than conditions to handle?

    Choose every answer that is right.

    Show the answer: Question 1 of 2

    Answer:

    • RangeError from index 3 of a 3-item list
    • StateError from first on an empty list

    Errors report mistakes the programmer should have avoided; Exceptions report conditions the program should expect and handle.

  2. Question 2 of 2 Your report screen throws Bad state: No element when the orders list is empty. What is the right change?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: Check orders.isEmpty, or use firstOrNull, before taking the first order

    An Error points at a line to change. Catching it makes the symptom go away and keeps the mistake.

finally runs on every way out, except exit()

Some work must happen however a block ends: a file closed, a lock released, a spinner stopped. A finally clause: The block after try and its catch clauses that runs on every way out of the try: normal completion, a caught exception or one nobody caught. exit() from dart:io skips it. runs after the try block and after any catch that ran, and also when nothing caught the failure. This program opens two files, and the second one holds 12a:

finally_close.dart: a file closed on success and on failure Dart · finally_close.dart
// finally closes the file whether the sum finishes or a line fails to parse.
import 'dart:convert';
import 'dart:io';

int sumQuantities(String path) {
  final file = File(path).openSync();
  print('opened $path');
  try {
    final text = utf8.decode(file.readSync(file.lengthSync()));
    var total = 0;
    for (final line in const LineSplitter().convert(text)) {
      total += int.parse(line.split(',')[1]);
    }
    return total;
  } finally {
    file.closeSync();
    print('closed $path');
  }
}

void main() {
  File('orders.csv').writeAsStringSync('pens,3\npads,12\nclips,5\n');
  File('orders_bad.csv').writeAsStringSync('pens,3\npads,12a\nclips,5\n');

  print('good file: ${sumQuantities('orders.csv')} items');
  try {
    sumQuantities('orders_bad.csv');
  } on FormatException catch (e) {
    print('bad file: ${e.message}, "${e.source}"');
  }
}

Output

opened orders.csv
closed orders.csv
good file: 20 items
opened orders_bad.csv
closed orders_bad.csv
bad file: Invalid radix-10 number, "12a"

Recorded with Dart SDK 3.13.5 (stable) on macos_arm64 on macOS 26 arm64. To run it yourself: dart run finally_close.dart

Look at the order of the last three lines: closed orders_bad.csv came before bad file:. The finally block ran while the FormatException was on its way out of sumQuantities, then main caught it. Without finally, the failing run would have left the file open.

There is one way out that skips finally: exit() from dart:io ends the process on the spot. This program calls it inside a try:

exit_skips_finally.dart: exit() ends the process at once Dart · exit_skips_finally.dart
// exit() ends the process on the spot: no finally block runs, and no later line either.
import 'dart:io';

void main() {
  try {
    print('writing the report');
    exit(2);
  } finally {
    print('finally: report file closed');
  }
}

Output (exit status 2)

writing the report

Recorded with Dart SDK 3.13.5 (stable) on macos_arm64 on macOS 26 arm64. To run it yourself: dart run exit_skips_finally.dart

The program stopped with exit code 2 and never printed its finally line. The dart:io documentation warns that exit runs no finally blocks and does not wait for asynchronous work, so it is likely to lose data. To end a command line program with a code of your choice, set exitCode from dart:io and let main return normally.

Checkpoint

Check: finally on every way out

  1. Question 1 of 2 In finally_close.dart, why does closed orders_bad.csv print before bad file?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: finally runs as the exception leaves sumQuantities, before main catches it

    A finally clause runs on every way out of its try, including an exception that nobody in that function caught.

  2. Question 2 of 2 A try block calls exit(2) from dart:io. Does its finally block run?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: No, exit ends the process at once

    Set exitCode and return from main instead when cleanup must still run.

rethrow keeps the stack trace that throw e replaces

Sometimes a function catches a failure only to log it or count it, and then passes it on. How you pass it on decides what the stack trace shows. catch (e, stack) gives you the trace as a StackTrace. This program passes the same FormatException on in three ways and prints the first frame of its own file in each trace:

traces.dart: rethrow, throw e and throwWithStackTrace Dart · traces.dart
// Three ways to pass a FormatException on, and the first frame of this file each stack trace shows.
int parseQty(String s) => int.parse(s);

int withRethrow(String s) {
  try {
    return parseQty(s);
  } on FormatException {
    rethrow;
  }
}

int withThrowE(String s) {
  try {
    return parseQty(s);
  } on FormatException catch (e) {
    throw e;
  }
}

int withWrap(String s) {
  try {
    return parseQty(s);
  } on FormatException catch (_, stack) {
    Error.throwWithStackTrace(ArgumentError.value(s, 'qty', 'not a whole number'), stack);
  }
}

void main() {
  final ways = [('rethrow', withRethrow), ('throw e', withThrowE), ('throwWithStackTrace', withWrap)];
  for (final (label, parse) in ways) {
    try {
      parse('7x');
    } catch (e, stack) {
      final frame = stack.toString().split('\n').firstWhere((l) => l.contains('traces.dart'));
      print('$label: ${e.runtimeType}');
      print('  first frame here: ${frame.substring(frame.indexOf(' ')).trim()}');
    }
  }
}

Output

rethrow: FormatException
  first frame here: parseQty (traces.dart:2:31)
throw e: FormatException
  first frame here: withThrowE (traces.dart:16:5)
throwWithStackTrace: ArgumentError
  first frame here: parseQty (traces.dart:2:31)

Recorded with Dart SDK 3.13.5 (stable) on macos_arm64 on macOS 26 arm64. To run it yourself: dart run traces.dart

With rethrow: A statement inside a catch block that throws the caught object again with its original stack trace, so the trace still starts where the failure happened. the trace still starts in parseQty, at line 2, where int.parse failed. throw e throws the same object again but starts a new trace at its own line, 16, so whoever reads the log learns where it was thrown again, not where it went wrong. Effective Dart’s rule is short: use rethrow to pass a caught exception on.

To pass on a different object, use Error.throwWithStackTrace(newObject, stack) (in dart:core since Dart 2.16). The third run threw an ArgumentError that names the parameter, and its trace still starts at line 2. That is how to change what a failure says without losing where it came from.

Read the first frames that are yours

The top frames of a trace are often inside the SDK (int._handleFormatError, int.parse). Scan down to the first frame in your own file: that is the line to read first.

Checkpoint

Check: Stack traces, rethrow and wrapping

  1. One question traces.dart passes a FormatException on with throw e. Where does the new stack trace start?

    Choose one answer.

    Show the answer: One question

    Answer: At the throw e line inside withThrowE

    rethrow and Error.throwWithStackTrace keep the original first frame, parseQty at line 2; throw e moves it to the catch block.

A custom exception names the field, the input and the reason

FormatException: Invalid radix-10 number tells a developer something, but a form needs to say which field failed and what to do. Dart can throw any non-null object, even a string, but a class that implements Exception can be caught by type and carry data. Here is one for a coupon field:

coupon.dart: a CouponException with field, input and reason Dart · coupon.dart
// A custom exception that says which field failed, what was typed and why.
class CouponException implements Exception {
  CouponException(this.field, this.input, this.reason);

  final String field;
  final String input;
  final String reason;

  @override
  String toString() => 'CouponException($field): "$input" $reason';
}

final _allowed = RegExp(r'^[A-Z0-9]+$');

String readCoupon(String input, {String field = 'coupon'}) {
  final code = input.trim().toUpperCase();
  if (code.length < 4 || code.length > 12) {
    throw CouponException(field, input, 'has ${code.length} characters; use 4 to 12');
  }
  if (!_allowed.hasMatch(code)) {
    throw CouponException(field, input, 'may hold only letters and digits');
  }
  return code;
}

void main() {
  for (final typed in ['festive50', ' new10 ', 'diwali 50', 'ok', 'SAVE-20']) {
    try {
      print('accepted ${readCoupon(typed)}');
    } on CouponException catch (e) {
      print('refused: $e');
    }
  }
}

Output

accepted FESTIVE50
accepted NEW10
refused: CouponException(coupon): "diwali 50" may hold only letters and digits
refused: CouponException(coupon): "ok" has 2 characters; use 4 to 12
refused: CouponException(coupon): "SAVE-20" may hold only letters and digits

Recorded with Dart SDK 3.13.5 (stable) on macos_arm64 on macOS 26 arm64. To run it yourself: dart run coupon.dart

Each refusal names the field, shows what was typed and gives a reason a person can act on: too short, or a character that is not allowed. The class implements Exception, so on CouponException catches exactly these and on Exception catches them along with the others. A thrown string can be caught only by a catch with no on, which is why the only_throw_errors lint asks you to throw only classes that extend Exception or Error. A custom exception: A class of your own that implements Exception and carries data about a failure, such as the field, the input and the reason, so callers can catch it by type and act on it. also gets a precise type: the Exception documentation discourages library code from throwing a plain Exception("message") for exactly that reason.

Checkpoint

Check: Custom exceptions with context

  1. One question readCoupon throws CouponException, which implements Exception. Which clause does NOT catch it?

    Choose one answer.

    Show the answer: One question

    Answer: on FormatException

    A custom exception can be caught by its own type or by any type it implements, here Exception.

Practise: a quantity parser with sample tests

The package below holds a QuantityException like the coupon one, a parseQuantity that is not written yet, and four sample tests:

The quantities package: a stub for parseQuantity and four sample tests

practice/pubspec.yaml

name: quantities
description: parseQuantity to write, with its exception type and four sample tests.
publish_to: none

environment:
  sdk: ^3.13.0

dev_dependencies:
  test: ^1.33.0

lib/quantities.dart · practice/lib/quantities.dart

/// Thrown when a typed quantity cannot be used: says which field, what was typed and why.
class QuantityException implements Exception {
  QuantityException(this.field, this.input, this.reason);

  final String field;
  final String input;
  final String reason;

  @override
  String toString() => 'QuantityException($field): "$input" $reason';
}

/// Reads a quantity of 1 to 999 typed into [field], ignoring spaces around it.
///
/// Throws a [QuantityException] for anything else.
int parseQuantity(String input, {String field = 'quantity'}) {
  throw UnimplementedError('parseQuantity');
}

test/quantities_test.dart · practice/test/quantities_test.dart

import 'package:quantities/quantities.dart';
import 'package:test/test.dart';

void main() {
  test('a plain number is read', () {
    expect(parseQuantity('3'), 3);
  });

  test('spaces around the number are ignored', () {
    expect(parseQuantity(' 12 '), 12);
  });

  test('text that is not a whole number names its field', () {
    expect(
      () => parseQuantity('12a', field: 'pads'),
      throwsA(isA<QuantityException>().having((e) => e.field, 'field', 'pads')),
    );
  });

  test('zero is outside 1 to 999', () {
    expect(
      () => parseQuantity('0'),
      throwsA(isA<QuantityException>().having((e) => e.reason, 'reason', contains('1 to 999'))),
    );
  });
}

As handed out, parseQuantity throws UnimplementedError, so every sample test fails:

dart test on the quantities package as handed out Python · practice_session.py
from session import files, run

# The quantities package as handed out: parseQuantity still throws, so all four sample tests fail.
files("practice=quantities")
run(
    """
cd quantities
dart pub get --offline > /dev/null
!dart test --reporter failures-only
""",
    pub=True,
)

Output

$ cd quantities
$ dart pub get --offline > /dev/null
$ dart test --reporter failures-only
+0 -1: test/quantities_test.dart: a plain number is read [E]
  UnimplementedError: parseQuantity
  package:quantities/quantities.dart 17:3  parseQuantity
  test/quantities_test.dart 6:12           main.<fn>

+0 -2: test/quantities_test.dart: spaces around the number are ignored [E]
  UnimplementedError: parseQuantity
  package:quantities/quantities.dart 17:3  parseQuantity
  test/quantities_test.dart 10:12          main.<fn>

+0 -3: test/quantities_test.dart: text that is not a whole number names its field [E]
  Expected: throws <Instance of 'QuantityException'> with `field`: 'pads'
    Actual: <Closure: () => int>
     Which: threw UnimplementedError:<UnimplementedError: parseQuantity>
            stack package:quantities/quantities.dart 17:3  parseQuantity
                  test/quantities_test.dart 15:13          main.<fn>.<fn>
                  package:matcher                          expect
                  test/quantities_test.dart 14:5           main.<fn>

            which is not an instance of 'QuantityException'

  package:matcher                 expect
  test/quantities_test.dart 14:5  main.<fn>

+0 -4: test/quantities_test.dart: zero is outside 1 to 999 [E]
  Expected: throws <Instance of 'QuantityException'> with `reason`: contains '1 to 999'
    Actual: <Closure: () => int>
     Which: threw UnimplementedError:<UnimplementedError: parseQuantity>
            stack package:quantities/quantities.dart 17:3  parseQuantity
                  test/quantities_test.dart 22:13          main.<fn>.<fn>
                  package:matcher                          expect
                  test/quantities_test.dart 21:5           main.<fn>

            which is not an instance of 'QuantityException'

  package:matcher                 expect
  test/quantities_test.dart 21:5  main.<fn>

+0 -4: Some tests failed.

Consider enabling the flag chain-stack-traces to receive more detailed exceptions.
For example, 'dart test --chain-stack-traces'.
[exit status 1]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 practice_session.py

  1. Copy the three files into a folder of your own, run dart pub get, then dart test: four failures, as above.
  2. Trim the input and read it with int.parse inside a try. Turn its FormatException into a QuantityException that names the field and says the text is not a whole number.
  3. Refuse values below 1 and above 999 with a reason that contains 1 to 999. Run dart test until all four succeed.
  4. Add tests of your own for '', '+5' and '99999999999999999999'. int.parse reads +5 as 5 and throws a FormatException for the last one, so decide what your function should do with each and test it.

Then check what you have learned, one question at a time, the idea you know least first:

Practice that adapts to you

Practise this lesson

12 questions about 6 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 12 What happens to the rest of a function when a call inside it throws and the function has no try?

    Choose one answer.

    Show the answer: Question 1 of 12

    Answer: It stops at that line and the exception moves to its caller

    A throw leaves each function in turn, starting with the one that threw, until a matching catch takes it.

  2. Question 2 of 12 A trace shows #4 parseQuantity (crash.dart:4:39), #5 totalItems (crash.dart:10:14), #6 main (crash.dart:17:19). Which line called parseQuantity?

    Choose one answer.

    Show the answer: Question 2 of 12

    Answer: Line 10 of crash.dart, in totalItems

    Read a trace from the throw downwards: each frame is a function and the line it was on when the failure passed through.

  3. Question 3 of 12 What does catch (e, s) after on FormatException give you?

    Choose one answer.

    Show the answer: Question 3 of 12

    Answer: The FormatException as e and its stack trace as s

    on chooses the type, catch names the object, and a second name in catch receives the stack trace.

  4. Question 4 of 12 import_lines.dart reads pens,3 / pads,12a / clips,5 / tape,-. What total does it print?

    Choose one answer.

    Show the answer: Question 4 of 12

    Answer: 8, from the two lines that were read

    With the try inside the loop, a failure ends only the current pass, and the loop moves to the next line.

  5. Question 5 of 12 Which of these is an Exception, a condition a program is expected to handle?

    Choose one answer.

    Show the answer: Question 5 of 12

    Answer: IOException from a file that is missing

    FormatException, IOException and TimeoutException are the everyday Exceptions; RangeError, StateError and TypeError are bugs.

  6. Question 6 of 12 A service crashes with Null check operator used on a null value. A teammate proposes on TypeError catch (e) around the call. What do you answer?

    Choose one answer.

    Show the answer: Question 6 of 12

    Answer: Find the ! that met null and handle the missing value there, for example with ?? or a check

    Errors are fixed in the code. The trace points at the line with the !, and the null case needs a decision there.

  7. Question 7 of 12 In which of these cases does a finally block run?

    Choose every answer that is right.

    Show the answer: Question 7 of 12

    Answer:

    • A catch clause of the same try handles an exception
    • An exception leaves the try and nothing in the function catches it
    • The try block finishes normally

    Every way out of a try runs its finally block, except ending the whole process with exit.

  8. Question 8 of 12 A command line tool must close its log file and still end with status 3 when an import fails. What do you write?

    Choose one answer.

    Show the answer: Question 8 of 12

    Answer: Close the file in finally, set exitCode = 3 and let main return

    exitCode from dart:io records the status for when main ends; exit stops everything on the spot.

  9. Question 9 of 12 Which way of passing a caught exception on keeps its original stack trace?

    Choose one answer.

    Show the answer: Question 9 of 12

    Answer: rethrow

    Effective Dart asks for rethrow when you pass a caught exception on.

  10. Question 10 of 12 A parser catches a FormatException and must throw an ArgumentError instead, without losing where the failure began. What does it call?

    Choose one answer.

    Show the answer: Question 10 of 12

    Answer: Error.throwWithStackTrace(ArgumentError.value(...), stack)

    Error.throwWithStackTrace (dart:core, since Dart 2.16) changes the object and keeps the trace.

  11. Question 11 of 12 What should a custom exception for a form field carry?

    Choose one answer.

    Show the answer: Question 11 of 12

    Answer: The field, the text that was typed and the reason it was refused

    The Exception documentation says an exception should contain useful data fields; CouponException carries field, input and reason.

  12. Question 12 of 12 A library does throw 'bad coupon' for a refused code. Which caller code can catch it?

    Choose one answer.

    Show the answer: Question 12 of 12

    Answer: Only a catch with no on clause

    Dart can throw any non-null object, but only classes that extend Exception or Error can be caught by type, which is what the only_throw_errors lint asks for.

Interview questions

Warm-up (fresher): Exception or Error, which one belongs in a catch clause?

An Exception reports a failure the program can expect and do something about: a FormatException from text that does not parse, an IOException from a file that is missing, a TimeoutException from a call that took too long. Catch those with an on clause where you can recover, retry or tell the user. An Error such as StateError, RangeError or TypeError reports a bug: the code asked for .first of an empty list or read an index past the end. The Error documentation says callers should not expect or catch them, because the fix is in the code. Catching one hides the bug; letting it reach the top gives a stack trace that points at the line to fix.

Warm-up (fresher): Why write rethrow instead of throw e inside a catch block?

Both send the same object on, but they leave different stack traces. rethrow keeps the original trace, so it still starts at the line where the failure happened, inside the function that threw. throw e starts a new trace at the throw itself, so the log points at the catch block instead of the cause. In traces.dart the first frame was parseQty at line 2 with rethrow and withThrowE at line 16 with throw e. To throw a different object while keeping the original trace, use Error.throwWithStackTrace.

Two longer questions start from these ideas and ask for a policy and a design:

Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.

  1. Mid to senior

    A nightly job imports 40,000 supplier records, each inside try { ... } catch (e) { log(e); }. For three weeks an off-by-one RangeError skipped about 4 % of the records and nobody noticed. Propose a catch policy for the job and say how you would roll it out.

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Mid to senior

    A file-import library wraps every failure in its own OrderFileException(message). Since that change, production traces start inside the wrapper, and nobody can tell which parser or which line of the file failed. Fix the wrapper while keeping OrderFileException as the type callers catch.

    Model answer, Pro. First step and follow-ups

Flashcards · 8 cards

Recall it, then check

Say the answer to yourself before you look: recalling it is what makes it stick.

  1. What happens when nothing catches a thrown exception in a Dart program?

    The throw leaves the function that threw and every caller in turn. With no catch, the program prints "Unhandled exception:" and a stack trace and ends with exit code 255.

  2. What does on FormatException catch (e, s) do, and in which order are clauses tried?

    It handles one type of failure and names the thrown object and its stack trace. Clauses are tried from the top, so specific types go first; a catch with no on takes everything.

  3. Why avoid a catch with no on clause around ordinary code?

    It also catches bugs, such as the StateError of an empty cart, and reports them as bad input. Write on FormatException catch (e) for the failure you expect, so a bug still stops the program. Effective Dart: "avoid catches without on clauses".

  4. Which belongs in a catch clause, an Exception or an Error?

    An Exception, a failure the program can expect and handle, such as FormatException or TimeoutException. An Error such as StateError or RangeError reports a bug: its fix is a change to the code, so normal code does not catch it.

  5. When does a finally block run, and when does it not?

    On every way out of a try, an exception nobody caught included. exit() ends the process without running it.

  6. Why write rethrow instead of throw e in a catch block?

    rethrow keeps the original stack trace, starting where the failure happened; throw e starts a new trace at the catch block. Error.throwWithStackTrace throws a new object with the old trace.

  7. What should a custom exception class carry?

    It implements Exception and carries the field, the input and the reason, so callers can catch it by type and show a message a person can act on.

  8. Where do you start reading a long stack trace?

    At the first frame in your own file. The top frames are often inside the SDK, such as int._handleFormatError and int.parse.

Key takeaways

  • A throw stops the function that threw and every caller in turn until a matching catch takes it; with no catch, a Dart program prints Unhandled exception: and a stack trace and ends with exit code 255.
  • on Type catch (e, s) handles one type of failure and names the object and its stack trace; clauses are tried from the top, so specific types go first, and a catch with no on takes everything, bugs included.
  • An Exception is a condition to handle; an Error such as StateError or RangeError is a bug to fix, so normal code does not catch it.
  • finally runs on every way out of a try, including an exception nobody caught, but exit() ends the process without running it.
  • rethrow keeps the original stack trace, throw e starts a new one, and Error.throwWithStackTrace throws a new object with the old trace.
  • A custom exception implements Exception and carries the field, the input and the reason, so callers can catch it by type and show a message a person can act on.

Terms in this lesson

custom exception
A class of your own that implements Exception and carries data about a failure, such as the field, the input and the reason, so callers can catch it by type and act on it.
Error (programming error)
An object of the Error class or a subtype, such as StateError, RangeError or TypeError, thrown when code does something its author should have avoided. It reports a bug to fix, not a condition to catch.
exception
An object a Dart program throws to report a failure it is expected to handle, such as text that does not parse or a file that is missing. Classes that implement Exception can be caught by type with an on clause.
finally clause
The block after try and its catch clauses that runs on every way out of the try: normal completion, a caught exception or one nobody caught. exit() from dart:io skips it.
rethrow
A statement inside a catch block that throws the caught object again with its original stack trace, so the trace still starts where the failure happened.
stack trace
The list of calls that were running when a value was thrown, newest first, each with its file, line and column. Dart prints it for an unhandled exception and hands it to catch (e, s) as a StackTrace.
unhandled exception
An exception that no catch clause took. A Dart command line program then prints Unhandled exception, the message and the stack trace on standard error, and exits with code 255.

References

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.