Gradle recently introduced an API for lazy configuration and now internal functionality like plugin extensions and task types are migrated to this new API. In summary, it can be stated that (almost) every configuration property in the build script should be realized using Property<T> or Provider<T> (read-only) instead of just a getter and setter of the simple type T. To provide backward compatibility, a lot of properties are not changed, but new properties are added and the old properties read from and write to these new properties.
This is also the case for your problem, since the task type BootRun from the Spring Boot Gradle Plugin extends the JavaExec task type provided by Gradle. The new property mainClass (a Property<string>) was added and the old property main was modified to use the new property. The method getMain() reads from mainClass using Property.get() and the method setMain(String), which is invoked when using the syntax main = '...' in Groovy, writes to mainClass using Property.set(String).
This would all work fine, but Gradle introduced some additional features to their lazy configuration APIs. One of these features is the finalization of writable properties (Property<>). At some point in the build, a task property (e.g. mainClass) will be read for its original purpose (running the task), so any changes after that point won't have effect. In earlier versions of Gradle, this caused a lot of difficult to debug problems, because Gradle would not show any error. Now those properties are finalized once they are read for their original purpose, causing Gradle to fail when someone tries to change them afterwards.
Regarding your use case, the property mainClass (accessed by main) is already finalized in the doFirst closure, so you need to apply this configuration earlier. Since you want to set the value to the value of the property bootJar.mainClassName (which is a simple String), you must ensure that this property has its final value before it is read for configuring the bootRunB task:
bootJar {
mainClassName = '...'
}
task bootRunB(type: org.springframework.boot.gradle.tasks.run.BootRun, dependsOn: 'build') {
group = 'Application'
main = bootJar.mainClassName
// From this point, bootRun.classpath must not be changed !
classpath = bootRun.classpath
systemProperty '...'
}
To eliminate this dependency on the configuration order, you may create a Provider<String> using Project.provider(...):
task bootRunB(type: org.springframework.boot.gradle.tasks.run.BootRun, dependsOn: 'build') {
group = 'Application'
main = provider({ bootJar.mainClassName })
classpath = bootRun.classpath
systemProperty '...'
}