Skip to content
Art2link ESB HomeDocumentationBlogContact
Updated Jul 20, 2026
Patterns/Resilience & operational/Flexible validation

Flexible validation

A trading partner starts sending a shape you never agreed to, an extra field appears out of nowhere, and a strict schema turns that surprise into an outage. Flexible validation moves the check off the message type and into a pipeline component, where a harmless deviation is let through and reported rather than rejected, and only a deviation that would corrupt the data still fails.

Partner drift is a fact of B2B integration. You agree an 850 purchase order or an HL7 feed with a partner, it runs for months, and then one day the payload arrives carrying a field nobody arranged. Often it is a trailing element that the standard has always defined but the partner never populated, until now. The transaction that used to process cleanly starts failing on your side, and the cost lands on you: a stalled flow, an emergency investigation, and unplanned development to accommodate a change you did not ask for.

The instinct is to validate harder. That is the wrong lever, because it makes the failure louder, not rarer. Flexible validation takes the opposite stance, drawn from the classic tolerant reader principle: be liberal in what you accept, strict about what you reject, and never accept a change silently. Accept the harmless deviation, keep the business moving, and raise a signal so a human decides what to do about the partner, all at once.

trading partner PO 850 + unplanned field Flexible validation accept & report Order proceeds to fulfilment ✓ Team notified review the change contact partner, decide, adjust without it Strict schema rejects the PO → flow stalls → emergency fix → the order is late and the cost is yours.

This is not partner-only, and it is not inbound-only. Any stage where a message is checked against an expected shape can use the same idea. Partner drift is simply the clearest case, so it leads the rest of this page.

Art2link ESB can validate a message in two different places, and the difference is the whole pattern.

The first is a schema bound to the message type. It is easy to configure and strict by nature: if the payload does not conform, the message is marked errored and never reaches its subscribers. Full stop. Your only downstream control is port exception handling. This is the right tool when any deviation genuinely is an error, and it is exactly what turns partner drift into an outage.

The second is validation performed inside a pipeline component in the receive pipeline. Here the outcome is yours to decide. The component inspects the payload, judges whether the deviation is tolerable, and chooses what happens next: let the message continue, fail it deliberately, and, in the tolerable case, publish a second message that reports the deviation. Flexible validation lives entirely in this second place. The strict schema is not the mechanism, it is the thing you deliberately do not bind when you want this behaviour.

How the component validates is up to you. Two common approaches: compare the instance against a second, looser schema the component loads for the check (bound to nothing, used only as a reference shape), or run a lightweight rule that inspects just the field in question. The first is thorough, the second is cheap and targeted. The pattern does not prescribe one, it only asks that the check happen in the component so you keep control of the outcome.

“Different but it still works” is not a judgement to make by eye. The classification is the heart of the pattern, because in positional and delimited formats such as X12 and HL7 some deviations are harmless and others silently corrupt every field after them. The component’s job is to tell them apart.

DeviationEffect on the agreed fieldsVerdict
Extra trailing element or segment (defined by the standard, previously unused, now populated)None. Existing fields keep their position and meaning; the extra data is simply not parsed.Accept & report
Unknown optional field appended at the endNone, for the same reason.Accept & report
Inserted element in the middle of a positional rowEvery field after it shifts by one, so values land in the wrong place.Fail loud
Reordered fieldsValues reinterpreted against the wrong definitions.Fail loud
Type or length change on an existing fieldA value that no longer means what downstream expects.Fail loud

Concretely, in an X12 850 the difference is visible in one segment. Take a baseline line-item your map reads as quantity, unit, and price:

EDI 850
PO1*1*10*EA*12.50**VN*ABC123~

Safe. A partner starts populating a trailing product-id pair the standard has always defined but never sent. The added *UP*0079123456 sits past the last element your map touches, so it is simply not parsed and nothing you read changes:

EDI 850
PO1*1*10*EA*12.50**VN*ABC123*UP*0079123456~

Unsafe. Now the partner slips a packaging code into the third position. Every element after it shifts one place, so the price your map expects in the fourth position now reads EA and the quantity-to-price mapping is quietly wrong:

EDI 850
PO1*1*10*CA*EA*12.50**VN*ABC123~

That second one parses cleanly, and it must still fail because it parses. HL7 v2 has the same trap when components are reordered: a patient name that was family^given arriving with the two swapped is a different person on paper, with no error to catch. Baseline, then the corrupting reorder in PID-5:

HL7 v2
PID|1||12345^^^HOSP^MR||DOE^JOHN||19800101|M
PID|1||12345^^^HOSP^MR||JOHN^DOE||19800101|M

By contrast, an HL7 partner populating a trailing field that was previously empty, an address in PID-11 the feed never carried before, is the safe, additive case again: the fields ahead of it keep their meaning.

