5XX HTTP Status Codes in ASP.NET Core Web API

5XX HTTP Status Codes in ASP.NET Core Web API

In this article, I will explain 5XX HTTP Status Codes in ASP.NET Core Web API Application with examples. Please read our previous article discussing 4XX HTTP Status Codes in ASP.NET Core Web API Applications.

5XX HTTP Status Codes in ASP.NET Core Web API

5XX HTTP status codes indicate server errors. These HTTP status codes are returned when the server encounters a situation that prevents it from fulfilling a valid client request. These errors are typically caused by issues on the server side, such as misconfigurations, server overload, unavailable services, failure to connect to external resources or unexpected failures.

When a client (like a browser or another web service) receives a 5XX response, it usually means an unexpected condition prevents the server from successfully handling the request. The most common 5XX status codes are:

  • 500 Internal Server Error
  • 501 Not Implemented
  • 502 Bad Gateway
  • 503 Service Unavailable
  • 504 Gateway Timeout

500 Internal Server Error

The 500 Internal Server Error is a generic error message and is returned when no more specific information is available. It indicates that the server encountered an unexpected condition that prevented it from fulfilling the request. It doesn’t specify the exact issue but signals a server-side problem. The following are some of the Common Causes:

  • Any unhandled exceptions that occur during the execution of your code, such as logic errors, null reference exceptions, or missing files, will result in a 500 response.
  • Database connection failures (e.g., DB server down). When the server fails to connect to the database due to misconfiguration, authentication errors, or network issues, it will return a 500 error.
  • Null reference exceptions in code.
  • Deployment or configuration issues (missing environment variables, etc.).
How to Return 500 HTTP Status Code in ASP.NET Core Web API

When an unhandled exception occurs in ASP.NET Core, the framework returns a 500 status by default. However, you can manually return a 500 status if you detect a critical failure. For example:

How to Return 500 HTTP Status Code in ASP.NET Core Web API

501 Not Implemented HTTP Status Code

The 501 Not Implemented HTTP status code indicates that the server does not support the functionality required to fulfill the request. This could be due to missing features or unimplemented HTTP methods. Typically returned when a feature is not yet implemented. An Ideal use case is when you have defined an API endpoint but have not yet implemented it. The client calls an endpoint /api/products/bulk-update, but bulk updates are not supported.

How to Return 501 HTTP Status Code in ASP.NET Core Web API

If you have a route or endpoint that you plan to implement later but haven’t done so yet, you can manually return 501 to let the client know it is not available. For example:

How to Return 501 HTTP Status Code in ASP.NET Core Web API

502 Bad Gateway HTTP Status Code

The 502 Bad Gateway status code indicates that the server while acting as a gateway or proxy, received an invalid response from an upstream server. This is typically used when your API relies on an external service or an API that returns an invalid response. The following are the real-time scenarios:

  • External API Failure: If your Web API calls an external service (e.g., a third-party payment gateway), and the external service is down or responds with an error, the API should return a 502 Bad Gateway status.
  • Server Dependency Failure: If your application acts as a proxy to another microservice or internal server and that service is unavailable or misbehaving.

Note: In pure ASP.NET Core code (without a proxy), you rarely explicitly return 502. However, if your app calls other services internally, you might return 502 if the upstream service sends invalid data.

How to Return 502 HTTP Status Code in ASP.NET Core Web API

You might decide to return 502 if an internal microservice or external API you rely on is responding incorrectly. For example:

How to Return 502 HTTP Status Code in ASP.NET Core Web API

503 Service Unavailable HTTP Status Code

The 503 Service Unavailable status code indicates that the server is temporarily unable to handle the request, typically due to being overloaded or undergoing maintenance. The following are the Common Real-World scenarios:

  • Maintenance mode: When your server is down for maintenance, you can return a 503 error to notify users that the service is temporarily unavailable.
  • Server Overload: If the server experiences high traffic, and is overloaded (high memory or CPU usage), and cannot serve additional requests, it may return a 503 error.
  • Throttling: If your application has an internal mechanism to reject requests above a certain threshold, you might return 503 to indicate the service is temporarily unavailable.
  • Database Server is Down: The database server is down for maintenance, and the API cannot process requests.
How to Return 503 HTTP Status Code in ASP.NET Core Web API

