Set up Java apps in the AppHost

Este conteúdo não está disponível em sua língua ainda.

Java logo

This article is the reference for the Aspire Java hosting integration. It enumerates the AppHost APIs — with examples for both apphost.mts and AppHost.cs — that you use to model Java apps in your AppHost: Spring Boot and Quarkus apps with framework-aware defaults, apps that launch through a Maven goal or a Gradle task, prebuilt JARs, and container images that were built elsewhere. It also describes how Aspire builds those apps, passes them JVM options, attaches the OpenTelemetry Java agent, configures certificate trust, debugs them, and packages them into container images when you publish.

If you’re new to the Java integration, start with Get started with the Java integration. For how other resources call your Java apps — and how your Java apps read the configuration that Aspire injects — see Connect to Java apps. If you’re moving from the Community Toolkit package, see Migrate from the Community Toolkit.

To start modeling Java apps in your AppHost, install the 📦 Aspire.Hosting.Java NuGet package:

Terminal
aspire add java

Learn more about aspire add in the command reference.

This updates your aspire.config.json with the Java hosting integration package:

aspire.config.json
{
"packages": {
"Aspire.Hosting.Java": "13.6.0-preview.1.26479.8"
}
}

Use AddSpringBootAppaddSpringBootApp to add a Spring Boot app whose Maven or Gradle build file is in the app directory. The app directory is resolved relative to the AppHost directory. It’s the app’s working directory and, when you publish, the build context of its container image.

apphost.mts
import {
function createBuilder(): IDistributedApplicationBuilder

Creates a new distributed application builder

createBuilder
} from './.aspire/modules/aspire.mjs';
const
const builder: IDistributedApplicationBuilder
builder
= await
function createBuilder(): IDistributedApplicationBuilder

Creates a new distributed application builder

createBuilder
();
const
const catalog: JavaAppResource
catalog
= await
const builder: IDistributedApplicationBuilder
builder
.
IDistributedApplicationBuilder.addSpringBootApp(name: string, appDirectory: string): JavaAppResource

Adds a Spring Boot application to the application model, built and launched with its own Maven or Gradle wrapper.

addSpringBootApp
('catalog', '../catalog');
await
const catalog: JavaAppResource
catalog
.
ExecutableResource.withHttpHealthCheck(options?: {
path?: string;
statusCode?: number;
endpointName?: string;
} | undefined): JavaAppResource (+1 overload)

Adds a health check to the resource which is mapped to a specific endpoint.

withHttpHealthCheck
({
path?: string | undefined
path
: '/actuator/health' });
const
const orders: JavaAppResource
orders
= await
const builder: IDistributedApplicationBuilder
builder
.
IDistributedApplicationBuilder.addSpringBootApp(name: string, appDirectory: string): JavaAppResource

Adds a Spring Boot application to the application model, built and launched with its own Maven or Gradle wrapper.

addSpringBootApp
('orders', '../orders');
await
const orders: JavaAppResource
orders
.
ExecutableResource.withReference(source: IResource | EndpointReference | string | uri, options?: {
connectionName?: string;
optional?: boolean;
name?: string;
} | undefined): JavaAppResource (+1 overload)

Adds a reference to another resource

withReference
(
const catalog: JavaAppResource
catalog
);
await
const orders: JavaAppResource
orders
.
ExecutableResource.waitFor(dependency: IResource | IResourceWithConnectionString, waitBehavior?: WaitBehavior): JavaAppResource

Waits for another resource to be ready

waitFor
(
const catalog: JavaAppResource
catalog
);
await
const builder: IDistributedApplicationBuilder
builder
.
IDistributedApplicationBuilder.build(): DistributedApplication

Builds the distributed application

build
().
DistributedApplication.run(cancellationToken?: cancellationToken): void

Runs the distributed application

run
();

In the preceding example, WithHttpHealthCheckwithHttpHealthCheck makes Aspire poll the catalog app’s /actuator/health endpoint. The orders app uses WithReferencewithReference to receive the catalog app’s URL, and WaitForwaitFor to start only after catalog is healthy.

When a Spring Boot app starts, Aspire:

  • Detects the build tool from the app directory: pom.xml selects Maven, and build.gradle, build.gradle.kts, settings.gradle, or settings.gradle.kts selects Gradle. If the directory contains neither — or both — the app fails to start with an error. For apps that are laid out differently, see Add a Java app.
  • Launches the app through the project’s wrapper with the Spring Boot plugin’s run goal: spring-boot:run for Maven, or bootRun for Gradle.
  • Adds an HTTP endpoint named http and passes its port to the app in the SERVER_PORT environment variable, which Spring Boot uses as its server.port setting. No fixed port is set, so several Spring Boot apps can run side by side.

The helper doesn’t add a health check, because /actuator/health responds only when the app depends on spring-boot-starter-actuator. A health check against a missing endpoint would leave the app permanently unhealthy and block every resource that waits for it.

When you publish, the app is packaged inside its container image with -B -ntp -DskipTests package for Maven or build -x test for Gradle. To change those arguments, see Build before running.

Use AddQuarkusAppaddQuarkusApp to add a Quarkus app. The build tool is detected the same way as for Spring Boot apps. The app runs in Quarkus dev mode — quarkus:dev for Maven or quarkusDev for Gradle — so live coding keeps working while the AppHost runs, and its HTTP endpoint’s port reaches the app in the QUARKUS_HTTP_PORT environment variable.

apphost.mts
import {
function createBuilder(): IDistributedApplicationBuilder

Creates a new distributed application builder

createBuilder
} from './.aspire/modules/aspire.mjs';
const
const builder: IDistributedApplicationBuilder
builder
= await
function createBuilder(): IDistributedApplicationBuilder

