Definition
404 Not Found and 410 Gone are both HTTP client error codes indicating a resource is unavailable, but they convey different semantic meanings about the resource’s state.
404 Not Found:
- Meaning: The requested resource could not be found on the server
- Reason: Resource never existed, wrong URI, or moved without redirect
- Permanence: Status may be temporary or permanent (ambiguous)
- Caching: Clients may retry later (could be a temporary issue)
- Use Case: Resource doesn’t exist, wrong URL, or soft-deleted items
410 Gone:
- Meaning: The resource existed previously but has been permanently removed
- Reason: Intentional permanent deletion (e.g., content removed, user deleted account)
- Permanence: Explicitly permanent (resource will NEVER return)
- Caching: Clients should not retry and can purge cached references
- Use Case: Content takedowns, expired promotions, deleted user accounts
Key Distinction:
- 404 β “I don’t have that resource” (ambiguous - might come back)
- 410 β “I used to have it, but it’s gone forever” (definitive)
Example
404 Not Found - Resource Never Existed:
GET /api/users/999 HTTP/1.1
Host: api.example.com
HTTP/1.1 404 Not Found
Content-Type: application/json
{
"error": "Not Found",
"message": "User with ID 999 does not exist"
}
404 Not Found - Wrong URL:
GET /api/usres/123 HTTP/1.1
Host: api.example.com
HTTP/1.1 404 Not Found
Content-Type: application/json
{
"error": "Not Found",
"message": "The requested endpoint does not exist. Did you mean /api/users/123?"
}
410 Gone - Permanently Deleted:
GET /api/users/123 HTTP/1.1
Host: api.example.com
HTTP/1.1 410 Gone
Content-Type: application/json
{
"error": "Gone",
"message": "This user account was permanently deleted on 2026-01-05",
"deletedAt": "2026-01-05T10:30:00Z",
"reason": "User requested account deletion"
}
410 Gone - Expired Content:
GET /api/promotions/summer-sale-2025 HTTP/1.1
Host: api.example.com
HTTP/1.1 410 Gone
Content-Type: application/json
{
"error": "Gone",
"message": "This promotion has expired and is no longer available",
"expiredAt": "2025-09-01T00:00:00Z",
"alternative": "/api/promotions/current"
}
Code Example
JavaScript (Fetch API):
const fetchUser = async (userId) => {
try {
const response = await fetch(`https://api.example.com/users/${userId}`, {
headers: {
'Accept': 'application/json',
'Authorization': 'Bearer YOUR_TOKEN'
}
});
// Handle 404 Not Found
if (response.status === 404) {
const error = await response.json();
console.warn('404 Not Found:', error.message);
// Could retry later or check if URL is correct
// Resource might exist at a different location
throw new Error(`User ${userId} not found`);
}
// Handle 410 Gone
if (response.status === 410) {
const error = await response.json();
console.error('410 Gone:', error.message);
// Resource permanently deleted - don't retry
// Remove from cache, delete bookmarks, etc.
removeFromCache(userId);
throw new Error(`User ${userId} was permanently deleted: ${error.message}`);
}
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return await response.json();
} catch (error) {
console.error('Error fetching user:', error);
throw error;
}
};
const removeFromCache = (userId) => {
// Remove from local cache
localStorage.removeItem(`user-${userId}`);
// Remove from UI bookmarks
console.log(`Removed user ${userId} from cache`);
};
// Example: Handling with different UI feedback
const displayUser = async (userId) => {
try {
const user = await fetchUser(userId);
console.log('User:', user);
} catch (error) {
if (error.message.includes('not found')) {
// 404 - might be temporary
alert('User not found. Please check the user ID and try again.');
} else if (error.message.includes('permanently deleted')) {
// 410 - permanent, don't offer retry
alert('This user account has been permanently deleted and cannot be accessed.');
window.location.href = '/users'; // Redirect to users list
}
}
};
Python (requests library):
import requests
def fetch_user(user_id):
try:
response = requests.get(
f'https://api.example.com/users/{user_id}',
headers={
'Accept': 'application/json',
'Authorization': 'Bearer YOUR_TOKEN'
}
)
# Handle 404 Not Found
if response.status_code == 404:
error = response.json()
print(f'404 Not Found: {error.get("message")}')
# Could retry later or check if URL is correct
# Resource might exist at a different location
raise FileNotFoundError(f'User {user_id} not found')
# Handle 410 Gone
if response.status_code == 410:
error = response.json()
print(f'410 Gone: {error.get("message")}')
# Resource permanently deleted - don't retry
# Remove from cache, delete bookmarks, etc.
remove_from_cache(user_id)
raise Exception(f'User {user_id} was permanently deleted: {error.get("message")}')
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
print(f'Error fetching user: {e}')
raise
def remove_from_cache(user_id):
# Remove from cache/database
print(f'Removed user {user_id} from cache')
# Example: Handling with different UI feedback
def display_user(user_id):
try:
user = fetch_user(user_id)
print(f'User: {user}')
except FileNotFoundError as e:
# 404 - might be temporary
print(f'User not found: {e}')
print('Please check the user ID and try again.')
except Exception as e:
# 410 - permanent, don't offer retry
if 'permanently deleted' in str(e):
print(f'User permanently deleted: {e}')
print('This user account cannot be accessed.')
Diagram
flowchart TB
START[GET /api/resource/123] --> EXISTS{Resource
Exists?}
EXISTS -->|No| EVER_EXISTED{Ever
Existed?}
EXISTS -->|Yes| RETURN_200[Return 200 OK
with resource data]
EVER_EXISTED -->|Never| RETURN_404[Return 404 Not Found
Resource unknown]
EVER_EXISTED -->|Yes, Deleted| PERMANENT{Permanent
Deletion?}
PERMANENT -->|Yes| RETURN_410[Return 410 Gone
Permanently removed]
PERMANENT -->|No| RETURN_404_SOFT[Return 404 Not Found
Soft deleted/hidden]
RETURN_404 --> CLIENT_404[Client: Check URL
May retry later]
RETURN_404_SOFT --> CLIENT_404_SOFT[Client: Check permissions
May retry]
RETURN_410 --> CLIENT_410[Client: Purge cache
Never retry]
RETURN_200 --> CLIENT_200[Client: Use resource]
style RETURN_404 fill:#ffa726
style RETURN_404_SOFT fill:#ffa726
style RETURN_410 fill:#ef5350
style RETURN_200 fill:#66bb6a
Analogy
Think of 404 vs 410 like looking for a book in a library:
- 404 Not Found β “We don’t have that book” (might never had it, or it’s checked out, or misplaced)
- 410 Gone β “We used to have it, but we permanently removed it from the collection” (weeded out, destroyed, or deaccessioned)
With 404, you might check back later. With 410, don’t bother - it’s never coming back.
Best Practices
- Use 410 for Intentional Deletions - When you deliberately remove content permanently
- Use 404 for Unknown Resources - When resource never existed or location unknown
- Include Deletion Timestamp - Add
deletedAtfield in 410 responses - Suggest Alternatives - Provide links to similar resources in 410 responses
- Cache 410 Responses - Clients can cache 410 to avoid repeated requests
- Log Deletion Reasons - Track why resources were permanently removed
- Soft Delete vs Hard Delete - Use 404 for soft deletes, 410 for hard deletes
Common Mistakes
- Using 404 for Everything - Not using 410 when resources are permanently deleted
- No Deletion Info - Not explaining why resource is gone in 410 responses
- Inconsistent Semantics - Using 410 for temporary unavailability (should be 503)
- No Timestamp - Not including when resource was deleted in 410 responses
- Retrying 410 - Clients retrying 410 responses (should purge from cache)
- 404 for Soft Deletes - Confusing soft deletes with permanent deletions
- No Cache Headers - Not setting appropriate caching for 404/410 responses
Standards & RFCs
Standards & RFCs