You can create an endpoint or middleware that checks if the application is in maintenance mode or a resource threshold is reached, then returns 503. For example:

How to Return 503 HTTP Status Code in ASP.NET Core Web API

504 Gateway Timeout HTTP Status Code

The 504 Gateway Timeout error occurs when a server acting as a gateway or proxy does not receive a timely response from an upstream server. When an ASP.NET Core Web API is waiting for a response from a downstream service (like a database or external API) and the response is not received within the expected timeframe. The following are some of the Common Real-World scenarios:

  • API Timeout: If your API calls another service, and that service takes too long to respond, you might return a 504 Gateway Timeout error.
  • External Server Timeout: When a request to a third-party API or a database server exceeds the time limit, causing a timeout.
How to Return 504 HTTP Status Code in ASP.NET Core Web API

If your ASP.NET Core service is the one calling to other services, you may detect a timeout and return 504. For example:

5XX HTTP Status Codes in ASP.NET Core Web API

Example to Understand 5XX HTTP Status Codes in ASP.NET Core Web API:

In the Controllers folder, add a new Empty API controller named ServerErrorDemoController and copy and paste the following code. The following controller will demonstrate the different 5XX HTTP status codes in ASP.NET Core Web API:

using Microsoft.AspNetCore.Mvc;

namespace HTTPStatusCodeDemo.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class ServerErrorDemoController : ControllerBase
    {
        // 500 Internal Server Error
        // A database query fails unexpectedly, or a piece of code throws an exception that’s not caught.
        [HttpGet("simulate-500")]
        public IActionResult Simulate500()
        {
            try
            {
                // Force an exception
                throw new Exception("Simulated exception for demonstration.");
            }
            catch (Exception ex)
            {
                var errorResponse = new
                {
                    Detail = $"Server error: {ex.Message}",
                    StatusCode = StatusCodes.Status500InternalServerError,
                    Title = "Internal Server Error"
                };

                return StatusCode(StatusCodes.Status500InternalServerError, errorResponse);
            }
        }

        // 501 Not Implemented
        // An API endpoint for updating product details is requested, but the functionality has not been implemented yet.
        [HttpGet("simulate-501")]
        public IActionResult Simulate501()
        {
            var errorResponse = new
            {
                Detail = "This functionality has not been implemented yet.",
                StatusCode = StatusCodes.Status501NotImplemented,
                Title = "501 Not Implemented"
            };

            return StatusCode(StatusCodes.Status501NotImplemented, errorResponse);
        }

        // 502 Bad Gateway
        // Your API is a reverse proxy to another service, and that service returns invalid data or is down.
        [HttpGet("simulate-502")]
        public IActionResult Simulate502()
        {
            // Pretend we called an upstream service and got an invalid response
            bool invalidUpstreamResponse = true;
            if (invalidUpstreamResponse)
            {
                var errorResponse = new
                {
                    Detail = "Received invalid response from upstream service.",
                    StatusCode = StatusCodes.Status502BadGateway,
                    Title = "502 Bad Gateway"
                };

                return StatusCode(StatusCodes.Status502BadGateway, errorResponse);
            }

            return Ok("Upstream service responded properly.");
        }

        // 503 Service Unavailable
        // You are performing maintenance, or your server is under heavy load and cannot handle requests temporarily.
        [HttpGet("simulate-503")]
        public IActionResult Simulate503()
        {
            bool _maintenanceMode = true;
            if (_maintenanceMode)
            {
                var errorResponse = new
                {
                    Detail = "Service is under maintenance. Please try again later.",
                    StatusCode = StatusCodes.Status503ServiceUnavailable,
                    Title = "503 Service Unavailable"
                };

                return StatusCode(StatusCodes.Status503ServiceUnavailable, errorResponse);
            }
            return Ok("Service is running normally.");
        }

        // 504 Gateway Timeout
        // Your API calls an upstream service that takes too long to respond.
        [HttpGet("simulate-504")]
        public IActionResult Simulate504()
        {
            // Simulate waiting for a service that times out
            bool serviceTimedOut = true;
            if (serviceTimedOut)
            {
                var errorResponse = new
                {
                    Detail = "The upstream service timed out.",
                    StatusCode = StatusCodes.Status504GatewayTimeout,
                    Title = "504 Gateway Timeout"
                };

                return StatusCode(StatusCodes.Status504GatewayTimeout, errorResponse);
            }

            return Ok("Received response before timeout.");
        }
    }
}
Program Configuration:

