flutter
/

Dart Exception Handling – Try, Catch, Finally, and Custom Exceptions

Last Sync: Today

On this page

11
0%
5 min read
Remaining
5 minleft

Click any section to jump — progress syncs automatically

flutter

Dart Exception Handling – Try, Catch, Finally, and Custom Exceptions

What are Exceptions?

An exception is an error that occurs during program execution, disrupting the normal flow. Dart provides a robust exception‑handling mechanism to catch and respond to these errors gracefully, preventing crashes. Exceptions can be thrown by the Dart runtime (e.g., division by zero) or explicitly by your code using the throw keyword.

Types of Exceptions: Error vs Exception

Dart distinguishes two kinds of throwable objects:

    • Error – indicates a serious problem that should not be caught and handled; usually bugs in your code (e.g., AssertionError, RangeError).
    • Exception – indicates a recoverable error that you can catch and handle (e.g., IOException, FormatException).

In practice, you can throw any object, but it's best practice to throw Exception or a subclass of it for expected errors.

Throwing an Exception

DARTRead-only
1
void main() {
  throw Exception('Something went wrong');
  // or
  throw FormatException('Invalid number format');
}

Try / Catch / Finally

Use try to enclose code that might throw an exception, catch to handle it, and finally to run code regardless of whether an exception occurred (e.g., cleanup).

DARTRead-only
1
void main() {
  try {
    int result = 10 ~/ 0; // throws IntegerDivisionByZeroException
    print(result);
  } catch (e) {
    print('Caught an exception: $e');
  } finally {
    print('This always runs');
  }
}

Catching Specific Exceptions

You can catch specific exception types using on before the catch clause. This allows you to handle different exceptions differently.

DARTRead-only
1
void main() {
  try {
    int result = 10 ~/ 0;
  } on IntegerDivisionByZeroException {
    print('Cannot divide by zero.');
  } on FormatException catch (e) {
    print('Format error: $e');
  } catch (e, s) {
    print('Unexpected error: $e');
    print('Stack trace: $s');
  }
}

The catch clause can provide two arguments: the exception object and the stack trace. Use on without catch if you only need the type, or use on with catch to get the object.

Rethrowing Exceptions

Sometimes you want to handle an exception partially and then let it propagate further. Use rethrow to throw the same exception again.

DARTRead-only
1
void main() {
  try {
    riskyOperation();
  } catch (e) {
    print('Caught: $e');
  }
}

void riskyOperation() {
  try {
    throw Exception('Something failed');
  } catch (e) {
    print('Logging error');
    rethrow; // let the caller handle it
  }
}

Custom Exceptions

You can define your own exception classes by extending Exception. This makes error handling more expressive.

DARTRead-only
1
class ValidationException implements Exception {
  final String message;
  ValidationException(this.message);

  @override
  String toString() => 'ValidationException: $message';
}

void validateAge(int age) {
  if (age < 0) throw ValidationException('Age cannot be negative');
  if (age > 120) throw ValidationException('Age too high');
}

void main() {
  try {
    validateAge(-5);
  } on ValidationException catch (e) {
    print(e);
  }
}

Asynchronous Error Handling

Errors in asynchronous code can be handled using try/catch inside an async function, or using .catchError on a Future. For streams, use the onError callback in listen() or the handleError transformer.

DARTRead-only
1
Future<String> fetchData() async {
  await Future.delayed(Duration(seconds: 1));
  throw Exception('Network error');
}

void main() async {
  // With try/catch
  try {
    var data = await fetchData();
    print(data);
  } catch (e) {
    print('Caught: $e');
  }

  // With .catchError
  fetchData().then((data) {
    print(data);
  }).catchError((e) {
    print('Caught with catchError: $e');
  });
}

Best Practices

    • Catch only exceptions you can handle. Let others propagate.
    • Use specific exception types for better control.
    • Always clean up resources in finally blocks.
    • In asynchronous code, always handle errors either with try/catch or .catchError.
    • For custom exceptions, extend Exception (or Error if truly unrecoverable).
    • Use rethrow to log or transform an exception before passing it up.

Complete Example

DARTRead-only
1
class NegativeNumberException implements Exception {
  final String message;
  NegativeNumberException(this.message);
  @override
  String toString() => 'NegativeNumberException: $message';
}

double squareRoot(double n) {
  if (n < 0) throw NegativeNumberException('Cannot take sqrt of negative number');
  return n.sqrt(); // hypothetical
}

void main() {
  try {
    print(squareRoot(25));
    print(squareRoot(-1));
  } on NegativeNumberException catch (e) {
    print(e);
  } finally {
    print('Done');
  }
}

Key Takeaways

    • Exceptions are used to handle errors gracefully.
    • Use try, catch, finally to handle exceptions.
    • Catch specific exceptions with on.
    • Define custom exceptions by extending Exception.
    • Asynchronous errors are handled with try/catch in async functions or .catchError.
    • Always consider whether to let an exception propagate or handle it.

Try it yourself

void main() {
  try {
    int result = 10 ~/ 0;
    print(result);
  } catch (e) {
    print('Error: $e');
  } finally {
    print('Cleanup');
  }
}

Test Your Knowledge

Q1
of 4

Which keyword is used to throw an exception in Dart?

A
raise
B
throw
C
exception
D
error
Q2
of 4

What does the `finally` block do?

A
Catches exceptions
B
Runs only if an exception occurred
C
Runs always, regardless of exception
D
Defines a custom exception
Q3
of 4

How do you catch a specific exception type and also get the exception object?

A
on SpecificException {}
B
catch (SpecificException e) {}
C
on SpecificException catch (e) {}
D
catch (e, s) {}
Q4
of 4

What does `rethrow` do?

A
Throws a new exception
B
Throws the same exception while preserving the stack trace
C
Returns from the function
D
Catches the exception

Frequently Asked Questions

What is the difference between `Error` and `Exception` in Dart?

Error indicates a serious, unrecoverable problem that should not be caught (e.g., stack overflow). Exception is used for recoverable errors that you can handle (e.g., invalid input). In practice, you should throw Exception or a subclass of it for your own error handling.

Can I throw any object in Dart?

Yes, you can throw any object, not just Exception or Error. For example, throw 'Oops' is allowed. However, it's better practice to throw an Exception subclass to maintain clarity and allow proper catching by type.

What happens if I don't catch an exception?

An uncaught exception will propagate up the call stack. If it reaches the top level, the program will terminate (or the isolate will stop). In Flutter, uncaught exceptions are caught by the framework and displayed in the console.

How do I get the stack trace of an exception?

In the catch clause, add a second parameter: catch (e, s) { ... }. The s is the stack trace. You can also use StackTrace.current inside a catch block.

Should I use `rethrow` or simply `throw` the same exception?

Always use rethrow to preserve the original stack trace. If you use throw e, you'll lose the original stack trace information.

How do I handle errors in a stream?

When listening to a stream, provide an onError callback: stream.listen(..., onError: (e) { ... }). You can also use handleError transformer: stream.handleError((e) => ...).listen(...).

Previous

dart event loop

Next

dart custom exceptions

Related Content

Need help?

Explore our comprehensive docs or start a chat with our tech experts.