Maven and Gradle
Java and Kotlin projects, diffed with japicmp on the published jars, located again from your compiled classes during the fix, and verified with your build tool offline.
| Maven and Gradle | |
|---|---|
| Maven | every pom.xml in the modules tree, local parents, ${properties}, local dependencyManagement |
| Gradle | settings.gradle(.kts), build.gradle(.kts), the libs.versions.toml catalog, gradle.lockfile |
| API diff | japicmp 0.26.3 on both jars' bytecode |
| Finds your lines with | your imports at scan time, then your compiled classes during the fix |
| Sandbox | Eclipse Temurin JDK 21, with 17 and 25 beside it; Maven 3.9.16 and Gradle 9.8.1 |
| Docs for the agent | japicmp's changes and the target's sources jar with its Javadoc or KDoc |
What Teire reads
Maven versions come from literal <version> tags, properties (5 levels deep), local parents, and the parents and BOMs outside the repository. Those POMs are read from a repository your build names that has a credential saved in Private registries first, then Maven Central, Google Maven and the Gradle Plugin Portal, then the other repositories your build names.
A profile's dependencies and plugins count when a build here switches it on: activeByDefault, -P in .mvn/maven.config or on a CI workflow's mvn line, or a !property activation. A profile switched on only by a JDK, an OS, a file or a property is listed with that condition and not compared.
Gradle declarations are read from:
"group:artifact:version"strings,libs.x.ycatalog accessors,- configurations from
implementationandapitokaptandksp, - versioned plugins (
id 'org.springframework.boot' version '2.6.3'and catalogalias(libs.plugins.x)), as the plugin's marker coordinate.
Versions come from gradle.lockfile when you have one, which also gives the transitive dependencies. Without it, a declared version is treated as a floor. The mvnw and gradlew wrappers are used when present. A version stays in its flavour: a -jre artifact never moves to -android.
How breaks are found
japicmp compares the old and new jars' bytecode in two passes, leaving out internal, impl and shaded packages. Kotlin multiplatform libraries are read through their -jvm variant. Kotlin's @Deprecated(level = ERROR or HIDDEN) counts as removed, with its replaceWith.
Your sources are first matched through their imports: explicit, wildcard, static and Kotlin as. For a new major, the scan then compiles your project in the JVM sandbox (the dependencies fetched with the network, the compile offline) and reads the references out of your compiled classes, exactly as javac and kotlinc resolved them. Those type-level sites replace the textual ones, so an unused removed API reads not affected. A source file that uses the library but didn't compile (another source set, an Android variant) is unresolved and keeps the verdict inconclusive. Inlined constants and Kotlin inline bodies can't be seen in bytecode, so their textual sites stay. The fix reads the classes again after its own compile.
How it upgrades
Teire changes only the version text, wherever it lives:
- Maven: the
<version>tag, the property, or localdependencyManagement. - Gradle: the coordinate string, the catalog
[versions]entry (all entries sharing thatversion.refmove together), agradle.propertiesvariable, or a plugin'sversionin the plugins block or the catalog. - Java: when the target's published metadata needs a newer Java than the build declares, the same change raises it.
Projects with a gradle.lockfile then run dependencies --update-locks for that module.
How it verifies
Teire runs your real build once online so the dependencies download. Only a resolution failure counts at this stage. It does this on the unchanged repository first. Then, offline:
| Build | typecheck | test |
|---|---|---|
| Maven | mvn -o test-compile | mvn -o verify |
| Gradle | gradle --offline testClasses | gradle --offline --continue test |
| Android (Gradle) | the debug variants' unit-test compile | the debug variants' unit tests |
An Android build (a module that applies com.android.application, com.android.library or another Android Gradle Plugin, in its build file or the version catalog) runs in the Android image: the JVM image plus the Android SDK, licences accepted, with platforms 35 and 36 and build tools 35 and 36 already there. A platform or build tools version your project names that isn't there is fetched by the Android Gradle Plugin during the online install. A plain JVM module in the same build keeps testClasses and test. Instrumented tests need a device and don't run.
The JDK follows your CI's setup-java, .java-version, .sdkmanrc, .tool-versions, or the Gradle toolchain or Maven release, else 21. Test tasks always run, never "up to date". Test counts come from Surefire or Teire's Gradle init script.
In the evaluation
| Repository | Upgrade | Result |
|---|---|---|
| demo-jvm-major | guava 19.0 → 33.7.2-jre, 4 sites | verified by Nemotron 3 Ultra · 1 attempt · 17 steps · $0.165 |
| demo-kotlin-major | okhttp 3.14.9 → 5.5.0 (Gradle, Kotlin), 13 sites | verified · 1 attempt · 24 steps · $0.249 |
| demo-jvm-clean | guava 19.0 → 33.7.2-jre | inconclusive at scan time, upgrade verified as a plain bump |
Limits
Android's instrumented tests (androidTest) need a device or emulator and don't run; unit tests do. Maven has no lockfile, so transitive dependencies aren't read. Versions computed with $ interpolation in Gradle scripts are unresolved.
RubyGems
Bundler projects and Rails apps, diffed with Prism from both published gems, upgraded with bundle lock --conservative, and verified with your tests and databases.
Providers, models and APIs
Retired models, removed endpoints and API changes, read from each provider's own pages only when your code talks to it, and kept only when the quote matches the page.