RequestPattern hashcode could produce inconsistent results
Proposal
This issue was discovered when investigating scenarios and how the RequestPattern is used to determine which stubs should be part of what scenario. It seems that the hashcode of a given RequestPattern changes depending on whether the code is run in intellij using a normal 'Run' mode or whether 'debug' mode is used.
An initial investigation led to the matcher field in the hashcode() implementation. That field is initialized like this:
this.matcher = new RequestMatcher() {
@Override
public MatchResult match(Request request) { ... }
@Override
public String getName() { return "request-matcher"; }
};
This is an anonymous inner class. In Java, each anonymous inner class has a unique synthetic class name such as:
RequestPattern$1
RequestPattern$2
and each instance has its own identity-based hashCode() (inherited from Object), which depends on the object’s memory address.
That means:
The field matcher’s hashCode() (and therefore RequestPattern.hashCode()) includes the identity hash code of that anonymous object.
The identity hash code is not stable across JVMs or even across different class-loading orders.
When you run in debug mode, the debugger may load classes in a different order, which changes the class index and sometimes the JVM’s object identity hash seed.
Thus, even though all the “meaningful” fields (method, url, headers, etc.) are identical, the anonymous inner matcher object makes the computed hash unpredictable.
When you run in the same mode repeatedly, the JVM class-loading sequence and JIT decisions are the same, so the identity hash seed is deterministic → stable hash.
When you switch to debug mode, the debugger’s agent instrumentation loads classes differently, and sometimes object allocation order changes — resulting in a different identity hash code for matcher.
Reproduction steps
A simple test such as:
@Test
void requestPatternHashcode() {
RequestPattern requestPattern = RequestPattern.everything();
System.out.println(requestPattern.hashCode());
}
Run in intelliJ 'Run' mode and the output is 1431581724
Run in intelliJ 'Debug' mode and the output is -2125087406
Create a new method on RequestPattern without the matcher field:
public int stableHashCode() {
return Objects.hash(
scheme,
host,
port,
clientIp,
url,
method,
headers,
pathParams,
queryParams,
formParams,
cookies,
basicAuthCredentials,
bodyPatterns,
multipartPatterns,
customMatcherDefinition,
hasInlineCustomMatcher);
}
and update the test to use this new method:
@Test
void requestPatternHashcode() {
RequestPattern requestPattern = RequestPattern.everything();
System.out.println(requestPattern.stableHashCode());
}
Run in intelliJ 'Run' mode and the output is 312656061
Run in intelliJ 'Debug' mode and the output is 312656061
References
https://github.com/wiremock/wiremock/blob/master/wiremock-common/src/main/java/com/github/tomakehurst/wiremock/matching/RequestPattern.java#L100
Hi @leeturner I'd like to work on this! I'll implement the stableHashCode() method excluding
the matcher field as described. Can I take this issue?
Hi @baezzys many thanks for wanting to take a look. I am not 100% sure we just want to implement the stableHashCode() method. That was just added as an example. We might want to look at the implementation of the anonymous matcher field and figure out how to change it so the hashcode used is consistent.
Is that still something you would want to take a look at ?
Hi @leeturner, I'd like to dig deeper into this and find the best solution.