Only the additive, trailing cases are ever accepted. They are provably safe because they cannot move an existing field. Everything that could shift or reinterpret data still fails, and it fails loudly, exactly as a strict schema would. The dangerous cases are listed here not because the pattern handles them leniently, but so the boundary is explicit: accepting an insertion because it happened to parse is precisely the trap this pattern is designed to avoid.

Validate against the agreement, not the standard. A field the standard permits is still a deviation if it is not in what you and the partner agreed. Check the instance against your agreed subset, or the reference shape will call an unarranged change “legal” and you will miss the very drift you set out to catch.

The work lives in a custom pipeline component on the receive pipeline. It compares the incoming instance against the agreed baseline and classifies the deviation. A structural deviation is failed on the spot and carried out to a dead-letter channel for inspection, no different from a strict rejection. A tolerable deviation takes the two-message path: the component lets the original message continue, classified with its normal message type so subscribers pick it up as usual, and it also publishes a second message, a deviation report, onto the bus.

Publishing a companion message from a receive-pipeline component is the same capability the multi-type splitter uses to emit differently-typed messages from one arrival. Here the second message is the out-of-band copy of a wire tap: the primary message flows untouched, and a tap carries a description of what deviated to whoever needs to see it. Keeping the report on the bus, rather than having the component send an email itself, is what keeps this clean. Components classify and publish; they do not deliver. A subscription routes the report onward.

Receive port Flexible validation receive pipeline component classify deviation structural Fail loud → dead-letter tolerable Order (typed) DeviationReport BUS subscriber fulfils the order email notification team reviews One arrival, two messages: the order continues as always, and a report of the deviation is published for a subscription to route.

The report is an ordinary message type, say DeviationReport, carrying enough to act on: the partner, the transaction type, what deviated, the offending fragment, the classification, and whether it was accepted or failed. For the accepted 850 above, its body might read:

JSON
{
  "report": "DeviationReport",
  "detectedAt": "2026-07-20T14:32:05Z",
  "partner": "ACME-FOODS",
  "transaction": { "standard": "EDI 850", "messageType": "PurchaseOrder", "controlNumber": "0001023" },
  "deviation": {
    "classification": "additive-trailing",
    "verdict": "accepted",
    "segment": "PO1",
    "expected": "PO101-PO107",
    "found": "trailing pair at PO108-PO109 (*UP*0079123456)",
    "fragment": "PO1*1*10*EA*12.50**VN*ABC123*UP*0079123456~"
  },
  "action": "Order published as normal. Confirm with partner whether the UPC pair is now standing, and decide whether PO108-PO109 should be mapped."
}

A structural deviation produces the same shape with verdict set to failed and the classification naming the shift, so the notification tells the team both what changed and what the pipeline did about it. From there nothing is special. A subscription picks the report up and a send port delivers it, an email to the integration team, an activity notification, or both:

EXPRESSION
{{Message.MessageType}} == "DeviationReport"
Report once, not on every message. A partner that starts adding a field will send it on every transaction from then on. Do not alert per message or the team learns to ignore the alerts. Report on first occurrence, or on a change of deviation signature, so the drift surfaces once and stays quiet until it changes again.

Flexible validation is opt-in and targeted, not a default on every port. Turn it on where a partner or a flow has a demonstrated history of drift, and leave strict validation everywhere else. Accepting a deviation is, in effect, a change to the trading-partner agreement made by software, so the default should be strict, and tolerance a decision you make with eyes open. The report-and-decide loop is what makes that decision a human one.

It may look as though this softens fail loud, never drop. It does not, and it is worth being precise about why. Nothing is dropped: a tolerable deviation is accepted and made loud through the report message, and an unsafe deviation still fails exactly as before. This is an application of the rule, not an exception to it, silence is the thing the rule forbids, and this pattern never chooses silence.

The deeper reason it is worth naming as a pattern is economic. Tolerant-reader-with-alerting used to be too expensive to build per integration, so teams either hardened every flow with a heavy canonical model or simply ate the failures. When a pipeline component costs minutes to build, you can instrument exactly the partner, transaction, and scenario where the risk is known, and nowhere else. That shifts integration design from defensive-and-generic to observable-and-specific, and it is a big part of what cheap, purpose-built components change about how integrations are architected.

Related. Flexible validation partly fills the role of a classic wire tap, an in-band message with an out-of-band copy for observation. It is the deliberate counterpart to validate at the edge, choosing where and how strictly to check rather than always rejecting, and it hands its hard failures to the dead-letter channel just as poison-message handling does. The two-message mechanism is the same one behind the multi-type splitter, and the “accept many shapes” goal is the lenient cousin of the normalizer.