Dart: Tratamento de Exceções em Dart

Última atualização: 2026-08-26

O tratamento de exceções é a rede de segurança do seu código — sem ele, um único erro pode derrubar todo o sistema.

1. O que Você Aprenderá


2. A História Real de um Desenvolvedor

(1) O Problema: Exceções Não Tratadas Interrompem o Processamento em Lote

O DataPipeline do Bob, ao processar milhões de pedidos, travou devido a uma linha CSV malformada causando uma FormatException. Todos os 800.000 registros processados foram perdidos, exigindo uma nova execução completa. Pior, a mensagem de erro mostrava apenas "FormatException" sem número de linha ou contexto, e Bob levou 4 horas para localizar o problema.

(2) A Solução com Tratamento de Exceções

Usar try-on-catch-finally para capturar exceções específicas, classes de exceção personalizadas para carregar informações de contexto, e finally para garantir a limpeza de recursos.

DART
try {
  final records = await parseCsvFile(path);
  await processRecords(records);
} on FormatException catch (e) {
  log.error('CSV parse error: ${e.message} at line ${e.offset}');
  // Pular registros malformados, continuar processamento
} on TimeoutException {
  log.error('API timeout, retrying...');
  await retryWithBackoff();
} finally {
  await closeResources();
}
TEXT 📖 Somente leitura
> **Saída:** Execute em um DartPad local ou com `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x, e os resultados podem variar ligeiramente dependendo da versão do SDK.

(3) Os Benefícios


3. Exception vs Error

(1) Diferença Semântica

100%
flowchart TD
  A[Throwable] --> B[Error<br/>Recuperável: NÃO]
  A --> C[Exception<br/>Recuperável: SIM]
  B --> B1[OutOfMemoryError]
  B --> B2[StackOverflowError]
  C --> C1[FormatException]
  C --> C2[TimeoutException]
  C --> C3[IOException]
  C --> C4[Custom Exception]
TEXT 📖 Somente leitura
> **Saída:** Execute em um DartPad local ou com `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x, e os resultados podem variar ligeiramente dependendo da versão do SDK.
Aspecto Error Exception
Recuperabilidade Não recuperável Recuperável
Deve ser capturado Não Sim
Produzido por VM / Runtime Código da aplicação
Exemplo StackOverflowError FormatException
⚠️ Nota: Não capture Errors. Um Error indica que o estado do programa está corrompido; capturá-lo e continuar a execução pode levar a problemas mais graves. Capture apenas Exceptions.


4. try / on / catch / finally

(1) Sintaxe Completa

▶ Exemplo

TEXT 📖 Somente leitura
> **Saída:** Execute em um DartPad local ou com `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x, e os resultados podem variar ligeiramente dependendo da versão do SDK.

:try-catch básico

DART
void main() {
  try {
    final result = int.parse('abc');
    print(result);
  } on FormatException catch (e) {
    print('Format error: ${e.message}');
  } catch (e) {
    print('Unexpected error: $e');
  }
}
TEXT 📖 Somente leitura
> **Saída:** Execute em um DartPad local ou com `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x, e os resultados podem variar ligeiramente dependendo da versão do SDK.

▶ Exemplo

TEXT 📖 Somente leitura
> **Saída:** Execute em um DartPad local ou com `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x, e os resultados podem variar ligeiramente dependendo da versão do SDK.

:Diferença entre on e catch

DART
void main() {
  // on - captura tipo específico, sem acesso ao objeto de exceção
  try {
    int.parse('not a number');
  } on FormatException {
    print('Caught FormatException (no details needed)');
  }

  // on + catch - captura tipo específico COM acesso à exceção
  try {
    int.parse('not a number');
  } on FormatException catch (e) {
    print('Caught: ${e.message}');
  }

  // catch com stack trace
  try {
    int.parse('not a number');
  } on FormatException catch (e, stackTrace) {
    print('Error: $e');
    print('Stack: $stackTrace');
  }
}
TEXT 📖 Somente leitura
> **Saída:** Execute em um DartPad local ou com `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x, e os resultados podem variar ligeiramente dependendo da versão do SDK.

