Member-only story
C# & .NET for Java/Spring Developers-2
In Part 1 we compared project files and dotnet CLI basics. This time it’s async. If you’ve written a Spring Boot service that fans out to two or three downstream calls, you’ve reached for CompletableFuture, WebClient’s reactive chain, or, since Java 21, a virtual thread that lets you write it like blocking code again and let the runtime worry about the rest. C# solved this at the language level a decade earlier with async/await, and Task<T> ends up looking a lot like CompletableFuture<T> with a friendlier syntax on top.
This part covers the mechanics: how Task<T> compares to CompletableFuture<T>, how await replaces .thenApply/.thenCompose chains, how errors propagate, how you actually run things in parallel instead of accidentally serializing them, and where cancellation differs between the two runtimes.
The Mental Model: Task<T> vs CompletableFuture<T>
Both represent a value that will exist eventually. The difference is how much the language helps you work with that value.
public class InventoryClient
{
private readonly HttpClient _httpClient;
public InventoryClient(HttpClient httpClient)
{
_httpClient = httpClient;
}
public async Task<InventoryLevel> GetInventoryAsync(string sku, CancellationToken cancellationToken = default)
{…








