Skills
Skill 18 of 69
Upgrade an Android application from Datadog’s v2 mobile RUM SDK to the latest stable v3 SDK, including Core, RUM, Logs, Trace, OkHttp, and Session Replay breaking changes.
6 minutes · 1,264 words · 17 sections
Install
npx skills add datadog-labs/agent-skills --skill dd-android-sdk-v3-upgradenpx skills add datadog-labs/agent-skills/plugin marketplace add datadog-labs/agent-skillsThe first command installs just this skill, by the name in its SKILL.md; the second installs the whole repository.
Use this skill when an Android project uses Datadog Mobile SDK 2.x and must be upgraded to the latest stable 3.x release. The v3 migration removes legacy OpenTracing support and updates APIs across the Android SDK modules.
This skill incorporates the Android v2-to-v3 migration guidance from
DataDog/documentation (opens in a new tab), reviewed at commit
a27f7562165b9058f8cd597b48473cc7ecbeda81 on 2026-09-25. Use the instructions
below as the migration source of truth; consult the live documentation only when
the project contains an API not covered here or the requested target is newer
than this reviewed revision.
Before editing, inspect the entire Android project for:
com.datadoghq:dd-sdk-android-* dependency, including dependencies in
version catalogs, convention plugins, and included builds;com.datadog.android and io.opentracing import;useCustomEndpoint;DatadogInterceptor and TracingInterceptor construction;minSdk and Kotlin version.Do not remove a Datadog feature, instrumentation path, or test merely to make the upgrade compile. Preserve the application’s existing telemetry behavior.
minSdk to 23, stop and report that it cannot adopt SDK v3.composeOptions.kotlinCompilerExtensionVersion together using the official
Compose-to-Kotlin compatibility map (opens in a new tab). Do not suppress
the compatibility check.minSdk below
26, enable core-library desugaring as required by OpenTelemetry Java.Before editing dependencies, query the official
DataDog/dd-sdk-android GitHub releases (opens in a new tab) and select the
highest stable semantic version matching 3.x.y. Ignore drafts, prereleases,
release candidates, -SNAPSHOT versions, and releases from other major
versions. The GitHub Releases API can be queried at:
https://api.github.com/repos/DataDog/dd-sdk-android/releases?per_page=100Use each release object’s tag_name, draft, and prerelease fields rather
than assuming the first tag or the repository’s development changelog is a
stable release. If GitHub cannot be reached or the response cannot be parsed,
use 3.14.1 as the fallback version and report that fallback explicitly.
Change every Datadog Android SDK artifact from 2.x to the selected concrete 3.x version. Follow the project’s existing version-management convention and update the lockfile or version catalog where applicable.
Do not introduce moving versions such as 3.+ or latest.release, and do not
leave a mixture of v2 and v3 Datadog artifacts in the resolved dependency graph.
If the application uses Jetpack Compose and enables Compose instrumentation in the Datadog Gradle plugin, add the v3 Compose integration at the same SDK version:
implementation("com.datadoghq:dd-sdk-android-compose:<datadog-v3-version>")Leaving Compose instrumentation enabled without this artifact causes the Datadog compiler plugin to fail compilation with a missing-dependency error.
The user ID passed to Datadog.setUserInfo is mandatory in v3. Replace null
with the application’s stable user identifier:
// v2
Datadog.setUserInfo(null, "Jane Smith", "jane@example.com")
// v3
Datadog.setUserInfo("user123", "Jane Smith", "jane@example.com")Do not invent a production identity value. Reuse the application’s actual user identifier or preserve its existing anonymous-user strategy with an appropriate non-null identifier.
RumConfiguration.Builder.useCustomEndpoint must receive the complete RUM
upload URL rather than only a host:
Rum.enable(
RumConfiguration.Builder("<application-id>")
.useCustomEndpoint("https://example.com/rum/upload")
.build()
)Preserve the project’s custom host and path.
Replace the string HTTP method argument of startResource with
RumResourceMethod:
import com.datadog.android.rum.RumResourceMethod
GlobalRumMonitor.get().startResource(
key = "resource-key",
method = RumResourceMethod.GET,
url = "https://example.com/api/resource",
attributes = mapOf("source" to "manual")
)Use the enum member corresponding to the original method; do not default every
resource to GET.
Update the global RUM symbol:
| v2 | v3 |
|---|---|
com.datadog.android.rum.GlobalRum | com.datadog.android.rum.GlobalRumMonitor |
Use GlobalRumMonitor.get() to access the registered monitor. Do not replace it
with RumMonitor.get(); RumMonitor does not provide that v3 accessor.
In v3, the attributes: Map<String, Any?> parameter is optional for these APIs:
RumMonitor.addActionRumMonitor.startActionRumMonitor.stopResourceRumMonitor.addErrorRumMonitor.addErrorWithStacktraceAdvancedNetworkRumMonitor.stopResourceExisting attribute maps remain valid. Remove redundant empty maps only when it improves the migrated code without changing behavior.
Logs no longer reports fatal errors on its own. If the existing application expects crashes to appear in Error Tracking, keep RUM enabled and enable Crash Reporting explicitly in addition to Logs.
LogsConfiguration.Builder.useCustomEndpoint must receive the complete Logs
upload URL:
Logs.enable(
LogsConfiguration.Builder()
.useCustomEndpoint("https://example.com/logs/upload")
.build()
)Preserve all products that were enabled before the migration. In particular,
do not remove Rum.enable(...) or Logs.enable(...) to resolve compilation
failures.
TraceConfiguration.Builder.useCustomEndpoint must receive the complete Trace
upload URL:
Trace.enable(
TraceConfiguration.Builder()
.useCustomEndpoint("https://example.com/trace/upload")
.build()
)SDK v3 removes OpenTracing. Choose one of the following migrations based on the project’s constraints.
Add the v3 OpenTelemetry bridge using the same SDK version as the other Datadog artifacts:
implementation("com.datadoghq:dd-sdk-android-trace-otel:<datadog-v3-version>")For the simple case, replace global tracer registration:
// v2 OpenTracing
GlobalTracer.registerIfAbsent(
AndroidTracer.Builder()
.setService(BuildConfig.APPLICATION_ID)
.build()
)
// v3 OpenTelemetry
GlobalOpenTelemetry.set(
DatadogOpenTelemetry(BuildConfig.APPLICATION_ID)
)DatadogOpenTelemetry is a convenience wrapper. Its constructor accepts only
the service name; do not pass an SdkCore to it. When the v2 tracer has custom
builder configuration, such as an SDK instance or partial-flush threshold,
build the provider directly instead:
val tracerProvider = OtelTracerProvider.Builder(sdkCore)
.setService(BuildConfig.APPLICATION_ID)
.setPartialFlushThreshold(4)
.build()
GlobalOpenTelemetry.set(object : OpenTelemetry {
override fun getTracerProvider(): TracerProvider = tracerProvider
override fun getPropagators(): ContextPropagators = ContextPropagators.noop()
})
val tracer = tracerProvider.get("SampleApplicationTracer")OtelTracerProvider.Builder(...).build() returns a TracerProvider, not an
OpenTelemetry instance. Do not pass the provider directly to
GlobalOpenTelemetry.set(...); wrap it as shown above.
setPartialFlushThreshold belongs to OtelTracerProvider.Builder, not to the
built OtelTracerProvider. Configure it before calling build(); do not cast
openTelemetry.tracerProvider and attempt to mutate it afterward.
Replace manual tracing with the OpenTelemetry API:
val tracer: Tracer = GlobalOpenTelemetry
.get()
.getTracer("SampleApplicationTracer")
val span = tracer
.spanBuilder("Executing operation")
.startSpan()
// Instrumented operation
span.end()Apply these type and API mappings:
| v2 OpenTracing | v3 OpenTelemetry |
|---|---|
io.opentracing.util.GlobalTracer | io.opentelemetry.api.GlobalOpenTelemetry |
com.datadog.android.trace.AndroidTracer | io.opentelemetry.api.trace.Tracer |
io.opentracing.Span | io.opentelemetry.api.trace.Span |
io.opentracing.Scope | io.opentelemetry.context.Scope |
io.opentracing.SpanContext | io.opentelemetry.api.trace.SpanContext |
AndroidTracer.Builder(...).setPartialFlushThreshold(Int) | OtelTracerProvider.Builder(...).setPartialFlushThreshold(Int) |
SpanContext.toTraceId() | SpanContext.traceId (Kotlin) / SpanContext.getTraceId() (Java) |
Span.setError() | Span.recordException() |
Use this path only when the project cannot enable the desugaring required by OpenTelemetry. It exists to ease migration and may be removed in a future major SDK version.
Replace global tracer registration:
// v2 OpenTracing
GlobalTracer.registerIfAbsent(
AndroidTracer.Builder()
.setService(BuildConfig.APPLICATION_ID)
.build()
)
// v3 Datadog tracing API
GlobalDatadogTracer.registerIfAbsent(
DatadogTracing.newTracerBuilder(sdkCore)
.withServiceName(BuildConfig.APPLICATION_ID)
.withPartialFlushMinSpans(4)
.build()
)Replace manual tracing:
val tracer = GlobalDatadogTracer.get()
val span = tracer
.buildSpan("Executing operation")
.start()
val scope = tracer.activateSpan(span)
try {
// Instrumented operation
val traceId = span.context().traceId.toString()
} finally {
scope?.close()
span.finish()
}DatadogTracer.activateSpan() returns a nullable DatadogScope; close it with
scope?.close().
DatadogSpan.context() is a function, not a Kotlin property. Call it with
parentheses: use span.context().traceId, not span.context.traceId.
Apply these type and API mappings:
| v2 OpenTracing | v3 Datadog tracing API |
|---|---|
io.opentracing.util.GlobalTracer | com.datadog.android.trace.GlobalDatadogTracer |
com.datadog.android.trace.AndroidTracer | com.datadog.android.trace.api.tracer.DatadogTracer |
io.opentracing.Span | com.datadog.android.trace.api.span.DatadogSpan |
io.opentracing.Scope | com.datadog.android.trace.api.scope.DatadogScope |
io.opentracing.SpanContext | com.datadog.android.trace.api.span.DatadogSpanContext |
AndroidTracer.Builder().build() | DatadogTracing.newTracerBuilder().build() |
AndroidTracer.Builder(...).setService(String) | DatadogTracerBuilder.withServiceName(String) |
AndroidTracer.Builder(...).setPartialFlushThreshold(Int) | DatadogTracerBuilder.withPartialFlushMinSpans(Int) |
SpanContext.toTraceId() | DatadogSpanContext.traceId.toString() |
Span.setError() | DatadogSpan.addThrowable() |
After either path, verify that no source or dependency still relies on
io.opentracing.
The com.datadoghq:dd-sdk-android-okhttp artifact itself does not require
desugaring. Replace the removed positional constructors with builders while
preserving service names, traced hosts and header types, listeners, resource
attribute providers, and samplers.
| Removed v2 constructor family | v3 replacement |
|---|---|
TracingInterceptor(...) | TracingInterceptor.Builder(tracedHosts) |
DatadogInterceptor(...) | DatadogInterceptor.Builder(tracedHosts) |
Search every overload, including constructors that accept any combination of:
List<String> traced hosts;Map<String, Set<TracingHeaderType>> traced-host configuration;TracedRequestListener;RumResourceAttributesProvider;Sampler<Span>.The builder requires either List<String> or
Map<String, Set<TracingHeaderType>> in its constructor. There is no
zero-argument builder and no setTracedHostsWithHeaderType method. Do not drop
constructor arguments during the conversion; map each behavior to the
corresponding builder option:
val datadogInterceptor = DatadogInterceptor.Builder(tracedHostsWithHeaderType)
.setSdkInstanceName(sdkCore.name)
.setTracedRequestListener(tracedRequestListener)
.setRumResourceAttributesProvider(resourceAttributesProvider)
.setTraceSampler(DeterministicTraceSampler(20f))
.build()
val tracingInterceptor = TracingInterceptor.Builder(tracedHostsWithHeaderType)
.setSdkInstanceName(sdkCore.name)
.setTracedRequestListener(tracedRequestListener)
.setTraceSampler(DeterministicTraceSampler(20f))
.build()In v3, TracedRequestListener receives a Datadog span, even when the
application’s manual tracing uses OpenTelemetry:
val tracedRequestListener = object : TracedRequestListener {
override fun onRequestIntercepted(
request: Request,
span: DatadogSpan,
response: Response?,
throwable: Throwable?
) {
span.setTag("request.method", request.method)
}
}Update custom samplers from Sampler<io.opentracing.Span> to
Sampler<DatadogSpan>.
SessionReplayConfiguration.Builder.useCustomEndpoint must receive the full
Session Replay upload URL:
SessionReplay.enable(
SessionReplayConfiguration.Builder(...)
.useCustomEndpoint("https://example.com/session_replay/upload")
.build()
)Preserve the application’s existing Session Replay privacy and sampling configuration.
Run the project’s normal Gradle compilation task. Prefer the same compilation command used by the project’s CI.
Before finishing, confirm:
com.datadoghq:dd-sdk-android-* artifacts are on one stable 3.x
version;minSdk is at least 23 and Kotlin is at least 1.9;io.opentracing dependency or import remains;RumResourceMethod;GlobalRumMonitor.get();Fix only migration-related failures. Do not rewrite unrelated application code or remove instrumentation to obtain a successful compilation.
Report the original and resulting SDK versions, the migration path chosen for tracing, the files changed, and the exact Gradle compilation command that passed.
main, last pushed 6 November 2026.SKILL.md, not by matching a directory convention. 11 distinct layouts observed: agent-observability/*/SKILL.md, */SKILL.md, dd-android-sdk/*/SKILL.md, dd-apm/*/SKILL.md, dd-apm/k8s-ssi/*/SKILL.md, dd-apm/linux-ssi/*/SKILL.md, dd-apps/*/SKILL.md, dd-audit/*/SKILL.md, dd-browser-sdk/*/SKILL.md, dd-security/csm/*/SKILL.md, dd-software-delivery/*/SKILL.md.h1 and no skipped levels:.claude-plugin/marketplace.json by datadog-labs, declaring 1 plugin. It is read for editorial metadata only — never as the skill index, which is always the repository tree./datadog-labs/agent-skills.md, and each skill at its own .md URL.