Repository: victools/jsonschema-generator
Pull request #98 (2020-05-09 21:31:56)

Configuration of Property Order
By default, an object's properties are being sorted alphabetically by their names – with fields being listed first followed by the methods (depending on what is being included).

With this change, it is possible to alter the sorting algorithm.

Unfortunately, the possibility to preserve the declaration order requested in GH-92 depends on the specific compiler being used – but at least, this way you can apply any sorting you want, e.g. based on the Jackson `@JsonPropertyOrder` or similar annotations if you so chose.


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4aed93c..92e51e3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file.
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
+## [Unreleased]
+### `jsonschema-generator`
+#### Added
+- New `SchemaGeneratorGeneralConfigPart.withPropertySorter()` exposing the sorting logic of an object schema's properties
+
 ## [4.11.1] - 2020-04-30
 ### `jsonschema-maven-plugin`
 #### Fixed
diff --git a/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/SchemaGeneratorConfig.java b/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/SchemaGeneratorConfig.java
index 90e3ee8..0887ddb 100644
--- a/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/SchemaGeneratorConfig.java
+++ b/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/SchemaGeneratorConfig.java
@@ -132,6 +132,16 @@ public interface SchemaGeneratorConfig {
      */
     ArrayNode createArrayNode();
 
+    /**
+     * Implementation of the {@link java.util.Comparator#compare(Object, Object)} interface method to determine the order of fields and methods in an
+     * object's {@code "properties"}.
+     *
+     * @param first first field/method to compare to {@code second}
+     * @param second second field/method to compare to {@code first}
+     * @return a negative/positive integer as the first field/method should be positioned before/after the second respectively
+     */
+    int sortProperties(MemberScope<?, ?> first, MemberScope<?, ?> second);
+
     /**
      * Look-up the non-standard JSON schema definition for a given property. Falling-back on the per-type custom definitions.
      *
diff --git a/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/SchemaGeneratorGeneralConfigPart.java b/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/SchemaGeneratorGeneralConfigPart.java
index 1842c5e..73dcab0 100644
--- a/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/SchemaGeneratorGeneralConfigPart.java
+++ b/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/SchemaGeneratorGeneralConfigPart.java
@@ -16,11 +16,13 @@
 
 package com.github.victools.jsonschema.generator;
 
+import com.github.victools.jsonschema.generator.impl.PropertySortUtils;
 import java.lang.reflect.Type;
 import java.math.BigDecimal;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.List;
 import java.util.Map;
 
@@ -29,6 +31,8 @@ import java.util.Map;
  */
 public class SchemaGeneratorGeneralConfigPart extends SchemaGeneratorTypeConfigPart<TypeScope> {
 
+    private Comparator<MemberScope<?, ?>> propertySorter = PropertySortUtils.DEFAULT_PROPERTY_ORDER;
+
     private final List<CustomDefinitionProviderV2> customDefinitionProviders = new ArrayList<>();
     private final List<SubtypeResolver> subtypeResolvers = new ArrayList<>();
     private final List<TypeAttributeOverrideV2> typeAttributeOverrides = new ArrayList<>();
@@ -36,6 +40,26 @@ public class SchemaGeneratorGeneralConfigPart extends SchemaGeneratorTypeConfigP
     private final List<ConfigFunction<TypeScope, String>> idResolvers = new ArrayList<>();
     private final List<ConfigFunction<TypeScope, String>> anchorResolvers = new ArrayList<>();
 
+    /**
+     * Replacing the current sorting algorithm of properties (fields and methods).
+     *
+     * @param propertySorter sorting algorithm for an object's properties
+     * @return this builder instance (for chaining)
+     */
+    public SchemaGeneratorGeneralConfigPart withPropertySorter(Comparator<MemberScope<?, ?>> propertySorter) {
+        this.propertySorter = propertySorter;
+        return this;
+    }
+
+    /**
+     * Getter for the sorting algorithm for an object's properties (fields and methods).
+     *
+     * @return applicable {@link Comparator} for an object's properties
+     */
+    public Comparator<MemberScope<?, ?>> getPropertySorter() {
+        return this.propertySorter;
+    }
+
     /**
      * Adding a custom schema provider – if it returns null for a given type, the next definition provider will be applied.
      * <br>
diff --git a/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/impl/PropertySortUtils.java b/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/impl/PropertySortUtils.java
new file mode 100644
index 0000000..374a55d
--- /dev/null
+++ b/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/impl/PropertySortUtils.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2020 VicTools.
+ *
+ * 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
+ *
+ *      http://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 com.github.victools.jsonschema.generator.impl;
+
+import com.github.victools.jsonschema.generator.MemberScope;
+import com.github.victools.jsonschema.generator.MethodScope;
+import java.util.Comparator;
+
+/**
+ * Utility class containing the declaration of the default property sort algorithm and its components.
+ */
+public class PropertySortUtils {
+
+    /**
+     * {@link Comparator} sorting properties: with fields before methods.
+     */
+    public static final Comparator<MemberScope<?, ?>> SORT_PROPERTIES_FIELDS_BEFORE_METHODS
+            = (first, second) -> Boolean.compare(first instanceof MethodScope, second instanceof MethodScope);
+
+    /**
+     * {@link Comparator} sorting properties: alphabetically by their name.
+     *
+     * @see MemberScope#getSchemaPropertyName()
+     */
+    public static final Comparator<MemberScope<?, ?>> SORT_PROPERTIES_BY_NAME_ALPHABETICALLY
+            = (first, second) -> first.getSchemaPropertyName().compareTo(second.getSchemaPropertyName());
+
+    /**
+     * {@link Comparator} sorting properties into the following groups and within each group alphabetically by their name.
+     * <ol>
+     * <li>instance fields</li>
+     * <li>instance methods</li>
+     * <li>static fields</li>
+     * <li>static methods</li>
+     * </ol>
+     *
+     * @see MemberScope#getSchemaPropertyName()
+     */
+    public static final Comparator<MemberScope<?, ?>> DEFAULT_PROPERTY_ORDER
+            = SORT_PROPERTIES_FIELDS_BEFORE_METHODS.thenComparing(SORT_PROPERTIES_BY_NAME_ALPHABETICALLY);
+}
diff --git a/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/impl/SchemaGenerationContextImpl.java b/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/impl/SchemaGenerationContextImpl.java
index c40a3af..2974801 100644
--- a/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/impl/SchemaGenerationContextImpl.java
+++ b/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/impl/SchemaGenerationContextImpl.java
@@ -45,7 +45,6 @@ import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import java.util.TreeMap;
 import java.util.function.Function;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
@@ -156,11 +155,7 @@ public class SchemaGenerationContextImpl implements SchemaGenerationContext {
             CustomDefinitionProviderV2 ignoredDefinitionProvider, boolean isNullable) {
         Map<DefinitionKey, List<ObjectNode>> targetMap = isNullable ? this.nullableReferences : this.references;
         DefinitionKey key = new DefinitionKey(javaType, ignoredDefinitionProvider);
-        List<ObjectNode> valueList = targetMap.get(key);
-        if (valueList == null) {
-            valueList = new ArrayList<>();
-            targetMap.put(key, valueList);
-        }
+        List<ObjectNode> valueList = targetMap.computeIfAbsent(key, k -> new ArrayList<>());
         valueList.add(referencingNode);
         return this;
     }
@@ -387,15 +382,15 @@ public class SchemaGenerationContextImpl implements SchemaGenerationContext {
         }
         if (targetScope instanceof MemberScope<?, ?> && !((MemberScope<?, ?>) targetScope).isFakeContainerItemScope()) {
             MemberScope<?, ?> fakeArrayItemMember = ((MemberScope<?, ?>) targetScope).asFakeContainerItemScope();
-            Map<String, JsonNode> fakeItemDefinition = new HashMap<>();
+            JsonNode fakeItemDefinition;
             if (targetScope instanceof FieldScope) {
-                this.populateField((FieldScope) fakeArrayItemMember, fakeItemDefinition, null);
+                fakeItemDefinition = this.populateFieldSchema((FieldScope) fakeArrayItemMember);
             } else if (targetScope instanceof MethodScope) {
-                this.populateMethod((MethodScope) fakeArrayItemMember, fakeItemDefinition, null);
+                fakeItemDefinition = this.populateMethodSchema((MethodScope) fakeArrayItemMember);
             } else {
                 throw new IllegalStateException("Unsupported member type: " + targetScope.getClass().getName());
             }
-            definition.set(this.getKeyword(SchemaKeyword.TAG_ITEMS), fakeItemDefinition.values().iterator().next());
+            definition.set(this.getKeyword(SchemaKeyword.TAG_ITEMS), fakeItemDefinition);
         } else {
             ObjectNode arrayItemTypeRef = this.generatorConfig.createObjectNode();
             definition.set(this.getKeyword(SchemaKeyword.TAG_ITEMS), arrayItemTypeRef);
@@ -412,21 +407,40 @@ public class SchemaGenerationContextImpl implements SchemaGenerationContext {
     private void generateObjectDefinition(ResolvedType targetType, ObjectNode definition) {
         definition.put(this.getKeyword(SchemaKeyword.TAG_TYPE), this.getKeyword(SchemaKeyword.TAG_TYPE_OBJECT));
 
-        final Map<String, JsonNode> targetFields = new TreeMap<>();
-        final Map<String, JsonNode> targetMethods = new TreeMap<>();
+        /*
+         * Collecting fields and methods according to their order in the Java Byte Code.
+         * Depending on the compiler implementation, that order may or may not match the declaration order in the source code.
+         */
+        final Map<String, MemberScope<?, ?>> targetProperties = new LinkedHashMap<>();
         final Set<String> requiredProperties = new HashSet<>();
 
-        this.collectObjectProperties(targetType, targetFields, targetMethods, requiredProperties);
+        this.collectObjectProperties(targetType, targetProperties, requiredProperties);
 
-        if (!targetFields.isEmpty() || !targetMethods.isEmpty()) {
+        if (!targetProperties.isEmpty()) {
             ObjectNode propertiesNode = this.generatorConfig.createObjectNode();
-            propertiesNode.setAll(targetFields);
-            propertiesNode.setAll(targetMethods);
+            List<MemberScope<?, ?>> sortedProperties = targetProperties.values().stream()
+                    .sorted(this.generatorConfig::sortProperties)
+                    .collect(Collectors.toList());
+            for (MemberScope<?, ?> property : sortedProperties) {
+                JsonNode subSchema;
+                if (property instanceof FieldScope) {
+                    subSchema = this.populateFieldSchema((FieldScope) property);
+                } else if (property instanceof MethodScope) {
+                    subSchema = this.populateMethodSchema((MethodScope) property);
+                } else {
+                    throw new IllegalStateException("Unsupported member scope of type " + property.getClass());
+                }
+                propertiesNode.set(property.getSchemaPropertyName(), subSchema);
+            }
             definition.set(this.getKeyword(SchemaKeyword.TAG_PROPERTIES), propertiesNode);
 
             if (!requiredProperties.isEmpty()) {
                 ArrayNode requiredNode = this.generatorConfig.createArrayNode();
-                requiredProperties.forEach(requiredNode::add);
+                // list required properties in the same order as the property
+                sortedProperties.stream()
+                        .map(MemberScope::getSchemaPropertyName)
+                        .filter(requiredProperties::contains)
+                        .forEach(requiredNode::add);
                 definition.set(this.getKeyword(SchemaKeyword.TAG_REQUIRED), requiredNode);
             }
         }
@@ -436,17 +450,15 @@ public class SchemaGenerationContextImpl implements SchemaGenerationContext {
      * Recursively collect all properties of the given object type and add them to the respective maps.
      *
      * @param targetType the type for which to collect fields and methods
-     * @param targetFields map of named JSON schema nodes representing individual fields
-     * @param targetMethods map of named JSON schema nodes representing individual methods
+     * @param targetProperties map of named fields and methods
      * @param requiredProperties set of properties value required
      */
-    private void collectObjectProperties(ResolvedType targetType, Map<String, JsonNode> targetFields, Map<String, JsonNode> targetMethods,
-            Set<String> requiredProperties) {
+    private void collectObjectProperties(ResolvedType targetType, Map<String, MemberScope<?, ?>> targetProperties, Set<String> requiredProperties) {
         logger.debug("collecting non-static fields and methods from {}", targetType);
         final ResolvedTypeWithMembers targetTypeWithMembers = this.typeContext.resolveWithMembers(targetType);
         // member fields and methods are being collected from the targeted type as well as its super types
-        this.populateFields(targetTypeWithMembers, ResolvedTypeWithMembers::getMemberFields, targetFields, requiredProperties);
-        this.populateMethods(targetTypeWithMembers, ResolvedTypeWithMembers::getMemberMethods, targetMethods, requiredProperties);
+        this.collectFields(targetTypeWithMembers, ResolvedTypeWithMembers::getMemberFields, targetProperties, requiredProperties);
+        this.collectMethods(targetTypeWithMembers, ResolvedTypeWithMembers::getMemberMethods, targetProperties, requiredProperties);
 
         final boolean includeStaticFields = this.generatorConfig.shouldIncludeStaticFields();
         final boolean includeStaticMethods = this.generatorConfig.shouldIncludeStaticMethods();
@@ -468,10 +480,10 @@ public class SchemaGenerationContextImpl implements SchemaGenerationContext {
                     hierarchyTypeMembers = this.typeContext.resolveWithMembers(hierachyType);
                 }
                 if (includeStaticFields) {
-                    this.populateFields(hierarchyTypeMembers, ResolvedTypeWithMembers::getStaticFields, targetFields, requiredProperties);
+                    this.collectFields(hierarchyTypeMembers, ResolvedTypeWithMembers::getStaticFields, targetProperties, requiredProperties);
                 }
                 if (includeStaticMethods) {
-                    this.populateMethods(hierarchyTypeMembers, ResolvedTypeWithMembers::getStaticMethods, targetMethods, requiredProperties);
+                    this.collectMethods(hierarchyTypeMembers, ResolvedTypeWithMembers::getStaticMethods, targetProperties, requiredProperties);
                 }
             }
         }
@@ -482,15 +494,15 @@ public class SchemaGenerationContextImpl implements SchemaGenerationContext {
      *
      * @param declaringTypeMembers the type declaring the fields to populate
      * @param fieldLookup retrieval function for getter targeted fields from {@code declaringTypeMembers}
-     * @param collectedFields property nodes in the JSON schema to which the field sub schemas should be added
+     * @param collectedProperties collected fields and methods for which sub schemas should be added
      * @param requiredProperties set of properties value required
      */
-    private void populateFields(ResolvedTypeWithMembers declaringTypeMembers, Function<ResolvedTypeWithMembers, ResolvedField[]> fieldLookup,
-            Map<String, JsonNode> collectedFields, Set<String> requiredProperties) {
+    private void collectFields(ResolvedTypeWithMembers declaringTypeMembers, Function<ResolvedTypeWithMembers, ResolvedField[]> fieldLookup,
+            Map<String, MemberScope<?, ?>> collectedProperties, Set<String> requiredProperties) {
         Stream.of(fieldLookup.apply(declaringTypeMembers))
                 .map(declaredField -> this.typeContext.createFieldScope(declaredField, declaringTypeMembers))
                 .filter(fieldScope -> !this.generatorConfig.shouldIgnore(fieldScope))
-                .forEach(fieldScope -> this.populateField(fieldScope, collectedFields, requiredProperties));
+                .forEach(fieldScope -> this.collectField(fieldScope, collectedProperties, requiredProperties));
     }
 
     /**
@@ -498,25 +510,25 @@ public class SchemaGenerationContextImpl implements SchemaGenerationContext {
      *
      * @param declaringTypeMembers the type declaring the methods to populate
      * @param methodLookup retrieval function for getter targeted methods from {@code declaringTypeMembers}
-     * @param collectedMethods property nodes in the JSON schema to which the method sub schemas should be added
+     * @param collectedProperties collected fields and methods for which sub schemas should be added
      * @param requiredProperties set of properties value required
      */
-    private void populateMethods(ResolvedTypeWithMembers declaringTypeMembers, Function<ResolvedTypeWithMembers, ResolvedMethod[]> methodLookup,
-            Map<String, JsonNode> collectedMethods, Set<String> requiredProperties) {
+    private void collectMethods(ResolvedTypeWithMembers declaringTypeMembers, Function<ResolvedTypeWithMembers, ResolvedMethod[]> methodLookup,
+            Map<String, MemberScope<?, ?>> collectedProperties, Set<String> requiredProperties) {
         Stream.of(methodLookup.apply(declaringTypeMembers))
                 .map(declaredMethod -> this.typeContext.createMethodScope(declaredMethod, declaringTypeMembers))
                 .filter(methodScope -> !this.generatorConfig.shouldIgnore(methodScope))
-                .forEach(methodScope -> this.populateMethod(methodScope, collectedMethods, requiredProperties));
+                .forEach(methodScope -> this.collectMethod(methodScope, collectedProperties, requiredProperties));
     }
 
     /**
      * Preparation Step: add the given field to the specified {@link Map}.
      *
-     * @param field declared field that should be added to the specified node
-     * @param collectedFields node in the JSON schema to which the field's sub schema should be added as property
-     * @param requiredProperties set of properties value required
+     * @param field declared field that should be added
+     * @param collectedProperties collection of fields/methods to be added as properties
+     * @param requiredProperties set of required properties
      */
-    private void populateField(FieldScope field, Map<String, JsonNode> collectedFields, Set<String> requiredProperties) {
+    private void collectField(FieldScope field, Map<String, MemberScope<?, ?>> collectedProperties, Set<String> requiredProperties) {
         final FieldScope fieldWithNameOverride;
         final String propertyName;
         if (field.isFakeContainerItemScope()) {
@@ -529,38 +541,46 @@ public class SchemaGenerationContextImpl implements SchemaGenerationContext {
             if (this.generatorConfig.isRequired(field)) {
                 requiredProperties.add(propertyName);
             }
-            if (collectedFields.containsKey(propertyName)) {
+            if (collectedProperties.containsKey(propertyName)) {
                 logger.debug("ignoring overridden {}.{}", fieldWithNameOverride.getDeclaringType(), fieldWithNameOverride.getDeclaredName());
                 return;
             }
         }
+        collectedProperties.put(propertyName, fieldWithNameOverride);
+    }
 
-        List<ResolvedType> typeOverrides = this.generatorConfig.resolveTargetTypeOverrides(fieldWithNameOverride);
+    /**
+     * Preparation Step: create a node for a schema representing the given field's associated value type.
+     *
+     * @param field field/property to populate the schema node for
+     * @return schema node representing the given field/property
+     */
+    private ObjectNode populateFieldSchema(FieldScope field) {
+        List<ResolvedType> typeOverrides = this.generatorConfig.resolveTargetTypeOverrides(field);
         if (typeOverrides == null) {
-            typeOverrides = this.generatorConfig.resolveSubtypes(fieldWithNameOverride.getType(), this);
+            typeOverrides = this.generatorConfig.resolveSubtypes(field.getType(), this);
         }
         List<FieldScope> fieldOptions;
         if (typeOverrides == null || typeOverrides.isEmpty()) {
-            fieldOptions = Collections.singletonList(fieldWithNameOverride);
+            fieldOptions = Collections.singletonList(field);
         } else {
             fieldOptions = typeOverrides.stream()
-                    .map(fieldWithNameOverride::withOverriddenType)
+                    .map(field::withOverriddenType)
                     .collect(Collectors.toList());
         }
         // consider declared type (instead of overridden one) for determining null-ability
         boolean isNullable = !field.getRawMember().isEnumConstant() && !field.isFakeContainerItemScope() && this.generatorConfig.isNullable(field);
         if (fieldOptions.size() == 1) {
-            collectedFields.put(propertyName, this.createFieldSchema(fieldOptions.get(0), isNullable, false, null));
-        } else {
-            ObjectNode subSchema = this.generatorConfig.createObjectNode();
-            collectedFields.put(propertyName, subSchema);
-            ArrayNode anyOfArray = subSchema.withArray(this.getKeyword(SchemaKeyword.TAG_ANYOF));
-            if (isNullable) {
-                anyOfArray.addObject()
-                        .put(this.getKeyword(SchemaKeyword.TAG_TYPE), this.getKeyword(SchemaKeyword.TAG_TYPE_NULL));
-            }
-            fieldOptions.forEach(option -> anyOfArray.add(this.createFieldSchema(option, false, false, null)));
+            return this.createFieldSchema(fieldOptions.get(0), isNullable, false, null);
+        }
+        ObjectNode subSchema = this.generatorConfig.createObjectNode();
+        ArrayNode anyOfArray = subSchema.withArray(this.getKeyword(SchemaKeyword.TAG_ANYOF));
+        if (isNullable) {
+            anyOfArray.addObject()
+                    .put(this.getKeyword(SchemaKeyword.TAG_TYPE), this.getKeyword(SchemaKeyword.TAG_TYPE_NULL));
         }
+        fieldOptions.forEach(option -> anyOfArray.add(this.createFieldSchema(option, false, false, null)));
+        return subSchema;
     }
 
     /**
@@ -584,10 +604,10 @@ public class SchemaGenerationContextImpl implements SchemaGenerationContext {
      * Preparation Step: add the given method to the specified {@link Map}.
      *
      * @param method declared method that should be added to the specified node
-     * @param collectedMethods node in the JSON schema to which the method's (and its return value's) sub schema should be added as property
+     * @param collectedProperties collection of fields/methods to be added as properties
      * @param requiredProperties set of properties value required
      */
-    private void populateMethod(MethodScope method, Map<String, JsonNode> collectedMethods, Set<String> requiredProperties) {
+    private void collectMethod(MethodScope method, Map<String, MemberScope<?, ?>> collectedProperties, Set<String> requiredProperties) {
         final MethodScope methodWithNameOverride;
         final String propertyName;
         if (method.isFakeContainerItemScope()) {
@@ -600,39 +620,47 @@ public class SchemaGenerationContextImpl implements SchemaGenerationContext {
             if (this.generatorConfig.isRequired(method)) {
                 requiredProperties.add(propertyName);
             }
-            if (collectedMethods.containsKey(propertyName)) {
+            if (collectedProperties.containsKey(propertyName)) {
                 logger.debug("ignoring overridden {}.{}", methodWithNameOverride.getDeclaringType(), methodWithNameOverride.getDeclaredName());
                 return;
             }
         }
+        collectedProperties.put(propertyName, methodWithNameOverride);
+    }
 
-        List<ResolvedType> typeOverrides = this.generatorConfig.resolveTargetTypeOverrides(methodWithNameOverride);
-        if (typeOverrides == null && !methodWithNameOverride.isVoid()) {
-            typeOverrides = this.generatorConfig.resolveSubtypes(methodWithNameOverride.getType(), this);
+    /**
+     * Preparation Step: create a node for a schema representing the given method's associated return type.
+     *
+     * @param method method to populate the schema node for
+     * @return schema node representing the given method's return type
+     */
+    private JsonNode populateMethodSchema(MethodScope method) {
+        List<ResolvedType> typeOverrides = this.generatorConfig.resolveTargetTypeOverrides(method);
+        if (typeOverrides == null && !method.isVoid()) {
+            typeOverrides = this.generatorConfig.resolveSubtypes(method.getType(), this);
         }
         List<MethodScope> methodOptions;
         if (typeOverrides == null || typeOverrides.isEmpty()) {
-            methodOptions = Collections.singletonList(methodWithNameOverride);
+            methodOptions = Collections.singletonList(method);
         } else {
             methodOptions = typeOverrides.stream()
-                    .map(methodWithNameOverride::withOverriddenType)
+                    .map(method::withOverriddenType)
                     .collect(Collectors.toList());
         }
         // consider declared type (instead of overridden one) for determining null-ability
-        boolean isNullable = methodWithNameOverride.isVoid()
-                || !method.isFakeContainerItemScope() && this.generatorConfig.isNullable(methodWithNameOverride);
+        boolean isNullable = method.isVoid()
+                || !method.isFakeContainerItemScope() && this.generatorConfig.isNullable(method);
         if (methodOptions.size() == 1) {
-            collectedMethods.put(propertyName, this.createMethodSchema(methodOptions.get(0), isNullable, false, null));
-        } else {
-            ObjectNode subSchema = this.generatorConfig.createObjectNode();
-            collectedMethods.put(propertyName, subSchema);
-            ArrayNode anyOfArray = subSchema.withArray(this.getKeyword(SchemaKeyword.TAG_ANYOF));
-            if (isNullable) {
-                anyOfArray.add(this.generatorConfig.createObjectNode()
-                        .put(this.getKeyword(SchemaKeyword.TAG_TYPE), this.getKeyword(SchemaKeyword.TAG_TYPE_NULL)));
-            }
-            methodOptions.forEach(option -> anyOfArray.add(this.createMethodSchema(option, false, false, null)));
+            return this.createMethodSchema(methodOptions.get(0), isNullable, false, null);
         }
+        ObjectNode subSchema = this.generatorConfig.createObjectNode();
+        ArrayNode anyOfArray = subSchema.withArray(this.getKeyword(SchemaKeyword.TAG_ANYOF));
+        if (isNullable) {
+            anyOfArray.add(this.generatorConfig.createObjectNode()
+                    .put(this.getKeyword(SchemaKeyword.TAG_TYPE), this.getKeyword(SchemaKeyword.TAG_TYPE_NULL)));
+        }
+        methodOptions.forEach(option -> anyOfArray.add(this.createMethodSchema(option, false, false, null)));
+        return subSchema;
     }
 
     /**
@@ -666,7 +694,7 @@ public class SchemaGenerationContextImpl implements SchemaGenerationContext {
      * @param collectedAttributes separately collected attribute for the field/method in their respective declaring type
      * @param ignoredDefinitionProvider first custom definition provider to ignore
      * @see #populateField(FieldScope, Map, Set)
-     * @see #populateMethod(MethodScope, Map, Set)
+     * @see #collectMethod(MethodScope, Map, Set)
      */
     private <M extends MemberScope<?, ?>> void populateMemberSchema(M scope, ObjectNode targetNode, boolean isNullable, boolean forceInlineDefinition,
             ObjectNode collectedAttributes, CustomPropertyDefinitionProvider<M> ignoredDefinitionProvider) {
diff --git a/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/impl/SchemaGeneratorConfigImpl.java b/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/impl/SchemaGeneratorConfigImpl.java
index 9d048d5..73ef006 100644
--- a/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/impl/SchemaGeneratorConfigImpl.java
+++ b/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/impl/SchemaGeneratorConfigImpl.java
@@ -158,6 +158,11 @@ public class SchemaGeneratorConfigImpl implements SchemaGeneratorConfig {
         return this.getObjectMapper().createArrayNode();
     }
 
+    @Override
+    public int sortProperties(MemberScope<?, ?> first, MemberScope<?, ?> second) {
+        return this.typesInGeneralConfigPart.getPropertySorter().compare(first, second);
+    }
+
     @Override
     @SuppressWarnings("unchecked")
     public <M extends MemberScope<?, ?>> CustomDefinition getCustomDefinition(M scope, SchemaGenerationContext context,


Test patch:
diff --git a/jsonschema-generator/src/test/java/com/github/victools/jsonschema/generator/SchemaGeneratorComplexTypesTest.java b/jsonschema-generator/src/test/java/com/github/victools/jsonschema/generator/SchemaGeneratorComplexTypesTest.java
index 6df0c2a..98e72eb 100644
--- a/jsonschema-generator/src/test/java/com/github/victools/jsonschema/generator/SchemaGeneratorComplexTypesTest.java
+++ b/jsonschema-generator/src/test/java/com/github/victools/jsonschema/generator/SchemaGeneratorComplexTypesTest.java
@@ -110,7 +110,8 @@ public class SchemaGeneratorComplexTypesTest {
         Module typeInGeneralModule = configBuilder -> populateTypeConfigPart(
                 configBuilder.with(Option.FORBIDDEN_ADDITIONAL_PROPERTIES_BY_DEFAULT).forTypesInGeneral()
                         .withIdResolver(scope -> scope.getType().getTypeName().contains("$Test") ? "id-" + scope.getSimpleTypeDescription() : null)
-                        .withAnchorResolver(scope -> scope.isContainerType() ? null : "#anchor"),
+                        .withAnchorResolver(scope -> scope.isContainerType() ? null : "#anchor")
+                        .withPropertySorter((_prop1, _prop2) -> 0),
                 "for type in general: ");
         Module methodModule = configBuilder -> populateConfigPart(configBuilder.forMethods(), "looked-up from method: ");
         Module fieldModule = configBuilder -> populateConfigPart(configBuilder.with(Option.INLINE_ALL_SCHEMAS).forFields(), "looked-up from field: ");
diff --git a/jsonschema-generator/src/test/java/com/github/victools/jsonschema/generator/impl/PropertySortUtilsTest.java b/jsonschema-generator/src/test/java/com/github/victools/jsonschema/generator/impl/PropertySortUtilsTest.java
new file mode 100644
index 0000000..c3a259b
--- /dev/null
+++ b/jsonschema-generator/src/test/java/com/github/victools/jsonschema/generator/impl/PropertySortUtilsTest.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2020 VicTools.
+ *
+ * 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
+ *
+ *      http://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 com.github.victools.jsonschema.generator.impl;
+
+import com.github.victools.jsonschema.generator.FieldScope;
+import com.github.victools.jsonschema.generator.MemberScope;
+import com.github.victools.jsonschema.generator.MethodScope;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+/**
+ * Test for the {@link PropertySortUtils} class.
+ */
+public class PropertySortUtilsTest {
+
+    private FieldScope instanceFieldA;
+    private FieldScope instanceFieldC;
+    private MethodScope instanceMethodB;
+    private FieldScope staticFieldE;
+    private MethodScope staticMethodD;
+    private MethodScope staticMethodF;
+    private List<MemberScope<?, ?>> memberList;
+
+    @Before
+    public void setUp() {
+        this.instanceFieldA = this.createMemberMock(FieldScope.class, false, "a");
+        this.instanceFieldC = this.createMemberMock(FieldScope.class, false, "c");
+        this.staticFieldE = this.createMemberMock(FieldScope.class, true, "e");
+        this.instanceMethodB = this.createMemberMock(MethodScope.class, false, "b()");
+        this.staticMethodD = this.createMemberMock(MethodScope.class, true, "d()");
+        this.staticMethodF = this.createMemberMock(MethodScope.class, true, "f()");
+
+        this.memberList = Arrays.asList(
+                this.instanceFieldC, this.staticMethodF, this.staticFieldE, this.instanceFieldA, this.instanceMethodB, this.staticMethodD);
+    }
+
+    private <S extends MemberScope<?, ?>> S createMemberMock(Class<S> scopeType, boolean isStatic, String name) {
+        S mock = Mockito.mock(scopeType);
+        Mockito.when(mock.isStatic()).thenReturn(isStatic);
+        Mockito.when(mock.getSchemaPropertyName()).thenReturn(name);
+        return mock;
+    }
+
+    /**
+     * Test the correct sorting based on the {@link PropertySortUtils#SORT_PROPERTIES_FIELDS_BEFORE_METHODS} {@code Comparator}.
+     */
+    @Test
+    public void testSortPropertiesFieldsBeforeMethods() {
+        String sortingResult = this.memberList.stream()
+                .sorted(PropertySortUtils.SORT_PROPERTIES_FIELDS_BEFORE_METHODS)
+                .map(MemberScope::getSchemaPropertyName)
+                .collect(Collectors.joining(" "));
+        Assert.assertEquals("c e a f() b() d()", sortingResult);
+    }
+
+    /**
+     * Test the correct sorting based on the {@link PropertySortUtils#SORT_PROPERTIES_BY_NAME_ALPHABETICALLY} {@code Comparator}.
+     */
+    @Test
+    public void testSortPropertiesByNameAlphabetically() {
+        String sortingResult = this.memberList.stream()
+                .sorted(PropertySortUtils.SORT_PROPERTIES_BY_NAME_ALPHABETICALLY)
+                .map(MemberScope::getSchemaPropertyName)
+                .collect(Collectors.joining(" "));
+        Assert.assertEquals("a b() c d() e f()", sortingResult);
+    }
+
+    /**
+     * Test the correct sorting based on the {@link PropertySortUtils#DEFAULT_PROPERTY_ORDER} {@code Comparator}.
+     */
+    @Test
+    public void testDefaultPropertyOrder() {
+        String sortingResult = this.memberList.stream()
+                .sorted(PropertySortUtils.DEFAULT_PROPERTY_ORDER)
+                .map(MemberScope::getSchemaPropertyName)
+                .collect(Collectors.joining(" "));
+        Assert.assertEquals("a c e b() d() f()", sortingResult);
+    }
+}
diff --git a/jsonschema-generator/src/test/resources/com/github/victools/jsonschema/generator/testclass1-FULL_DOCUMENTATION-typeattributes.json b/jsonschema-generator/src/test/resources/com/github/victools/jsonschema/generator/testclass1-FULL_DOCUMENTATION-typeattributes.json
index 88ebbc2..b27b16f 100644
--- a/jsonschema-generator/src/test/resources/com/github/victools/jsonschema/generator/testclass1-FULL_DOCUMENTATION-typeattributes.json
+++ b/jsonschema-generator/src/test/resources/com/github/victools/jsonschema/generator/testclass1-FULL_DOCUMENTATION-typeattributes.json
@@ -1,19 +1,16 @@
 {
     "type": "object",
     "properties": {
-        "CONSTANT": {
-            "type": "integer",
-            "const": 5,
+        "genericValue": {
+            "type": ["string", "null"],
             "$anchor": "#anchor",
-            "title": "Long",
-            "description": "for type in general: Long",
-            "default": 1,
-            "enum": [1, 2, 3, 4, 5],
-            "minimum": 1,
-            "exclusiveMinimum": 0,
-            "maximum": 1E+1,
-            "exclusiveMaximum": 11,
-            "multipleOf": 1
+            "title": "String",
+            "description": "for type in general: String",
+            "const": "constant string value",
+            "minLength": 1,
+            "maxLength": 256,
+            "format": "date",
+            "pattern": "^.{1,256}$"
         },
         "genericArray": {
             "type": ["array", "null"],
@@ -34,16 +31,11 @@
             "maxItems": 100,
             "uniqueItems": false
         },
-        "genericValue": {
-            "type": ["string", "null"],
+        "primitiveValue": {
+            "type": "integer",
             "$anchor": "#anchor",
-            "title": "String",
-            "description": "for type in general: String",
-            "const": "constant string value",
-            "minLength": 1,
-            "maxLength": 256,
-            "format": "date",
-            "pattern": "^.{1,256}$"
+            "title": "int",
+            "description": "for type in general: int"
         },
         "ignoredInternalValue": {
             "type": ["integer", "null"],
@@ -58,7 +50,13 @@
             "exclusiveMaximum": 11,
             "multipleOf": 1
         },
-        "primitiveValue": {
+        "isSimpleTestClass()": {
+            "type": "boolean",
+            "$anchor": "#anchor",
+            "title": "boolean",
+            "description": "for type in general: boolean"
+        },
+        "getPrimitiveValue()": {
             "type": "integer",
             "$anchor": "#anchor",
             "title": "int",
@@ -76,17 +74,19 @@
             "format": "date",
             "pattern": "^.{1,256}$"
         },
-        "getPrimitiveValue()": {
+        "CONSTANT": {
             "type": "integer",
+            "const": 5,
             "$anchor": "#anchor",
-            "title": "int",
-            "description": "for type in general: int"
-        },
-        "isSimpleTestClass()": {
-            "type": "boolean",
-            "$anchor": "#anchor",
-            "title": "boolean",
-            "description": "for type in general: boolean"
+            "title": "Long",
+            "description": "for type in general: Long",
+            "default": 1,
+            "enum": [1, 2, 3, 4, 5],
+            "minimum": 1,
+            "exclusiveMinimum": 0,
+            "maximum": 1E+1,
+            "exclusiveMaximum": 11,
+            "multipleOf": 1
         }
     },
     "$id": "id-TestClass1",
diff --git a/jsonschema-generator/src/test/resources/com/github/victools/jsonschema/generator/testclass3-FULL_DOCUMENTATION-typeattributes.json b/jsonschema-generator/src/test/resources/com/github/victools/jsonschema/generator/testclass3-FULL_DOCUMENTATION-typeattributes.json
index b0ec99d..0aef090 100644
--- a/jsonschema-generator/src/test/resources/com/github/victools/jsonschema/generator/testclass3-FULL_DOCUMENTATION-typeattributes.json
+++ b/jsonschema-generator/src/test/resources/com/github/victools/jsonschema/generator/testclass3-FULL_DOCUMENTATION-typeattributes.json
@@ -16,12 +16,6 @@
                     "exclusiveMaximum": 11,
                     "multipleOf": 1
                 },
-                "isPresent()": {
-                    "type": "boolean",
-                    "$anchor": "#anchor",
-                    "title": "boolean",
-                    "description": "for type in general: boolean"
-                },
                 "orElse(Integer)": {
                     "type": ["integer", "null"],
                     "$anchor": "#anchor",
@@ -34,6 +28,12 @@
                     "maximum": 1E+1,
                     "exclusiveMaximum": 11,
                     "multipleOf": 1
+                },
+                "isPresent()": {
+                    "type": "boolean",
+                    "$anchor": "#anchor",
+                    "title": "boolean",
+                    "description": "for type in general: boolean"
                 }
             },
             "$anchor": "#anchor",
@@ -55,19 +55,13 @@
                     "format": "date",
                     "pattern": "^.{1,256}$"
                 },
-                "oldMode": {
-                    "type": "integer",
-                    "$anchor": "#anchor",
-                    "title": "int",
-                    "description": "for type in general: int"
-                },
                 "ordinal": {
                     "type": "integer",
                     "$anchor": "#anchor",
                     "title": "int",
                     "description": "for type in general: int"
                 },
-                "compareTo(RoundingMode)": {
+                "oldMode": {
                     "type": "integer",
                     "$anchor": "#anchor",
                     "title": "int",
@@ -85,11 +79,11 @@
                     "format": "date",
                     "pattern": "^.{1,256}$"
                 },
-                "valueOf(String)": {
-                    "$ref": "#/definitions/RoundingMode-nullable"
-                },
-                "valueOf(int)": {
-                    "$ref": "#/definitions/RoundingMode-nullable"
+                "compareTo(RoundingMode)": {
+                    "type": "integer",
+                    "$anchor": "#anchor",
+                    "title": "int",
+                    "description": "for type in general: int"
                 },
                 "values()": {
                     "type": ["array", "null"],
@@ -101,6 +95,12 @@
                     "minItems": 2,
                     "maxItems": 100,
                     "uniqueItems": false
+                },
+                "valueOf(int)": {
+                    "$ref": "#/definitions/RoundingMode-nullable"
+                },
+                "valueOf(String)": {
+                    "$ref": "#/definitions/RoundingMode-nullable"
                 }
             },
             "$anchor": "#anchor",
@@ -118,19 +118,16 @@
         "TestClass1": {
             "type": "object",
             "properties": {
-                "CONSTANT": {
-                    "type": "integer",
-                    "const": 5,
+                "genericValue": {
+                    "type": ["string", "null"],
                     "$anchor": "#anchor",
-                    "title": "Long",
-                    "description": "for type in general: Long",
-                    "default": 1,
-                    "enum": [1, 2, 3, 4, 5],
-                    "minimum": 1,
-                    "exclusiveMinimum": 0,
-                    "maximum": 1E+1,
-                    "exclusiveMaximum": 11,
-                    "multipleOf": 1
+                    "title": "String",
+                    "description": "for type in general: String",
+                    "const": "constant string value",
+                    "minLength": 1,
+                    "maxLength": 256,
+                    "format": "date",
+                    "pattern": "^.{1,256}$"
                 },
                 "genericArray": {
                     "type": ["array", "null"],
@@ -151,16 +148,11 @@
                     "maxItems": 100,
                     "uniqueItems": false
                 },
-                "genericValue": {
-                    "type": ["string", "null"],
+                "primitiveValue": {
+                    "type": "integer",
                     "$anchor": "#anchor",
-                    "title": "String",
-                    "description": "for type in general: String",
-                    "const": "constant string value",
-                    "minLength": 1,
-                    "maxLength": 256,
-                    "format": "date",
-                    "pattern": "^.{1,256}$"
+                    "title": "int",
+                    "description": "for type in general: int"
                 },
                 "ignoredInternalValue": {
                     "type": ["integer", "null"],
@@ -175,7 +167,13 @@
                     "exclusiveMaximum": 11,
                     "multipleOf": 1
                 },
-                "primitiveValue": {
+                "isSimpleTestClass()": {
+                    "type": "boolean",
+                    "$anchor": "#anchor",
+                    "title": "boolean",
+                    "description": "for type in general: boolean"
+                },
+                "getPrimitiveValue()": {
                     "type": "integer",
                     "$anchor": "#anchor",
                     "title": "int",
@@ -193,17 +191,19 @@
                     "format": "date",
                     "pattern": "^.{1,256}$"
                 },
-                "getPrimitiveValue()": {
+                "CONSTANT": {
                     "type": "integer",
+                    "const": 5,
                     "$anchor": "#anchor",
-                    "title": "int",
-                    "description": "for type in general: int"
-                },
-                "isSimpleTestClass()": {
-                    "type": "boolean",
-                    "$anchor": "#anchor",
-                    "title": "boolean",
-                    "description": "for type in general: boolean"
+                    "title": "Long",
+                    "description": "for type in general: Long",
+                    "default": 1,
+                    "enum": [1, 2, 3, 4, 5],
+                    "minimum": 1,
+                    "exclusiveMinimum": 0,
+                    "maximum": 1E+1,
+                    "exclusiveMaximum": 11,
+                    "multipleOf": 1
                 }
             },
             "$id": "id-TestClass1",
@@ -228,6 +228,19 @@
         "TestClass2(Long)": {
             "type": "object",
             "properties": {
+                "genericValue": {
+                    "type": ["integer", "null"],
+                    "$anchor": "#anchor",
+                    "title": "Long",
+                    "description": "for type in general: Long",
+                    "default": 1,
+                    "enum": [1, 2, 3, 4, 5],
+                    "minimum": 1,
+                    "exclusiveMinimum": 0,
+                    "maximum": 1E+1,
+                    "exclusiveMaximum": 11,
+                    "multipleOf": 1
+                },
                 "genericArray": {
                     "type": ["array", "null"],
                     "items": {
@@ -249,19 +262,6 @@
                     "maxItems": 100,
                     "uniqueItems": false
                 },
-                "genericValue": {
-                    "type": ["integer", "null"],
-                    "$anchor": "#anchor",
-                    "title": "Long",
-                    "description": "for type in general: Long",
-                    "default": 1,
-                    "enum": [1, 2, 3, 4, 5],
-                    "minimum": 1,
-                    "exclusiveMinimum": 0,
-                    "maximum": 1E+1,
-                    "exclusiveMaximum": 11,
-                    "multipleOf": 1
-                },
                 "getGenericValue()": {
                     "type": ["integer", "null"],
                     "$anchor": "#anchor",
@@ -307,6 +307,17 @@
         "TestClass2(String)": {
             "type": "object",
             "properties": {
+                "genericValue": {
+                    "type": ["string", "null"],
+                    "$anchor": "#anchor",
+                    "title": "String",
+                    "description": "for type in general: String",
+                    "const": "constant string value",
+                    "minLength": 1,
+                    "maxLength": 256,
+                    "format": "date",
+                    "pattern": "^.{1,256}$"
+                },
                 "genericArray": {
                     "type": ["array", "null"],
                     "items": {
@@ -326,17 +337,6 @@
                     "maxItems": 100,
                     "uniqueItems": false
                 },
-                "genericValue": {
-                    "type": ["string", "null"],
-                    "$anchor": "#anchor",
-                    "title": "String",
-                    "description": "for type in general: String",
-                    "const": "constant string value",
-                    "minLength": 1,
-                    "maxLength": 256,
-                    "format": "date",
-                    "pattern": "^.{1,256}$"
-                },
                 "getGenericValue()": {
                     "type": ["string", "null"],
                     "$anchor": "#anchor",
@@ -378,6 +378,18 @@
         "TestClass2(TestClass1*)-nullable": {
             "type": ["object", "null"],
             "properties": {
+                "genericValue": {
+                    "type": ["array", "null"],
+                    "items": {
+                        "$ref": "#/definitions/TestClass1"
+                    },
+                    "$id": "id-TestClass1[]",
+                    "title": "TestClass1[]",
+                    "description": "for type in general: TestClass1[]",
+                    "minItems": 2,
+                    "maxItems": 100,
+                    "uniqueItems": false
+                },
                 "genericArray": {
                     "type": ["array", "null"],
                     "items": {
@@ -399,18 +411,6 @@
                     "maxItems": 100,
                     "uniqueItems": false
                 },
-                "genericValue": {
-                    "type": ["array", "null"],
-                    "items": {
-                        "$ref": "#/definitions/TestClass1"
-                    },
-                    "$id": "id-TestClass1[]",
-                    "title": "TestClass1[]",
-                    "description": "for type in general: TestClass1[]",
-                    "minItems": 2,
-                    "maxItems": 100,
-                    "uniqueItems": false
-                },
                 "getGenericValue()": {
                     "type": ["array", "null"],
                     "items": {
@@ -447,6 +447,9 @@
         "TestClass2(TestClass2(String))-nullable": {
             "type": ["object", "null"],
             "properties": {
+                "genericValue": {
+                    "$ref": "#/definitions/TestClass2(String)-nullable"
+                },
                 "genericArray": {
                     "type": ["array", "null"],
                     "items": {
@@ -459,9 +462,6 @@
                     "maxItems": 100,
                     "uniqueItems": false
                 },
-                "genericValue": {
-                    "$ref": "#/definitions/TestClass2(String)-nullable"
-                },
                 "getGenericValue()": {
                     "$ref": "#/definitions/TestClass2(String)-nullable"
                 }
@@ -480,13 +480,6 @@
         "TestClass4(Integer,String)-nullable": {
             "type": ["object", "null"],
             "properties": {
-                "DEFAULT_ROUNDING_MODE": {
-                    "allOf": [{
-                            "$ref": "#/definitions/RoundingMode"
-                        }, {
-                            "const": "HALF_UP"
-                        }]
-                },
                 "class2OfClass2OfT": {
                     "$ref": "#/definitions/TestClass2(TestClass2(String))-nullable"
                 },
@@ -495,6 +488,13 @@
                 },
                 "getClass2OfClass2OfT()": {
                     "$ref": "#/definitions/TestClass2(TestClass2(String))-nullable"
+                },
+                "DEFAULT_ROUNDING_MODE": {
+                    "allOf": [{
+                            "$ref": "#/definitions/RoundingMode"
+                        }, {
+                            "const": "HALF_UP"
+                        }]
                 }
             },
             "$id": "id-TestClass4<Integer, String>",
@@ -516,15 +516,12 @@
     },
     "type": "object",
     "properties": {
-        "class4": {
-            "$ref": "#/definitions/TestClass4(Integer,String)-nullable"
+        "nestedLong": {
+            "$ref": "#/definitions/TestClass2(Long)-nullable"
         },
         "nestedClass1Array": {
             "$ref": "#/definitions/TestClass2(TestClass1*)-nullable"
         },
-        "nestedLong": {
-            "$ref": "#/definitions/TestClass2(Long)-nullable"
-        },
         "nestedLongList": {
             "type": ["array", "null"],
             "items": {
@@ -537,15 +534,9 @@
             "maxItems": 100,
             "uniqueItems": false
         },
-        "getClass4()": {
+        "class4": {
             "$ref": "#/definitions/TestClass4(Integer,String)-nullable"
         },
-        "getNestedClass1Array()": {
-            "$ref": "#/definitions/TestClass2(TestClass1*)-nullable"
-        },
-        "getNestedLong()": {
-            "$ref": "#/definitions/TestClass2(Long)-nullable"
-        },
         "getNestedLongList()": {
             "type": ["array", "null"],
             "items": {
@@ -557,6 +548,15 @@
             "minItems": 2,
             "maxItems": 100,
             "uniqueItems": false
+        },
+        "getClass4()": {
+            "$ref": "#/definitions/TestClass4(Integer,String)-nullable"
+        },
+        "getNestedLong()": {
+            "$ref": "#/definitions/TestClass2(Long)-nullable"
+        },
+        "getNestedClass1Array()": {
+            "$ref": "#/definitions/TestClass2(TestClass1*)-nullable"
         }
     },
     "$id": "id-TestClass3",
diff --git a/jsonschema-generator/src/test/resources/com/github/victools/jsonschema/generator/testclass3-PLAIN_JSON-fieldattributes.json b/jsonschema-generator/src/test/resources/com/github/victools/jsonschema/generator/testclass3-PLAIN_JSON-fieldattributes.json
index 1305de6..a761e39 100644
--- a/jsonschema-generator/src/test/resources/com/github/victools/jsonschema/generator/testclass3-PLAIN_JSON-fieldattributes.json
+++ b/jsonschema-generator/src/test/resources/com/github/victools/jsonschema/generator/testclass3-PLAIN_JSON-fieldattributes.json
@@ -451,5 +451,5 @@
             }
         }
     },
-    "required": ["nestedLongList", "nestedLong", "nestedClass1Array"]
+    "required": ["nestedClass1Array", "nestedLong", "nestedLongList"]
 }
diff --git a/jsonschema-module-javax-validation/src/test/resources/com/github/victools/jsonschema/module/javax/validation/integration-test-result.json b/jsonschema-module-javax-validation/src/test/resources/com/github/victools/jsonschema/module/javax/validation/integration-test-result.json
index 4994517..eade445 100644
--- a/jsonschema-module-javax-validation/src/test/resources/com/github/victools/jsonschema/module/javax/validation/integration-test-result.json
+++ b/jsonschema-module-javax-validation/src/test/resources/com/github/victools/jsonschema/module/javax/validation/integration-test-result.json
@@ -59,5 +59,5 @@
             "maxLength": 12
         }
     },
-    "required": ["notNullEmail", "notEmptyPatternText", "notBlankText", "notEmptyList", "notNullList"]
+    "required": ["notBlankText", "notEmptyList", "notEmptyPatternText", "notNullEmail", "notNullList"]
 }