▶ Exemplo

TEXT 📖 Somente leitura
> **Saída:** Execute em um DartPad local ou com `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x, e os resultados podem variar ligeiramente dependendo da versão do SDK.

:O bloco finally

DART
import 'dart:io';

void main() async {
  File? file;
  try {
    file = File('orders.csv');
    final content = await file.readAsString();
    print('Read ${content.length} characters');
  } on FileSystemException catch (e) {
    print('File error: ${e.message}');
  } finally {
    // Sempre executado - mesmo com return ou throw
    print('Cleanup: file handle released');
  }
}
TEXT 📖 Somente leitura
> **Saída:** Execute em um DartPad local ou com `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x, e os resultados podem variar ligeiramente dependendo da versão do SDK.
Cláusula Propósito Pode ter múltiplos Ordem
try Envolve código que pode lançar exceção 1 Primeiro
on Type Captura um tipo específico Múltiplos Após try
catch (e) Captura qualquer exceção 1 Após on
finally Sempre executado 1 Último

5. Classes de Exceção Personalizadas

(1) Princípios de Design de Classes de Exceção

▶ Exemplo

TEXT 📖 Somente leitura
> **Saída:** Execute em um DartPad local ou com `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x, e os resultados podem variar ligeiramente dependendo da versão do SDK.

:Classes de exceção personalizadas

DART
// Exceção base para DataPipeline
class PipelineException implements Exception {
  final String message;
  final String? source;
  final int? lineNumber;

  PipelineException(this.message, {this.source, this.lineNumber});

  @override
  String toString() => 'PipelineException: $message'
      '${source != null ? " (source: $source)" : ""}'
      '${lineNumber != null ? " at line $lineNumber" : ""}';
}

// Tipos específicos de exceção
class DataFormatException extends PipelineException {
  final String fieldName;
  final String invalidValue;

  DataFormatException({
    required this.fieldName,
    required this.invalidValue,
    required super.message,
    super.source,
    super.lineNumber,
  });

  @override
  String toString() => 'DataFormatException: $message '
      '(field: $fieldName, value: "$invalidValue")';
}

class NetworkTimeoutException extends PipelineException {
  final Duration timeout;
  final String endpoint;

  NetworkTimeoutException({
    required this.timeout,
    required this.endpoint,
    super.message = 'Request timed out',
  }) : super(message);

  @override
  String toString() => 'NetworkTimeout: ${timeout.inSeconds}s '
      'timeout on $endpoint';
}
TEXT 📖 Somente leitura
> **Saída:** Execute em um DartPad local ou com `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x, e os resultados podem variar ligeiramente dependendo da versão do SDK.

▶ Exemplo

TEXT 📖 Somente leitura
> **Saída:** Execute em um DartPad local ou com `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x, e os resultados podem variar ligeiramente dependendo da versão do SDK.

:Design de códigos de erro

DART
enum ErrorCode {
  fileNotFound('E001', 'File not found'),
  invalidFormat('E002', 'Invalid data format'),
  networkTimeout('E003', 'Network request timed out'),
  authFailed('E004', 'Authentication failed'),
  rateLimitExceeded('E005', 'Rate limit exceeded');

  final String code;
  final String description;

  const ErrorCode(this.code, this.description);
}

class CodedException extends PipelineException {
  final ErrorCode errorCode;

  CodedException(this.errorCode, {String? detail})
      : super('${errorCode.code}: ${errorCode.description}'
            '${detail != null ? " - $detail" : ""}');

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

void main() {
  try {
    throw CodedException(ErrorCode.invalidFormat, detail: 'amount field is not a number');
  } on CodedException catch (e) {
    print(e);  // [ErrorCode.invalidFormat] E002: Invalid data format - amount field is not a number
  }
}
TEXT 📖 Somente leitura
> **Saída:** Execute em um DartPad local ou com `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x, e os resultados podem variar ligeiramente dependendo da versão do SDK.

