ErrLookup › Background articles › InvalidUserDataException: Gradle's invalid-user-data build failure — malformed notations, missing properties, and validation errors explained
InvalidUserDataException: Gradle's invalid-user-data build failure — malformed notations, missing properties, and validation errors explained
InvalidUserDataException is the build-time error thrown when user-supplied input fails structural validation: malformed group:name:version or capability notations in useTarget(), useModule(), and dependency substitution; wrong version-catalog accessors; missing signing properties; colliding subproject accessor names; artifact transform outputs that are missing, absolute, or the wrong file/dir shape; and component metadata rules that assume variants the target does not publish. Elasticsearch's documentation build reuses the same class for REST-test snippet errors such as orphaned // TEST and // TESTRESPONSE markers, misplaced TESTSETUP directives, and response JSON that fails to parse. In every documented case the fix is to correct the build script, property, or snippet you wrote, not the tool.
Distilled from 223 documented records across 3 repositories.
Background
InvalidUserDataException is the build tool telling you that your input is wrong, as opposed to the tool, the network, or the environment failing. Every documented trigger in this family is a defect in something a person wrote: a coordinate string in a build script, a gradle.properties entry, a settings.gradle subproject name, or (in Elasticsearch) a documentation snippet marked with // CONSOLE, // TEST, or // TESTSETUP directives. The exception exists to fail fast at a trust boundary: before an invalid coordinate is written into a published .module file, before a transform output that was never created gets cached and consumed, and before a docs example ships without its generated REST test.
Mechanically the family has two territories. In Gradle core, notation parsers (ModuleComponentSelectorParsers, CapabilityNotationParserFactory, ParsedModuleStringNotation) reject strings that do not split into non-empty group, name, and version parts; model validators reject colliding project-accessor names (DefaultDependenciesAccessors), non-conforming version catalog names (DefaultVersionCatalogBuilderContainer), absent signing properties (PgpSignatoryFactory), and toolchain properties set to conflicting values in two scopes; DefaultTransformOutputs verifies that every registered transform output exists, sits inside the transform workspace, and matches its declared file or directory shape; and component metadata handling allows only String and Boolean attributes on the legacy ComponentMetadataBuilder path and requires a resolvable base variant for non-lenient addVariant rules. In Elasticsearch's docs build the same exception class enforces snippet grammar: directives must attach to an open snippet, TESTSETUP must be first in its file, // TEST[continued] cannot follow a setup or teardown snippet, TESTRESPONSE JSON must parse after variable substitution, and the converted/unconverted snippet accounting must match what the docs actually contain.
Timing varies within the family, which matters when debugging. Many checks fire eagerly at configuration or parse time, but several fire lazily: the flatDir empty-dirs check runs inside createRealResolver() only when a configuration is actually resolved, non-lenient addVariant(name, base) rules surface during metadata realisation rather than rule execution, and transform output validation runs only after the transform body returns. A build can therefore pass one phase and fail later, with the real cause sitting in an earlier declaration. From the caller's side it is always a hard build failure; the message usually names the offending value, often with a worked example of the expected format, or lists exactly which files, variants, or properties are at fault.
Treat the message text as a strong hint rather than a contract. The records document several message-level defects: the '// TEST[continued] cannot immediately follow // TEARDOWN' guard calls testSetup() where testTearDown() was presumably intended, so it fires under the TESTSETUP condition; the missing-teardown message embeds a literal, un-interpolated '$name'; the toolchain property message labels the system-property value as the 'Gradle property'; and the attribute-type message contains a typo ('have been provider by'). One record's guard on detached configurations reportedly raises InvalidUserCodeException rather than InvalidUserDataException, so the exact class is library- and code-path-specific. When a label contradicts what you know you wrote, verify against the source instead of assuming the message is right.
Common causes
- Doc snippet structure violations (Elasticsearch).// TEST, // TESTRESPONSE, and // NOTCONSOLE directives must attach to an open // CONSOLE snippet; TESTSETUP must be the first snippet in its file; // TEST[continued] cannot immediately follow a setup or teardown snippet; response snippets must pair with an in-file request. Orphaned or misordered markers are the dominant docs-build trigger.
- Malformed coordinate or capability notation.Strings passed to useTarget(), useModule(), substitute(module(...)), or capability declarations must split into non-empty group, name, and (in most contexts) version parts. Version-less 'org.foo:bar', empty segments like 'org.foo::1.0', single-segment names, and four-part strings with classifiers are all rejected at parse time.
- Artifact transform output mismatches.outputs.file()/dir() declarations must use relative paths, must be produced on every code path, and must match the declared shape. A registered file that is actually a directory (or the reverse), an output that was never written because a branch skipped it, or an absolute path outside the transform workspace all fail validation after the transform runs.
- Build input out of sync with reality.Stale expectedUnconvertedCandidates entries after snippets are converted or files renamed, teardown names referenced in docs but never registered on the task, or a flatDir repository whose dirs list is empty at configuration time. The build's bookkeeping no longer matches what exists on disk.
- Component metadata rule assumptions.Rules depend on variants the target does not publish: project dependencies resolving to unpublished custom configurations, non-lenient addVariant(name, base) with a base variant missing from the module (surfacing at resolve time, after the rule ran), and non-String/non-Boolean attributes set through the legacy ComponentMetadataBuilder path.
- Missing or mistyped required properties.The signing plugin requires signing.keyId, signing.password, and signing.secretKeyRingFile, resolved case-sensitively from project properties, so 'signing.keyid' or properties absent in CI fail. Toolchain keys set as both a Gradle property and a systemProp entry with different values also fail.
- Version catalog misuse.useTarget()/useModule() accept only dependency accessors (libs.group.name); version accessors (libs.versions.foo), bundles, and plugin aliases cannot become a ModuleComponentSelector. Catalog names themselves must match [a-z][a-zA-Z0-9]*, so 'my-libs' is rejected.
- Subproject name normalization collisions.Sibling subprojects such as :foo-bar and :foo.bar normalize to the same Java accessor name because '-', '.', '_', and ':' are all stripped during conversion, so projects.fooBar becomes ambiguous and accessor generation aborts.
What usually fixes it
- Read the message as a specification: it names the offending value and usually shows the expected format with a worked example (e.g. 'org.gradle:gradle-core:1.0'). Make your input match that shape exactly — non-empty group, name, and version, no extra segments — and assert non-blank parts when you build notations from variables at runtime.
- Keep declarations synchronized with what exists: remove allowlist entries when snippets are converted, update paths after file renames, register every teardown name in the build, and treat any rename as a change to both the artifact and its list.
- Make transform outputs unconditional and relative: register outputs at the top of transform(), write every declared path on every code path (emit a valid empty artifact when there is nothing to do), and never derive outputs from project.file(), java.io.tmpdir, or other absolute prefixes.
- Prefer the lenient or purpose-built API: maybeAddVariant over addVariant when the base may be missing, requireFeature("name") for feature selection instead of capability notation, dependency accessors with real versions instead of libs.versions.* or libs.bundles.* as selectors, and guard optional stages such as signing with onlyIf { project.hasProperty('signing.keyId') }.
- For documentation builds, keep directives physically attached to their snippets — setup first, continued snippets after a base request, responses paired in the same file — and run the docs build task locally before pushing so structural errors surface in your working tree instead of CI.
- When a message's label contradicts what you know you wrote, check the source branch before trusting it: several messages in this family have documented defects, including a guard that checks the wrong flag, an un-interpolated variable, and swapped property labels.
Go deeper
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Documented occurrences
- // TEST[continued] cannot immediately follow // TESTSETUP: ${test}(elastic/elasticsearch)
- // TEST[continued] cannot immediately follow // TEARDOWN: ${test}(elastic/elasticsearch)
- Expected unconverted snippets but none found in: {listedButNotFound}(elastic/elasticsearch)
- Could not resolve coordinates for variant '%s' of project '%s'.(gradle/gradle)
- Invalid format: '{}'. Group, name and version cannot be empty. Correct example: 'org.gradle:gradle-core:1.0'(gradle/gradle)
- Invalid format for capability: '{}'. The correct notation is a 3-part group:name:version notation, e.g: 'org.group:capability:1.0'(gradle/gradle)
- Cannot generate project dependency accessors because {}(gradle/gradle)
- Invalid attributes types have been provider by component metadata supplier. Attributes must either be strings or booleans(gradle/gradle)
- Invalid test marker: {line}(elastic/elasticsearch)
- Unexpected unconverted snippets: {foundButNotListed}(elastic/elasticsearch)
- Invalid json in {name}. The error is: {errorMessage}. After substitutions and munging, the json looks like: {quoted}(elastic/elasticsearch)
- Couldn't find named teardown $name for ${snippet}(elastic/elasticsearch)
- Transform output %s must exist.(gradle/gradle)
- ${snippet}: wasn't first. TESTSETUP can only be used in the first snippet of a document.(elastic/elasticsearch)
- Cannot convert a version catalog entry '{}' to an object of type ModuleComponentSelector. Only dependency accessors are supported but not plugin, bundle or version accessors for '{}'.(gradle/gradle)
- Invalid model name '${name}': it must match the following regular expression: [a-z]([a-zA-Z0-9])+(gradle/gradle)
- property '{}' could not be found on project and is needed for signing(gradle/gradle)
- TEST not paired with a snippet at (elastic/elasticsearch)
- TESTRESPONSE not paired with a snippet at (elastic/elasticsearch)
- The Gradle property '<propertyName>' (set to '<systemProperty>') has a different value than the project property '<propertyName>' (set to '<projectProperty>'). Please set them to the same value or only set the Gradle property.(gradle/gradle)
…and 203 more across the corpus — use search.
Honest provenance: generated on 2026-08-22 from AI-assisted analysis of the linked records. See how records are made.