Search nomadLab

Structured Outputs in 2026: OpenAI vs Claude vs Gemini

Schema-constrained decoding is real on all three providers now, and the parameter you reach for is different on each. What each one guarantees, and what it still cannot.

Updated

If you are still prompting “respond only in valid JSON” and hoping, you are carrying debt you no longer need to carry. All three major providers constrain generation against a schema now. Not pleading in the system prompt: the decoder itself refuses to emit a token that would break the shape.

The catch is that “structured output” means three different things depending on which API you are holding, and the parameter names moved recently on at least two of them.

Checked against the provider docs on 21 August 2026.

Two different failures, one of which is silent

Prompting for JSON fails in two ways and it is worth separating them, because the fixes are not the same.

The first is a syntax failure. A dangling comma, an unclosed brace, a markdown fence wrapped around the answer even though you asked for none. Annoying, and cheap to catch: a try/catch around JSON.parse, one retry, move on.

The second is worse because nothing throws. The model returns syntactically perfect JSON that is not the shape you asked for. A field you never defined, a string where you wanted a number, an enum value outside your three allowed options. Your downstream code eats it happily until something dereferences a null three functions later.

Schema-constrained decoding exists for the second one. Prompting cannot touch it.

What each layer guarantees: prompting gives you parseable output sometimes, JSON mode always parses but ignores your shape, schema-constrained decoding matches the shape, and only your own validator can check whether the content is right it parses shape matches content is right "please reply in JSON" usually no no JSON mode yes no no schema-constrained yes yes no your validator - - yes The provider can hand you a perfectly shaped invoice with the wrong amount on it.
The last column never becomes the provider's job. Everything left of it can stop being yours.

OpenAI: strict mode, and the parameter moved

Strict schema mode is the production path. Set strict: true alongside a JSON Schema and output is constrained at the token level, the same machinery that guarantees tool-call arguments.

Where it is set depends on which API you are on. On Chat Completions it is response_format with type: "json_schema". On the Responses API the same object lives under text.format. Code written against the older guide will not error usefully when you paste it into the newer API, it will just not be doing what you think.

The schema authoring rules are stricter than people expect on first contact:

Every property in an object must also appear in required. There is no optional key. You model optionality as anyOf against a null type instead.

additionalProperties: false is mandatory on every object, nested ones included. Miss one three levels down and the request is rejected outright rather than quietly ignored, which is the better failure but still a surprise.

Deep nesting and long enum lists are where the friction lives. The accepted schema subset is narrower than full JSON Schema, so a schema that validates fine locally can still come back refused.

Plain json_object mode still exists and guarantees only that the bytes parse. No field guarantees at all. Treat it as legacy; if a 2024 tutorial is why it is in your code, switch.

Claude: the tool-call workaround is retired, and the parameter is not what you remember

This is the section that has moved most, and it is where the stale blog posts are thickest.

For a long time the way to get JSON out of Claude was a pseudo-tool: define your target schema as a tool’s input_schema, force tool_choice to that tool, and read the payload out of the tool call. It worked. It was also borrowing the tool-use pathway for something that had nothing to do with tools.

Native structured output is generally available now, and there are two distinct pieces.

For plain shaping, pass output_config: {format: {...}} on messages.create(). Note the name: the older output_format parameter is deprecated, and it is exactly the sort of thing that gets copied forward from an old file for a year. In the SDKs the friendlier path is client.messages.parse(), which validates the response against your schema for you rather than handing you a string to check.

For agentic work, set strict: true as a top-level field on the tool definition itself. Not on tool_choice, which is where people reach first. The schema needs additionalProperties: false and a required list, and in exchange the arguments to whichever tool the model picks are guaranteed to validate.

The distinction maps cleanly to intent. Use the format path when you want shaped data back. Use strict tools when the model is genuinely deciding what to do next. If a project still runs the tool-call-as-extraction workaround, migrating removes an indirection and the edge cases that came with pretending extraction was a tool call.

One gotcha worth knowing before you design around it: the format path does not combine with citations. Ask for both and you get a 400. Strict tools also refuse to combine with programmatic tool calling and with a forced tool_choice.

Gemini: schema-first, with a subset you should test

Set the response mime type to JSON and pass a schema, and generation is constrained to match. At a glance it is the most schema-native of the three.

Where it trails is coverage. The schema language is an OpenAPI-derived subset, and the docs say plainly that very large or deeply nested schemas may be rejected without naming the threshold. Basic types, enums, format, minimum and maximum, and required all work. Heavy polymorphism and recursive references are where things stop translating.

If your schema is flat to moderately nested with standard types, you will never notice. If you are modeling a recursive tree, test it against Gemini specifically before assuming what worked on the other two carries over.

Schema rules that hold on all three

Flatten before you nest. Constrained decoding gets slower and more failure-prone with depth on every provider. A flat schema with thirty top-level fields is more reliable than the same data as three levels of objects.

Use enums wherever the value space is closed. Five valid categories means an enum with five values, not a string plus a sentence in the prompt hoping the model picks one. This is one of the few places you get an actual guarantee instead of a nudge.

Prefer required fields. OpenAI forces this outright. Even where optionality is allowed, models are more consistent filling a field with an explicit null than deciding whether to include it.

Keep enum lists short. Long taxonomies measurably cost latency and accuracy across implementations. Past a few dozen values, a two-step classification usually beats one giant enum.

Structured output is not tool calling

They overlap in the API and solve different problems.

Structured output shapes a response you already know is coming: extraction, classification, a generated report. Tool calling is for letting the model choose among actions, with the schema constraining the arguments of whatever it picks.

The common mistake is reaching for tool machinery when no decision is being made. No branching, no menu of five actions, no need for a tool. Use the extraction path and skip the indirection.

Validation is still yours

Constrained decoding is a large reliability win and it is not a validator. It guarantees structure: the shape matches, the types match, the enum value is one of the allowed ones. It says nothing about whether the content is true.

A model can hand back a flawless {"amount": 400000, "currency": "USD"} that is simply wrong because it misread the figure in your source document. The shape passed. The invoice is still wrong.

So keep the Zod or Pydantic pass, but repoint it. It is no longer doing shape validation, the provider did that. It is doing business logic: is this amount plausible, is this date inside a sane range, does this category apply to this input at all. Provider-side enforcement removed one entire class of bug, not all of them.

Cost and latency

Early constrained decoding carried a real tax on the first call against a new schema while a grammar got compiled. Compiled-schema caching has matured across all three, and repeat calls against the same schema now sit close to unconstrained latency.

Per-token cost is unchanged. The saving is upstream of that: the retries and repair prompts you no longer send. For high-volume extraction that is the bigger line item.

If you are still regexing JSON out of a chat response

Pick the extraction-native path for your provider rather than the tool workaround, even if the workaround is what your current code does. Write the schema with required fields and enums wherever the value space is closed. Keep nesting shallow. Put a validation pass on top for the things the schema cannot know.

None of that is exotic. It is mostly deleting the repair code you wrote in 2024 and letting the decoder do a job it can now actually do.

Take one extraction endpoint still running on prompt-only JSON and swap it. The interesting number is how many retry-on-parse-failure branches you get to delete afterwards.

Keep reading