Buggy Created response in ASP.NET Core 8

August 22nd 2025 ASP.NET Core .NET

After getting a bug report that an ASP.NET Core Web API endpoint is returning 204 (No content) instead of 201 (Created), it turned out that the bug wasn't in the application code but in .NET.

The bug is easy to reproduce in .NET 8. All you need is an endpoint returning Created() without any arguments:

[HttpPost("empty")]
public IActionResult CreateEmpty(Message request)
{
    try
    {
        var message = Create(request);
        return Created();
    }
    catch (ArgumentException)
    {
        return Conflict();
    }
}

Surprisingly, it will return a 204 response:

[Test]
public async Task EmptyReturns204OnSuccess()
{
    using var httpClient = _factory.CreateClient();

    var message = new Message(Guid.NewGuid(), "foo");
    var response = await httpClient.PostAsJsonAsync("/message/empty", message);

    response.StatusCode.ShouldBe(HttpStatusCode.NoContent);
}

This was reported as a bug, but fixed only for .NET 9. so, in .NET 9, the same code returns 201, as expected:

[Test]
public async Task EmptyReturns201OnSuccess()
{
    using var httpClient = _factory.CreateClient();

    var message = new Message(Guid.NewGuid(), "foo");
    var response = await httpClient.PostAsJsonAsync("/message/empty", message);

    response.StatusCode.ShouldBe(HttpStatusCode.Created);
}

However, this is of no help if you want to stick with an LTS (Long Term Support) version of .NET. Fortunately, there are workarounds available:

  • Use StatusCode() instead of Created():

    return StatusCode(StatusCodes.Status201Created);
    
  • Provide at least a Location header with the URL to the created resource in the response, as recommended:

    return Created(Url.Action("GetMessage", new { id = message.Id }), null);
    

In both of these cases, the endpoint will return a 201 response, as expected, even in .NET 8.

A sample project reproducing the bug is available in my GitHub repository. The latest commit uses .NET 9 with the bug already fixed. The one before that uses .NET 8 where the bug is still present.

I was surprised to encounter such a bug in .NET 8 almost 2 years after its initial release. I was even more surprised when I learned that it was fixed in .NET 9, but the fix wasn't backported to .NET 8 even though it is an LTS version. I wonder what is the reasoning for this. Well, at least the workarounds are easy to implement.

Get notified when a new blog post is published (usually every Friday):

Copyright
Creative Commons License