6. rethrow e Encadeamento de Exceções

▶ Exemplo

TEXT 📖 Somente leitura
> **Saída:** Execute em um DartPad local ou com `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x, e os resultados podem variar ligeiramente dependendo da versão do SDK.

:rethrow

DART
double parseAmount(String input) {
  try {
    return double.parse(input);
  } on FormatException catch (e) {
    // Registrar e relançar - não engolir a exceção
    print('Failed to parse amount: "$input"');
    rethrow;  // Preserva o stack trace original
  }
}

void main() {
  try {
    final amount = parseAmount('not_a_number');
  } on FormatException {
    print('Caught rethrown exception');
  }
}
TEXT 📖 Somente leitura
> **Saída:** Execute em um DartPad local ou com `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x, e os resultados podem variar ligeiramente dependendo da versão do SDK.

▶ Exemplo

TEXT 📖 Somente leitura
> **Saída:** Execute em um DartPad local ou com `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x, e os resultados podem variar ligeiramente dependendo da versão do SDK.

:Encadeamento de exceções

DART
class ChainedException implements Exception {
  final String message;
  final Exception? innerException;

  ChainedException(this.message, {this.innerException});

  @override
  String toString() {
    var result = 'ChainedException: $message';
    if (innerException != null) {
      result += '\n  Caused by: $innerException';
    }
    return result;
  }
}

Future``<double>`` fetchOrderAmount(String orderId) async {
  try {
    // Simular chamada de API
    throw FormatException('Invalid JSON response');
  } on FormatException catch (e) {
    throw ChainedException(
      'Failed to fetch order $orderId',
      innerException: e,
    );
  }
}

void main() async {
  try {
    await fetchOrderAmount('ORD-001');
  } on ChainedException catch (e) {
    print(e);
  }
}
TEXT 📖 Somente leitura
> **Saída:** Execute em um DartPad local ou com `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x, e os resultados podem variar ligeiramente dependendo da versão do SDK.

7. Cenário do Bob: Tratamento de Exceções no DataPipeline

▶ Exemplo

TEXT 📖 Somente leitura
> **Saída:** Execute em um DartPad local ou com `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x, e os resultados podem variar ligeiramente dependendo da versão do SDK.

:Fluxo completo de tratamento de exceções

DART
import 'dart:async';

// Exceções personalizadas
class PipelineException implements Exception {
  final String message;
  PipelineException(this.message);
  @override
  String toString() => 'PipelineException: $message';
}

class CsvParseException extends PipelineException {
  final int lineNumber;
  CsvParseException(String message, this.lineNumber) : super(message);
  @override
  String toString() => 'CsvParseException: $message (line $lineNumber)';
}

// Parser CSV seguro com tratamento de exceções
List<Map<String, String>> parseCsv(String content) {
  final lines = content.split('\n');
  if (lines.isEmpty) throw PipelineException('Empty CSV content');

  final headers = lines[0].split(',');
  final records = <Map<String, String>>[];

  for (var i = 1; i < lines.length; i++) {
    final line = lines[i].trim();
    if (line.isEmpty) continue;

    try {
      final values = line.split(',');
      if (values.length != headers.length) {
        throw CsvParseException(
          'Column count mismatch: expected ${headers.length}, got ${values.length}',
          i + 1,
        );
      }
      final record = <String, String>{};
      for (var j = 0; j < headers.length; j++) {
        record[headers[j].trim()] = values[j].trim();
      }
      records.add(record);
    } on CsvParseException {
      rethrow;
    } catch (e) {
      throw CsvParseException('Unexpected error: $e', i + 1);
    }
  }
  return records;
}

Future``<void>`` processData(String csvContent) async {
  List<Map<String, String>>? records;

  try {
    records = parseCsv(csvContent);
    print('Parsed ${records.length} records');

    // Simular chamada de API com timeout
    await Future.delayed(const Duration(seconds: 1));
    print('Data submitted successfully');
  } on CsvParseException catch (e) {
    print('Parse error: $e - skipping malformed records');
  } on TimeoutException catch (e) {
    print('Network timeout: $e - will retry later');
  } on PipelineException catch (e) {
    print('Pipeline error: $e');
  } finally {
    print('Cleanup: resources released');
  }
}

void main() async {
  final csv = 'id,amount,status\nORD-001,1500,completed\nORD-002,50,pending\nORD-003,bad_data,completed';
  await processData(csv);

  print('\n--- Test with malformed CSV ---');
  final badCsv = 'id,amount\nORD-001,1500,completed';  // Wrong column count
  await processData(badCsv);
}
TEXT 📖 Somente leitura
> **Saída:** Execute em um DartPad local ou com `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x, e os resultados podem variar ligeiramente dependendo da versão do SDK.

