ZetCode

Dart HttpStatus

last modified May 29, 2026

The HttpStatus class in Dart provides named integer constants for every standard HTTP status code. It is part of the dart:io library and is used both when building HTTP servers and when consuming REST APIs.

HTTP status codes are three-digit integers grouped by meaning: 1xx informational, 2xx success, 3xx redirection, 4xx client error, and 5xx server error. Using HttpStatus constants instead of raw numbers makes code more readable and less prone to typos.

Common HttpStatus Constants

The table below lists the most frequently used constants from the HttpStatus class along with their numeric values.

ConstantValueMeaning
HttpStatus.continue_100Server received headers; client should proceed
HttpStatus.ok200Request succeeded
HttpStatus.created201Resource created successfully
HttpStatus.accepted202Request accepted but not yet completed
HttpStatus.noContent204Success with no response body
HttpStatus.movedPermanently301Resource moved permanently to new URL
HttpStatus.found302Resource temporarily at different URL
HttpStatus.seeOther303Redirect to another resource using GET
HttpStatus.notModified304Cached version is still valid
HttpStatus.temporaryRedirect307Temporary redirect, keep original method
HttpStatus.permanentRedirect308Permanent redirect, keep original method
HttpStatus.badRequest400Malformed or invalid request
HttpStatus.unauthorized401Authentication required
HttpStatus.forbidden403Authenticated but not authorized
HttpStatus.notFound404Resource not found
HttpStatus.methodNotAllowed405HTTP method not supported for this resource
HttpStatus.conflict409Request conflicts with current state
HttpStatus.gone410Resource permanently removed
HttpStatus.unprocessableEntity422Validation errors in request body
HttpStatus.tooManyRequests429Rate limit exceeded
HttpStatus.internalServerError500Unexpected server-side error
HttpStatus.badGateway502Invalid response from upstream server
HttpStatus.serviceUnavailable503Server temporarily unavailable
HttpStatus.gatewayTimeout504Upstream server timed out

Checking Success Status

This example demonstrates checking if an HTTP response indicates success. Success responses fall in the 200–299 range.

main.dart
import 'dart:io';

void main() {
  int statusCode = 200;

  if (statusCode >= HttpStatus.ok &&
      statusCode < HttpStatus.multipleChoices) {
    print('Request succeeded');
  } else {
    print('Request failed');
  }

  print('ok        = ${HttpStatus.ok}');
  print('created   = ${HttpStatus.created}');
  print('noContent = ${HttpStatus.noContent}');
}

We check if the status code falls in the 2xx success range using HttpStatus.ok (200) and HttpStatus.multipleChoices (300) as boundaries.

$ dart main.dart
Request succeeded
ok        = 200
created   = 201
noContent = 204

Making an HTTP GET Request

This example uses dart:io's HttpClient to make an HTTP GET request and inspect the status code of the response.

main.dart
import 'dart:io';
import 'dart:convert';

Future<void> main() async {
  final client = HttpClient();
  try {
    final request = await client
        .getUrl(Uri.parse('https://jsonplaceholder.typicode.com/todos/1'));
    final response = await request.close();

    print('Status: ${response.statusCode}');

    if (response.statusCode == HttpStatus.ok) {
      final body = await response.transform(utf8.decoder).join();
      print('Body: $body');
    } else if (response.statusCode == HttpStatus.notFound) {
      print('Resource not found.');
    } else {
      print('Unexpected status: ${response.statusCode}');
    }
  } finally {
    client.close();
  }
}

We create an HttpClient, issue a GET request, and compare response.statusCode against HttpStatus constants to decide how to process the response body.

$ dart main.dart
Status: 200
Body: {
  "userId": 1,
  "id": 1,
  "title": "delectus aut autem",
  "completed": false
}

Handling Redirects

This example shows how to identify different types of redirect responses.

main.dart
import 'dart:io';

void handleRedirect(int statusCode) {
  switch (statusCode) {
    case HttpStatus.movedPermanently:
      print('301 - Moved Permanently: update bookmarks and links');
      break;
    case HttpStatus.found:
      print('302 - Found: temporary redirect');
      break;
    case HttpStatus.seeOther:
      print('303 - See Other: follow with GET');
      break;
    case HttpStatus.notModified:
      print('304 - Not Modified: use cached response');
      break;
    case HttpStatus.temporaryRedirect:
      print('307 - Temporary Redirect: repeat with same method');
      break;
    case HttpStatus.permanentRedirect:
      print('308 - Permanent Redirect: update links, keep method');
      break;
    default:
      print('Not a redirect: $statusCode');
  }
}

void main() {
  handleRedirect(HttpStatus.movedPermanently);
  handleRedirect(HttpStatus.seeOther);
  handleRedirect(HttpStatus.notModified);
  handleRedirect(HttpStatus.temporaryRedirect);
}

301 and 308 are permanent; 302, 303, and 307 are temporary. 303 always redirects to a GET regardless of the original method. 307 and 308 preserve the original HTTP method.

$ dart main.dart
301 - Moved Permanently: update bookmarks and links
303 - See Other: follow with GET
304 - Not Modified: use cached response
307 - Temporary Redirect: repeat with same method

Authentication Error Handling

This example distinguishes between 401 Unauthorized and 403 Forbidden — two commonly confused status codes.

main.dart
import 'dart:io';

void handleAuthError(int statusCode, {String? wwwAuthenticate}) {
  if (statusCode == HttpStatus.unauthorized) {
    // No valid credentials were provided, or they have expired.
    // The WWW-Authenticate header tells the client how to authenticate.
    print('401 Unauthorized: authentication required.');
    if (wwwAuthenticate != null) {
      print('  Challenge: $wwwAuthenticate');
    }
    // Action: prompt for credentials or refresh the access token.
  } else if (statusCode == HttpStatus.forbidden) {
    // Credentials are valid but the caller lacks permission.
    // Retrying with the same credentials will not help.
    print('403 Forbidden: access denied.');
    print('  Contact an administrator to request access.');
  } else {
    print('Non-auth status: $statusCode');
  }
}

void main() {
  handleAuthError(
    HttpStatus.unauthorized,
    wwwAuthenticate: 'Bearer realm="api"',
  );
  handleAuthError(HttpStatus.forbidden);
  handleAuthError(HttpStatus.ok);
}

401 means the request lacks valid authentication credentials; the server should include a WWW-Authenticate challenge header. 403 means credentials were accepted but the resource is off-limits for that user.

$ dart main.dart
401 Unauthorized: authentication required.
  Challenge: Bearer realm="api"
403 Forbidden: access denied.
  Contact an administrator to request access.
Non-auth status: 200

Error Handling

This example demonstrates handling different HTTP error statuses across client (4xx) and server (5xx) error ranges.

main.dart
import 'dart:io';

String getErrorMessage(int statusCode) {
  switch (statusCode) {
    case HttpStatus.badRequest:
      return '400 Bad Request: malformed syntax or invalid parameters';
    case HttpStatus.unauthorized:
      return '401 Unauthorized: valid credentials required';
    case HttpStatus.forbidden:
      return '403 Forbidden: insufficient permissions';
    case HttpStatus.notFound:
      return '404 Not Found: resource does not exist';
    case HttpStatus.methodNotAllowed:
      return '405 Method Not Allowed: check the Allow header';
    case HttpStatus.conflict:
      return '409 Conflict: duplicate resource or version mismatch';
    case HttpStatus.unprocessableEntity:
      return '422 Unprocessable Entity: validation failed';
    case HttpStatus.tooManyRequests:
      return '429 Too Many Requests: rate limit exceeded';
    case HttpStatus.internalServerError:
      return '500 Internal Server Error: unexpected server failure';
    case HttpStatus.serviceUnavailable:
      return '503 Service Unavailable: server overloaded or in maintenance';
    default:
      if (statusCode >= HttpStatus.internalServerError) {
        return '5xx Server Error: $statusCode';
      }
      return 'Unknown error: $statusCode';
  }
}

