Skip to content

Instantly share code, notes, and snippets.

@davidfowl
Last active December 10, 2025 23:53
Show Gist options
  • Select an option

  • Save davidfowl/154891c5786ac5682c90e7b231c71675 to your computer and use it in GitHub Desktop.

Select an option

Save davidfowl/154891c5786ac5682c90e7b231c71675 to your computer and use it in GitHub Desktop.

Aspire + Java Integration Proposal

See 2024 JetBrains Java Developer Survey and Spring Boot Statistics for context on the Java ecosystem.

Note: The Community Toolkit provides Java/Spring Boot hosting. Aspire 13 elevated Python and JavaScript to first-class citizens with polyglot connection properties including JDBC format. This proposal outlines bringing Java to the same first-class status.

Executive Summary

Java is the second most popular enterprise language after JavaScript/TypeScript, with Spring Boot dominating the web framework space (~70% of Java web applications). With Aspire 13's rebrand from ".NET Aspire" to "Aspire" and first-class Python/JavaScript support, Java is the natural next step. This document outlines a Spring Boot-centric approach while maintaining flexibility for other Java frameworks.


Application Support

There are several ways to launch Java applications:

  1. Maven: mvn spring-boot:run or mvn exec:java
  2. Gradle: ./gradlew bootRun or ./gradlew run
  3. Executable JAR: java -jar application.jar
  4. Exploded application: java -cp "lib/*" com.example.Main

Aspire should support all methods, with the ability to configure JVM arguments, system properties, and environment variables.

Build Tool Detection

Aspire should auto-detect the build system:

File Present Build System Default Command
pom.xml Maven mvn spring-boot:run
build.gradle or build.gradle.kts Gradle ./gradlew bootRun
*.jar Pre-built java -jar <app>.jar

Tip

On Windows, use mvnw.cmd and gradlew.bat if wrapper scripts are present. This ensures consistent build tool versions across teams.


Framework Support

Spring Boot (Primary Target)

Spring Boot should be the primary focus given its market dominance. Aspire integration should leverage existing Spring Boot capabilities:

Profiles

Spring Boot profiles allow environment-specific configuration. Aspire should:

  • Set SPRING_PROFILES_ACTIVE=aspire (or configurable profile name)
  • Allow developers to define Aspire-specific configuration in application-aspire.yml
# application-aspire.yml
spring:
  datasource:
    url: ${CATALOG_JDBC}
    username: ${CATALOG_USERNAME}
    password: ${CATALOG_PASSWORD}
  data:
    redis:
      url: ${REDIS_URL}

Auto-Configuration

Spring Boot's auto-configuration automatically configures beans based on classpath and properties. Aspire should:

  • Provide a spring-boot-starter-aspire that auto-configures:
    • OpenTelemetry instrumentation
    • Service discovery
    • Health check endpoints
    • Connection string parsing

Actuator Integration

Spring Boot Actuator provides production-ready features. Aspire should leverage:

Actuator Endpoint Aspire Use Case
/actuator/health Health checks in dashboard
/actuator/info Service metadata
/actuator/metrics Metrics collection
/actuator/loggers Dynamic log level changes
/actuator/env Environment inspection

DevTools Integration

Spring Boot DevTools provides:

  • Automatic restart on code changes
  • LiveReload support
  • Development-time property defaults

Aspire should detect and support DevTools for local development scenarios.

Other Frameworks

While Spring Boot is the priority, Aspire should support:

Framework Priority Notes
Spring Boot P0 Primary target
Quarkus P1 Growing in cloud-native space, has dev mode
Micronaut P2 Compile-time DI, fast startup
Jakarta EE P3 Enterprise deployments
Plain Java P3 Console apps, utilities

Web Application Support

Development vs Production Modes

Like Python frameworks, Java web applications have distinct modes:

Mode Spring Boot Quarkus
Development mvn spring-boot:run mvn quarkus:dev
Production java -jar app.jar java -jar app-runner.jar

Aspire should:

  1. Default to development mode for local runs
  2. Support production mode configuration
  3. Handle graceful transitions between modes

Embedded Servers

Spring Boot supports multiple embedded servers:

  • Tomcat (default)
  • Jetty
  • Undertow
  • Netty (for reactive applications)

Aspire should be agnostic to the embedded server choice.


Database Support

Polyglot Connection Properties (Aspire 13+)

Aspire 13 introduced polyglot connection properties that expose database connections in multiple formats:

Format Example Use Case
JDBC jdbc:postgresql://host:port/dbname Java applications
URI postgresql://user:pass@host:port/dbname Python, Node.js
Individual properties DB_HOST, DB_PORT, DB_USERNAME, DB_PASSWORD, DB_DATABASENAME Any language

