diff --git a/examples/example-exporter-multi-target/src/main/java/io/prometheus/metrics/examples/multitarget/Main.java b/examples/example-exporter-multi-target/src/main/java/io/prometheus/metrics/examples/multitarget/Main.java
index da36346b9..9c7c9c495 100644
--- a/examples/example-exporter-multi-target/src/main/java/io/prometheus/metrics/examples/multitarget/Main.java
+++ b/examples/example-exporter-multi-target/src/main/java/io/prometheus/metrics/examples/multitarget/Main.java
@@ -12,7 +12,7 @@ public class Main {
 
     public static void main(String[] args) throws IOException, InterruptedException {
 
-        SampleMultiCollector xmc = new SampleMultiCollector();
+        SampleCollector xmc = new SampleCollector();
         PrometheusRegistry.defaultRegistry.register(xmc);
         HTTPServer server = HTTPServer.builder()
                 .port(9401)
diff --git a/examples/example-exporter-multi-target/src/main/java/io/prometheus/metrics/examples/multitarget/SampleMultiCollector.java b/examples/example-exporter-multi-target/src/main/java/io/prometheus/metrics/examples/multitarget/SampleCollector.java
similarity index 73%
rename from examples/example-exporter-multi-target/src/main/java/io/prometheus/metrics/examples/multitarget/SampleMultiCollector.java
rename to examples/example-exporter-multi-target/src/main/java/io/prometheus/metrics/examples/multitarget/SampleCollector.java
index 819bb3028..71d8f8672 100644
--- a/examples/example-exporter-multi-target/src/main/java/io/prometheus/metrics/examples/multitarget/SampleMultiCollector.java
+++ b/examples/example-exporter-multi-target/src/main/java/io/prometheus/metrics/examples/multitarget/SampleCollector.java
@@ -1,37 +1,23 @@
 package io.prometheus.metrics.examples.multitarget;
 
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
+import java.util.function.Predicate;
 
-import io.prometheus.metrics.model.registry.MultiCollector;
+import io.prometheus.metrics.model.registry.Collector;
 import io.prometheus.metrics.model.registry.PrometheusScrapeRequest;
 import io.prometheus.metrics.model.snapshots.CounterSnapshot;
 import io.prometheus.metrics.model.snapshots.CounterSnapshot.CounterDataPointSnapshot.Builder;
 import io.prometheus.metrics.model.snapshots.GaugeSnapshot;
 import io.prometheus.metrics.model.snapshots.Labels;
-import io.prometheus.metrics.model.snapshots.MetricSnapshot;
 import io.prometheus.metrics.model.snapshots.MetricSnapshots;
 import io.prometheus.metrics.model.snapshots.PrometheusNaming;
 
-public class SampleMultiCollector implements MultiCollector {
-
-	public SampleMultiCollector() {
-		super();
-	}
-	
-	@Override
-	public MetricSnapshots collect() {
-		return new MetricSnapshots();
-	}
-
+public class SampleCollector implements Collector {
 	@Override
-	public MetricSnapshots collect(PrometheusScrapeRequest scrapeRequest) {
-		return collectMetricSnapshots(scrapeRequest);
+	public MetricSnapshots collect(Predicate<String> nameFilter, PrometheusScrapeRequest scrapeRequest) {
+		return collectMetricSnapshots(scrapeRequest).filter(nameFilter);
 	}
 
 	protected MetricSnapshots collectMetricSnapshots(PrometheusScrapeRequest scrapeRequest) {
-
 		GaugeSnapshot.Builder gaugeBuilder = GaugeSnapshot.builder();
 		gaugeBuilder.name("x_load").help("process load");
 
@@ -71,18 +57,7 @@ protected MetricSnapshots collectMetricSnapshots(PrometheusScrapeRequest scrapeR
 				gaugeBuilder.dataPoint(gaugeDataPointBuilder.build());
 			}
 		}
-		Collection<MetricSnapshot> snaps = new ArrayList<MetricSnapshot>();
-		snaps.add(counterBuilder.build());
-		snaps.add(gaugeBuilder.build());
-		MetricSnapshots msnaps = new MetricSnapshots(snaps);
+		MetricSnapshots msnaps = new MetricSnapshots(counterBuilder.build(), gaugeBuilder.build());
 		return msnaps;
 	}
-
-	public List<String> getPrometheusNames() {
-		List<String> names = new ArrayList<String>();
-		names.add("x_calls_total");
-		names.add("x_load");
-		return names;
-	}
-
 }
diff --git a/integration-tests/it-exporter/it-exporter-httpserver-sample/src/main/java/io/prometheus/metrics/it/exporter/httpserver/HTTPServerSample.java b/integration-tests/it-exporter/it-exporter-httpserver-sample/src/main/java/io/prometheus/metrics/it/exporter/httpserver/HTTPServerSample.java
index 9187ecfcb..a2602aa82 100644
--- a/integration-tests/it-exporter/it-exporter-httpserver-sample/src/main/java/io/prometheus/metrics/it/exporter/httpserver/HTTPServerSample.java
+++ b/integration-tests/it-exporter/it-exporter-httpserver-sample/src/main/java/io/prometheus/metrics/it/exporter/httpserver/HTTPServerSample.java
@@ -4,9 +4,9 @@
 import io.prometheus.metrics.core.metrics.Gauge;
 import io.prometheus.metrics.core.metrics.Info;
 import io.prometheus.metrics.exporter.httpserver.HTTPServer;
+import io.prometheus.metrics.model.registry.CollectorBuilder;
 import io.prometheus.metrics.model.registry.Collector;
 import io.prometheus.metrics.model.registry.PrometheusRegistry;
-import io.prometheus.metrics.model.snapshots.MetricSnapshot;
 import io.prometheus.metrics.model.snapshots.Unit;
 
 import java.io.IOException;
@@ -53,9 +53,9 @@ public static void main(String[] args) throws IOException, InterruptedException
         gauge.labelValues("outside").set(27.0);
 
         if (mode == Mode.error) {
-            Collector failingCollector = () -> {
+            Collector failingCollector = CollectorBuilder.fromMetric(() -> {
                 throw new RuntimeException("Simulating an error.");
-            };
+            });
 
             PrometheusRegistry.defaultRegistry.register(failingCollector);
         }
diff --git a/integration-tests/it-exporter/it-exporter-servlet-jetty-sample/src/main/java/io/prometheus/metrics/it/exporter/servlet/jetty/ExporterServletJettySample.java b/integration-tests/it-exporter/it-exporter-servlet-jetty-sample/src/main/java/io/prometheus/metrics/it/exporter/servlet/jetty/ExporterServletJettySample.java
index 9e1a22487..af506eec0 100644
--- a/integration-tests/it-exporter/it-exporter-servlet-jetty-sample/src/main/java/io/prometheus/metrics/it/exporter/servlet/jetty/ExporterServletJettySample.java
+++ b/integration-tests/it-exporter/it-exporter-servlet-jetty-sample/src/main/java/io/prometheus/metrics/it/exporter/servlet/jetty/ExporterServletJettySample.java
@@ -4,9 +4,9 @@
 import io.prometheus.metrics.core.metrics.Gauge;
 import io.prometheus.metrics.core.metrics.Info;
 import io.prometheus.metrics.exporter.servlet.jakarta.PrometheusMetricsServlet;
+import io.prometheus.metrics.model.registry.CollectorBuilder;
 import io.prometheus.metrics.model.registry.Collector;
 import io.prometheus.metrics.model.registry.PrometheusRegistry;
-import io.prometheus.metrics.model.snapshots.MetricSnapshot;
 import io.prometheus.metrics.model.snapshots.Unit;
 import org.eclipse.jetty.server.Connector;
 import org.eclipse.jetty.server.Server;
@@ -57,9 +57,9 @@ public static void main(String[] args) throws Exception {
         gauge.labelValues("outside").set(27.0);
 
         if (mode == Mode.error) {
-            Collector failingCollector = () -> {
+            Collector failingCollector = CollectorBuilder.fromMetrics(() -> {
                 throw new RuntimeException("Simulating an error.");
-            };
+            });
 
             PrometheusRegistry.defaultRegistry.register(failingCollector);
         }
diff --git a/integration-tests/it-exporter/it-exporter-servlet-tomcat-sample/src/main/java/io/prometheus/metrics/it/exporter/servlet/tomcat/ExporterServletTomcatSample.java b/integration-tests/it-exporter/it-exporter-servlet-tomcat-sample/src/main/java/io/prometheus/metrics/it/exporter/servlet/tomcat/ExporterServletTomcatSample.java
index 8d13082b7..cd75a7832 100644
--- a/integration-tests/it-exporter/it-exporter-servlet-tomcat-sample/src/main/java/io/prometheus/metrics/it/exporter/servlet/tomcat/ExporterServletTomcatSample.java
+++ b/integration-tests/it-exporter/it-exporter-servlet-tomcat-sample/src/main/java/io/prometheus/metrics/it/exporter/servlet/tomcat/ExporterServletTomcatSample.java
@@ -4,9 +4,9 @@
 import io.prometheus.metrics.core.metrics.Gauge;
 import io.prometheus.metrics.core.metrics.Info;
 import io.prometheus.metrics.exporter.servlet.jakarta.PrometheusMetricsServlet;
+import io.prometheus.metrics.model.registry.CollectorBuilder;
 import io.prometheus.metrics.model.registry.Collector;
 import io.prometheus.metrics.model.registry.PrometheusRegistry;
-import io.prometheus.metrics.model.snapshots.MetricSnapshot;
 import io.prometheus.metrics.model.snapshots.Unit;
 import org.apache.catalina.Context;
 import org.apache.catalina.LifecycleException;
@@ -61,9 +61,9 @@ public static void main(String[] args) throws LifecycleException, IOException {
         gauge.labelValues("outside").set(27.0);
 
         if (mode == Mode.error) {
-            Collector failingCollector = () -> {
+            Collector failingCollector = CollectorBuilder.fromMetrics(() -> {
                 throw new RuntimeException("Simulating an error.");
-            };
+            });
 
             PrometheusRegistry.defaultRegistry.register(failingCollector);
         }
diff --git a/prometheus-metrics-core/pom.xml b/prometheus-metrics-core/pom.xml
index 7773301d0..9576bab78 100644
--- a/prometheus-metrics-core/pom.xml
+++ b/prometheus-metrics-core/pom.xml
@@ -19,6 +19,18 @@
     <properties>
         <automatic.module.name>io.prometheus.metrics.core</automatic.module.name>
     </properties>
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <source>16</source>
+                    <target>16</target>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
 
     <licenses>
         <license>
diff --git a/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Metric.java b/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Metric.java
index b7db83208..b10e4e932 100644
--- a/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Metric.java
+++ b/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Metric.java
@@ -2,6 +2,7 @@
 
 import io.prometheus.metrics.config.PrometheusProperties;
 import io.prometheus.metrics.model.registry.Collector;
+import io.prometheus.metrics.model.registry.CollectorBuilder;
 import io.prometheus.metrics.model.registry.PrometheusRegistry;
 import io.prometheus.metrics.model.snapshots.Label;
 import io.prometheus.metrics.model.snapshots.Labels;
@@ -21,9 +22,6 @@ protected Metric(Builder<?, ?> builder) {
         this.constLabels = builder.constLabels;
     }
 
-    @Override
-    public abstract MetricSnapshot collect();
-
     protected static abstract class Builder<B extends Builder<B, M>, M extends Metric> {
 
         protected final List<String> illegalLabelNames;
diff --git a/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/MetricWithFixedMetadata.java b/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/MetricWithFixedMetadata.java
index 9d39593eb..27fcd4a62 100644
--- a/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/MetricWithFixedMetadata.java
+++ b/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/MetricWithFixedMetadata.java
@@ -1,13 +1,15 @@
 package io.prometheus.metrics.core.metrics;
 
 import io.prometheus.metrics.config.PrometheusProperties;
-import io.prometheus.metrics.model.snapshots.Labels;
-import io.prometheus.metrics.model.snapshots.MetricMetadata;
-import io.prometheus.metrics.model.snapshots.PrometheusNaming;
-import io.prometheus.metrics.model.snapshots.Unit;
+import io.prometheus.metrics.model.registry.Collector;
+import io.prometheus.metrics.model.registry.CollectorBuilder;
+import io.prometheus.metrics.model.registry.PrometheusRegistry;
+import io.prometheus.metrics.model.registry.PrometheusScrapeRequest;
+import io.prometheus.metrics.model.snapshots.*;
 
 import java.util.Arrays;
 import java.util.List;
+import java.util.function.Predicate;
 
 /**
  * Almost all metrics have fixed metadata, i.e. the metric name is known when the metric is created.
@@ -30,6 +32,15 @@ protected MetricMetadata getMetadata() {
         return metadata;
     }
 
+    protected abstract MetricSnapshot collect();
+
+    public MetricSnapshots collect(Predicate<String> includedNames, PrometheusScrapeRequest scrapeRequest) {
+        if(includedNames.test(this.metadata.getPrometheusName()))
+            return MetricSnapshots.of(collect());
+        else
+            return MetricSnapshots.empty();
+    }
+
     private String makeName(String name, Unit unit) {
         if (unit != null) {
             if (!name.endsWith(unit.toString())) {
@@ -39,11 +50,6 @@ private String makeName(String name, Unit unit) {
         return name;
     }
 
-    @Override
-    public String getPrometheusName() {
-        return metadata.getPrometheusName();
-    }
-
     public static abstract class Builder<B extends Builder<B, M>, M extends MetricWithFixedMetadata> extends Metric.Builder<B, M> {
 
         protected String name;
diff --git a/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/NameFilterTest.java b/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/NameFilterTest.java
new file mode 100644
index 000000000..836a1a26f
--- /dev/null
+++ b/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/NameFilterTest.java
@@ -0,0 +1,61 @@
+package io.prometheus.metrics.core.metrics;
+
+import io.prometheus.metrics.model.registry.MetricNameFilter;
+import io.prometheus.metrics.model.registry.PrometheusRegistry;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class NameFilterTest {
+    @Test
+    public void testCounterWithCallback() {
+        AtomicInteger accessCount = new AtomicInteger();
+        CounterWithCallback metrics = CounterWithCallback.builder()
+                .name("my_counter")
+                .callback(cb -> {
+                    accessCount.incrementAndGet();
+                    cb.call(1.0);
+                })
+                .build();
+
+
+        PrometheusRegistry registry = new PrometheusRegistry();
+        registry.register(metrics);
+
+        var result1 = registry.scrape(MetricNameFilter.builder().nameMustBeEqualTo("XXX").build());
+        Assert.assertEquals(accessCount.get(), 0);
+        Assert.assertTrue(result1.stream().toList().isEmpty());
+
+        var result2 = registry.scrape(MetricNameFilter.builder().nameMustBeEqualTo("my_counter").build());
+        Assert.assertEquals(accessCount.get(), 1);
+        Assert.assertEquals(result2.stream().toList().size(), 1);
+        Assert.assertEquals(result2.get(0).getMetadata().getPrometheusName(), "my_counter");
+    }
+
+    @Test
+    public void testGaugeWithCallback() {
+        AtomicInteger accessCount = new AtomicInteger();
+        GaugeWithCallback metrics = GaugeWithCallback.builder()
+                .name("my_gauge")
+                .callback(cb -> {
+                    accessCount.incrementAndGet();
+                    cb.call(1.0);
+                })
+                .build();
+
+
+        PrometheusRegistry registry = new PrometheusRegistry();
+        registry.register(metrics);
+
+        var result1 = registry.scrape(MetricNameFilter.builder().nameMustBeEqualTo("XXX").build());
+        Assert.assertEquals(accessCount.get(), 0);
+        Assert.assertTrue(result1.stream().toList().isEmpty());
+
+        var result2 = registry.scrape(MetricNameFilter.builder().nameMustBeEqualTo("my_gauge").build());
+        Assert.assertEquals(accessCount.get(), 1);
+        Assert.assertEquals(result2.stream().toList().size(), 1);
+        Assert.assertEquals(result2.get(0).getMetadata().getPrometheusName(), "my_gauge");
+    }
+
+}
diff --git a/prometheus-metrics-exporter-common/src/main/java/io/prometheus/metrics/exporter/common/PrometheusScrapeHandler.java b/prometheus-metrics-exporter-common/src/main/java/io/prometheus/metrics/exporter/common/PrometheusScrapeHandler.java
index 5155457df..a21a04f7a 100644
--- a/prometheus-metrics-exporter-common/src/main/java/io/prometheus/metrics/exporter/common/PrometheusScrapeHandler.java
+++ b/prometheus-metrics-exporter-common/src/main/java/io/prometheus/metrics/exporter/common/PrometheusScrapeHandler.java
@@ -4,6 +4,7 @@
 import io.prometheus.metrics.config.PrometheusProperties;
 import io.prometheus.metrics.expositionformats.ExpositionFormatWriter;
 import io.prometheus.metrics.expositionformats.ExpositionFormats;
+import io.prometheus.metrics.model.registry.Collector;
 import io.prometheus.metrics.model.registry.MetricNameFilter;
 import io.prometheus.metrics.model.registry.PrometheusRegistry;
 import io.prometheus.metrics.model.snapshots.MetricSnapshots;
@@ -23,7 +24,7 @@
  */
 public class PrometheusScrapeHandler {
 
-    private final PrometheusRegistry registry;
+    private final Collector registry;
     private final ExpositionFormats expositionFormats;
     private final Predicate<String> nameFilter;
     private AtomicInteger lastResponseSize = new AtomicInteger(2 << 9); //  0.5 MB
@@ -32,7 +33,7 @@ public PrometheusScrapeHandler() {
         this(PrometheusProperties.get(), PrometheusRegistry.defaultRegistry);
     }
 
-    public PrometheusScrapeHandler(PrometheusRegistry registry) {
+    public PrometheusScrapeHandler(Collector registry) {
         this(PrometheusProperties.get(), registry);
     }
 
@@ -40,7 +41,7 @@ public PrometheusScrapeHandler(PrometheusProperties config) {
         this(config, PrometheusRegistry.defaultRegistry);
     }
 
-    public PrometheusScrapeHandler(PrometheusProperties config, PrometheusRegistry registry) {
+    public PrometheusScrapeHandler(PrometheusProperties config, Collector registry) {
         this.expositionFormats = ExpositionFormats.init(config.getExporterProperties());
         this.registry = registry;
         this.nameFilter = makeNameFilter(config.getExporterFilterProperties());
@@ -92,7 +93,7 @@ public void handleRequest(PrometheusHttpExchange exchange) throws IOException {
 
     private Predicate<String> makeNameFilter(ExporterFilterProperties props) {
         if (props.getAllowedMetricNames() == null && props.getExcludedMetricNames() == null && props.getAllowedMetricNamePrefixes() == null && props.getExcludedMetricNamePrefixes() == null) {
-            return null;
+            return MetricNameFilter.ALLOW_ALL;
         } else {
             return MetricNameFilter.builder()
                     .nameMustBeEqualTo(props.getAllowedMetricNames())
@@ -104,26 +105,16 @@ private Predicate<String> makeNameFilter(ExporterFilterProperties props) {
     }
 
     private MetricSnapshots scrape(PrometheusHttpRequest request) {
-
         Predicate<String> filter = makeNameFilter(request.getParameterValues("name[]"));
-        if (filter != null) {
-            return registry.scrape(filter, request);
-        } else {
-            return registry.scrape(request);
-        }
+        return registry.collect(filter, request);
     }
 
     private Predicate<String> makeNameFilter(String[] includedNames) {
-        Predicate<String> result = null;
         if (includedNames != null && includedNames.length > 0) {
-            result = MetricNameFilter.builder().nameMustBeEqualTo(includedNames).build();
-        }
-        if (result != null && nameFilter != null) {
-            result = result.and(nameFilter);
-        } else if (nameFilter != null) {
-            result = nameFilter;
+            return nameFilter.and(MetricNameFilter.builder().nameMustBeEqualTo(includedNames).build());
+        } else {
+            return nameFilter;
         }
-        return result;
     }
 
     private boolean writeDebugResponse(MetricSnapshots snapshots, PrometheusHttpExchange exchange) throws IOException {
diff --git a/prometheus-metrics-exporter-httpserver/src/main/java/io/prometheus/metrics/exporter/httpserver/HTTPServer.java b/prometheus-metrics-exporter-httpserver/src/main/java/io/prometheus/metrics/exporter/httpserver/HTTPServer.java
index 81f8871e4..6644c4413 100644
--- a/prometheus-metrics-exporter-httpserver/src/main/java/io/prometheus/metrics/exporter/httpserver/HTTPServer.java
+++ b/prometheus-metrics-exporter-httpserver/src/main/java/io/prometheus/metrics/exporter/httpserver/HTTPServer.java
@@ -6,8 +6,8 @@
 import com.sun.net.httpserver.HttpServer;
 import com.sun.net.httpserver.HttpsConfigurator;
 import com.sun.net.httpserver.HttpsServer;
-import io.prometheus.metrics.config.ExporterHttpServerProperties;
 import io.prometheus.metrics.config.PrometheusProperties;
+import io.prometheus.metrics.model.registry.Collector;
 import io.prometheus.metrics.model.registry.PrometheusRegistry;
 
 import java.io.Closeable;
@@ -46,7 +46,7 @@ public class HTTPServer implements Closeable {
     protected final HttpServer server;
     protected final ExecutorService executorService;
 
-    private HTTPServer(PrometheusProperties config, ExecutorService executorService, HttpServer httpServer, PrometheusRegistry registry, Authenticator authenticator, HttpHandler defaultHandler) {
+    private HTTPServer(PrometheusProperties config, ExecutorService executorService, HttpServer httpServer, Collector registry, Authenticator authenticator, HttpHandler defaultHandler) {
         if (httpServer.getAddress() == null) {
             throw new IllegalArgumentException("HttpServer hasn't been bound to an address");
         }
@@ -104,7 +104,7 @@ public static class Builder {
         private String hostname = null;
         private InetAddress inetAddress = null;
         private ExecutorService executorService = null;
-        private PrometheusRegistry registry = null;
+        private Collector registry = null;
         private Authenticator authenticator = null;
         private HttpsConfigurator httpsConfigurator = null;
         private HttpHandler defaultHandler = null;
@@ -153,7 +153,7 @@ public Builder executorService(ExecutorService executorService) {
         /**
          * Optional: Default is {@link PrometheusRegistry#defaultRegistry}.
          */
-        public Builder registry(PrometheusRegistry registry) {
+        public Builder registry(Collector registry) {
             this.registry = registry;
             return this;
         }
diff --git a/prometheus-metrics-exporter-httpserver/src/main/java/io/prometheus/metrics/exporter/httpserver/MetricsHandler.java b/prometheus-metrics-exporter-httpserver/src/main/java/io/prometheus/metrics/exporter/httpserver/MetricsHandler.java
index 3506ddd4b..827e701e4 100644
--- a/prometheus-metrics-exporter-httpserver/src/main/java/io/prometheus/metrics/exporter/httpserver/MetricsHandler.java
+++ b/prometheus-metrics-exporter-httpserver/src/main/java/io/prometheus/metrics/exporter/httpserver/MetricsHandler.java
@@ -4,6 +4,7 @@
 import com.sun.net.httpserver.HttpHandler;
 import io.prometheus.metrics.config.PrometheusProperties;
 import io.prometheus.metrics.exporter.common.PrometheusScrapeHandler;
+import io.prometheus.metrics.model.registry.Collector;
 import io.prometheus.metrics.model.registry.PrometheusRegistry;
 
 import java.io.ByteArrayOutputStream;
@@ -26,7 +27,7 @@ public MetricsHandler() {
         prometheusScrapeHandler = new PrometheusScrapeHandler();
     }
 
-    public MetricsHandler(PrometheusRegistry registry) {
+    public MetricsHandler(Collector registry) {
         prometheusScrapeHandler = new PrometheusScrapeHandler(registry);
     }
 
@@ -34,7 +35,7 @@ public MetricsHandler(PrometheusProperties config) {
         prometheusScrapeHandler = new PrometheusScrapeHandler(config);
     }
 
-    public MetricsHandler(PrometheusProperties config, PrometheusRegistry registry) {
+    public MetricsHandler(PrometheusProperties config, Collector registry) {
         prometheusScrapeHandler = new PrometheusScrapeHandler(config, registry);
     }
 
diff --git a/prometheus-metrics-instrumentation-jvm/pom.xml b/prometheus-metrics-instrumentation-jvm/pom.xml
index f55dd9c6f..38f1e3231 100644
--- a/prometheus-metrics-instrumentation-jvm/pom.xml
+++ b/prometheus-metrics-instrumentation-jvm/pom.xml
@@ -19,6 +19,18 @@
     <properties>
         <automatic.module.name>io.prometheus.metrics.instrumentation.jvm</automatic.module.name>
     </properties>
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <source>10</source>
+                    <target>10</target>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
 
     <licenses>
         <license>
diff --git a/prometheus-metrics-instrumentation-jvm/src/main/java/io/prometheus/metrics/instrumentation/jvm/JvmBufferPoolMetrics.java b/prometheus-metrics-instrumentation-jvm/src/main/java/io/prometheus/metrics/instrumentation/jvm/JvmBufferPoolMetrics.java
index 9c8cddb46..f7a8631b7 100644
--- a/prometheus-metrics-instrumentation-jvm/src/main/java/io/prometheus/metrics/instrumentation/jvm/JvmBufferPoolMetrics.java
+++ b/prometheus-metrics-instrumentation-jvm/src/main/java/io/prometheus/metrics/instrumentation/jvm/JvmBufferPoolMetrics.java
@@ -2,6 +2,8 @@
 
 import io.prometheus.metrics.config.PrometheusProperties;
 import io.prometheus.metrics.core.metrics.GaugeWithCallback;
+import io.prometheus.metrics.model.registry.Collector;
+import io.prometheus.metrics.model.registry.CollectorBuilder;
 import io.prometheus.metrics.model.registry.PrometheusRegistry;
 import io.prometheus.metrics.model.snapshots.Unit;
 
@@ -40,17 +42,10 @@ public class JvmBufferPoolMetrics {
     private static final String JVM_BUFFER_POOL_CAPACITY_BYTES = "jvm_buffer_pool_capacity_bytes";
     private static final String JVM_BUFFER_POOL_USED_BUFFERS = "jvm_buffer_pool_used_buffers";
 
-    private final PrometheusProperties config;
-    private final List<BufferPoolMXBean> bufferPoolBeans;
+    private static Collector build(List<BufferPoolMXBean> bufferPoolBeans, PrometheusProperties config) {
+        var builder = CollectorBuilder.compositeBuilder();
 
-    private JvmBufferPoolMetrics(List<BufferPoolMXBean> bufferPoolBeans, PrometheusProperties config) {
-        this.config = config;
-        this.bufferPoolBeans = bufferPoolBeans;
-    }
-
-    private void register(PrometheusRegistry registry) {
-
-        GaugeWithCallback.builder(config)
+        builder.add(GaugeWithCallback.builder(config)
                 .name(JVM_BUFFER_POOL_USED_BYTES)
                 .help("Used bytes of a given JVM buffer pool.")
                 .unit(Unit.BYTES)
@@ -60,9 +55,9 @@ private void register(PrometheusRegistry registry) {
                         callback.call(pool.getMemoryUsed(), pool.getName());
                     }
                 })
-                .register(registry);
+                .build());
 
-        GaugeWithCallback.builder(config)
+        builder.add(GaugeWithCallback.builder(config)
                 .name(JVM_BUFFER_POOL_CAPACITY_BYTES)
                 .help("Bytes capacity of a given JVM buffer pool.")
                 .unit(Unit.BYTES)
@@ -72,9 +67,9 @@ private void register(PrometheusRegistry registry) {
                         callback.call(pool.getTotalCapacity(), pool.getName());
                     }
                 })
-                .register(registry);
+                .build());
 
-        GaugeWithCallback.builder(config)
+        builder.add(GaugeWithCallback.builder(config)
                 .name(JVM_BUFFER_POOL_USED_BUFFERS)
                 .help("Used buffers of a given JVM buffer pool.")
                 .labelNames("pool")
@@ -83,7 +78,9 @@ private void register(PrometheusRegistry registry) {
                         callback.call(pool.getCount(), pool.getName());
                     }
                 })
-                .register(registry);
+                .build());
+
+        return builder.build();
     }
 
     public static Builder builder() {
@@ -111,16 +108,16 @@ Builder bufferPoolBeans(List<BufferPoolMXBean> bufferPoolBeans) {
             return this;
         }
 
+        public Collector build() {
+            return JvmBufferPoolMetrics.build(bufferPoolBeans, config);
+        }
+
         public void register() {
-            register(PrometheusRegistry.defaultRegistry);
+            PrometheusRegistry.defaultRegistry.register(build());
         }
 
         public void register(PrometheusRegistry registry) {
-            List<BufferPoolMXBean> bufferPoolBeans = this.bufferPoolBeans;
-            if (bufferPoolBeans == null) {
-                bufferPoolBeans = ManagementFactory.getPlatformMXBeans(BufferPoolMXBean.class);
-            }
-            new JvmBufferPoolMetrics(bufferPoolBeans, config).register(registry);
+            registry.register(build());
         }
     }
 }
diff --git a/prometheus-metrics-instrumentation-jvm/src/test/java/io/prometheus/metrics/instrumentation/jvm/ProcessMetricsTest.java b/prometheus-metrics-instrumentation-jvm/src/test/java/io/prometheus/metrics/instrumentation/jvm/ProcessMetricsTest.java
index 92f286e01..b382537d6 100644
--- a/prometheus-metrics-instrumentation-jvm/src/test/java/io/prometheus/metrics/instrumentation/jvm/ProcessMetricsTest.java
+++ b/prometheus-metrics-instrumentation-jvm/src/test/java/io/prometheus/metrics/instrumentation/jvm/ProcessMetricsTest.java
@@ -16,9 +16,7 @@
 import static io.prometheus.metrics.instrumentation.jvm.TestUtil.convertToOpenMetricsFormat;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.*;
 
 public class ProcessMetricsTest {
 
@@ -42,8 +40,8 @@ public void setUp() throws IOException {
     public void testGoodCase() throws IOException {
         PrometheusRegistry registry = new PrometheusRegistry();
         ProcessMetrics.builder()
-                        .osBean(sunOsBean)
-                                .runtimeBean(runtimeBean)
+                .osBean(sunOsBean)
+                .runtimeBean(runtimeBean)
                 .grepper(linuxGrepper)
                 .register(registry);
         MetricSnapshots snapshots = registry.scrape();
diff --git a/prometheus-metrics-model/pom.xml b/prometheus-metrics-model/pom.xml
index f45d5b6fb..c69ffbfaa 100644
--- a/prometheus-metrics-model/pom.xml
+++ b/prometheus-metrics-model/pom.xml
@@ -19,6 +19,18 @@
     <properties>
         <automatic.module.name>io.prometheus.metrics.model</automatic.module.name>
     </properties>
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <source>16</source>
+                    <target>16</target>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
 
     <licenses>
         <license>
diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/Collector.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/Collector.java
index 0c69a89a9..e268e73f5 100644
--- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/Collector.java
+++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/Collector.java
@@ -1,76 +1,25 @@
 package io.prometheus.metrics.model.registry;
 
-import io.prometheus.metrics.model.snapshots.MetricSnapshot;
+import io.prometheus.metrics.model.snapshots.MetricSnapshots;
 
 import java.util.function.Predicate;
 
-import static io.prometheus.metrics.model.snapshots.PrometheusNaming.prometheusName;
-
 /**
- * To be registered with the Prometheus collector registry.
- * See <i>Overall Structure</i> on
- * <a href="https://prometheus.io/docs/instrumenting/writing_clientlibs/">https://prometheus.io/docs/instrumenting/writing_clientlibs/</a>.
+ * Basic interface for fetching metrics.
+ * Accepts name filter (allowing to avoid fetching of unnecessary data)
+ * as well as scrape request (for multi target).
+ *
+ * Used to be named MultiCollector in v1.1.
  */
 @FunctionalInterface
 public interface Collector {
-
     /**
      * Called when the Prometheus server scrapes metrics.
-     */
-    MetricSnapshot collect();
-
-    /**
-     * Provides Collector with the details of the request issued by Prometheus to allow multi-target pattern implementation
-     * Override to implement request dependent logic to provide MetricSnapshot
-     */
-	default MetricSnapshot collect(PrometheusScrapeRequest scrapeRequest) {
-		return collect();
-	}
-    
-    /**
-     * Like {@link #collect()}, but returns {@code null} if {@code includedNames.test(name)} is {@code false}.
-     * <p>
-     * Override this if there is a more efficient way than first collecting the snapshot and then discarding it.
-     */
-    default MetricSnapshot collect(Predicate<String> includedNames) {
-        MetricSnapshot result = collect();
-        if (includedNames.test(result.getMetadata().getPrometheusName())) {
-            return result;
-        } else {
-            return null;
-        }
-    }
-    
-    /**
-     * Like {@link #collect(Predicate)}, but with support for multi-target pattern.
-     * <p>
-     * Override this if there is a more efficient way than first collecting the snapshot and then discarding it.
-     */
-    default MetricSnapshot collect(Predicate<String> includedNames, PrometheusScrapeRequest scrapeRequest) {
-        MetricSnapshot result = collect(scrapeRequest);
-        if (includedNames.test(result.getMetadata().getPrometheusName())) {
-            return result;
-        } else {
-            return null;
-        }
-    }
-    
-
-    /**
-     * This is called in two places:
-     * <ol>
-     * <li>During registration to check if a metric with that name already exists.</li>
-     * <li>During scrape to check if this collector can be skipped because a name filter is present and the metric name is excluded.</li>
-     * </ol>
-     * Returning {@code null} means checks are omitted (registration the metric always succeeds),
-     * and the collector is always scraped (the result is dropped after scraping if a name filter is present and
-     * the metric name is excluded).
-     * <p>
-     * If your metric has a name that does not change at runtime it is a good idea to overwrite this and return the name.
      * <p>
-     * All metrics in {@code prometheus-metrics-core} override this.
+     * Should return only the snapshots where {@code includedNames.test(name)} is {@code true}.
+     *
+     * @param includedNames prometheusName filter (non-null, use MetricNameFilter.ALLOW_ALL to disable filtering)
+     * @param scrapeRequest null allowed
      */
-    default String getPrometheusName() {
-        return null;
-    }
+    MetricSnapshots collect(Predicate<String> includedNames, PrometheusScrapeRequest scrapeRequest);
 }
diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/CollectorBuilder.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/CollectorBuilder.java
new file mode 100644
index 000000000..01d44201f
--- /dev/null
+++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/CollectorBuilder.java
@@ -0,0 +1,130 @@
+package io.prometheus.metrics.model.registry;
+
+import io.prometheus.metrics.model.snapshots.Labels;
+import io.prometheus.metrics.model.snapshots.MetricSnapshot;
+import io.prometheus.metrics.model.snapshots.MetricSnapshots;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+
+public class CollectorBuilder {
+    /**
+     * Assembles collector from metric snapshot supplier.
+     */
+    public static Collector fromMetric(Supplier<MetricSnapshot> collector) {
+        return new Collector() {
+            @Override
+            public MetricSnapshots collect(Predicate<String> includedNames, PrometheusScrapeRequest scrapeRequest) {
+                MetricSnapshot result = collector.get();
+                if (result.matches(includedNames))
+                    return MetricSnapshots.of(result);
+                else
+                    return MetricSnapshots.empty();
+            }
+        };
+    }
+
+    /**
+     * Assembles collector from metric snapshot supplier.
+     */
+    public static Collector fromMetrics(Supplier<MetricSnapshots> collector) {
+        return (includedNames, scrapeRequest) -> collector.get().filter(includedNames);
+    }
+
+    /**
+     * Applies name filter over result of another collector.
+     * Useful if one suspects underlying collector does not follow filtering spec.
+     * Required mainly for historical reasons.
+     */
+    public static Collector filtered(Collector collector) {
+        return new Collector() {
+            @Override
+            public MetricSnapshots collect(Predicate<String> includedNames, PrometheusScrapeRequest scrapeRequest) {
+                return collector.collect(includedNames, scrapeRequest).filter(includedNames);
+            }
+        };
+    }
+
+    /** Applies additional labels to all data points. */
+    public static Collector withLabels(Collector collector, Labels labels) {
+        return new Collector() {
+            @Override
+            public MetricSnapshots collect(Predicate<String> includedNames, PrometheusScrapeRequest scrapeRequest) {
+                return collector.collect(includedNames, scrapeRequest).withLabels(labels);
+            }
+        };
+    }
+
+    /** Applies name prefix. */
+    public static Collector withNamePrefix(Collector collector, String prefix) {
+        return new Collector() {
+            @Override
+            public MetricSnapshots collect(Predicate<String> includedNames, PrometheusScrapeRequest scrapeRequest) {
+                return collector.collect(name -> includedNames.test(prefix + name), scrapeRequest).withNamePrefix(prefix);
+            }
+        };
+    }
+
+    /** Constructs composite collector returning all the metrics from underling collectors. */
+    public static Collector composite(Collector... collectors) {
+        return new Collector() {
+            @Override
+            public MetricSnapshots collect(Predicate<String> includedNames, PrometheusScrapeRequest scrapeRequest) {
+                MetricSnapshots.Builder result = MetricSnapshots.builder();
+                for (Collector collector : collectors) {
+                    result.metricSnapshots(collector.collect(includedNames, scrapeRequest));
+                }
+                return result.build();
+            }
+        };
+    }
+
+    public static CompositeCollector.Builder compositeBuilder() {
+        return CompositeCollector.builder();
+    }
+
+    static class CompositeCollector implements Collector {
+        private final Collection<Collector> collectors;
+
+        private CompositeCollector(Collection<Collector> collectors) {
+            this.collectors = collectors;
+        }
+
+        @Override
+        public MetricSnapshots collect(Predicate<String> includedNames, PrometheusScrapeRequest scrapeRequest) {
+            MetricSnapshots.Builder result = MetricSnapshots.builder();
+            for (Collector collector : collectors) {
+                result.metricSnapshots(collector.collect(includedNames, scrapeRequest));
+            }
+            return result.build();
+        }
+
+        public static Builder builder() {
+            return new Builder();
+        }
+
+        public static class Builder {
+            private final List<Collector> collectors = new ArrayList<>();
+
+            private Builder() {
+            }
+
+            public Builder add(Collector collector) {
+                collectors.add(collector);
+                return this;
+            }
+
+            public Builder add(Iterable<Collector> collectors) {
+                collectors.forEach(Builder.this.collectors::add);
+                return this;
+            }
+
+            public Collector build() {
+                return new CompositeCollector(collectors);
+            }
+        }
+    }
+}
diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/MetricNameFilter.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/MetricNameFilter.java
index c0c345a1b..4b204f425 100644
--- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/MetricNameFilter.java
+++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/MetricNameFilter.java
@@ -190,6 +190,7 @@ public Builder nameMustNotStartWith(Collection<String> prefixes) {
         }
 
         public MetricNameFilter build() {
+            // TODO: should return MetricNameFilter.ALLOW_ALL if no filtering is needed
             return new MetricNameFilter(nameEqualTo, nameNotEqualTo, nameStartsWith, nameDoesNotStartWith);
         }
     }
diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/MultiCollector.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/MultiCollector.java
deleted file mode 100644
index 5434c0ec0..000000000
--- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/MultiCollector.java
+++ /dev/null
@@ -1,71 +0,0 @@
-package io.prometheus.metrics.model.registry;
-
-import io.prometheus.metrics.model.snapshots.MetricSnapshot;
-import io.prometheus.metrics.model.snapshots.MetricSnapshots;
-
-import java.util.Collections;
-import java.util.List;
-import java.util.function.Predicate;
-
-/**
- * Like {@link Collector}, but collecting multiple Snapshots at once.
- */
-@FunctionalInterface
-public interface MultiCollector {
-
-    /**
-     * Called when the Prometheus server scrapes metrics.
-     */
-    MetricSnapshots collect();
-
-    /**
-     * Provides Collector with the details of the request issued by Prometheus to allow multi-target pattern implementation
-     * Override to implement request dependent logic to provide MetricSnapshot
-     */
-	default MetricSnapshots collect(PrometheusScrapeRequest scrapeRequest) {
-		return collect();
-	}
-    
-    
-    /**
-     * Like {@link #collect()}, but returns only the snapshots where {@code includedNames.test(name)} is {@code true}.
-     * <p>
-     * Override this if there is a more efficient way than first collecting all snapshot and then discarding the excluded ones.
-     */
-    default MetricSnapshots collect(Predicate<String> includedNames) {
-    	return collect(includedNames, null);
-    }
-
-    /**
-     * Like {@link #collect(Predicate)}, but with support for multi-target pattern.
-     * <p>
-     * Override this if there is a more efficient way than first collecting the snapshot and then discarding it.
-     */
-    default MetricSnapshots collect(Predicate<String> includedNames, PrometheusScrapeRequest scrapeRequest) {
-    	MetricSnapshots allSnapshots = scrapeRequest == null ? collect(): collect(scrapeRequest);
-        MetricSnapshots.Builder result = MetricSnapshots.builder();
-        for (MetricSnapshot snapshot : allSnapshots) {
-            if (includedNames.test(snapshot.getMetadata().getPrometheusName())) {
-                result.metricSnapshot(snapshot);
-            }
-        }
-        return result.build();
-    }
-
-
-    /**
-     * This is called in two places:
-     * <ol>
-     * <li>During registration to check if a metric with that name already exists.</li>
-     * <li>During scrape to check if the collector can be skipped because a name filter is present and all names are excluded.</li>
-     * </ol>
-     * Returning an empty list means checks are omitted (registration metric always succeeds),
-     * and the collector is always scraped (if a name filter is present and all names are excluded the result is dropped).
-     * <p>
-     * If your collector returns a constant list of metrics that have names that do not change at runtime
-     * it is a good idea to overwrite this and return the names.
-     */
-    default List<String> getPrometheusNames() {
-        return Collections.emptyList();
-    }
-}
diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/PrometheusRegistry.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/PrometheusRegistry.java
index 8b059adb3..76ef76e12 100644
--- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/PrometheusRegistry.java
+++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/PrometheusRegistry.java
@@ -1,129 +1,49 @@
 package io.prometheus.metrics.model.registry;
 
-import static io.prometheus.metrics.model.snapshots.PrometheusNaming.prometheusName;
-
 import java.util.List;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.function.Predicate;
 
-import io.prometheus.metrics.model.snapshots.MetricSnapshot;
 import io.prometheus.metrics.model.snapshots.MetricSnapshots;
 
-public class PrometheusRegistry {
+/** Collector that allows dynamic registration/unregistration of scrape sources.
+ *
+ * Consider to use CollectorBuilder.CompositeCollector if no dynamic registration is needed.
+  */
+public class PrometheusRegistry implements Collector {
 
 	public static final PrometheusRegistry defaultRegistry = new PrometheusRegistry();
 
-	private final Set<String> prometheusNames = ConcurrentHashMap.newKeySet();
 	private final List<Collector> collectors = new CopyOnWriteArrayList<>();
-	private final List<MultiCollector> multiCollectors = new CopyOnWriteArrayList<>();
 
 	public void register(Collector collector) {
-		String prometheusName = collector.getPrometheusName();
-		if (prometheusName != null) {
-			if (!prometheusNames.add(prometheusName)) {
-				throw new IllegalStateException("Can't register " + prometheusName + " because a metric with that name is already registered.");
-			}
-		}
 		collectors.add(collector);
 	}
 
-	public void register(MultiCollector collector) {
-		for (String prometheusName : collector.getPrometheusNames()) {
-			if (!prometheusNames.add(prometheusName)) {
-				throw new IllegalStateException("Can't register " + prometheusName + " because that name is already registered.");
-			}
-		}
-		multiCollectors.add(collector);
-	}
-
 	public void unregister(Collector collector) {
 		collectors.remove(collector);
-		String prometheusName = collector.getPrometheusName();
-		if (prometheusName != null) {
-			prometheusNames.remove(collector.getPrometheusName());
-		}
 	}
 
-	public void unregister(MultiCollector collector) {
-		multiCollectors.remove(collector);
-		for (String prometheusName : collector.getPrometheusNames()) {
-			prometheusNames.remove(prometheusName(prometheusName));
-		}
+	/** Use collect() instead. */
+	public MetricSnapshots scrape(Predicate<String> includedNames) {
+		return collect(includedNames, null);
 	}
 
+	/** Use collect() instead. */
 	public MetricSnapshots scrape() {
-		return scrape((PrometheusScrapeRequest) null);
-	}
-
-	public MetricSnapshots scrape(PrometheusScrapeRequest scrapeRequest) {
-		MetricSnapshots.Builder result = MetricSnapshots.builder();
-		for (Collector collector : collectors) {
-			MetricSnapshot snapshot = scrapeRequest == null ? collector.collect() : collector.collect(scrapeRequest);
-			if (snapshot != null) {
-				if (result.containsMetricName(snapshot.getMetadata().getName())) {
-					throw new IllegalStateException(snapshot.getMetadata().getPrometheusName() + ": duplicate metric name.");
-				}
-				result.metricSnapshot(snapshot);
-			}
-		}
-		for (MultiCollector collector : multiCollectors) {
-			MetricSnapshots snaphots = scrapeRequest == null ? collector.collect() : collector.collect(scrapeRequest);
-			for (MetricSnapshot snapshot : snaphots) {
-				if (result.containsMetricName(snapshot.getMetadata().getName())) {
-					throw new IllegalStateException(snapshot.getMetadata().getPrometheusName() + ": duplicate metric name.");
-				}
-				result.metricSnapshot(snapshot);
-			}
-		}
-		return result.build();
+		return collect(MetricNameFilter.ALLOW_ALL, null);
 	}
 
-	public MetricSnapshots scrape(Predicate<String> includedNames) {
-		if (includedNames == null) {
-			return scrape();
-		}
-		return scrape(includedNames, null);
+	/** Use collect() instead. */
+	public MetricSnapshots scrape(Predicate<String> includedNames, PrometheusScrapeRequest scrapeRequest) {
+		return collect(includedNames, scrapeRequest);
 	}
 
-	public MetricSnapshots scrape(Predicate<String> includedNames, PrometheusScrapeRequest scrapeRequest) {
-		if (includedNames == null) {
-			return scrape(scrapeRequest);
-		}
+	public MetricSnapshots collect(Predicate<String> includedNames, PrometheusScrapeRequest scrapeRequest) {
 		MetricSnapshots.Builder result = MetricSnapshots.builder();
 		for (Collector collector : collectors) {
-			String prometheusName = collector.getPrometheusName();
-			// prometheusName == null means the name is unknown, and we have to scrape to learn the name.
-			// prometheusName != null means we can skip the scrape if the name is excluded.
-			if (prometheusName == null || includedNames.test(prometheusName)) {
-				MetricSnapshot snapshot = scrapeRequest == null ? collector.collect(includedNames) : collector.collect(includedNames, scrapeRequest);
-				if (snapshot != null) {
-					result.metricSnapshot(snapshot);
-				}
-			}
-		}
-		for (MultiCollector collector : multiCollectors) {
-			List<String> prometheusNames = collector.getPrometheusNames();
-			// empty prometheusNames means the names are unknown, and we have to scrape to learn the names.
-			// non-empty prometheusNames means we can exclude the collector if all names are excluded by the filter.
-			boolean excluded = !prometheusNames.isEmpty();
-			for (String prometheusName : prometheusNames) {
-				if (includedNames.test(prometheusName)) {
-					excluded = false;
-					break;
-				}
-			}
-			if (!excluded) {
-				MetricSnapshots snapshots = scrapeRequest == null ? collector.collect(includedNames) : collector.collect(includedNames, scrapeRequest);
-				for (MetricSnapshot snapshot : snapshots) {
-					if (snapshot != null) {
-						result.metricSnapshot(snapshot);
-					}
-				}
-			}
+			result.metricSnapshots(collector.collect(includedNames, scrapeRequest));
 		}
 		return result.build();
 	}
-
 }
diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/PrometheusScrapeRequest.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/PrometheusScrapeRequest.java
index e8651292e..243fc5f33 100644
--- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/PrometheusScrapeRequest.java
+++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/PrometheusScrapeRequest.java
@@ -4,7 +4,6 @@
  * Infos extracted from the request received by the endpoint
  */
 public interface PrometheusScrapeRequest {
-
 	/**
 	 * Absolute path of the HTTP request.
 	 */
diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/CounterSnapshot.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/CounterSnapshot.java
index e62aeb9f8..361894bcb 100644
--- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/CounterSnapshot.java
+++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/CounterSnapshot.java
@@ -3,6 +3,7 @@
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
+import java.util.stream.Collectors;
 
 /**
  * Immutable snapshot of a Counter.
@@ -26,6 +27,35 @@ public List<CounterDataPointSnapshot> getDataPoints() {
         return (List<CounterDataPointSnapshot>) dataPoints;
     }
 
+    @Override
+    public CounterSnapshot merge(MetricSnapshot snapshot) {
+        if (!(getMetadata().equals(snapshot.getMetadata())))
+            throw new IllegalArgumentException("Unable to merge - metadata mismatch for metric " + this.getMetadata().getPrometheusName());
+        if (snapshot instanceof CounterSnapshot s) {
+            var result = new ArrayList<CounterSnapshot.CounterDataPointSnapshot>();
+            result.addAll(this.getDataPoints());
+            result.addAll(s.getDataPoints());
+            return new CounterSnapshot(getMetadata(), result);
+        } else {
+            throw new IllegalArgumentException("Unable to merge - invalid type of metric " + this.getMetadata().getPrometheusName());
+        }
+    }
+
+
+    @Override
+    public CounterSnapshot withNamePrefix(String prefix) {
+        return new CounterSnapshot(getMetadata().withNamePrefix(prefix), getDataPoints());
+    }
+
+    /** Merge additional labels to all the data points. */
+    public CounterSnapshot withLabels(Labels labels) {
+        var points = getDataPoints()
+                .stream()
+                .map(point -> new CounterSnapshot.CounterDataPointSnapshot(point.value, point.getLabels().merge(labels), point.exemplar, point.getCreatedTimestampMillis(), point.getScrapeTimestampMillis()))
+                .collect(Collectors.toList());
+        return new CounterSnapshot(getMetadata(), points);
+    }
+
     public static class CounterDataPointSnapshot extends DataPointSnapshot {
 
         private final double value;
diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/GaugeSnapshot.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/GaugeSnapshot.java
index f69b8d2a6..4af1c29d8 100644
--- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/GaugeSnapshot.java
+++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/GaugeSnapshot.java
@@ -3,6 +3,7 @@
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
+import java.util.stream.Collectors;
 
 /**
  * Immutable snapshot of a Gauge.
@@ -25,6 +26,34 @@ public List<GaugeDataPointSnapshot> getDataPoints() {
         return (List<GaugeDataPointSnapshot>) dataPoints;
     }
 
+    @Override
+    public GaugeSnapshot merge(MetricSnapshot snapshot) {
+        if (!(getMetadata().equals(snapshot.getMetadata())))
+            throw new IllegalArgumentException("Unable to merge - metadata mismatch.");
+        if (snapshot instanceof GaugeSnapshot s) {
+            var result = new ArrayList<GaugeSnapshot.GaugeDataPointSnapshot>();
+            result.addAll(this.getDataPoints());
+            result.addAll(s.getDataPoints());
+            return new GaugeSnapshot(getMetadata(), result);
+        } else {
+            throw new IllegalArgumentException("Unable to merge - invalid snapshot type");
+        }
+    }
+
+    @Override
+    public GaugeSnapshot withNamePrefix(String prefix) {
+        return new GaugeSnapshot(getMetadata().withNamePrefix(prefix), getDataPoints());
+    }
+
+    /** Merge additional labels to all the data points. */
+    public GaugeSnapshot withLabels(Labels labels) {
+        var points = getDataPoints()
+                .stream()
+                .map(point -> new GaugeSnapshot.GaugeDataPointSnapshot(point.value, point.getLabels().merge(labels), point.exemplar, point.getScrapeTimestampMillis()))
+                .collect(Collectors.toList());
+        return new GaugeSnapshot(getMetadata(), points);
+    }
+
     public static final class GaugeDataPointSnapshot extends DataPointSnapshot {
 
         private final double value;
diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/HistogramSnapshot.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/HistogramSnapshot.java
index 2e66c1a25..dcaf11571 100644
--- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/HistogramSnapshot.java
+++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/HistogramSnapshot.java
@@ -3,6 +3,7 @@
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
+import java.util.stream.Collectors;
 
 /**
  * Immutable snapshot of a Histogram.
@@ -38,6 +39,48 @@ public boolean isGaugeHistogram() {
         return gaugeHistogram;
     }
 
+
+    @Override
+    public HistogramSnapshot merge(MetricSnapshot snapshot) {
+        if (!(getMetadata().equals(snapshot.getMetadata())))
+            throw new IllegalArgumentException("Unable to merge - metadata mismatch.");
+        if (snapshot instanceof HistogramSnapshot s) {
+            var result = new ArrayList<HistogramSnapshot.HistogramDataPointSnapshot>();
+            result.addAll(this.getDataPoints());
+            result.addAll(s.getDataPoints());
+            return new HistogramSnapshot(gaugeHistogram, getMetadata(), result);
+        } else {
+            throw new IllegalArgumentException("Unable to merge - invalid snapshot type");
+        }
+    }
+
+    @Override
+    public HistogramSnapshot withNamePrefix(String prefix) {
+        return new HistogramSnapshot(getMetadata().withNamePrefix(prefix), getDataPoints());
+    }
+
+    /**
+     * Merge additional labels to all the data points.
+     */
+    public HistogramSnapshot withLabels(Labels labels) {
+        var points = getDataPoints()
+                .stream()
+                .map(point -> new HistogramSnapshot.HistogramDataPointSnapshot(
+                        point.classicBuckets,
+                        point.nativeSchema,
+                        point.nativeZeroCount,
+                        point.nativeZeroThreshold,
+                        point.nativeBucketsForPositiveValues,
+                        point.nativeBucketsForNegativeValues,
+                        point.getSum(),
+                        point.getLabels().merge(labels),
+                        point.getExemplars(),
+                        point.getCreatedTimestampMillis(),
+                        point.getScrapeTimestampMillis()))
+                .collect(Collectors.toList());
+        return new HistogramSnapshot(getMetadata(), points);
+    }
+
     @Override
     public List<HistogramDataPointSnapshot> getDataPoints() {
         return (List<HistogramDataPointSnapshot>) dataPoints;
diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/InfoSnapshot.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/InfoSnapshot.java
index e24747181..fd7b03207 100644
--- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/InfoSnapshot.java
+++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/InfoSnapshot.java
@@ -3,6 +3,7 @@
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
+import java.util.stream.Collectors;
 
 /**
  * Immutable snapshot of an Info metric.
@@ -30,6 +31,35 @@ public List<InfoDataPointSnapshot> getDataPoints() {
         return (List<InfoDataPointSnapshot>) dataPoints;
     }
 
+    @Override
+    public InfoSnapshot merge(MetricSnapshot snapshot) {
+        if (!(getMetadata().equals(snapshot.getMetadata())))
+            throw new IllegalArgumentException("Unable to merge - metadata mismatch.");
+        if (snapshot instanceof InfoSnapshot s) {
+            var result = new ArrayList<InfoSnapshot.InfoDataPointSnapshot>();
+            result.addAll(this.getDataPoints());
+            result.addAll(s.getDataPoints());
+            return new InfoSnapshot(getMetadata(), result);
+        } else {
+            throw new IllegalArgumentException("Unable to merge - invalid snapshot type");
+        }
+    }
+
+    @Override
+    public InfoSnapshot withNamePrefix(String prefix) {
+        return new InfoSnapshot(getMetadata().withNamePrefix(prefix), getDataPoints());
+    }
+
+    /** Merge additional labels to all the data points. */
+    public MetricSnapshot withLabels(Labels labels) {
+        var points = getDataPoints()
+                .stream()
+                .map(point -> new InfoDataPointSnapshot(point.getLabels().merge(labels), point.getScrapeTimestampMillis()))
+                .collect(Collectors.toList());
+        return new InfoSnapshot(getMetadata(), points);
+    }
+
+
     public static class InfoDataPointSnapshot extends DataPointSnapshot {
 
         /**
diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricMetadata.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricMetadata.java
index 366805ddf..c1ddf5fec 100644
--- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricMetadata.java
+++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricMetadata.java
@@ -1,5 +1,7 @@
 package io.prometheus.metrics.model.snapshots;
 
+import java.util.Objects;
+
 /**
  * Immutable container for metric metadata: name, help, unit.
  */
@@ -96,6 +98,10 @@ public Unit getUnit() {
         return unit;
     }
 
+    public MetricMetadata withNamePrefix(String prefix) {
+        return new MetricMetadata(prefix + name, help, unit);
+    }
+
     private void validate() {
         if (name == null) {
             throw new IllegalArgumentException("Missing required field: name is null");
@@ -106,4 +112,17 @@ private void validate() {
                     + " Call " + PrometheusNaming.class.getSimpleName() + ".sanitizeMetricName(name) to avoid this error.");
         }
     }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        MetricMetadata that = (MetricMetadata) o;
+        return Objects.equals(name, that.name) && Objects.equals(prometheusName, that.prometheusName) && Objects.equals(help, that.help) && Objects.equals(unit, that.unit);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(prometheusName);
+    }
 }
diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricSnapshot.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricSnapshot.java
index 273b304eb..ac100e8d1 100644
--- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricSnapshot.java
+++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricSnapshot.java
@@ -6,6 +6,7 @@
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
+import java.util.function.Predicate;
 
 /**
  * Base class for metric snapshots.
@@ -33,12 +34,24 @@ protected MetricSnapshot(MetricMetadata metadata, Collection<? extends DataPoint
         validateLabels();
     }
 
+    /** Merge datapoints from two metric snapshots, as long as type and metadata matches (exception otherwise). */
+    public abstract MetricSnapshot merge(MetricSnapshot snapshot);
+
+    public boolean matches(Predicate<String> nameFilter) {
+        return nameFilter.test(getMetadata().getPrometheusName());
+    }
+
     public MetricMetadata getMetadata() {
         return metadata;
     }
 
     public abstract List<? extends DataPointSnapshot> getDataPoints();
 
+    public abstract MetricSnapshot withNamePrefix(String prefix);
+
+    /** Merge additional labels to all the data points. */
+    public abstract MetricSnapshot withLabels(Labels labels);
+
     protected void validateLabels() {
         // Verify that labels are unique (the same set of names/values must not be used multiple times for the same metric).
         for (int i = 0; i < dataPoints.size() - 1; i++) {
diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricSnapshots.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricSnapshots.java
index 6fcf3fd25..408e0531e 100644
--- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricSnapshots.java
+++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricSnapshots.java
@@ -1,55 +1,65 @@
 package io.prometheus.metrics.model.snapshots;
 
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Iterator;
-import java.util.List;
+import java.util.*;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
-import static io.prometheus.metrics.model.snapshots.PrometheusNaming.prometheusName;
-import static java.util.Collections.unmodifiableList;
-import static java.util.Comparator.comparing;
-
 /**
  * Immutable list of metric snapshots.
+ * Guaranteed entries have unique metric names.
  */
 public class MetricSnapshots implements Iterable<MetricSnapshot> {
 
     private final List<MetricSnapshot> snapshots;
 
     /**
-     * See {@link #MetricSnapshots(Collection)}
+     * To create MetricSnapshots, use builder that takes care of all validations.
      */
-    public MetricSnapshots(MetricSnapshot... snapshots) {
-        this(Arrays.asList(snapshots));
+    private MetricSnapshots(Collection<MetricSnapshot> snapshots) {
+        this.snapshots = Collections.unmodifiableList(new ArrayList<>(snapshots));
     }
 
     /**
-     * To create MetricSnapshots, you can either call the constructor directly
-     * or use {@link #builder()}.
-     *
-     * @param snapshots the constructor creates a sorted copy of snapshots.
-     * @throws IllegalArgumentException if snapshots contains duplicate metric names.
-     *                                  To avoid duplicate metric names use {@link #builder()} and check
-     *                                  {@link Builder#containsMetricName(String)} before calling
-     *                                  {@link Builder#metricSnapshot(MetricSnapshot)}.
+     * TODO: just for compatibility
      */
-    public MetricSnapshots(Collection<MetricSnapshot> snapshots) {
-        List<MetricSnapshot> list = new ArrayList<>(snapshots);
-        list.sort(comparing(s -> s.getMetadata().getPrometheusName()));
-        for (int i = 0; i < snapshots.size() - 1; i++) {
-            if (list.get(i).getMetadata().getPrometheusName().equals(list.get(i + 1).getMetadata().getPrometheusName())) {
-                throw new IllegalArgumentException(list.get(i).getMetadata().getPrometheusName() + ": duplicate metric name");
-            }
-        }
-        this.snapshots = unmodifiableList(list);
+    public MetricSnapshots(MetricSnapshot... snapshots) {
+        this.snapshots = builder().metricSnapshots(snapshots).build().snapshots;
+    }
+
+    public static MetricSnapshots empty() {
+        return new MetricSnapshots(Collections.emptyList());
     }
 
     public static MetricSnapshots of(MetricSnapshot... snapshots) {
-        return new MetricSnapshots(snapshots);
+        return builder().metricSnapshots(snapshots).build();
     }
 
+    public MetricSnapshots filter(Predicate<String> nameFilter) {
+        var result = snapshots
+                .stream()
+                .filter(snapshot -> snapshot.matches(nameFilter))
+                .collect(Collectors.toList());
+        return new MetricSnapshots(result);
+    }
+
+    public MetricSnapshots withLabels(Labels labels) {
+        var result = snapshots
+                .stream()
+                .map(snapshot -> snapshot.withLabels(labels))
+                .collect(Collectors.toList());
+        return new MetricSnapshots(result);
+    }
+
+    public MetricSnapshots withNamePrefix(String prefix) {
+        var result = snapshots
+                .stream()
+                .map(snapshot -> snapshot.withNamePrefix(prefix))
+                .collect(Collectors.toList());
+        return new MetricSnapshots(result);
+    }
+
+
     @Override
     public Iterator<MetricSnapshot> iterator() {
         return snapshots.iterator();
@@ -73,30 +83,38 @@ public static Builder builder() {
 
     public static class Builder {
 
-        private final List<MetricSnapshot> snapshots = new ArrayList<>();
+        /** Used to merge metrics by prometheus names, and to produce correct output order. */
+        private final Map<String, MetricSnapshot> result = new TreeMap<>();
 
         private Builder() {
         }
 
-        public boolean containsMetricName(String name) {
-            for (MetricSnapshot snapshot : snapshots) {
-                if (snapshot.getMetadata().getPrometheusName().equals(prometheusName(name))) {
-                    return true;
-                }
-            }
-            return false;
-        }
-
         /**
          * Add a metric snapshot. Call multiple times to add multiple metric snapshots.
          */
         public Builder metricSnapshot(MetricSnapshot snapshot) {
-            snapshots.add(snapshot);
+            result.merge(snapshot.getMetadata().getPrometheusName(), snapshot, MetricSnapshot::merge);
+            return this;
+        }
+
+        /**
+         * Add a metric snapshot collection.
+         */
+        public Builder metricSnapshots(Iterable<MetricSnapshot> snapshots) {
+            snapshots.forEach(this::metricSnapshot);
+            return this;
+        }
+
+        /**
+         * Add a metric snapshot collection.
+         */
+        public Builder metricSnapshots(MetricSnapshot[] snapshots) {
+            for (MetricSnapshot snapshot : snapshots) this.metricSnapshot(snapshot);
             return this;
         }
 
         public MetricSnapshots build() {
-            return new MetricSnapshots(snapshots);
+            return new MetricSnapshots(result.values());
         }
     }
 }
diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/StateSetSnapshot.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/StateSetSnapshot.java
index 2a6094354..4869bf84c 100644
--- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/StateSetSnapshot.java
+++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/StateSetSnapshot.java
@@ -1,11 +1,7 @@
 package io.prometheus.metrics.model.snapshots;
 
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.List;
+import java.util.*;
+import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
 /**
@@ -25,6 +21,34 @@ public StateSetSnapshot(MetricMetadata metadata, Collection<StateSetDataPointSna
         validate();
     }
 
+    @Override
+    public StateSetSnapshot merge(MetricSnapshot snapshot) {
+        if (!(getMetadata().equals(snapshot.getMetadata())))
+            throw new IllegalArgumentException("Unable to merge - metadata mismatch.");
+        if (snapshot instanceof StateSetSnapshot s) {
+            var result = new ArrayList<StateSetDataPointSnapshot>();
+            result.addAll(this.getDataPoints());
+            result.addAll(s.getDataPoints());
+            return new StateSetSnapshot(getMetadata(), result);
+        } else {
+            throw new IllegalArgumentException("Unable to merge - invalid snapshot type");
+        }
+    }
+
+    @Override
+    public StateSetSnapshot withNamePrefix(String prefix) {
+        return new StateSetSnapshot(getMetadata().withNamePrefix(prefix), getDataPoints());
+    }
+
+    /** Merge additional labels to all the data points. */
+    public StateSetSnapshot withLabels(Labels labels) {
+        var points = getDataPoints()
+                .stream()
+                .map(point -> new StateSetSnapshot.StateSetDataPointSnapshot(point.names, point.values, point.getLabels().merge(labels), point.getScrapeTimestampMillis()))
+                .collect(Collectors.toList());
+        return new StateSetSnapshot(getMetadata(), points);
+    }
+
     private void validate() {
         if (getMetadata().hasUnit()) {
             throw new IllegalArgumentException("An state set metric cannot have a unit.");
@@ -151,7 +175,8 @@ public static class Builder extends DataPointSnapshot.Builder<Builder> {
             private final ArrayList<String> names = new ArrayList<>();
             private final ArrayList<Boolean> values = new ArrayList<>();
 
-            private Builder() {}
+            private Builder() {
+            }
 
             /**
              * Add a state. Call multple times to add multiple states.
diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/SummarySnapshot.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/SummarySnapshot.java
index 16682dbae..cfc16f539 100644
--- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/SummarySnapshot.java
+++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/SummarySnapshot.java
@@ -3,6 +3,7 @@
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
+import java.util.stream.Collectors;
 
 /**
  * Immutable snapshot of a Summary metric.
@@ -20,6 +21,36 @@ public SummarySnapshot(MetricMetadata metadata, Collection<SummaryDataPointSnaps
         super(metadata, data);
     }
 
+    @Override
+    public SummarySnapshot merge(MetricSnapshot snapshot) {
+        if (!(getMetadata().equals(snapshot.getMetadata())))
+            throw new IllegalArgumentException("Unable to merge - metadata mismatch.");
+        if (snapshot instanceof SummarySnapshot s) {
+            var result = new ArrayList<SummarySnapshot.SummaryDataPointSnapshot>();
+            result.addAll(this.getDataPoints());
+            result.addAll(s.getDataPoints());
+            return new SummarySnapshot(getMetadata(), result);
+        } else {
+            throw new IllegalArgumentException("Unable to merge - invalid snapshot type");
+        }
+    }
+
+    @Override
+    public SummarySnapshot withNamePrefix(String prefix) {
+        return new SummarySnapshot(getMetadata().withNamePrefix(prefix), getDataPoints());
+    }
+
+    /**
+     * Merge additional labels to all the data points.
+     */
+    public SummarySnapshot withLabels(Labels labels) {
+        var points = getDataPoints()
+                .stream()
+                .map(point -> new SummarySnapshot.SummaryDataPointSnapshot(point.getCount(), point.getSum(), point.getQuantiles(), point.getLabels().merge(labels), point.getExemplars(), point.getCreatedTimestampMillis(), point.getScrapeTimestampMillis()))
+                .collect(Collectors.toList());
+        return new SummarySnapshot(getMetadata(), points);
+    }
+
     @Override
     public List<SummaryDataPointSnapshot> getDataPoints() {
         return (List<SummaryDataPointSnapshot>) dataPoints;
diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/UnknownSnapshot.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/UnknownSnapshot.java
index b02228907..6195e29ed 100644
--- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/UnknownSnapshot.java
+++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/UnknownSnapshot.java
@@ -3,6 +3,7 @@
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
+import java.util.stream.Collectors;
 
 /**
  * Immutable snapshot of an Unknown (Untyped) metric.
@@ -21,6 +22,36 @@ public UnknownSnapshot(MetricMetadata metadata, Collection<UnknownDataPointSnaps
         super(metadata, data);
     }
 
+
+    @Override
+    public UnknownSnapshot merge(MetricSnapshot snapshot) {
+        if (!(getMetadata().equals(snapshot.getMetadata())))
+            throw new IllegalArgumentException("Unable to merge - metadata mismatch.");
+        if (snapshot instanceof UnknownSnapshot s) {
+            var result = new ArrayList<UnknownSnapshot.UnknownDataPointSnapshot>();
+            result.addAll(this.getDataPoints());
+            result.addAll(s.getDataPoints());
+            return new UnknownSnapshot(getMetadata(), result);
+        } else {
+            throw new IllegalArgumentException("Unable to merge - invalid snapshot type");
+        }
+    }
+
+
+    @Override
+    public UnknownSnapshot withNamePrefix(String prefix) {
+        return new UnknownSnapshot(getMetadata().withNamePrefix(prefix), getDataPoints());
+    }
+
+    /** Merge additional labels to all the data points. */
+    public UnknownSnapshot withLabels(Labels labels) {
+        var points = getDataPoints()
+                .stream()
+                .map(point -> new UnknownSnapshot.UnknownDataPointSnapshot(point.value, point.getLabels().merge(labels), point.exemplar, point.getScrapeTimestampMillis()))
+                .collect(Collectors.toList());
+        return new UnknownSnapshot(getMetadata(), points);
+    }
+
     @Override
     public List<UnknownDataPointSnapshot> getDataPoints() {
         return (List<UnknownDataPointSnapshot>) dataPoints;
diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/registry/CollectorTransformationsTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/registry/CollectorTransformationsTest.java
new file mode 100644
index 000000000..e4ecaf829
--- /dev/null
+++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/registry/CollectorTransformationsTest.java
@@ -0,0 +1,54 @@
+package io.prometheus.metrics.model.registry;
+
+import io.prometheus.metrics.model.snapshots.CounterSnapshot;
+import io.prometheus.metrics.model.snapshots.CounterSnapshot.CounterDataPointSnapshot;
+import io.prometheus.metrics.model.snapshots.Labels;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class CollectorTransformationsTest {
+
+    @Test
+    public void testNamePrefix() {
+        var metrics = CollectorBuilder.fromMetric(() -> CounterSnapshot.builder()
+                        .name("counter1")
+                        .dataPoint(CounterDataPointSnapshot.builder()
+                                .labels(Labels.of("path", "/hello"))
+                                .value(1.0)
+                                .build()
+                        )
+                        .build()
+                );
+
+        var prefixed = CollectorBuilder.withNamePrefix(metrics, "my_");
+        var labeled = CollectorBuilder.withLabels(metrics, Labels.of("l", "v"));
+
+        Assert.assertEquals(metrics.collect(MetricNameFilter.ALLOW_ALL, null).get(0).getMetadata().getPrometheusName(), "counter1");
+        Assert.assertEquals(prefixed.collect(MetricNameFilter.ALLOW_ALL, null).get(0).getMetadata().getPrometheusName(), "my_counter1");
+        Assert.assertEquals(prefixed.collect(name -> name.equals("counter1"), null).size(), 0);
+        Assert.assertEquals(prefixed.collect(name -> name.equals("my_counter1"), null).size(), 1);
+
+        Assert.assertEquals(metrics.collect(MetricNameFilter.ALLOW_ALL, null).get(0).getDataPoints().get(0).getLabels().size(), 1);
+        Assert.assertEquals(labeled.collect(MetricNameFilter.ALLOW_ALL, null).get(0).getDataPoints().get(0).getLabels().size(), 2);
+        Assert.assertTrue(labeled.collect(MetricNameFilter.ALLOW_ALL, null).get(0).getDataPoints().get(0).getLabels().contains("l"));
+    }
+
+    @Test
+    public void testLabels() {
+        var metrics = CollectorBuilder.fromMetric(() -> CounterSnapshot.builder()
+                .name("counter1")
+                .dataPoint(CounterDataPointSnapshot.builder()
+                        .labels(Labels.of("path", "/hello"))
+                        .value(1.0)
+                        .build()
+                )
+                .build()
+        );
+
+        var labeled = CollectorBuilder.withLabels(metrics, Labels.of("l", "v"));
+        Assert.assertEquals(metrics.collect(MetricNameFilter.ALLOW_ALL, null).get(0).getDataPoints().get(0).getLabels().size(), 1);
+        Assert.assertEquals(labeled.collect(MetricNameFilter.ALLOW_ALL, null).get(0).getDataPoints().get(0).getLabels().size(), 2);
+        Assert.assertTrue(labeled.collect(MetricNameFilter.ALLOW_ALL, null).get(0).getDataPoints().get(0).getLabels().contains("l"));
+    }
+
+}
diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/registry/MetricNameFilterTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/registry/MetricNameFilterTest.java
index 91bcf74fc..92f815252 100644
--- a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/registry/MetricNameFilterTest.java
+++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/registry/MetricNameFilterTest.java
@@ -2,18 +2,12 @@
 
 import io.prometheus.metrics.model.snapshots.CounterSnapshot;
 import io.prometheus.metrics.model.snapshots.CounterSnapshot.CounterDataPointSnapshot;
-import io.prometheus.metrics.model.snapshots.GaugeSnapshot;
-import io.prometheus.metrics.model.snapshots.GaugeSnapshot.GaugeDataPointSnapshot;
 import io.prometheus.metrics.model.snapshots.Labels;
 import io.prometheus.metrics.model.snapshots.MetricSnapshots;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.Predicate;
 
 public class MetricNameFilterTest {
@@ -27,30 +21,33 @@ public void setUp() {
 
     @Test
     public void testCounter() {
-        registry.register(() -> CounterSnapshot.builder()
-                .name("counter1")
-                .help("test counter 1")
-                .dataPoint(CounterDataPointSnapshot.builder()
-                        .labels(Labels.of("path", "/hello"))
-                        .value(1.0)
+        registry.register(
+                CollectorBuilder.fromMetric(() -> CounterSnapshot.builder()
+                        .name("counter1")
+                        .help("test counter 1")
+                        .dataPoint(CounterDataPointSnapshot.builder()
+                                .labels(Labels.of("path", "/hello"))
+                                .value(1.0)
+                                .build()
+                        )
+                        .dataPoint(CounterDataPointSnapshot.builder()
+                                .labels(Labels.of("path", "/goodbye"))
+                                .value(2.0)
+                                .build()
+                        )
                         .build()
-                )
-                .dataPoint(CounterDataPointSnapshot.builder()
-                        .labels(Labels.of("path", "/goodbye"))
-                        .value(2.0)
-                        .build()
-                )
-                .build()
-        );
-        registry.register(() -> CounterSnapshot.builder()
-                .name("counter2")
-                .help("test counter 2")
-                .dataPoint(CounterDataPointSnapshot.builder()
-                        .value(1.0)
+                ));
+
+        registry.register(
+                CollectorBuilder.fromMetric(() -> CounterSnapshot.builder()
+                        .name("counter2")
+                        .help("test counter 2")
+                        .dataPoint(CounterDataPointSnapshot.builder()
+                                .value(1.0)
+                                .build()
+                        )
                         .build()
-                )
-                .build()
-        );
+                ));
 
         MetricNameFilter filter = MetricNameFilter.builder().build();
         Assert.assertEquals(2, registry.scrape(filter).size());
diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/registry/MultiCollectorNameFilterTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/registry/MultiCollectorNameFilterTest.java
index b36d58aa6..723541f24 100644
--- a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/registry/MultiCollectorNameFilterTest.java
+++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/registry/MultiCollectorNameFilterTest.java
@@ -9,92 +9,58 @@
 import org.junit.Before;
 import org.junit.Test;
 
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
 import java.util.function.Predicate;
 
 public class MultiCollectorNameFilterTest {
 
     private PrometheusRegistry registry;
-    private boolean[] collectCalled = {false};
     private Predicate<String> includedNames = null;
-    List<String> prometheusNames = new ArrayList<>();
 
     @Before
     public void setUp() {
         registry = new PrometheusRegistry();
-        collectCalled[0] = false;
-        includedNames = null;
-        prometheusNames = Collections.emptyList();
 
-        registry.register(new MultiCollector() {
-            @Override
-            public MetricSnapshots collect() {
-                collectCalled[0] = true;
-                return MetricSnapshots.builder()
-                        .metricSnapshot(CounterSnapshot.builder()
-                                .name("counter_1")
-                                .dataPoint(CounterDataPointSnapshot.builder().value(1.0).build())
-                                .build()
-                        )
-                        .metricSnapshot(GaugeSnapshot.builder()
-                                .name("gauge_2")
-                                .dataPoint(GaugeDataPointSnapshot.builder().value(1.0).build())
-                                .build()
-                        )
-                        .build();
-            }
-
-            @Override
-            public List<String> getPrometheusNames() {
-                return prometheusNames;
-            }
-        });
+        registry.register(CollectorBuilder.fromMetrics(() -> MetricSnapshots.builder()
+                .metricSnapshot(CounterSnapshot.builder()
+                        .name("counter_1")
+                        .dataPoint(CounterDataPointSnapshot.builder().value(1.0).build())
+                        .build()
+                )
+                .metricSnapshot(GaugeSnapshot.builder()
+                        .name("gauge_2")
+                        .dataPoint(GaugeDataPointSnapshot.builder().value(1.0).build())
+                        .build()
+                )
+                .build()));
     }
 
     @Test
-    public void testPartialFilter() {
-
+    public void filterAvoidsScrape() {
         includedNames = name -> name.equals("counter_1");
-
         MetricSnapshots snapshots = registry.scrape(includedNames);
-        Assert.assertTrue(collectCalled[0]);
         Assert.assertEquals(1, snapshots.size());
         Assert.assertEquals("counter_1", snapshots.get(0).getMetadata().getName());
     }
 
     @Test
     public void testPartialFilterWithPrometheusNames() {
-
         includedNames = name -> name.equals("counter_1");
-        prometheusNames = Arrays.asList("counter_1", "gauge_2");
-
         MetricSnapshots snapshots = registry.scrape(includedNames);
-        Assert.assertTrue(collectCalled[0]);
         Assert.assertEquals(1, snapshots.size());
         Assert.assertEquals("counter_1", snapshots.get(0).getMetadata().getName());
     }
 
     @Test
     public void testCompleteFilter_CollectCalled() {
-
         includedNames = name -> !name.equals("counter_1") && !name.equals("gauge_2");
-
         MetricSnapshots snapshots = registry.scrape(includedNames);
-        Assert.assertTrue(collectCalled[0]);
         Assert.assertEquals(0, snapshots.size());
     }
 
     @Test
     public void testCompleteFilter_CollectNotCalled() {
-
         includedNames = name -> !name.equals("counter_1") && !name.equals("gauge_2");
-        prometheusNames = Arrays.asList("counter_1", "gauge_2");
-
         MetricSnapshots snapshots = registry.scrape(includedNames);
-        Assert.assertFalse(collectCalled[0]);
         Assert.assertEquals(0, snapshots.size());
     }
 }
diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/registry/PrometheusRegistryTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/registry/PrometheusRegistryTest.java
index 498d2354b..026eb154e 100644
--- a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/registry/PrometheusRegistryTest.java
+++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/registry/PrometheusRegistryTest.java
@@ -2,82 +2,54 @@
 
 import io.prometheus.metrics.model.snapshots.CounterSnapshot;
 import io.prometheus.metrics.model.snapshots.GaugeSnapshot;
-import io.prometheus.metrics.model.snapshots.MetricSnapshot;
 import io.prometheus.metrics.model.snapshots.MetricSnapshots;
 import org.junit.Assert;
 import org.junit.Test;
 
 public class PrometheusRegistryTest {
 
-    Collector noName = () -> GaugeSnapshot.builder()
-            .name("no_name_gauge")
-            .build();
-
-    Collector counterA1 = new Collector() {
-        @Override
-        public MetricSnapshot collect() {
-            return CounterSnapshot.builder().name("counter_a").build();
-        }
-
-        @Override
-        public String getPrometheusName() {
-            return "counter_a";
-        }
-    };
-
-    Collector counterA2 = new Collector() {
-        @Override
-        public MetricSnapshot collect() {
-            return CounterSnapshot.builder().name("counter.a").build();
-        }
-
-        @Override
-        public String getPrometheusName() {
-            return "counter_a";
-        }
-    };
-
-    Collector counterB = new Collector() {
-        @Override
-        public MetricSnapshot collect() {
-            return CounterSnapshot.builder().name("counter_b").build();
-        }
-
-        @Override
-        public String getPrometheusName() {
-            return "counter_b";
-        }
-    };
-
-    Collector gaugeA = new Collector() {
-        @Override
-        public MetricSnapshot collect() {
-            return GaugeSnapshot.builder().name("gauge_a").build();
-        }
-
-        @Override
-        public String getPrometheusName() {
-            return "gauge_a";
-        }
-    };
+    Collector counterA1 = CollectorBuilder.fromMetric(() -> CounterSnapshot.builder().name("metric_a").build());
+    Collector counterA2 = CollectorBuilder.fromMetric(() -> CounterSnapshot.builder().name("metric_a")
+            .dataPoint(CounterSnapshot.CounterDataPointSnapshot.builder().value(1.0).build()).build());
+    Collector counterA3 = CollectorBuilder.fromMetric(() -> CounterSnapshot.builder().name("metric_a")
+            .dataPoint(CounterSnapshot.CounterDataPointSnapshot.builder().value(2.0).build()).build());
+    Collector counterB = CollectorBuilder.fromMetric(() -> CounterSnapshot.builder().name("metric_b").build());
+    Collector gaugeA = CollectorBuilder.fromMetric(() -> GaugeSnapshot.builder().name("metric_a").build());
 
     @Test
-    public void registerNoName() {
+    public void scrapeFailsOnTypeMismatch() {
         PrometheusRegistry registry = new PrometheusRegistry();
-        // If the collector does not have a name at registration time, there is no conflict during registration.
-        registry.register(noName);
-        registry.register(noName);
+        registry.register(counterA1);
+        registry.register(gaugeA);
         // However, at scrape time the collector has to provide a metric name, and then we'll get a duplicat name error.
         try {
             registry.scrape();
-        } catch (IllegalStateException e) {
-            Assert.assertTrue(e.getMessage().contains("duplicate") && e.getMessage().contains("no_name_gauge"));
+        } catch (IllegalArgumentException e) {
+            Assert.assertTrue(e.getMessage().contains("invalid type") && e.getMessage().contains("metric_a"));
             return;
         }
         Assert.fail("Expected duplicate name exception");
     }
 
-    @Test(expected = IllegalStateException.class)
+    @Test
+    public void scrapeFailsOnDuplicateLabels() {
+        PrometheusRegistry registry = new PrometheusRegistry();
+        registry.register(counterA2);
+        registry.register(counterA3);
+        try {
+            registry.scrape();
+        } catch (IllegalArgumentException e) {
+            Assert.assertTrue(e.getMessage(), e.getMessage().contains("Duplicate labels"));
+            return;
+        }
+        Assert.fail("Expected duplicate labels exception");
+    }
+
+
+    /** It is allowed to register collectors with duplicate names.
+     * Scrape will fail if metadata or metric types do not match.
+     */
+    @Test
     public void registerDuplicateName() {
         PrometheusRegistry registry = new PrometheusRegistry();
         registry.register(counterA1);
@@ -89,16 +61,15 @@ public void registerOk() {
         PrometheusRegistry registry = new PrometheusRegistry();
         registry.register(counterA1);
         registry.register(counterB);
-        registry.register(gaugeA);
         MetricSnapshots snapshots = registry.scrape();
-        Assert.assertEquals(3, snapshots.size());
+        Assert.assertEquals(2, snapshots.size());
 
         registry.unregister(counterB);
         snapshots = registry.scrape();
-        Assert.assertEquals(2, snapshots.size());
+        Assert.assertEquals(1, snapshots.size());
 
         registry.register(counterB);
         snapshots = registry.scrape();
-        Assert.assertEquals(3, snapshots.size());
+        Assert.assertEquals(2, snapshots.size());
     }
 }
diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/MetricSnapshotsTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/MetricSnapshotsTest.java
index 61b54cb3a..489830602 100644
--- a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/MetricSnapshotsTest.java
+++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/MetricSnapshotsTest.java
@@ -27,15 +27,44 @@ public void testSort() {
                 .name("counter3")
                 .dataPoint(CounterSnapshot.CounterDataPointSnapshot.builder().value(1.0).build())
                 .build();
-        MetricSnapshots snapshots = new MetricSnapshots(c2, c3, c1);
+        MetricSnapshots snapshots = MetricSnapshots.of(c2, c3, c1);
         Assert.assertEquals(3, snapshots.size());
         Assert.assertEquals("counter1", snapshots.get(0).getMetadata().getName());
         Assert.assertEquals("counter2", snapshots.get(1).getMetadata().getName());
         Assert.assertEquals("counter3", snapshots.get(2).getMetadata().getName());
     }
 
-    @Test(expected = IllegalArgumentException.class)
+    /** It is legal to have duplicate names as long as labels are different. */
+    @Test
     public void testDuplicateName() {
+        CounterSnapshot c1 = CounterSnapshot.builder()
+                .name("my_metric")
+                .dataPoint(CounterSnapshot.CounterDataPointSnapshot.builder().value(1.0).labels(Labels.of("name", "c1")).build())
+                .build();
+        CounterSnapshot c2 = CounterSnapshot.builder()
+                .name("my_metric")
+                .dataPoint(CounterSnapshot.CounterDataPointSnapshot.builder().value(1.0).labels(Labels.of("name", "c2")).build())
+                .build();
+        MetricSnapshots.of(c1, c2);
+    }
+
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testDuplicateNameSameLabels() {
+        CounterSnapshot c1 = CounterSnapshot.builder()
+                .name("my_metric")
+                .dataPoint(CounterSnapshot.CounterDataPointSnapshot.builder().value(1.0).build())
+                .build();
+        CounterSnapshot c2 = CounterSnapshot.builder()
+                .name("my_metric")
+                .dataPoint(CounterSnapshot.CounterDataPointSnapshot.builder().value(1.0).build())
+                .build();
+        MetricSnapshots.of(c1, c2);
+    }
+
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testDuplicateNameDifferentTypes() {
         // Q: What if you have a counter named "foo" and a gauge named "foo"?
         // A: Great question. You might think this is a valid scenario, because the counter will produce
         //    the values "foo_total" and "foo_created" while the gauge will produce the value "foo".
@@ -49,19 +78,7 @@ public void testDuplicateName() {
                 .name("my_metric")
                 .dataPoint(GaugeSnapshot.GaugeDataPointSnapshot.builder().value(1.0).build())
                 .build();
-        new MetricSnapshots(c, g);
-    }
-
-    @Test
-    public void testBuilder() {
-        CounterSnapshot counter = CounterSnapshot.builder()
-                .name("my_metric")
-                .dataPoint(CounterSnapshot.CounterDataPointSnapshot.builder().value(1.0).build())
-                .build();
-        MetricSnapshots.Builder builder = MetricSnapshots.builder();
-        Assert.assertFalse(builder.containsMetricName("my_metric"));
-        builder.metricSnapshot(counter);
-        Assert.assertTrue(builder.containsMetricName("my_metric"));
+        MetricSnapshots.of(c, g);
     }
 
     @Test(expected = UnsupportedOperationException.class)
diff --git a/prometheus-metrics-simpleclient-bridge/src/main/java/io/prometheus/metrics/simpleclient/bridge/SimpleclientCollector.java b/prometheus-metrics-simpleclient-bridge/src/main/java/io/prometheus/metrics/simpleclient/bridge/SimpleclientCollector.java
index a775b2f55..4aebcfe96 100644
--- a/prometheus-metrics-simpleclient-bridge/src/main/java/io/prometheus/metrics/simpleclient/bridge/SimpleclientCollector.java
+++ b/prometheus-metrics-simpleclient-bridge/src/main/java/io/prometheus/metrics/simpleclient/bridge/SimpleclientCollector.java
@@ -1,10 +1,10 @@
 package io.prometheus.metrics.simpleclient.bridge;
 
-import io.prometheus.client.Collector;
 import io.prometheus.client.CollectorRegistry;
 import io.prometheus.metrics.config.PrometheusProperties;
-import io.prometheus.metrics.model.registry.MultiCollector;
+import io.prometheus.metrics.model.registry.Collector;
 import io.prometheus.metrics.model.registry.PrometheusRegistry;
+import io.prometheus.metrics.model.registry.PrometheusScrapeRequest;
 import io.prometheus.metrics.model.snapshots.ClassicHistogramBuckets;
 import io.prometheus.metrics.model.snapshots.CounterSnapshot;
 import io.prometheus.metrics.model.snapshots.Exemplar;
@@ -48,7 +48,7 @@
  *     .register(prometheusRegistry);
  * }</pre>
  */
-public class SimpleclientCollector implements MultiCollector {
+public class SimpleclientCollector implements Collector {
 
     private final CollectorRegistry simpleclientRegistry;
 
@@ -57,14 +57,20 @@ private SimpleclientCollector(CollectorRegistry simpleclientRegistry) {
     }
 
     @Override
-    public MetricSnapshots collect() {
-        return convert(simpleclientRegistry.metricFamilySamples());
+    public MetricSnapshots collect(Predicate<String> nameFilter, PrometheusScrapeRequest scrapeRequest) {
+        io.prometheus.client.Predicate<String> filter = new io.prometheus.client.Predicate<String>() {
+            @Override
+            public boolean test(String s) {
+                return nameFilter.test(s);
+            }
+        };
+        return convert(simpleclientRegistry.filteredMetricFamilySamples(filter));
     }
 
-    private MetricSnapshots convert(Enumeration<Collector.MetricFamilySamples> samples) {
+    private MetricSnapshots convert(Enumeration<io.prometheus.client.Collector.MetricFamilySamples> samples) {
         MetricSnapshots.Builder result = MetricSnapshots.builder();
         while (samples.hasMoreElements()) {
-            Collector.MetricFamilySamples sample = samples.nextElement();
+            io.prometheus.client.Collector.MetricFamilySamples sample = samples.nextElement();
             switch (sample.type) {
                 case COUNTER:
                     result.metricSnapshot(convertCounter(sample));
@@ -97,13 +103,13 @@ private MetricSnapshots convert(Enumeration<Collector.MetricFamilySamples> sampl
         return result.build();
     }
 
-    private MetricSnapshot convertCounter(Collector.MetricFamilySamples samples) {
+    private MetricSnapshot convertCounter(io.prometheus.client.Collector.MetricFamilySamples samples) {
         CounterSnapshot.Builder counter = CounterSnapshot.builder()
                 .name(stripSuffix(samples.name, "_total"))
                 .help(samples.help)
                 .unit(convertUnit(samples));
         Map<Labels, CounterSnapshot.CounterDataPointSnapshot.Builder> dataPoints = new HashMap<>();
-        for (Collector.MetricFamilySamples.Sample sample : samples.samples) {
+        for (io.prometheus.client.Collector.MetricFamilySamples.Sample sample : samples.samples) {
             Labels labels = Labels.of(sample.labelNames, sample.labelValues);
             CounterSnapshot.CounterDataPointSnapshot.Builder dataPoint = dataPoints.computeIfAbsent(labels, l -> CounterSnapshot.CounterDataPointSnapshot.builder().labels(labels));
             if (sample.name.endsWith("_created")) {
@@ -121,12 +127,12 @@ private MetricSnapshot convertCounter(Collector.MetricFamilySamples samples) {
         return counter.build();
     }
 
-    private MetricSnapshot convertGauge(Collector.MetricFamilySamples samples) {
+    private MetricSnapshot convertGauge(io.prometheus.client.Collector.MetricFamilySamples samples) {
         GaugeSnapshot.Builder gauge = GaugeSnapshot.builder()
                 .name(samples.name)
                 .help(samples.help)
                 .unit(convertUnit(samples));
-        for (Collector.MetricFamilySamples.Sample sample : samples.samples) {
+        for (io.prometheus.client.Collector.MetricFamilySamples.Sample sample : samples.samples) {
             GaugeSnapshot.GaugeDataPointSnapshot.Builder dataPoint = GaugeSnapshot.GaugeDataPointSnapshot.builder()
                     .value(sample.value)
                     .labels(Labels.of(sample.labelNames, sample.labelValues))
@@ -139,7 +145,7 @@ private MetricSnapshot convertGauge(Collector.MetricFamilySamples samples) {
         return gauge.build();
     }
 
-    private MetricSnapshot convertHistogram(Collector.MetricFamilySamples samples, boolean isGaugeHistogram) {
+    private MetricSnapshot convertHistogram(io.prometheus.client.Collector.MetricFamilySamples samples, boolean isGaugeHistogram) {
         HistogramSnapshot.Builder histogram = HistogramSnapshot.builder()
                 .name(samples.name)
                 .help(samples.help)
@@ -148,7 +154,7 @@ private MetricSnapshot convertHistogram(Collector.MetricFamilySamples samples, b
         Map<Labels, HistogramSnapshot.HistogramDataPointSnapshot.Builder> dataPoints = new HashMap<>();
         Map<Labels, Map<Double, Long>> cumulativeBuckets = new HashMap<>();
         Map<Labels, Exemplars.Builder> exemplars = new HashMap<>();
-        for (Collector.MetricFamilySamples.Sample sample : samples.samples) {
+        for (io.prometheus.client.Collector.MetricFamilySamples.Sample sample : samples.samples) {
             Labels labels = labelsWithout(sample, "le");
             dataPoints.computeIfAbsent(labels, l -> HistogramSnapshot.HistogramDataPointSnapshot.builder()
                     .labels(labels));
@@ -179,7 +185,7 @@ private MetricSnapshot convertHistogram(Collector.MetricFamilySamples samples, b
         return histogram.build();
     }
 
-    private MetricSnapshot convertSummary(Collector.MetricFamilySamples samples) {
+    private MetricSnapshot convertSummary(io.prometheus.client.Collector.MetricFamilySamples samples) {
         SummarySnapshot.Builder summary = SummarySnapshot.builder()
                 .name(samples.name)
                 .help(samples.help)
@@ -187,7 +193,7 @@ private MetricSnapshot convertSummary(Collector.MetricFamilySamples samples) {
         Map<Labels, SummarySnapshot.SummaryDataPointSnapshot.Builder> dataPoints = new HashMap<>();
         Map<Labels, Quantiles.Builder> quantiles = new HashMap<>();
         Map<Labels, Exemplars.Builder> exemplars = new HashMap<>();
-        for (Collector.MetricFamilySamples.Sample sample : samples.samples) {
+        for (io.prometheus.client.Collector.MetricFamilySamples.Sample sample : samples.samples) {
             Labels labels = labelsWithout(sample, "quantile");
             dataPoints.computeIfAbsent(labels, l -> SummarySnapshot.SummaryDataPointSnapshot.builder()
                     .labels(labels));
@@ -223,12 +229,12 @@ private MetricSnapshot convertSummary(Collector.MetricFamilySamples samples) {
         return summary.build();
     }
 
-    private MetricSnapshot convertStateSet(Collector.MetricFamilySamples samples) {
+    private MetricSnapshot convertStateSet(io.prometheus.client.Collector.MetricFamilySamples samples) {
         StateSetSnapshot.Builder stateSet = StateSetSnapshot.builder()
                 .name(samples.name)
                 .help(samples.help);
         Map<Labels, StateSetSnapshot.StateSetDataPointSnapshot.Builder> dataPoints = new HashMap<>();
-        for (Collector.MetricFamilySamples.Sample sample : samples.samples) {
+        for (io.prometheus.client.Collector.MetricFamilySamples.Sample sample : samples.samples) {
             Labels labels = labelsWithout(sample, sample.name);
             dataPoints.computeIfAbsent(labels, l -> StateSetSnapshot.StateSetDataPointSnapshot.builder().labels(labels));
             String stateName = null;
@@ -252,12 +258,12 @@ private MetricSnapshot convertStateSet(Collector.MetricFamilySamples samples) {
         return stateSet.build();
     }
 
-    private MetricSnapshot convertUnknown(Collector.MetricFamilySamples samples) {
+    private MetricSnapshot convertUnknown(io.prometheus.client.Collector.MetricFamilySamples samples) {
         UnknownSnapshot.Builder unknown = UnknownSnapshot.builder()
                 .name(samples.name)
                 .help(samples.help)
                 .unit(convertUnit(samples));
-        for (Collector.MetricFamilySamples.Sample sample : samples.samples) {
+        for (io.prometheus.client.Collector.MetricFamilySamples.Sample sample : samples.samples) {
             UnknownSnapshot.UnknownDataPointSnapshot.Builder dataPoint = UnknownSnapshot.UnknownDataPointSnapshot.builder()
                     .value(sample.value)
                     .labels(Labels.of(sample.labelNames, sample.labelValues))
@@ -278,7 +284,7 @@ private String stripSuffix(String name, String suffix) {
         }
     }
 
-    private Unit convertUnit(Collector.MetricFamilySamples samples) {
+    private Unit convertUnit(io.prometheus.client.Collector.MetricFamilySamples samples) {
         if (samples.unit != null && !samples.unit.isEmpty()) {
             return new Unit(samples.unit);
         } else {
@@ -300,7 +306,7 @@ private ClassicHistogramBuckets makeBuckets(Map<Double, Long> cumulativeBuckets)
         return result.build();
     }
 
-    private void addBucket(Map<Double, Long> buckets, Collector.MetricFamilySamples.Sample sample) {
+    private void addBucket(Map<Double, Long> buckets, io.prometheus.client.Collector.MetricFamilySamples.Sample sample) {
         for (int i = 0; i < sample.labelNames.size(); i++) {
             if (sample.labelNames.get(i).equals("le")) {
                 double upperBound;
@@ -322,7 +328,7 @@ private void addBucket(Map<Double, Long> buckets, Collector.MetricFamilySamples.
     }
 
 
-    private Labels labelsWithout(Collector.MetricFamilySamples.Sample sample, String excludedLabelName) {
+    private Labels labelsWithout(io.prometheus.client.Collector.MetricFamilySamples.Sample sample, String excludedLabelName) {
         Labels.Builder labels = Labels.builder();
         for (int i = 0; i < sample.labelNames.size(); i++) {
             if (!sample.labelNames.get(i).equals(excludedLabelName)) {
@@ -332,11 +338,11 @@ private Labels labelsWithout(Collector.MetricFamilySamples.Sample sample, String
         return labels.build();
     }
 
-    private MetricSnapshot convertInfo(Collector.MetricFamilySamples samples) {
+    private MetricSnapshot convertInfo(io.prometheus.client.Collector.MetricFamilySamples samples) {
         InfoSnapshot.Builder info = InfoSnapshot.builder()
                 .name(stripSuffix(samples.name, "_info"))
                 .help(samples.help);
-        for (Collector.MetricFamilySamples.Sample sample : samples.samples) {
+        for (io.prometheus.client.Collector.MetricFamilySamples.Sample sample : samples.samples) {
             info.dataPoint(InfoSnapshot.InfoDataPointSnapshot.builder()
                     .labels(Labels.of(sample.labelNames, sample.labelValues))
                     .build());