---
url: /howto/integrating-with-gradle.md
---

# Integrating with Gradle

The Teamscale Gradle Plugin enables seamless integration between your Gradle builds and Teamscale, allowing you to upload test reports, coverage data, and leverage Test Impact Analysis (TIA) capabilities.
The plugin also provides utilities for aggregating and compacting reports to reduce their storage footprint.

It is split into two cooperating pieces.

* The `com.teamscale` plugin, applied to each project, instruments individual test tasks:
  it attaches a coverage agent to the JVM, collects execution data per test, and optionally enables Test Impact Analysis so only tests affected by recent changes are run.
  It also exposes `TeamscaleUpload` tasks that send those reports to the Teamscale server.
* The `com.teamscale.aggregation` plugin, applied once at a project that depends on all projects from which data should be aggregated (e.g. the root project or the application project), acts as a collection point:
  it pulls the coverage and JUnit XML produced by all projects it depends on (also transitively), merges them into a single report per test suite before they are uploaded.

:::tip Option Reference
For the full task and property reference, see [our Gradle plugin reference](/reference/integrations/gradle-plugin/).
:::

\[\[toc]]

## Common Scenarios

### Single project

If you only have a single Gradle project with no subprojects, follow the [Setup](#setup) section only.

### Application project with many library sibling projects

If you have a root project with many subprojects, one of which is the main application and the others are libraries the application depends on, follow the [Setup](#setup) section, then also follow [Aggregation for Multi-Project Builds](#aggregation-for-multi-project-builds) and apply `com.teamscale.aggregation` to the application project.

### Root project with many subprojects

If you have a root project with many subprojects, follow the [Setup](#setup) section, then also follow [Aggregation for Multi-Project Builds](#aggregation-for-multi-project-builds) and apply `com.teamscale.aggregation` to the root project.

### Separate aggregation project

If you already have a separate subproject that does not contain code but solely handles e.g. report publishing, follow the [Setup](#setup) section, then also follow [Aggregation for Multi-Project Builds](#aggregation-for-multi-project-builds) and apply `com.teamscale.aggregation` to that project.
Make sure that this project has a dependency on all projects from which data should be aggregated.

## Setup

### Apply the Plugin

Add the Teamscale plugin to your build scripts ([latest version](https://plugins.gradle.org/plugin/com.teamscale)).
You must add it to each project that runs tests for which you want coverage or test results in Teamscale.

:::: tabs key:gradle\_lang
\== Groovy

```groovy
plugins {
	id "com.teamscale" version "35.0.2"
}
```

\== Kotlin DSL

```kotlin
plugins {
	id("com.teamscale") version "35.0.2"
}
```

::::

### Configure the `teamscale` Extension

The `teamscale` extension configures the connection to Teamscale.
Configure *once* in the project that should perform the report upload to Teamscale, e.g. the root project.

:::: tabs key:gradle\_lang
\== Groovy

```groovy
teamscale {
	server {
		url = 'https://mycompany.teamscale.io/'
		project = 'my-project'
		userName = 'build'
		userAccessToken = property('teamscale.access-token')
	}
}
```

\== Kotlin DSL

```kotlin
teamscale {
	server {
		url = "https://mycompany.teamscale.io/"
		project = "my-project"
		userName = "build"
		userAccessToken = System.getProperty("teamscale.access-token")
	}
}
```

::::

By default, the plugin tries to detect the commit from CI environment variables ([supported environment variables](/reference/integrations/gradle-plugin/#optional-commit-and-baseline-properties)) or the local Git repository.
If you need to manually specify a commit or baseline, see the [full options reference](/reference/integrations/gradle-plugin/#teamscale-extension-options).
If auto-detection fails (e.g., building outside a Git repository without CI environment variables set), the upload will fail. In that case, set `commit.revision` explicitly.

### Upload Reports

Register a `TeamscaleUpload` task to send test results and coverage to Teamscale.

:::: tabs key:gradle\_lang
\== Groovy

```groovy
import com.teamscale.TeamscaleUpload

tasks.register('teamscaleTestUpload', TeamscaleUpload) {
	partition = "Unit Tests"
	from(tasks.test)
	from(tasks.jacocoTestReport)
}
```

\== Kotlin DSL

```kotlin
import com.teamscale.TeamscaleUpload

tasks.register<TeamscaleUpload>("teamscaleTestUpload") {
	partition = "Unit Tests"
	from(tasks.named("test"))
	from(tasks.named("jacocoTestReport"))
}
```

::::

### Run the Upload Task

Run the upload task to execute your tests and send the results to Teamscale:

```bash
./gradlew teamscaleTestUpload --continue
```

We recommend using `--continue` if you have multiple projects and want test results/coverage even if the tests of one project fail.

The upload task depends on the test and coverage tasks, so they run automatically.
After the build completes, the data is available in Teamscale:

* **Test results**: visible in the *Metrics* > *Tests* perspective of your project
* **Coverage**: visible in the *Test Gaps* and *Metrics* > *Code* perspective

## Aggregation for Multi-Project Builds

For projects with multiple subprojects, we **recommend** to aggregate reports before uploading them.
This reduces the amount of data that needs to be processed by Teamscale.
The plugin works similarly to Gradle's [JaCoCo Report Aggregation Plugin](https://docs.gradle.org/current/userguide/jacoco_report_aggregation_plugin.html) and [Test Report Aggregation Plugin](https://docs.gradle.org/current/userguide/test_report_aggregation_plugin.html).
Please refer to the Gradle documentation for more details on the concepts behind the aggregation mechanism.

### Apply the Plugins

Apply both plugins to the aggregation project:

:::: tabs key:gradle\_lang
\== Groovy

```groovy
plugins {
	id "com.teamscale"
	id 'com.teamscale.aggregation'
}
```

\== Kotlin DSL

```kotlin
plugins {
	id("com.teamscale")
	id("com.teamscale.aggregation")
}
```

::::

### Register Subprojects for Aggregation

The plugin adds the `reportAggregation` Gradle dependency configuration.
You register subprojects in it the same way you declare library dependencies - Gradle then resolves test outputs from those projects for aggregation.
There are two typical scenarios:

* **The aggregation project is a JVM project itself** - i.e., the aggregation happens in the project that also applies the `application` plugin.
  In this case the `reportAggregation` configuration will automatically depend on all transitive projects and hence aggregate their reports.
* **The aggregation project is a standalone project** - i.e., the project contains no code and has no dependencies to other projects declared.
  This is usually the case if aggregation happens via the root project or a dedicated subproject that just exists for the purpose to aggregate data.
  In this case you have to ensure that the projects that you want to aggregate are added as dependencies to the `reportAggregation` configuration.

:::: tabs key:gradle\_lang
\== Groovy

```groovy
plugins {
	id 'com.teamscale.aggregation'
}

dependencies {
	// either register each project from which data should be aggregated separately:
	reportAggregation(project(":lib:lib1"))
	reportAggregation(project(":lib:lib2"))
	// alternatively, if you want to aggregate data from all subprojects:
	reportAggregation(subprojects())
}
```

\== Kotlin DSL

```kotlin
plugins {
    id("com.teamscale.aggregation")
}

dependencies {
    reportAggregation(project(":lib:lib1"))
    reportAggregation(project(":lib:lib2"))
    // alternatively, if you want to aggregate data from all subprojects:
    // reportAggregation(subprojects)
}
```

::::

### Register Upload Tasks

#### With JVM Test Suites

When using the [JVM Test Suite Plugin][jvm-test-suites], aggregate reports and their tasks are automatically created for each test suite - you only need to register a `TeamscaleUpload` task that collects from them.
See the [Gradle Plugin Reference](/reference/integrations/gradle-plugin/#automatically-created-aggregation-tasks-for-jvm-test-suites) for the full list of auto-created task types.

:::: tabs key:gradle\_lang
\== Groovy

```groovy
plugins {
	id 'com.teamscale.aggregation'
}

tasks.register('teamscaleIntegrationTestReportUpload', TeamscaleUpload) {
	partition = 'Integration Tests'
	from(tasks.named('integrationTestAggregateCompactCoverageReport'))
	from(tasks.named('integrationTestAggregateJUnitReport'))
}
```

\== Kotlin DSL

```kotlin
plugins {
	id("com.teamscale.aggregation")
}

tasks.register<TeamscaleUpload>("teamscaleIntegrationTestReportUpload") {
	partition = "Integration Tests"
	from(tasks.named("integrationTestAggregateCompactCoverageReport"))
	from(tasks.named("integrationTestAggregateJUnitReport"))
}
```

::::

#### Without JVM Test Suites

If your build differentiates different test types only by package/class name rather than using the [JVM Test Suite Plugin][jvm-test-suites], no aggregate tasks are created automatically.
Use the following approach to explicitly register each test type for separate aggregation.
You will need one `Test` task for each type of test that you want to differentiate in the Teamscale UI.

You can associate a `Test` task with an arbitrary test suite name.
For this purpose we provide the `TestSuiteCompatibilityUtil.exposeTestForAggregation(testTask, suiteName)`.
Call this for each task in each subproject that you want to take part in aggregation.

:::: tabs key:gradle\_lang
\== Groovy

```groovy
import com.teamscale.aggregation.TestSuiteCompatibilityUtil

tasks.register('unitTest', Test) {
	// ...
}

tasks.register('systemTest', Test) {
	teamscale {
		collectTestwiseCoverage = true
	}
}

TestSuiteCompatibilityUtil.exposeTestForAggregation(tasks.named('unitTest'), 'myUnitTestSuite')
TestSuiteCompatibilityUtil.exposeTestForAggregation(tasks.named('systemTest'), 'mySystemTestSuite')
```

\== Kotlin DSL

```kotlin
import com.teamscale.extension.TeamscaleTaskExtension
import com.teamscale.aggregation.TestSuiteCompatibilityUtil

tasks.register<Test>("unitTest") {
    // ...
}

tasks.register<Test>("systemTest") { 
    configure<TeamscaleTaskExtension> {
        collectTestwiseCoverage = true
    }
}

TestSuiteCompatibilityUtil.exposeTestForAggregation(tasks.named<Test>("unitTest"), "myUnitTestSuite")
TestSuiteCompatibilityUtil.exposeTestForAggregation(tasks.named<Test>("systemTest"), "mySystemTestSuite")
```

::::

In the aggregation project, create an aggregation report for each type of data you want to aggregate (coverage, test results, testwise coverage) and each test suite name you picked:

:::: tabs key:gradle\_lang
\== Groovy

```groovy
import com.teamscale.aggregation.compact.AggregateCompactCoverageReport
import com.teamscale.aggregation.junit.AggregateJUnitReport
import com.teamscale.aggregation.testwise.AggregateTestwiseCoverageReport

plugins {
	id 'com.teamscale.aggregation'
}

reporting {
	reports {
		unitTestAggregateCompactCoverageReport(AggregateCompactCoverageReport) {
			testSuiteName = 'myUnitTestSuite'
		}
		unitTestAggregateJUnitReport(AggregateJUnitReport) {
			testSuiteName = 'myUnitTestSuite'
		}
		systemTestAggregateTestwiseCoverageReport(AggregateTestwiseCoverageReport) {
			testSuiteName = 'mySystemTestSuite'
		}
	}
}

tasks.register("teamscaleUnitTestReportUpload", TeamscaleUpload) {
	partition = "Unit Tests"
	from(tasks.named('unitTestAggregateCompactCoverageReport'))
	from(tasks.named('unitTestAggregateJUnitReport'))
}

tasks.register("teamscaleSystemTestReportUpload", TeamscaleUpload) {
	partition = "System Tests"
	from(tasks.named('systemTestAggregateTestwiseCoverageReport'))
}
```

\== Kotlin DSL

```kotlin
import com.teamscale.aggregation.compact.AggregateCompactCoverageReport
import com.teamscale.aggregation.junit.AggregateJUnitReport
import com.teamscale.aggregation.testwise.AggregateTestwiseCoverageReport

plugins {
	id("com.teamscale.aggregation")
}

reporting {
    reports {
        register<AggregateCompactCoverageReport>("unitTestAggregateCompactCoverageReport") {
            testSuiteName = "myUnitTestSuite"
        }
        register<AggregateJUnitReport>("unitTestAggregateJUnitReport") {
            testSuiteName = "myUnitTestSuite"
        }
        register<AggregateTestwiseCoverageReport>("systemTestAggregateTestwiseCoverageReport") {
            testSuiteName = "mySystemTestSuite"
        }
    }
}

tasks.register<TeamscaleUpload>("teamscaleUnitTestReportUpload") {
	partition = "Unit Tests"
	from(tasks.named("unitTestAggregateCompactCoverageReport"))
	from(tasks.named("unitTestAggregateJUnitReport"))
}

tasks.register<TeamscaleUpload>("teamscaleSystemTestReportUpload") {
	partition = "System Tests"
	from(tasks.named("systemTestAggregateTestwiseCoverageReport"))
}
```

::::

## Troubleshooting

### Log Files

When [`debugLogging`](/reference/integrations/gradle-plugin/#test-extension-teamscaletaskextension) is enabled in the `teamscale` extension on a `Test` task, the plugin creates detailed log files to help diagnose issues:

* **`logs/teamscale-jacoco-agent.log`** - Debug logs from the Java profiler
* **`engine.log`** - Debug logs from the impacted test engine

:::: tabs key:gradle\_lang
\== Groovy

```groovy
tasks.register('unitTest', Test) {
	teamscale {
		collectTestwiseCoverage = true
		runImpacted = true
		debugLogging = true
	}
}
```

\== Kotlin DSL

```kotlin
import com.teamscale.extension.TeamscaleTaskExtension

tasks.register<Test>("unitTest") {
	configure<TeamscaleTaskExtension> {
		collectTestwiseCoverage = true
		runImpacted = true
		debugLogging = true
	}
}
```

::::

These log files will be located in the test task's JaCoCo destination directory (e.g., `build/jacoco/unitTest/`).

### Spring Boot: Missing Dependencies

Spring boot uses its own dependency versioning mechanism, not the Gradle built-in one.
This can lead to problems when adding the `com.teamscale.aggregation` plugin.
You'll see error messages like this one:

```
Could not find org.springframework.boot:spring-boot-starter-validation:.
     Required by:
         root project 'myproject' > project :subproject
```

The `aggregateReportResults` configuration is where the plugin resolves reports from subprojects.
Setting `transitive = false` prevents Gradle from following Spring Boot's BOM transitive dependencies into the aggregation resolution, which causes the version conflict.

In the project with the aggregation plugin, add:

```
dependencies {
  reportAggregation(subprojects)
}

configurations.aggregateReportResults {
    transitive = false
}
```

***

[jvm-test-suites]: https://docs.gradle.org/current/userguide/jvm_test_suite_plugin.html