Modify the Program class as follows:

namespace HTTPStatusCodeDemo
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var builder = WebApplication.CreateBuilder(args);

            // Add services to the container.

            // Add controller services and configure JSON options
            builder.Services.AddControllers()
               .AddJsonOptions(options =>
               {
                   // Disable the default camelCase naming policy to preserve property names as defined
                   options.JsonSerializerOptions.PropertyNamingPolicy = null;
               });

            // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
            builder.Services.AddEndpointsApiExplorer();
            builder.Services.AddSwaggerGen();

            var app = builder.Build();

            // Configure the HTTP request pipeline.
            if (app.Environment.IsDevelopment())
            {
                app.UseSwagger();
                app.UseSwaggerUI();
            }

            app.UseHttpsRedirection();

            app.UseAuthorization();

            app.MapControllers();

            app.Run();
        }
    }
}

Testing 5XX HTTP Status Codes:

Simulate 500

GET: https://localhost:7067/api/ServerErrorDemo/simulate-500

Response:

{
  "Detail": "Server error: Simulated exception for demonstration.",
  "StatusCode": 500,
  "Title": "Internal Server Error"
}
Simulate 501

GET: https://localhost:7067/api/ServerErrorDemo/simulate-501

Response:

{
  "Detail": "This functionality has not been implemented yet.",
  "StatusCode": 501,
  "Title": "501 Not Implemented"
}
Simulate 502

GET: https://localhost:7067/api/ServerErrorDemo/simulate-502

Response:

{
  "Detail": "Received invalid response from upstream service.",
  "StatusCode": 502,
  "Title": "502 Bad Gateway"
}
Simulate 503

GET: https://localhost:7067/api/ServerErrorDemo/simulate-503

Response:

{
  "Detail": "Service is under maintenance. Please try again later.",
  "StatusCode": 503,
  "Title": "503 Service Unavailable"
}
Simulate 504

GET: https://localhost:7067/api/ServerErrorDemo/simulate-504

Response:

{
  "Detail": "The upstream service timed out.",
  "StatusCode": 504,
  "Title": "504 Gateway Timeout"
}

Global Exception Handling

Instead of manually handling exceptions in each controller action, it’s best practice to implement a global exception-handling middleware. This centralizes error handling, ensuring uniform responses for unhandled exceptions.

Creating the Exception Handling Middleware

Create a new folder named Middleware in the project root. Within the Middleware folder, create a file named ExceptionHandlingMiddleware.cs and add the following code:

using Microsoft.AspNetCore.Mvc;      
using System.Net;                    

namespace HTTPStatusCodeDemo.Middleware
{
    public class ExceptionHandlingMiddleware
    {
        // A delegate that defines the next middleware in the pipeline
        private readonly RequestDelegate _next;

        // The middleware's constructor receives the next middleware delegate from the pipeline
        public ExceptionHandlingMiddleware(RequestDelegate next)
        {
            // Store the next delegate so we can call it when we're done with our logic
            _next = next;
        }

        // This is the method called by the ASP.NET Core runtime to process each HTTP request
        public async Task InvokeAsync(HttpContext context)
        {
            try
            {
                // We pass the request to the next middleware in the pipeline
                // If everything runs without throwing an exception, we come back here afterward
                await _next(context);

                // After the next middleware has finished, we check if the response status code
                // is in the 5XX range (500 to 599). This indicates a server error.
                // Also check Response.HasStarted to ensure we can still modify the response.
                if (context.Response.StatusCode >= 500 && context.Response.StatusCode <= 599
                    && !context.Response.HasStarted)
                {
                    // If we detect a 5XX status but no exception was thrown (maybe set manually
                    // in a controller), we handle it here to provide a custom JSON error response.
                    await HandleNonException5xx(context);
                }
            }
            catch (Exception ex)
            {
                // If any unhandled exception bubbles up here, we treat it as a 500 error
                // and call HandleExceptionAsync for a consistent JSON error response.
                await HandleExceptionAsync(context, ex);
            }
        }

