Skip to content

Postel's Law: Be Liberal in What You Accept (and When Not To)

Scribelet Team
11 min read

A single missing zero took the integration down. A partner had sent a date, not a wrong date, just one shaped slightly differently than the one your importer was built for: 2026-9-4 instead of 2026-09-04. Your parser did the strict, correct thing and rejected the whole payload, the nightly sync failed, and by morning a customer's dashboard was a day stale and someone was on a call apologizing. The partner's data was fine by any reasonable human reading. Your code was fine by the letter of its own contract. And yet the integration was down, because two systems that agreed on everything that mattered disagreed on a single character, and one of them had been built to treat that disagreement as fatal. The other option had always been there. You could have accepted the date, logged the deviation, and moved on. Whether you should have is a question with a name, and it is older than almost anything else in networking.

That name is Postel's law, and it is one of those ideas that sounds like a throwaway aphorism until you have been burned by both sides of it. Most summaries give you the one-line version, "be conservative in what you send, be liberal in what you accept," and stop there, as if the hard part were remembering the slogan. The hard part is knowing when to obey it, because the same principle that let the early internet survive its own chaos has, in the decades since, been blamed for a long tail of security holes and un-removable bugs. Here is what Postel's law actually says, why "be liberal in what you accept" quietly built the web, why the people who maintain protocols now argue it should be walked back, and a way to decide, for a given interface, whether tolerance is a strength or a slow-acting trap.

What is Postel's law?

Postel's law, also called the robustness principle, states: be conservative in what you send, and liberal in what you accept from others. It comes from Jon Postel, one of the architects of the early internet, who wrote a version of it into the 1980 specification for TCP. The idea has two halves that pull in opposite directions on purpose. When your system produces output, it should follow the specification strictly, emitting only well-formed, standard-conforming data. When your system receives input, it should be forgiving, accepting anything whose meaning is clear even if it bends the letter of the format.

The asymmetry is the whole point. A strict sender and a tolerant receiver, wired together across a network, produce a system that keeps working even when the two ends were built years apart by people who never met and read the same specification slightly differently. Your side does not depend on everyone else being perfect, only on their intent being legible. That is a remarkably robust arrangement, and for a long time it was treated as simply the right way to build anything that had to talk to something it did not control.

A system accepts many messy but legible inputs on the left and emits one strict, spec-conforming output on the right, illustrating Postel's law

The reason the principle is worth more than its slogan is that the two halves are not equally easy to follow, and the tolerant half is where all the trouble lives. Being conservative in what you send is mostly a matter of discipline. Being liberal in what you accept is a series of judgment calls, each of which looks harmless and some of which are not. This is the part worth keeping the reasoning for somewhere durable, because a year later the leniency will still be in your code and the reason for it will not be in anyone's head.

Why "be liberal in what you accept" built the web

To see why the principle earned its reputation, look at the thing it made possible: a web that renders. Open your browser's developer tools on almost any page and you will find HTML that does not validate, tags left unclosed, attributes without quotes, elements nested in ways the spec forbids. A strict parser would refuse all of it and show you a blank screen. Browsers instead do the tolerant thing, inferring what the author meant and rendering a page anyway. The entire experience of the web as a place that mostly works, rather than a minefield of syntax errors, rests on receivers being liberal about what they accept.

The same forgiveness shows up everywhere two systems meet. A form field that accepts a phone number with dashes, spaces, parentheses, or none of them, and normalizes it quietly, is applying Postel's law. An email system that delivers a message despite a header a stricter reader would reject is applying it. An event-driven consumer that ignores an unfamiliar field in a message rather than crashing on it is applying it, and that particular application is what lets a producer add a field without coordinating a synchronized deploy with every consumer downstream. Tolerance at the boundaries is what lets independently built systems evolve without lockstep, which is the same reason simple systems that work grow into complex ones rather than being designed complete: robustness at the interfaces is what buys you the freedom to change one side without breaking the other.

There is a deeper structural reason the principle fits distributed systems so well. The seams between systems tend to fall exactly where the seams between the teams that built them fall, a pattern strong enough to have its own law. Where two teams communicate well, the interface between their systems is clean and current. Where they barely talk, the interface is brittle and out of date. Postel's law is, in part, an insurance policy against that brittleness: a tolerant receiver keeps working across the exact organizational gaps where a strict contract would have quietly drifted out of sync.

The modern criticism: tolerance hides bugs

Here is the turn that the one-line version never prepares you for. The people who maintain internet protocols today largely think Postel's law, applied without limits, was a mistake. The IETF published RFC 9413, "Maintaining Robust Protocols", essentially to argue the point in detail, and its conclusion is close to the opposite of the original slogan. The reasons are worth understanding, because they are the reasons the strict rejection that took down your sync was not, in fact, the wrong behavior.

The first problem is that leniency hides bugs. When a receiver quietly accepts malformed input, the sender never finds out it is producing malformed input. The error that should have surfaced immediately, at the boundary, while the person who introduced it was still looking, instead vanishes into a receiver that patched over it. The deviation becomes invisible, and invisible deviations accumulate. Over years, "liberal in what you accept" means every receiver ends up quietly compensating for a slightly different set of sender bugs, and no two implementations agree on what the format actually is anymore.

The second problem is that this accumulation ossifies. Once one popular receiver tolerates a particular malformation, senders start relying on that tolerance, and now the malformation is load-bearing. You cannot tighten the receiver without breaking the senders that grew to depend on its leniency. The tolerant behavior, added once as a small kindness, becomes permanent, and the specification and the reality drift apart until the real protocol is "whatever the dominant implementation happens to accept." This is how a forgiving parser turns into a maintenance liability that behaves like software getting slower and heavier faster than anyone chose: each individual act of tolerance was cheap, and their sum is a system nobody can safely change.

