{"record":{"id":"e19173cb41b0ca80","repo":"OpenAPITools/openapi-generator","slug":"cannot-merge-specs-where-some-declare-an-openapi-v","errorCode":null,"errorMessage":"Cannot merge specs where some declare an OpenAPI version and others do not. The 'openapi' field is required; every source spec must declare a version so compatibility can be verified.","messagePattern":"Cannot merge specs where some declare an OpenAPI version and others do not\\. The 'openapi' field is required; every source spec must declare a version so compatibility can be verified\\.","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"error","filePath":"modules/openapi-generator/src/main/java/org/openapitools/codegen/config/MergedSpecBuilder.java","lineNumber":528,"sourceCode":"     * unversioned. Malformed version strings (non-numeric segments such as {@code 3.x.3}) are\n     * rejected outright.</p>\n     *\n     * @throws RuntimeException if the sources declare different major.minor OpenAPI versions, mix\n     *                          versioned and unversioned specs, or contain a malformed version\n     */\n    private String resolveOutputVersion(List<OpenAPI> specs) {\n        List<String> versions = specs.stream()\n                .map(OpenAPI::getOpenapi)\n                .collect(Collectors.toList());\n\n        if (versions.stream().noneMatch(Objects::nonNull)) {\n            // Every source is unversioned — fall back to the default.\n            return \"3.0.3\";\n        }\n        // At least one source declares a version. Since 'openapi' is required, an unversioned\n        // source cannot be assumed compatible and must not be silently accepted.\n        if (versions.stream().anyMatch(Objects::isNull)) {\n            throw new RuntimeException(\n                    \"Cannot merge specs where some declare an OpenAPI version and others do not. The 'openapi' \"\n                            + \"field is required; every source spec must declare a version so compatibility can be verified.\");\n        }\n        // Reject malformed versions before comparing, so a value like '3.x.3' cannot pass the\n        // major.minor check by having its non-numeric segment normalized to zero.\n        for (String version : versions) {\n            if (!isWellFormedVersion(version)) {\n                throw new RuntimeException(String.format(Locale.ROOT,\n                        \"Malformed OpenAPI version '%s' in a source spec. Expected exactly three numeric \"\n                                + \"components (MAJOR.MINOR.PATCH) without leading zeroes, such as '3.0.3' or '3.1.0'.\",\n                        version));\n            }\n        }\n        String firstMajorMinor = majorMinor(versions.get(0));\n        for (String version : versions) {\n            if (!majorMinor(version).equals(firstMajorMinor)) {\n                throw new RuntimeException(String.format(Locale.ROOT,\n                        \"Cannot merge specs that declare incompatible OpenAPI versions %s. All specs must share \"","sourceCodeStart":510,"sourceCodeEnd":546,"githubUrl":"https://github.com/OpenAPITools/openapi-generator/blob/fcec517be3cf5b7964296bcba25fbc97541484e7/modules/openapi-generator/src/main/java/org/openapitools/codegen/config/MergedSpecBuilder.java#L510-L546","documentation":"MergedSpecBuilder.resolveOutputVersion() collects each source spec's `openapi` field. If at least one spec declares a version and at least one does not, merging is refused: an unversioned source cannot be assumed compatible with a versioned one, and the code refuses to guess. Only when every source is unversioned does it fall back to the default '3.0.3'. This is a deliberate strictness change — the required-ness of `openapi` is enforced at merge time.","triggerScenarios":"Merging pet.yaml (openapi: 3.0.1) with fragments.yaml (no openapi field, e.g. a shared parameters/components file); adding a new component-only file without a version header to an existing versioned catalog.","commonSituations":"Spec catalogs where common fragments were never given `openapi` because individual generators tolerated it; converting multi-file Swagger 2.0-era catalogs; teams introducing new partial specs after a merge pipeline already exists.","solutions":["Add an explicit `openapi: <version>` field to every source spec in the merge set, matching the others' major.minor.","Alternatively remove the field from all sources so the 3.0.3 default applies — only valid if the specs are genuinely 3.0.x-compatible.","Validate with a pre-merge lint rule that every file has an `openapi` key."],"exampleFix":"# before (fragments.yaml)\ncomponents:\n  parameters: ...\n# after\nopenapi: 3.0.3\ncomponents:\n  parameters: ...","handlingStrategy":"validation","validationCode":"// Every spec must declare an openapi version before merge\nfor (String f : specFiles) {\n    JsonNode n = new ObjectMapper(new YAMLFactory()).readTree(new File(f));\n    if (n.get(\"openapi\") == null || n.get(\"openapi\").asText().isBlank()) {\n        throw new IllegalArgumentException(f + \" is missing the required 'openapi' field\");\n    }\n}","typeGuard":null,"tryCatchPattern":"Catch RuntimeException with \"Cannot merge specs where some declare\"; list which inputs lack the openapi field and stop for manual fix.","preventionTips":["Make 'openapi' a required key in your spec lint/CI schema.","Template new fragment files with the version header pre-filled."],"tags":["openapi","spec-merge","version-mismatch","validation"],"backgroundTag":"spec-version-mismatch","analyzedSha":"fcec517be3cf5b7964296bcba25fbc97541484e7","analyzedAt":"2026-08-22T11:13:11.613Z","schemaVersion":2},"datasetVersion":"2026-08-22T14:17:55.899Z"}