        // This method handles all exceptions that we didn't explicitly catch in the pipeline
        private async Task HandleExceptionAsync(HttpContext context, Exception exception)
        {
            // Clear any partial response or headers that might have been set before the exception
            context.Response.Clear();

            // Set the Content-Type so clients know we're returning JSON
            context.Response.ContentType = "application/json";

            // We explicitly set the status code to 500 (Internal Server Error) in this scenario
            context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;

            // Build a ProblemDetails object (according to RFC 7807) with relevant information
            var problemDetails = new ProblemDetails
            {
                Title = "Internal Server Error",                  // A short, human-readable summary of the error
                Status = StatusCodes.Status500InternalServerError, // Numeric status code (500)
                Detail = exception.Message,                       // More details about the error (exception message)
                Instance = context.Request.Path                   // The request path where the error happened
            };

            // Serialize this ProblemDetails object as JSON and write it to the response body
            await context.Response.WriteAsJsonAsync(problemDetails);
        }

        // This method handles scenarios where the response code is 5XX
        // but no unhandled exception was thrown.
        private async Task HandleNonException5xx(HttpContext context)
        {
            // Again, set the Content-Type to JSON so we return a structured JSON error
            context.Response.ContentType = "application/json";

            // We know the status is in the 500–599 range, so we keep that status code
            int statusCode = context.Response.StatusCode;

            // Create a new ProblemDetails object to describe the error
            var problemDetails = new ProblemDetails
            {
                Status = statusCode,                // Set the Status to the existing 5XX code
                Instance = context.Request.Path      // Which path caused the error?
            };

            // Use a switch statement to provide specific messages for various 5XX status codes
            switch (statusCode)
            {
                case 500:
                    problemDetails.Title = "Internal Server Error";
                    problemDetails.Detail = "An internal server error occurred.";
                    break;
                case 501:
                    problemDetails.Title = "Not Implemented";
                    problemDetails.Detail = "This feature is not implemented yet.";
                    break;
                case 502:
                    problemDetails.Title = "Bad Gateway";
                    problemDetails.Detail = "The server received an invalid response from an upstream service.";
                    break;
                case 503:
                    problemDetails.Title = "Service Unavailable";
                    problemDetails.Detail = "The service is temporarily unavailable. Please try again later.";
                    break;
                case 504:
                    problemDetails.Title = "Gateway Timeout";
                    problemDetails.Detail = "The server timed out while waiting for an upstream service.";
                    break;
                default:
                    // If it's some other 5XX code not specifically listed above,
                    // we give a generic "Server Error" response
                    problemDetails.Title = "Server Error";
                    problemDetails.Detail = "A server error occurred.";
                    break;
            }

            // Return the ProblemDetails object as JSON in the response body
            await context.Response.WriteAsJsonAsync(problemDetails);
        }
    }
}
Register Custom Middleware

Please modify the Program class as follows:

using HTTPStatusCodeDemo.Middleware;

namespace HTTPStatusCodeDemo
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var builder = WebApplication.CreateBuilder(args);

            // Add services to the container.

            // Add controller services and configure JSON options
            builder.Services.AddControllers()
               .AddJsonOptions(options =>
               {
                   // Disable the default camelCase naming policy to preserve property names as defined
                   options.JsonSerializerOptions.PropertyNamingPolicy = null;
               });

            // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
            builder.Services.AddEndpointsApiExplorer();
            builder.Services.AddSwaggerGen();

            var app = builder.Build();

            // Configure the HTTP request pipeline.
            if (app.Environment.IsDevelopment())
            {
                app.UseSwagger();
                app.UseSwaggerUI();
            }

            app.UseHttpsRedirection();

            // Register your custom middleware to handle 5XX
            // This should come before app.MapControllers
            app.UseMiddleware<ExceptionHandlingMiddleware>();

            app.UseAuthorization();

            app.MapControllers();

            app.Run();
        }
    }
}
Modifying the Controller:

To ensure that the custom middleware returns 5XX ProblemDetails with the messages we have configured there instead of the controller returning its own JSON. The following key changes we need to do:

  • Remove the manual creation of error responses in the controller (the anonymous objects).
  • Either throw exceptions for unhandled errors (which will become a 500 in the middleware), or set HttpContext.Response.StatusCode for other 5XX codes (501, 502, 503, 504, etc.) and return an empty result (so the middleware can detect the 5XX status and provide the final JSON response).

The custom middleware will generate the title, detail, status (etc.) with these modifications. For a better understanding, please modify the ServerErrorDemoController as follows:

using Microsoft.AspNetCore.Mvc;

namespace HTTPStatusCodeDemo.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class ServerErrorDemoController : ControllerBase
    {
        // 500 Internal Server Error
        [HttpGet("simulate-500")]
        public IActionResult Simulate500()
        {
            // Just throw the exception
            // The middleware will catch it and return 500 "Internal Server Error" with ProblemDetails
            throw new Exception("Simulated exception for demonstration.");
        }

        // 501 Not Implemented
        [HttpGet("simulate-501")]
        public IActionResult Simulate501()
        {
            // Set the response status to 501, return EmptyResult
            // The middleware sees that it's 5XX, and will produce the correct ProblemDetails JSON.
            HttpContext.Response.StatusCode = StatusCodes.Status501NotImplemented;
            return new EmptyResult();
        }

        // 502 Bad Gateway
        [HttpGet("simulate-502")]
        public IActionResult Simulate502()
        {
            bool invalidUpstreamResponse = true;
            if (invalidUpstreamResponse)
            {
                HttpContext.Response.StatusCode = StatusCodes.Status502BadGateway;
                return new EmptyResult();
            }

            return Ok("Upstream service responded properly.");
        }

        // 503 Service Unavailable
        [HttpGet("simulate-503")]
        public IActionResult Simulate503()
        {
            bool maintenanceMode = true;
            if (maintenanceMode)
            {
                HttpContext.Response.StatusCode = StatusCodes.Status503ServiceUnavailable;
                return new EmptyResult();
            }

            return Ok("Service is running normally.");
        }

        // 504 Gateway Timeout
        [HttpGet("simulate-504")]
        public IActionResult Simulate504()
        {
            bool serviceTimedOut = true;
            if (serviceTimedOut)
            {
                HttpContext.Response.StatusCode = StatusCodes.Status504GatewayTimeout;
                return new EmptyResult();
            }

            return Ok("Received response before timeout.");
        }
    }
}
Code Explanation
simulate-500:
  • Throws an exception (no try/catch). The middleware will catch this exception and generate a 500 response with the custom error body (title, detail, status, etc.) that you defined in HandleExceptionAsync(…).
simulate-501, 502, 503, 504:
  • We set Response.StatusCode to the desired 5XX code (501, 502, 503, or 504).
  • Return new EmptyResult() so the controller does not override the response body.
  • After the request returns to the middleware, it sees a 5XX code and calls HandleNonException5xx(context), which contains your switch logic (title, detail) for each 5XX code.

With the above changes in place, run the application and test the endpoints, and it should work as expected.

Summary of Common 5XX HTTP Status Codes in ASP.NET Core Web API:

By explicitly returning 5XX codes, we clarify to the client why their request failed. Additionally, please log these responses using structured logging to gain insights into impacted endpoints and root causes. The summary is as follows:

  • 500 Internal Server Error: This general-purpose error code indicates the server encountered an unexpected condition. It’s often used as a fallback when a more specific code isn’t applicable.
  • 501 Not Implemented: Indicates that the server does not recognize the request method or cannot fulfill it. This is often used when a feature is not yet implemented.
  • 502 Bad Gateway: This means the server, while acting as a gateway or proxy, received an invalid response from the upstream server. This could happen if the upstream server is down or returns an unexpected response.
  • 503 Service Unavailable: Indicates that the server is temporarily unavailable, usually due to maintenance or overloading. It typically suggests that the client should try again later.
  • 504 Gateway Timeout: This indicates that the server, acting as a gateway or proxy, did not receive a timely response from an upstream server.

We can create more reliable and maintainable applications by understanding these 5XX HTTP Status Codes and implementing appropriate handling in our ASP.NET Core Web API applications.

In the next article, I will discuss Dependency Injection in ASP.NET Core Web API applications with Examples. In this article, I explain 5XX HTTP Status Codes in ASP.NET Core Web API application with multiple Examples. I hope you enjoy this article on “5XX HTTP Status Codes in ASP.NET Core Web API”.

Leave a Reply

Your email address will not be published. Required fields are marked *