Validation has a right place and a wrong place. The right place is a trust boundary — the edge where data arrives from somewhere you do not control. The wrong place is everywhere else, and teams that validate defensively at every internal function call end up with slow code, duplicated rules that disagree, and error messages that reference internal structures no caller understands.
Decide where your boundaries are, validate hard there, and trust your own data inside them.
What changed in 2026
- Schema and type generation matured. Tooling to derive validation schemas from static types, or types from schemas, became reliable enough that maintaining both by hand became clearly unnecessary.
- Structured output raised the stakes. With models emitting schema-conformant JSON, as covered in constrained decoding explained, schema quality started affecting generation quality rather than only validation.
- Error message quality got attention. Default validator output being unhelpful to API consumers pushed teams to build translation layers.
- Contract testing spread. Verifying that producers and consumers agree on schemas, continuously, moved from advanced practice into ordinary pipelines.
Where boundaries actually are
| Boundary |
Validate? |
Why |
| Incoming API request |
Always |
Untrusted input by definition |
| Response from an external service |
Yes |
Their contract can change without warning |
| Message consumed from a queue |
Yes |
Producer may be a different team or version |
| Configuration file at startup |
Yes |
Fail loudly at boot, not at 3am |
| Data read from your own database |
Usually not |
You wrote it; a migration is the real fix |
| Between internal functions |
No |
Use types; runtime checks here are noise |
| Outbound API response |
Yes, in tests or staging |
Catches contract breaks before consumers do |
The external response row is the one teams skip most often and regret. Trusting that a third-party API returns what its documentation says produces failures deep inside your logic when they change a field type. Validating at that boundary converts a mysterious downstream error into a clear message naming the contract violation.
Keeping schemas and types in sync
Maintaining a validation schema alongside a separate static type definition guarantees they will diverge. Someone adds a field to one and forgets the other, and now the type system says one thing and the runtime says another.
Pick one direction. Either derive the schema from your types at build time, or generate types from a schema that is the source of truth. Which direction depends on whether the schema is a published contract other teams consume — if it is, the schema should lead. If it is internal, types leading is usually more ergonomic.
Then make error messages usable. Default validator output tends to describe failures in terms of schema paths and constraint names, which is meaningful to you and opaque to a caller. Map failures to a response naming the field, what was wrong, and what was expected — and return all failures at once rather than the first, so a caller can fix everything in one pass. The wider conventions for this are in API error design.
Common mistakes
- Validating everywhere. Slow, redundant, and produces conflicting rules.
- Maintaining schema and types separately. They will drift.
- Returning raw validator output to callers. Technically accurate, practically unusable.
- Stopping at the first error. Callers iterate one fix at a time instead of correcting everything.
- Not validating external responses. Third-party contract changes become mysterious internal failures.
- Overly strict schemas on inbound data. Rejecting unknown fields breaks forward compatibility when a client adds something harmless.
FAQ
Should I reject unknown fields?
Usually not on inbound requests — allowing them keeps clients forward-compatible. Do reject them in configuration, where an unknown key is almost always a typo.
Does validation slow things down?
Meaningfully only at very high throughput with large payloads. Validating once at the boundary rather than repeatedly is the optimization that matters.
What about validating deeply nested structures?
Validate the shape you actually depend on. Exhaustively specifying every nested field of a large third-party response creates a brittle schema that breaks on harmless upstream changes.
Can I use the same schema for documentation?
Yes, and you should. A schema that generates both validation and API documentation cannot drift from itself.
Where to go next
For error response conventions, read API error design. For configuration validation, config as code explained, and for model output, constrained decoding explained.