gradle/gradle · error · IllegalArgumentException

%s is a non-static inner class.

Error message

%s is a non-static inner class.

What it means

Jsr330ConstructorSelector.validateType rejects non-static inner classes before any constructor selection runs: the generated decorator subclass cannot synthesize the implicit reference to an enclosing instance that a non-static inner class requires, so instantiation would be impossible. Interfaces, top-level classes, and static nested classes pass.

Source

Thrown at platforms/core-configuration/model-core/src/main/java/org/gradle/internal/instantiation/generator/Jsr330ConstructorSelector.java:71

        CachedConstructor constructor = constructorCache.get(type, () -> {
            try {
                validateType(type);
                ClassGenerator.GeneratedClass<?> implClass = classGenerator.generate(type);
                ClassGenerator.GeneratedConstructor<?> generatedConstructor = InjectUtil.selectConstructor(implClass, type);
                return CachedConstructor.of(generatedConstructor);
            } catch (RuntimeException e) {
                return CachedConstructor.of(e);
            }
        });
        return Cast.uncheckedCast(constructor.getConstructor());
    }

    private static <T> void validateType(Class<T> type) {
        if (!type.isInterface() && type.getEnclosingClass() != null && !Modifier.isStatic(type.getModifiers())) {
            TreeFormatter formatter = new TreeFormatter();
            formatter.node(type);
            formatter.append(" is a non-static inner class.");
            throw new IllegalArgumentException(formatter.toString());
        }
    }

    public static class CachedConstructor {
        private final ClassGenerator.GeneratedConstructor<?> constructor;
        private final RuntimeException error;

        private CachedConstructor(ClassGenerator.GeneratedConstructor<?> constructor, RuntimeException error) {
            this.constructor = constructor;
            this.error = error;
        }

        public ClassGenerator.GeneratedConstructor<?> getConstructor() {
            if (error != null) {
                throw error;
            }
            return constructor;
        }

View on GitHub (pinned to 534f27719b)

Solutions

  1. Declare the nested class static: public static class Helper.
  2. Move the class to top level.
  3. Model the type as an interface, which passes this check automatically.

Example fix

// before
class Outer {
    class Helper {} // carries implicit Outer.this
}
// after
class Outer {
    static class Helper {}
}
Defensive patterns

Strategy: type-guard

Type guard

static boolean isInjectable(Class<?> type) {
    return type.isInterface()
        || type.getEnclosingClass() == null
        || Modifier.isStatic(type.getModifiers());
}

Prevention

When it happens

Trigger: Passing Class<Helper> where Helper is a nested class without the static modifier to a JSR-330-based instantiator; validation runs once per type inside the constructor cache and the failure is replayed for every later attempt.

Common situations: Helper classes nested inside plugin classes for packaging convenience; top-level classes moved into nested positions without adding static; note that Kotlin nested classes are fine unless marked inner.

Related errors


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