Note: JDBC connection strings do not include credentials for security best practices. Username and password are provided as separate environment variables.

var postgres = builder.AddPostgres("postgres")
    .AddDatabase("catalog");

builder.AddSpringApp("catalog-api", "../CatalogApi")
    .WithReference(postgres);  // Provides JDBC URL + credentials

The Java application receives environment variables using the resource name:

CATALOG_JDBC=jdbc:postgresql://localhost:5432/catalog
CATALOG_USERNAME=postgres
CATALOG_PASSWORD=...

This can be used directly in Spring Boot:

# application-aspire.yml
spring:
  datasource:
    url: ${CATALOG_JDBC}
    username: ${CATALOG_USERNAME}
    password: ${CATALOG_PASSWORD}

Or programmatically:

String jdbcUrl = System.getenv("CATALOG_JDBC");
String username = System.getenv("CATALOG_USERNAME");
String password = System.getenv("CATALOG_PASSWORD");

Supported Databases

Database JDBC Driver Spring Boot Starter
PostgreSQL postgresql spring-boot-starter-data-jpa
MySQL/MariaDB mysql spring-boot-starter-data-jpa
SQL Server mssql-jdbc spring-boot-starter-data-jpa
MongoDB mongodb-driver spring-boot-starter-data-mongodb
Redis jedis/lettuce spring-boot-starter-data-redis

Packaging and Dependencies

Maven Projects

For pom.xml-based projects:

  1. Detect Maven wrapper (mvnw) presence
  2. Run ./mvnw dependency:resolve to download dependencies
  3. Run ./mvnw spring-boot:run or configured goal
<!-- Aspire could inject a profile -->
<profile>
    <id>aspire</id>
    <dependencies>
        <dependency>
            <groupId>io.aspire</groupId>
            <artifactId>spring-boot-starter-aspire</artifactId>
        </dependency>
    </dependencies>
</profile>

Gradle Projects

For build.gradle-based projects:

  1. Detect Gradle wrapper (gradlew) presence
  2. Run ./gradlew dependencies to resolve
  3. Run ./gradlew bootRun or configured task
// build.gradle.kts - Aspire could add this
dependencies {
    if (System.getenv("ASPIRE_ENABLED") != null) {
        implementation("io.aspire:spring-boot-starter-aspire")
    }
}

Pre-built JARs

For executable JARs:

  1. Locate the JAR file (configurable path)
  2. Run with java -jar <app>.jar
  3. Pass JVM arguments and environment variables

JDK Management

Version Detection

Aspire should detect and respect:

  1. JAVA_HOME environment variable
  2. .java-version file (SDKMAN convention)
  3. .tool-versions file (asdf convention)
  4. pom.xml <maven.compiler.source> property
  5. build.gradle sourceCompatibility setting

JDK Requirements

LTS Version Support Status Notes
Java 8 Maintenance Legacy, declining usage
Java 11 Supported Enterprise standard
Java 17 Recommended Current LTS, Spring Boot 3 minimum
Java 21 Supported Latest LTS, virtual threads

Warning

Spring Boot 3.x requires Java 17+. Spring Boot 2.x (maintenance mode) supports Java 8+. Aspire should validate compatibility and warn appropriately.


Debugging

Remote Debugging

Java applications support remote debugging via JDWP. Aspire should:

  1. Enable debug agent in development mode:

    -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005
    
  2. Expose debug port in dashboard

  3. Provide IDE connection instructions

Hot Reload

Spring Boot DevTools provides automatic restart. For frameworks without DevTools:

  • JRebel (commercial)
  • DCEVM + HotswapAgent
  • Spring Loaded (deprecated)

Aspire should document integration with these tools but not require them.

Clean Shutdown

Java applications should receive SIGTERM for graceful shutdown. Spring Boot handles this via:

@Bean
public GracefulShutdown gracefulShutdown() {
    return new GracefulShutdown();
}

Or via server.shutdown=graceful in application.properties.

Aspire should:

  1. Send SIGTERM first
  2. Wait configurable timeout (default 30s)
  3. Send SIGKILL if process doesn't terminate

Logging and Tracing

Standard OTEL Environment Variables

Aspire injects standard OpenTelemetry environment variables that Java applications can use directly:

Variable Description Default
OTEL_EXPORTER_OTLP_ENDPOINT OTLP collector endpoint http://localhost:4318
OTEL_SERVICE_NAME Service name for telemetry Resource name
OTEL_TRACES_EXPORTER Traces exporter otlp
OTEL_METRICS_EXPORTER Metrics exporter otlp
OTEL_LOGS_EXPORTER Logs exporter otlp
OTEL_RESOURCE_ATTRIBUTES Additional resource attributes -

This means Java applications with OpenTelemetry configured will automatically export telemetry to Aspire's dashboard without additional configuration.

Three Integration Approaches

1. Java Agent (Zero-Code) - Recommended for Quick Start

The OpenTelemetry Java Agent provides automatic instrumentation for 150+ libraries including Spring, JDBC, HTTP clients, and messaging systems.

java -javaagent:opentelemetry-javaagent.jar -jar application.jar

The agent automatically reads the OTEL_* environment variables injected by Aspire. No code changes required.

Aspire should:

  • Automatically download and cache the OTEL Java agent
  • Inject the -javaagent JVM argument
  • Set all required OTEL_* environment variables

2. OpenTelemetry Spring Boot Starter - Recommended for Spring Boot

The OpenTelemetry Spring Boot Starter (stable since 2024) provides native Spring Boot integration.

<dependency>
    <groupId>io.opentelemetry.instrumentation</groupId>
    <artifactId>opentelemetry-spring-boot-starter</artifactId>
</dependency>

The starter automatically picks up OTEL_* environment variables. It also supports Spring-style configuration:

# application.yml - these are read automatically from OTEL_* env vars
otel:
  exporter:
    otlp:
      endpoint: ${OTEL_EXPORTER_OTLP_ENDPOINT}
  service:
    name: ${OTEL_SERVICE_NAME}

Advantages over Java agent:

  • Lighter weight (no bytecode manipulation)
  • Better IDE debugging support
  • Spring-native configuration
  • Easier custom instrumentation

3. Micrometer with OTLP (Spring Boot Native)

Spring Boot 3.x includes native observability via Micrometer, which can export to OTLP:

# application.yml
management:
  otlp:
    tracing:
      endpoint: ${OTEL_EXPORTER_OTLP_ENDPOINT}/v1/traces
    metrics:
      export:
        endpoint: ${OTEL_EXPORTER_OTLP_ENDPOINT}/v1/metrics
  tracing:
    sampling:
      probability: 1.0

Note: Spring Boot's native OpenTelemetry auto-configuration uses spring.application.name as the service name by default, not OTEL_SERVICE_NAME. The OpenTelemetry Spring Boot Starter respects OTEL_SERVICE_NAME.

Recommended Approach for Aspire

For first-class Java support, Aspire should:

  1. Detect OpenTelemetry setup - Check if the project has OTEL dependencies
  2. If no OTEL detected - Automatically attach the Java agent
  3. If OTEL starter detected - Just inject environment variables (already works)
  4. If Micrometer OTLP detected - Inject environment variables (already works)
builder.AddSpringApp("catalog-api", "../CatalogApi")
    .WithOpenTelemetry();  // Auto-detect and configure appropriately

// Or explicitly choose approach
builder.AddSpringApp("catalog-api", "../CatalogApi")
    .WithOpenTelemetryAgent();  // Force Java agent

builder.AddSpringApp("catalog-api", "../CatalogApi")
    .WithOpenTelemetry(agent: false);  // Rely on app's own OTEL setup

Logging Integration

All three approaches support log export to OTLP:

Approach Log Configuration
Java Agent Automatic (captures SLF4J/Logback/Log4j2)
Spring Starter Add opentelemetry-logback-appender dependency
Micrometer Not supported natively, use starter or agent

For the Spring Boot Starter with Logback:

<!-- logback-spring.xml -->
<configuration>
    <appender name="OTEL" class="io.opentelemetry.instrumentation.logback.appender.v1_0.OpenTelemetryAppender"/>
    <root level="INFO">
        <appender-ref ref="OTEL"/>
        <appender-ref ref="CONSOLE"/>
    </root>
</configuration>

Metrics Integration

Spring Boot Actuator with Micrometer automatically exports metrics when configured:

management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics,prometheus
  otlp:
    metrics:
      export:
        enabled: true
        endpoint: ${OTEL_EXPORTER_OTLP_ENDPOINT}/v1/metrics

The Java agent also captures JVM metrics automatically.


Service Discovery

Simplified Service URLs (Aspire 13+)

Aspire 13 introduced polyglot-friendly environment variables for service URLs:

services__basket-api__http__0=http://localhost:5001
services__basket-api__https__0=https://localhost:5002

# Simplified format (Aspire 13+)
BASKET_API_HTTP=http://localhost:5001
BASKET_API_HTTPS=https://localhost:5002

This simplifies Spring Boot configuration:

# application-aspire.yml
app:
  services:
    basket-api: ${BASKET_API_HTTP}
    catalog-api: ${CATALOG_API_HTTP}