Creates a new distributed application builder

createBuilder
();
const
const pricing: JavaAppResource
pricing
= await
const builder: IDistributedApplicationBuilder
builder
.
IDistributedApplicationBuilder.addQuarkusApp(name: string, appDirectory: string): JavaAppResource

Adds a Quarkus application to the application model, built and launched with its own Maven or Gradle wrapper.

addQuarkusApp
('pricing', '../pricing');
await
const pricing: JavaAppResource
pricing
.
ExecutableResource.withHttpHealthCheck(options?: {
path?: string;
statusCode?: number;
endpointName?: string;
} | undefined): JavaAppResource (+1 overload)

Adds a health check to the resource which is mapped to a specific endpoint.

withHttpHealthCheck
({
path?: string | undefined
path
: '/q/health' });
const
const inventory: JavaAppResource
inventory
= await
const builder: IDistributedApplicationBuilder
builder
.
IDistributedApplicationBuilder.addQuarkusApp(name: string, appDirectory: string): JavaAppResource

Adds a Quarkus application to the application model, built and launched with its own Maven or Gradle wrapper.

addQuarkusApp
('inventory', '../inventory');
await
const inventory: JavaAppResource
inventory
.
ExecutableResource.withReference(source: IResource | EndpointReference | string | uri, options?: {
connectionName?: string;
optional?: boolean;
name?: string;
} | undefined): JavaAppResource (+1 overload)

Adds a reference to another resource

withReference
(
const pricing: JavaAppResource
pricing
);
await
const inventory: JavaAppResource
inventory
.
ExecutableResource.waitFor(dependency: IResource | IResourceWithConnectionString, waitBehavior?: WaitBehavior): JavaAppResource

Waits for another resource to be ready

waitFor
(
const pricing: JavaAppResource
pricing
);
await
const builder: IDistributedApplicationBuilder
builder
.
IDistributedApplicationBuilder.build(): DistributedApplication

Builds the distributed application

build
().
DistributedApplication.run(cancellationToken?: cancellationToken): void

Runs the distributed application

run
();

When you run the AppHost, the helper also sets the following environment variables on the app:

Environment variableValuePurpose
QUARKUS_PROFILEdevSelects the dev profile, including when a debugger launches the packaged app instead of dev mode.
QUARKUS_HTTP_HOST0.0.0.0Binds all interfaces. When Quarkus binds a localhost name, its Host header validation rejects requests to the endpoint URL that the Aspire dashboard shows with 400 Bad Request.
QUARKUS_OBSERVABILITY_ENABLEDfalseTurns off the observability Dev Service, which would otherwise pull and start a grafana/otel-lgtm container and send the app’s telemetry there instead of to the Aspire dashboard.

The Quarkus OpenTelemetry extension reads its own quarkus.otel.* configuration rather than the standard OTEL_* environment variables that Aspire sets. So that the app’s telemetry reaches the dashboard, the helper copies Aspire’s OpenTelemetry settings to the matching QUARKUS_OTEL_* variables, such as QUARKUS_OTEL_EXPORTER_OTLP_ENDPOINT and QUARKUS_OTEL_SERVICE_NAME. A Quarkus app that uses the extension doesn’t need the OpenTelemetry Java agent, so don’t attach both.

The helper copies these settings only when you run the AppHost. A published app receives the standard OTEL_* variables from its deployment environment, so map them in the app’s application.properties:

