diff --git a/docs/supported-libraries.md b/docs/supported-libraries.md
index 7ff7d2a70f22..3d274eeea72c 100644
--- a/docs/supported-libraries.md
+++ b/docs/supported-libraries.md
@@ -94,6 +94,7 @@ These are the supported libraries and frameworks:
| [Logback](http://logback.qos.ch/) | 1.0+ | [opentelemetry-logback-appender-1.0](../instrumentation/logback/logback-appender-1.0/library),
[opentelemetry-logback-mdc-1.0](../instrumentation/logback/logback-mdc-1.0/library) | none |
| [Micrometer](https://micrometer.io/) | 1.5+ | [opentelemetry-micrometer-1.5](../instrumentation/micrometer/micrometer-1.5/library) | none |
| [MongoDB Driver](https://mongodb.github.io/mongo-java-driver/) | 3.1+ | [opentelemetry-mongo-3.1](../instrumentation/mongo/mongo-3.1/library) | [Database Client Spans] |
+| [MyBatis](https://mybatis.org/mybatis-3/) | 3.2+ | N/A | none |
| [Netty](https://github.com/netty/netty) | 3.8+ | [opentelemetry-netty-4.1](../instrumentation/netty/netty-4.1/library) | [HTTP Client Spans], [HTTP Client Metrics], [HTTP Server Spans], [HTTP Server Metrics] |
| [OkHttp](https://github.com/square/okhttp/) | 2.2+ | [opentelemetry-okhttp-3.0](../instrumentation/okhttp/okhttp-3.0/library) | [HTTP Client Spans], [HTTP Client Metrics] |
| [Oracle UCP](https://docs.oracle.com/database/121/JJUCP/) | 11.2+ | [opentelemetry-oracle-ucp-11.2](../instrumentation/oracle-ucp-11.2/library) | [Database Pool Metrics] |
diff --git a/instrumentation/mybatis-3.2/javaagent/build.gradle.kts b/instrumentation/mybatis-3.2/javaagent/build.gradle.kts
new file mode 100644
index 000000000000..c7c8a098ea77
--- /dev/null
+++ b/instrumentation/mybatis-3.2/javaagent/build.gradle.kts
@@ -0,0 +1,26 @@
+plugins {
+ id("otel.javaagent-instrumentation")
+}
+
+muzzle {
+ pass {
+ group.set("org.mybatis")
+ module.set("mybatis")
+ versions.set("[3.2.0,)")
+ assertInverse.set(true)
+ }
+}
+
+dependencies {
+ library("org.mybatis:mybatis:3.2.0")
+
+ testImplementation("com.h2database:h2:1.4.191")
+}
+
+tasks.withType().configureEach {
+ jvmArgs("-Dotel.instrumentation.mybatis.enabled=true")
+
+ // required on jdk17
+ jvmArgs("--add-opens=java.base/java.lang=ALL-UNNAMED")
+ jvmArgs("-XX:+IgnoreUnrecognizedVMOptions")
+}
diff --git a/instrumentation/mybatis-3.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mybatis/v3_2/MapperMethodInstrumentation.java b/instrumentation/mybatis-3.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mybatis/v3_2/MapperMethodInstrumentation.java
new file mode 100644
index 000000000000..741005717f60
--- /dev/null
+++ b/instrumentation/mybatis-3.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mybatis/v3_2/MapperMethodInstrumentation.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.mybatis.v3_2;
+
+import static io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge.currentContext;
+import static io.opentelemetry.javaagent.instrumentation.mybatis.v3_2.MyBatisSingletons.instrumenter;
+import static net.bytebuddy.matcher.ElementMatchers.named;
+
+import io.opentelemetry.context.Context;
+import io.opentelemetry.context.Scope;
+import io.opentelemetry.instrumentation.api.incubator.semconv.util.ClassAndMethod;
+import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
+import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
+import net.bytebuddy.asm.Advice;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.matcher.ElementMatcher;
+import org.apache.ibatis.binding.MapperMethod.SqlCommand;
+
+public class MapperMethodInstrumentation implements TypeInstrumentation {
+
+ @Override
+ public ElementMatcher typeMatcher() {
+ return named("org.apache.ibatis.binding.MapperMethod");
+ }
+
+ @Override
+ public void transform(TypeTransformer transformer) {
+ transformer.applyAdviceToMethod(
+ named("execute"), MapperMethodInstrumentation.class.getName() + "$ExecuteAdvice");
+ }
+
+ @SuppressWarnings("unused")
+ public static class ExecuteAdvice {
+
+ @Advice.OnMethodEnter(suppress = Throwable.class)
+ public static void getMapperInfo(
+ @Advice.FieldValue("command") SqlCommand command,
+ @Advice.Local("otelRequest") ClassAndMethod request,
+ @Advice.Local("otelContext") Context context,
+ @Advice.Local("otelScope") Scope scope) {
+ if (command == null) {
+ return;
+ }
+ request = SqlCommandUtil.getClassAndMethod(command);
+ if (request == null) {
+ return;
+ }
+ Context parentContext = currentContext();
+ if (!instrumenter().shouldStart(parentContext, request)) {
+ return;
+ }
+ context = instrumenter().start(parentContext, request);
+ scope = context.makeCurrent();
+ }
+
+ @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
+ public static void stopSpan(
+ @Advice.Thrown Throwable throwable,
+ @Advice.Local("otelRequest") ClassAndMethod request,
+ @Advice.Local("otelContext") Context context,
+ @Advice.Local("otelScope") Scope scope) {
+ if (scope != null) {
+ scope.close();
+ instrumenter().end(context, request, null, throwable);
+ }
+ }
+ }
+}
diff --git a/instrumentation/mybatis-3.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mybatis/v3_2/MyBatisInstrumentationModule.java b/instrumentation/mybatis-3.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mybatis/v3_2/MyBatisInstrumentationModule.java
new file mode 100644
index 000000000000..55d4910ef232
--- /dev/null
+++ b/instrumentation/mybatis-3.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mybatis/v3_2/MyBatisInstrumentationModule.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.mybatis.v3_2;
+
+import static java.util.Arrays.asList;
+
+import com.google.auto.service.AutoService;
+import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
+import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
+import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
+import java.util.List;
+
+@AutoService(InstrumentationModule.class)
+public class MyBatisInstrumentationModule extends InstrumentationModule {
+
+ public MyBatisInstrumentationModule() {
+ super("mybatis", "mybatis-3.2");
+ }
+
+ @Override
+ public List typeInstrumentations() {
+ return asList(new MapperMethodInstrumentation(), new SqlCommandInstrumentation());
+ }
+
+ @Override
+ public boolean defaultEnabled(ConfigProperties config) {
+ return false;
+ }
+}
diff --git a/instrumentation/mybatis-3.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mybatis/v3_2/MyBatisSingletons.java b/instrumentation/mybatis-3.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mybatis/v3_2/MyBatisSingletons.java
new file mode 100644
index 000000000000..b24eac8cce1e
--- /dev/null
+++ b/instrumentation/mybatis-3.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mybatis/v3_2/MyBatisSingletons.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.mybatis.v3_2;
+
+import io.opentelemetry.api.GlobalOpenTelemetry;
+import io.opentelemetry.instrumentation.api.incubator.semconv.code.CodeAttributesExtractor;
+import io.opentelemetry.instrumentation.api.incubator.semconv.code.CodeAttributesGetter;
+import io.opentelemetry.instrumentation.api.incubator.semconv.code.CodeSpanNameExtractor;
+import io.opentelemetry.instrumentation.api.incubator.semconv.util.ClassAndMethod;
+import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
+import io.opentelemetry.instrumentation.api.instrumenter.SpanKindExtractor;
+
+public final class MyBatisSingletons {
+ private static final String INSTRUMENTATION_NAME = "io.opentelemetry.mybatis-3.2";
+ private static final Instrumenter INSTRUMENTER;
+
+ static {
+ CodeAttributesGetter codeAttributesGetter =
+ ClassAndMethod.codeAttributesGetter();
+
+ INSTRUMENTER =
+ Instrumenter.builder(
+ GlobalOpenTelemetry.get(),
+ INSTRUMENTATION_NAME,
+ CodeSpanNameExtractor.create(codeAttributesGetter))
+ .addAttributesExtractor(CodeAttributesExtractor.create(codeAttributesGetter))
+ .buildInstrumenter(SpanKindExtractor.alwaysInternal());
+ }
+
+ public static Instrumenter instrumenter() {
+ return INSTRUMENTER;
+ }
+
+ private MyBatisSingletons() {}
+}
diff --git a/instrumentation/mybatis-3.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mybatis/v3_2/SqlCommandInstrumentation.java b/instrumentation/mybatis-3.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mybatis/v3_2/SqlCommandInstrumentation.java
new file mode 100644
index 000000000000..c137bd294082
--- /dev/null
+++ b/instrumentation/mybatis-3.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mybatis/v3_2/SqlCommandInstrumentation.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.mybatis.v3_2;
+
+import static net.bytebuddy.matcher.ElementMatchers.isConstructor;
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
+
+import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
+import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
+import java.lang.reflect.Method;
+import net.bytebuddy.asm.Advice;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.matcher.ElementMatcher;
+import org.apache.ibatis.binding.MapperMethod.SqlCommand;
+
+public class SqlCommandInstrumentation implements TypeInstrumentation {
+ @Override
+ public ElementMatcher typeMatcher() {
+ return named("org.apache.ibatis.binding.MapperMethod$SqlCommand");
+ }
+
+ @Override
+ public void transform(TypeTransformer transformer) {
+ transformer.applyAdviceToMethod(
+ isConstructor().and(takesArgument(1, Class.class)).and(takesArgument(2, Method.class)),
+ SqlCommandInstrumentation.class.getName() + "$ConstructorAdvice");
+ }
+
+ @SuppressWarnings("unused")
+ public static class ConstructorAdvice {
+
+ @Advice.OnMethodExit(suppress = Throwable.class)
+ public static void onExit(
+ @Advice.This SqlCommand command,
+ @Advice.Argument(1) Class> mapperInterface,
+ @Advice.Argument(2) Method method) {
+ SqlCommandUtil.setClassAndMethod(command, mapperInterface, method);
+ }
+ }
+}
diff --git a/instrumentation/mybatis-3.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mybatis/v3_2/SqlCommandUtil.java b/instrumentation/mybatis-3.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mybatis/v3_2/SqlCommandUtil.java
new file mode 100644
index 000000000000..f67692f47229
--- /dev/null
+++ b/instrumentation/mybatis-3.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mybatis/v3_2/SqlCommandUtil.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.mybatis.v3_2;
+
+import io.opentelemetry.instrumentation.api.incubator.semconv.util.ClassAndMethod;
+import io.opentelemetry.instrumentation.api.util.VirtualField;
+import java.lang.reflect.Method;
+import org.apache.ibatis.binding.MapperMethod.SqlCommand;
+
+public final class SqlCommandUtil {
+ private static final VirtualField field =
+ VirtualField.find(SqlCommand.class, ClassAndMethod.class);
+
+ public static void setClassAndMethod(SqlCommand command, Class> clazz, Method method) {
+ if (clazz == null || method == null || method.getName() == null) {
+ return;
+ }
+ field.set(command, ClassAndMethod.create(clazz, method.getName()));
+ }
+
+ public static ClassAndMethod getClassAndMethod(SqlCommand command) {
+ return field.get(command);
+ }
+
+ private SqlCommandUtil() {}
+}
diff --git a/instrumentation/mybatis-3.2/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/mybatis/v3_2/MyBatisTest.java b/instrumentation/mybatis-3.2/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/mybatis/v3_2/MyBatisTest.java
new file mode 100644
index 000000000000..42b81e0b1ee0
--- /dev/null
+++ b/instrumentation/mybatis-3.2/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/mybatis/v3_2/MyBatisTest.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.mybatis.v3_2;
+
+import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo;
+
+import io.opentelemetry.api.trace.SpanKind;
+import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension;
+import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
+import io.opentelemetry.semconv.SemanticAttributes;
+import org.apache.ibatis.mapping.Environment;
+import org.apache.ibatis.session.Configuration;
+import org.apache.ibatis.session.SqlSession;
+import org.apache.ibatis.session.SqlSessionFactoryBuilder;
+import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory;
+import org.h2.jdbcx.JdbcDataSource;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+class MyBatisTest {
+
+ @RegisterExtension
+ private static final InstrumentationExtension testing = AgentInstrumentationExtension.create();
+
+ private static SqlSession sqlSession;
+
+ @BeforeAll
+ static void setUp() {
+ JdbcDataSource dataSource = new JdbcDataSource();
+ dataSource.setURL("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1");
+ Configuration configuration = new Configuration();
+ configuration.setEnvironment(new Environment("test", new JdbcTransactionFactory(), dataSource));
+ configuration.addMapper(TestMapper.class);
+ sqlSession = new SqlSessionFactoryBuilder().build(configuration).openSession();
+ }
+
+ @AfterAll
+ static void cleanUp() {
+ if (sqlSession != null) {
+ sqlSession.close();
+ }
+ }
+
+ @Test
+ void testSelect() {
+ TestMapper testMapper = sqlSession.getMapper(TestMapper.class);
+ testMapper.select();
+
+ testing.waitAndAssertTraces(
+ trace ->
+ trace.hasSpansSatisfyingExactly(
+ span ->
+ span.hasKind(SpanKind.INTERNAL)
+ .hasName("TestMapper.select")
+ .hasAttributesSatisfyingExactly(
+ equalTo(SemanticAttributes.CODE_NAMESPACE, TestMapper.class.getName()),
+ equalTo(SemanticAttributes.CODE_FUNCTION, "select"))));
+ }
+}
diff --git a/instrumentation/mybatis-3.2/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/mybatis/v3_2/TestMapper.java b/instrumentation/mybatis-3.2/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/mybatis/v3_2/TestMapper.java
new file mode 100644
index 000000000000..1d5e858a91ce
--- /dev/null
+++ b/instrumentation/mybatis-3.2/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/mybatis/v3_2/TestMapper.java
@@ -0,0 +1,14 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.mybatis.v3_2;
+
+import org.apache.ibatis.annotations.Select;
+
+public interface TestMapper {
+
+ @Select("SELECT 1")
+ int select();
+}
diff --git a/settings.gradle.kts b/settings.gradle.kts
index ae4b4afe767a..785e057fe3d9 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -384,6 +384,7 @@ include(":instrumentation:mongo:mongo-3.7:javaagent")
include(":instrumentation:mongo:mongo-4.0:javaagent")
include(":instrumentation:mongo:mongo-async-3.3:javaagent")
include(":instrumentation:mongo:mongo-common:testing")
+include(":instrumentation:mybatis-3.2:javaagent")
include(":instrumentation:netty:netty-3.8:javaagent")
include(":instrumentation:netty:netty-4.0:javaagent")
include(":instrumentation:netty:netty-4.1:javaagent")