8. Exemplo Completo: Processamento de Dados Robusto no DataPipeline

DART
// ============================================
// DataPipeline Robust Data Processing
// Tratamento de exceções completo com exceções personalizadas
// ============================================

import 'dart:async';

// Códigos de erro
enum PipelineError {
  fileNotFound('E001', 'File not found'),
  invalidFormat('E002', 'Invalid data format'),
  networkTimeout('E003', 'Network timeout'),
  validationFailed('E004', 'Validation failed');

  final String code;
  final String label;
  const PipelineError(this.code, this.label);
}

class PipelineException implements Exception {
  final PipelineError error;
  final String detail;
  final Exception? cause;

  PipelineException(this.error, {this.detail = '', this.cause});

  @override
  String toString() => '[${error.code}] ${error.label}'
      '${detail.isNotEmpty ? ": $detail" : ""}'
      '${cause != null ? " (caused by: $cause)" : ""}';
}

// Pedido com validação
class Order {
  final String id;
  final double amount;
  final String status;

  Order({required this.id, required this.amount, required this.status}) {
    if (id.isEmpty) {
      throw PipelineException(PipelineError.validationFailed, detail: 'Order ID is empty');
    }
    if (amount <= 0) {
      throw PipelineException(PipelineError.validationFailed, detail: 'Amount must be positive: $amount');
    }
  }

  @override
  String toString() => 'Order($id, \$${amount.toStringAsFixed(2)}, $status)';
}

// Parser de pedidos robusto
class OrderParser {
  final List``<PipelineException>`` _errors = [];
  int parsed = 0;
  int skipped = 0;

  List``<PipelineException>`` get errors => List.unmodifiable(_errors);

  Order? tryParse(Map<String, dynamic> data) {
    try {
      final order = Order(
        id: (data['id'] ?? '') as String,
        amount: (data['amount'] as num).toDouble(),
        status: (data['status'] ?? 'unknown') as String,
      );
      parsed++;
      return order;
    } on PipelineException catch (e) {
      _errors.add(e);
      skipped++;
      return null;
    } on TypeError catch (e) {
      _errors.add(PipelineException(
        PipelineError.invalidFormat,
        detail: 'Type mismatch in record: $e',
      ));
      skipped++;
      return null;
    }
  }

  void printReport() {
    print('Parsed: $parsed, Skipped: $skipped');
    if (_errors.isNotEmpty) {
      print('Errors:');
      for (final e in _errors) {
        print('  $e');
      }
    }
  }
}

