microsoft/aspire · error · InvalidOperationException

The Java AppHost project in

Error message

The Java AppHost project in '{projectDirectory.FullName}' declares a {GetDisplayName(toolchain)} build but ships no {wrapperName}, and none was found at an enclosing build root. Generate one with '{generateCommand}', or remove the build file to build the AppHost with javac instead.

What it means

When a Java AppHost declares a Maven or Gradle build file, the toolchain resolver insists on using that tool's wrapper (mvnw/gw). If no wrapper exists in the project or any enclosing build root, resolution fails rather than silently falling back to a globally installed Maven/Gradle, so builds stay reproducible.

Solutions

  1. Run 'mvn wrapper:wrapper' (Maven) or 'gradle wrapper' (Gradle) in the AppHost project directory to generate the wrapper
  2. Commit the generated mvnw/gradlew scripts and .mvn/gradle wrapper properties to the repository
  3. Remove the pom.xml/build.gradle file if you intentionally want the AppHost built with plain javac instead

Example fix

// before: pom.xml present, no wrapper
$ aspire run  # -> error: ships no wrapper
// after
$ cd <AppHostProjectDir>
$ mvn wrapper:wrapper
$ git add mvnw .mvn/ && git commit
Defensive patterns

Strategy: validation

Validate before calling

var wrapper = toolchain == Maven ? "mvnw" : "gradlew";
if (!File.Exists(Path.Combine(appHostDir, wrapper)) && !FindUpward(appHostDir, wrapper, out _))
    throw new InvalidOperationException($"Run '{genCmd}' in {appHostDir} before invoking the CLI");

Type guard

bool HasWrapper(DirectoryInfo dir) => File.Exists(Path.Combine(dir.FullName, "mvnw")) || File.Exists(Path.Combine(dir.FullName, "gradlew"));

Try / catch

try { var invocation = resolver.GetToolInvocation(projectDir, ...); }
catch (InvalidOperationException ex) when (ex.Message.Contains("ships no"))
{
    // prompt user to generate the wrapper
}

Prevention

When it happens

Trigger: Calling GetToolInvocation for a Java AppHost directory containing a pom.xml/build.gradle but no matching wrapper script anywhere up the directory tree.

Common situations: Cloning a repo without wrapper files (gitignored or generated by a tool), scaffolding a Maven/Gradle project by hand and forgetting 'mvn wrapper:wrapper' or 'gradle wrapper', or running the CLI from a subdirectory whose parent lacks the wrapper.

Understand the failure class

Background: "not installed", "pip install", "required for": how missing-dependency errors surface across open-source libraries — this error's family across 34 libraries.

Related errors


AI-assisted analysis of microsoft/aspire@25830f84bd (2026-09-16). Data as JSON: /api/errors/4a99d64f6651a984. Report an issue: GitHub.

Appendix: source

Thrown at src/Aspire.Cli/Projects/JavaAppHostToolchainResolver.cs:312

        DirectoryInfo appHostDirectory,
        JavaAppHostToolchain toolchain)
    {
        var (wrapperName, generateCommand) = toolchain switch
        {
            // -N keeps the goal from recursing into the modules of a multi-module build.
            JavaAppHostToolchain.Maven => (OperatingSystem.IsWindows() ? "mvnw.cmd" : "mvnw", "mvn -N wrapper:wrapper"),
            JavaAppHostToolchain.Gradle => (OperatingSystem.IsWindows() ? "gradlew.bat" : "gradlew", "gradle wrapper"),
            _ => throw new ArgumentOutOfRangeException(nameof(toolchain), toolchain, null)
        };

        var wrapperPath = FindWrapper(projectDirectory, wrapperName, toolchain);

        // A globally installed Maven or Gradle is deliberately not used as a fallback: the wrapper pins the
        // tool version in the repository, so every machine builds the AppHost with the same one. Falling
        // back silently would make the AppHost build depend on whatever the developer happens to have.
        if (wrapperPath is null)
        {
            throw new InvalidOperationException(
                $"The Java AppHost project in '{projectDirectory.FullName}' declares a {GetDisplayName(toolchain)} " +
                $"build but ships no {wrapperName}, and none was found at an enclosing build root. Generate one " +
                $"with '{generateCommand}', or remove the build file to build the AppHost with javac instead.");
        }

        var wrapperDirectory = new DirectoryInfo(Path.GetDirectoryName(wrapperPath)!);

        if (!OperatingSystem.IsWindows())
        {
            // Invoked through "sh" rather than executed directly because a wrapper checked out on
            // Windows, or committed without its mode bit, arrives without the executable bit and
            // exec fails with "Permission denied". The wrappers are POSIX shell scripts and are
            // documented to be run that way, so "sh <path>" is always valid. This matches how the
            // hosted Java resources invoke wrappers (JavaHostingExtensions.WrapperInvocationFor).
            //
            // The absolute path is kept because the process is started without a shell, so a bare
            // "mvnw" would be looked up on PATH and never found in the project directory.
            return new JavaToolInvocation("sh", [wrapperPath], wrapperDirectory);

View on GitHub (pinned to 25830f84bd)