Lex Manos
02/19/2026, 4:57 AMtasks.named(name, type){} Intellij correctly determines the closure type. When held in a variable it does not.
See attached screenshot
Both configuration handlers work (the json is printed twice)
However when i try to do the same thing that tasks.named does with my own code I run into issues.
Long story short, I am trying to write a helper method that registers multiple tasks.
this.json = tasks.register(name + "Json", InstallerJson.class);
public TaskProvider<@NotNull InstallerJson> getJson() {
return this.json;
}
public void json(Action<? super InstallerJson> action) {
getJson().configure(action);
}
When I do:
inst.json {
prop = value
}
IntelliJ sees the argument type correctly, but I get Could not set unknown property 'prop' for root project 'root' of type org.gradle.api.Project.
When I do:
inst.json.configure {
prop = value
}
IntelliJ says the argument is of type T but it executes correctly.
Any suggestions on how to make both sides happy?Vampire
02/19/2026, 9:53 AMinst yourself but let Gradle create it.
One of the magic things Gradle does is creating Closure overloads for Action taking methods that behave like you expect.
With
abstract class InstallerJson extends DefaultTask {
@Input
abstract Property<String> getProp()
@TaskAction
void execute() {
}
}
class Inst {
final TaskProvider<InstallerJson> json
@Inject
Inst(TaskContainer tasks) {
this.json = tasks.register('name' + 'Json', InstallerJson.class)
}
TaskProvider<InstallerJson> getJson() {
return this.json
}
void json(Action<? super InstallerJson> action) {
getJson().configure(action)
}
}
def inst = new Inst(tasks)
you have to use it as the Action method is used, so you need to do
inst.json {
it.prop = 'value'
}
By changing def inst = new Inst(tasks) to def inst = objects.newInstance(Inst), you can successfully do
inst.json {
prop = 'value'
}
Otherwise you probably need an own Closure overload, but you should really always let Gradle instantiate things, especially things that might be used by a consumer in a build script. You then also for example automatically have ExtensionAware implemented (and best is to declare it explicitly so that non-Gradle consumers do not need to cast the instance) and can inject various Gradle services you might need, or let Gradle provide various abstract method or field implementations like for Property properties and so on.Lex Manos
02/19/2026, 7:14 PMVampire
02/20/2026, 10:18 AMIs there any documentation on what exactly that magic function adds to the class?None I'm aware of, but for best developer and user experience you should always use it. Some things I have in mind that it does: • Implement
ExtensionAware so that you can set ext / extra properties on it, and add other extensions to dynamically extend the DSL. This is done even if you don't declare ExtensionAware but best is you do so that someone that wants to use it does not need to cast it
• Care for proper Closure usage of Action methods if not done manually
• Inject various services like ObjectFactory, Project, ProviderFactory, TaskContainer, and many more (no complete list available, doc bug pending)
• Implement for you Property properties and friends (managed types)
Changing from a record, to a normal class, and using object.newInstance did indeed solve the immediate issue, but I'd like to know whyBecause the decoration automatically adds a
Closure overload if no manual one is present so that Action methods are conveniently usable by Groovy consumers.
Coming from actual Java its very weird to have to run everything through a magic function.Gradle is written in Java and best way to write build logic also is Java, so this is just normal Java. It might not be an idiom that you are used to, but it still is pure Java. There are also many similar things like using decorators or using annotation processing to generate things and so on that are also all coming from pure Java.
or figure out how to implement the magic Closure method myself.There is the internal
ConfigureUtil that could be used for that iirc, but ... well ... it is internal so could break with any minor version update of Gradle.
You can probably also just manually do it by writing the overload, handling it properly inside, maybe wrapping it in an Action that forwards to the closure and give it to the Action method or similar, adding IDE-helping annotations like @DelegatesTo so that the IDE knows to what type the Closure will delegate and thus provide proper IntelliSense, ...
But it imho really is preferable to just follow the best practices and let Gradle instantiate stuff that eventually ends up in the build script of a user, also for ExtensionAware and so on, and then you can also not do it wrongly or forget it somewhere.
I would much prefer the second option.Really, do yourself and your users a favor and use the best practice. 🙂
I'm also assuming there is no static instance of ObjectFactory (or other services) available anywhere. Is this correct?Yes, none I'm aware of. And it is usually not necessary, as in best practice world, you can just let Gradle inject it.
It also doesn't explain why intellij doesn't like the first configure call in my screenshot.I'm not sure, might be a bug somewhere. 🤷♂️
Changing from a recordUsing a record in something that might end up in the build script is anyway a very bad idea, because in the record you will not have lazy types. In the DSL-surface you should practically only ever use
Property and friends (Property, RegularFileProperty, DirectoryProperty, ConfigurableFileCollection, ...), especially with properties that are only needed at execution time, so that a user can properly wire in other `Provider`s, so that you get out of the typical afterEvaluate ordering problems, timing problems, and race conditions, because one can properly wire `Provider`s to `Property`s and also preserve task dependencies that way if used properly.
So for example instead of
public record Type(String prop) { }
you would have
public interface Type {
Property<String> getProp();
}
and that's it.
You don't need to provide an implementation but just use objectFactory.newInstance(Type::class) and Gradle cares about the rest.Remi Gelinas
02/21/2026, 2:13 AMVampire
02/21/2026, 7:42 AMProperty<Task>?