Java-gradle-plugin gradleApi() api dependency problem

By the document:
the plugin java-gradle-plugin adds the gradleApi() dependency to the api configuration.

I developed a Gradle plugin by java-gradle-plugin:

and I provide an example to use the plugin:

However, I find if I remove the line implementation gradleApi() from example/build.gradle, the example will run (./gradlew :example:run) to fail with the errors

Exception in thread "main" java.lang.NoClassDefFoundError: org/gradle/tooling/GradleConnector
        at com.github.MrRogerHuang.ExampleKt.main(Example.kt:6)
        at com.github.MrRogerHuang.ExampleKt.main(Example.kt)
Caused by: java.lang.ClassNotFoundException: org.gradle.tooling.GradleConnector
        at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(
        at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(
        at java.base/java.lang.ClassLoader.loadClass(
        ... 4 more

I check build/publish-generated-resources/pom.xml generated by ./gradlew publishPlugins and find there is no dependencies tag related to any Gradle API. Why does java-gradle-plugin add gradleApi() api dependency, but not generate any dependencies tag to pom.xml?

The example project should only need to depend on org.gradle:gradle-tooling-api, instead of gradleApi().
$toolingApiVersion should be a version chosen by you.

Thanks. Yes, I agree only org.gradle:gradle-tooling-api is needed. However, I think that org.gradle:gradle-tooling-api is part of gradleApi() and java-gradle-plugin adds the api gradleApi() to my plugin, thus gradleApi() would be transitively exposed to the example implementing the plugin, isn’t it? With the transitive expose, the example build.gradle could be simplified by removing the an explicitly dependency to either org.gradle:gradle-tooling-api or gradleApi(). But in my example run, it looks like api gradleApi() added by java-gradle-plugin is not transitive…

gradleAPI() is not intended to be a published set of artifacts that you depend on in your plugin’s pom. The API is provided by Gradle at runtime of your plugin. Your plugin can be applied to projects running different versions of Gradle and that specific version’s API will be made available on the plugin’s classpath. You need to be aware about what features of the Gradle API you use in your plugin (you maintain the compatibility). If the plugin uses something added in Gradle 6 and the plugin is used in a Gradle 5 project, it will fail.

The model interfaces should not contain Gradle API classes. This maintains an abstraction between the consumer of the model and any Gradle version specific API details. My personal suggestion is that you split the model classes into their own project. This helps enforce the abstraction and prevents the model from accidentally using Gradle API classes.

In summary:

  • Plugin project
    • Depends on gradleAPI() and Model
  • Model project
  • Application/Consumer project
    • Depends on gradle-tooling-api and Model.

Thanks for your information and suggestions.