Code mappings connect your stack traces to source code, enabling features like source context, suspect commits, and stack trace linking. Previously, you had to configure them one-by-one through the Sentry UI — tedious for monorepos with dozens of modules.
You can now upload code mappings in bulk using sentry-cli:
sentry-cli code-mappings upload ./mappings.json
The JSON file pairs stack trace roots with their source paths in your repository:
[
{"stackRoot": "io/sentry/android/core", "sourceRoot": "sentry-android-core/src/main/java/io/sentry/android/core"},
{"stackRoot": "io/sentry/opentelemetry", "sourceRoot": "sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry"},
{"stackRoot": "io/sentry/opentelemetry", "sourceRoot": "sentry-opentelemetry/sentry-opentelemetry-bootstrap/src/main/java/io/sentry/opentelemetry"}
]
Multiple mappings can share the same stack root — useful when the same package prefix exists across multiple modules (like io/sentry/opentelemetry above).
For Android/JVM projects, you can generate the mappings JSON from your Gradle build. Add this task to your root build.gradle.kts:
abstract class GenerateSentryMappingsTask : DefaultTask() {
@get:InputFiles
@get:PathSensitive(PathSensitivity.RELATIVE)
abstract val sourceDirs: ConfigurableFileCollection
@get:Internal
abstract val rootDir: DirectoryProperty
@get:OutputFile
abstract val outputFile: RegularFileProperty
@TaskAction
fun generate() {
val outFile = outputFile.get().asFile
val rootPath = rootDir.get().asFile
val mappings = sourceDirs.files
.filter { it.exists() && !it.path.contains("/build/") }
.mapNotNull { srcDir ->
val packageRoot = findPackageRoot(srcDir) ?: return@mapNotNull null
val sourceRoot = packageRoot.relativeTo(rootPath).path
val stackRoot = packageRoot.relativeTo(srcDir).path
""" {"stackRoot": "$stackRoot", "sourceRoot": "$sourceRoot"}"""
}
val json = "[\n${mappings.joinToString(",\n")}\n]"
outFile.writeText(json)
logger.lifecycle("Generated ${mappings.size} code mappings to ${outFile.name}")
}
private fun findPackageRoot(root: File): File? {
var dir = root
while (true) {
val children = dir.listFiles() ?: return null
if (children.any { it.isFile }) return dir
val subdirs = children.filter { it.isDirectory }
if (subdirs.size == 1) {
dir = subdirs.single()
} else {
return if (subdirs.isNotEmpty()) dir else null
}
}
}
}
val generateMappings = tasks.register<GenerateSentryMappingsTask>("generateSentryMappings") {
rootDir.set(rootProject.layout.projectDirectory)
outputFile.set(rootProject.layout.projectDirectory.file("sentry-mappings.json"))
}
subprojects {
plugins.withId("com.android.library") {
val android = extensions.getByType(com.android.build.gradle.BaseExtension::class.java)
generateMappings.configure {
sourceDirs.from(android.sourceSets.getByName("main").java.srcDirs)
}
}
plugins.withId("com.android.application") {
val android = extensions.getByType(com.android.build.gradle.BaseExtension::class.java)
generateMappings.configure {
sourceDirs.from(android.sourceSets.getByName("main").java.srcDirs)
}
}
plugins.withType(JavaPlugin::class.java) {
val java = extensions.getByType(JavaPluginExtension::class.java)
generateMappings.configure {
sourceDirs.from(java.sourceSets.getByName("main").allJava.srcDirs)
}
}
}
Then run:
./gradlew generateSentryMappings
sentry-cli code-mappings upload ./sentry-mappings.json

Check our docs for more details.