Spring Cloud Integration (Optional)

For teams already using Spring Cloud, Aspire could provide a DiscoveryClient implementation:

// Aspire could provide a DiscoveryClient implementation
@Configuration
@ConditionalOnClass(DiscoveryClient.class)
public class AspireDiscoveryConfiguration {
    @Bean
    public DiscoveryClient aspireDiscoveryClient() {
        return new AspireDiscoveryClient();
    }
}

This would allow:

@Autowired
private DiscoveryClient discoveryClient;

// Discovers "basket-api" service registered in Aspire
ServiceInstance instance = discoveryClient
    .getInstances("basket-api")
    .get(0);

However, the simplified environment variable approach is recommended for most use cases.


Proposed API (App Host)

Basic Spring Boot Application

var builder = DistributedApplication.CreateBuilder(args);

var postgres = builder.AddPostgres("postgres")
    .AddDatabase("catalog");

builder.AddSpringApp("catalog-api", "../CatalogApi")
    .WithReference(postgres)
    .WithHttpEndpoint(port: 8080);

builder.Build().Run();

With Maven Configuration

builder.AddSpringApp("catalog-api", "../CatalogApi")
    .WithMavenGoal("spring-boot:run")
    .WithProfile("aspire")
    .WithJvmArgs("-Xmx512m", "-Xms256m")
    .WithReference(postgres);

With Pre-built JAR

builder.AddJavaApp("worker", "./workers/processor.jar")
    .WithJavaVersion(21)
    .WithJvmArgs("-Xmx1g")
    .WithEnvironment("WORKER_THREADS", "4");

With Gradle

builder.AddSpringApp("api", "../ApiProject")
    .WithGradleTask("bootRun")
    .WithProfile("aspire");

Non-Spring Applications

// Quarkus
builder.AddQuarkusApp("quarkus-api", "../QuarkusProject")
    .WithDevMode();

// Plain Java
builder.AddJavaApp("processor", "../JavaProcessor")
    .WithMainClass("com.example.Main")
    .WithClasspath("lib/*", "classes");

Java Library Deliverables

1. aspire-java-core

Core utilities for any Java application:

// Connection string parsing
String jdbcUrl = AspireConnectionStrings.getJdbcUrl("postgres");
Map<String, String> props = AspireConnectionStrings.parse("redis");

// Service discovery
String basketApiUrl = AspireServices.getUrl("basket-api");

// Environment helpers
boolean isDevelopment = AspireEnvironment.isDevelopment();

2. spring-boot-starter-aspire

Auto-configuration for Spring Boot:

// Just add the dependency - everything auto-configures
@SpringBootApplication
public class CatalogApiApplication {
    public static void main(String[] args) {
        SpringApplication.run(CatalogApiApplication.class, args);
    }
}

Auto-configured features:

  • DataSource from {RESOURCE}_JDBC, {RESOURCE}_USERNAME, {RESOURCE}_PASSWORD
  • Redis from {RESOURCE}_URL
  • Health check endpoints
  • Service discovery client

Note: OpenTelemetry configuration is not needed in this starter. Aspire injects standard OTEL_* environment variables that the OpenTelemetry Spring Boot Starter or Java agent read automatically.

3. aspire-quarkus-extension (Future)

Quarkus extension with similar functionality.


Migration Path from Community Toolkit

The existing Community Toolkit integration should continue working. Proposed migration:

Phase Timeline Changes
Phase 1 - Add spring-boot-starter-aspire
Phase 2 - Enhance AddSpringApp with auto-detection
Phase 3 - Automatic OTEL agent management
Phase 4 - First-party support consideration

Open Questions

  1. Should Aspire manage the JDK installation?

    • Pro: Consistent environment
    • Con: Complexity, existing tools (SDKMAN, Jabba) do this well
  2. Should Aspire auto-attach the Java agent for OTEL?

    • Pro: Zero-code telemetry for any Java app
    • Con: Adds startup overhead, may conflict with existing OTEL setup
    • Recommendation: Auto-detect and only attach if no OTEL dependencies found
  3. How to handle multi-module Maven/Gradle projects?

    • Detect module structure
    • Allow specifying target module
  4. Should we support GraalVM native images?

    • Faster startup, lower memory
    • Requires additional build configuration
  5. What about Kotlin?

    • Runs on JVM, same infrastructure
    • Spring Boot fully supports Kotlin
    • Should work out of the box

References


Contributors

This document should be reviewed by Java Champions and experts:

  • Manfred Riem (Microsoft)
  • Bruno Borges (Microsoft)
  • Jonathan Giles (Microsoft)

Feedback welcome! Please comment or open issues for discussion.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment