gradle/gradle · error · InvalidUserCodeException

The closure provided is not valid as a rule for '%s'.

Error message

The closure provided is not valid as a rule for '%s'.

What it means

DefaultRuleActionAdapter.createFromClosure wraps any RuleActionValidationException raised while building a closure-based rule action into an InvalidUserCodeException: 'The closure provided is not valid as a rule for <context>'. The context string names the rule container/DSL where the closure was declared. The real reason is in the cause: typically a wrong first-parameter type or an input parameter of an unsupported type.

Source

Thrown at platforms/software/dependency-management/src/main/java/org/gradle/internal/rules/DefaultRuleActionAdapter.java:45

    @SuppressWarnings("InlineFormatString")
    private static final String INVALID_ACTION_ERROR = "The action provided is not valid as a rule for '%s'.";
    @SuppressWarnings("InlineFormatString")
    private static final String INVALID_RULE_SOURCE_ERROR = "The rule source provided does not provide a valid rule for '%s'.";

    private final RuleActionValidator ruleActionValidator;
    private final String context;

    public DefaultRuleActionAdapter(RuleActionValidator ruleActionValidator, String context) {
        this.ruleActionValidator = ruleActionValidator;
        this.context = context;
    }

    @Override
    public <T> RuleAction<? super T> createFromClosure(Class<T> subjectType, Closure<?> closure) {
        try {
            return ruleActionValidator.validate(new ClosureBackedRuleAction<>(subjectType, closure));
        } catch (RuleActionValidationException e) {
            throw new InvalidUserCodeException(String.format(INVALID_CLOSURE_ERROR, context), e);
        }
    }

    @Override
    public <T> RuleAction<? super T> createFromAction(Action<? super T> action) {
        try {
            return ruleActionValidator.validate(new NoInputsRuleAction<>(action));
        } catch (RuleActionValidationException e) {
            throw new InvalidUserCodeException(String.format(INVALID_ACTION_ERROR, context), e);
        }
    }

    @Override
    @Deprecated
    public <T> RuleAction<? super T> createFromRuleSource(Class<T> subjectType, Object ruleSource) {
        try {
            return ruleActionValidator.validate(RuleSourceBackedRuleAction.create(ModelType.of(subjectType), ruleSource));
        } catch (RuleActionValidationException e) {

View on GitHub (pinned to 534f27719b)

Solutions

  1. Read the caused-by RuleActionValidationException in the stack trace to see the concrete signature problem
  2. Fix the closure's first parameter to match the rule subject type
  3. Restrict extra closure parameters to the input types allowed by that container (listed in the underlying validation message)
Defensive patterns

Strategy: try-catch

Try / catch

try {
    adapter.createFromClosure(subjectType, closure)
} catch (org.gradle.api.InvalidUserCodeException e) {
    def cause = e.cause
    if (cause instanceof org.gradle.internal.rules.RuleActionValidationException) {
        // fix the closure signature per cause.message
        throw new GradleException('Invalid rule closure: ' + cause.message, e)
    }
    throw e
}

Prevention

When it happens

Trigger: Adding a closure rule to a rule-based container (e.g. legacy native/software-model DSLs built on RuleActionAdapter) whose closure signature fails validation — bad subject parameter type (1247) or disallowed input parameter types (1251).

Common situations: Authoring or upgrading software-model-era build logic; migrating between Gradle versions where allowed rule input types changed; copy-pasting example closures into a different rule container.

Related errors


AI-assisted analysis of gradle/gradle@534f27719b (2026-08-22). Data as JSON: /api/errors/8cedf464944c92e7. Report an issue: GitHub.