Introduction
.NET Aspire started with a strong focus on distributed .NET applications, but modern systems rarely use only one programming language.
A production application may contain a .NET API, a Java service, a Rust worker, a Node.js frontend, and several infrastructure dependencies. The challenge is not necessarily how to run each technology independently. The harder problem is coordinating them as one distributed application during development.
Aspire 13.6 expands the hosting model with support for Java and Rust applications, allowing developers to describe these services alongside .NET projects and infrastructure resources.
This makes the AppHost more useful as an application composition layer rather than a .NET-only launcher.
What Is the Aspire Hosting Model?
The Aspire hosting model allows developers to describe the resources that make up a distributed application.
A simplified application might contain:
AppHost
|
+---- .NET API
|
+---- Java Service
|
+---- Rust Worker
|
+---- PostgreSQL
|
+---- Redis
Instead of starting every component manually, the AppHost defines how the resources relate to each other.
The Aspire tooling can then provide a unified development experience around those resources.
Why Multi-Language Hosting Matters
Real-world systems often evolve over time.
A company might already have a Java service that handles payments while newer APIs are being developed with .NET.
Another team might use Rust for a high-performance worker.
Without a common application model, developers may need separate startup scripts:
Terminal 1 -> .NET API
Terminal 2 -> Java Service
Terminal 3 -> Rust Worker
Terminal 4 -> PostgreSQL
Terminal 5 -> Redis
That approach works for small systems but becomes increasingly difficult to manage.
A composed Aspire application can instead describe the system centrally:
Aspire AppHost
|
+--------------+--------------+
| | |
.NET Java Rust
| | |
+--------------+--------------+
|
Infrastructure
PostgreSQL / Redis
The programming language becomes an implementation detail of an individual resource rather than a limitation of the application model.
Java and Rust as Application Resources
The important architectural change is that Java and Rust applications can participate in the same resource graph as .NET projects.
Conceptually:
Application
|
+---- api
+---- payment-service
+---- worker
|
+---- postgres
+---- cache
The resources can have different implementations:
api -> .NET
payment-service -> Java
worker -> Rust
postgres -> PostgreSQL
cache -> Redis
The AppHost becomes the place where these components are composed.
Why Use Aspire Instead of Separate Startup Scripts?
A distributed application has more than startup commands.
Developers also need to understand:
Which services are running
Which endpoints are available
Which dependencies are required
Which environment variables are configured
Which service depends on another
Which component is unhealthy
Where logs are located
A centralized application model can make these relationships easier to see.
For example:
Java Payment Service
|
+---- PostgreSQL
|
+---- Redis
.NET API
|
+---- Java Payment Service
Rust Worker
|
+---- PostgreSQL
The dependency graph becomes part of the development environment.
Hosting a Java Application
A Java application can be represented as an application resource in the Aspire AppHost.
The exact configuration depends on the application's build and runtime model.
For example, a Java service may be built using Maven or Gradle:
payment-service/
pom.xml
src/
or:
payment-service/
build.gradle
src/
The AppHost can then coordinate the Java service alongside the other application resources.
The Java application does not need to be rewritten in .NET.
That distinction is important.
Aspire provides application composition and orchestration capabilities. It does not convert one programming language into another.
Hosting a Rust Application
Rust is useful for services where developers need predictable resource usage, native performance, or low-level control.
A Rust project might look like:
worker/
Cargo.toml
src/
main.rs
The worker can participate in the same distributed application:
Aspire AppHost
|
+---- .NET API
|
+---- Java Service
|
+---- Rust Worker
The Rust process remains a Rust application.
Aspire provides the surrounding development composition rather than changing the implementation language.
Connecting Services Together
A multi-language application becomes useful when services can communicate through well-defined contracts.
For example:
.NET API
|
| HTTP
v
Java Payment Service
|
| PostgreSQL
v
Database
A Rust worker might consume messages separately:
.NET API
|
v
Message Broker
|
v
Rust Worker
The application model can describe these relationships.
This is more useful than simply starting three processes because the development environment understands that they form one application.
Service Discovery and Configuration
Distributed applications should avoid hardcoding local addresses whenever possible.
For example, avoid building development configuration around:
http://localhost:5007
http://localhost:8081
http://localhost:9000
These ports can change.
A composed application can instead provide service references and connection information through the application model.
The conceptual flow is:
AppHost
|
+---- Service A
|
+---- Service B
|
v
Resolved Endpoint
This reduces the amount of manual configuration developers need to maintain.
Environment Variables Across Languages
Different programming languages consume configuration differently.
A .NET application might use:
ConnectionStrings__Database
A Java application might read:
DATABASE_URL
A Rust application might use:
DATABASE_URL
RUST_LOG
The application composition layer can provide the required environment configuration while each service remains responsible for interpreting it according to its own runtime conventions.
This is an important separation:
Aspire
|
+---- Provides configuration
|
+---- Provides resource relationships
|
v
Application
|
+---- Interprets configuration
Database Dependencies
Suppose the application contains a PostgreSQL resource:
PostgreSQL
|
+---- .NET API
+---- Java Service
+---- Rust Worker
Each application may use the database differently.
The .NET service may use Entity Framework Core.
The Java service may use JDBC or another PostgreSQL driver.
The Rust service may use a PostgreSQL-compatible Rust library.
The infrastructure remains the same even though the application stacks differ.
This is one of the strongest reasons to think of Aspire as an application composition model rather than simply a .NET launcher.
Example Application Architecture
Consider an order-processing platform:
AppHost
|
+--------------------+--------------------+
| | |
v v v
.NET API Java Payment Service Rust Worker
| | |
| v |
| PostgreSQL |
| |
+-------------- Message Broker <----------+
The responsibilities might be:
.NET API
Handles:
HTTP requests
Authentication
Order creation
API responses
Java Payment Service
Handles:
Payment processing
Payment provider integration
Payment status
Rust Worker
Handles:
Background processing
Queue consumption
CPU-intensive operations
PostgreSQL
Stores:
Orders
Customers
Payment records
The languages are selected according to service requirements, while Aspire provides a common development composition model.
Why This Is Useful for Polyglot Teams
Organizations frequently have multiple engineering teams with different technology stacks.
A platform team may standardize:
Application startup
Observability
Local development
Dependencies
Configuration
while allowing individual teams to select:
.NET
Java
Rust
Node.js
Python
for their services.
This can reduce the need for every team to maintain its own collection of startup scripts and local infrastructure instructions.
Observability Across Languages
A multi-language application also needs consistent observability.
A useful distributed development environment should let developers understand:
Request
|
v
.NET API
|
v
Java Service
|
v
PostgreSQL
or:
Request
|
v
.NET API
|
v
Queue
|
v
Rust Worker
Logs, traces, and metrics become especially valuable when the request crosses language boundaries.
The application model should therefore be paired with consistent telemetry conventions.
Debugging Polyglot Applications
Debugging a multi-language application is different from debugging a single .NET project.
For example:
.NET API
|
| HTTP 500
v
Java Service
The error may originate in Java even though the user-facing failure appears in the .NET API.
A useful investigation flow is:
User Request
|
v
.NET API Logs
|
v
Distributed Trace
|
v
Java Service Logs
|
v
Database
The Aspire dashboard can provide the system-level visibility while language-specific debuggers handle code-level investigation.
Containers and Polyglot Services
Some services may run as local processes while others run in containers.
For example:
.NET API Local
Java Service Container
Rust Worker Local
PostgreSQL Container
Redis Container
This mixed model can be useful during development.
Developers may want rapid debugging for their active service while keeping infrastructure dependencies containerized.
The application composition layer provides a common way to think about these resources.
Common Mistakes
Assuming Every Service Needs to Be .NET
The purpose of multi-language hosting is to compose different technologies, not force them into one runtime.
Hardcoding Local Ports
Service endpoints should be managed through the application's resource configuration rather than scattered throughout source code.
Sharing Database Credentials Manually
Avoid maintaining separate copies of credentials in multiple local configuration files.
Ignoring Language-Specific Conventions
Java, Rust, and .NET have different logging, configuration, build, and debugging ecosystems.
A common application model does not eliminate those differences.
Treating Local Aspire as Production Infrastructure
The development environment helps teams build and debug distributed applications. Production deployment may require Kubernetes, managed container platforms, or other infrastructure.
Forgetting Service Contracts
Polyglot applications depend on stable interfaces.
HTTP APIs, event schemas, and message contracts should be versioned and documented.
Troubleshooting
Java Service Does Not Start
Check:
Java runtime availability
Maven or Gradle configuration
Build output
Environment variables
Port configuration
Application logs
Rust Service Fails to Build
Check:
Rust toolchain
Cargo configuration
Dependency resolution
Environment variables
Native system dependencies
.NET Cannot Reach Java
Check:
Service endpoint
Port configuration
Network connectivity
Java application status
HTTP protocol configuration
Application logs
Rust Worker Cannot Reach PostgreSQL
Check:
Database resource status
Connection configuration
Credentials
Database availability
Network configuration
Rust database client configuration
The Application Starts but Requests Fail
Inspect the complete dependency chain instead of debugging only the first service that reports an error.
Best Practices
Treat the AppHost as the composition layer for the distributed application.
Give every resource a clear name.
Avoid hardcoded service addresses.
Keep service contracts explicit and versioned.
Use consistent configuration patterns across languages.
Standardize logging and telemetry.
Keep secrets out of source control.
Separate application code from infrastructure configuration.
Use representative local dependencies.
Test cross-service communication during development.
Keep language-specific tooling available for individual services.
Document which team owns each resource.
Keep development orchestration separate from production deployment decisions.
Monitor resource health before debugging application code.
Advantages and Disadvantages
Advantages
Supports polyglot distributed applications
Provides a common application composition model
Reduces manual startup coordination
Makes service relationships easier to understand
Allows different teams to use different languages
Can simplify local development of complex systems
Provides a common place to describe infrastructure dependencies
Disadvantages
Teams still need expertise in each programming language
Polyglot systems increase operational complexity
Service contracts require careful governance
Debugging across language boundaries can be more involved
Development orchestration does not automatically solve production deployment
Different runtimes have different configuration and tooling requirements
When Should You Use a Polyglot Aspire Application?
A multi-language Aspire application makes sense when a system already contains services implemented in different languages or when teams have a deliberate reason to use different technology stacks.
Examples include:
.NET API with Java enterprise services
.NET applications with Rust workers
Existing Java services being integrated with new .NET components
Polyglot microservice architectures
Development environments containing multiple runtime technologies
Teams that want a common local composition model
It is less useful for a small application that consists of one service and a database.
Summary
Aspire 13.6's expanded hosting capabilities make the application model more useful for polyglot distributed systems.
Java and Rust services can participate alongside .NET applications and infrastructure resources, allowing teams to describe a distributed application as a connected system rather than a collection of unrelated processes.
The main benefit is not that Aspire replaces Java or Rust tooling. It provides a common composition layer around those technologies.
For teams working with mixed-language architectures, this can simplify local startup, dependency configuration, service discovery, observability, and debugging. The individual services still require language-specific development practices, but the overall application becomes easier to understand and operate during development.

Join the conversation! Your thoughts help the community grow.