{"record":{"id":"4e50a85d3631cd23","repo":"halo-dev/halo","slug":"invalid-file-type-only-jar-is-supported","errorCode":null,"errorMessage":"Invalid file type, only jar is supported","messagePattern":"Invalid file type, only jar is supported","errorType":"validation","errorClass":"ServerWebInputException","httpStatus":400,"severity":"error","filePath":"application/src/main/java/run/halo/app/core/endpoint/console/PluginEndpoint.java","lineNumber":662,"sourceCode":"\n        private final MultiValueMap<String, Part> multipartData;\n\n        public InstallRequest(MultiValueMap<String, Part> multipartData) {\n            this.multipartData = multipartData;\n        }\n\n        /** Plugin JAR file. */\n        @Schema(requiredMode = NOT_REQUIRED)\n        public FilePart getFile() {\n            var part = multipartData.getFirst(\"file\");\n            if (part == null) {\n                throw new ServerWebInputException(\"Form field file is required\");\n            }\n            if (!(part instanceof FilePart file)) {\n                throw new ServerWebInputException(\"Invalid parameter of file\");\n            }\n            if (!Paths.get(file.filename()).toString().endsWith(\".jar\")) {\n                throw new ServerWebInputException(\"Invalid file type, only jar is supported\");\n            }\n            return file;\n        }\n\n        /** Plugin preset name. Halo finds the plugin from plugin presets. */\n        @Schema(requiredMode = NOT_REQUIRED)\n        public Mono<String> getPresetName() {\n            var part = multipartData.getFirst(\"presetName\");\n            if (part == null) {\n                return Mono.error(new ServerWebInputException(\"Form field presetName is required.\"));\n            }\n            if (!(part instanceof FormFieldPart presetName)) {\n                return Mono.error(new ServerWebInputException(\"Invalid format of presetName field, string required\"));\n            }\n            if (!StringUtils.hasText(presetName.value())) {\n                return Mono.error(new ServerWebInputException(\"presetName must not be blank\"));\n            }\n            return Mono.just(presetName.value());","sourceCodeStart":644,"sourceCodeEnd":680,"githubUrl":"https://github.com/halo-dev/halo/blob/d2f5165f9c8f055ffcb3fa9c3f4032821a7b68c8/application/src/main/java/run/halo/app/core/endpoint/console/PluginEndpoint.java#L644-L680","documentation":"Thrown as a ServerWebInputException (HTTP 400) by the plugin install endpoint's InstallRequest.getFile() when the uploaded multipart part named 'file' exists and is a FilePart but its filename does not end with '.jar'. Halo only accepts Java plugin archives, so any other extension (zip, tar, no extension) is rejected before the install pipeline runs.","triggerScenarios":"POST /apis/api.console.halo.run/v1alpha1/plugins/install with a multipart/form-data body whose 'file' field is a non-.jar file (e.g. plugin.zip, plugin.tar.gz, screenshot.png, or a jar with trailing case differences like .JAR on a case-sensitive path check). Paths.get(file.filename()).toString().endsWith(\".jar\") is case-sensitive, so '.JAR' or '.Jar' will also fail.","commonSituations":"User zips the plugin source instead of building the jar; plugin author distributes a .zip that must be unpacked; Windows user with hidden extensions uploads 'plugin.jar.zip'; case mismatch from a renamed file; plugin built but renamed to '.jar.bak' then re-renamed incorrectly.","solutions":["Re-package the plugin as a .jar (run './gradlew build' and grab the jar from build/libs), then upload the artifact whose filename literally ends in lowercase '.jar'.","Check the exact filename your client sends (including hidden extensions and case) and rename it so it ends with '.jar'.","If installing from a preset or URL instead, set the 'source' form field to PRESET or URL and provide presetName/url instead of the file part."],"exampleFix":"// before: client sends plugin.zip as the 'file' part\n// after: build and send the jar\n//   ./gradlew :build\n//   upload build/libs/my-plugin-1.0.0.jar with field name 'file'","handlingStrategy":"validation","validationCode":"// before building the install request, check the file's name\nString name = selectedFile.getName();\nif (!name.toLowerCase(Locale.ROOT).endsWith(\".jar\")) {\n    throw new IllegalArgumentException(\"Select a .jar plugin archive, got: \" + name);\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Gate the upload UI to accept only .jar files via the file input's accept='.jar' attribute.","Run the plugin build ('./gradlew build') and upload the artifact from build/libs, never a source zip."],"tags":["plugin","upload","validation","multipart","webflux"],"backgroundTag":null,"analyzedSha":"d2f5165f9c8f055ffcb3fa9c3f4032821a7b68c8","analyzedAt":"2026-08-14T00:18:38.915Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}