Skip to content

Add Java debugger support#12076

Open
marshalhayes wants to merge 13 commits intodotnet:mainfrom
marshalhayes:vscode-extension/java
Open

Add Java debugger support#12076
marshalhayes wants to merge 13 commits intodotnet:mainfrom
marshalhayes:vscode-extension/java

Conversation

@marshalhayes
Copy link
Contributor

@marshalhayes marshalhayes commented Oct 16, 2025

Description

This PR introduces Java debugger support for the VSCode extension.

This will require a corresponding change in the Community Toolkit.

cc @adamint

Checklist

  • Is this feature complete?
    • Yes. Ready to ship.
    • No. Follow-up changes expected.
  • Are you including unit tests for the changes and scenario tests if relevant?
    • Yes
    • No
  • Did you add public API?
    • Yes
      • If yes, did you have an API Review for it?
        • Yes
        • No
      • Did you add <remarks /> and <code /> elements on your triple slash comments?
        • Yes
        • No
    • No
  • Does the change make any security assumptions or guarantees?
    • Yes
      • If yes, have you done a threat model and had a security review?
        • Yes
        • No
    • No
  • Does the change require an update in our Aspire docs?

@github-actions
Copy link
Contributor

github-actions bot commented Oct 16, 2025

🚀 Dogfood this PR with:

⚠️ WARNING: Do not do this without first carefully reviewing the code of this PR to satisfy yourself it is safe.

curl -fsSL https://raw.githubusercontent.com/dotnet/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 12076

Or

  • Run remotely in PowerShell:
iex "& { $(irm https://raw.githubusercontent.com/dotnet/aspire/main/eng/scripts/get-aspire-cli-pr.ps1) } 12076"

@dotnet-policy-service dotnet-policy-service bot added the community-contribution Indicates that the PR has been added by a community member label Oct 16, 2025
@marshalhayes marshalhayes marked this pull request as ready for review October 17, 2025 03:09
Copilot AI review requested due to automatic review settings October 17, 2025 03:09
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

Adds Java debugger support to the VS Code extension, enabling detection, configuration, and launch handling for Java services.

  • Introduces Java launch configuration types and a Java-specific debugger extension.
  • Adds capability detection via the Java Extension Pack and updates supported capabilities.
  • Provides unit tests for Java getProjectFile behavior and updates README with Java docs.

Reviewed Changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
extension/src/test/javaDebugger.test.ts Adds unit tests covering Java getProjectFile logic, precedence, and error cases.
extension/src/debugger/languages/java.ts Implements the Java ResourceDebuggerExtension and getProjectFile resolution.
extension/src/debugger/debuggerExtensions.ts Registers Java debugger extension when Java capability is present.
extension/src/dcp/types.ts Adds JavaLaunchConfiguration type and type guard.
extension/src/capabilities.ts Adds Java capability detection and capability list entries.
extension/README.md Documents Java debugger option and links to VS Code Java debugging docs.

@marshalhayes
Copy link
Contributor Author

I'll add the new getSupportedFileTypes to the debugger extension soon.

I did hit a weird issue that crashes the Java debugger on startup. It looks like something is introducing duplicate environment variables. Is it possible the Java debugger already merges the launch configuration env with process.env?

This is the stacktrace from the Language Support for Java output window:

Oct 18, 2025 2:20:25 PM com.microsoft.java.debug.core.adapter.handler.LaunchRequestHandler constructEnvironmentVariables
WARNING: There are duplicated environment variables. The values specified in launch.json will be used. Here are the duplicated entries: ALLUSERSPROFILE,ANDROID_HOME,APPDATA,APPLICATION_INSIGHTS_NO_STATSBEAT,CHROME_CRASHPAD_PIPE_NAME,CommonPropertyBagPath,CommonPropertyBagWithConfigPath,COMPUTERNAME,ComSpec,CommonProgramFiles,CommonProgramFiles(x86),CommonProgramW6432,DOTNET_ROOT,DriverData,EFC_9208_1592913036,ELECTRON_RUN_AS_NODE,FPS_BROWSER_APP_PROFILE_STRING,FPS_BROWSER_USER_PROFILE_STRING,HOMEDRIVE,HOMEPATH,JAVA_HOME,LOCALAPPDATA,LOGONSERVER,NUGET_PACKAGES,NUMBER_OF_PROCESSORS,NVM_HOME,NVM_SYMLINK,ORIGINAL_XDG_CURRENT_DESKTOP,OS,OneDrive,OneDriveConsumer,PATHEXT,POWERSHELL_DISTRIBUTION_CHANNEL,PROCESSOR_ARCHITECTURE,PROCESSOR_IDENTIFIER,PROCESSOR_LEVEL,PROCESSOR_REVISION,PSModulePath,PUBLIC,Path,ProgramData,ProgramFiles,ProgramFiles(x86),ProgramW6432,SESSIONNAME,SystemDrive,SystemRoot,TEMP,TMP,USERDOMAIN,USERDOMAIN_ROAMINGPROFILE,USERNAME,USERPROFILE,VSCODE_CRASH_REPORTER_PROCESS_TYPE,VSCODE_CWD,VSCODE_DOTNET_INSTALL_TOOL_ORIGINAL_HOME,VSCODE_ESM_ENTRYPOINT,VSCODE_HANDLES_UNCAUGHT_ERRORS,VSCODE_IPC_HOOK,VSCODE_NLS_CONFIG,VSCODE_PID,ZES_ENABLE_SYSMAN,windir.
[Error - 2:20:25 PM] Oct 18, 2025, 2:20:25PM [error response][launch]: Failed to launch debuggee VM. Reason: VM exited with status 1
Failed to launch debuggee VM. Reason: VM exited with status 1
com.microsoft.java.debug.core.DebugException: Failed to launch debuggee VM. Reason: VM exited with status 1
	at com.microsoft.java.debug.core.adapter.handler.LaunchRequestHandler.launch(LaunchRequestHandler.java:326)
	at com.microsoft.java.debug.core.adapter.handler.LaunchRequestHandler.handleLaunchCommand(LaunchRequestHandler.java:193)
	at com.microsoft.java.debug.core.adapter.handler.LaunchRequestHandler.handle(LaunchRequestHandler.java:95)
	at com.microsoft.java.debug.core.adapter.DebugAdapter.lambda$dispatchRequest$0(DebugAdapter.java:94)
	at java.base/java.util.concurrent.CompletableFuture.uniComposeStage(Unknown Source)
	at java.base/java.util.concurrent.CompletableFuture.thenCompose(Unknown Source)
	at com.microsoft.java.debug.core.adapter.DebugAdapter.dispatchRequest(DebugAdapter.java:93)
	at com.microsoft.java.debug.core.adapter.ProtocolServer.dispatchRequest(ProtocolServer.java:132)
	at com.microsoft.java.debug.core.protocol.AbstractProtocolServer.lambda$new$0(AbstractProtocolServer.java:81)
	at io.reactivex.internal.observers.LambdaObserver.onNext(LambdaObserver.java:63)
	at io.reactivex.internal.operators.observable.ObservableObserveOn$ObserveOnObserver.drainNormal(ObservableObserveOn.java:201)
	at io.reactivex.internal.operators.observable.ObservableObserveOn$ObserveOnObserver.run(ObservableObserveOn.java:255)
	at io.reactivex.internal.schedulers.ScheduledRunnable.run(ScheduledRunnable.java:66)
	at io.reactivex.internal.schedulers.ScheduledRunnable.call(ScheduledRunnable.java:57)
	at java.base/java.util.concurrent.FutureTask.run(Unknown Source)
	at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(Unknown Source)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
	at java.base/java.lang.Thread.run(Unknown Source)

@adamint
Copy link
Member

adamint commented Oct 18, 2025

You should place a breakpoint right before calling viscode.debug.startDebugging to see the contents of the env property. That may be a good place to start

@marshalhayes
Copy link
Contributor Author

There are definitely duplicate environment variables being set, but it looks like they weren't the cause of the crash. It is actually the JVM crashing because the otel agent path.

From the Community Toolkit Java example, this works:

var executableapp = builder.AddSpringApp("executableapp",
                           workingDirectory: "../CommunityToolkit.Aspire.Hosting.Java.Spring.Maven",
                           new JavaAppExecutableResourceOptions()
                           {
                               ApplicationName = "target/spring-maven-0.0.1-SNAPSHOT.jar",
                               Port = 8085,
                               OtelAgentPath = Path.Combine(Directory.GetCurrentDirectory(), "../../../agents")
                           })
                           .WithVSCodeDebugSupport(Path.Combine(Directory.GetCurrentDirectory(), "../CommunityToolkit.Aspire.Hosting.Java.Spring.Maven"), "java", "vscjava.vscode-java-pack")

This does not:

var executableapp = builder.AddSpringApp("executableapp",
                           workingDirectory: "../CommunityToolkit.Aspire.Hosting.Java.Spring.Maven",
                           new JavaAppExecutableResourceOptions()
                           {
                               ApplicationName = "target/spring-maven-0.0.1-SNAPSHOT.jar",
                               Port = 8085,
                               OtelAgentPath = "../../../agents",
                           })
                           .WithVSCodeDebugSupport(Path.Combine(Directory.GetCurrentDirectory(), "../CommunityToolkit.Aspire.Hosting.Java.Spring.Maven"), "java", "vscjava.vscode-java-pack")

Nevertheless, it looks like the bug may be in how the Community Toolkit reads the OtelAgentPath, and not because of the Java debugger.

@adamint
Copy link
Member

adamint commented Oct 19, 2025

There are definitely duplicate environment variables being set, but it looks like they weren't the cause of the crash. It is actually the JVM crashing because the otel agent path.

From the Community Toolkit Java example, this works:

var executableapp = builder.AddSpringApp("executableapp",
                           workingDirectory: "../CommunityToolkit.Aspire.Hosting.Java.Spring.Maven",
                           new JavaAppExecutableResourceOptions()
                           {
                               ApplicationName = "target/spring-maven-0.0.1-SNAPSHOT.jar",
                               Port = 8085,
                               OtelAgentPath = Path.Combine(Directory.GetCurrentDirectory(), "../../../agents")
                           })
                           .WithVSCodeDebugSupport(Path.Combine(Directory.GetCurrentDirectory(), "../CommunityToolkit.Aspire.Hosting.Java.Spring.Maven"), "java", "vscjava.vscode-java-pack")

This does not:

var executableapp = builder.AddSpringApp("executableapp",
                           workingDirectory: "../CommunityToolkit.Aspire.Hosting.Java.Spring.Maven",
                           new JavaAppExecutableResourceOptions()
                           {
                               ApplicationName = "target/spring-maven-0.0.1-SNAPSHOT.jar",
                               Port = 8085,
                               OtelAgentPath = "../../../agents",
                           })
                           .WithVSCodeDebugSupport(Path.Combine(Directory.GetCurrentDirectory(), "../CommunityToolkit.Aspire.Hosting.Java.Spring.Maven"), "java", "vscjava.vscode-java-pack")

Nevertheless, it looks like the bug may be in how the Community Toolkit reads the OtelAgentPath, and not because of the Java debugger.

Can you update the community toolkit example as well? Thank you for investigating!

@marshalhayes
Copy link
Contributor Author

marshalhayes commented Oct 19, 2025

Just for clarity, this is the error I get when the JVM crashes:

Error occurred during initialization of VM
agent library failed Agent_OnLoad: instrument
Picked up JAVA_TOOL_OPTIONS: -javaagent:../../agents/opentelemetry-javaagent.jar
Error opening zip file or JAR manifest missing : ../../agents/opentelemetry-javaagent.jar

This error seems to only occur when running through the debugger. It works just fine if I use aspire run or dotnet run --file .\apphost.cs.

My code is under D:\Marshal\Projects\spring-aspire, but the Java debugger's cwd is being set to D:\Marshal\Projects. I confirmed this by adding a breakpoint at the end of createDebugSessionConfiguration:

image

If I set the cwd in the Aspire launch.json, it works through the extension as well:

{
  "configurations": [
    {
      "type": "aspire",
      "request": "launch",
      "name": "Aspire: Launch default apphost",
      "program": "${workspaceFolder}",
      "debuggers": {
        "java": {
          "cwd": "${workspaceFolder}"
        }
      }
    }
  ]
}

Without the cwd there, the path is set to D:\Marshal\Projects. If it matters, I'm using a single file app host. Both my Java application and the Aspire apphost are in the same directory.

Am I doing something wrong?

@marshalhayes
Copy link
Contributor Author

Overriding the debugConfiguration.cwd in the Java debugger's createDebugSessionConfigurationCallback fixes it as well, but this feels hacky:

export const javaDebuggerExtension: ResourceDebuggerExtension = {
    resourceType: 'java',
    debugAdapter: 'java',
    extensionId: 'vscjava.vscode-java-pack',
    displayName: 'Java',
    getSupportedFileTypes: () => ['.java'],
    getProjectFile: (launchConfig) => {
        if (isJavaLaunchConfiguration(launchConfig)) {
            const programPath = launchConfig.main_class_path || launchConfig.project_path;
            if (programPath) {
                return programPath;
            }
        }

        throw new Error(invalidLaunchConfiguration(JSON.stringify(launchConfig)));
    },
    async createDebugSessionConfigurationCallback(launchConfig, args, env, launchOptions, debugConfiguration) {
        debugConfiguration.cwd = this.getProjectFile(launchConfig);
    }
};

@marshalhayes
Copy link
Contributor Author

This should be ready now.

@marshalhayes
Copy link
Contributor Author

I used the Java example from the Community Toolkit to test this change.

The following steps are one way to try this out locally:

  1. Install Aspire CLI 9.5 or above

  2. Download JDK 17 or above and add it to the path

  3. Checkout this PR and open the extension folder in VSCode

  4. Run npm install

  5. Clone CommunityToolkit/Aspire

  6. Move to the examples/java folder

  7. Add a .vscode/launch.json

    {
      "version": "0.2.0",
      "configurations": [
        {
          "type": "aspire",
          "request": "launch",
          "name": "Aspire: Launch default apphost",
          "program": "${workspaceFolder}"
        }
      ]
    }
  8. In the app host Program.cs, add the following line to the executable app:

    .WithVSCodeDebugSupport(builder.AppHostDirectory, "java", "vscjava.vscode-java-pack");

    This line will be automatically added for you once the Community Toolkit change is released.

  9. Back in VS Code, run the extension. A new VSCode window should appear.

  10. In the new VSCode window, open the Community Toolkit examples/java folder.

  11. Run Aspire

You should be able to place breakpoints anywhere inside the Java app.

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

Labels

area-extension community-contribution Indicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants