dotnet/eShop · error · CatalogDomainException
Empty stock, product item {Name} is sold out
Error message
Empty stock, product item {Name} is sold out What it means
Thrown by CatalogItem.RemoveStock when AvailableStock is exactly zero. RemoveStock is the only sanctioned way to decrement catalog inventory (DDD aggregate behavior); it refuses to operate on a sold-out item rather than returning a partial/zero result. It is a CatalogDomainException, surfaced to callers as a domain rule violation.
Source
Thrown at src/Catalog.API/Model/CatalogItem.cs:66
/// <summary>
/// Decrements the quantity of a particular item in inventory and ensures the restockThreshold hasn't
/// been breached. If so, a RestockRequest is generated in CheckThreshold.
///
/// If there is sufficient stock of an item, then the integer returned at the end of this call should be the same as quantityDesired.
/// In the event that there is not sufficient stock available, the method will remove whatever stock is available and return that quantity to the client.
/// In this case, it is the responsibility of the client to determine if the amount that is returned is the same as quantityDesired.
/// It is invalid to pass in a negative number.
/// </summary>
/// <param name="quantityDesired"></param>
/// <returns>int: Returns the number actually removed from stock. </returns>
///
public int RemoveStock(int quantityDesired)
{
if (AvailableStock == 0)
{
throw new CatalogDomainException($"Empty stock, product item {Name} is sold out");
}
if (quantityDesired <= 0)
{
throw new CatalogDomainException($"Item units desired should be greater than zero");
}
int removed = Math.Min(quantityDesired, this.AvailableStock);
this.AvailableStock -= removed;
return removed;
}
/// <summary>
/// Increments the quantity of a particular item in inventory.
/// <param name="quantity"></param>
/// <returns>int: Returns the quantity that has been added to stock</returns>View on GitHub (pinned to 9b4f9434f4)
Solutions
- Before calling RemoveStock, check AvailableStock and short-circuit or surface an out-of-stock message to the user when it is 0.
- Treat the returned int as authoritative: RemoveStock may return fewer units than requested when stock is partial — if it returns 0, branch to out-of-stock handling instead of retrying blindly.
- Use optimistic concurrency (a row version / ETag on CatalogItem) to prevent two writers racing the last unit, and re-read stock on a concurrency conflict before retrying.
- Restock via AddStock so AvailableStock is non-zero before further RemoveStock calls.
Example fix
// before
var taken = item.RemoveStock(qty);
// after
if (item.AvailableStock == 0) {
return Result.OutOfStock(item.Name);
}
var taken = item.RemoveStock(qty);
if (taken < qty) {
return Result.PartiallyFulfilled(taken);
} Defensive patterns
Strategy: validation
Validate before calling
if (item.AvailableStock <= 0) {
return Result.Fail($"{item.Name} is sold out");
}
var taken = item.RemoveStock(qty); Type guard
static bool HasStock(CatalogItem item) => item.AvailableStock > 0;
Try / catch
try {
var taken = item.RemoveStock(qty);
} catch (CatalogDomainException ex) when (ex.Message.Contains("sold out")) {
// surface out-of-stock to the user; do not retry blindly
} Prevention
- Read AvailableStock fresh (no stale cache) before reserving.
- Use optimistic concurrency on CatalogItem to serialize last-unit races.
- Treat the returned int as authoritative and handle partial fulfillment.
When it happens
Trigger: A checkout/stock-reservation flow calls RemoveStock(quantityDesired) for an item whose AvailableStock has already been driven to 0 by prior orders. Also triggered by race conditions where two concurrent reservations drain the last unit, or by stale catalog data read before a previous decrement committed.
Common situations: High-demand/SKU product selling out under load; catalog cache returning an outdated positive stock figure; ordering service processing a backlogged queue after the catalog was depleted; integration tests not reseeding stock between runs.
Related errors
- Item units desired should be greater than zero
- Is not possible to change the order status from {OrderStatus
- Invalid number of units
- The total of order item is lower than applied discount
- Discount is not valid
AI-assisted analysis of dotnet/eShop@9b4f9434f4 (2026-08-13).
Data as JSON: /api/errors/e84acdcdfd3a2e63.
Report an issue: GitHub.