diff --git a/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/otlp/internal/grpc/DefaultGrpcExporter.java b/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/otlp/internal/grpc/DefaultGrpcExporter.java
new file mode 100644
index 00000000000..47ad16d19e3
--- /dev/null
+++ b/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/otlp/internal/grpc/DefaultGrpcExporter.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.exporter.otlp.internal.grpc;
+
+import com.google.common.util.concurrent.FutureCallback;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.MoreExecutors;
+import io.grpc.Codec;
+import io.grpc.ManagedChannel;
+import io.grpc.Status;
+import io.opentelemetry.api.common.Attributes;
+import io.opentelemetry.api.metrics.BoundLongCounter;
+import io.opentelemetry.api.metrics.GlobalMeterProvider;
+import io.opentelemetry.api.metrics.LongCounter;
+import io.opentelemetry.api.metrics.Meter;
+import io.opentelemetry.exporter.otlp.internal.Marshaler;
+import io.opentelemetry.sdk.common.CompletableResultCode;
+import io.opentelemetry.sdk.internal.ThrottlingLogger;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+/**
+ * A {@link GrpcExporter} which uses the standard grpc-java library.
+ *
+ *
This class is internal and is hence not for public use. Its APIs are unstable and can change
+ * at any time.
+ */
+public final class DefaultGrpcExporter implements GrpcExporter {
+
+ private static final Logger internalLogger =
+ Logger.getLogger(DefaultGrpcExporter.class.getName());
+
+ private final ThrottlingLogger logger = new ThrottlingLogger(internalLogger);
+
+ private final String type;
+ private final ManagedChannel managedChannel;
+ private final MarshalerServiceStub stub;
+ private final long timeoutNanos;
+
+ private final BoundLongCounter seen;
+ private final BoundLongCounter success;
+ private final BoundLongCounter failed;
+
+ /** Creates a new {@link DefaultGrpcExporter}. */
+ public DefaultGrpcExporter(
+ String type,
+ ManagedChannel channel,
+ MarshalerServiceStub stub,
+ long timeoutNanos,
+ boolean compressionEnabled) {
+ this.type = type;
+ Meter meter = GlobalMeterProvider.get().get("io.opentelemetry.exporters.otlp-grpc");
+ Attributes attributes = Attributes.builder().put("type", type).build();
+ seen = meter.counterBuilder("otlp.exporter.seen").build().bind(attributes);
+ LongCounter exported = meter.counterBuilder("otlp.exported.exported").build();
+ success = exported.bind(attributes.toBuilder().put("success", true).build());
+ failed = exported.bind(attributes.toBuilder().put("success", false).build());
+
+ this.managedChannel = channel;
+ this.timeoutNanos = timeoutNanos;
+ Codec codec = compressionEnabled ? new Codec.Gzip() : Codec.Identity.NONE;
+ this.stub = stub.withCompression(codec.getMessageEncoding());
+ }
+
+ @Override
+ public CompletableResultCode export(T exportRequest, int numItems) {
+ seen.add(numItems);
+
+ CompletableResultCode result = new CompletableResultCode();
+
+ MarshalerServiceStub stub = this.stub;
+ if (timeoutNanos > 0) {
+ stub = stub.withDeadlineAfter(timeoutNanos, TimeUnit.NANOSECONDS);
+ }
+ Futures.addCallback(
+ stub.export(exportRequest),
+ new FutureCallback