The third problem is security, and it is the sharpest. Every input your system is liberal about is an input an attacker can be creative about. Ambiguity is attack surface. When two systems in a chain interpret the same malformed input differently, and one is lenient, you get request smuggling, parser differentials, and injection attacks that live precisely in the gap between what one component accepts and what the next one does. "Be liberal in what you accept" and "reject anything you are not certain about" are directly opposed, and the security posture wants the second one.

When to be liberal, and when to be strict

None of this means the robustness principle is dead. It means it is a decision, not a default, and the decision turns on a few questions you can actually answer for a given interface. Being liberal in what you accept is right when the cost of rejecting a legible input is high and the risk of accepting it is low. Being strict is right when the input crosses a trust boundary, when silent acceptance would hide a bug you need to see, or when the format is one you still control and can afford to keep clean.

SituationLean liberal (accept)Lean strict (reject)
Where the input comes fromA trusted partner or an older version of your own systemAn untrusted or public source, anything an attacker can shape
Cost of rejectingHigh: a real user is blocked, an integration goes darkLow: the sender is a system that can be fixed and redeployed
Whether you can fix the senderNo: you do not control it and never willYes: it is your code or a partner who will respond
What ambiguity buys an attackerNothing: the format carries no security meaningA parser differential, an injection, a smuggled request
Whether you want to see the bugNo: the deviation is cosmetic and legibleYes: silent acceptance would bury an error you need surfaced

The pattern under the table is that liberal acceptance is a loan against the future, and the question is whether you can afford the interest. When you accept a malformed input, you are promising to keep accepting it, forever, because someone will come to depend on it. If that promise is cheap, tolerance is a gift. If it is expensive, tolerance is a trap you are setting for a future maintainer. Deciding which it is, before you write the lenient branch, is exactly the kind of consequential-but-reversible-looking call worth running through a real decision framework rather than settling by reflex, because the reflex ("just accept it, be nice") is the one Postel's law trained into a generation of engineers.

A useful middle path, and the one RFC 9413 actually recommends, is to accept liberally but loudly. Take the input if rejecting it would break something real, but log the deviation, alert on it, and treat it as a defect in the sender to be driven to zero, not a permanent feature of your receiver. That way you get the robustness without the silent accumulation. The leniency becomes a temporary, visible bridge instead of a permanent, invisible dependency.

Where the trap actually springs

The failure mode is rarely the first lenient line of code. It is what happens to that line over time. An engineer accepts a slightly-off input for a good, specific reason: a particular partner sends dates without leading zeros, and blocking them was causing incidents. That is a defensible call. The problem is that the call, and the reason behind it, live only in that engineer's memory and maybe a commit message nobody will ever find again. The code says what it does, that it tolerates the deviation, but code cannot record why it does it, and the why is the entire load-bearing part.

Two years later the situation is unrecognizable. The lenient branch is still there. Nobody remembers which partner it was for, whether that partner still exists, or whether the deviation it forgives is still a real thing or a fossil. A new engineer sees the odd tolerance, cannot find a reason for it, and faces the worst version of the choice: leave in code they do not understand, or remove it and risk breaking something invisible. This is the second-order cost that makes generalizing "be liberal" into a house style so dangerous, and it is close kin to the way an ambitious team reasons its way into over-tolerant, over-general systems that try to accept everything and end up understood by no one.

The fix is not to be less tolerant. It is to record the tolerance the way you would record any decision you will have to live with: what deviation is being accepted, who it is for, why rejecting it was worse, and the condition under which it can be removed. That is the same discipline that makes an architecture decision record worth keeping for the big calls, applied to the small, quiet ones that actually rot. A lenient parser with a recorded reason is a maintainable system. A lenient parser whose reasons have decayed out of everyone's memory is a haunted house, and every leniency in it is a door nobody dares open or close. Try Scribelet free and keep the reasoning behind your interfaces where it will still make sense to whoever inherits them.

Reading Postel's law correctly

The most common misreading is to treat the principle as a personality trait, a general instruction to be easygoing at every boundary. It is not. It is a targeted engineering trade-off with a specific cost, and by 2026 the field's considered view is that the cost was underpriced for decades. The second misreading is the opposite overcorrection, to conclude from the criticism that you should reject everything strictly and let real users and real integrations break to preserve the purity of your format. That is how you get a system that is technically correct and practically hostile, the broken integration as a way of life.

The mature reading is that "be liberal in what you accept" is a lever, not a law of nature. Pull it when you cannot fix the sender and rejecting would cause real harm, and when you do, pull it visibly and write down why. Keep it in the box when the input is untrusted, when the sender is yours to fix, or when the ambiguity buys an attacker anything at all. Postel gave the early internet a principle that let incompatible systems survive contact with each other, and that was exactly the right principle for a network being built by strangers in the dark. Knowing when it applies, and when its opposite does, is what separates using it from being used by it.

Getting started

Postel's law is not a rule to follow or reject wholesale. It is a question to ask at every interface: if this input is slightly wrong but legible, what does accepting it cost me later, and can I afford it?

Do three things with it. Sort each boundary by trust and by whether you can fix the other side, and reserve liberal acceptance for the inputs you genuinely cannot control and cannot afford to reject. When you do accept something malformed, accept it loudly: log the deviation, alert on it, and drive it back to zero rather than letting it harden into a permanent dependency. And record the reason behind every deliberate leniency, because the tolerance will outlive the memory of why it was added, and a lenient interface whose reasons are gone is the most expensive kind of code to own. Try Scribelet free and let it hold the why behind the calls your systems make at their edges, so the robustness you chose stays a choice and never curdles into a mystery.

Share this article

We use cookies for analytics to improve your experience. Learn more