diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts
index d49f94e..59ec084 100644
--- a/buildSrc/build.gradle.kts
+++ b/buildSrc/build.gradle.kts
@@ -14,4 +14,5 @@ dependencies {
   implementation("me.champeau.gradle:japicmp-gradle-plugin:0.4.2")
   // Needed for japicmp but not automatically brought in for some reason.
   implementation("com.google.guava:guava:32.1.3-jre")
+  implementation("biz.aQute.bnd:biz.aQute.bnd.gradle:7.0.0")
 }
diff --git a/buildSrc/src/main/kotlin/io/opentelemetry/gradle/OtelJavaExtension.kt b/buildSrc/src/main/kotlin/io/opentelemetry/gradle/OtelJavaExtension.kt
index bf584e7..2ad241f 100644
--- a/buildSrc/src/main/kotlin/io/opentelemetry/gradle/OtelJavaExtension.kt
+++ b/buildSrc/src/main/kotlin/io/opentelemetry/gradle/OtelJavaExtension.kt
@@ -9,4 +9,5 @@ import org.gradle.api.provider.Property
 
 abstract class OtelJavaExtension {
     abstract val moduleName: Property<String>
+    abstract val bundleName: Property<String>
 }
diff --git a/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts b/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts
index d0bf775..77a961c 100644
--- a/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts
+++ b/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts
@@ -8,6 +8,7 @@ plugins {
   eclipse
   idea
 
+  id("biz.aQute.bnd.builder")
   id("otel.spotless-conventions")
 }
 
@@ -59,6 +60,9 @@ tasks {
       )
     }
 
+    systemProperty("felix.cache.dir", buildDir)
+    systemProperty("felix.bundle.path", "$buildDir/libs/${project.base.archivesName.get()}-${project.version}.jar")
+
     testLogging {
       exceptionFormat = TestExceptionFormat.FULL
       showExceptions = true
@@ -88,11 +92,17 @@ tasks {
 
     manifest {
       attributes(
-          "Automatic-Module-Name" to otelJava.moduleName,
+        "Automatic-Module-Name" to otelJava.moduleName,
         "Built-By" to System.getProperty("user.name"),
         "Built-JDK" to System.getProperty("java.version"),
         "Implementation-Title" to project.base.archivesName,
-        "Implementation-Version" to project.version)
+        "Implementation-Version" to project.version,
+        // Add OSGi manifest headers with bnd
+        "-exportcontents" to "${otelJava.moduleName.get()}.*",
+        "Bundle-Name" to otelJava.bundleName,
+        "Bundle-SymbolicName" to "${otelJava.moduleName.get()}.${project.base.archivesName.get()}",
+        "Import-Package" to "io.opentelemetry.api.*;resolution:=optional" // FIXME: should not be optional, dependency should be provided
+      )
     }
   }
 
diff --git a/semconv-incubating/build.gradle.kts b/semconv-incubating/build.gradle.kts
index 3a5eee4..e8f814b 100644
--- a/semconv-incubating/build.gradle.kts
+++ b/semconv-incubating/build.gradle.kts
@@ -10,6 +10,7 @@ base {
   archivesName.set("opentelemetry-semconv-incubating")
 }
 otelJava.moduleName.set("io.opentelemetry.semconv.incubating")
+otelJava.bundleName.set("OpenTelemetry - Semantic Conventions Incubating")
 
 dependencies {
   api(project(":semconv"))
diff --git a/semconv/build.gradle.kts b/semconv/build.gradle.kts
index 890c43f..19c2b30 100644
--- a/semconv/build.gradle.kts
+++ b/semconv/build.gradle.kts
@@ -12,9 +12,12 @@ base {
   archivesName.set("opentelemetry-semconv")
 }
 otelJava.moduleName.set("io.opentelemetry.semconv")
+otelJava.bundleName.set("OpenTelemetry - Semantic Conventions")
 
 dependencies {
   compileOnly("io.opentelemetry:opentelemetry-api")
 
   testImplementation("io.opentelemetry:opentelemetry-api")
+  testImplementation("org.apache.felix:org.apache.felix.framework:7.0.5")
+  testImplementation("org.osgi:osgi.core:6.0.0")
 }
diff --git a/semconv/src/test/java/io/opentelemetry/semconv/OSGiBundleTest.java b/semconv/src/test/java/io/opentelemetry/semconv/OSGiBundleTest.java
new file mode 100644
index 0000000..2b120af
--- /dev/null
+++ b/semconv/src/test/java/io/opentelemetry/semconv/OSGiBundleTest.java
@@ -0,0 +1,34 @@
+package io.opentelemetry.semconv;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+import org.apache.felix.framework.Felix;
+import org.junit.jupiter.api.Test;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.Constants;
+import org.osgi.framework.launch.Framework;
+
+class OSGiBundleTest {
+  @Test
+  void bundleIsActive() throws BundleException {
+    Map<String,String> params = new HashMap<String, String>();
+    params.put(Constants.FRAMEWORK_STORAGE, System.getProperty("felix.cache.dir"));
+
+    Framework framework = new Felix(params);
+    framework.init();
+    framework.start();
+
+    BundleContext context = framework.getBundleContext();
+    File bundleFile = new File(System.getProperty("felix.bundle.path"));
+
+    Bundle bundle = context.installBundle(bundleFile.toURI().toString());
+    bundle.start();
+
+    assertEquals(Bundle.ACTIVE, bundle.getState());
+  }
+}