quarkusio/quarkus · error · IllegalArgumentException
Object of class ${object.getClass().getName()} has non-deter
Error message
Object of class ${object.getClass().getName()} has non-deterministic hash code, it cannot be passed in a HashSet through the recorder boundary: ${object} What it means
With the reproducibility check enabled, Quarkus validates HashSet parameters recorded across the recorder boundary. Objects relying on Object.hashCode() (identity hash) produce iteration order that changes between JVM runs, breaking reproducible builds, so they are rejected. The element class must override hashCode().
Source
Thrown at core/deployment/src/main/java/io/quarkus/deployment/recording/BytecodeRecorderImpl.java:1145
* @param existing The existing object map
* @param expectedType The expected type of the object
* @param relaxedValidation
* @return
*/
private DeferredParameter loadComplexObject(Object param, Map<Object, DeferredParameter> existing,
Class<?> expectedType, boolean relaxedValidation) {
//a list of steps that are performed on the object after it has been created
//we need to create all these first, to ensure the required objects have already
//been deserialized
List<SerializationStep> setupSteps = new ArrayList<>();
List<SerializationStep> ctorSetupSteps = new ArrayList<>();
if (REPRODUCIBILITY_CHECK) {
try {
if (param instanceof Set<?> set && set.getClass() == HashSet.class) {
for (Object object : set) {
if (object.getClass().getMethod("hashCode").getDeclaringClass() == Object.class) {
throw new IllegalArgumentException("Object of class " + object.getClass().getName()
+ " has non-deterministic hash code, it cannot be passed"
+ " in a HashSet through the recorder boundary: " + object);
}
}
} else if (param instanceof Map<?, ?> map && map.getClass() == HashMap.class) {
for (Object object : map.keySet()) {
if (object.getClass().getMethod("hashCode").getDeclaringClass() == Object.class) {
throw new IllegalArgumentException("Object of class " + object.getClass().getName()
+ " has non-deterministic hash code, it cannot be passed"
+ " as a HashMap key through the recorder boundary: " + object);
}
}
}
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
View on GitHub (pinned to e1c734241f)
Solutions
- Override hashCode() (and equals()) on the element class so set ordering is deterministic
- Use a LinkedHashSet or sorted set (TreeSet with a stable comparator) instead of HashSet for recorded sets
- Replace HashSet with a List if order semantics are unimportant but stability is
- Disable the reproducibility check only as a last resort (it exists to keep builds byte-for-byte reproducible)
- Wrap elements in a type with a content-based hashCode before recording
Example fix
// before
class Entry { String name; } // no hashCode -> identity hash
recorder.setEntries(new HashSet<>(entries));
// after
class Entry { String name; @Override public int hashCode() { return name.hashCode(); } }
recorder.setEntries(new HashSet<>(entries)); Defensive patterns
Strategy: type-guard
Validate before calling
static boolean hasDeterministicHash(Collection<?> c) {
for (Object o : c) {
try {
if (o.getClass().getMethod("hashCode").getDeclaringClass() == Object.class) return false;
} catch (NoSuchMethodException e) { return false; }
}
return true;
} Type guard
static boolean overridesHashCode(Class<?> c) throws NoSuchMethodException {
return c.getMethod("hashCode").getDeclaringClass() != Object.class;
} Try / catch
try {
recorder.setEntries(set);
} catch (IllegalArgumentException e) {
if (e.getMessage().contains("non-deterministic hash code")) {
recorder.setEntries(new LinkedHashSet<>(set)); // stable iteration order
} else { throw e; }
} Prevention
- Always override hashCode/equals on classes recorded inside HashSets
- Prefer LinkedHashSet or TreeSet for recorded collections
- Enable/keep the reproducibility check in CI to catch these early
When it happens
Trigger: Passing a plain HashSet through a recorder method where at least one element does not override hashCode() (inherits it from java.lang.Object), while REPRODUCIBILITY_CHECK is on (quarkus.test.record-scan-results / reproducible-build settings).
Common situations: Recording sets of internally-defined classes lacking hashCode/equals overrides; storing config-derived sets of arbitrary classes; build reproducibility verification failing in CI after adding a new recorded set.
Related errors
- Object of class ${object.getClass().getName()} has non-deter
- %s is marked @Record but does not inject an @Recorder object
- Unknown recorder constructor parameter: %s in recorder %s
- Failed to record call to method ${call.method}
- All parameters have already been loaded, it is too late to c
AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05).
Data as JSON: /api/errors/2798e8bac15af763.
Report an issue: GitHub.