flutter
/

Dart Custom Exceptions – Creating Your Own Exception Types

Last Sync: Today

On this page

9
0%
5 min read
Remaining
5 minleft

Click any section to jump — progress syncs automatically

flutter

Dart Custom Exceptions – Creating Your Own Exception Types

Why Custom Exceptions?

Built‑in exception types like Exception and FormatException are useful, but often you need to represent errors specific to your application. Custom exceptions allow you to:

    • Give meaningful names to error conditions.
    • Attach additional data (like error codes, context).
    • Differentiate between different failure scenarios.
    • Catch and handle them precisely.

Defining a Custom Exception

To create a custom exception, define a class that implements or extends Exception. It can contain fields, constructors, and override toString() for better error messages.

DARTRead-only
1
class ValidationException implements Exception {
  final String message;
  final int errorCode;

  ValidationException(this.message, {this.errorCode = 1001});

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

Throwing a Custom Exception

Use throw with an instance of your custom exception class.

DARTRead-only
1
void validateEmail(String email) {
  if (!email.contains('@')) {
    throw ValidationException('Invalid email format', errorCode: 1002);
  }
}

void main() {
  try {
    validateEmail('user@example.com');
    validateEmail('userexample.com');
  } on ValidationException catch (e) {
    print(e); // ValidationException: Invalid email format (code: 1002)
  }
}

Extending Exception vs Implementing

Both extends Exception and implements Exception work. Extending is simpler and gives you a default toString() implementation (though you'll likely override it). Implementing requires you to define toString() if you want a custom message, but it's also valid.

DARTRead-only
1
// Extending Exception (recommended)
class DatabaseException extends Exception {
  final String message;
  DatabaseException(this.message);
  @override
  String toString() => 'DatabaseException: $message';
}

// Implementing Exception (also works)
class NetworkException implements Exception {
  final int statusCode;
  NetworkException(this.statusCode);
  @override
  String toString() => 'NetworkException: HTTP $statusCode';
}

Adding Fields to Custom Exceptions

Custom exceptions can carry any data you need, such as error codes, original values, or contextual information. This allows handlers to respond intelligently.

DARTRead-only
1
class AgeException implements Exception {
  final int attemptedAge;
  final int minAge;
  final int maxAge;

  AgeException(this.attemptedAge, this.minAge, this.maxAge);

  @override
  String toString() =>
      'AgeException: $attemptedAge is outside allowed range [$minAge, $maxAge]';
}

void setAge(int age) {
  if (age < 0 || age > 120) {
    throw AgeException(age, 0, 120);
  }
}

void main() {
  try {
    setAge(150);
  } on AgeException catch (e) {
    print(e);
    print('Attempted age: ${e.attemptedAge}');
  }
}

Using Enums for Error Categories

Sometimes it's useful to combine a category (enum) with a message. This gives both a type and rich data.

DARTRead-only
1
enum ErrorType { validation, network, database, unknown }

class AppException implements Exception {
  final ErrorType type;
  final String message;
  final StackTrace? stackTrace;

  AppException(this.type, this.message, [this.stackTrace]);

  @override
  String toString() => '[$type] $message';
}

void main() {
  try {
    throw AppException(ErrorType.network, 'Connection timed out');
  } on AppException catch (e) {
    print(e); // [ErrorType.network] Connection timed out
  }
}

Best Practices

    • Name your exception classes with a clear suffix like Exception (e.g., ValidationException).
    • Keep them immutable – use final fields.
    • Override toString() to provide meaningful output.
    • Include only necessary data; avoid adding too much context that might be irrelevant for handlers.
    • Use implements Exception if you want to ensure the class is purely an interface (no implementation).
    • For large applications, consider a hierarchy (e.g., AppException base with subclasses).

Complete Example: User Registration

DARTRead-only
1
class InvalidUsernameException implements Exception {
  final String username;
  final String reason;
  InvalidUsernameException(this.username, this.reason);
  @override
  String toString() => 'Invalid username "$username": $reason';
}

class WeakPasswordException implements Exception {
  final String message;
  WeakPasswordException(this.message);
  @override
  String toString() => 'Weak password: $message';
}

void register(String username, String password) {
  if (username.length < 3) {
    throw InvalidUsernameException(username, 'Must be at least 3 characters');
  }
  if (password.length < 8) {
    throw WeakPasswordException('Password must be at least 8 characters');
  }
  print('Registration successful for $username');
}

void main() {
  try {
    register('jo', 'short');
  } on InvalidUsernameException catch (e) {
    print(e);
  } on WeakPasswordException catch (e) {
    print(e);
  } catch (e) {
    print('Unexpected error: $e');
  }
}

Key Takeaways

    • Custom exceptions improve code clarity and error handling.
    • Define a class that extends or implements Exception.
    • Add fields to carry relevant error data.
    • Override toString() for meaningful error messages.
    • Use custom exceptions to differentiate between error types.
    • Follow naming conventions and keep exceptions focused.

Try it yourself

class NegativeNumberException implements Exception {
  final double value;
  NegativeNumberException(this.value);
  @override
  String toString() => 'NegativeNumberException: $value is negative';
}

double safeSqrt(double n) {
  if (n < 0) throw NegativeNumberException(n);
  return n.sqrt();
}

void main() {
  try {
    print(safeSqrt(25));
    print(safeSqrt(-1));
  } on NegativeNumberException catch (e) {
    print(e);
  }
}

Test Your Knowledge

Q1
of 4

Which keyword is used to define a custom exception class?

A
exception
B
error
C
class
D
custom
Q2
of 4

What should a custom exception typically extend?

A
Object
B
Error
C
Exception
D
Throwable
Q3
of 4

Why might you add fields to a custom exception?

A
To store error details like error codes or original values
B
To make the class larger
C
To slow down the program
D
To avoid using constructors
Q4
of 4

What is the purpose of overriding `toString()` in a custom exception?

A
To provide a readable error message
B
To serialize the exception
C
To compare two exceptions
D
To catch the exception

Frequently Asked Questions

Do I have to extend `Exception` to create a custom exception?

No, you can throw any object in Dart. However, extending or implementing Exception is a convention that makes your code more readable and allows for type‑based catching. It's highly recommended.

Can I create a hierarchy of custom exceptions?

Yes, you can create a base exception class (e.g., AppException) and then extend it for specific cases (e.g., NetworkException extends AppException). This allows catching all app‑specific exceptions with one on AppException clause.

How do I decide between using an enum or separate classes for different errors?

If the handling logic differs significantly, separate classes are better. If the only difference is a category, an enum field inside a single exception class might suffice. Choose based on how you plan to catch and handle them.

Should I attach a stack trace to custom exceptions?

Sometimes it's helpful for logging, but it's not required. You can capture the current stack trace with StackTrace.current when throwing. However, rethrowing with rethrow preserves the original stack trace without needing to store it.

Can I make my custom exception implement `Error` instead of `Exception`?

Technically yes, but Error is meant for serious, unrecoverable problems (like programming errors). Use Exception for recoverable conditions that your application can handle.

Previous

dart exception handling

Next

dart generics

Related Content

Need help?

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