Code review comment: Can't this happen outside of the `MetricRegistryImpl` when we create the `MetricReporter` or is there a contract that certain operations can only be when we create the `MetricRegistryImpl`? If the `MetricReporter` depend on the `MetricRegistryImpl` then I understand why we need a factory. But if not, then I think it would be easier to directly start the `MetricReporters`.

Code before review:
public MetricRegistryImpl(MetricRegistryConfiguration config) {
this.maximumFramesize = config.getQueryServiceMessageSizeLimit();
this.scopeFormats = config.getScopeFormats();
this.globalDelimiter = config.getDelimiter();
this.delimiters = new ArrayList<>(10);
this.terminationFuture = new CompletableFuture<>();
this.isShutdown = false;
this.reporters = new ArrayList<>(4);
List<MetricRegistryConfiguration.ReporterSetup> reporterConfigurations = config.getReporterSetups();
this.executor = Executors.newSingleThreadScheduledExecutor(new ExecutorThreadFactory("Flink-MetricRegistry"));
this.queryService = null;
this.metricQueryServicePath = null;
if (reporterConfigurations.isEmpty()) {
LOG.info("No metrics reporter configured, no metrics will be exposed/reported.");
} else {
for (MetricRegistryConfiguration.ReporterSetup reporterSetup : reporterConfigurations) {
final String namedReporter = reporterSetup.getName();
final MetricConfig metricConfig = reporterSetup.getConfiguration();
try {
String configuredPeriod = metricConfig.getString(ConfigConstants.METRICS_REPORTER_INTERVAL_SUFFIX, null);
TimeUnit timeunit = TimeUnit.SECONDS;
long period = 10;
if (configuredPeriod != null) {
try {
String[] interval = configuredPeriod.split(" ");
period = Long.parseLong(interval[0]);
timeunit = TimeUnit.valueOf(interval[1]);
}
catch (Exception e) {
LOG.error("Cannot parse report interval from config: " + configuredPeriod +
" - please use values like '10 SECONDS' or '500 MILLISECONDS'. " +
"Using default reporting interval.");
}
}
final MetricReporter reporterInstance = reporterSetup.getSupplier().get();
final String className = reporterInstance.getClass().getName();
LOG.info("Configuring {} with {}.", namedReporter, metricConfig);
reporterInstance.open(metricConfig);
if (reporterInstance instanceof Scheduled) {
LOG.info("Periodically reporting metrics in intervals of {} {} for reporter {} of type {}.", period, timeunit.name(), namedReporter, className);
executor.scheduleWithFixedDelay(
new MetricRegistryImpl.ReporterTask((Scheduled) reporterInstance), period, period, timeunit);
} else {
LOG.info("Reporting metrics for reporter {} of type {}.", namedReporter, className);
}
reporters.add(reporterInstance);
String delimiterForReporter = metricConfig.getString(ConfigConstants.METRICS_REPORTER_SCOPE_DELIMITER, String.valueOf(globalDelimiter));
if (delimiterForReporter.length() != 1) {
LOG.warn("Failed to parse delimiter '{}' for reporter '{}', using global delimiter '{}'.", delimiterForReporter, namedReporter, globalDelimiter);
delimiterForReporter = String.valueOf(globalDelimiter);
}
this.delimiters.add(delimiterForReporter.charAt(0));
}
catch (Throwable t) {
LOG.error("Could not instantiate metrics reporter {}. Metrics might not be exposed/reported.", namedReporter, t);
}
}
}
}

Line under review: final MetricReporter reporterInstance = reporterSetup.getSupplier().get();

Code after addressing the review:
public MetricRegistryImpl(MetricRegistryConfiguration config) {
this(config, Collections.emptyList());
}