Transient dependencies from sibling subproject roject missing

I’m using 7.3.1 with the java plugin (not the java-library plugin).

I have a subproject (org.jgrapes.http, “C” for short) with an implementation dependency on another (sibling) subproject (org.jgrapes.io, “B” for short).

dependencies {
    implementation project(':org.jgrapes.io')
    implementation 'org.jdrupes.httpcodec:httpcodec:[2.0.3,3)'
}

The other subproject (org.jgrapes.io, “B”) has an implementation dependency (defined in the same way) on yet another (sibling) subproject (org.jgrapes.core, “A” for short).

So we have “C” --implementation–> “B” --implementation–> “A”.

Trying to build (compile) org.jgrapes.http (“C”) fails because the classes defined in the indirectly dependent project (org.jgrapes.core, “A”) are not on the compileClasspath. Output from dependencies:

compileClasspath - Compile classpath for source set 'main'.
+--- org.osgi:org.osgi.annotation:6.0.0
+--- project :org.jgrapes.io
\--- org.jdrupes.httpcodec:httpcodec:[2.0.3,3) -> 2.0.3
     \--- com.sun.activation:javax.activation:1.2.0

Note that the transient dependencies can be found on the runtimeClasspath:

runtimeClasspath - Runtime classpath of source set 'main'.
+--- project :org.jgrapes.io
|    +--- project :org.jgrapes.core
|    \--- project :org.jgrapes.util
|         +--- project :org.jgrapes.core
|         \--- org.jdrupes.json:json:[2.0.0,2.1.0) -> 2.0.1
|              \--- com.fasterxml.jackson.core:jackson-core:2.9.4
\--- org.jdrupes.httpcodec:httpcodec:[2.0.3,3) -> 2.0.3
     \--- com.sun.activation:javax.activation:1.2.0

Is this a bug or am I missing something?

This is exactly how it is designed / how you configured it.

implementation means the dependency is only used in your implementation, not on your API, so only in method bodies of signatures of private or package private methods.

If you use the dependency in your API, like in signatures of public or protected methods or as super classes, you should declare the dependency as api dependency instead.

If you simply want to use the classes of A in C, it is bad practice to just use it transitively. Declare an explicit dependency instead.

This somehow leaves the impression that the ‘java-library’ plugin should be used by default and ‘java’ remains there for very special cases only (can’t really think of one).

Yes, if you write a library use java-library, hence the name. java is more for end product, but usually a different plugin on top is more suitable like application for example.

There’s a big problem with terminology here. My subprojects most definitely aren’t libraries. They aren’t applications either. I think of them as components. They contribute to a larger system, but only use APIs of that larger system (very much like plugins) they don’t provide any API (as libraries do, think of slf4j-api as a very good example).

As I can get transient dependencies only for “api” I now have to move everything from “implementation” to “api”. Of course I can do this, but it it doesn’t reflect my project any more, because everything I provide with it is “implementation”. So “api” simply doesn’t make sense in my context, it becomes an unfitting alias for “implementation-transient”.

I would have expected that “implementation” dependencies are handled differently by the different plugins. The “java-library” plugin handles them as non-transient, which makes sense, given that there is also a distinguished “api”. But the “java” plugin should handle them as transient (as “compile” did in the older versions of the plugin) simply because using this plugin indicates that for this project there is no distinction between an API and its implementation.

It would be fatal if they would treat it differently. What if both plugins are applied? Also this would be majorly confusing of things are treated differently depending on applied plugins. Besides that, the java-library plugin automatically applies the java plugin and just provides functionality on top.

Every component that is used by another component is a library.

There is nothing like an implementation-transient dependency. If a dependency is only used internally (to implement method bodies, in signatures of private or package-private methods, …) then it is an implementation dependency and should not leak into consumer compile classpaths. If a dependency is used in the API (signatures of public or protected methods, superclass, …) then it is an api dependency and automatically lands in the consumer compile classpath. If a consumer uses code from a transitive dependency without explicitly declaring it, this imho is a build bug. Every module should declare the dependencies it is using.