{"record":{"id":"60263c6e3391d51e","repo":"gradle/gradle","slug":"artifact-file-is-a-directory-s","errorCode":null,"errorMessage":"artifact file is a directory: '%s'","messagePattern":"artifact file is a directory: '(.+?)'","errorType":"validation","errorClass":"InvalidIvyPublicationException","httpStatus":null,"severity":"error","filePath":"platforms/software/ivy/src/main/java/org/gradle/api/publish/ivy/internal/publisher/ValidatingIvyPublisher.java","lineNumber":145,"sourceCode":"                        \"multiple artifacts with the identical name, extension, type and classifier ('%s', %s', '%s', '%s').\",\n                        name, extension, type, classifier\n                );\n                throw new InvalidIvyPublicationException(publication.getName(), message);\n            }\n        }\n    }\n\n    private boolean hasCoordinates(IvyArtifact one, String name, String extension, String type, String classifier) {\n        return Objects.equals(one.getName(), name)\n                && Objects.equals(one.getType(), type)\n                && Objects.equals(one.getExtension(), extension)\n                && Objects.equals(one.getClassifier(), classifier);\n    }\n\n    private void checkCanPublish(String name, IvyArtifact artifact) {\n        File artifactFile = artifact.getFile();\n        if (artifactFile.isDirectory()) {\n            throw new InvalidIvyPublicationException(name, String.format(\"artifact file is a directory: '%s'\", artifactFile));\n        }\n    }\n\n    private IvyFieldValidator field(IvyNormalizedPublication publication, String name, String value) {\n        return new IvyFieldValidator(publication.getName(), name, value);\n    }\n\n    private static class IvyFieldValidator extends PublicationFieldValidator<IvyFieldValidator> {\n        private IvyFieldValidator(String publicationName, String name, String value) {\n            super(IvyFieldValidator.class, publicationName, name, value);\n        }\n\n        public IvyFieldValidator matches(String expectedValue) {\n            if (!value.equals(expectedValue)) {\n                throw new InvalidIvyPublicationException(publicationName,\n                        String.format(\"supplied %s does not match ivy descriptor (cannot edit %1$s directly in the ivy descriptor file).\", name)\n                );\n            }","sourceCodeStart":127,"sourceCodeEnd":163,"githubUrl":"https://github.com/gradle/gradle/blob/534f27719b66953f95cc907aae7f2c1b12f5482d/platforms/software/ivy/src/main/java/org/gradle/api/publish/ivy/internal/publisher/ValidatingIvyPublisher.java#L127-L163","documentation":"Gradle's Ivy publishing pipeline validates every artifact immediately before upload. ValidatingIvyPublisher.checkCanPublish rejects any artifact whose File is a directory, because an Ivy repository coordinate must point to a downloadable regular file.","triggerScenarios":"An IvyPublication artifact whose file resolves to a directory: publication.artifact(someDir), IvyArtifact.setFile(dir), or attaching a task whose output is a directory (Copy/Sync destinationDir, sourceSets.main.output) instead of an archive output like the Jar task's archiveFile.","commonSituations":"Pointing the artifact at the resources or classes directory instead of the packaged jar; build logic migrated from the old artifacts{...} syntax where builtBy pointed at a directory-producing task; scripts that publish a folder of native libs directly.","solutions":["Attach a file-producing task or its output instead of a directory: publication.artifact(tasks.jar)","If the content is a directory, package it first with a Zip/Jar task and publish that archive","Add a doFirst guard on the publish task that fails fast when any artifact file is a directory"],"exampleFix":"// before\npublication.artifact(file(\"$buildDir/resources/main\")) // a directory\n\n// after\npublication.artifact(tasks.jar) // regular file produced by the build","handlingStrategy":"validation","validationCode":"tasks.withType(PublishToIvyRepository).configureEach {\n    doFirst {\n        publication.artifacts.each { a ->\n            if (a.file != null && a.file.isDirectory()) {\n                throw new GradleException(\"Ivy artifact '${a.file}' is a directory - attach a file (e.g. tasks.jar) instead\")\n            }\n        }\n    }\n}","typeGuard":null,"tryCatchPattern":"try {\n    project.tasks.named('publish').get().publish()\n} catch (org.gradle.api.publish.ivy.InvalidIvyPublicationException e) {\n    // message names the offending directory; fix the artifact definition, do not retry blindly\n}","preventionTips":["Always attach Ivy artifacts via task outputs (tasks.jar, custom Zip tasks) rather than raw File instances","For directory content, define an archive task and publish its output","Run a publishToMavenLocal-equivalent smoke publish in CI to catch artifact wiring errors before release"],"tags":["gradle","ivy","publishing","artifact","file-validation"],"backgroundTag":"invalid-artifact-path","analyzedSha":"534f27719b66953f95cc907aae7f2c1b12f5482d","analyzedAt":"2026-08-22T08:09:12.375Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}