Repository: apache/kafka
File: connect/runtime/src/main/java/org/apache/kafka/connect/runtime/ConnectMetrics.java (Java)

Code before:
        return Plugin.wrapInstance(headerConverter, metrics, headerConverterPluginTags(connectorTaskId));
    }

    public Plugin<Converter> wrap(Converter converter, ConnectorTaskId connectorTaskId, boolean isKey) {
        return Plugin.wrapInstance(converter, metrics, converterPluginTags(connectorTaskId, isKey));
    }

    public <R extends ConnectRecord<R>> Plugin<Transformation<R>> wrap(Transformation<R> transformation, ConnectorTaskId connectorTaskId, String alias) {
        return Plugin.wrapInstance(transformation, metrics, transformationPluginTags(connectorTaskId, alias));
    }

    public <R extends ConnectRecord<R>> Plugin<Predicate<R>> wrap(Predicate<R> predicate, ConnectorTaskId connectorTaskId, String alias) {
        return Plugin.wrapInstance(predicate, metrics, predicatePluginTags(connectorTaskId, alias));
    }

    public static class MetricGroupId {
        private final String groupName;
        private final Map<String, String> tags;
        private final int hc;
        private final String str;

        public MetricGroupId(String groupName, Map<String, String> tags) {
            Objects.requireNonNull(groupName);
            Objects.requireNonNull(tags);
            this.groupName = groupName;
            this.tags = Map.copyOf(new LinkedHashMap<>(tags));
            this.hc = Objects.hash(this.groupName, this.tags);
            StringBuilder sb = new StringBuilder(this.groupName);
            for (Map.Entry<String, String> entry : this.tags.entrySet()) {
                sb.append(";").append(entry.getKey()).append('=').append(entry.getValue());
            }
            this.str = sb.toString();
        }

        /**
         * Get the group name.
         *
         * @return the group name; never null
         */
        public String groupName() {
            return groupName;
        }

        /**
         * Get the immutable map of tag names and values.
         *
         * @return the tags; never null
         */
        public Map<String, String> tags() {
            return tags;
        }

Review comment (suggestion):
IIRC, `Map.copyOf` does not guarantee insertion order, so we should keep using `Collections.unmodifiableMap` instead. By the way, it would be useful to add comment to prevent unintended change in the future. 

Code after:
    public Plugin<HeaderConverter> wrap(HeaderConverter headerConverter, ConnectorTaskId connectorTaskId) {
        return Plugin.wrapInstance(headerConverter, metrics, headerConverterPluginTags(connectorTaskId));
    }

    public Plugin<Converter> wrap(Converter converter, ConnectorTaskId connectorTaskId, boolean isKey) {
        return Plugin.wrapInstance(converter, metrics, converterPluginTags(connectorTaskId, isKey));
    }

    public <R extends ConnectRecord<R>> Plugin<Transformation<R>> wrap(Transformation<R> transformation, ConnectorTaskId connectorTaskId, String alias) {
        return Plugin.wrapInstance(transformation, metrics, transformationPluginTags(connectorTaskId, alias));
    }

    public <R extends ConnectRecord<R>> Plugin<Predicate<R>> wrap(Predicate<R> predicate, ConnectorTaskId connectorTaskId, String alias) {
        return Plugin.wrapInstance(predicate, metrics, predicatePluginTags(connectorTaskId, alias));
    }

    public static class MetricGroupId {
        private final String groupName;
        private final Map<String, String> tags;
        private final int hc;
        private final String str;

        public MetricGroupId(String groupName, Map<String, String> tags) {
            Objects.requireNonNull(groupName);
            Objects.requireNonNull(tags);
            this.groupName = groupName;
            this.tags = Collections.unmodifiableMap(new LinkedHashMap<>(tags)); // To ensure the order of insertion, we have to use Collections.
            this.hc = Objects.hash(this.groupName, this.tags);
            StringBuilder sb = new StringBuilder(this.groupName);
            for (Map.Entry<String, String> entry : this.tags.entrySet()) {
                sb.append(";").append(entry.getKey()).append('=').append(entry.getValue());
            }
            this.str = sb.toString();
        }

        /**
         * Get the group name.
         *
         * @return the group name; never null
         */
        public String groupName() {
            return groupName;
        }

        /**
         * Get the immutable map of tag names and values.
         *
         * @return the tags; never null
         */
        public Map<String, String> tags() {
            return tags;