# Multi-project build can't resolve custom plugin

**URL:** <https://discuss.gradle.org/t/multi-project-build-cant-resolve-custom-plugin/51224>\
**Category:** Help/Discuss\
**Created:** [June 24, 2025, 8:19am UTC](https://discuss.gradle.org/t/multi-project-build-cant-resolve-custom-plugin/51224 "2025-06-24T08:19:28Z")\
**Posts on this page:** 7\
**Page:** 1

<div class="post-metadata">

**Author:** ![sl19](https://avatars.discourse-cdn.com/v4/letter/s/e95f7d/32.png) [@sl19](https://discuss.gradle.org/u/sl19)\
**Post date:** [June 24, 2025, 8:19am UTC](https://discuss.gradle.org/t/multi-project-build-cant-resolve-custom-plugin/51224/1 "2025-06-24T08:19:28Z")

</div>

Hi,

What I’m trying to do:

- I have a custom Maven repository containing Plugins, for example a Sonarqube Plugin which I want to add in my Gradle project
- The project should consist of multiple subprojects where I can share common build logic via buildSrc as well as share logic between subprojects

Thus I did setup the following project structure:

```gradle
.
├── api
│ ├── build.gradle
├── buildSrc
│ ├── build.gradle
│ └── src
│ └── main
│ └── groovy
│ └── java-common-conventions.gradle
├── gradle
└── settings.gradle

```

The content of aforementioned files is as following:

**settings.gradle**

```gradle
pluginManagement {
    repositories {
        maven {
            name = "plugin-releases"
            url = "https://my-custom-repo-url/artifactory/plugins-release"
        }
    }
}

rootProject.name = 'gradle-multiproject'

include("api")

```

**buildSrc/build.gradle**

```gradle
plugins {
    id 'groovy-gradle-plugin'
}

repositories {
    gradlePluginPortal()
}

```

**buildSrc/src/main/groovy/java-common-conventions.gradle**

```gradle
plugins {
    id 'java-library'
    id 'org.sonarqube'
}

dependencies {
    implementation 'org.slf4j:slf4j-api:2.0.9'
    testImplementation 'org.junit.jupiter:junit-jupiter:5.11.3'
    testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}

java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(17)
    }
}

tasks.named('test', Test) {
    useJUnitPlatform()
}

// Just a test to make sure the plugin is loaded correctly and can be configured
sonarqube {
   description = "Global sonarqube configuration"
   property 'sonar.sourceEncoding', 'UTF-8'
}

```

**api/build.gradle**

```gradle
plugins {
    id 'java-common-conventions'
}

```

**Error message:**

> An exception occurred applying plugin request [id: ‘java-common-conventions’]  
> Failed to apply plugin ‘java-common-conventions’.  
> org.gradle.api.plugins.UnknownPluginException: Plugin with id ‘org.sonarqube’ not found.

If I’m browsing the repository URL, I can find the plugin, but I think it couldn’t be resolved.  
Former projects used buildscript and classpath as following:

```gradle
buildscript {
    group = group
    version = version
    description = description
    apply from: "${repositoryBaseUrl}/main.gradle"

    dependencies {
        classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.1'
    }

    project.repositories {
        mavenLocal()
        maven {
            name 'lib-snapshots'
            url 'https://my-custom-repo-url/artifactory/libs-snapshot'
        }
    }
}

```

I think using buildscript this way doesn’t work in newer versions of Gradle and Java and is even discouraged, otherwise I am not sure how to tell Gradle the exact plugin ID or even if that’s the real issue here.

The maven POM looks like this:

```gradle
<?xml version="1.0" encoding="US-ASCII"?>
<metadata>
  <groupId>org.sonarqube</groupId>
  <artifactId>org.sonarqube.gradle.plugin</artifactId>
  <version>3.1</version>
  <versioning>
    <latest>3.1</latest>
    <release>3.1</release>
    <versions>
      <version>3.1</version>
    </versions>
    <lastUpdated>20250515071239</lastUpdated>
  </versioning>
</metadata>

```

I’m sure I’m confusing a few things, so any advice would be highly appreciated!

---

<div class="post-metadata">

**Author:** ![sl19](https://avatars.discourse-cdn.com/v4/letter/s/e95f7d/32.png) [@sl19](https://discuss.gradle.org/u/sl19)\
**Post date:** [June 25, 2025, 12:27pm UTC](https://discuss.gradle.org/t/multi-project-build-cant-resolve-custom-plugin/51224/2 "2025-06-25T12:27:23Z")

</div>

Hi again,

just as an update, I _somehow_ got it working, my files now look like the following:

**settings.gradle**

```gradle
pluginManagement {
    repositories {
        maven {
            name = "plugin-releases"
            url = "https://my-custom-repo-url/artifactory/plugins-release"
        }
    }
}

rootProject.name = 'gradle-multiproject'

include("api")

```

**buildSrc/build.gradle**

```gradle
plugins {
    id 'groovy-gradle-plugin'
}

repositories {
        maven {
            name = "plugin-releases"
            url = "https://my-custom-repo-url/artifactory/plugins-release"
        }
    gradlePluginPortal()
}

dependencies {
    implementation("org.sonarqube:org.sonarqube.gradle.plugin:3.3")
}

```

**buildSrc/src/main/groovy/java-common-conventions.gradle**

```gradle

plugins {
    id 'java-library'
    id 'org.sonarqube'
}

repositories {
        maven {
            name = "plugin-releases"
            url = "https://my-custom-repo-url/artifactory/plugins-release"
        }
}

dependencies {
    implementation 'org.slf4j:slf4j-api:2.0.17'
    implementation 'org.slf4j:slf4j-simple:2.0.17'
    testImplementation 'org.junit.jupiter:junit-jupiter:5.11.3'
    testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}

java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(17)
    }
}

tasks.named('test', Test) {
    useJUnitPlatform()
}

sonarqube {
    properties {
        property "sonar.sourceEncoding", "UTF-8"
        property "sonar.projectKey", "${projectGroup}:${serviceKey}-${projectName}"
        property "sonar.projectName", "${serviceKey}-${projectName}"
        property "sonar.host.url", "https://sonar.dev.local"
    }
}

```

**gradle.properties**

```gradle
projectName=gradle-multiproject
description=A neat description!
projectGroup=org.example
gitProjectPath=gradle-multiproject
serviceKey=test

```

Now when I run ./gradlew sonarqube, it will run the task as expected. But I’m still uncertain why I actually have to declare the repositories all over again. I actually thought that setting it up in settings.gradle would enable subprojects to also use the somewhat _globally_ configured repositories, but if I’m going to remove the explicit repository declaration in build.gradle of subprojects, it won’t be fetching subproject-relevant dependencies e.g. slf4j anymore.

Also, in Intellij it will not recognize the sonarqube task in buildSrc/build.gradle, but in any other subproject e.g. api/build.gradle, any idea here (although it’s IDE specific..)?

 ![image](https://global.discourse-cdn.com/gradle/original/2X/f/fb734d449b88fd94632190c7522ca6c79fa1ffee.png)

Again, thanks for your time!

---

<div class="post-metadata">

**Author:** ![Vampire](https://sea1.discourse-cdn.com/gradle/user_avatar/discuss.gradle.org/vampire/32/9082_2.png) [@Vampire](https://discuss.gradle.org/u/Vampire)\
**Post date:** [June 25, 2025, 1:40pm UTC](https://discuss.gradle.org/t/multi-project-build-cant-resolve-custom-plugin/51224/3 "2025-06-25T13:40:29Z")

</div>

> I _somehow_ got it working

What you were missing and now added is the dependency on the SQ plugin in your `buildSrc/build.gradle` which was missing in your original files.  
The `plugin-releases` in the `java-common-conventions.gradle` should be unnecessary, unless you need that repository for dependencies in the actual project.

> I actually thought that setting it up in settings.gradle would enable subprojects to also use the somewhat _globally_ configured repositories

In your settings script you only configured plugin repositories which are for resolving plugins and their dependencies.

`slf4j` is not a plugin or plugin dependency but a dependency of your actual project.  
For that you can either declare `dependencyManagement { repositories { ... } }` in your settings script that will be used for all projects, or have it in your convention plugin.

Btw. your convention plugin should contain a dot `.` somewhere in its id (filename). Plugin ids without namespace should stay reserved for built-in plugins provided by Gradle itself.

> Also, in Intellij it will not recognize the sonarqube task in buildSrc/build.gradle, but in any other subproject

I strongly recommend switching to Kotlin DSL. By now it is the default DSL, you immediately get type-safe build scripts, actually helpful error messages if you mess up the syntax, and amazingly better IDE support if you use a good IDE like IntelliJ IDEA or Android Studio.

---

<div class="post-metadata">

**Author:** ![sl19](https://avatars.discourse-cdn.com/v4/letter/s/e95f7d/32.png) [@sl19](https://discuss.gradle.org/u/sl19)\
**Post date:** [June 26, 2025, 10:00am UTC](https://discuss.gradle.org/t/multi-project-build-cant-resolve-custom-plugin/51224/4 "2025-06-26T10:00:42Z")

</div>

Thank you very much for the clarification and hints, now it works and I can both use my custom repositories in subprojects as well as plugins and dependencies that are pre-compiled by buildSrc/conventions-plugin.

What’s confusing and not working now is that in :api-subproject, I can’t import classes that I declared in buildSrc/src/main/groovy|java.I can use slf4j but not junit (both should be compiled at the same time in buildSrc).

Stacktrace for the import issue from classes in buildSrc:

```gradle
Execution failed for task ':api:compileJava'.
> Could not resolve all files for configuration ':api:compileClasspath'.
   > Could not find :buildSrc:.
     Required by:
         project :api

```

What I tried already was to add buildSrc in api/build.gradle:

```gradle
plugins {
    id 'my-org.java-common-conventions'
}

dependencies {
    implementation(
            ':buildSrc'
    )
}

```

---

<div class="post-metadata">

**Author:** ![Vampire](https://sea1.discourse-cdn.com/gradle/user_avatar/discuss.gradle.org/vampire/32/9082_2.png) [@Vampire](https://discuss.gradle.org/u/Vampire)\
**Post date:** [June 26, 2025, 11:33am UTC](https://discuss.gradle.org/t/multi-project-build-cant-resolve-custom-plugin/51224/5 "2025-06-26T11:33:48Z")

</div>

Of course this does not work, why should it? o\_O  
In `buildSrc` you develop build logic to be used in your build scripts like convention plugins.  
It is a separate stand-alone build that “happens” to run before your main build and its results added to your build script classpaths by adding it to a parent classloader.  
You do not need to do anything to use the convention plugin in your build scripts.

The dependency you declared is a production dependency on a project called `buildSrc` but you do not have (and cannot have) a project called `buildSrc` in your build.

Applying the convention plugin already does add the slf4j dependency as that is what you programmed in your convention plugin.

---

<div class="post-metadata">

**Author:** ![sl19](https://avatars.discourse-cdn.com/v4/letter/s/e95f7d/32.png) [@sl19](https://discuss.gradle.org/u/sl19)\
**Post date:** [June 30, 2025, 9:19am UTC](https://discuss.gradle.org/t/multi-project-build-cant-resolve-custom-plugin/51224/6 "2025-06-30T09:19:33Z")

</div>

Thank you very much for the explanation, I think I got it now.

I moved the test classes out of buildSrc and use `java-test-fixtures` to share these between subprojects.

If I remove the repository {…} config in buildSrc/build.gradle, the plugins can’t be fetched anymore. I thought that will be done in settings.gradle via `pluginManagement`, no?

---

<div class="post-metadata">

**Author:** ![Vampire](https://sea1.discourse-cdn.com/gradle/user_avatar/discuss.gradle.org/vampire/32/9082_2.png) [@Vampire](https://discuss.gradle.org/u/Vampire)\
**Post date:** [June 30, 2025, 9:31am UTC](https://discuss.gradle.org/t/multi-project-build-cant-resolve-custom-plugin/51224/7 "2025-06-30T09:31:58Z")

</div>

No.

If you would use an included build, then it would use both.  
The one in the build logic build script to build it,  
the on in the consuming builds settings script to resolve it.  
With `buildSrc` the result including the transitive dependencies is put to a parent class loader so the declared plugin repositories are irrelevant for that.
