diff --git a/docs/customization.md b/docs/customization.md
index b180d783c2bd58eec2468cf320b09553b14fb98e..22c34373eacbd91ff45ed80445f205ebd33c7bef 100644
--- a/docs/customization.md
+++ b/docs/customization.md
@@ -451,3 +451,16 @@ Another useful option is `inlineSchemaNameDefaults`, which allows you to customi
 ```
 
 Note: Only arrayItemSuffix, mapItemSuffix are supported at the moment. `SKIP_SCHEMA_REUSE=true` is a special value to skip reusing inline schemas.
+
+## OpenAPI Normalizer
+
+OpenAPI Normalizer (off by default) transforms the input OpenAPI doc/spec (which may not perfectly conform to the specification) to make it workable with OpenAPI Generator. Here is a list of rules supported:
+
+- `REF_AS_PARENT_IN_ALLOF`: when set to `true`, child schemas in `allOf` is considered a parent if it's a `$ref` (instead of inline schema)
+
+
+Example:
+```
+java -jar modules/openapi-generator-cli/target/openapi-generator-cli.jar generate -g java -i modules/openapi-generator/src/test/resources/3_0/allOf_extension_parent.yaml -o /tmp/java-okhttp/ --additional-properties hideGenerationTimestamp="true" --openapi-normalizer REF_AS_PARENT_IN_ALLOF=true
+```
+
diff --git a/modules/openapi-generator-cli/src/main/java/org/openapitools/codegen/cmd/ConfigHelp.java b/modules/openapi-generator-cli/src/main/java/org/openapitools/codegen/cmd/ConfigHelp.java
index d206a6749f94b6722ebced6150827e834c4142e3..25c1d3cd61c4ec3864dee20b3a67cba8e44af7de 100644
--- a/modules/openapi-generator-cli/src/main/java/org/openapitools/codegen/cmd/ConfigHelp.java
+++ b/modules/openapi-generator-cli/src/main/java/org/openapitools/codegen/cmd/ConfigHelp.java
@@ -80,6 +80,9 @@ public class ConfigHelp extends OpenApiGeneratorCommand {
     @Option(name = {"--inline-schema-name-defaults"}, title = "inline schema name defaults", description = "default values used when naming inline schema name")
     private Boolean inlineSchemaNameDefaults;
 
+    @Option(name = {"--openapi-normalizer"}, title = "openapi normalizer rules", description = "displays the OpenAPI normalizer rules (none)")
+    private Boolean openapiNormalizer;
+
     @Option(name = {"--metadata"}, title = "metadata", description = "displays the generator metadata like the help txt for the generator and generator type etc")
     private Boolean metadata;
 
@@ -494,6 +497,18 @@ public class ConfigHelp extends OpenApiGeneratorCommand {
             sb.append(newline);
         }
 
+        if (Boolean.TRUE.equals(openapiNormalizer)) {
+            sb.append(newline).append("OPENAPI NORMALIZER RULES").append(newline).append(newline);
+            Map<String, String> map = config.openapiNormalizer()
+                    .entrySet()
+                    .stream()
+                    .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (a, b) -> {
+                        throw new IllegalStateException(String.format(Locale.ROOT, "Duplicated options! %s and %s", a, b));
+                    }, TreeMap::new));
+            writePlainTextFromMap(sb, map, optIndent, optNestedIndent, "OpenAPI normalizer rule", "Set to");
+            sb.append(newline);
+        }
+
         if (Boolean.TRUE.equals(instantiationTypes)) {
             sb.append(newline).append("INSTANTIATION TYPES").append(newline).append(newline);
             Map<String, String> map = config.instantiationTypes()
diff --git a/modules/openapi-generator-cli/src/main/java/org/openapitools/codegen/cmd/Generate.java b/modules/openapi-generator-cli/src/main/java/org/openapitools/codegen/cmd/Generate.java
index ae78b2f9fd94043453b814e5eb417d0f74d85ec0..2d473df2a3eb668e5de8e86ca5e9d9e9c032aa48 100644
--- a/modules/openapi-generator-cli/src/main/java/org/openapitools/codegen/cmd/Generate.java
+++ b/modules/openapi-generator-cli/src/main/java/org/openapitools/codegen/cmd/Generate.java
@@ -180,6 +180,13 @@ public class Generate extends OpenApiGeneratorCommand {
                     + " ONLY arrayItemSuffix, mapItemSuffix are supported at the moment. `SKIP_SCHEMA_REUSE=true` is a special value to skip reusing inline schemas.")
     private List<String> inlineSchemaNameDefaults = new ArrayList<>();
 
+    @Option(
+            name = {"--openapi-normalizer"},
+            title = "OpenAPI normalizer rules",
+            description = "specifies the rules to be enabled in OpenAPI normalizer in the form of RULE_1=true,RULE_2=original."
+                    + " You can also have multiple occurrences of this option.")
+    private List<String> openapiNormalizer = new ArrayList<>();
+
     @Option(
             name = {"--server-variables"},
             title = "server variables",
@@ -447,6 +454,7 @@ public class Generate extends OpenApiGeneratorCommand {
         applySchemaMappingsKvpList(schemaMappings, configurator);
         applyInlineSchemaNameMappingsKvpList(inlineSchemaNameMappings, configurator);
         applyInlineSchemaNameDefaultsKvpList(inlineSchemaNameDefaults, configurator);
+        applyOpenAPINormalizerKvpList(openapiNormalizer, configurator);
         applyTypeMappingsKvpList(typeMappings, configurator);
         applyAdditionalPropertiesKvpList(additionalProperties, configurator);
         applyLanguageSpecificPrimitivesCsvList(languageSpecificPrimitives, configurator);
diff --git a/modules/openapi-generator-core/src/main/java/org/openapitools/codegen/config/GeneratorSettings.java b/modules/openapi-generator-core/src/main/java/org/openapitools/codegen/config/GeneratorSettings.java
index c14a06721e8d4e55237c96d5ad809aa7640fd5fb..207bf477580d73ff2552dcdaeb3e9da54e7ebb36 100644
--- a/modules/openapi-generator-core/src/main/java/org/openapitools/codegen/config/GeneratorSettings.java
+++ b/modules/openapi-generator-core/src/main/java/org/openapitools/codegen/config/GeneratorSettings.java
@@ -53,6 +53,7 @@ public final class GeneratorSettings implements Serializable {
     private final Map<String, String> schemaMappings;
     private final Map<String, String> inlineSchemaNameMappings;
     private final Map<String, String> inlineSchemaNameDefaults;
+    private final Map<String, String> openapiNormalizer;
     private final Set<String> languageSpecificPrimitives;
     private final Map<String, String> reservedWordsMappings;
     private final Map<String, String> serverVariables;
@@ -264,6 +265,15 @@ public final class GeneratorSettings implements Serializable {
         return inlineSchemaNameDefaults;
     }
 
+    /**
+     * Gets OpenAPI normalizer rules
+     *
+     * @return a map of rules
+     */
+    public Map<String, String> getOpenAPINormalizer() {
+        return openapiNormalizer;
+    }
+
     /**
      * Gets language specific primitives. These are in addition to the "base" primitives defined in a generator.
      * <p>
@@ -382,6 +392,7 @@ public final class GeneratorSettings implements Serializable {
         schemaMappings = Collections.unmodifiableMap(builder.schemaMappings);
         inlineSchemaNameMappings = Collections.unmodifiableMap(builder.inlineSchemaNameMappings);
         inlineSchemaNameDefaults = Collections.unmodifiableMap(builder.inlineSchemaNameDefaults);
+        openapiNormalizer = Collections.unmodifiableMap(builder.openapiNormalizer);
         languageSpecificPrimitives = Collections.unmodifiableSet(builder.languageSpecificPrimitives);
         reservedWordsMappings = Collections.unmodifiableMap(builder.reservedWordsMappings);
         serverVariables = Collections.unmodifiableMap(builder.serverVariables);
@@ -455,6 +466,7 @@ public final class GeneratorSettings implements Serializable {
         schemaMappings = Collections.unmodifiableMap(new HashMap<>(0));
         inlineSchemaNameMappings = Collections.unmodifiableMap(new HashMap<>(0));
         inlineSchemaNameDefaults = Collections.unmodifiableMap(new HashMap<>(0));
+        openapiNormalizer = Collections.unmodifiableMap(new HashMap<>(0));
         languageSpecificPrimitives = Collections.unmodifiableSet(new HashSet<>(0));
         reservedWordsMappings = Collections.unmodifiableMap(new HashMap<>(0));
         serverVariables = Collections.unmodifiableMap(new HashMap<>(0));
@@ -515,6 +527,9 @@ public final class GeneratorSettings implements Serializable {
         if (copy.getInlineSchemaNameDefaults() != null) {
             builder.inlineSchemaNameDefaults.putAll(copy.getInlineSchemaNameDefaults());
         }
+        if (copy.getOpenAPINormalizer() != null) {
+            builder.openapiNormalizer.putAll(copy.getOpenAPINormalizer());
+        }
         if (copy.getLanguageSpecificPrimitives() != null) {
             builder.languageSpecificPrimitives.addAll(copy.getLanguageSpecificPrimitives());
         }
@@ -557,6 +572,7 @@ public final class GeneratorSettings implements Serializable {
         private Map<String, String> schemaMappings;
         private Map<String, String> inlineSchemaNameMappings;
         private Map<String, String> inlineSchemaNameDefaults;
+        private Map<String, String> openapiNormalizer;
         private Set<String> languageSpecificPrimitives;
         private Map<String, String> reservedWordsMappings;
         private Map<String, String> serverVariables;
@@ -577,6 +593,7 @@ public final class GeneratorSettings implements Serializable {
             schemaMappings = new HashMap<>();
             inlineSchemaNameMappings = new HashMap<>();
             inlineSchemaNameDefaults = new HashMap<>();
+            openapiNormalizer = new HashMap<>();
             languageSpecificPrimitives = new HashSet<>();
             reservedWordsMappings = new HashMap<>();
             serverVariables = new HashMap<>();
@@ -897,6 +914,32 @@ public final class GeneratorSettings implements Serializable {
             return this;
         }
 
+        /**
+         * Sets the {@code openapiNormalizer} and returns a reference to this Builder so that the methods can be chained together.
+         *
+         * @param openapiNormalizer the {@code openapiNormalizer} to set
+         * @return a reference to this Builder
+         */
+        public Builder withOpenAPINormalizer(Map<String, String> openapiNormalizer) {
+            this.openapiNormalizer = openapiNormalizer;
+            return this;
+        }
+
+        /**
+         * Sets a single {@code openapiNormalizer} and returns a reference to this Builder so that the methods can be chained together.
+         *
+         * @param key   A key for the OpenAPI normalizer rule
+         * @param value The value of the OpenAPI normalizer rule
+         * @return a reference to this Builder
+         */
+        public Builder withOpenAPINormalizer(String key, String value) {
+            if (this.openapiNormalizer == null) {
+                this.openapiNormalizer = new HashMap<>();
+            }
+            this.openapiNormalizer.put(key, value);
+            return this;
+        }
+
         /**
          * Sets the {@code languageSpecificPrimitives} and returns a reference to this Builder so that the methods can be chained together.
          *
@@ -1085,6 +1128,7 @@ public final class GeneratorSettings implements Serializable {
                 Objects.equals(getSchemaMappings(), that.getSchemaMappings()) &&
                 Objects.equals(getInlineSchemaNameMappings(), that.getInlineSchemaNameMappings()) &&
                 Objects.equals(getInlineSchemaNameDefaults(), that.getInlineSchemaNameDefaults()) &&
+                Objects.equals(getOpenAPINormalizer(), that.getOpenAPINormalizer()) &&
                 Objects.equals(getLanguageSpecificPrimitives(), that.getLanguageSpecificPrimitives()) &&
                 Objects.equals(getReservedWordsMappings(), that.getReservedWordsMappings()) &&
                 Objects.equals(getGitHost(), that.getGitHost()) &&
@@ -1116,6 +1160,7 @@ public final class GeneratorSettings implements Serializable {
                 getSchemaMappings(),
                 getInlineSchemaNameMappings(),
                 getInlineSchemaNameDefaults(),
+                getOpenAPINormalizer(),
                 getLanguageSpecificPrimitives(),
                 getReservedWordsMappings(),
                 getGitHost(),
diff --git a/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/OpenApiGeneratorPlugin.kt b/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/OpenApiGeneratorPlugin.kt
index 4975fe2166195ac76032b67cc3bc8d5e8c9bd18b..5dfccba04c944ccce13b3cc655b6a27e4a103e83 100644
--- a/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/OpenApiGeneratorPlugin.kt
+++ b/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/OpenApiGeneratorPlugin.kt
@@ -118,6 +118,7 @@ class OpenApiGeneratorPlugin : Plugin<Project> {
                     schemaMappings.set(generate.schemaMappings)
                     inlineSchemaNameMappings.set(generate.inlineSchemaNameMappings)
                     inlineSchemaNameDefaults.set(generate.inlineSchemaNameDefaults)
+                    openapiNormalizer.set(generate.openapiNormalizer)
                     invokerPackage.set(generate.invokerPackage)
                     groupId.set(generate.groupId)
                     id.set(generate.id)
diff --git a/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/extensions/OpenApiGeneratorGenerateExtension.kt b/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/extensions/OpenApiGeneratorGenerateExtension.kt
index 4d9cff54143fe4a6e67b2c6f204a976355893aa1..21751733b67f87a63930a3bd0dc3d97b64683835 100644
--- a/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/extensions/OpenApiGeneratorGenerateExtension.kt
+++ b/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/extensions/OpenApiGeneratorGenerateExtension.kt
@@ -162,6 +162,11 @@ open class OpenApiGeneratorGenerateExtension(project: Project) {
      */
     val inlineSchemaNameDefaults = project.objects.mapProperty<String, String>()
 
+    /**
+     * Specifies mappings (rules) in OpenAPI normalizer
+     */
+    val openapiNormalizer = project.objects.mapProperty<String, String>()
+
     /**
      * Root package for generated code.
      */
diff --git a/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/tasks/GenerateTask.kt b/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/tasks/GenerateTask.kt
index f3ef513a2f225936fba297cf55b0bfb5f0f244e1..b368945e77ccc85737dabec91f82701a31fe9855 100644
--- a/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/tasks/GenerateTask.kt
+++ b/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/tasks/GenerateTask.kt
@@ -250,6 +250,13 @@ open class GenerateTask : DefaultTask() {
     @Input
     val inlineSchemaNameDefaults = project.objects.mapProperty<String, String>()
 
+    /**
+     * Specifies mappings (rules) in OpenAPI normalizer
+     */
+    @Optional
+    @Input
+    val openapiNormalizer = project.objects.mapProperty<String, String>()
+
     /**
      * Root package for generated code.
      */
@@ -758,6 +765,12 @@ open class GenerateTask : DefaultTask() {
                 }
             }
 
+            if (openapiNormalizer.isPresent) {
+                openapiNormalizer.get().forEach { entry ->
+                    configurator.addOpenAPINormalizer(entry.key, entry.value)
+                }
+            }
+
             if (typeMappings.isPresent) {
                 typeMappings.get().forEach { entry ->
                     configurator.addTypeMapping(entry.key, entry.value)
diff --git a/modules/openapi-generator-maven-plugin/src/main/java/org/openapitools/codegen/plugin/CodeGenMojo.java b/modules/openapi-generator-maven-plugin/src/main/java/org/openapitools/codegen/plugin/CodeGenMojo.java
index f796ca6002a96acb3ea0dc4624a8a4be12a908e0..8eb2073faa67bd16bbee67364afbf883d90e8657 100644
--- a/modules/openapi-generator-maven-plugin/src/main/java/org/openapitools/codegen/plugin/CodeGenMojo.java
+++ b/modules/openapi-generator-maven-plugin/src/main/java/org/openapitools/codegen/plugin/CodeGenMojo.java
@@ -315,6 +315,12 @@ public class CodeGenMojo extends AbstractMojo {
     @Parameter(name = "inlineSchemaNameDefaults", property = "openapi.generator.maven.plugin.inlineSchemaNameDefaults")
     private List<String> inlineSchemaNameDefaults;
 
+    /**
+     * A set of rules for OpenAPI normalizer
+     */
+    @Parameter(name = "openapiNormalizer", property = "openapi.generator.maven.plugin.openapiNormalizer")
+    private List<String> openapiNormalizer;
+
     /**
      * A map of swagger spec types and the generated code types to use for them
      */
@@ -700,6 +706,12 @@ public class CodeGenMojo extends AbstractMojo {
                             configurator);
                 }
 
+                // Retained for backwards-compatibility with configOptions -> openapi-normalizer
+                if (openapiNormalizer == null && configOptions.containsKey("openapi-normalizer")) {
+                    applyOpenAPINormalizerKvp(configOptions.get("openapi-normalizer").toString(),
+                            configurator);
+                }
+
                 // Retained for backwards-compatibility with configOptions -> type-mappings
                 if (typeMappings == null && configOptions.containsKey("type-mappings")) {
                     applyTypeMappingsKvp(configOptions.get("type-mappings").toString(), configurator);
@@ -753,6 +765,11 @@ public class CodeGenMojo extends AbstractMojo {
                 applyInlineSchemaNameDefaultsKvpList(inlineSchemaNameDefaults, configurator);
             }
 
+            // Apply OpenAPI normalizer rules
+            if (openapiNormalizer != null && (configOptions == null || !configOptions.containsKey("openapi-normalizer"))) {
+                applyOpenAPINormalizerKvpList(openapiNormalizer, configurator);
+            }
+
             // Apply Type Mappings
             if (typeMappings != null && (configOptions == null || !configOptions.containsKey("type-mappings"))) {
                 applyTypeMappingsKvpList(typeMappings, configurator);
diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenConfig.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenConfig.java
index 2917c2ee0a9b385796d091feef0e9517ce44b216..9b448b01e669ee7327cc60f7371c3aefd68c019d 100644
--- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenConfig.java
+++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenConfig.java
@@ -147,6 +147,8 @@ public interface CodegenConfig {
 
     Map<String, String> inlineSchemaNameDefault();
 
+    Map<String, String> openapiNormalizer();
+
     Map<String, String> apiTemplateFiles();
 
     Map<String, String> modelTemplateFiles();
@@ -330,4 +332,7 @@ public interface CodegenConfig {
     boolean getUseInlineModelResolver();
 
     boolean getAddSuffixToDuplicateOperationNicknames();
+
+    boolean getUseOpenAPINormalizer();
+
 }
diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java
index 30e1c099c5e36a8bde46fe2c0f056ad4f56b4f04..ae20651a4b69e61c84f510a07103d3547e072b8b 100644
--- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java
+++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java
@@ -167,6 +167,8 @@ public class DefaultCodegen implements CodegenConfig {
     protected Map<String, String> inlineSchemaNameMapping = new HashMap<>();
     // a map to store the inline schema naming conventions
     protected Map<String, String> inlineSchemaNameDefault = new HashMap<>();
+    // a map to store the rules in OpenAPI Normalizer
+    protected Map<String, String> openapiNormalizer = new HashMap<>();
     protected String modelPackage = "", apiPackage = "", fileSuffix;
     protected String modelNamePrefix = "", modelNameSuffix = "";
     protected String apiNamePrefix = "", apiNameSuffix = "Api";
@@ -1122,6 +1124,11 @@ public class DefaultCodegen implements CodegenConfig {
         return inlineSchemaNameDefault;
     }
 
+    @Override
+    public Map<String, String> openapiNormalizer() {
+        return openapiNormalizer;
+    }
+
     @Override
     public String testPackage() {
         return testPackage;
@@ -7924,6 +7931,9 @@ public class DefaultCodegen implements CodegenConfig {
     @Override
     public boolean getUseInlineModelResolver() { return true; }
 
+    @Override
+    public boolean getUseOpenAPINormalizer() { return true; }
+
     /*
     A function to convert yaml or json ingested strings like property names
     And convert special characters like newline, tab, carriage return
diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultGenerator.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultGenerator.java
index 2da1e53f84e2360530a35556d74e1f62cb50c2d2..e13a7809676318000e06cf8f91eb13272c1c6f58 100644
--- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultGenerator.java
+++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultGenerator.java
@@ -255,6 +255,12 @@ public class DefaultGenerator implements Generator {
 
         config.processOpts();
 
+        // normalize the spec
+        if (config.getUseOpenAPINormalizer()) {
+            OpenAPINormalizer openapiNormalizer = new OpenAPINormalizer(openAPI, config.openapiNormalizer());
+            openapiNormalizer.normalize();
+        }
+
         // resolve inline models
         if (config.getUseInlineModelResolver()) {
             InlineModelResolver inlineModelResolver = new InlineModelResolver();
diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/OpenAPINormalizer.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/OpenAPINormalizer.java
new file mode 100644
index 0000000000000000000000000000000000000000..4f1de87ba93a40336e0d63293723d4f0bb81f02c
--- /dev/null
+++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/OpenAPINormalizer.java
@@ -0,0 +1,384 @@
+/*
+ * Copyright 2018 OpenAPI-Generator Contributors (https://openapi-generator.tech)
+ * Copyright 2018 SmartBear Software
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openapitools.codegen;
+
+import io.swagger.v3.oas.models.*;
+import io.swagger.v3.oas.models.callbacks.Callback;
+import io.swagger.v3.oas.models.media.*;
+import io.swagger.v3.oas.models.parameters.Parameter;
+import io.swagger.v3.oas.models.parameters.RequestBody;
+import io.swagger.v3.oas.models.responses.ApiResponse;
+import io.swagger.v3.oas.models.responses.ApiResponses;
+import org.apache.commons.lang3.StringUtils;
+import org.openapitools.codegen.utils.ModelUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+public class OpenAPINormalizer {
+    private OpenAPI openAPI;
+    private Map<String, String> rules = new HashMap<>();
+
+    final Logger LOGGER = LoggerFactory.getLogger(OpenAPINormalizer.class);
+
+    // ============= a list of rules =============
+    // when set to true, all rules are enabled
+    final String ALL = "ALL";
+    boolean enableAll;
+
+    // when set to true, $ref in allOf is treated as parent so that x-parent: true will be added
+    // to the schema in $ref (if x-parent is not present)
+    final String REF_AS_PARENT_IN_ALLOF = "REF_AS_PARENT_IN_ALLOF";
+    boolean enableRefAsParentInAllOf;
+
+    // ============= end of rules =============
+
+    /**
+     * Initializes OpenAPI Normalizer with a set of rules
+     *
+     * @param openAPI OpenAPI
+     * @param rules   a map of rules
+     */
+    public OpenAPINormalizer(OpenAPI openAPI, Map<String, String> rules) {
+        this.openAPI = openAPI;
+        this.rules = rules;
+        parseRules(rules);
+    }
+
+    /**
+     * Parses the rules.
+     *
+     * @param rules a map of rules
+     */
+    public void parseRules(Map<String, String> rules) {
+        if (rules == null) {
+            return;
+        }
+
+        if ("true".equalsIgnoreCase(rules.get(ALL))) {
+            enableAll = true;
+        }
+
+        if (enableAll || "true".equalsIgnoreCase(rules.get(REF_AS_PARENT_IN_ALLOF))) {
+            enableRefAsParentInAllOf = true;
+        }
+    }
+
+    /**
+     * Normalizes the OpenAPI input, which may not perfectly conform to
+     * the specification.
+     */
+    void normalize() {
+        if (rules == null || rules.isEmpty()) {
+            return;
+        }
+
+        if (this.openAPI.getComponents() == null) {
+            this.openAPI.setComponents(new Components());
+        }
+
+        if (this.openAPI.getComponents().getSchemas() == null) {
+            this.openAPI.getComponents().setSchemas(new HashMap<String, Schema>());
+        }
+
+        normalizePaths();
+        normalizeComponents();
+    }
+
+    /**
+     * Normalizes inline models in Paths
+     */
+    private void normalizePaths() {
+        Paths paths = openAPI.getPaths();
+        if (paths == null) {
+            return;
+        }
+
+        for (Map.Entry<String, PathItem> pathsEntry : paths.entrySet()) {
+            PathItem path = pathsEntry.getValue();
+            List<Operation> operations = new ArrayList<>(path.readOperations());
+
+            // Include callback operation as well
+            for (Operation operation : path.readOperations()) {
+                Map<String, Callback> callbacks = operation.getCallbacks();
+                if (callbacks != null) {
+                    operations.addAll(callbacks.values().stream()
+                            .flatMap(callback -> callback.values().stream())
+                            .flatMap(pathItem -> pathItem.readOperations().stream())
+                            .collect(Collectors.toList()));
+                }
+            }
+
+            for (Operation operation : operations) {
+                normalizeRequestBody(operation);
+                normalizeParameters(operation);
+                normalizeResponses(operation);
+            }
+        }
+    }
+
+    /**
+     * Normalizes schemas in content
+     *
+     * @param content target content
+     */
+    private void normalizeContent(Content content) {
+        if (content == null || content.isEmpty()) {
+            return;
+        }
+
+        for (String contentType : content.keySet()) {
+            MediaType mediaType = content.get(contentType);
+            if (mediaType == null) {
+                continue;
+            } else if (mediaType.getSchema() == null) {
+                continue;
+            } else {
+                normalizeSchema(mediaType.getSchema(), new HashSet<>());
+            }
+        }
+    }
+
+    /**
+     * Normalizes schemas in RequestBody
+     *
+     * @param operation target operation
+     */
+    private void normalizeRequestBody(Operation operation) {
+        RequestBody requestBody = operation.getRequestBody();
+        if (requestBody == null) {
+            return;
+        }
+
+        // unalias $ref
+        if (requestBody.get$ref() != null) {
+            String ref = ModelUtils.getSimpleRef(requestBody.get$ref());
+            requestBody = openAPI.getComponents().getRequestBodies().get(ref);
+
+            if (requestBody == null) {
+                return;
+            }
+        }
+
+        normalizeContent(requestBody.getContent());
+    }
+
+    /**
+     * Normalizes schemas in parameters
+     *
+     * @param operation target operation
+     */
+    private void normalizeParameters(Operation operation) {
+        List<Parameter> parameters = operation.getParameters();
+        if (parameters == null) {
+            return;
+        }
+
+        for (Parameter parameter : parameters) {
+            if (parameter.getSchema() == null) {
+                continue;
+            } else {
+                normalizeSchema(parameter.getSchema(), new HashSet<>());
+            }
+        }
+    }
+
+    /**
+     * Normalizes schemas in ApiResponses
+     *
+     * @param operation target operation
+     */
+    private void normalizeResponses(Operation operation) {
+        ApiResponses responses = operation.getResponses();
+        if (responses == null) {
+            return;
+        }
+
+        for (Map.Entry<String, ApiResponse> responsesEntry : responses.entrySet()) {
+            if (responsesEntry.getValue() == null) {
+                continue;
+            } else {
+                normalizeContent(responsesEntry.getValue().getContent());
+            }
+        }
+    }
+
+    /**
+     * Normalizes schemas in components
+     */
+    private void normalizeComponents() {
+        Map<String, Schema> schemas = openAPI.getComponents().getSchemas();
+        if (schemas == null) {
+            return;
+        }
+
+        List<String> schemaNames = new ArrayList<String>(schemas.keySet());
+        for (String schemaName : schemaNames) {
+            Schema schema = schemas.get(schemaName);
+            if (schema == null) {
+                LOGGER.warn("{} not fount found in openapi/components/schemas.", schemaName);
+            } else {
+                normalizeSchema(schema, new HashSet<>());
+            }
+        }
+    }
+
+    /**
+     * Normalizes a schema
+     *
+     * @param schema         Schema
+     * @param visitedSchemas a set of visited schemas
+     */
+    public void normalizeSchema(Schema schema, Set<Schema> visitedSchemas) {
+        if (schema == null) {
+            return;
+        }
+
+        if (StringUtils.isNotEmpty(schema.get$ref())) {
+            // not need to process $ref
+            return;
+        }
+
+        if ((visitedSchemas.contains(schema))) {
+            return; // skip due to circular reference
+        } else {
+            visitedSchemas.add(schema);
+        }
+
+        if (schema instanceof ArraySchema) {
+            normalizeSchema(schema.getItems(), visitedSchemas);
+        } else if (schema.getAdditionalProperties() instanceof Schema) { // map
+            normalizeSchema((Schema) schema.getAdditionalProperties(), visitedSchemas);
+        } else if (ModelUtils.isComposedSchema(schema)) {
+            ComposedSchema m = (ComposedSchema) schema;
+            if (m.getAllOf() != null && !m.getAllOf().isEmpty()) {
+                normalizeAllOf(m, visitedSchemas);
+            }
+
+            if (m.getOneOf() != null && !m.getOneOf().isEmpty()) {
+                normalizeOneOf(m, visitedSchemas);
+            }
+
+            if (m.getAnyOf() != null && !m.getAnyOf().isEmpty()) {
+                normalizeAnyOf(m, visitedSchemas);
+            }
+
+            if (m.getProperties() != null && !m.getProperties().isEmpty()) {
+                normalizeProperties(m.getProperties(), visitedSchemas);
+            }
+
+            if (m.getAdditionalProperties() != null) {
+                // normalizeAdditionalProperties(m);
+            }
+        } else if (schema.getNot() != null) {// not schema
+            normalizeSchema(schema.getNot(), visitedSchemas);
+        } else if (schema.getProperties() != null && !schema.getProperties().isEmpty()) {
+            normalizeProperties(schema.getProperties(), visitedSchemas);
+        } else if (schema instanceof Schema) {
+            normalizeNonComposedSchema(schema, visitedSchemas);
+        } else {
+            throw new RuntimeException("Unknown schema type found in normalizer: " + schema);
+        }
+    }
+
+    private void normalizeNonComposedSchema(Schema schema, Set<Schema> visitedSchemas) {
+        // normalize non-composed schema (e.g. schema with only properties)
+    }
+
+    private void normalizeProperties(Map<String, Schema> properties, Set<Schema> visitedSchemas) {
+        if (properties == null) {
+            return;
+        }
+        for (Map.Entry<String, Schema> propertiesEntry : properties.entrySet()) {
+            Schema property = propertiesEntry.getValue();
+            normalizeSchema(property, visitedSchemas);
+        }
+    }
+
+    private void normalizeAllOf(Schema schema, Set<Schema> visitedSchemas) {
+        for (Object item : schema.getAllOf()) {
+            if (!(item instanceof Schema)) {
+                throw new RuntimeException("Error! allOf schema is not of the type Schema: " + item);
+            }
+            // normalize allOf sub schemas one by one
+            normalizeSchema((Schema) item, visitedSchemas);
+        }
+        // process rules here
+        processUseAllOfRefAsParent(schema);
+    }
+
+    private void normalizeOneOf(Schema schema, Set<Schema> visitedSchemas) {
+        for (Object item : schema.getAllOf()) {
+            if (!(item instanceof Schema)) {
+                throw new RuntimeException("Error! allOf schema is not of the type Schema: " + item);
+            }
+            // normalize oenOf sub schemas one by one
+            normalizeSchema((Schema) item, visitedSchemas);
+        }
+        // process rules here
+    }
+
+    private void normalizeAnyOf(Schema schema, Set<Schema> visitedSchemas) {
+        for (Object item : schema.getAllOf()) {
+            if (!(item instanceof Schema)) {
+                throw new RuntimeException("Error! allOf schema is not of the type Schema: " + item);
+            }
+            // normalize anyOf sub schemas one by one
+            normalizeSchema((Schema) item, visitedSchemas);
+        }
+        // process rules here
+    }
+
+    // ===================== a list of rules =====================
+    // all rules (fuctions) start with the word "process"
+    private void processUseAllOfRefAsParent(Schema schema) {
+        if (!enableRefAsParentInAllOf) {
+            return;
+        }
+
+        for (Object item : schema.getAllOf()) {
+            if (!(item instanceof Schema)) {
+                throw new RuntimeException("Error! allOf schema is not of the type Schema: " + item);
+            }
+            Schema s = (Schema) item;
+
+            if (StringUtils.isNotEmpty(s.get$ref())) {
+                String ref = ModelUtils.getSimpleRef(s.get$ref());
+                // TODO need to check for requestBodies?
+                Schema refSchema = openAPI.getComponents().getSchemas().get(ref);
+                if (refSchema == null) {
+                    throw new RuntimeException("schema cannot be null with ref " + ref);
+                }
+                if (refSchema.getExtensions() == null) {
+                    refSchema.setExtensions(new HashMap<>());
+                }
+
+                if (refSchema.getExtensions().containsKey("x-parent")) {
+                    // doing nothing as x-parent already exists
+                } else {
+                    refSchema.getExtensions().put("x-parent", true);
+                }
+
+                LOGGER.debug("processUseAllOfRefAsParent added `x-parent: true` to {}", refSchema);
+            }
+        }
+    }
+    // ===================== end of rules =====================
+}
diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/config/CodegenConfigurator.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/config/CodegenConfigurator.java
index 3d2d9c3aaacc4f63278e8b5afb59c2dd7270a977..8b984a23bed045716eae9e71d1211d2b3e072a44 100644
--- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/config/CodegenConfigurator.java
+++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/config/CodegenConfigurator.java
@@ -71,6 +71,7 @@ public class CodegenConfigurator {
     private Map<String, String> schemaMappings = new HashMap<>();
     private Map<String, String> inlineSchemaNameMappings = new HashMap<>();
     private Map<String, String> inlineSchemaNameDefaults = new HashMap<>();
+    private Map<String, String> openapiNormalizer = new HashMap<>();
     private Set<String> languageSpecificPrimitives = new HashSet<>();
     private Map<String, String> reservedWordsMappings = new HashMap<>();
     private Map<String, String> serverVariables = new HashMap<>();
@@ -123,6 +124,9 @@ public class CodegenConfigurator {
             if(generatorSettings.getInlineSchemaNameDefaults() != null) {
                 configurator.inlineSchemaNameDefaults.putAll(generatorSettings.getInlineSchemaNameDefaults());
             }
+            if(generatorSettings.getOpenAPINormalizer() != null) {
+                configurator.openapiNormalizer.putAll(generatorSettings.getOpenAPINormalizer());
+            }
             if(generatorSettings.getLanguageSpecificPrimitives() != null) {
                 configurator.languageSpecificPrimitives.addAll(generatorSettings.getLanguageSpecificPrimitives());
             }
@@ -210,6 +214,12 @@ public class CodegenConfigurator {
         return this;
     }
 
+    public CodegenConfigurator addOpenAPINormalizer(String key, String value) {
+        this.openapiNormalizer.put(key, value);
+        generatorSettingsBuilder.withOpenAPINormalizer(key, value);
+        return this;
+    }
+
     public CodegenConfigurator addInstantiationType(String key, String value) {
         this.instantiationTypes.put(key, value);
         generatorSettingsBuilder.withInstantiationType(key, value);
@@ -382,6 +392,12 @@ public class CodegenConfigurator {
         return this;
     }
 
+    public CodegenConfigurator setOpenAPINormalizer(Map<String, String> openapiNormalizer) {
+        this.openapiNormalizer = openapiNormalizer;
+        generatorSettingsBuilder.withOpenAPINormalizer(openapiNormalizer);
+        return this;
+    }
+
     public CodegenConfigurator setInputSpec(String inputSpec) {
         this.inputSpec = inputSpec;
         workflowSettingsBuilder.withInputSpec(inputSpec);
@@ -661,6 +677,7 @@ public class CodegenConfigurator {
         config.schemaMapping().putAll(generatorSettings.getSchemaMappings());
         config.inlineSchemaNameMapping().putAll(generatorSettings.getInlineSchemaNameMappings());
         config.inlineSchemaNameDefault().putAll(generatorSettings.getInlineSchemaNameDefaults());
+        config.openapiNormalizer().putAll(generatorSettings.getOpenAPINormalizer());
         config.languageSpecificPrimitives().addAll(generatorSettings.getLanguageSpecificPrimitives());
         config.reservedWordsMappings().putAll(generatorSettings.getReservedWordsMappings());
         config.additionalProperties().putAll(generatorSettings.getAdditionalProperties());
diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/config/CodegenConfiguratorUtils.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/config/CodegenConfiguratorUtils.java
index fb708d4b2f55bcd02738b2f6327459470ccc0180..5a0b40d3a429a3b0271e7fcc2571d451bef8e0b0 100644
--- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/config/CodegenConfiguratorUtils.java
+++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/config/CodegenConfiguratorUtils.java
@@ -120,6 +120,19 @@ public final class CodegenConfiguratorUtils {
         }
     }
 
+    public static void applyOpenAPINormalizerKvpList(List<String> openapiNormalizer, CodegenConfigurator configurator) {
+        for (String propString : openapiNormalizer) {
+            applyOpenAPINormalizerKvp(propString, configurator);
+        }
+    }
+
+    public static void applyOpenAPINormalizerKvp(String openapiNormalizer, CodegenConfigurator configurator) {
+        final Map<String, String> map = createMapFromKeyValuePairs(openapiNormalizer);
+        for (Map.Entry<String, String> entry : map.entrySet()) {
+            configurator.addOpenAPINormalizer(entry.getKey().trim(), entry.getValue().trim());
+        }
+    }
+
     public static void applyTypeMappingsKvpList(List<String> typeMappings, CodegenConfigurator configurator) {
         for (String propString : typeMappings) {
             applyTypeMappingsKvp(propString, configurator);
diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/utils/ModelUtils.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/utils/ModelUtils.java
index 107045d3958c4b89600f446911e7320758868a29..c1b98fc92202e49309e3b4a62d8d41abcef76a22 100644
--- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/utils/ModelUtils.java
+++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/utils/ModelUtils.java
@@ -308,15 +308,15 @@ public class ModelUtils {
 
     /**
      * Invoke the specified visitor function for every schema that matches mimeType in the OpenAPI document.
-     *
+     * <p>
      * To avoid infinite recursion, referenced schemas are visited only once. When a referenced schema is visited,
      * it is added to visitedSchemas.
      *
-     * @param openAPI the OpenAPI document that contains schema objects.
-     * @param schema the root schema object to be visited.
-     * @param mimeType the mime type. TODO: does not seem to be used in a meaningful way.
+     * @param openAPI        the OpenAPI document that contains schema objects.
+     * @param schema         the root schema object to be visited.
+     * @param mimeType       the mime type. TODO: does not seem to be used in a meaningful way.
      * @param visitedSchemas the list of referenced schemas that have been visited.
-     * @param visitor the visitor function which is invoked for every visited schema.
+     * @param visitor        the visitor function which is invoked for every visited schema.
      */
     private static void visitSchema(OpenAPI openAPI, Schema schema, String mimeType, List<String> visitedSchemas, OpenAPISchemaVisitor visitor) {
         visitor.visit(schema, mimeType);
@@ -425,13 +425,14 @@ public class ModelUtils {
 
     /**
      * Return true if the specified schema is an object with a fixed number of properties.
-     *
+     * <p>
      * A ObjectSchema differs from a MapSchema in the following way:
      * - An ObjectSchema is not extensible, i.e. it has a fixed number of properties.
      * - A MapSchema is an object that can be extended with an arbitrary set of properties.
      *   The payload may include dynamic properties.
-     *
+     * <p>
      * For example, an OpenAPI schema is considered an ObjectSchema in the following scenarios:
+     * <p>
      *
      *   type: object
      *   additionalProperties: false
@@ -479,16 +480,17 @@ public class ModelUtils {
      * Return true if the specified 'schema' is an object that can be extended with additional properties.
      * Additional properties means a Schema should support all explicitly defined properties plus any
      * undeclared properties.
-     *
+     * <p>
      * A MapSchema differs from an ObjectSchema in the following way:
      * - An ObjectSchema is not extensible, i.e. it has a fixed number of properties.
      * - A MapSchema is an object that can be extended with an arbitrary set of properties.
-     *   The payload may include dynamic properties.
-     *
+     * The payload may include dynamic properties.
+     * <p>
      * Note that isMapSchema returns true for a composed schema (allOf, anyOf, oneOf) that also defines
      * additionalproperties.
-     *
+     * <p>
      * For example, an OpenAPI schema is considered a MapSchema in the following scenarios:
+     * <p>
      *
      *   type: object
      *   additionalProperties: true
@@ -772,14 +774,14 @@ public class ModelUtils {
 
     /**
      * Check to see if the schema is a free form object.
-     *
+     * <p>
      * A free form object is an object (i.e. 'type: object' in a OAS document) that:
      * 1) Does not define properties, and
      * 2) Is not a composed schema (no anyOf, oneOf, allOf), and
      * 3) additionalproperties is not defined, or additionalproperties: true, or additionalproperties: {}.
-     *
+     * <p>
      * Examples:
-     *
+     * <p>
      * components:
      *   schemas:
      *     arbitraryObject:
@@ -798,7 +800,7 @@ public class ModelUtils {
      *         The value can be any type except the 'null' value.
      *
      * @param openAPI the object that encapsulates the OAS document.
-     * @param schema potentially containing a '$ref'
+     * @param schema  potentially containing a '$ref'
      * @return true if it's a free-form object
      */
     public static boolean isFreeFormObject(OpenAPI openAPI, Schema schema) {
@@ -1054,10 +1056,10 @@ public class ModelUtils {
 
     /**
      * Return the first Schema from a specified OAS 'content' section.
-     *
+     * <p>
      * For example, given the following OAS, this method returns the schema
      * for the 'application/json' content type because it is listed first in the OAS.
-     *
+     * <p>
      * responses:
      *   '200':
      *     content:
@@ -1099,8 +1101,8 @@ public class ModelUtils {
     /**
      * Has self reference?
      *
-     * @param openAPI OpenAPI spec.
-     * @param schema  Schema
+     * @param openAPI            OpenAPI spec.
+     * @param schema             Schema
      * @param visitedSchemaNames A set of visited schema names
      * @return boolean true if it has at least one self reference
      */
@@ -1257,7 +1259,7 @@ public class ModelUtils {
 
     /**
      * Returns the additionalProperties Schema for the specified input schema.
-     *
+     * <p>
      * The additionalProperties keyword is used to control the handling of additional, undeclared
      * properties, that is, properties whose names are not listed in the properties keyword.
      * The additionalProperties keyword may be either a boolean or an object.
@@ -1267,9 +1269,9 @@ public class ModelUtils {
      * to the boolean value True or setting additionalProperties: {}
      *
      * @param openAPI the object that encapsulates the OAS document.
-     * @param schema the input schema that may or may not have the additionalProperties keyword.
+     * @param schema  the input schema that may or may not have the additionalProperties keyword.
      * @return the Schema of the additionalProperties. The null value is returned if no additional
-     *         properties are allowed.
+     * properties are allowed.
      */
     public static Schema getAdditionalProperties(OpenAPI openAPI, Schema schema) {
         Object addProps = schema.getAdditionalProperties();
@@ -1380,10 +1382,10 @@ public class ModelUtils {
      * that specify a determinator.
      * If there are multiple elements in the composed schema and it is not clear
      * which one should be the parent, return null.
-     *
+     * <p>
      * For example, given the following OAS spec, the parent of 'Dog' is Animal
      * because 'Animal' specifies a discriminator.
-     *
+     * <p>
      * animal:
      *   type: object
      *   discriminator:
@@ -1391,6 +1393,7 @@ public class ModelUtils {
      *   properties:
      *     type: string
      *
+     * <p>
      * dog:
      *   allOf:
      *      - $ref: '#/components/schemas/animal'
@@ -1418,10 +1421,10 @@ public class ModelUtils {
                         LOGGER.error("Failed to obtain schema from {}", parentName);
                         return "UNKNOWN_PARENT_NAME";
                     } else if (hasOrInheritsDiscriminator(s, allSchemas)) {
-                        // discriminator.propertyName is used
+                        // discriminator.propertyName is used or x-parent is used
                         return parentName;
                     } else {
-                        // not a parent since discriminator.propertyName is not set
+                        // not a parent since discriminator.propertyName or x-parent is not set
                         hasAmbiguousParents = true;
                         refedWithoutDiscriminator.add(parentName);
                     }
@@ -1476,7 +1479,7 @@ public class ModelUtils {
                         LOGGER.error("Failed to obtain schema from {}", parentName);
                         names.add("UNKNOWN_PARENT_NAME");
                     } else if (hasOrInheritsDiscriminator(s, allSchemas)) {
-                        // discriminator.propertyName is used
+                        // discriminator.propertyName is used or x-parent is used
                         names.add(parentName);
                         if (includeAncestors && s instanceof ComposedSchema) {
                             names.addAll(getAllParentsName((ComposedSchema) s, allSchemas, true));
@@ -1501,7 +1504,8 @@ public class ModelUtils {
     }
 
     private static boolean hasOrInheritsDiscriminator(Schema schema, Map<String, Schema> allSchemas) {
-        if (schema.getDiscriminator() != null && StringUtils.isNotEmpty(schema.getDiscriminator().getPropertyName())) {
+        if ((schema.getDiscriminator() != null && StringUtils.isNotEmpty(schema.getDiscriminator().getPropertyName()))
+                || (isExtensionParent(schema))) { // x-parent is used
             return true;
         } else if (StringUtils.isNotEmpty(schema.get$ref())) {
             String parentName = getSimpleRef(schema.get$ref());
@@ -1523,18 +1527,43 @@ public class ModelUtils {
         return false;
     }
 
+    /**
+     * If it's a boolean, returns the value of the extension `x-parent`.
+     * If it's string, return true if it's non-empty.
+     * If the return value is `true`, the schema is a parent.
+     *
+     * @param schema    Schema
+     * @return boolean
+     */
+    public static boolean isExtensionParent(Schema schema) {
+        if (schema.getExtensions() == null) {
+            return false;
+        } else {
+            Object xParent = schema.getExtensions().get("x-parent");
+            if (xParent == null) {
+                return false;
+            } else if (xParent instanceof Boolean) {
+                return (Boolean) xParent;
+            } else if (xParent instanceof String) {
+                return StringUtils.isNotEmpty((String) xParent);
+            } else {
+                return false;
+            }
+        }
+    }
+
     /**
      * Return true if the 'nullable' attribute is set to true in the schema, i.e. if the value
      * of the property can be the null value.
-     *
+     * <p>
      * In addition, if the OAS document is 3.1 or above, isNullable returns true if the input
      * schema is a 'oneOf' composed document with at most two children, and one of the children
      * is the 'null' type.
-     *
+     * <p>
      * The caller is responsible for resolving schema references before invoking isNullable.
      * If the input schema is a $ref and the referenced schema has 'nullable: true', this method
      * returns false (because the nullable attribute is defined in the referenced schema).
-     *
+     * <p>
      * The 'nullable' attribute was introduced in OAS 3.0.
      * The 'nullable' attribute is deprecated in OAS 3.1. In a OAS 3.1 document, the preferred way
      * to specify nullable properties is to use the 'null' type.
@@ -1564,11 +1593,11 @@ public class ModelUtils {
     /**
      * Return true if the specified composed schema is 'oneOf', contains one or two elements,
      * and at least one of the elements is the 'null' type.
-     *
+     * <p>
      * The 'null' type is supported in OAS 3.1 and above.
      * In the example below, the 'OptionalOrder' can have the null value because the 'null'
      * type is one of the elements under 'oneOf'.
-     *
+     * <p>
      * OptionalOrder:
      *   oneOf:
      *     - type: 'null'
@@ -1591,13 +1620,13 @@ public class ModelUtils {
 
     /**
      * isNullType returns true if the input schema is the 'null' type.
-     *
+     * <p>
      * The 'null' type is supported in OAS 3.1 and above. It is not supported
      * in OAS 2.0 and OAS 3.0.x.
-     *
+     * <p>
      * For example, the "null" type could be used to specify that a value must
      * either be null or a specified type:
-     *
+     * <p>
      * OptionalOrder:
      *   oneOf:
      *     - type: 'null'
@@ -1617,6 +1646,7 @@ public class ModelUtils {
      * For when a type is not defined on a schema
      * Note: properties, additionalProperties, enums, validations, items, and composed schemas (oneOf/anyOf/allOf)
      * can be defined or omitted on these any type schemas
+     *
      * @param schema the schema that we are checking
      * @return boolean
      */
@@ -1713,7 +1743,7 @@ public class ModelUtils {
 
     private static ObjectMapper getRightMapper(String data) {
         ObjectMapper mapper;
-        if  (data.trim().startsWith("{")) {
+        if (data.trim().startsWith("{")) {
             mapper = JSON_MAPPER;
         } else {
             mapper = YAML_MAPPER;
@@ -1725,11 +1755,9 @@ public class ModelUtils {
      * Parse and return a JsonNode representation of the input OAS document.
      *
      * @param location the URL of the OAS document.
-     * @param auths the list of authorization values to access the remote URL.
-     *
-     * @throws java.lang.Exception if an error occurs while retrieving the OpenAPI document.
-     *
+     * @param auths    the list of authorization values to access the remote URL.
      * @return A JsonNode representation of the input OAS document.
+     * @throws java.lang.Exception if an error occurs while retrieving the OpenAPI document.
      */
     public static JsonNode readWithInfo(String location, List<AuthorizationValue> auths) throws Exception {
         String data;
@@ -1756,14 +1784,13 @@ public class ModelUtils {
     /**
      * Parse the OAS document at the specified location, get the swagger or openapi version
      * as specified in the source document, and return the version.
-     *
+     * <p>
      * For OAS 2.0 documents, return the value of the 'swagger' attribute.
      * For OAS 3.x documents, return the value of the 'openapi' attribute.
      *
-     * @param openAPI the object that encapsulates the OAS document.
+     * @param openAPI  the object that encapsulates the OAS document.
      * @param location the URL of the OAS document.
-     * @param auths the list of authorization values to access the remote URL.
-     *
+     * @param auths    the list of authorization values to access the remote URL.
      * @return the version of the OpenAPI document.
      */
     public static SemVer getOpenApiVersion(OpenAPI openAPI, String location, List<AuthorizationValue> auths) {
diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/DefaultCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/DefaultCodegenTest.java
index 05fa241e40e9a1ed6c371b3c2915ae5791dee930..95dc963329920dee788a27b79c719d04b9214f17 100644
--- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/DefaultCodegenTest.java
+++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/DefaultCodegenTest.java
@@ -56,7 +56,6 @@ import java.util.stream.Collectors;
 
 import static org.testng.Assert.*;
 
-
 public class DefaultCodegenTest {
 
     @Test
@@ -4300,4 +4299,29 @@ public class DefaultCodegenTest {
         Assert.assertFalse(inlineEnumSchemaProperty.isContainer);
         Assert.assertFalse(inlineEnumSchemaProperty.isPrimitiveType);
     }
+
+    @Test
+    public void testOpenAPINormalizer() {
+        OpenAPI openAPI = TestUtils.parseSpec("src/test/resources/3_0/allOf_extension_parent.yaml");
+
+        Schema schema = openAPI.getComponents().getSchemas().get("AnotherPerson");
+        assertNull(schema.getExtensions());
+
+        Schema schema2 = openAPI.getComponents().getSchemas().get("Person");
+        assertEquals(schema2.getExtensions().get("x-parent"), "abstract");
+
+        Map<String, String> options = new HashMap<>();
+        options.put("REF_AS_PARENT_IN_ALLOF", "true");
+        OpenAPINormalizer openAPINormalizer = new OpenAPINormalizer(openAPI, options);
+        openAPINormalizer.normalize();
+
+        Schema schema3 = openAPI.getComponents().getSchemas().get("AnotherPerson");
+        assertEquals(schema3.getExtensions().get("x-parent"), true);
+
+        Schema schema4 = openAPI.getComponents().getSchemas().get("AnotherParent");
+        assertEquals(schema4.getExtensions().get("x-parent"), true);
+
+        Schema schema5 = openAPI.getComponents().getSchemas().get("Person");
+        assertEquals(schema5.getExtensions().get("x-parent"), "abstract");
+    }
 }
diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/JavaClientCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/JavaClientCodegenTest.java
index 9efb392203655960999ae134052e938c859a8a2c..b046fd2a344fb05aa9c419c95e21174bc2d9f560 100644
--- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/JavaClientCodegenTest.java
+++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/JavaClientCodegenTest.java
@@ -1717,4 +1717,39 @@ public class JavaClientCodegenTest {
             "localVarQueryParams.addAll(ApiClient.parameterToPairs(\"multi\", \"values\", queryObject.getValues()));"
         );
     }
+
+    @Test
+    public void testJdkHttpClientWithAndWithoutParentExtension() throws Exception {
+        Map<String, Object> properties = new HashMap<>();
+        properties.put(CodegenConstants.API_PACKAGE, "xyz.abcdef.api");
+        properties.put(CodegenConstants.MODEL_PACKAGE, "xyz.abcdef.model");
+        properties.put(CodegenConstants.INVOKER_PACKAGE, "xyz.abcdef.invoker");
+
+        File output = Files.createTempDirectory("test").toFile();
+        output.deleteOnExit();
+
+        final CodegenConfigurator configurator = new CodegenConfigurator()
+                .setGeneratorName("java")
+                // use default `okhttp-gson`
+                //.setLibrary(JavaClientCodegen.NATIVE)
+                .setAdditionalProperties(properties)
+                .setInputSpec("src/test/resources/3_0/allOf_extension_parent.yaml")
+                .setOutputDir(output.getAbsolutePath().replace("\\", "/"));
+
+        final ClientOptInput clientOptInput = configurator.toClientOptInput();
+        DefaultGenerator generator = new DefaultGenerator();
+        generator.setGeneratorPropertyDefault(CodegenConstants.MODELS, "true");
+        generator.setGeneratorPropertyDefault(CodegenConstants.APIS, "true");
+        List<File> files = generator.opts(clientOptInput).generate();
+
+        Assert.assertEquals(files.size(), 30);
+        validateJavaSourceFiles(files);
+
+        TestUtils.assertFileContains(Paths.get(output + "/src/main/java/xyz/abcdef/model/Child.java"),
+                "public class Child extends Person {");
+        TestUtils.assertFileContains(Paths.get(output + "/src/main/java/xyz/abcdef/model/Adult.java"),
+                "public class Adult extends Person {");
+        TestUtils.assertFileContains(Paths.get(output + "/src/main/java/xyz/abcdef/model/AnotherChild.java"),
+                "public class AnotherChild {");
+    }
 }
diff --git a/modules/openapi-generator/src/test/resources/3_0/allOf_extension_parent.yaml b/modules/openapi-generator/src/test/resources/3_0/allOf_extension_parent.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..8b5e27936044d5661124f5bc286f04485f9e3fef
--- /dev/null
+++ b/modules/openapi-generator/src/test/resources/3_0/allOf_extension_parent.yaml
@@ -0,0 +1,87 @@
+openapi: 3.0.1
+info:
+  version: 1.0.0
+  title: Example
+  license:
+    name: MIT
+servers:
+  - url: http://api.example.xyz/v1
+paths:
+  /person/display/{personId}:
+    get:
+      parameters:
+        - name: personId
+          in: path
+          required: true
+          description: The id of the person to retrieve
+          schema:
+            type: string
+      operationId: list
+      responses:
+        '200':
+          description: OK
+          content:
+            application/json:
+              schema:
+                $ref: "#/components/schemas/Person"
+components:
+  schemas:
+    Person:
+      description: person using x-parent (abstract) to indicate it's a parent class
+      type: object
+      x-parent: "abstract"
+      properties:
+        $_type:
+          type: string
+        lastName:
+          type: string
+        firstName:
+          type: string
+    Adult:
+      description: A representation of an adult
+      allOf:
+      - $ref: '#/components/schemas/Person'
+      - type: object
+        properties:
+          children:
+            type: array
+            items:
+              $ref: "#/components/schemas/Child"
+    Child:
+      description: A representation of a child
+      allOf:
+      - type: object
+        properties:
+          age:
+            type: integer
+            format: int32
+      - $ref: '#/components/schemas/Person'
+    AnotherChild:
+      description: another child class that does NOT extend/inherit AnotherPerson
+      allOf:
+        - type: object
+          properties:
+            age:
+              type: integer
+              format: int32
+        - $ref: '#/components/schemas/AnotherPerson'
+    AnotherPerson:
+      description: person object without x-parent extension
+      type: object
+      allOf:
+        - properties:
+            $_type:
+              type: string
+            lastName:
+              type: string
+            firstName:
+              type: string
+        - $ref: '#/components/schemas/AnotherParent'
+    AnotherParent:
+      description: parent object without x-parent extension
+      type: object
+      properties:
+        isParent:
+          type: boolean
+        mum_or_dad:
+          type: string