Why I stopped using exceptions for control flow in my .NET 8 APIs For a long time, my .NET APIs looked like this: public async Task < Product > GetByIdAsync ( string id ) { var product = await _collection . Find ( x => x . Id == id ). FirstOrDefaultAsync (); if ( product is null ) throw new KeyNotFoundException ( $"Product with id ' { id } ' was not found." ); return product ; } Enter fullscreen mode Exit fullscreen mode And in the endpoint: app . MapGet ( "/api/products/{id}" , async ( string id , IProductRepository repo ) => { try { var product = await repo . GetByIdAsync ( id ); return Results . Ok ( product ); } catch ( KeyNotFoundException ex ) { return Results . NotFound ( ex . Message ); } catch ( Exception ex ) { return Results . Problem ( ex . Message ); } }); Enter fullscreen mode Exit fullscreen mode It worked. But every endpoint had a try/catch. Every service method threw a different exception type. The caller had to know which exceptions to catch.…