void main() {
  final rawData = <Map<String, dynamic>>[
    {'id': 'ORD-001', 'amount': 1500.0, 'status': 'completed'},
    {'id': '', 'amount': 500.0, 'status': 'pending'},           // Inválido: ID vazio
    {'id': 'ORD-003', 'amount': -50.0, 'status': 'completed'},  // Inválido: valor negativo
    {'id': 'ORD-004', 'amount': 'not_a_number', 'status': 'pending'}, // Inválido: tipo errado
    {'id': 'ORD-005', 'amount': 3200.0, 'status': 'completed'},
  ];

  final parser = OrderParser();
  final validOrders = ``<Order>``[];

  for (final data in rawData) {
    final order = parser.tryParse(data);
    if (order != null) validOrders.add(order);
  }

  print('=== DataPipeline Processing Report ===');
  parser.printReport();

  print('\nValid Orders:');
  for (final order in validOrders) {
    print('  $order');
  }

  final totalRevenue = validOrders.fold``<double>``(0, (s, o) => s + o.amount);
  print('\nTotal Revenue: \$${totalRevenue.toStringAsFixed(2)} USD');
}
TEXT 📖 Somente leitura
> **Saída:** Execute em um DartPad local ou com `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x, e os resultados podem variar ligeiramente dependendo da versão do SDK.

Saída:

TEXT 📖 Somente leitura
=== DataPipeline Processing Report ===
Parsed: 3, Skipped: 2
Errors:
  [E004] Validation failed: Order ID is empty
  [E004] Validation failed: Amount must be positive: -50.0
  [E002] Invalid data format: Type mismatch in record: ...

Valid Orders:
  Order(ORD-001, $1500.00, completed)
  Order(ORD-005, $3200.00, completed)

Total Revenue: $4700.00 USD

❓ Perguntas Frequentes

P: O Dart possui exceções verificadas (checked exceptions)? R: Não. Todas as exceções em Dart são não verificadas; o compilador não exige declaração ou captura. Isso oferece flexibilidade, mas exige que os desenvolvedores tratem exceções conscientemente.

P: Qual é a diferença entre on e catch? R: on Type captura um tipo específico de exceção sem vincular uma variável (a menos que catch seja adicionado). catch (e) captura qualquer exceção e a vincula a uma variável. Normalmente, usa-se a combinação on Type catch (e).

P: Qual é a utilidade de catch e rethrow? R: catch permite registrar logs, realizar limpeza, etc., após capturar uma exceção. Depois, use rethrow para relançar a mesma exceção para que as camadas superiores possam continuar tratando-a. rethrow preserva o stack trace original, o que é melhor que throw e.

P: Quando o bloco finally é executado? R: O bloco finally sempre executa, independentemente de uma exceção ser lançada no bloco try, de ser capturada, ou de haver uma instrução return. A única exceção é se o programa for encerrado (por exemplo, via SIGKILL).

P: O que as exceções personalizadas devem herdar? R: Recomenda-se implement Exception em vez de extend Exception. implements é mais flexível, não sendo restrito pela herança única. Essa também é a abordagem recomendada pelas diretrizes oficiais do Dart.

P: O tratamento de exceções afeta o desempenho? R: O bloco try-catch em si tem sobrecarga quase zero (a Dart VM não adiciona instruções extras dentro de blocos try). No entanto, a criação e o lançamento de exceções têm um custo e não devem ser usados como mecanismo normal de controle de fluxo.

P: Como evitar engolir exceções? R: Um bloco catch vazio é um code smell. Pelo menos registre a exceção no log ou use rethrow. Se precisar ignorá-la, use catch (_) {} e adicione um comentário explicando o motivo.


📖 Resumo


📝 Exercícios

  1. Básico (Dificuldade ⭐): Escreva uma função safeParseInt(String s) que use int.parse dentro de um bloco try-catch. Se a análise falhar, retorne 0 em vez de lançar uma exceção. Teste com 3 casos.
  2. Intermediário (Dificuldade ⭐⭐): Defina uma classe de exceção personalizada DataFormatException que carregue o nome do campo, valor inválido e número da linha. Escreva uma função de análise de CSV que lance essa exceção em erros de formato, e capture-a no local da chamada para imprimir informações detalhadas.
  3. Desafio (Dificuldade ⭐⭐⭐): Implemente uma função de requisição HTTP com mecanismo de retry que suporte um número customizado de tentativas e uma estratégia de backoff. Tente novamente em TimeoutException, e após exceder a contagem de retries, lance uma exceção agregada contendo todas as exceções individuais de cada tentativa.

← Lição Anterior | Próxima Lição →

Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%