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:

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:

Java Payment Service

Handles:

Rust Worker

Handles:

PostgreSQL

Stores:

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:

  1. Java runtime availability

  2. Maven or Gradle configuration

  3. Build output

  4. Environment variables

  5. Port configuration

  6. Application logs

Rust Service Fails to Build

Check:

  1. Rust toolchain

  2. Cargo configuration

  3. Dependency resolution

  4. Environment variables

  5. Native system dependencies

.NET Cannot Reach Java

Check:

Rust Worker Cannot Reach PostgreSQL

Check:

The Application Starts but Requests Fail

Inspect the complete dependency chain instead of debugging only the first service that reports an error.

Best Practices

  1. Treat the AppHost as the composition layer for the distributed application.

  2. Give every resource a clear name.

  3. Avoid hardcoded service addresses.

  4. Keep service contracts explicit and versioned.

  5. Use consistent configuration patterns across languages.

  6. Standardize logging and telemetry.

  7. Keep secrets out of source control.

  8. Separate application code from infrastructure configuration.

  9. Use representative local dependencies.

  10. Test cross-service communication during development.

  11. Keep language-specific tooling available for individual services.

  12. Document which team owns each resource.

  13. Keep development orchestration separate from production deployment decisions.

  14. Monitor resource health before debugging application code.

Advantages and Disadvantages

Advantages

Disadvantages

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:

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.