Citrus Version
5.0.0-M1 (targeting 5.0.0 / Matrix release)
Context
With the major 5.0 release underway, this is the right moment to introduce breaking changes that clean up long-standing API confusion.
The current EXCLUSIVE default for custom validators was intentional — it was designed to let users substitute the default validation logic entirely with their own. However, this default causes subtle, hard-to-diagnose issues in practice.
Problem
When a custom validator is provided, it runs exclusively, silently bypassing all other validation. This means any other constraints passed to validate() are simply ignored. Two concrete (pseudo-code'ish) examples:
Example 1 — contradictory assertions on the same field:
javatestRunner.then(
receiveMessage()
.validate(jsonPath().expression("$.id", "foo"))
.validate((message, testContext) -> {
assertThat(message).contains("$.id", "bar");
})
);
The JSON Path assertion ($.id == "foo") is silently skipped. Only the custom validator runs.
Example 2 — custom validator makes all other configuration irrelevant:
testCaseRunner.then(
api.receiveGetAccountDetails(NOT_FOUND)
.validate((message, testContext) -> assertThat(message.getPayload(String.class)).isEmpty())
);
No matter what else is configured on receiveGetAccountDetails(NOT_FOUND), the test passes as long as the custom validator does — giving a false sense of security.
Both cases led to real debugging confusion (see #1422).
Suggested Fix
Change the default custom validator mode fromEXCLUSIVE to COMBINED, so that custom validators run alongside other validation rather than replacing it:
|
public static final CustomValidatorStrategy CUSTOM_VALIDATOR_STRATEGY_DEFAULT = CustomValidatorStrategy.EXCLUSIVE; |
Users who genuinely need the old exclusive behaviour can still opt in explicitly, but the footgun default is gone.
Citrus Version
5.0.0-M1 (targeting 5.0.0 / Matrix release)
Context
With the major 5.0 release underway, this is the right moment to introduce breaking changes that clean up long-standing API confusion.
The current
EXCLUSIVEdefault for custom validators was intentional — it was designed to let users substitute the default validation logic entirely with their own. However, this default causes subtle, hard-to-diagnose issues in practice.Problem
When a custom validator is provided, it runs exclusively, silently bypassing all other validation. This means any other constraints passed to
validate()are simply ignored. Two concrete (pseudo-code'ish) examples:Example 1 — contradictory assertions on the same field:
The JSON Path assertion (
$.id == "foo") is silently skipped. Only the custom validator runs.Example 2 — custom validator makes all other configuration irrelevant:
No matter what else is configured on
receiveGetAccountDetails(NOT_FOUND), the test passes as long as the custom validator does — giving a false sense of security.Both cases led to real debugging confusion (see #1422).
Suggested Fix
Change the default custom validator mode from
EXCLUSIVEtoCOMBINED, so that custom validators run alongside other validation rather than replacing it:citrus/core/citrus-api/src/main/java/org/citrusframework/CitrusSettings.java
Line 275 in cb605ff
Users who genuinely need the old exclusive behaviour can still opt in explicitly, but the footgun default is gone.