application.properties
quarkus.otel.exporter.otlp.endpoint=${OTEL_EXPORTER_OTLP_ENDPOINT:http://localhost:4317}
quarkus.otel.exporter.otlp.protocol=${OTEL_EXPORTER_OTLP_PROTOCOL:grpc}
quarkus.otel.service.name=${OTEL_SERVICE_NAME:inventory}

Quarkus expands these expressions when the app starts. The endpoint and protocol defaults match the extension’s own, and the service name falls back to the app’s name, so the app also runs outside Aspire. Keep the expressions in application.properties rather than passing them as environment variables, because Docker Compose interprets ${...} in environment variables itself and rejects this syntax.

Other Quarkus Dev Services stay enabled. A Dev Service starts its own container only when the configuration it provides is missing. When your app references an Aspire resource, such as a database, map the connection information that Aspire injects to the matching Quarkus property — for example, quarkus.datasource.jdbc.url — and Quarkus uses the Aspire resource instead. For examples, see Connect to Java apps.

As with Spring Boot, no health check is added, because /q/health responds only when the app depends on the quarkus-smallrye-health extension. When you publish, Quarkus apps are packaged with the same default arguments as Spring Boot apps.

Use AddJavaApp(string name, string appDirectory)addJavaApp(name: string, appDirectory: string) for any other Java app — one that’s built on another framework, one that needs a different launch goal, or one whose build files aren’t in the app directory. Configure exactly one of the following launch modes:

Launch modeConfigure it withWhat Aspire runs
Maven goalWithMavenGoalwithMavenGoalThe Maven wrapper with the goal and its arguments
Gradle taskWithGradleTaskwithGradleTaskThe Gradle wrapper with the task and its arguments
Prebuilt JARAddJavaApp(string name, string appDirectory, string jarPath, params string[] args)addJavaAppWithJar(name: string, appDirectory: string, jarPath: string, args: string[]) with a JAR pathjava -jar with the JAR path and the app’s arguments

An app without a launch mode fails to start. Mixing launch modes — a JAR with a Maven goal or Gradle task, or Maven with Gradle — throws an exception when you configure it. Calling the same method again replaces the earlier goal or task.

apphost.mts
import {
function createBuilder(): IDistributedApplicationBuilder

Creates a new distributed application builder

createBuilder
} from './.aspire/modules/aspire.mjs';
const
const builder: IDistributedApplicationBuilder
builder
= await
function createBuilder(): IDistributedApplicationBuilder

Creates a new distributed application builder

createBuilder
();
const
const catalog: JavaAppResource
catalog
= await
const builder: IDistributedApplicationBuilder
builder
.
IDistributedApplicationBuilder.addJavaApp(name: string, appDirectory: string): JavaAppResource

Adds a Java application to the application model, launched with java.

addJavaApp
('catalog', '../catalog');
await
const catalog: JavaAppResource
catalog
.
JavaAppResource.withMavenGoal(goal: string, args: string[]): JavaAppResource

Launches the Java application through a Maven goal instead of java, for example spring-boot:run.

withMavenGoal
('spring-boot:run', ['-Dspring-boot.run.profiles=dev']);
await
const catalog: JavaAppResource
catalog
.
ExecutableResource.withHttpEndpoint(options?: {
port?: number;
targetPort?: number;
name?: string;
env?: string;
isProxied?: boolean;
} | undefined): JavaAppResource (+1 overload)

Adds an HTTP endpoint

withHttpEndpoint
({
env?: string | undefined
env
: 'SERVER_PORT' });
const
const orders: JavaAppResource
orders
= await
const builder: IDistributedApplicationBuilder
builder
.
IDistributedApplicationBuilder.addJavaApp(name: string, appDirectory: string): JavaAppResource

Adds a Java application to the application model, launched with java.

addJavaApp
('orders', '../orders');
await
const orders: JavaAppResource
orders
.
JavaAppResource.withGradleTask(task: string, args: string[]): JavaAppResource

Launches the Java application through a Gradle task instead of java, for example bootRun.

withGradleTask
('bootRun', ['--args=--spring.profiles.active=dev']);
await
const orders: JavaAppResource
orders
.
ExecutableResource.withHttpEndpoint(options?: {
port?: number;
targetPort?: number;
name?: string;
env?: string;
isProxied?: boolean;
} | undefined): JavaAppResource (+1 overload)

Adds an HTTP endpoint

withHttpEndpoint
({
env?: string | undefined
env
: 'SERVER_PORT' });
await
const builder: IDistributedApplicationBuilder
builder
.
IDistributedApplicationBuilder.build(): DistributedApplication

Builds the distributed application

build
().
DistributedApplication.run(cancellationToken?: cancellationToken): void

Runs the distributed application

run
();

Arguments that you pass with the goal or task go to the build tool. In TypeScript, the arguments array is required, so pass [] when there are none. Arguments that you add with WithArgswithArgs are also placed after the goal or task, so in these launch modes they reach the build tool rather than your app. To pass arguments to the app itself, use the build plugin’s option for it, such as -Dspring-boot.run.arguments=... for the Spring Boot Maven plugin or --args=... for the Spring Boot Gradle plugin.

Unlike the framework helpers, AddJavaApp doesn’t declare an endpoint. Add one with WithHttpEndpointwithHttpEndpoint, and use its env parameter to pass the port that Aspire allocates in the environment variable that your app reads, as the preceding example does with SERVER_PORT. Leave the target port unset: Java apps run as processes on your machine, so two apps with the same fixed port would collide.

Pass a JAR path to the JAR overload of AddJavaApp to run the app with java -jar. The JAR path is relative to the app directory, and the remaining arguments are the app’s own arguments, which Aspire passes after the JAR path. In TypeScript, this overload is named addJavaAppWithJar, and its arguments array is required.

apphost.mts
import {
function createBuilder(): IDistributedApplicationBuilder

Creates a new distributed application builder

createBuilder
} from './.aspire/modules/aspire.mjs';
const
const builder: IDistributedApplicationBuilder
builder
= await
function createBuilder(): IDistributedApplicationBuilder

Creates a new distributed application builder

createBuilder
();
await
const builder: IDistributedApplicationBuilder
builder
.
IDistributedApplicationBuilder.addJavaAppWithJar(name: string, appDirectory: string, jarPath: string, args: string[]): JavaAppResource

Adds a Java application that runs a prebuilt JAR with java -jar.

addJavaAppWithJar
(
'worker',
'../worker',
'target/worker.jar',
['--interval-seconds', '10']
);
await
const builder: IDistributedApplicationBuilder
builder
.
IDistributedApplicationBuilder.build(): DistributedApplication

Builds the distributed application

build
().
DistributedApplication.run(cancellationToken?: cancellationToken): void

Runs the distributed application

run
();

The JAR must exist before the app starts. When the project’s own build produces it, have Aspire run that build first, as described in Build before running. When you publish, the JAR path selects which of the build’s JARs the image runs. For more information, see Select the published JAR.

Use WithMavenBuildwithMavenBuild or WithGradleBuildwithGradleBuild to build an app with its wrapper before it starts — typically an app that runs a JAR its own build produces. When you run the AppHost, Aspire adds a child resource named <name>-maven-build or <name>-gradle-build that runs the build, and the app waits for that resource to finish successfully. When you publish, the same arguments build the app inside its generated container image, so they should produce the deployable JAR.

apphost.mts
import {
function createBuilder(): IDistributedApplicationBuilder

Creates a new distributed application builder

createBuilder
} from './.aspire/modules/aspire.mjs';
const
const builder: IDistributedApplicationBuilder
builder
= await
function createBuilder(): IDistributedApplicationBuilder

Creates a new distributed application builder

createBuilder
();
const
const worker: JavaAppResource
worker
= await
const builder: IDistributedApplicationBuilder
builder
.
IDistributedApplicationBuilder.addJavaAppWithJar(name: string, appDirectory: string, jarPath: string, args: string[]): JavaAppResource

Adds a Java application that runs a prebuilt JAR with java -jar.

addJavaAppWithJar
('worker', '../worker', 'target/worker.jar', []);
await
const worker: JavaAppResource
worker
.
JavaAppResource.withMavenBuild(args: string[]): JavaAppResource

Runs a Maven build before the Java application starts.

withMavenBuild
(['-B', '-ntp', '-DskipTests', 'package']);
const
const reports: JavaAppResource
reports
= await
const builder: IDistributedApplicationBuilder
builder
.
IDistributedApplicationBuilder.addJavaAppWithJar(name: string, appDirectory: string, jarPath: string, args: string[]): JavaAppResource

Adds a Java application that runs a prebuilt JAR with java -jar.

addJavaAppWithJar
('reports', '../reports', 'build/libs/reports.jar', []);
await
const reports: JavaAppResource
reports
.
JavaAppResource.withGradleBuild(args: string[]): JavaAppResource

Runs a Gradle build before the Java application starts.

withGradleBuild
(['build', '-x', 'test']);
await
const builder: IDistributedApplicationBuilder
builder
.
IDistributedApplicationBuilder.build(): DistributedApplication

Builds the distributed application

build
().
DistributedApplication.run(cancellationToken?: cancellationToken): void

Runs the distributed application

run
();

By default, Maven builds with clean package and Gradle builds with clean build. Arguments that you pass replace the defaults, so include the packaging goal or task. An app is built with one tool only: combining the Maven and Gradle methods on the same app throws an exception.

A Maven goal or Gradle task compiles the app as it launches. For apps that use one — including apps added with AddSpringBootApp or AddQuarkusApp — Aspire keeps the build arguments for publishing without adding a build resource. Two cases still build first: an app whose OpenTelemetry agent the build produces, and a Quarkus app that VS Code launches for debugging. On a Spring Boot or Quarkus app, calling WithMavenBuild or WithGradleBuild replaces the default packaging arguments and keeps the framework’s launch goal.

Aspire builds and launches apps only through the project’s wrapper — mvnw or gradlew, or mvnw.cmd or gradlew.bat on Windows — so that the AppHost, your CI, and your published images all use the tool version that the repository pins. It doesn’t fall back to a globally installed mvn or gradle.

Aspire looks for the wrapper in the app directory first. If the wrapper isn’t there, Aspire checks each parent directory up to the repository root, and uses a wrapper that sits next to the root build file of a multi-module build: a Maven pom.xml, or a Gradle settings.gradle or settings.gradle.kts. If there’s no wrapper, the app fails to start with an error that suggests generating one with mvn -N wrapper:wrapper or gradle wrapper.

To use a wrapper in another location or with another name, call WithWrapperPathwithWrapperPath. The path is absolute, or relative to the app’s working directory — the app directory, unless you change it. You can call the method before or after the one that selects the build tool.

apphost.mts
import {
function createBuilder(): IDistributedApplicationBuilder

Creates a new distributed application builder

createBuilder
} from './.aspire/modules/aspire.mjs';
const
const builder: IDistributedApplicationBuilder
builder
= await
function createBuilder(): IDistributedApplicationBuilder

Creates a new distributed application builder

createBuilder
();
const
const catalog: JavaAppResource
catalog
= await
const builder: IDistributedApplicationBuilder
builder
.
IDistributedApplicationBuilder.addJavaApp(name: string, appDirectory: string): JavaAppResource

Adds a Java application to the application model, launched with java.

addJavaApp
('catalog', '../catalog');
await
const catalog: JavaAppResource
catalog
.
JavaAppResource.withWrapperPath(wrapperPath: string): JavaAppResource

Overrides the build tool wrapper script path, for repositories whose wrapper is not in the default location or does not use the default name.

withWrapperPath
('tools/mvnw');
await
const catalog: JavaAppResource
catalog
.
JavaAppResource.withMavenGoal(goal: string, args: string[]): JavaAppResource

Launches the Java application through a Maven goal instead of java, for example spring-boot:run.

withMavenGoal
('spring-boot:run', []);
await
const builder: IDistributedApplicationBuilder
builder
.
IDistributedApplicationBuilder.build(): DistributedApplication

Builds the distributed application

build
().
DistributedApplication.run(cancellationToken?: cancellationToken): void

Runs the distributed application

run
();

When Aspire generates the Dockerfile to publish the app, the wrapper must be inside the app directory, because that directory is the container build context. This applies even when a local run finds the wrapper in a parent directory. For more information, see Publish Java apps.

Use WithJvmArgswithJvmArgs to pass options to the JVM, such as heap sizes or system properties. Aspire appends them to the JAVA_TOOL_OPTIONS environment variable, and quotes values that contain spaces. Every JVM reads that variable when it starts — including the build tool’s own JVM and the app JVM it starts in a Maven goal or Gradle task — so the options apply in every launch mode, and to Java containers as well.

apphost.mts
import {
function createBuilder(): IDistributedApplicationBuilder

Creates a new distributed application builder

createBuilder
} from './.aspire/modules/aspire.mjs';
const
const builder: IDistributedApplicationBuilder
builder
= await
function createBuilder(): IDistributedApplicationBuilder

Creates a new distributed application builder

createBuilder
();
const
const catalog: JavaAppResource
catalog
= await
const builder: IDistributedApplicationBuilder
builder
.
IDistributedApplicationBuilder.addSpringBootApp(name: string, appDirectory: string): JavaAppResource

Adds a Spring Boot application to the application model, built and launched with its own Maven or Gradle wrapper.

addSpringBootApp
('catalog', '../catalog');
await
const catalog: JavaAppResource
catalog
.
JavaAppResource.withJvmArgs(args: string[]): JavaAppResource

Adds arguments to the Java Virtual Machine that runs the application.

withJvmArgs
(['-Xms256m', '-Xmx512m', '-Dfile.encoding=UTF-8']);
await
const builder: IDistributedApplicationBuilder
builder
.
IDistributedApplicationBuilder.build(): DistributedApplication

Builds the distributed application

build
().
DistributedApplication.run(cancellationToken?: cancellationToken): void

Runs the distributed application

run
();

Aspire also uses JAVA_TOOL_OPTIONS for the OpenTelemetry Java agent and the development certificate trust store, and adds your options alongside those. When a JVM reads the variable, it prints a Picked up JAVA_TOOL_OPTIONS: line to standard error, so that line appears in the app’s console logs.

Aspire sets the standard OTEL_* environment variables on every Java resource, such as OTEL_EXPORTER_OTLP_ENDPOINT and OTEL_SERVICE_NAME, so that telemetry can reach the Aspire dashboard. A JVM doesn’t export telemetry by itself, though. To produce traces, metrics, and logs without changing your code, attach the OpenTelemetry Java agent, which instruments common libraries and frameworks automatically and reads those variables. Aspire doesn’t download the agent — your build provides it, or you point Aspire at a copy that already exists:

  • Without an argument, WithOtelAgent()withOtelAgentDefaultPath() loads the agent from where the build writes it by convention: target/agent/opentelemetry-javaagent.jar for Maven, or build/agent/opentelemetry-javaagent.jar for Gradle. Aspire needs to know the build tool, so use it on an app added with AddSpringBootApp, or on one that calls WithMavenBuild or WithGradleBuild.
  • With a path, WithOtelAgent(string agentPath)withOtelAgent(agentPath: string) loads the agent from that path. A relative path is resolved against the app directory and names a file that the build produces. An absolute path names a file that already exists, such as an agent installed on the machine, and doesn’t need a build.
apphost.mts
import {
function createBuilder(): IDistributedApplicationBuilder

Creates a new distributed application builder

createBuilder
} from './.aspire/modules/aspire.mjs';
const
const builder: IDistributedApplicationBuilder
builder
= await
function createBuilder(): IDistributedApplicationBuilder

Creates a new distributed application builder

createBuilder
();
const
const catalog: JavaAppResource
catalog
= await
const builder: IDistributedApplicationBuilder
builder
.
IDistributedApplicationBuilder.addSpringBootApp(name: string, appDirectory: string): JavaAppResource

Adds a Spring Boot application to the application model, built and launched with its own Maven or Gradle wrapper.

addSpringBootApp
('catalog', '../catalog');
await
const catalog: JavaAppResource
catalog
.
JavaAppResource.withOtelAgentDefaultPath(): JavaAppResource

Runs the application with the OpenTelemetry Java agent from the location the build tool writes it to.

withOtelAgentDefaultPath
();
await
const builder: IDistributedApplicationBuilder
builder
.
IDistributedApplicationBuilder.build(): DistributedApplication

Builds the distributed application

build
().
DistributedApplication.run(cancellationToken?: cancellationToken): void

Runs the distributed application

run
();

When the build produces the agent, Aspire runs that build as a <name>-maven-build or <name>-gradle-build resource before the app starts — even for a Maven goal or Gradle task that would otherwise compile the app itself. The agent has to exist first, because JAVA_TOOL_OPTIONS loads it into the build tool’s own JVM too. Calling the method again replaces the agent path.

The following build configuration copies the agent to the conventional location. Replace the version with the latest agent release:

Binding the copy to the initialize phase writes the agent at the start of every Maven build, including the build that Aspire runs before the app.

pom.xml
<properties>
<opentelemetry-javaagent.version>2.24.0</opentelemetry-javaagent.version>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy-opentelemetry-javaagent</id>
<phase>initialize</phase>
<goals>
<goal>copy</goal>
</goals>
<configuration>
<artifactItems>
<artifactItem>
<groupId>io.opentelemetry.javaagent</groupId>
<artifactId>opentelemetry-javaagent</artifactId>
<version>${opentelemetry-javaagent.version}</version>
<outputDirectory>${project.build.directory}/agent</outputDirectory>
<destFileName>opentelemetry-javaagent.jar</destFileName>
</artifactItem>
</artifactItems>
<overWriteIfNewer>true</overWriteIfNewer>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>

When you publish with a generated Dockerfile, Aspire copies a build-produced agent into the image and points the JVM at that copy. An absolute agent path is used unchanged, so the agent must already exist at that path inside the image. If you author your own Dockerfile, copy the agent in that Dockerfile and pass its absolute path inside the image, because Aspire copies a build-produced agent only into Dockerfiles that it generates.

Use AddJavaContaineraddJavaContainer when a Java app ships as a container image that’s built elsewhere, such as by a separate pipeline or another team. Aspire runs the image as-is and never rebuilds it, so the JAR, the JDK, and any agent all come from the image. The resource still receives the OpenTelemetry exporter settings and the development certificate trust store, and it supports WithJvmArgs.

apphost.mts
import {
function createBuilder(): IDistributedApplicationBuilder

Creates a new distributed application builder

createBuilder
} from './.aspire/modules/aspire.mjs';
const
const builder: IDistributedApplicationBuilder
builder
= await
function createBuilder(): IDistributedApplicationBuilder

Creates a new distributed application builder

createBuilder
();
const
const catalog: JavaContainerResource
catalog
= await
const builder: IDistributedApplicationBuilder
builder
.
IDistributedApplicationBuilder.addJavaContainer(name: string, image: string, options?: {
tag?: string;
}): JavaContainerResource (+1 overload)

Adds a Java application that runs from an existing container image.

addJavaContainer
('catalog', 'mycompany/catalog', {
tag?: string | undefined
tag
: '1.4.0' });
await
const catalog: JavaContainerResource
catalog
.
ContainerResource.withHttpEndpoint(options?: {
port?: number;
targetPort?: number;
name?: string;
env?: string;
isProxied?: boolean;
} | undefined): JavaContainerResource (+1 overload)

Adds an HTTP endpoint

withHttpEndpoint
({
targetPort?: number | undefined
targetPort
: 8080 });
await
const catalog: JavaContainerResource
catalog
.
JavaContainerResource.withJvmArgs(args: string[]): JavaContainerResource

Adds arguments to the Java Virtual Machine that runs the application.

withJvmArgs
(['-Xmx512m']);
await
const builder: IDistributedApplicationBuilder
builder
.
IDistributedApplicationBuilder.build(): DistributedApplication

Builds the distributed application

build
().
DistributedApplication.run(cancellationToken?: cancellationToken): void

Runs the distributed application

run
();

Aspire doesn’t add an endpoint to a Java container, because the port that the app listens on is a property of the image. Declare it with WithHttpEndpoint and a target port — 8080 is the default for both Spring Boot and Quarkus.

The OpenTelemetry agent methods aren’t available on a Java container, because there’s no build to take the agent from. If the image already includes the agent, enable it with a JVM option that points to its location inside the image, such as -javaagent:/app/opentelemetry-javaagent.jar.

Aspire configures each Java resource to trust the development certificate, so HTTPS calls to other local resources succeed without turning off certificate validation. It creates a PKCS12 trust store that contains the development certificate together with the system’s root certificates, and points the JVM at it by adding -Djavax.net.ssl.trustStore and -Djavax.net.ssl.trustStoreType=PKCS12 to JAVA_TOOL_OPTIONS.

A trust store that’s passed to the JVM replaces the JVM’s default trusted certificates rather than adding to them, so the default trust scope for Java resources is System. If you change a Java resource’s scope to Append with WithCertificateTrustScopewithCertificateTrustScope, Aspire can’t add the certificate without replacing the defaults, so it leaves the JVM’s trust store unchanged and logs a message.

For more information, see Certificate trust configuration.

To debug Java apps, use the standard debugging flow of the Aspire VS Code extension with the Language Support for Java and Debugger for Java extensions installed. Debugging is enabled for every app that you add with AddJavaApp, AddSpringBootApp, or AddQuarkusApp.

When the debugger launches a Java app, VS Code starts the app’s JVM directly instead of going through the wrapper, because goals such as spring-boot:run and bootRun start a second JVM that a debugger attached to the wrapper would never see. Aspire drops the launch goal or task and its arguments for that launch, and passes the app’s environment variables — including JAVA_TOOL_OPTIONS — and the arguments that you add with WithArgs to the debugger. Settings that you pass only through goal or task arguments, such as -Dspring-boot.run.profiles=dev, don’t apply to a debug launch, so use an environment variable, such as SPRING_PROFILES_ACTIVE, for settings that should apply to both.

The debugger needs the app’s main class. Aspire reads it from the JAR that the project’s most recent build left in its output directory: the Start-Class that the Spring Boot plugins record, or a plain JAR’s Main-Class. If there’s no such JAR, the Java debugger looks for the main class in the project, and asks you to choose one when it finds more than one. To choose the main class in the AppHost instead, call WithMainClasswithMainClass, which affects only launches from the IDE:

apphost.mts
import {
function createBuilder(): IDistributedApplicationBuilder

Creates a new distributed application builder

createBuilder
} from './.aspire/modules/aspire.mjs';
const
const builder: IDistributedApplicationBuilder
builder
= await
function createBuilder(): IDistributedApplicationBuilder

Creates a new distributed application builder

createBuilder
();
const
const catalog: JavaAppResource
catalog
= await
const builder: IDistributedApplicationBuilder
builder
.
IDistributedApplicationBuilder.addSpringBootApp(name: string, appDirectory: string): JavaAppResource

Adds a Spring Boot application to the application model, built and launched with its own Maven or Gradle wrapper.

addSpringBootApp
('catalog', '../catalog');
await
const catalog: JavaAppResource
catalog
.
JavaAppResource.withMainClass(mainClass: string): JavaAppResource

Sets the main class an IDE launches when running or debugging this application. Has no effect on how Aspire starts the process, which is decided by the JAR path, Maven goal, or Gradle task.

withMainClass
('com.example.catalog.CatalogApplication');
await
const builder: IDistributedApplicationBuilder
builder
.
IDistributedApplicationBuilder.build(): DistributedApplication

Builds the distributed application

build
().
DistributedApplication.run(cancellationToken?: cancellationToken): void

Runs the distributed application

run
();

For a prebuilt JAR, the debugger puts the JAR on the classpath and launches the Main-Class from its manifest, as java -jar does, unless you choose another class with WithMainClass. A Quarkus app is launched from the fast JAR that its build packages (quarkus-app/quarkus-run.jar in the build output), so when VS Code launches a Quarkus app, Aspire runs the app’s build first. Breakpoints in your source still bind.

When you run aspire publish or aspire deploy, Aspire packages each Java app as a container image. If the app directory contains a Dockerfile, Aspire uses it as-is. Otherwise, Aspire generates a multi-stage Dockerfile that:

  • Builds the project inside the image with the project’s wrapper and the arguments from WithMavenBuild or WithGradleBuild. Without either method, it packages the app with -B -ntp -DskipTests package for Maven or -x test build for Gradle. Dependency caches are kept in a BuildKit cache mount rather than in an image layer.
  • Detects the Java version that the project targets from pom.xml or the Gradle build file, defaulting to Java 21, and runs the app on the matching eclipse-temurin JRE image. The build stage uses the matching JDK image, unless the Maven or Gradle version that the wrapper pins can’t run on that JDK, in which case it uses the nearest JDK version that the tool supports.
  • Runs the app as an unprivileged user (USER 999:999) with the JVM as process ID 1, so the JVM receives SIGTERM directly and shutdown hooks run.
  • Copies a build-produced OpenTelemetry agent into the image.

The app directory is the build context, so everything that the build needs must be inside it, including the wrapper when Aspire generates the Dockerfile. For the same reason, changing a Java app’s working directory after you add it isn’t supported when Aspire generates the Dockerfile. Java containers that you add with AddJavaContainer keep using the image that you supply.

For more information about publishing, see Aspire deployment.

A JAR path doesn’t by itself mean that the image copies a prebuilt JAR. When the app directory contains a Maven or Gradle build file — or the app has a build step, Maven goal, or Gradle task — the image builds the project, and Aspire selects the JAR to run from the build output in this order:

  1. The JAR that you name with WithJarArtifactwithJarArtifact.
  2. For a Quarkus app, the artifact that the build’s quarkus-artifact.properties file names, together with the dependencies that it needs. All three Quarkus packaging types — fast-jar, legacy-jar, and uber-jar — work without extra configuration.
  3. The JAR path that you passed to AddJavaApp.
  4. The only JAR that the build produced, ignoring -plain, -sources, and -javadoc JARs.

If the last step finds more than one candidate — such as the original-*.jar that a shade plugin leaves beside the shaded JAR — the image build fails and lists the candidates. Name the right one with WithJarArtifact. The method affects only publishing, so an app can run one JAR locally and publish another.

apphost.mts
import {
function createBuilder(): IDistributedApplicationBuilder

Creates a new distributed application builder

createBuilder
} from './.aspire/modules/aspire.mjs';
const
const builder: IDistributedApplicationBuilder
builder
= await
function createBuilder(): IDistributedApplicationBuilder

Creates a new distributed application builder

createBuilder
();
const
const catalog: JavaAppResource
catalog
= await
const builder: IDistributedApplicationBuilder
builder
.
IDistributedApplicationBuilder.addSpringBootApp(name: string, appDirectory: string): JavaAppResource

Adds a Spring Boot application to the application model, built and launched with its own Maven or Gradle wrapper.

addSpringBootApp
('catalog', '../catalog');
await
const catalog: JavaAppResource
catalog
.
JavaAppResource.withJarArtifact(jarPath: string): JavaAppResource

Selects the JAR the generated container image runs, for projects whose build produces more than one.

withJarArtifact
('target/catalog-0.0.1-SNAPSHOT-exec.jar');
await
const builder: IDistributedApplicationBuilder
builder
.
IDistributedApplicationBuilder.build(): DistributedApplication

Builds the distributed application

build
().
DistributedApplication.run(cancellationToken?: cancellationToken): void

Runs the distributed application

run
();

Only an app directory without a build file, for an app without a build step, Maven goal, or Gradle task, publishes by copying the prebuilt JAR into the image. Build output directories usually aren’t in source control, so building inside the image avoids publishing a stale local JAR, and still works in a clean clone or on a CI agent.

The generated Dockerfile builds on docker.io/library/eclipse-temurin:<version>-jdk and runs on docker.io/library/eclipse-temurin:<version>-jre. The build stage needs only a JDK, because the project’s wrapper downloads the Maven or Gradle version that it pins. To use other images, such as mirrors in your own registry, set both in a single call to WithDockerfileBaseImagewithDockerfileBaseImage:

apphost.mts
import {
function createBuilder(): IDistributedApplicationBuilder

Creates a new distributed application builder

createBuilder
} from './.aspire/modules/aspire.mjs';
const
const builder: IDistributedApplicationBuilder
builder
= await
function createBuilder(): IDistributedApplicationBuilder

Creates a new distributed application builder

createBuilder
();
const
const catalog: JavaAppResource
catalog
= await
const builder: IDistributedApplicationBuilder
builder
.
IDistributedApplicationBuilder.addSpringBootApp(name: string, appDirectory: string): JavaAppResource

Adds a Spring Boot application to the application model, built and launched with its own Maven or Gradle wrapper.

addSpringBootApp
('catalog', '../catalog');
await
const catalog: JavaAppResource
catalog
.
ExecutableResource.withDockerfileBaseImage(options?: {
buildImage?: string;
runtimeImage?: string;
} | undefined): JavaAppResource (+1 overload)

Configures custom base images for generated Dockerfiles.

withDockerfileBaseImage
({
buildImage?: string | undefined
buildImage
: 'myregistry.azurecr.io/eclipse-temurin:21-jdk',
runtimeImage?: string | undefined
runtimeImage
: 'myregistry.azurecr.io/eclipse-temurin:21-jre',
});
await
const builder: IDistributedApplicationBuilder
builder
.
IDistributedApplicationBuilder.build(): DistributedApplication

Builds the distributed application

build
().
DistributedApplication.run(cancellationToken?: cancellationToken): void

Runs the distributed application

run
();

The generated Dockerfile can copy files that another resource produces, such as a frontend’s build output, into the Java app’s image. Call PublishWithContainerFilespublishWithContainerFiles on the Java app with the source resource and a destination path. A relative destination path is resolved against the image’s /app working directory, so the following example places the frontend app’s build output in /app/static:

apphost.mts
import {
function createBuilder(): IDistributedApplicationBuilder

Creates a new distributed application builder

createBuilder
} from './.aspire/modules/aspire.mjs';
const
const builder: IDistributedApplicationBuilder
builder
= await
function createBuilder(): IDistributedApplicationBuilder

Creates a new distributed application builder

createBuilder
();
const
const catalog: JavaAppResource
catalog
= await
const builder: IDistributedApplicationBuilder
builder
.
IDistributedApplicationBuilder.addSpringBootApp(name: string, appDirectory: string): JavaAppResource

Adds a Spring Boot application to the application model, built and launched with its own Maven or Gradle wrapper.

addSpringBootApp
('catalog', '../catalog');
await
const catalog: JavaAppResource
catalog
.
ExecutableResource.withExternalHttpEndpoints(): JavaAppResource

Marks existing http or https endpoints on a resource as external.

withExternalHttpEndpoints
();
const
const frontend: ViteAppResource
frontend
= await
const builder: IDistributedApplicationBuilder
builder
.
IDistributedApplicationBuilder.addViteApp(name: string, appDirectory: string, options?: {
runScriptName?: string;
}): ViteAppResource (+1 overload)

Adds a Vite app to the distributed application builder.

addViteApp
('frontend', '../frontend');
await
const frontend: ViteAppResource
frontend
.
ExecutableResource.withReference(source: IResource | EndpointReference | string | uri, options?: {
connectionName?: string;
optional?: boolean;
name?: string;
} | undefined): ViteAppResource (+1 overload)

Adds a reference to another resource

withReference
(
const catalog: JavaAppResource
catalog
);
await
const frontend: ViteAppResource
frontend
.
ExecutableResource.waitFor(dependency: IResource | IResourceWithConnectionString, waitBehavior?: WaitBehavior): ViteAppResource

Waits for another resource to be ready

waitFor
(
const catalog: JavaAppResource
catalog
);
await
const catalog: JavaAppResource
catalog
.
IContainerFilesDestinationResource.publishWithContainerFiles(source: IResourceWithContainerFiles, destinationPath: string): JavaAppResource

Configures the resource to copy container files from the specified source resource during publishing.

publishWithContainerFiles
(
const frontend: ViteAppResource
frontend
, './static');
await
const builder: IDistributedApplicationBuilder
builder
.
IDistributedApplicationBuilder.build(): DistributedApplication

Builds the distributed application

build
().
DistributedApplication.run(cancellationToken?: cancellationToken): void

Runs the distributed application

run
();

Aspire builds the source resource before it builds the Java app’s image. Copying the files doesn’t make the app serve them, so configure the app to serve static files from that directory. Aspire adds the files only to the Dockerfile that it generates, so an authored Dockerfile in the app directory doesn’t receive them.

For the environment variables that a Java app receives — and how other apps discover a Java app’s endpoints — see Connect to Java apps.

The Java hosting integration doesn’t add health checks to Java resources. When your app exposes a health endpoint, such as /actuator/health with Spring Boot Actuator or /q/health with SmallRye Health, add a check with WithHttpHealthCheck.

For Aspire 13.6 and later, Aspire.Hosting.Java replaces 📦 CommunityToolkit.Aspire.Hosting.Java. To migrate an AppHost:

  1. Remove CommunityToolkit.Aspire.Hosting.Java from the AppHost’s package references, #:package directives, or aspire.config.json. Don’t keep both packages, because they define overlapping APIs.
  2. Add Aspire.Hosting.Java, as described in Installation.
  3. Update your AppHost code with the replacements in the following table. The table uses C# names. TypeScript AppHosts use the same names in camel case, except that the JAR overload of AddJavaApp is addJavaAppWithJar, and the parameterless WithOtelAgent is withOtelAgentDefaultPath.
  4. Review how your Java apps are published. The first-party integration builds a container image for each app that you add with AddJavaApp, AddSpringBootApp, or AddQuarkusApp, from the app’s own Dockerfile or from one that Aspire generates. For more information, see Publish Java apps.
Community Toolkit APIFirst-party replacement
AddJavaContainerAppAddJavaContainer. In TypeScript, pass the image tag as { tag: '...' }.
AddSpringApp, and overloads that take JavaAppExecutableResourceOptions or JavaAppContainerResourceOptionsAddSpringBootApp, AddJavaApp, or AddJavaContainer, configured with builder methods.
The workingDirectory parameter of AddJavaAppThe appDirectory parameter.
JavaAppExecutableResource and JavaAppContainerResourceJavaAppResource and JavaContainerResource.
The JarPath resource propertyPass the JAR path when you add the app, and use WithJarArtifact to choose the published JAR.
WithMavenBuild(MavenOptions)WithMavenBuild with the build arguments, including the packaging goal. For a custom Command, also call WithWrapperPath.
WithOtelAgent(), called only to configure telemetry exportRemove the call, because every Java resource is configured for export. The parameterless method now attaches the agent that the build produces.