{"record":{"id":"4a99d64f6651a984","repo":"microsoft/aspire","slug":"the-java-apphost-project-in-projectdirectory-fullname","errorCode":null,"errorMessage":"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.","messagePattern":"The Java AppHost project in '(.+?)' declares a (.+?) build but ships no (.+?), and none was found at an enclosing build root\\. Generate one with '(.+?)', or remove the build file to build the AppHost with javac instead\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/Aspire.Cli/Projects/JavaAppHostToolchainResolver.cs","lineNumber":312,"sourceCode":"        DirectoryInfo appHostDirectory,\n        JavaAppHostToolchain toolchain)\n    {\n        var (wrapperName, generateCommand) = toolchain switch\n        {\n            // -N keeps the goal from recursing into the modules of a multi-module build.\n            JavaAppHostToolchain.Maven => (OperatingSystem.IsWindows() ? \"mvnw.cmd\" : \"mvnw\", \"mvn -N wrapper:wrapper\"),\n            JavaAppHostToolchain.Gradle => (OperatingSystem.IsWindows() ? \"gradlew.bat\" : \"gradlew\", \"gradle wrapper\"),\n            _ => throw new ArgumentOutOfRangeException(nameof(toolchain), toolchain, null)\n        };\n\n        var wrapperPath = FindWrapper(projectDirectory, wrapperName, toolchain);\n\n        // A globally installed Maven or Gradle is deliberately not used as a fallback: the wrapper pins the\n        // tool version in the repository, so every machine builds the AppHost with the same one. Falling\n        // back silently would make the AppHost build depend on whatever the developer happens to have.\n        if (wrapperPath is null)\n        {\n            throw new InvalidOperationException(\n                $\"The Java AppHost project in '{projectDirectory.FullName}' declares a {GetDisplayName(toolchain)} \" +\n                $\"build but ships no {wrapperName}, and none was found at an enclosing build root. Generate one \" +\n                $\"with '{generateCommand}', or remove the build file to build the AppHost with javac instead.\");\n        }\n\n        var wrapperDirectory = new DirectoryInfo(Path.GetDirectoryName(wrapperPath)!);\n\n        if (!OperatingSystem.IsWindows())\n        {\n            // Invoked through \"sh\" rather than executed directly because a wrapper checked out on\n            // Windows, or committed without its mode bit, arrives without the executable bit and\n            // exec fails with \"Permission denied\". The wrappers are POSIX shell scripts and are\n            // documented to be run that way, so \"sh <path>\" is always valid. This matches how the\n            // hosted Java resources invoke wrappers (JavaHostingExtensions.WrapperInvocationFor).\n            //\n            // The absolute path is kept because the process is started without a shell, so a bare\n            // \"mvnw\" would be looked up on PATH and never found in the project directory.\n            return new JavaToolInvocation(\"sh\", [wrapperPath], wrapperDirectory);","sourceCodeStart":294,"sourceCodeEnd":330,"githubUrl":"https://github.com/microsoft/aspire/blob/25830f84bd145686607ad00c057b3f84e2e51d43/src/Aspire.Cli/Projects/JavaAppHostToolchainResolver.cs#L294-L330","documentation":"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.","triggerScenarios":"Calling GetToolInvocation for a Java AppHost directory containing a pom.xml/build.gradle but no matching wrapper script anywhere up the directory tree.","commonSituations":"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.","solutions":["Run 'mvn wrapper:wrapper' (Maven) or 'gradle wrapper' (Gradle) in the AppHost project directory to generate the wrapper","Commit the generated mvnw/gradlew scripts and .mvn/gradle wrapper properties to the repository","Remove the pom.xml/build.gradle file if you intentionally want the AppHost built with plain javac instead"],"exampleFix":"// before: pom.xml present, no wrapper\n$ aspire run  # -> error: ships no wrapper\n// after\n$ cd <AppHostProjectDir>\n$ mvn wrapper:wrapper\n$ git add mvnw .mvn/ && git commit","handlingStrategy":"validation","validationCode":"var wrapper = toolchain == Maven ? \"mvnw\" : \"gradlew\";\nif (!File.Exists(Path.Combine(appHostDir, wrapper)) && !FindUpward(appHostDir, wrapper, out _))\n    throw new InvalidOperationException($\"Run '{genCmd}' in {appHostDir} before invoking the CLI\");","typeGuard":"bool HasWrapper(DirectoryInfo dir) => File.Exists(Path.Combine(dir.FullName, \"mvnw\")) || File.Exists(Path.Combine(dir.FullName, \"gradlew\"));","tryCatchPattern":"try { var invocation = resolver.GetToolInvocation(projectDir, ...); }\ncatch (InvalidOperationException ex) when (ex.Message.Contains(\"ships no\"))\n{\n    // prompt user to generate the wrapper\n}","preventionTips":["Always generate and commit mvnw/gradlew with the repository","Don't gitignore wrapper scripts or .mvn/gradle-wrapper.properties","Check for the wrapper before scripting CI that invokes the AppHost"],"tags":["java","maven","gradle","build-tool","wrapper"],"backgroundTag":"missing-dependency","analyzedSha":"25830f84bd145686607ad00c057b3f84e2e51d43","analyzedAt":"2026-09-16T11:10:06.193Z","contentChangedAt":"2026-09-16T11:10:06.193Z","schemaVersion":2},"datasetVersion":"2026-09-21T04:17:39.646Z"}