void main() {
  final codes = [
    HttpStatus.notFound,
    HttpStatus.unprocessableEntity,
    HttpStatus.tooManyRequests,
    HttpStatus.internalServerError,
    HttpStatus.serviceUnavailable,
  ];

  for (final code in codes) {
    print(getErrorMessage(code));
  }
}

Using a switch with HttpStatus constants makes error handling explicit and exhaustive. The default clause catches any remaining 5xx codes not listed individually.

$ dart main.dart
404 Not Found: resource does not exist
422 Unprocessable Entity: validation failed
429 Too Many Requests: rate limit exceeded
500 Internal Server Error: unexpected server failure
503 Service Unavailable: server overloaded or in maintenance

REST API Response Handling

Different HTTP methods have conventional success status codes. GET and PUT return 200 OK, POST returns 201 Created, and DELETE returns 204 No Content. This example maps method–status pairs to human-readable descriptions using a Dart 3 switch expression.

main.dart
import 'dart:io';

String describeResponse(String method, int status) {
  return switch (status) {
    HttpStatus.ok when method == 'GET'   => 'Resource returned',
    HttpStatus.ok when method == 'PUT'   => 'Resource updated',
    HttpStatus.ok when method == 'PATCH' => 'Resource partially updated',
    HttpStatus.created                   => 'Resource created (Location header set)',
    HttpStatus.noContent                 => 'Success with no body',
    HttpStatus.badRequest                => 'Invalid request data',
    HttpStatus.notFound                  => 'Resource not found',
    HttpStatus.conflict                  => 'Duplicate or version conflict',
    HttpStatus.unprocessableEntity       => 'Validation failed',
    _                                    => 'Status $status',
  };
}

void main() {
  final operations = [
    ('GET',    HttpStatus.ok),
    ('POST',   HttpStatus.created),
    ('PUT',    HttpStatus.ok),
    ('PATCH',  HttpStatus.ok),
    ('DELETE', HttpStatus.noContent),
    ('POST',   HttpStatus.conflict),
    ('PUT',    HttpStatus.unprocessableEntity),
  ];

  for (final (method, status) in operations) {
    print('$method $status: ${describeResponse(method, status)}');
  }
}

Switch expressions with when guards let us distinguish the same status code used by different methods. 422 Unprocessable Entity is the correct code for field-level validation failures (preferred over 400).

$ dart main.dart
GET 200: Resource returned
POST 201: Resource created (Location header set)
PUT 200: Resource updated
PATCH 200: Resource partially updated
DELETE 204: Success with no body
POST 409: Duplicate or version conflict
PUT 422: Validation failed

Creating HTTP Responses

This example shows using HttpStatus constants when building an HTTP server that follows REST conventions for multiple routes and methods.

main.dart
import 'dart:io';
import 'dart:convert';

Future<void> handleRequest(HttpRequest request) async {
  final path   = request.uri.path;
  final method = request.method;

  request.response.headers.contentType = ContentType.json;

  if (method == 'GET' && path == '/api/items') {
    request.response.statusCode = HttpStatus.ok;
    request.response.write(jsonEncode({'items': ['alpha', 'beta', 'gamma']}));

  } else if (method == 'POST' && path == '/api/items') {
    request.response.statusCode = HttpStatus.created;
    request.response.headers.set('Location', '/api/items/42');
    request.response.write(jsonEncode({'id': 42, 'created': true}));

  } else if (method == 'DELETE' &&
             RegExp(r'^/api/items/\d+$').hasMatch(path)) {
    request.response.statusCode = HttpStatus.noContent;

  } else if (method == 'GET' && path == '/api/admin') {
    request.response.statusCode = HttpStatus.forbidden;
    request.response.write(jsonEncode({'error': 'Admin access denied'}));

  } else {
    request.response.statusCode = HttpStatus.notFound;
    request.response.write(jsonEncode({'error': 'Not found'}));
  }

  await request.response.close();
}

void main() async {
  var server = await HttpServer.bind('localhost', 8080);
  print('Server running on http://localhost:8080');

  await for (var request in server) {
    handleRequest(request);
  }
}

POST uses 201 Created and sets a Location header pointing to the new resource. DELETE returns 204 No Content because there is no body to return after a deletion. The admin route demonstrates 403 Forbidden for authenticated-but-unauthorized access.

