diff --git a/instrumentation/jmx-metrics/library/cassandra.md b/instrumentation/jmx-metrics/library/cassandra.md
index a4eefe9e59e3..fc56e79b35db 100644
--- a/instrumentation/jmx-metrics/library/cassandra.md
+++ b/instrumentation/jmx-metrics/library/cassandra.md
@@ -2,15 +2,17 @@
Here is the list of metrics based on MBeans exposed by Cassandra.
-| Metric Name | Type | Unit | Attributes | Description |
-| ------------------------------------ | ------------- | --------- | ------------------------------------- | ---------------------------------------------------------------- |
-| cassandra.client.request.count | Counter | {request} | cassandra.operation | Number of requests by operation. |
-| cassandra.client.request.error | Counter | {error} | cassandra.operation, cassandra.status | Number of request errors by operation. |
-| cassandra.client.request.latency.p50 | Gauge | s | cassandra.operation | Request latency 50th percentile by operation. |
-| cassandra.client.request.latency.p99 | Gauge | s | cassandra.operation | Request latency 99th percentile by operation. |
-| cassandra.client.request.latency.max | Gauge | s | cassandra.operation | Maximum request latency by operation. |
-| cassandra.compaction.tasks.completed | Counter | {task} | | Number of completed compactions since server start. |
-| cassandra.compaction.tasks.pending | Gauge | {task} | | Estimated number of compactions remaining to perform. |
-| cassandra.storage.load | UpDownCounter | By | | Size of the on disk data size this node manages. |
-| cassandra.storage.hints.count | Counter | {hint} | | Number of hint messages written to this node since server start. |
-| cassandra.storage.hints.in_progress | UpDownCounter | {hint} | | Number of hints attempting to be sent currently. |
+| Metric Name | Type | Unit | Attributes | Description |
+| ------------------------------------ | ------------- | --------- | ------------------------------------------------------------------- | ---------------------------------------------------------------- |
+| cassandra.client.request.count | Counter | {request} | cassandra.operation | Number of requests by operation. |
+| cassandra.client.request.error | Counter | {error} | cassandra.operation, cassandra.status | Number of request errors by operation. |
+| cassandra.client.request.latency.p50 | Gauge | s | cassandra.operation | Request latency 50th percentile by operation. |
+| cassandra.client.request.latency.p99 | Gauge | s | cassandra.operation | Request latency 99th percentile by operation. |
+| cassandra.client.request.latency.max | Gauge | s | cassandra.operation | Maximum request latency by operation. |
+| cassandra.compaction.progress.bytes | Gauge | By | cassandra.compaction.task_type, cassandra.keyspace, cassandra.table | Bytes completed for in-flight compactions. |
+| cassandra.compaction.progress.total | Gauge | By | cassandra.compaction.task_type, cassandra.keyspace, cassandra.table | Total bytes for in-flight compactions. |
+| cassandra.compaction.tasks.completed | Counter | {task} | | Number of completed compactions since server start. |
+| cassandra.compaction.tasks.pending | Gauge | {task} | | Estimated number of compactions remaining to perform. |
+| cassandra.storage.load | UpDownCounter | By | | Size of the on disk data size this node manages. |
+| cassandra.storage.hints.count | Counter | {hint} | | Number of hint messages written to this node since server start. |
+| cassandra.storage.hints.in_progress | UpDownCounter | {hint} | | Number of hints attempting to be sent currently. |
diff --git a/instrumentation/jmx-metrics/library/src/main/java/io/opentelemetry/instrumentation/jmx/internal/handler/CassandraCompactionProgressHandler.java b/instrumentation/jmx-metrics/library/src/main/java/io/opentelemetry/instrumentation/jmx/internal/handler/CassandraCompactionProgressHandler.java
new file mode 100644
index 000000000000..cd2b0a43fa2e
--- /dev/null
+++ b/instrumentation/jmx-metrics/library/src/main/java/io/opentelemetry/instrumentation/jmx/internal/handler/CassandraCompactionProgressHandler.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.instrumentation.jmx.internal.handler;
+
+import static java.util.logging.Level.WARNING;
+
+import io.opentelemetry.api.common.Attributes;
+import io.opentelemetry.api.common.AttributesBuilder;
+import io.opentelemetry.api.metrics.Meter;
+import io.opentelemetry.api.metrics.ObservableLongMeasurement;
+import io.opentelemetry.instrumentation.jmx.internal.ExperimentalJmxMetricHandler;
+import java.math.BigInteger;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Supplier;
+import java.util.logging.Logger;
+import javax.annotation.Nullable;
+import javax.management.MBeanServerConnection;
+import javax.management.ObjectName;
+
+/**
+ * JMX metric handler that reports per-compaction byte progress from Cassandra's CompactionManager.
+ *
+ *
Queries {@code org.apache.cassandra.db:type=CompactionManager}, reads the {@code Compactions}
+ * attribute (a list of maps representing in-flight compactions), groups entries by {@code taskType
+ * + keyspace + columnfamily}, and emits two gauges per group:
+ *
+ *
+ * - {@code cassandra.compaction.progress.bytes} — bytes completed so far
+ *
- {@code cassandra.compaction.progress.total} — total bytes for the compaction
+ *
+ *
+ * Both metrics carry {@code cassandra.compaction.task_type}, {@code cassandra.keyspace}, and
+ * {@code cassandra.table} attributes. Entries missing any of these dimension fields are skipped.
+ * Byte values are string-encoded in the MBean and parsed via {@link BigInteger}.
+ *
+ *
Note: these are distinct from {@code cassandra.compaction.tasks.pending/completed}, which are
+ * simple scalar task counts from {@code org.apache.cassandra.metrics:type=Compaction}.
+ *
+ *
This class is internal and is hence not for public use. Its APIs are unstable and can change
+ * at any time.
+ */
+public class CassandraCompactionProgressHandler implements ExperimentalJmxMetricHandler {
+
+ static final String HANDLER_NAME = "cassandra-compaction-progress";
+ static final String METRIC_CURRENT = "cassandra.compaction.progress.bytes";
+ static final String METRIC_TOTAL = "cassandra.compaction.progress.total";
+
+ private static final String ATTR_TASK_TYPE = "cassandra.compaction.task_type";
+ private static final String ATTR_KEYSPACE = "cassandra.keyspace";
+ private static final String ATTR_TABLE = "cassandra.table";
+
+ private static final Logger logger =
+ Logger.getLogger(CassandraCompactionProgressHandler.class.getName());
+
+ @Override
+ public String getName() {
+ return HANDLER_NAME;
+ }
+
+ @Override
+ public AutoCloseable create(Meter meter, Supplier detectorSupplier) {
+ ObservableLongMeasurement currentGauge =
+ meter
+ .gaugeBuilder(METRIC_CURRENT)
+ .setDescription("Bytes completed for in-flight compactions")
+ .setUnit("By")
+ .ofLongs()
+ .buildObserver();
+
+ ObservableLongMeasurement totalGauge =
+ meter
+ .gaugeBuilder(METRIC_TOTAL)
+ .setDescription("Total bytes for in-flight compactions")
+ .setUnit("By")
+ .ofLongs()
+ .buildObserver();
+
+ return meter.batchCallback(
+ () -> {
+ for (Map.Entry entry : queryGroups(detectorSupplier).entrySet()) {
+ currentGauge.record(entry.getValue()[0], entry.getKey());
+ totalGauge.record(entry.getValue()[1], entry.getKey());
+ }
+ },
+ currentGauge,
+ totalGauge);
+ }
+
+ private static Map queryGroups(Supplier detectorSupplier) {
+ Map groups = new HashMap<>();
+ Detector detector = detectorSupplier.get();
+ if (detector == null) {
+ return groups;
+ }
+ MBeanServerConnection connection = detector.getConnection();
+ for (ObjectName objectName : detector.getObjectNames()) {
+ queryCompactions(connection, objectName)
+ .forEach(
+ (attrs, values) ->
+ groups.merge(attrs, values, (a, b) -> new long[] {a[0] + b[0], a[1] + b[1]}));
+ }
+ return groups;
+ }
+
+ static Map queryCompactions(
+ MBeanServerConnection connection, ObjectName objectName) {
+ Map groups = new HashMap<>();
+ try {
+ // CompactionManager#getCompactions() returns List