diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0e1687e75c4..047a9f462c9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,11 @@
## Unreleased
+### Extensions
+
+* Stabilize `ServiceInstanceIdResourceProvider` by moving from incubator to autoconfigure module
+ ([#8410](https://github.com/open-telemetry/opentelemetry-java/issues/8410))
+
## Version 1.65.0 (2026-08-07)
**NOTE:** The `opentelemetry-exporter-zipkin` artifact has stopped being published. It was
diff --git a/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/EnvironmentResourceProvider.java b/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/EnvironmentResourceProvider.java
index 231967c833c..9b9c7a54803 100644
--- a/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/EnvironmentResourceProvider.java
+++ b/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/EnvironmentResourceProvider.java
@@ -24,7 +24,7 @@ public Resource createResource(ConfigProperties config) {
@Override
public int order() {
// Environment resource takes precedent over all other ResourceProviders except
- // ServiceInstanceIdResourceProvider.
+ // ServiceInstanceIdResourceProvider (which runs at Integer.MAX_VALUE).
return Integer.MAX_VALUE - 1;
}
}
diff --git a/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/resources/ServiceInstanceIdResourceProvider.java b/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/resources/ServiceInstanceIdResourceProvider.java
new file mode 100644
index 00000000000..3d5a16753bd
--- /dev/null
+++ b/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/resources/ServiceInstanceIdResourceProvider.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.sdk.autoconfigure.resources;
+
+import io.opentelemetry.api.common.AttributeKey;
+import io.opentelemetry.api.common.Attributes;
+import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
+import io.opentelemetry.sdk.autoconfigure.spi.ResourceProvider;
+import io.opentelemetry.sdk.autoconfigure.spi.internal.ConditionalResourceProvider;
+import io.opentelemetry.sdk.resources.Resource;
+import java.util.UUID;
+
+/**
+ * A {@link ConditionalResourceProvider} for {@code service.instance.id}. It implements {@link
+ * ConditionalResourceProvider} rather than a plain {@link ResourceProvider} because it depends on
+ * the attributes discovered by the other providers.
+ *
+ *
This provider generates a random UUID for {@code service.instance.id} if not already set by
+ * the user or another resource provider. The value is stable across calls to this provider within
+ * the same JVM instance.
+ *
+ * @since 1.47.0
+ */
+public final class ServiceInstanceIdResourceProvider implements ConditionalResourceProvider {
+
+ public static final AttributeKey SERVICE_INSTANCE_ID =
+ AttributeKey.stringKey("service.instance.id");
+
+ // multiple calls to this resource provider should return the same value
+ private static final Resource RANDOM =
+ Resource.create(Attributes.of(SERVICE_INSTANCE_ID, UUID.randomUUID().toString()));
+
+ static final int ORDER = Integer.MAX_VALUE;
+
+ @Override
+ public Resource createResource(ConfigProperties config) {
+ return RANDOM;
+ }
+
+ @Override
+ public boolean shouldApply(ConfigProperties config, Resource existing) {
+ return existing.getAttribute(SERVICE_INSTANCE_ID) == null;
+ }
+
+ @Override
+ public int order() {
+ // Run after environment resource provider - only set the service instance ID if it
+ // hasn't been set by any other provider or the user.
+ return ORDER;
+ }
+}
diff --git a/sdk-extensions/autoconfigure/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.ResourceProvider b/sdk-extensions/autoconfigure/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.ResourceProvider
index f9bd3554d79..f1f830c9554 100644
--- a/sdk-extensions/autoconfigure/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.ResourceProvider
+++ b/sdk-extensions/autoconfigure/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.ResourceProvider
@@ -1 +1,2 @@
io.opentelemetry.sdk.autoconfigure.EnvironmentResourceProvider
+io.opentelemetry.sdk.autoconfigure.resources.ServiceInstanceIdResourceProvider
diff --git a/sdk-extensions/autoconfigure/src/test/java/io/opentelemetry/sdk/autoconfigure/ResourceConfigurationTest.java b/sdk-extensions/autoconfigure/src/test/java/io/opentelemetry/sdk/autoconfigure/ResourceConfigurationTest.java
index d8e97e382ca..1b87bbd2072 100644
--- a/sdk-extensions/autoconfigure/src/test/java/io/opentelemetry/sdk/autoconfigure/ResourceConfigurationTest.java
+++ b/sdk-extensions/autoconfigure/src/test/java/io/opentelemetry/sdk/autoconfigure/ResourceConfigurationTest.java
@@ -41,17 +41,23 @@ void customConfigResourceWithDisabledKeys() {
"otel.resource.attributes", "food=cheesecake,drink=juice,animal= ,color=,shape=square");
props.put("otel.resource.disabled-keys", "drink");
- assertThat(
- ResourceConfiguration.configureResource(
- DefaultConfigProperties.create(props, componentLoader),
- SpiHelper.create(ResourceConfigurationTest.class.getClassLoader()),
- (r, c) -> r))
- .isEqualTo(
- Resource.getDefault().toBuilder()
- .put(stringKey("service.name"), "test-service")
- .put("food", "cheesecake")
- .put("shape", "square")
- .build());
+ Resource result =
+ ResourceConfiguration.configureResource(
+ DefaultConfigProperties.create(props, componentLoader),
+ SpiHelper.create(ResourceConfigurationTest.class.getClassLoader()),
+ (r, c) -> r);
+
+ // Verify expected attributes are present
+ assertThat(result.getAttribute(stringKey("service.name"))).isEqualTo("test-service");
+ assertThat(result.getAttribute(stringKey("food"))).isEqualTo("cheesecake");
+ assertThat(result.getAttribute(stringKey("shape"))).isEqualTo("square");
+ // Verify disabled attribute is not present
+ assertThat(result.getAttribute(stringKey("drink"))).isNull();
+ // Verify telemetry SDK attributes are present
+ assertThat(result.getAttribute(stringKey("telemetry.sdk.language"))).isEqualTo("java");
+ assertThat(result.getAttribute(stringKey("telemetry.sdk.name"))).isEqualTo("opentelemetry");
+ // Verify service.instance.id is now added by default via ServiceInstanceIdResourceProvider
+ assertThat(result.getAttribute(stringKey("service.instance.id"))).isNotNull();
}
@ParameterizedTest
diff --git a/sdk-extensions/autoconfigure/src/test/java/io/opentelemetry/sdk/autoconfigure/resources/ServiceInstanceIdResourceProviderTest.java b/sdk-extensions/autoconfigure/src/test/java/io/opentelemetry/sdk/autoconfigure/resources/ServiceInstanceIdResourceProviderTest.java
new file mode 100644
index 00000000000..5f925921ab8
--- /dev/null
+++ b/sdk-extensions/autoconfigure/src/test/java/io/opentelemetry/sdk/autoconfigure/resources/ServiceInstanceIdResourceProviderTest.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.sdk.autoconfigure.resources;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import com.google.common.collect.ImmutableMap;
+import io.opentelemetry.api.common.Attributes;
+import io.opentelemetry.api.common.AttributesBuilder;
+import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties;
+import io.opentelemetry.sdk.resources.Resource;
+import java.util.Collections;
+import java.util.Map;
+import java.util.stream.Stream;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+class ServiceInstanceIdResourceProviderTest {
+
+ @ParameterizedTest
+ @MethodSource("createResourceTestCases")
+ void createResource(String expectedValue, Map attributes) {
+ ServiceInstanceIdResourceProvider provider = new ServiceInstanceIdResourceProvider();
+ DefaultConfigProperties config = DefaultConfigProperties.createFromMap(Collections.emptyMap());
+ AttributesBuilder builder = Attributes.builder();
+ attributes.forEach(builder::put);
+ Resource existing = Resource.create(builder.build());
+ Resource resource =
+ provider.shouldApply(config, existing) ? provider.createResource(config) : Resource.empty();
+
+ String actual =
+ resource.getAttributes().get(ServiceInstanceIdResourceProvider.SERVICE_INSTANCE_ID);
+ if ("random".equals(expectedValue)) {
+ assertThat(actual).isNotNull();
+ } else {
+ assertThat(actual).isEqualTo(expectedValue);
+ }
+ }
+
+ static Stream createResourceTestCases() {
+ return Stream.of(
+ Arguments.argumentSet(
+ "user provided service.instance.id",
+ null,
+ ImmutableMap.of("service.instance.id", "custom")),
+ Arguments.argumentSet("random value", "random", Collections.emptyMap()));
+ }
+}
diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/resources/ServiceInstanceIdResourceProvider.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/resources/ServiceInstanceIdResourceProvider.java
index d3cb764c512..dd7fc3fe7d9 100644
--- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/resources/ServiceInstanceIdResourceProvider.java
+++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/resources/ServiceInstanceIdResourceProvider.java
@@ -17,7 +17,12 @@
* A {@link ConditionalResourceProvider} for {@code service.instance.id}. It implements {@link
* ConditionalResourceProvider} rather than a plain {@link ResourceProvider} because it depends on
* the attributes discovered by the other providers.
+ *
+ * @deprecated Use {@link
+ * io.opentelemetry.sdk.autoconfigure.resources.ServiceInstanceIdResourceProvider} instead. This
+ * class will be removed in a future release.
*/
+@Deprecated
public final class ServiceInstanceIdResourceProvider implements ConditionalResourceProvider {
public static final AttributeKey SERVICE_INSTANCE_ID =
diff --git a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/resources/ServiceInstanceIdResourceProviderTest.java b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/resources/ServiceInstanceIdResourceProviderTest.java
index b359b9fbcf0..9f442606ab8 100644
--- a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/resources/ServiceInstanceIdResourceProviderTest.java
+++ b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/resources/ServiceInstanceIdResourceProviderTest.java
@@ -23,6 +23,7 @@ class ServiceInstanceIdResourceProviderTest {
@ParameterizedTest
@MethodSource("createResourceTestCases")
+ @SuppressWarnings("OtelDeprecatedApiUsage")
void createResource(String expectedValue, Map attributes) {
ServiceInstanceIdResourceProvider provider = new ServiceInstanceIdResourceProvider();
DefaultConfigProperties config = DefaultConfigProperties.createFromMap(Collections.emptyMap());