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.
| Constant | Value | Meaning |
|---|---|---|
| HttpStatus.continue_ | 100 | Server received headers; client should proceed |
| HttpStatus.ok | 200 | Request succeeded |
| HttpStatus.created | 201 | Resource created successfully |
| HttpStatus.accepted | 202 | Request accepted but not yet completed |
| HttpStatus.noContent | 204 | Success with no response body |
| HttpStatus.movedPermanently | 301 | Resource moved permanently to new URL |
| HttpStatus.found | 302 | Resource temporarily at different URL |
| HttpStatus.seeOther | 303 | Redirect to another resource using GET |
| HttpStatus.notModified | 304 | Cached version is still valid |
| HttpStatus.temporaryRedirect | 307 | Temporary redirect, keep original method |
| HttpStatus.permanentRedirect | 308 | Permanent redirect, keep original method |
| HttpStatus.badRequest | 400 | Malformed or invalid request |
| HttpStatus.unauthorized | 401 | Authentication required |
| HttpStatus.forbidden | 403 | Authenticated but not authorized |
| HttpStatus.notFound | 404 | Resource not found |
| HttpStatus.methodNotAllowed | 405 | HTTP method not supported for this resource |
| HttpStatus.conflict | 409 | Request conflicts with current state |
| HttpStatus.gone | 410 | Resource permanently removed |
| HttpStatus.unprocessableEntity | 422 | Validation errors in request body |
| HttpStatus.tooManyRequests | 429 | Rate limit exceeded |
| HttpStatus.internalServerError | 500 | Unexpected server-side error |
| HttpStatus.badGateway | 502 | Invalid response from upstream server |
| HttpStatus.serviceUnavailable | 503 | Server temporarily unavailable |
| HttpStatus.gatewayTimeout | 504 | Upstream server timed out |
Checking Success Status
This example demonstrates checking if an HTTP response indicates success. Success responses fall in the 200–299 range.
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.
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.
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.
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.
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.
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.
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.
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.
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
- Use named constants: Always prefer
HttpStatus.notFoundover the raw integer404— it is self-documenting and catches typos at compile time. - Range checks: Use boundary constants such as
HttpStatus.okandHttpStatus.multipleChoicesto test ranges rather than hard-coding 200 and 300. - Distinguish 401 vs 403: Return 401 when the caller is not authenticated, and 403 when the caller is authenticated but unauthorized. Mixing them up confuses API clients.
- Use 201 for POST creation: Return 201 Created (not 200
OK) when a POST request successfully creates a resource, and include a
Locationheader pointing to the new resource. - Return 204 for DELETE: Return 204 No Content after a successful DELETE so clients know the operation succeeded without expecting a response body.
- Prefer 422 over 400 for validation: Use 422 Unprocessable Entity when the request is syntactically valid but fails business-rule validation, and reserve 400 for structurally malformed requests.
- Retry selectively: Only retry on transient errors (429, 502, 503, 504). Never retry 4xx client errors because the response will be identical on every attempt.
- Use switch expressions: Dart 3 switch expressions give a compile warning for unhandled cases, making status-code dispatch exhaustive and safer.
Source
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
List all Dart tutorials.