$ dart main.dart
Server running on http://localhost:8080

Retry Logic for Transient Errors

Some status codes indicate temporary problems that may resolve on retry. 429 Too Many Requests and 503 Service Unavailable are the most common; 502 Bad Gateway and 504 Gateway Timeout also warrant a retry. Client errors such as 400 and 404 should never be retried.

main.dart
import 'dart:io';
import 'dart:async';

/// Returns true for status codes that are safe to retry.
bool isTransient(int statusCode) => switch (statusCode) {
  HttpStatus.tooManyRequests    => true,   // 429 – rate limited
  HttpStatus.serviceUnavailable => true,   // 503 – overloaded
  HttpStatus.badGateway         => true,   // 502 – upstream error
  HttpStatus.gatewayTimeout     => true,   // 504 – upstream timeout
  _                             => false,
};

Future<int> fetchWithRetry(Uri url, {int maxAttempts = 3}) async {
  final client = HttpClient();
  try {
    for (var attempt = 1; attempt <= maxAttempts; attempt++) {
      final req  = await client.getUrl(url);
      final resp = await req.close();
      await resp.drain<void>();              // discard response body

      if (!isTransient(resp.statusCode)) {
        return resp.statusCode;              // success or permanent error
      }

      print('Attempt $attempt: got ${resp.statusCode}, retrying…');
      // Exponential back-off: 1 s, 2 s, 4 s, …
      await Future.delayed(Duration(seconds: 1 << (attempt - 1)));
    }
  } finally {
    client.close();
  }
  return HttpStatus.serviceUnavailable;
}

void main() {
  // Demonstrate which codes trigger a retry:
  final codes = [200, 201, 400, 404, 429, 500, 502, 503, 504];
  for (final code in codes) {
    print('isTransient($code) = ${isTransient(code)}');
  }
}

The isTransient helper uses a switch expression to map retryable codes to true. Exponential back-off (1 s, 2 s, 4 s) avoids hammering an already-overloaded server. 500 is intentionally not retried because it usually signals a reproducible bug, not a transient overload.

$ dart main.dart
isTransient(200) = false
isTransient(201) = false
isTransient(400) = false
isTransient(404) = false
isTransient(429) = true
isTransient(500) = false
isTransient(502) = true
isTransient(503) = true
isTransient(504) = true

Validating Status Codes

This example demonstrates categorizing any status code into its RFC-defined class using HttpStatus range boundaries.

main.dart
import 'dart:io';

String getStatusCategory(int statusCode) {
  if (statusCode < HttpStatus.continue_ || statusCode > 599) {
    return 'Invalid status code';
  } else if (statusCode < HttpStatus.ok) {
    return '1xx Informational';
  } else if (statusCode < HttpStatus.multipleChoices) {
    return '2xx Success';
  } else if (statusCode < HttpStatus.badRequest) {
    return '3xx Redirection';
  } else if (statusCode < HttpStatus.internalServerError) {
    return '4xx Client Error';
  } else {
    return '5xx Server Error';
  }
}

void main() {
  final codes = [99, 100, 200, 201, 301, 304, 400, 401, 403, 404, 500, 503, 600];
  for (final code in codes) {
    print('$code → ${getStatusCategory(code)}');
  }
}

We categorize status codes into standard HTTP classes using HttpStatus boundary constants. The function covers all valid ranges and rejects codes outside 100–599.

$ dart main.dart
99  → Invalid status code
100 → 1xx Informational
200 → 2xx Success
201 → 2xx Success
301 → 3xx Redirection
304 → 3xx Redirection
400 → 4xx Client Error
401 → 4xx Client Error
403 → 4xx Client Error
404 → 4xx Client Error
500 → 5xx Server Error
503 → 5xx Server Error
600 → Invalid status code

Best Practices

Source

Dart HttpStatus Documentation

This tutorial covered Dart's HttpStatus class with practical examples: making HTTP GET requests, handling authentication and redirect codes, building REST-compliant server responses, implementing retry logic for transient errors, and categorizing status codes by class.

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Dart tutorials.