From ec3827ba252e44fe9132c733e50ed5c3fcaa68fa Mon Sep 17 00:00:00 2001
From: dondonz <13839920+dondonz@users.noreply.github.com>
Date: Sat, 16 Aug 2025 08:25:48 +1000
Subject: [PATCH 1/3] Add Archunit test to enforce no Map.of
---
.../graphql/archunit/MapOfUsageTest.groovy | 36 +++++++++++++++++++
1 file changed, 36 insertions(+)
create mode 100644 src/test/groovy/graphql/archunit/MapOfUsageTest.groovy
diff --git a/src/test/groovy/graphql/archunit/MapOfUsageTest.groovy b/src/test/groovy/graphql/archunit/MapOfUsageTest.groovy
new file mode 100644
index 0000000000..5a77aa3c89
--- /dev/null
+++ b/src/test/groovy/graphql/archunit/MapOfUsageTest.groovy
@@ -0,0 +1,36 @@
+package graphql.archunit
+
+import com.tngtech.archunit.core.domain.JavaClasses
+import com.tngtech.archunit.core.importer.ClassFileImporter
+import com.tngtech.archunit.core.importer.ImportOption
+import com.tngtech.archunit.lang.ArchRule
+import com.tngtech.archunit.lang.EvaluationResult
+import com.tngtech.archunit.lang.syntax.ArchRuleDefinition
+import spock.lang.Specification
+
+class MapOfUsageTest extends Specification {
+
+ private static final JavaClasses importedClasses = new ClassFileImporter()
+ .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS)
+ .importPackages("graphql")
+
+ def "should not use Map.of()"() {
+ given:
+ ArchRule mapOfRule = ArchRuleDefinition.noClasses()
+ .should()
+ .callMethod(Map.class, "of")
+ .because("Map.of() does not guarantee insertion order. Use LinkedHashMap instead for consistent serialization order.")
+
+ when:
+ EvaluationResult result = mapOfRule.evaluate(importedClasses)
+
+ then:
+ if (result.hasViolation()) {
+ println "Map.of() usage detected. Please use LinkedHashMap instead for consistent serialization order:"
+ result.getFailureReport().getDetails().each { violation ->
+ println "- ${violation}"
+ }
+ }
+ !result.hasViolation()
+ }
+}
\ No newline at end of file
From de40420e231b941486fefd9300db24c092bc6994 Mon Sep 17 00:00:00 2001
From: dondonz <13839920+dondonz@users.noreply.github.com>
Date: Sat, 16 Aug 2025 08:56:36 +1000
Subject: [PATCH 2/3] Add LinkedHashMap factory
---
.../graphql/util/LinkedHashMapFactory.java | 344 ++++++++++++++++++
.../graphql/archunit/MapOfUsageTest.groovy | 4 +-
2 files changed, 346 insertions(+), 2 deletions(-)
create mode 100644 src/main/java/graphql/util/LinkedHashMapFactory.java
diff --git a/src/main/java/graphql/util/LinkedHashMapFactory.java b/src/main/java/graphql/util/LinkedHashMapFactory.java
new file mode 100644
index 0000000000..b9cc20ef75
--- /dev/null
+++ b/src/main/java/graphql/util/LinkedHashMapFactory.java
@@ -0,0 +1,344 @@
+package graphql.util;
+
+import graphql.Internal;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * Factory class for creating LinkedHashMap instances with insertion order preservation.
+ * Use this instead of Map.of() to ensure consistent serialization order.
+ *
+ * This class provides static factory methods similar to Map.of() but returns mutable LinkedHashMap
+ * instances that preserve insertion order, which is important for consistent serialization.
+ */
+@Internal
+public final class LinkedHashMapFactory {
+
+ private LinkedHashMapFactory() {
+ // utility class
+ }
+
+ /**
+ * Returns an empty LinkedHashMap.
+ *
+ * @param the key type
+ * @param the value type
+ * @return an empty LinkedHashMap
+ */
+ public static Map of() {
+ return new LinkedHashMap<>();
+ }
+
+ /**
+ * Returns a LinkedHashMap containing a single mapping.
+ *
+ * @param the key type
+ * @param the value type
+ * @param k1 the mapping's key
+ * @param v1 the mapping's value
+ * @return a LinkedHashMap containing the specified mapping
+ */
+ public static Map of(K k1, V v1) {
+ Map map = new LinkedHashMap<>(1);
+ map.put(k1, v1);
+ return map;
+ }
+
+ /**
+ * Returns a LinkedHashMap containing two mappings.
+ *
+ * @param the key type
+ * @param the value type
+ * @param k1 the first mapping's key
+ * @param v1 the first mapping's value
+ * @param k2 the second mapping's key
+ * @param v2 the second mapping's value
+ * @return a LinkedHashMap containing the specified mappings
+ */
+ public static Map of(K k1, V v1, K k2, V v2) {
+ Map map = new LinkedHashMap<>(2);
+ map.put(k1, v1);
+ map.put(k2, v2);
+ return map;
+ }
+
+ /**
+ * Returns a LinkedHashMap containing three mappings.
+ *
+ * @param the key type
+ * @param the value type
+ * @param k1 the first mapping's key
+ * @param v1 the first mapping's value
+ * @param k2 the second mapping's key
+ * @param v2 the second mapping's value
+ * @param k3 the third mapping's key
+ * @param v3 the third mapping's value
+ * @return a LinkedHashMap containing the specified mappings
+ */
+ public static Map of(K k1, V v1, K k2, V v2, K k3, V v3) {
+ Map map = new LinkedHashMap<>(3);
+ map.put(k1, v1);
+ map.put(k2, v2);
+ map.put(k3, v3);
+ return map;
+ }
+
+ /**
+ * Returns a LinkedHashMap containing four mappings.
+ *
+ * @param the key type
+ * @param the value type
+ * @param k1 the first mapping's key
+ * @param v1 the first mapping's value
+ * @param k2 the second mapping's key
+ * @param v2 the second mapping's value
+ * @param k3 the third mapping's key
+ * @param v3 the third mapping's value
+ * @param k4 the fourth mapping's key
+ * @param v4 the fourth mapping's value
+ * @return a LinkedHashMap containing the specified mappings
+ */
+ public static Map of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4) {
+ Map map = new LinkedHashMap<>(4);
+ map.put(k1, v1);
+ map.put(k2, v2);
+ map.put(k3, v3);
+ map.put(k4, v4);
+ return map;
+ }
+
+ /**
+ * Returns a LinkedHashMap containing five mappings.
+ *
+ * @param the key type
+ * @param the value type
+ * @param k1 the first mapping's key
+ * @param v1 the first mapping's value
+ * @param k2 the second mapping's key
+ * @param v2 the second mapping's value
+ * @param k3 the third mapping's key
+ * @param v3 the third mapping's value
+ * @param k4 the fourth mapping's key
+ * @param v4 the fourth mapping's value
+ * @param k5 the fifth mapping's key
+ * @param v5 the fifth mapping's value
+ * @return a LinkedHashMap containing the specified mappings
+ */
+ public static Map of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5) {
+ Map map = new LinkedHashMap<>(5);
+ map.put(k1, v1);
+ map.put(k2, v2);
+ map.put(k3, v3);
+ map.put(k4, v4);
+ map.put(k5, v5);
+ return map;
+ }
+
+ /**
+ * Returns a LinkedHashMap containing six mappings.
+ *
+ * @param the key type
+ * @param the value type
+ * @param k1 the first mapping's key
+ * @param v1 the first mapping's value
+ * @param k2 the second mapping's key
+ * @param v2 the second mapping's value
+ * @param k3 the third mapping's key
+ * @param v3 the third mapping's value
+ * @param k4 the fourth mapping's key
+ * @param v4 the fourth mapping's value
+ * @param k5 the fifth mapping's key
+ * @param v5 the fifth mapping's value
+ * @param k6 the sixth mapping's key
+ * @param v6 the sixth mapping's value
+ * @return a LinkedHashMap containing the specified mappings
+ */
+ public static Map of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5, K k6, V v6) {
+ Map map = new LinkedHashMap<>(6);
+ map.put(k1, v1);
+ map.put(k2, v2);
+ map.put(k3, v3);
+ map.put(k4, v4);
+ map.put(k5, v5);
+ map.put(k6, v6);
+ return map;
+ }
+
+ /**
+ * Returns a LinkedHashMap containing seven mappings.
+ *
+ * @param the key type
+ * @param the value type
+ * @param k1 the first mapping's key
+ * @param v1 the first mapping's value
+ * @param k2 the second mapping's key
+ * @param v2 the second mapping's value
+ * @param k3 the third mapping's key
+ * @param v3 the third mapping's value
+ * @param k4 the fourth mapping's key
+ * @param v4 the fourth mapping's value
+ * @param k5 the fifth mapping's key
+ * @param v5 the fifth mapping's value
+ * @param k6 the sixth mapping's key
+ * @param v6 the sixth mapping's value
+ * @param k7 the seventh mapping's key
+ * @param v7 the seventh mapping's value
+ * @return a LinkedHashMap containing the specified mappings
+ */
+ public static Map of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5, K k6, V v6, K k7, V v7) {
+ Map map = new LinkedHashMap<>(7);
+ map.put(k1, v1);
+ map.put(k2, v2);
+ map.put(k3, v3);
+ map.put(k4, v4);
+ map.put(k5, v5);
+ map.put(k6, v6);
+ map.put(k7, v7);
+ return map;
+ }
+
+ /**
+ * Returns a LinkedHashMap containing eight mappings.
+ *
+ * @param the key type
+ * @param the value type
+ * @param k1 the first mapping's key
+ * @param v1 the first mapping's value
+ * @param k2 the second mapping's key
+ * @param v2 the second mapping's value
+ * @param k3 the third mapping's key
+ * @param v3 the third mapping's value
+ * @param k4 the fourth mapping's key
+ * @param v4 the fourth mapping's value
+ * @param k5 the fifth mapping's key
+ * @param v5 the fifth mapping's value
+ * @param k6 the sixth mapping's key
+ * @param v6 the sixth mapping's value
+ * @param k7 the seventh mapping's key
+ * @param v7 the seventh mapping's value
+ * @param k8 the eighth mapping's key
+ * @param v8 the eighth mapping's value
+ * @return a LinkedHashMap containing the specified mappings
+ */
+ public static Map of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5, K k6, V v6, K k7, V v7, K k8, V v8) {
+ Map map = new LinkedHashMap<>(8);
+ map.put(k1, v1);
+ map.put(k2, v2);
+ map.put(k3, v3);
+ map.put(k4, v4);
+ map.put(k5, v5);
+ map.put(k6, v6);
+ map.put(k7, v7);
+ map.put(k8, v8);
+ return map;
+ }
+
+ /**
+ * Returns a LinkedHashMap containing nine mappings.
+ *
+ * @param the key type
+ * @param the value type
+ * @param k1 the first mapping's key
+ * @param v1 the first mapping's value
+ * @param k2 the second mapping's key
+ * @param v2 the second mapping's value
+ * @param k3 the third mapping's key
+ * @param v3 the third mapping's value
+ * @param k4 the fourth mapping's key
+ * @param v4 the fourth mapping's value
+ * @param k5 the fifth mapping's key
+ * @param v5 the fifth mapping's value
+ * @param k6 the sixth mapping's key
+ * @param v6 the sixth mapping's value
+ * @param k7 the seventh mapping's key
+ * @param v7 the seventh mapping's value
+ * @param k8 the eighth mapping's key
+ * @param v8 the eighth mapping's value
+ * @param k9 the ninth mapping's key
+ * @param v9 the ninth mapping's value
+ * @return a LinkedHashMap containing the specified mappings
+ */
+ public static Map of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5, K k6, V v6, K k7, V v7, K k8, V v8, K k9, V v9) {
+ Map map = new LinkedHashMap<>(9);
+ map.put(k1, v1);
+ map.put(k2, v2);
+ map.put(k3, v3);
+ map.put(k4, v4);
+ map.put(k5, v5);
+ map.put(k6, v6);
+ map.put(k7, v7);
+ map.put(k8, v8);
+ map.put(k9, v9);
+ return map;
+ }
+
+ /**
+ * Returns a LinkedHashMap containing ten mappings.
+ *
+ * @param the key type
+ * @param the value type
+ * @param k1 the first mapping's key
+ * @param v1 the first mapping's value
+ * @param k2 the second mapping's key
+ * @param v2 the second mapping's value
+ * @param k3 the third mapping's key
+ * @param v3 the third mapping's value
+ * @param k4 the fourth mapping's key
+ * @param v4 the fourth mapping's value
+ * @param k5 the fifth mapping's key
+ * @param v5 the fifth mapping's value
+ * @param k6 the sixth mapping's key
+ * @param v6 the sixth mapping's value
+ * @param k7 the seventh mapping's key
+ * @param v7 the seventh mapping's value
+ * @param k8 the eighth mapping's key
+ * @param v8 the eighth mapping's value
+ * @param k9 the ninth mapping's key
+ * @param v9 the ninth mapping's value
+ * @param k10 the tenth mapping's key
+ * @param v10 the tenth mapping's value
+ * @return a LinkedHashMap containing the specified mappings
+ */
+ public static Map of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5, K k6, V v6, K k7, V v7, K k8, V v8, K k9, V v9, K k10, V v10) {
+ Map map = new LinkedHashMap<>(10);
+ map.put(k1, v1);
+ map.put(k2, v2);
+ map.put(k3, v3);
+ map.put(k4, v4);
+ map.put(k5, v5);
+ map.put(k6, v6);
+ map.put(k7, v7);
+ map.put(k8, v8);
+ map.put(k9, v9);
+ map.put(k10, v10);
+ return map;
+ }
+
+ /**
+ * Returns a LinkedHashMap containing mappings derived from the given arguments.
+ *
+ * This method is provided for cases where more than 10 key-value pairs are needed.
+ * It accepts alternating keys and values.
+ *
+ * @param the key type
+ * @param the value type
+ * @param keyValues alternating keys and values
+ * @return a LinkedHashMap containing the specified mappings
+ * @throws IllegalArgumentException if an odd number of arguments is provided
+ */
+ @SuppressWarnings("unchecked")
+ public static Map ofEntries(Object... keyValues) {
+ if (keyValues.length % 2 != 0) {
+ throw new IllegalArgumentException("keyValues must contain an even number of arguments (key-value pairs)");
+ }
+
+ Map map = new LinkedHashMap<>(keyValues.length / 2);
+ for (int i = 0; i < keyValues.length; i += 2) {
+ K key = (K) keyValues[i];
+ V value = (V) keyValues[i + 1];
+ map.put(key, value);
+ }
+ return map;
+ }
+}
\ No newline at end of file
diff --git a/src/test/groovy/graphql/archunit/MapOfUsageTest.groovy b/src/test/groovy/graphql/archunit/MapOfUsageTest.groovy
index 5a77aa3c89..9f6a4471c7 100644
--- a/src/test/groovy/graphql/archunit/MapOfUsageTest.groovy
+++ b/src/test/groovy/graphql/archunit/MapOfUsageTest.groovy
@@ -19,14 +19,14 @@ class MapOfUsageTest extends Specification {
ArchRule mapOfRule = ArchRuleDefinition.noClasses()
.should()
.callMethod(Map.class, "of")
- .because("Map.of() does not guarantee insertion order. Use LinkedHashMap instead for consistent serialization order.")
+ .because("Map.of() does not guarantee insertion order. Use LinkedHashMapFactory.of() instead for consistent serialization order.")
when:
EvaluationResult result = mapOfRule.evaluate(importedClasses)
then:
if (result.hasViolation()) {
- println "Map.of() usage detected. Please use LinkedHashMap instead for consistent serialization order:"
+ println "Map.of() usage detected. Please use LinkedHashMapFactory.of() instead for consistent serialization order:"
result.getFailureReport().getDetails().each { violation ->
println "- ${violation}"
}
From 11d13578c1b47c5d6273afee193858831c825a39 Mon Sep 17 00:00:00 2001
From: dondonz <13839920+dondonz@users.noreply.github.com>
Date: Sat, 16 Aug 2025 08:58:27 +1000
Subject: [PATCH 3/3] Switch existing Map.of usage to new linked hash map
---
.../ExecutableNormalizedOperationToAstCompiler.java | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/src/main/java/graphql/normalized/ExecutableNormalizedOperationToAstCompiler.java b/src/main/java/graphql/normalized/ExecutableNormalizedOperationToAstCompiler.java
index 9509d9554b..f71e1a30e8 100644
--- a/src/main/java/graphql/normalized/ExecutableNormalizedOperationToAstCompiler.java
+++ b/src/main/java/graphql/normalized/ExecutableNormalizedOperationToAstCompiler.java
@@ -24,6 +24,7 @@
import graphql.schema.GraphQLObjectType;
import graphql.schema.GraphQLSchema;
import graphql.schema.GraphQLUnmodifiedType;
+import graphql.util.LinkedHashMapFactory;
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;
@@ -100,7 +101,7 @@ public static CompilerResult compileToDocument(@NonNull GraphQLSchema schema,
@Nullable String operationName,
@NonNull List topLevelFields,
@Nullable VariablePredicate variablePredicate) {
- return compileToDocument(schema, operationKind, operationName, topLevelFields, Map.of(), variablePredicate);
+ return compileToDocument(schema, operationKind, operationName, topLevelFields, LinkedHashMapFactory.of(), variablePredicate);
}
/**
@@ -151,7 +152,7 @@ public static CompilerResult compileToDocumentWithDeferSupport(@NonNull GraphQLS
@NonNull List topLevelFields,
@Nullable VariablePredicate variablePredicate
) {
- return compileToDocumentWithDeferSupport(schema, operationKind, operationName, topLevelFields, Map.of(), variablePredicate);
+ return compileToDocumentWithDeferSupport(schema, operationKind, operationName, topLevelFields, LinkedHashMapFactory.of(), variablePredicate);
}
/**