allOf composition where two branches redeclare the same property name with different types is spec-valid OpenAPI — no validator flags it. A generator has to pick one type for that field, and it silently picks the last branch's, producing a model whose field type contradicts the base schema it's supposed to extend. Worse: the resulting type doesn't even match what a strict JSON Schema validator will accept at runtime, meaning the generated SDK's own types can describe data the API will reject. Reproduced across a validator, two code generators, and a schema validator on 2026-09-21.
What does this actually look like?
Dog composes a shared Animal schema, but its own branch redefines age as a string instead of the integer Animal declares:
components:
schemas:
Animal:
type: object
properties:
name:
type: string
age:
type: integer
required: [name]
Dog:
allOf:
- $ref: '#/components/schemas/Animal'
- type: object
properties:
breed:
type: string
age:
type: string
required: [breed]
swagger-cli validate v4.0.4 and @redocly/cli lint v2.53.3 both pass this spec — no error, no warning about the conflicting age type across branches. Generating a TypeScript client with OpenAPI Generator v7.25.0 (-g typescript-fetch) produces this real, unedited model:
export interface Dog {
name: string;
age?: string;
breed: string;
}
age is typed string. Generating a Java client (-g java) from the identical spec shows the same thing:
public static final String JSON_PROPERTY_AGE = "age";
private String age;
Both generators silently resolved the conflict the same way: the second allOf branch's declaration of age overwrote the first's. There's no error, no comment, nothing distinguishing this field from any other in the generated output.
Why is this worse than it looks?
Because allOf's actual semantics make that generated type wrong, not just surprising. Per JSON Schema's own definition of allOf, "the given data must be valid against all of the given subschemas" simultaneously — not "the last one wins." Compiling the exact schema above and validating a realistic payload against it with ajv v8.20.0 (a strict JSON Schema validator, the kind an OpenAPI request-validation middleware uses at runtime) proves the point:
const data = { name: 'Rex', breed: 'lab', age: '3' };
validate(data); // false
[
{
"instancePath": "/age",
"schemaPath": "#/allOf/0/properties/age/type",
"keyword": "type",
"message": "must be integer"
}
]
age: '3' — a string, exactly what the generated TypeScript and Java models declare age should be — is rejected by the same schema, because it still has to satisfy Animal's branch (type: integer) at the same time. There is no value that can simultaneously be a string and an integer, so strictly speaking no object with a non-null age can ever validate against this Dog schema at all. The generator's types describe data the schema itself can't accept.
This is a real gap between what allOf means (conjunction — valid against every branch) and what people use it for (something closer to inheritance, where a later branch should be free to override or narrow an earlier one). JSON Schema's own guide is explicit that allOf "cannot be used to 'extend' a schema... in the sense of object-oriented inheritance" — but that's exactly the pattern this spec is written in, and neither swagger-cli nor redocly lint catches the mismatch between intent and actual semantics.
How do you fix it?
Don't redeclare a property across allOf branches with an incompatible type. If a subtype genuinely needs a different representation for an inherited field, give it a different name, or narrow (not contradict) the base type:
# Before — Dog's branch silently overrides Animal's age type; no valid value can satisfy both
Dog:
allOf:
- $ref: '#/components/schemas/Animal'
- type: object
properties:
breed:
type: string
age:
type: string
required: [breed]
# After — no property name collision; both branches stay independently satisfiable
Dog:
allOf:
- $ref: '#/components/schemas/Animal'
- type: object
properties:
breed:
type: string
ageDescription:
type: string
description: Human-readable age, e.g. "about 3 years"
required: [breed]
Regenerating from the fixed spec produces a Dog where every field type is unambiguous, and the same ajv validation run that rejected the broken version now accepts real payloads — because there's no longer a property with two competing type constraints.
Tool reaction table
| Tool (version) | Reaction to age: integer vs age: string across allOf branches |
|---|---|
swagger-cli validate v4.0.4 |
Passes — no conflict detection |
@redocly/cli lint v2.53.3 |
Passes — no conflict detection |
| OpenAPI Generator v7.25.0 (TypeScript) | Silently types age as string (last branch wins) |
| OpenAPI Generator v7.25.0 (Java) | Same silent override, private String age |
| ajv v8.20.0 (strict JSON Schema) | Rejects any payload with a non-integer age |
This is a different failure from additionalProperties: false breaking valid allOf payloads — that post covers a schema silently rejecting valid data; this one covers a schema whose generated type doesn't match what the schema itself will accept. Both come from allOf composition, but they break in opposite directions.
How to catch this before it ships
- Grep every
allOfblock for a property name that appears in more than one branch — that's the exact pattern that silently breaks, whether the types differ (as here) or one branch adds a stricter constraint the other doesn't know about. - Don't trust
swagger-cli validateorredocly lint's default ruleset to catch a cross-branch type conflict — neither does, as reproduced above. - If you're modeling something that reads like inheritance, prefer giving each subtype's added or narrowed fields distinct names over redeclaring a base field, since
allOfis conjunction, not override. - Actually validate a realistic payload against the composed schema with a strict validator (ajv or equivalent) at least once when
allOfbranches change — that's the step that caught the real problem here, not spec validation. - Run the spec through the free in-browser OpenAPI validator before generation — it surfaces the resolved, composed schema so a duplicated property name across branches is visible while you're still editing.
If your spec has grown allOf chains across several schema revisions — a common way this kind of conflict gets introduced without anyone noticing — Sourced's hosted docs and SDK pipeline previews the actual generated TypeScript and Python types before you publish, so a field whose generated type contradicts its own base schema is visible in review. Start a free report.
FAQ
Is redeclaring a property across allOf branches invalid OpenAPI?
No. Both swagger-cli validate and redocly lint's default ruleset accept it with no error, as reproduced above. allOf composition with overlapping property names is spec-valid; the problem only shows up when you generate code from it or validate real data against it.
Why does the generator pick the last branch instead of the first, or refuse to generate at all?
That's implementation-specific merge-order behavior in the generator's model-building logic, not something the OpenAPI or JSON Schema spec mandates — the spec doesn't define a "winning" branch for codegen purposes at all, since it only defines validation (data must satisfy every branch), not merging. Different generators or generator versions could resolve the same conflict differently, which is itself a reason not to rely on the behavior.
Does this only happen with conflicting types, or can it happen with compatible ones too?
It's most dangerous with conflicting types, because the generated code compiles and looks correct while describing impossible data, as shown here. Two branches declaring the same type for a shared property (or one adding a stricter constraint like minLength) merges more predictably in most generators, but is still worth checking with a real validator, since strictness rules can combine in non-obvious ways.
How is this different from the additionalProperties + allOf problem?
That post covers additionalProperties: false in one allOf branch rejecting fields that are legitimately defined in a sibling branch — valid data gets rejected. This post covers two branches disagreeing about one field's type — the generated SDK type doesn't match what the schema will actually accept. Both are allOf-composition problems, but the first is over-strict validation and the second is an incorrect generated type.
Would using oneOf or a discriminator avoid this?
Not for this specific pattern — oneOf and discriminated unions solve "this object is exactly one of several shapes," which is a different modeling problem than "this object extends a base type and adds fields." If you do need discriminated variants, see discriminator mapping errors for the failure modes specific to that keyword.
Is there a linter rule that catches this automatically?
Not in Redocly's default ruleset as tested here. The most reliable check found in this reproduction was compiling the composed schema and validating a real sample payload against it with a strict JSON Schema validator like ajv — that's the step that surfaces the actual contradiction, since spec-level linting only checks document structure, not cross-branch semantic conflicts.