draft-07 or 2020-12
Bottom line: 2020-12 is current and adds $defs, prefixItems and unevaluatedProperties, but many tools still speak draft-07 only. Choose by what your whole toolchain supports.

Bottom line: use 2020-12 for new projects, fall back to draft-07 the moment any validator, code generator or OpenAPI tool only supports it, and always pin the draft with $schema.
Short answer: 2020-12 for new work, draft-07 when an old component forces it.
Timeline
| Draft | Year | Notable |
|---|---|---|
| draft-04 | 2013 | id instead of $id; still common in older APIs |
| draft-06 | 2017 | const, propertyNames |
| draft-07 | 2018 | if/then/else, readOnly, contentMediaType; best compatibility |
| 2019-09 | 2019 | $defs replaces definitions, unevaluatedProperties |
| 2020-12 | 2020 | prefixItems, $dynamicRef; current recommendation |
What 2020-12 improves
Array tuples stop being a special case. draft-07 expresses "first element is A, second is B" with items: [{...}, {...}] plus additionalItems. 2020-12 uses prefixItems for position-specific schemas and items for the rest.
$defs is the standard name. Tooling written after 2019 looks there by default.
unevaluatedProperties works with composition. With allOf, additionalProperties: false rejects fields defined by sibling branches. unevaluatedProperties: false only considers properties actually evaluated by the whole schema, which is what composition needs.
When draft-07 is the right answer
| Situation | Reason |
|---|---|
| OpenAPI 3.0 | Its schema object is a draft-04/07 subset |
| Older validators (ajv 6, some Java libraries) | They do not know prefixItems or $dynamicRef |
| Platform mandates a version | Some low-code platforms and gateways pin it |
| Code generators that stop at 07 | Unknown keywords are ignored silently |
Test the last case directly: feed a prefixItems schema to your validator. If it passes without complaint rather than reporting an unknown keyword, it is ignoring the constraint — the most dangerous outcome of all.
Mixing versions
Always declare $schema. Defaults differ: ajv assumes draft-07, several Python libraries assume 2020-12. The same file then validates differently in two places.
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://example.com/schemas/order.json",
"type": "object"
}
Do not $ref across drafts. Behaviour is undefined.
Upgrade in batches. Two drafts in one repository turns "which rules apply here" into tribal knowledge.
FAQ
How do I check whether my validator supports 2020-12?
Feed a schema that uses prefixItems to the validator. An unknown-keyword error means it does not support the draft; passing silently means it is ignoring the constraint, which is worse.
Why should one project use a single draft?
Two drafts in one repository turn "which rules apply here" into tribal knowledge. Upgrade in batches so every file shares the same interpretation.
Should I always declare $schema?
Yes. Defaults differ: ajv assumes draft-07 while several Python libraries assume 2020-12, so the same file can validate differently in two places.