Introduction
.NET 10 landed last November and it's the most meaningful release for microservices developers in years. We've been using it in production at PiCode across a few client projects, and the improvements are real — not just incremental.
This post covers the features that actually matter when you're building microservices: orchestration, container performance, API ergonomics, and observability. No hype, just what we've shipped.
.NET Aspire: Finally, First-Class Orchestration
The biggest shift in .NET 10 is how mature .NET Aspire has become. It's no longer an experimental side project — it's the recommended way to build distributed applications in .NET.
Aspire gives you:
- Service orchestration — define your services, databases, message brokers, and caches in code, and the AppHost spins them all up for local development
- Service discovery — no more hardcoded URLs or manually wiring up HttpClient base addresses
- Environment propagation — connection strings, secrets, and configuration flow automatically between services
- Built-in dashboard — a local dashboard that shows logs, traces, metrics, and health across every service
A typical AppHost looks like this:
var builder = DistributedApplication.CreateBuilder(args);
var postgres = builder.AddPostgres("db")
.AddDatabase("orders");
var redis = builder.AddRedis("cache");
var apiService = builder.AddProject<Projects.OrderService_Api>("orders-api")
.WithReference(postgres)
.WithReference(redis);
builder.AddProject<Projects.Gateway>("gateway")
.WithReference(apiService);
builder.Build().Run();
That's your entire local development environment — databases, caches, and services all wired together. When it deploys to Azure, the same graph maps to Container Apps.
Native AOT: Smaller, Faster Containers
Native AOT (Ahead-of-Time) compilation went from "interesting experiment" in .NET 8 to "production-ready default" in .NET 10. The improvements matter for microservices because container size and startup time directly affect your costs.
What we're seeing in practice:
- Container image size — dropped from ~200MB to ~30MB for typical API services
- Cold start time — went from 800ms-1.5s down to 30-80ms
- Memory footprint — roughly half of the runtime-based equivalent
For scale-to-zero scenarios in Azure Container Apps or AWS Lambda, this is a big deal. We have one service that scales to zero overnight, and the cold start experience went from "user waits a second" to "user doesn't notice at all."
The tradeoff: build times are longer, and some reflection-heavy libraries still need work. EF Core has AOT support now, but complex scenarios with dynamic queries may require tweaks.
Minimal APIs Grow Up
Minimal APIs in .NET 10 have matured into a serious alternative to controllers for microservices:
- Endpoint groups with shared filters and middleware
- Native validation via
MinimalApis.Extensionsand the new[Validate]attribute - Better OpenAPI support — the built-in generator is finally comparable to Swashbuckle
- Type-safe results with
TypedResultseliminating manual status code handling
A realistic service endpoint now looks like:
app.MapGroup("/api/orders")
.RequireAuthorization()
.WithOpenApi()
.MapPost("/", async (
CreateOrderRequest request,
IOrderService orders,
CancellationToken ct) =>
{
var order = await orders.CreateAsync(request, ct);
return TypedResults.Created($"/api/orders/{order.Id}", order);
});
The result: less ceremony, better performance, and the type inference makes it harder to return wrong status codes.
HTTP Client Improvements
For service-to-service communication, .NET 10's HTTP stack got real attention:
- HTTP/3 enabled by default — lower latency, better mobile performance
- Resilience handlers — Polly is now integrated out of the box via
AddStandardResilienceHandler() - Automatic retries with jitter — no more writing the same retry policies in every service
Adding resilience to an HTTP client is now a one-liner:
builder.Services.AddHttpClient<IInventoryClient, InventoryClient>()
.AddStandardResilienceHandler();
That gives you retries, circuit breakers, timeouts, and rate limiting — all configured with sensible defaults.
gRPC and Streaming
gRPC support continues to improve. The headline changes in .NET 10:
- Native AOT compatibility for gRPC clients and servers
- Performance improvements — gRPC-over-HTTP/3 is stable
- Better streaming ergonomics with
IAsyncEnumerablethroughout
For microservices that need low-latency internal communication, gRPC with HTTP/3 and AOT is genuinely fast — we're seeing sub-millisecond overhead per call in ours.
Observability: OpenTelemetry Throughout
.NET 10 leans fully into OpenTelemetry as the observability standard. The setup is now minimal:
builder.Services.AddOpenTelemetry()
.WithTracing(t => t
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddEntityFrameworkCoreInstrumentation()
.AddOtlpExporter())
.WithMetrics(m => m
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddRuntimeInstrumentation()
.AddOtlpExporter());
Combined with Aspire's dashboard for local dev and Azure Monitor / Application Insights for production, you get distributed tracing across your entire service graph without writing correlation ID middleware or custom activity sources.
Patterns We Use in Production
Beyond the features, here's what's actually working for us on real projects:
1. Aspire AppHost as the source of truth
We define the full service topology in Aspire — including databases, caches, and external dependencies. The same AppHost drives local dev, CI integration tests, and deployment manifests. No drift between environments.
2. Vertical slice architecture with minimal APIs
Instead of splitting controllers, services, and repositories across folders, we organize by feature. Each slice contains its endpoint, handler, validation, and data access. Easier to navigate, easier to delete.
3. Contract-first with shared DTOs in NuGet packages
For internal service-to-service calls, we publish contract packages to a private feed. Consumers reference the package and get compile-time safety. For external APIs, we use OpenAPI + generated clients.
4. Native AOT everywhere it's compatible
We default to Native AOT for new services. When a library doesn't support it, we evaluate whether we actually need that library. Usually we don't.
5. OpenTelemetry from day one
Every service emits traces, metrics, and logs through OTLP. The pipeline goes to Azure Monitor in production and to the Aspire dashboard locally. When something breaks in production, we already have the instrumentation we need.
What to Watch Out For
.NET 10 isn't without rough edges for microservices:
- Aspire deployment tooling is still evolving. Azure Container Apps integration is solid, but Kubernetes manifests require
aspirateor manual work. - Native AOT compile times can be 3-5x longer than regular builds. Fine for production, annoying for CI on large solutions.
- Some NuGet packages lag on AOT support. Check trim warnings early.
None of these are blockers, but they affect planning.
Conclusion
If you're building microservices in the Microsoft ecosystem, .NET 10 is a genuine leap forward. Aspire handles the orchestration story that was always awkward, Native AOT makes containers fast and small, and the observability story finally works without glue code.
We're using it in production across client projects and would recommend it for any new microservices work. The learning curve is modest if you're already on .NET 8 or 9, and the operational improvements compound quickly.
If you're still on .NET 6 or earlier and considering a jump — skip 7, 8, 9 and go straight to 10. The gap is worth it.