In Dart, how do you define a custom exception class, and why implement Exception rather than throw a plain String?
answer
- any non-null object is throwable
- implements, not extends
- fields a caller can read
- override toString
- only_throw_errors lint
basics
~20 sDart can throw any non-null object, but a custom exception should be a class that implements Exception, carries fields describing the failure and overrides toString, so callers can catch it precisely with an on clause and read its data.
solid answer
~40 sDart lets you `throw` any non-null object, including `'Out of stock'` or `42`, but a string gives callers nothing to filter on except `on String` and nothing structured to read. The idiom is a small class that `implements Exception`, holds `final` fields such as the row and column, has a `const` constructor and overrides `toString()`. Callers then write `on RowException catch (e)` and read `e.row`. It must be `implements`: `Exception` is an `abstract interface class` with only a factory constructor, so `extends Exception` does not compile. `Exception('message')` works but builds a private `_Exception` callers cannot target, which the SDK discourages in library code. For bugs, reuse `ArgumentError` or `StateError`, or `extends Error` so the stack trace is recorded. The opt-in `only_throw_errors` lint rejects throwing anything else.
code
dart · 26 linesclass RowException implements Exception {
const RowException(this.row, this.column, this.message);
final int row;
final String column;
final String message;
@override
String toString() => 'RowException(row $row, $column): $message';
}
int quantityAt(int row, String cell) {
final value = int.tryParse(cell);
if (value == null) {
throw RowException(row, 'quantity', 'not an integer: "$cell"');
}
return value;
}
void main() {
try {
quantityAt(7, 'seven');
} on RowException catch (e) {
print('fix row ${e.row}, column ${e.column}'); // fix row 7, column quantity
}
}go deeper
Recall that any non-null object can be thrown, but real code throws a class that implements Exception and carries fields.
Explain why extends Exception fails, what Exception('msg') returns, and why a toString override and typed fields matter to callers.
Show where you draw the line between domain exceptions and Error types, and how you keep the cause and trace when translating one into another.
Decide how a codebase names and groups its exception types so public APIs document their throws and lints keep stray strings out.
## What Dart lets you throw A Dart **throw expression** accepts any value whose type is assignable to `Object` — that is, anything **non-null**. All of these compile: ```dart throw 'Out of stock'; throw 42; throw FormatException('Expected 2 cells'); ``` Two things do not compile: `throw null`, and throwing an expression of nullable type such as a `String?` (the analyzer reports `throw_of_invalid_type`). For the same reason an `on` clause cannot name a nullable type. Before Dart 3, a thrown `null` surfaced as `NullThrownError`; Dart 3.0 removed that class because sound null safety makes it impossible. Because `throw` is an expression, it also works in arrow bodies: `int get total => throw UnimplementedError();`. ## Why a String is a poor exception - **Nothing to filter on.** A caller can only write `on String` or a catch-all, and `on String` also matches any other string anyone throws. - **Nothing to read.** The row number, the column and the bad value are baked into prose, so the caller must parse your message. - **No intent.** A reader cannot tell whether the throw reports a bug or an expected failure. - **Lints object.** The `only_throw_errors` lint — not in the recommended set, but common in stricter configurations — flags any thrown value that is not an `Exception` or `Error`. ## Writing a custom exception The house pattern has four parts: 1. `implements Exception`, which documents that callers are expected to catch it; 2. `final` fields with the data a handler needs; 3. a `const` constructor, since the object is immutable; 4. a `toString()` override, because that is what logs and uncaught-error reports print. ```dart class RowException implements Exception { const RowException(this.row, this.column, this.message); final int row; final String column; final String message; @override String toString() => 'RowException(row $row, $column): $message'; } ``` A caller can now write `on RowException catch (e)` and use `e.row` without touching unrelated exceptions. ## Why implements and not extends `Exception` is declared as an **`abstract interface class`** whose only constructor is a **factory**. That rules out `extends Exception` twice over: the `interface` modifier forbids extending it outside `dart:core`, and a subclass would have no generative superclass constructor to call. `implements Exception` is the only way in, and the interface has no members to implement. `Exception('message')` compiles, but the factory returns a private `_Exception` whose `toString()` is `Exception: message`. Callers can catch it only with `on Exception`, which also catches every other exception, so the SDK discourages it in library code and suggests it only for tests or quick development. ## When the failure is a bug instead | Situation | Throw | |---|---| | a bad argument the caller could have checked | `ArgumentError` / `ArgumentError.value` / `RangeError` | | a method called in the wrong state | `StateError` | | an operation the object does not support | `UnsupportedError` | | a method not written yet | `UnimplementedError` | | none of those fit | your own class that **extends `Error`** | Extending `Error` — `Error` has a public generative constructor — matters because only classes that **extend** it get `stackTrace` filled in on the first throw. Custom `Error` subclasses are rare; the built-in ones cover most preconditions. ## Checklist for a reviewer - Does every thrown domain failure have its own type a caller can name in `on`? - Does it carry the data the handler needs as fields, not only as a message? - Is `toString()` readable in a log line? - Are programming mistakes reported with `Error` types instead of custom exceptions?
- In Dart, why does a custom Error subclass normally extend Error instead of implementing it?Only classes that extend `Error` have their `stackTrace` recorded automatically on the first throw, and `Error` has a public generative constructor to call. Implementing it means supplying a `stackTrace` getter yourself; `StackOverflowError` and `OutOfMemoryError` do that because they are special runtime errors.
- In Dart, should a custom exception hold the original exception it was translated from?Often yes: a field such as `final Object? cause` lets a handler or log show the low-level `FormatException` behind a `RowException`. Pair it with the original stack trace — pass it along or throw with `Error.throwWithStackTrace` — so the log still points at the real throw site.
saying these in an interview costs you the question
- Custom exceptions in Dart should use extends Exception.
- Dart can only throw objects of Exception or Error types.
- Exception('bad row') gives callers a precise type to catch.
- Domain failures such as a bad CSV row should extend Error.
- throw null is allowed and caught as a null e.