404 Not Found vs 410 Gone

Fundamentals Jan 9, 2026 HTTP
http status-codes rest client-errors

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

  1. Use 410 for Intentional Deletions - When you deliberately remove content permanently
  2. Use 404 for Unknown Resources - When resource never existed or location unknown
  3. Include Deletion Timestamp - Add deletedAt field in 410 responses
  4. Suggest Alternatives - Provide links to similar resources in 410 responses
  5. Cache 410 Responses - Clients can cache 410 to avoid repeated requests
  6. Log Deletion Reasons - Track why resources were permanently removed
  7. 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