Repository: joelittlejohn/jsonschema2pojo
Pull request #953 (2019-01-21 17:08:14)

Features/builders as inner classes
Adds support for using the https://en.wikipedia.org/wiki/Builder_pattern instead of just chainable setters. This commit maintains full backwards compatibility with existing capabilities, but enables the selection of a more robust builder pattern if desired (for example if you want to have immutable objects).

This pull request also includes multiple refactoring changes to avoid duplicate code wherever possible. Specifically, when implementing the builder mechanisms several capabilities which were already implemented in the code needed to be migrated into more accessible places.


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3d0c04fc..5fac78ae 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,13 +1,8 @@
 # Changelog
 
-## 1.0.0
-* Don't register output directory as a set of files, since this breaks Gradle 5.0+ ([#940](https://github.com/joelittlejohn/jsonschema2pojo/pull/940))
-* **Builder method should be named consistently with getter/setter ([#905](https://github.com/joelittlejohn/jsonschema2pojo/issues/905))**
-* Objects inside array with name ending with "List" are generated as "SomeNameList" ([#780](https://github.com/joelittlejohn/jsonschema2pojo/issues/780))
-
 ## 1.0.0-beta1
 * Add 'formatTypeMapping' config option to allow overriding types used for formats ([#923](https://github.com/joelittlejohn/jsonschema2pojo/pull/923))
-* **Modify Rule#apply signature to include parent node (allows context-aware rules) ([#917](https://github.com/joelittlejohn/jsonschema2pojo/pull/917))**
+* **Enable development of context-aware rules. ([#917](https://github.com/joelittlejohn/jsonschema2pojo/pull/917))**
 * Add 'javaOptional' extension rule to allow individual fields to use Java Optional on getter ([#913](https://github.com/joelittlejohn/jsonschema2pojo/pull/913))
 * Allow schema title to be used as class name (new useTitleAsClassname config option) ([#908](https://github.com/joelittlejohn/jsonschema2pojo/issues/908))
 * Incorrect @Nullable JSR305 annotations generated when using 'required' array ([#906](https://github.com/joelittlejohn/jsonschema2pojo/issues/906))
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
index 943d4323..65a04125 100644
--- a/CONTRIBUTORS.md
+++ b/CONTRIBUTORS.md
@@ -94,4 +94,3 @@
 * Klevis Ramo
 * Martin Bramwell
 * Jan Oopkaup
-* Michał Szymborski
diff --git a/README.md b/README.md
index 1626d2c5..091e93ba 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,9 @@
 # jsonschema2pojo [![Build Status](https://travis-ci.org/joelittlejohn/jsonschema2pojo.svg?branch=master)](https://travis-ci.org/joelittlejohn/jsonschema2pojo) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/org.jsonschema2pojo/jsonschema2pojo/badge.svg)](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22org.jsonschema2pojo%22)
 
-_jsonschema2pojo_ generates Java types from JSON Schema (or example JSON) and can annotate those types for data-binding with Jackson 1.x, Jackson 2.x or Gson.
+Current stable: **0.5.1**<br>
+Current unstable: **1.0.0-beta1**<br>
 
-**_*Note:*_ there are breaking changes between 0.5.1 and 1.0.0. Check the [change log](https://github.com/joelittlejohn/jsonschema2pojo/blob/master/CHANGELOG.md). Anything marked in bold in the 1.0.0 alpha, beta and final release is a breaking change.**
+_jsonschema2pojo_ generates Java types from JSON Schema (or example JSON) and can annotate those types for data-binding with Jackson 1.x, Jackson 2.x or Gson.
 
 ### [Try jsonschema2pojo online](http://jsonschema2pojo.org/)<br>or `brew install jsonschema2pojo`
 
@@ -13,7 +14,7 @@ A very simple Maven example:
 <plugin>
     <groupId>org.jsonschema2pojo</groupId>
     <artifactId>jsonschema2pojo-maven-plugin</artifactId>
-    <version>1.0.0</version>
+    <version>0.5.1</version>
     <configuration>
         <sourceDirectory>${basedir}/src/main/resources/schema</sourceDirectory>
         <targetPackage>com.example.types</targetPackage>
@@ -32,9 +33,9 @@ Useful pages:
   * **[Getting started](https://github.com/joelittlejohn/jsonschema2pojo/wiki/Getting-Started)**
   * **[How to contribute](https://github.com/joelittlejohn/jsonschema2pojo/blob/master/CONTRIBUTING.md)**
   * [Reference](https://github.com/joelittlejohn/jsonschema2pojo/wiki/Reference)
-  * [Latest Javadocs](https://joelittlejohn.github.io/jsonschema2pojo/javadocs/1.0.0/)
-  * [Documentation for the Maven plugin](https://joelittlejohn.github.io/jsonschema2pojo/site/1.0.0/generate-mojo.html)
-  * [Documentation for the Ant task](https://joelittlejohn.github.io/jsonschema2pojo/site/1.0.0/Jsonschema2PojoTask.html)
+  * [Latest Javadocs](https://joelittlejohn.github.io/jsonschema2pojo/javadocs/0.5.1/)
+  * [Documentation for the Maven plugin](https://joelittlejohn.github.io/jsonschema2pojo/site/0.5.1/generate-mojo.html)
+  * [Documentation for the Ant task](https://joelittlejohn.github.io/jsonschema2pojo/site/0.5.1/Jsonschema2PojoTask.html)
 
 Project resources:
   * [Downloads](https://github.com/joelittlejohn/jsonschema2pojo/releases)
diff --git a/jsonschema2pojo-ant/pom.xml b/jsonschema2pojo-ant/pom.xml
index 18c4f6f8..b327f204 100644
--- a/jsonschema2pojo-ant/pom.xml
+++ b/jsonschema2pojo-ant/pom.xml
@@ -5,7 +5,7 @@
     <parent>
         <artifactId>jsonschema2pojo</artifactId>
         <groupId>org.jsonschema2pojo</groupId>
-        <version>1.0.1-SNAPSHOT</version>
+        <version>1.0.0-beta2-SNAPSHOT</version>
     </parent>
 
     <artifactId>jsonschema2pojo-ant</artifactId>
diff --git a/jsonschema2pojo-ant/src/main/java/org/jsonschema2pojo/ant/Jsonschema2PojoTask.java b/jsonschema2pojo-ant/src/main/java/org/jsonschema2pojo/ant/Jsonschema2PojoTask.java
index 5e0a7f62..4bcbe11f 100644
--- a/jsonschema2pojo-ant/src/main/java/org/jsonschema2pojo/ant/Jsonschema2PojoTask.java
+++ b/jsonschema2pojo-ant/src/main/java/org/jsonschema2pojo/ant/Jsonschema2PojoTask.java
@@ -69,6 +69,8 @@ public class Jsonschema2PojoTask extends Task implements GenerationConfig {
 
     private boolean generateBuilders;
 
+    private boolean useInnerClassBuilders = false;
+
     private boolean includeConstructors = false;
 
     private boolean usePrimitives;
@@ -1201,5 +1203,18 @@ public class Jsonschema2PojoTask extends Task implements GenerationConfig {
     public Map<String, String> getFormatTypeMapping() {
         return formatTypeMapping;
     }
-    
+
+    @Override
+    public boolean isUseInnerClassBuilders() {
+        return useInnerClassBuilders;
+    }
+
+    /**
+     * Sets the 'useInnerClassBuilders' property of this class
+     *
+     * @param useInnerClassBuilders determines whether builders will be chainable setters or embedded classes when {@link #isGenerateBuilders()} used
+     */
+    public void setUseInnerClassBuilders(boolean useInnerClassBuilders) {
+        this.useInnerClassBuilders = useInnerClassBuilders;
+    }
 }
diff --git a/jsonschema2pojo-ant/src/site/Jsonschema2PojoTask.html b/jsonschema2pojo-ant/src/site/Jsonschema2PojoTask.html
index 5b823df1..cf725b08 100644
--- a/jsonschema2pojo-ant/src/site/Jsonschema2PojoTask.html
+++ b/jsonschema2pojo-ant/src/site/Jsonschema2PojoTask.html
@@ -156,6 +156,11 @@
         </td>
         <td align="center" valign="top">No (default <code>false</code>)</td>
     </tr>
+    <tr>
+        <td valign="top">useInnerClassBuilders</td>
+        <td valign="top">Determines whether builders will be chainable setters or embedded classes when generateBuilders is used</td>
+        <td align="center" valign="top">No (default <code>false</code>)</td>
+    </tr>
     <tr>
         <td valign="top">includeGetters</td>
         <td valign="top">Whether to include getters or to omit these accessor methods and create public fields instead.</td>
diff --git a/jsonschema2pojo-cli/pom.xml b/jsonschema2pojo-cli/pom.xml
index 07449403..2f61d83c 100644
--- a/jsonschema2pojo-cli/pom.xml
+++ b/jsonschema2pojo-cli/pom.xml
@@ -5,7 +5,7 @@
     <parent>
         <artifactId>jsonschema2pojo</artifactId>
         <groupId>org.jsonschema2pojo</groupId>
-        <version>1.0.1-SNAPSHOT</version>
+        <version>1.0.0-beta2-SNAPSHOT</version>
     </parent>
 
     <artifactId>jsonschema2pojo-cli</artifactId>
diff --git a/jsonschema2pojo-cli/src/main/java/org/jsonschema2pojo/cli/Arguments.java b/jsonschema2pojo-cli/src/main/java/org/jsonschema2pojo/cli/Arguments.java
index f7b5f33d..9f6704e5 100644
--- a/jsonschema2pojo-cli/src/main/java/org/jsonschema2pojo/cli/Arguments.java
+++ b/jsonschema2pojo-cli/src/main/java/org/jsonschema2pojo/cli/Arguments.java
@@ -63,6 +63,9 @@ public class Arguments implements GenerationConfig {
     @Parameter(names = { "-b", "--generate-builders" }, description = "Generate builder-style methods as well as setters")
     private boolean generateBuilderMethods = false;
 
+    @Parameter(names = { "--use-inner-class-builders" }, description = "Generate an inner class with builder-style methods")
+    private boolean useInnerClassBuilders = false;
+
     @Parameter(names = { "-c", "--generate-constructors" }, description = "Generate constructors")
     private boolean generateConstructors = false;
 
@@ -278,6 +281,11 @@ public class Arguments implements GenerationConfig {
         return generateBuilderMethods;
     }
 
+    @Override
+    public boolean isUseInnerClassBuilders() {
+        return useInnerClassBuilders;
+    }
+
     @Override
     public boolean isUsePrimitives() {
         return usePrimitives;
diff --git a/jsonschema2pojo-core/pom.xml b/jsonschema2pojo-core/pom.xml
index be05cc4c..f9ed720b 100644
--- a/jsonschema2pojo-core/pom.xml
+++ b/jsonschema2pojo-core/pom.xml
@@ -5,7 +5,7 @@
     <parent>
         <artifactId>jsonschema2pojo</artifactId>
         <groupId>org.jsonschema2pojo</groupId>
-        <version>1.0.1-SNAPSHOT</version>
+        <version>1.0.0-beta2-SNAPSHOT</version>
     </parent>
 
     <artifactId>jsonschema2pojo-core</artifactId>
diff --git a/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/DefaultGenerationConfig.java b/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/DefaultGenerationConfig.java
index d295b269..068a853d 100644
--- a/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/DefaultGenerationConfig.java
+++ b/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/DefaultGenerationConfig.java
@@ -444,5 +444,7 @@ public class DefaultGenerationConfig implements GenerationConfig {
     public Map<String, String> getFormatTypeMapping() {
         return Collections.emptyMap();
     }
-    
+
+    @Override
+    public boolean isUseInnerClassBuilders() { return false; }
 }
diff --git a/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/GenerationConfig.java b/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/GenerationConfig.java
index efcf91f5..ec27b894 100644
--- a/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/GenerationConfig.java
+++ b/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/GenerationConfig.java
@@ -571,4 +571,13 @@ public interface GenerationConfig {
      *         fully qualified type name (e.g. 'java.net.URI').
      */
     Map<String, String> getFormatTypeMapping();
+
+    /**
+     * If set to true, then the gang of four builder pattern will be used to generate builders on generated classes. Note: This property works
+     * in collaboration with the {@link #isGenerateBuilders()} method. If the {@link #isGenerateBuilders()} is false,
+     * then this property will not do anything.
+     * @return whether to include the gang of four builder patter on the generated classes. The default value for this is false.
+     */
+    default boolean isUseInnerClassBuilders(){ return false;}
+
 }
diff --git a/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/ScalaSingleStreamCodeWriter.java b/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/ScalaSingleStreamCodeWriter.java
index 1f843c84..17f7691a 100644
--- a/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/ScalaSingleStreamCodeWriter.java
+++ b/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/ScalaSingleStreamCodeWriter.java
@@ -16,15 +16,14 @@
 
 package org.jsonschema2pojo;
 
-import java.io.ByteArrayOutputStream;
-import java.io.FilterOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-
 import com.mysema.scalagen.ConversionSettings;
 import com.mysema.scalagen.Converter;
 import com.sun.codemodel.JPackage;
 import com.sun.codemodel.writer.SingleStreamCodeWriter;
+import java.io.ByteArrayOutputStream;
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
 
 public class ScalaSingleStreamCodeWriter extends SingleStreamCodeWriter {
 
diff --git a/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/rules/AdditionalPropertiesRule.java b/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/rules/AdditionalPropertiesRule.java
index 3225252d..aaddb1a7 100644
--- a/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/rules/AdditionalPropertiesRule.java
+++ b/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/rules/AdditionalPropertiesRule.java
@@ -19,6 +19,11 @@ package org.jsonschema2pojo.rules;
 import java.util.HashMap;
 import java.util.Map;
 
+import java.util.Optional;
+import java.util.Spliterator;
+import java.util.Spliterators;
+import java.util.stream.StreamSupport;
+import org.apache.commons.collections15.CollectionUtils;
 import org.jsonschema2pojo.Schema;
 
 import com.fasterxml.jackson.databind.JsonNode;
@@ -160,7 +165,19 @@ public class AdditionalPropertiesRule implements Rule<JDefinedClass, JDefinedCla
         return getter;
     }
 
-    private void addBuilder(JDefinedClass jclass, JType propertyType, JFieldVar field) {
+    private JMethod addBuilder(JDefinedClass jclass, JType propertyType, JFieldVar field) {
+
+        JMethod result = null;
+        if(ruleFactory.getGenerationConfig().isUseInnerClassBuilders()) {
+            result = addInnerBuilder(jclass, propertyType, field);
+        } else {
+            result = addLegacyBuilder(jclass, propertyType, field);
+        }
+
+        return result;
+    }
+
+    private JMethod addLegacyBuilder(JDefinedClass jclass, JType propertyType, JFieldVar field) {
         JMethod builder = jclass.method(JMod.PUBLIC, jclass, "withAdditionalProperty");
 
         JVar nameParam = builder.param(String.class, "name");
@@ -171,6 +188,32 @@ public class AdditionalPropertiesRule implements Rule<JDefinedClass, JDefinedCla
         mapInvocation.arg(nameParam);
         mapInvocation.arg(valueParam);
         body._return(JExpr._this());
+
+        return builder;
+    }
+
+    private JMethod addInnerBuilder(JDefinedClass jclass, JType propertyType, JFieldVar field) {
+        Optional<JDefinedClass> builderClass = StreamSupport
+            .stream(Spliterators.spliteratorUnknownSize(jclass.classes(), Spliterator.ORDERED), false)
+            .filter(definedClass -> definedClass.name().equals(getBuilderClassName(jclass)))
+            .findFirst();
+
+        JMethod builder = builderClass.get().method(JMod.PUBLIC, builderClass.get(), "withAdditionalProperty");
+
+        JVar nameParam = builder.param(String.class, "name");
+        JVar valueParam = builder.param(propertyType, "value");
+
+        JBlock body = builder.body();
+        JInvocation mapInvocation = body.invoke(JExpr.ref(JExpr.cast(jclass, JExpr._this().ref("instance")), field), "put");
+        mapInvocation.arg(nameParam);
+        mapInvocation.arg(valueParam);
+        body._return(JExpr._this());
+
+        return builder;
+    }
+
+    private String getBuilderClassName(JDefinedClass c) {
+        return ruleFactory.getNameHelper().getBuilderClassName(c);
     }
 
 }
diff --git a/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/rules/BuilderRule.java b/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/rules/BuilderRule.java
new file mode 100644
index 00000000..7f509513
--- /dev/null
+++ b/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/rules/BuilderRule.java
@@ -0,0 +1,121 @@
+/**
+ * Copyright Â© 2010-2017 Nokia
+ *
+ * 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 org.jsonschema2pojo.rules;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.sun.codemodel.JAnnotationUse;
+import com.sun.codemodel.JBlock;
+import com.sun.codemodel.JClass;
+import com.sun.codemodel.JClassAlreadyExistsException;
+import com.sun.codemodel.JConditional;
+import com.sun.codemodel.JDefinedClass;
+import com.sun.codemodel.JExpr;
+import com.sun.codemodel.JFieldVar;
+import com.sun.codemodel.JInvocation;
+import com.sun.codemodel.JMethod;
+import com.sun.codemodel.JMod;
+import com.sun.codemodel.JTypeVar;
+import com.sun.codemodel.JVar;
+import java.util.Objects;
+import org.jsonschema2pojo.Schema;
+import org.jsonschema2pojo.util.ReflectionHelper;
+
+public class BuilderRule implements Rule<JDefinedClass, JDefinedClass> {
+
+  private RuleFactory ruleFactory;
+  private ReflectionHelper reflectionHelper;
+
+  BuilderRule(RuleFactory ruleFactory, ReflectionHelper reflectionHelper) {
+    this.ruleFactory = ruleFactory;
+    this.reflectionHelper = reflectionHelper;
+  }
+
+  @Override
+  public JDefinedClass apply(String nodeName, JsonNode node, JsonNode parent, JDefinedClass instanceClass, Schema currentSchema) {
+
+    // Create the inner class for the builder
+    JDefinedClass builderClass;
+
+    try {
+      String builderName = ruleFactory.getNameHelper().getBuilderClassName(instanceClass);
+      builderClass = instanceClass._class(JMod.PUBLIC + JMod.STATIC, builderName);
+    } catch (JClassAlreadyExistsException e) {
+      return e.getExistingClass();
+    }
+
+    // Determine which builder (if any) this builder should inherit from
+    JClass parentBuilderClass = null;
+    JClass parentClass = instanceClass._extends();
+    if (!(parentClass.isPrimitive() || reflectionHelper.isFinal(parentClass) || Objects.equals(parentClass.fullName(), "java.lang.Object"))) {
+      parentBuilderClass = reflectionHelper.getBuilderClass(parentClass);
+    }
+
+    // Determine the generic type 'T' that the builder will create instances of
+    JTypeVar instanceType = builderClass.generify("T", instanceClass);
+
+    // For new builders we need to create an instance variable and 'build' method
+    // for inheriting builders we'll receive these from the superType
+    if (parentBuilderClass == null) {
+
+      // Create the instance variable
+      JFieldVar instanceField = builderClass.field(JMod.PROTECTED, instanceType, "instance");
+
+      // Create the actual "build" method
+      JMethod buildMethod = builderClass.method(JMod.PUBLIC, instanceType, "build");
+
+      JBlock body = buildMethod.body();
+      JVar result = body.decl(instanceType, "result");
+      body.assign(result, JExpr._this().ref(instanceField));
+      body.assign(JExpr._this().ref(instanceField), JExpr._null());
+      body._return(result);
+
+      // Create the noargs builder constructor
+      generateNoArgsBuilderConstructor(instanceClass, builderClass);
+    } else {
+      // Declare the inheritance
+      builderClass._extends(parentBuilderClass);
+
+      // Create the noargs builder constructor
+      generateNoArgsBuilderConstructor(instanceClass, builderClass);
+    }
+
+    return builderClass;
+  }
+
+  private void generateNoArgsBuilderConstructor(JDefinedClass instanceClass, JDefinedClass builderClass) {
+    JMethod noargsConstructor = builderClass.constructor(JMod.PUBLIC);
+    JAnnotationUse warningSuppression = noargsConstructor.annotate(SuppressWarnings.class);
+    warningSuppression.param("value", "unchecked");
+
+    JBlock constructorBlock = noargsConstructor.body();
+
+    JFieldVar instanceField = reflectionHelper.searchClassAndSuperClassesForField("instance", builderClass);
+
+    // Determine if we need to invoke the super() method for our parent builder
+    JClass parentClass = builderClass._extends();
+    if (!(parentClass.isPrimitive() || reflectionHelper.isFinal(parentClass) || Objects.equals(parentClass.fullName(), "java.lang.Object"))) {
+      constructorBlock.invoke("super");
+    }
+
+    // Only initialize the instance if the object being constructed is actually this class
+    // if it's a subtype then ignore the instance initialization since the subclass will initialize it
+    constructorBlock.directStatement("// Skip initialization when called from subclass");
+    JInvocation comparison = JExpr._this().invoke("getClass").invoke("equals").arg(JExpr.dotclass(builderClass));
+    JConditional ifNotSubclass = constructorBlock._if(comparison);
+    ifNotSubclass._then().assign(JExpr._this().ref(instanceField), JExpr.cast(instanceField.type(), JExpr._new(instanceClass)));
+  }
+
+}
diff --git a/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/rules/ConstructorRule.java b/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/rules/ConstructorRule.java
new file mode 100644
index 00000000..a1fa614f
--- /dev/null
+++ b/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/rules/ConstructorRule.java
@@ -0,0 +1,240 @@
+/**
+ * Copyright Â© 2010-2017 Nokia
+ *
+ * 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 org.jsonschema2pojo.rules;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.sun.codemodel.JBlock;
+import com.sun.codemodel.JClass;
+import com.sun.codemodel.JConditional;
+import com.sun.codemodel.JDefinedClass;
+import com.sun.codemodel.JExpr;
+import com.sun.codemodel.JFieldVar;
+import com.sun.codemodel.JInvocation;
+import com.sun.codemodel.JMethod;
+import com.sun.codemodel.JMod;
+import com.sun.codemodel.JVar;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Objects;
+import java.util.Set;
+import org.jsonschema2pojo.GenerationConfig;
+import org.jsonschema2pojo.Schema;
+import org.jsonschema2pojo.util.NameHelper;
+import org.jsonschema2pojo.util.ReflectionHelper;
+
+public class ConstructorRule  implements Rule<JDefinedClass, JDefinedClass> {
+
+  private final RuleFactory ruleFactory;
+  private final ReflectionHelper reflectionHelper;
+
+  ConstructorRule(RuleFactory ruleFactory, ReflectionHelper reflectionHelper) {
+    this.ruleFactory = ruleFactory;
+    this.reflectionHelper = reflectionHelper;
+  }
+
+  @Override
+  public JDefinedClass apply(String nodeName, JsonNode node, JsonNode parent, JDefinedClass instanceClass, Schema currentSchema) {
+
+    GenerationConfig generationConfig = ruleFactory.getGenerationConfig();
+
+    // Determine which properties belong to that class (or its superType/parent)
+    LinkedHashSet<String> classProperties = getConstructorProperties(node, generationConfig.isConstructorsRequiredPropertiesOnly());
+    LinkedHashSet<String> combinedSuperProperties = getSuperTypeConstructorPropertiesRecursive(node, currentSchema, generationConfig.isConstructorsRequiredPropertiesOnly());
+
+    // no properties to put in the constructor => default constructor is good enough.
+    if (classProperties.isEmpty() && combinedSuperProperties.isEmpty()) {
+      return instanceClass;
+    }
+
+    // Generate the no arguments constructor
+    generateNoArgsConstructor(instanceClass);
+
+    // Generate the constructor with the properties which were located
+    JMethod instanceConstructor = generateFieldsConstructor(instanceClass, classProperties, combinedSuperProperties);
+
+    // If we're using InnerClassBuilder implementations then we also need to generate those
+    if (generationConfig.isGenerateBuilders() && generationConfig.isUseInnerClassBuilders()) {
+      JDefinedClass builderClass = ruleFactory.getReflectionHelper().getBuilderClass(instanceClass);
+      generateFieldsBuilderConstructor(builderClass, instanceClass, instanceConstructor);
+    }
+
+    return instanceClass;
+  }
+
+  /**
+   * Retrieve the list of properties to go in the constructor from node. This is all properties listed in node["properties"] if ! onlyRequired, and
+   * only required properties if onlyRequired.
+   */
+  private LinkedHashSet<String> getConstructorProperties(JsonNode node, boolean onlyRequired) {
+
+    if (!node.has("properties")) {
+      return new LinkedHashSet<>();
+    }
+
+    LinkedHashSet<String> rtn = new LinkedHashSet<>();
+    Set<String> draft4RequiredProperties = new HashSet<>();
+
+    // setup the set of required properties for draft4 style "required"
+    if (onlyRequired && node.has("required")) {
+      JsonNode requiredArray = node.get("required");
+      if (requiredArray.isArray()) {
+        for (JsonNode requiredEntry : requiredArray) {
+          if (requiredEntry.isTextual()) {
+            draft4RequiredProperties.add(requiredEntry.asText());
+          }
+        }
+      }
+    }
+
+    NameHelper nameHelper = ruleFactory.getNameHelper();
+    for (Iterator<Entry<String, JsonNode>> properties = node.get("properties").fields(); properties.hasNext(); ) {
+      Map.Entry<String, JsonNode> property = properties.next();
+
+      JsonNode propertyObj = property.getValue();
+      if (onlyRequired) {
+        // draft3 style
+        if (propertyObj.has("required") && propertyObj.get("required").asBoolean()) {
+          rtn.add(nameHelper.getPropertyName(property.getKey(), property.getValue()));
+        }
+
+        // draft4 style
+        if (draft4RequiredProperties.contains(property.getKey())) {
+          rtn.add(nameHelper.getPropertyName(property.getKey(), property.getValue()));
+        }
+      } else {
+        rtn.add(nameHelper.getPropertyName(property.getKey(), property.getValue()));
+      }
+    }
+    return rtn;
+  }
+
+  /**
+   * Recursive, walks the schema tree and assembles a list of all properties of this schema's super schemas
+   */
+  private LinkedHashSet<String> getSuperTypeConstructorPropertiesRecursive(JsonNode node, Schema schema, boolean onlyRequired) {
+    Schema superTypeSchema = reflectionHelper.getSuperSchema(node, schema, true);
+
+    if (superTypeSchema == null) {
+      return new LinkedHashSet<>();
+    }
+
+    JsonNode superSchemaNode = superTypeSchema.getContent();
+
+    LinkedHashSet<String> rtn = getConstructorProperties(superSchemaNode, onlyRequired);
+    rtn.addAll(getSuperTypeConstructorPropertiesRecursive(superSchemaNode, superTypeSchema, onlyRequired));
+
+    return rtn;
+  }
+
+  private void generateFieldsBuilderConstructor(JDefinedClass builderClass, JDefinedClass instanceClass, JMethod instanceConstructor) {
+    // Locate the instance field since we'll need it to assign a value
+    JFieldVar instanceField = reflectionHelper.searchClassAndSuperClassesForField("instance", builderClass);
+
+    // Create a new method to be the builder constructor we're defining
+    JMethod builderConstructor = builderClass.constructor(JMod.PUBLIC);
+    builderConstructor.annotate(SuppressWarnings.class).param("value", "unchecked");
+    JBlock constructorBlock = builderConstructor.body();
+
+    // The builder constructor should have the exact same parameters as the instanceConstructor
+    for(JVar param : instanceConstructor.params()) {
+      builderConstructor.param(param.type(), param.name());
+    }
+
+    // Determine if we need to invoke the super() method for our parent builder
+    JClass parentClass = builderClass._extends();
+    if (!(parentClass.isPrimitive() || reflectionHelper.isFinal(parentClass) || Objects.equals(parentClass.fullName(), "java.lang.Object"))) {
+      constructorBlock.invoke("super");
+    }
+
+    // The constructor invocation will also need all the parameters passed through
+    JInvocation instanceConstructorInvocation = JExpr._new(instanceClass);
+    for(JVar param : instanceConstructor.params()) {
+      instanceConstructorInvocation.arg(param);
+    }
+
+    // Only initialize the instance if the object being constructed is actually this class
+    // if it's a subtype then ignore the instance initialization since the subclass will initialize it
+    constructorBlock.directStatement("// Skip initialization when called from subclass");
+
+    JInvocation comparison = JExpr._this().invoke("getClass").invoke("equals").arg(JExpr.dotclass(builderClass));
+    JConditional ifNotSubclass = constructorBlock._if(comparison);
+    ifNotSubclass._then().assign(JExpr._this().ref(instanceField), JExpr.cast(instanceField.type(), instanceConstructorInvocation));
+  }
+
+  private JMethod generateFieldsConstructor(JDefinedClass jclass, LinkedHashSet<String> classProperties, LinkedHashSet<String> combinedSuperProperties) {
+    // add the public constructor with property parameters
+    JMethod fieldsConstructor = jclass.constructor(JMod.PUBLIC);
+    JBlock constructorBody = fieldsConstructor.body();
+    JInvocation superInvocation = constructorBody.invoke("super");
+
+    Map<String, JFieldVar> fields = jclass.fields();
+    Map<String, JVar> classFieldParams = new HashMap<>();
+
+    for (String property : classProperties) {
+      JFieldVar field = fields.get(property);
+
+      if (field == null) {
+        throw new IllegalStateException("Property " + property + " hasn't been added to JDefinedClass before calling addConstructors");
+      }
+
+      fieldsConstructor.javadoc().addParam(property);
+      JVar param = fieldsConstructor.param(field.type(), field.name());
+      constructorBody.assign(JExpr._this().ref(field), param);
+      classFieldParams.put(property, param);
+    }
+
+    List<JVar> superConstructorParams = new ArrayList<>();
+
+    for (String property : combinedSuperProperties) {
+      JFieldVar field = reflectionHelper.searchSuperClassesForField(property, jclass);
+
+      if (field == null) {
+        throw new IllegalStateException("Property " + property + " hasn't been added to JDefinedClass before calling addConstructors");
+      }
+
+      JVar param = classFieldParams.get(property);
+
+      if (param == null) {
+        param = fieldsConstructor.param(field.type(), field.name());
+      }
+
+      fieldsConstructor.javadoc().addParam(property);
+      superConstructorParams.add(param);
+    }
+
+    for (JVar param : superConstructorParams) {
+      superInvocation.arg(param);
+    }
+
+    return fieldsConstructor;
+  }
+
+  private void generateNoArgsConstructor(JDefinedClass jclass) {
+    // add a no-args constructor for serialization purposes
+    JMethod noargsConstructor = jclass.constructor(JMod.PUBLIC);
+    noargsConstructor.javadoc().add("No args constructor for use in serialization");
+  }
+
+
+
+
+}
diff --git a/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/rules/ObjectRule.java b/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/rules/ObjectRule.java
index 8ec98277..b193abba 100644
--- a/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/rules/ObjectRule.java
+++ b/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/rules/ObjectRule.java
@@ -13,9 +13,14 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 package org.jsonschema2pojo.rules;
 
+import static org.apache.commons.lang3.StringUtils.substringAfter;
+import static org.apache.commons.lang3.StringUtils.substringBefore;
+import static org.jsonschema2pojo.rules.PrimitiveTypes.isPrimitive;
+import static org.jsonschema2pojo.rules.PrimitiveTypes.primitiveType;
+import static org.jsonschema2pojo.util.TypeUtil.resolveType;
+
 import com.fasterxml.jackson.annotation.JsonTypeInfo;
 import com.fasterxml.jackson.databind.JsonNode;
 import com.sun.codemodel.ClassType;
@@ -29,40 +34,25 @@ import com.sun.codemodel.JExpr;
 import com.sun.codemodel.JExpression;
 import com.sun.codemodel.JFieldRef;
 import com.sun.codemodel.JFieldVar;
-import com.sun.codemodel.JInvocation;
 import com.sun.codemodel.JMethod;
 import com.sun.codemodel.JMod;
 import com.sun.codemodel.JOp;
 import com.sun.codemodel.JPackage;
 import com.sun.codemodel.JType;
 import com.sun.codemodel.JVar;
-
-import org.jsonschema2pojo.AnnotationStyle;
-import org.jsonschema2pojo.Schema;
-import org.jsonschema2pojo.exception.ClassAlreadyExistsException;
-import org.jsonschema2pojo.exception.GenerationException;
-import org.jsonschema2pojo.util.MakeUniqueClassName;
-import org.jsonschema2pojo.util.NameHelper;
-import org.jsonschema2pojo.util.ParcelableHelper;
-import org.jsonschema2pojo.util.SerializableHelper;
-
-import java.lang.reflect.Modifier;
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
-import java.util.LinkedHashSet;
-import java.util.List;
 import java.util.Map;
 import java.util.Set;
-
-import static org.apache.commons.lang3.StringUtils.capitalize;
-import static org.apache.commons.lang3.StringUtils.substringAfter;
-import static org.apache.commons.lang3.StringUtils.substringBefore;
-import static org.jsonschema2pojo.rules.PrimitiveTypes.isPrimitive;
-import static org.jsonschema2pojo.rules.PrimitiveTypes.primitiveType;
-import static org.jsonschema2pojo.util.TypeUtil.resolveType;
+import org.jsonschema2pojo.AnnotationStyle;
+import org.jsonschema2pojo.Schema;
+import org.jsonschema2pojo.exception.ClassAlreadyExistsException;
+import org.jsonschema2pojo.exception.GenerationException;
+import org.jsonschema2pojo.util.ParcelableHelper;
+import org.jsonschema2pojo.util.ReflectionHelper;
+import org.jsonschema2pojo.util.SerializableHelper;
 
 /**
  * Applies the generation steps required for schemas of type "object".
@@ -74,11 +64,13 @@ import static org.jsonschema2pojo.util.TypeUtil.resolveType;
 public class ObjectRule implements Rule<JPackage, JType> {
 
     private final RuleFactory ruleFactory;
+    private final ReflectionHelper reflectionHelper;
     private final ParcelableHelper parcelableHelper;
 
-    protected ObjectRule(RuleFactory ruleFactory, ParcelableHelper parcelableHelper) {
+    protected ObjectRule(RuleFactory ruleFactory, ParcelableHelper parcelableHelper, ReflectionHelper reflectionHelper) {
         this.ruleFactory = ruleFactory;
         this.parcelableHelper = parcelableHelper;
+        this.reflectionHelper = reflectionHelper;
     }
 
     /**
@@ -91,9 +83,8 @@ public class ObjectRule implements Rule<JPackage, JType> {
     @Override
     public JType apply(String nodeName, JsonNode node, JsonNode parent, JPackage _package, Schema schema) {
 
-        JType superType = getSuperType(nodeName, node, _package, schema);
-
-        if (superType.isPrimitive() || isFinal(superType)) {
+        JType superType = reflectionHelper.getSuperType(nodeName, node, _package, schema);
+        if (superType.isPrimitive() || reflectionHelper.isFinal(superType)) {
             return superType;
         }
 
@@ -120,6 +111,11 @@ public class ObjectRule implements Rule<JPackage, JType> {
             ruleFactory.getDescriptionRule().apply(nodeName, node.get("description"), node, jclass, schema);
         }
 
+        // Creates the class definition for the builder
+        if(ruleFactory.getGenerationConfig().isGenerateBuilders() && ruleFactory.getGenerationConfig().isUseInnerClassBuilders()){
+            ruleFactory.getBuilderRule().apply(nodeName, node, parent, jclass, schema);
+        }
+
         ruleFactory.getPropertiesRule().apply(nodeName, node.get("properties"), node, jclass, schema);
 
         if (node.has("javaInterfaces")) {
@@ -148,7 +144,8 @@ public class ObjectRule implements Rule<JPackage, JType> {
         }
 
         if (ruleFactory.getGenerationConfig().isIncludeConstructors()) {
-            addConstructors(jclass, node, schema, ruleFactory.getGenerationConfig().isConstructorsRequiredPropertiesOnly());
+            ruleFactory.getConstructorRule().apply(nodeName, node, parent, jclass, schema);
+
         }
 
         if (ruleFactory.getGenerationConfig().isSerializable()) {
@@ -173,74 +170,7 @@ public class ObjectRule implements Rule<JPackage, JType> {
         }
     }
 
-    /**
-     * Retrieve the list of properties to go in the constructor from node. This
-     * is all properties listed in node["properties"] if ! onlyRequired, and
-     * only required properties if onlyRequired.
-     *
-     * @param node
-     * @return
-     */
-    private LinkedHashSet<String> getConstructorProperties(JsonNode node, boolean onlyRequired) {
-
-        if (!node.has("properties")) {
-            return new LinkedHashSet<>();
-        }
-
-        LinkedHashSet<String> rtn = new LinkedHashSet<>();
-        Set<String> draft4RequiredProperties = new HashSet<>();
-
-        // setup the set of required properties for draft4 style "required"
-        if (onlyRequired && node.has("required")) {
-            JsonNode requiredArray =  node.get("required");
-            if (requiredArray.isArray()) {
-                for (JsonNode requiredEntry: requiredArray) {
-                    if (requiredEntry.isTextual()) {
-                        draft4RequiredProperties.add(requiredEntry.asText());
-                    }
-                }
-            }
-        }
-
-        NameHelper nameHelper = ruleFactory.getNameHelper();
-        for (Iterator<Map.Entry<String, JsonNode>> properties = node.get("properties").fields(); properties.hasNext();) {
-            Map.Entry<String, JsonNode> property = properties.next();
-
-            JsonNode propertyObj = property.getValue();
-            if (onlyRequired) {
-                // draft3 style
-                if (propertyObj.has("required") && propertyObj.get("required").asBoolean()) {
-                    rtn.add(nameHelper.getPropertyName(property.getKey(), property.getValue()));
-                }
-
-                // draft4 style
-                if (draft4RequiredProperties.contains(property.getKey())) {
-                    rtn.add(nameHelper.getPropertyName(property.getKey(), property.getValue()));
-                }
-            } else {
-                rtn.add(nameHelper.getPropertyName(property.getKey(), property.getValue()));
-            }
-        }
-        return rtn;
-    }
-
-    /**
-     * Recursive, walks the schema tree and assembles a list of all properties of this schema's super schemas
-     */
-    private LinkedHashSet<String> getSuperTypeConstructorPropertiesRecursive(JsonNode node, Schema schema, boolean onlyRequired) {
-        Schema superTypeSchema = getSuperSchema(node, schema, true);
-
-        if (superTypeSchema == null) {
-            return new LinkedHashSet<>();
-        }
-
-        JsonNode superSchemaNode = superTypeSchema.getContent();
 
-        LinkedHashSet<String> rtn = getConstructorProperties(superSchemaNode, onlyRequired);
-        rtn.addAll(getSuperTypeConstructorPropertiesRecursive(superSchemaNode, superTypeSchema, onlyRequired));
-
-        return rtn;
-    }
 
     /**
      * Creates a new Java class that will be generated.
@@ -301,9 +231,9 @@ public class ObjectRule implements Rule<JPackage, JType> {
                 }
             } else {
                 if (usePolymorphicDeserialization) {
-                    newType = _package._class(JMod.PUBLIC, getClassName(nodeName, node, _package), ClassType.CLASS);
+                    newType = _package._class(JMod.PUBLIC, ruleFactory.getNameHelper().getUniqueClassName(nodeName, node, _package), ClassType.CLASS);
                 } else {
-                    newType = _package._class(getClassName(nodeName, node, _package));
+                    newType = _package._class(ruleFactory.getNameHelper().getUniqueClassName(nodeName, node, _package));
                 }
             }
         } catch (JClassAlreadyExistsException e) {
@@ -316,60 +246,6 @@ public class ObjectRule implements Rule<JPackage, JType> {
 
     }
 
-    private boolean isFinal(JType superType) {
-        try {
-            Class<?> javaClass = Class.forName(superType.fullName());
-            return Modifier.isFinal(javaClass.getModifiers());
-        } catch (ClassNotFoundException e) {
-            return false;
-        }
-    }
-
-    private JType getSuperType(String nodeName, JsonNode node, JPackage jPackage, Schema schema) {
-        if (node.has("extends") && node.has("extendsJavaClass")) {
-            throw new IllegalStateException("'extends' and 'extendsJavaClass' defined simultaneously");
-        }
-
-        JType superType = jPackage.owner().ref(Object.class);
-        Schema superTypeSchema = getSuperSchema(node, schema, false);
-        if (superTypeSchema != null) {
-            superType = ruleFactory.getSchemaRule().apply(nodeName + "Parent", node.get("extends"), node, jPackage, superTypeSchema);
-        } else if (node.has("extendsJavaClass")) {
-            superType = resolveType(jPackage, node.get("extendsJavaClass").asText());
-        }
-
-        return superType;
-    }
-
-    private Schema getSuperSchema(JsonNode node, Schema schema, boolean followRefs) {
-        if (node.has("extends")) {
-            String path;
-            if (schema.getId().getFragment() == null) {
-                path = "#extends";
-            } else {
-                path = "#" + schema.getId().getFragment() + "/extends";
-            }
-
-            Schema superSchema = ruleFactory.getSchemaStore().create(schema, path, ruleFactory.getGenerationConfig().getRefFragmentPathDelimiters());
-
-            if (followRefs) {
-                superSchema = resolveSchemaRefsRecursive(superSchema);
-            }
-
-            return superSchema;
-        }
-        return null;
-    }
-
-    private Schema resolveSchemaRefsRecursive(Schema schema) {
-        JsonNode schemaNode = schema.getContent();
-        if (schemaNode.has("$ref")) {
-            schema = ruleFactory.getSchemaStore().create(schema, schemaNode.get("$ref").asText(), ruleFactory.getGenerationConfig().getRefFragmentPathDelimiters());
-            return resolveSchemaRefsRecursive(schema);
-        }
-        return schema;
-    }
-
     private void addJsonTypeInfoAnnotation(JDefinedClass jclass, JsonNode node) {
         if (ruleFactory.getGenerationConfig().getAnnotationStyle() == AnnotationStyle.JACKSON2) {
             String annotationName = node.get("deserializationClassProperty").asText();
@@ -575,98 +451,6 @@ public class ObjectRule implements Rule<JPackage, JType> {
         return filteredFields;
     }
 
-    private void addConstructors(JDefinedClass jclass, JsonNode node, Schema schema, boolean onlyRequired) {
-
-        LinkedHashSet<String> classProperties = getConstructorProperties(node, onlyRequired);
-        LinkedHashSet<String> combinedSuperProperties = getSuperTypeConstructorPropertiesRecursive(node, schema, onlyRequired);
-
-        // no properties to put in the constructor => default constructor is good enough.
-        if (classProperties.isEmpty() && combinedSuperProperties.isEmpty()) {
-            return;
-        }
-
-        // add a no-args constructor for serialization purposes
-        JMethod noargsConstructor = jclass.constructor(JMod.PUBLIC);
-        noargsConstructor.javadoc().add("No args constructor for use in serialization");
-
-        // add the public constructor with property parameters
-        JMethod fieldsConstructor = jclass.constructor(JMod.PUBLIC);
-        JBlock constructorBody = fieldsConstructor.body();
-        JInvocation superInvocation = constructorBody.invoke("super");
-
-        Map<String, JFieldVar> fields = jclass.fields();
-        Map<String, JVar> classFieldParams = new HashMap<>();
-
-        for (String property : classProperties) {
-            JFieldVar field = fields.get(property);
-
-            if (field == null) {
-                throw new IllegalStateException("Property " + property + " hasn't been added to JDefinedClass before calling addConstructors");
-            }
-
-            fieldsConstructor.javadoc().addParam(property);
-            JVar param = fieldsConstructor.param(field.type(), field.name());
-            constructorBody.assign(JExpr._this().ref(field), param);
-            classFieldParams.put(property, param);
-        }
-
-        List<JVar> superConstructorParams = new ArrayList<>();
-
-
-        for (String property : combinedSuperProperties) {
-            JFieldVar field = searchSuperClassesForField(property, jclass);
-
-            if (field == null) {
-                throw new IllegalStateException("Property " + property + " hasn't been added to JDefinedClass before calling addConstructors");
-            }
-
-            JVar param = classFieldParams.get(property);
-
-            if (param == null) {
-                param = fieldsConstructor.param(field.type(), field.name());
-            }
-
-            fieldsConstructor.javadoc().addParam(property);
-            superConstructorParams.add(param);
-        }
-
-        for (JVar param : superConstructorParams) {
-            superInvocation.arg(param);
-        }
-    }
-
-    private static JDefinedClass definedClassOrNullFromType(JType type)
-    {
-        if (type == null || type.isPrimitive())
-        {
-            return null;
-        }
-        JClass fieldClass = type.boxify();
-        JPackage jPackage = fieldClass._package();
-        return jPackage._getClass(fieldClass.name());
-    }
-
-    /**
-     * This is recursive with searchClassAndSuperClassesForField
-     */
-    private JFieldVar searchSuperClassesForField(String property, JDefinedClass jclass) {
-        JClass superClass = jclass._extends();
-        JDefinedClass definedSuperClass = definedClassOrNullFromType(superClass);
-        if (definedSuperClass == null) {
-            return null;
-        }
-        return searchClassAndSuperClassesForField(property, definedSuperClass);
-    }
-
-    private JFieldVar searchClassAndSuperClassesForField(String property, JDefinedClass jclass) {
-        Map<String, JFieldVar> fields = jclass.fields();
-        JFieldVar field = fields.get(property);
-        if (field == null) {
-            return searchSuperClassesForField(property, jclass);
-        }
-        return field;
-    }
-
     private void addEquals(JDefinedClass jclass, JsonNode node) {
         Map<String, JFieldVar> fields = removeFieldsExcludedFromEqualsAndHashCode(jclass.fields(), node);
 
@@ -736,41 +520,6 @@ public class ObjectRule implements Rule<JPackage, JType> {
         }
     }
 
-    private String getClassName(String nodeName, JsonNode node, JPackage _package) {
-        String prefix = ruleFactory.getGenerationConfig().getClassNamePrefix();
-        String suffix = ruleFactory.getGenerationConfig().getClassNameSuffix();
-        String fieldName = ruleFactory.getNameHelper().getClassName(nodeName, node);
-        String capitalizedFieldName = capitalize(fieldName);
-        String fullFieldName = createFullFieldName(capitalizedFieldName, prefix, suffix);
-
-        String className = ruleFactory.getNameHelper().replaceIllegalCharacters(fullFieldName);
-        String normalizedName = ruleFactory.getNameHelper().normalizeName(className);
-        return makeUnique(normalizedName, _package);
-    }
-
-    private String createFullFieldName(String nodeName, String prefix, String suffix) {
-        String returnString = nodeName;
-        if (prefix != null) {
-            returnString = prefix + returnString;
-        }
-
-        if (suffix != null) {
-            returnString = returnString + suffix;
-        }
-
-        return returnString;
-    }
-
-    private String makeUnique(String className, JPackage _package) {
-        try {
-            JDefinedClass _class = _package._class(className);
-            _package.remove(_class);
-            return className;
-        } catch (JClassAlreadyExistsException e) {
-            return makeUnique(MakeUniqueClassName.makeUnique(className), _package);
-        }
-    }
-
     private boolean usesPolymorphicDeserialization(JsonNode node) {
         if (ruleFactory.getGenerationConfig().getAnnotationStyle() == AnnotationStyle.JACKSON2) {
             return node.has("deserializationClassProperty");
diff --git a/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/rules/PropertiesRule.java b/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/rules/PropertiesRule.java
index 38f817ef..f58d2009 100644
--- a/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/rules/PropertiesRule.java
+++ b/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/rules/PropertiesRule.java
@@ -93,6 +93,7 @@ public class PropertiesRule implements Rule<JDefinedClass, JDefinedClass> {
 
     private void addOverrideBuilder(JDefinedClass thisJDefinedClass, JMethod parentBuilder, JVar parentParam) {
 
+        // Confirm that this class doesn't already have a builder method matching the same name as the parentBuilder
         if (thisJDefinedClass.getMethod(parentBuilder.name(), new JType[] {parentParam.type()}) == null) {
 
             JMethod builder = thisJDefinedClass.method(parentBuilder.mods().getValue(), thisJDefinedClass, parentBuilder.name());
diff --git a/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/rules/PropertyRule.java b/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/rules/PropertyRule.java
index d4490677..40f41580 100644
--- a/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/rules/PropertyRule.java
+++ b/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/rules/PropertyRule.java
@@ -17,12 +17,18 @@
 package org.jsonschema2pojo.rules;
 
 import com.fasterxml.jackson.databind.JsonNode;
-import com.sun.codemodel.*;
+import com.sun.codemodel.JBlock;
+import com.sun.codemodel.JDefinedClass;
+import com.sun.codemodel.JDocCommentable;
+import com.sun.codemodel.JExpr;
+import com.sun.codemodel.JFieldVar;
+import com.sun.codemodel.JMethod;
+import com.sun.codemodel.JMod;
+import com.sun.codemodel.JType;
+import com.sun.codemodel.JVar;
 import org.jsonschema2pojo.GenerationConfig;
 import org.jsonschema2pojo.Schema;
 
-import static org.apache.commons.lang3.StringUtils.capitalize;
-
 
 /**
  * Applies the schema rules that represent a property definition.
@@ -90,7 +96,7 @@ public class PropertyRule implements Rule<JDefinedClass, JDefinedClass> {
         }
 
         if (ruleFactory.getGenerationConfig().isGenerateBuilders()) {
-            addBuilder(jclass, field, nodeName, node);
+            addBuilderMethod(jclass, field, nodeName, node);
         }
 
         if (node.has("pattern")) {
@@ -235,27 +241,54 @@ public class PropertyRule implements Rule<JDefinedClass, JDefinedClass> {
         return setter;
     }
 
-    private JMethod addBuilder(JDefinedClass c, JFieldVar field, String jsonPropertyName, JsonNode node) {
-        JMethod builder = c.method(JMod.PUBLIC, c, getBuilderName(jsonPropertyName, node));
+  private JMethod addBuilderMethod(JDefinedClass c, JFieldVar field, String jsonPropertyName, JsonNode node) {
+    JMethod result = null;
+    if(ruleFactory.getGenerationConfig().isUseInnerClassBuilders()) {
+      result = addInnerBuilderMethod(c, field, jsonPropertyName, node);
+    } else {
+      result = addLegacyBuilder(c, field, jsonPropertyName, node);
+    }
+    return result;
+  }
 
-        JVar param = builder.param(field.type(), field.name());
-        JBlock body = builder.body();
-        body.assign(JExpr._this().ref(field), param);
-        body._return(JExpr._this());
+  private JMethod addLegacyBuilder(JDefinedClass c, JFieldVar field, String jsonPropertyName, JsonNode node) {
+    JMethod builder = c.method(JMod.PUBLIC, c, getBuilderName(jsonPropertyName, node));
 
-        return builder;
-    }
+    JVar param = builder.param(field.type(), field.name());
+    JBlock body = builder.body();
+    body.assign(JExpr._this().ref(field), param);
+    body._return(JExpr._this());
 
-    private String getBuilderName(String propertyName, JsonNode node) {
-        return ruleFactory.getNameHelper().getBuilderName(propertyName, node);
-    }
+    return builder;
+  }
 
-    private String getSetterName(String propertyName, JsonNode node) {
-        return ruleFactory.getNameHelper().getSetterName(propertyName, node);
-    }
+  private JMethod addInnerBuilderMethod(JDefinedClass c, JFieldVar field, String jsonPropertyName, JsonNode node)    {
+    JDefinedClass builderClass = ruleFactory.getReflectionHelper().getBuilderClass(c);
 
-    private String getGetterName(String propertyName, JType type, JsonNode node) {
-        return ruleFactory.getNameHelper().getGetterName(propertyName, type, node);
-    }
+    JMethod builderMethod = builderClass.method(JMod.PUBLIC, builderClass, getBuilderName(jsonPropertyName, node));
+
+    JVar param = builderMethod.param(field.type(), field.name());
+    JBlock body = builderMethod.body();
+    body.assign(JExpr.ref(JExpr.cast(c, JExpr._this().ref("instance")), field), param);
+    body._return(JExpr._this());
+
+    return builderMethod;
+  }
+
+  private String getBuilderClassName(JDefinedClass c) {
+    return ruleFactory.getNameHelper().getBuilderClassName(c);
+  }
+
+  private String getBuilderName(String propertyName, JsonNode node) {
+      return ruleFactory.getNameHelper().getBuilderName(propertyName, node);
+  }
+
+  private String getSetterName(String propertyName, JsonNode node) {
+      return ruleFactory.getNameHelper().getSetterName(propertyName, node);
+  }
+
+  private String getGetterName(String propertyName, JType type, JsonNode node) {
+      return ruleFactory.getNameHelper().getGetterName(propertyName, type, node);
+  }
 
 }
diff --git a/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/rules/RuleFactory.java b/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/rules/RuleFactory.java
index ca1342e2..64ce830f 100644
--- a/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/rules/RuleFactory.java
+++ b/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/rules/RuleFactory.java
@@ -32,6 +32,8 @@ import com.sun.codemodel.JDocCommentable;
 import com.sun.codemodel.JFieldVar;
 import com.sun.codemodel.JPackage;
 import com.sun.codemodel.JType;
+import org.jsonschema2pojo.util.ReflectionHelper;
+import sun.reflect.Reflection;
 
 /**
  * Provides factory/creation methods for the code generation rules.
@@ -39,6 +41,7 @@ import com.sun.codemodel.JType;
 public class RuleFactory {
 
     private NameHelper nameHelper;
+    private ReflectionHelper reflectionHelper;
     private GenerationConfig generationConfig;
     private Annotator annotator;
     private SchemaStore schemaStore;
@@ -61,6 +64,7 @@ public class RuleFactory {
         this.annotator = annotator;
         this.schemaStore = schemaStore;
         this.nameHelper = new NameHelper(generationConfig);
+        this.reflectionHelper = new ReflectionHelper(this);
     }
 
     /**
@@ -119,7 +123,17 @@ public class RuleFactory {
      * @return a schema rule that can handle the "object" declaration.
      */
     public Rule<JPackage, JType> getObjectRule() {
-        return new ObjectRule(this, new ParcelableHelper());
+        return new ObjectRule(this, new ParcelableHelper(), reflectionHelper);
+    }
+
+    /**
+     * Provides a rule instance that should be applied to add constructors to a generated type
+     *
+     * @return a schema rule that can handle the "object" declaration.
+     */
+    public Rule<JDefinedClass, JDefinedClass> getConstructorRule()
+    {
+        return new ConstructorRule(this, reflectionHelper);
     }
 
     /**
@@ -356,6 +370,11 @@ public class RuleFactory {
         return nameHelper;
     }
 
+    public ReflectionHelper getReflectionHelper()    {
+        return reflectionHelper;
+    }
+
+
     /**
      * Provides a rule instance that should be applied when a "media"
      * declaration is found in the schema.
@@ -374,6 +393,10 @@ public class RuleFactory {
         return new DynamicPropertiesRule(this);
     }
 
+    public Rule<JDefinedClass, JDefinedClass> getBuilderRule(){
+        return new BuilderRule(this, reflectionHelper);
+    }
+
     public Rule<JDocCommentable, JDocComment> getJavaNameRule() {
         return new JavaNameRule();
     }
diff --git a/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/rules/TypeRule.java b/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/rules/TypeRule.java
index 6a7c9c5a..92f958cb 100644
--- a/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/rules/TypeRule.java
+++ b/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/rules/TypeRule.java
@@ -1,17 +1,14 @@
 /**
  * Copyright © 2010-2017 Nokia
  *
- * 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
+ * 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
+ * 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.
+ * 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.jsonschema2pojo.rules;
@@ -33,155 +30,145 @@ import com.sun.codemodel.JType;
 /**
  * Applies the "type" schema rule.
  *
- * @see <a href=
- *      "http://tools.ietf.org/html/draft-zyp-json-schema-03#section-5.1">http:/
- *      /tools.ietf.org/html/draft-zyp-json-schema-03#section-5.1</a>
+ * @see <a href= "http://tools.ietf.org/html/draft-zyp-json-schema-03#section-5.1">http:/ /tools.ietf.org/html/draft-zyp-json-schema-03#section-5.1</a>
  */
 public class TypeRule implements Rule<JClassContainer, JType> {
 
-    private static final String DEFAULT_TYPE_NAME = "any";
-
-    private final RuleFactory ruleFactory;
-
-    protected TypeRule(RuleFactory ruleFactory) {
-        this.ruleFactory = ruleFactory;
+  private static final String DEFAULT_TYPE_NAME = "any";
+
+  private final RuleFactory ruleFactory;
+
+  protected TypeRule(RuleFactory ruleFactory) {
+    this.ruleFactory = ruleFactory;
+  }
+
+  /**
+   * Applies this schema rule to take the required code generation steps.
+   * <p>
+   * When applied, this rule reads the details of the given node to determine the appropriate Java type to return. This may be a newly generated type,
+   * it may be a primitive type or other type such as {@link java.lang.String} or {@link java.lang.Object}.
+   * <p>
+   * JSON schema types and their Java type equivalent:
+   * <ul>
+   * <li>"type":"any" =&gt; {@link java.lang.Object}
+   * <li>"type":"array" =&gt; Either {@link java.util.Set} or
+   * {@link java.util.List}, see {@link ArrayRule}
+   * <li>"type":"boolean" =&gt; <code>boolean</code>
+   * <li>"type":"integer" =&gt; <code>int</code>
+   * <li>"type":"null" =&gt; {@link java.lang.Object}
+   * <li>"type":"number" =&gt; <code>double</code>
+   * <li>"type":"object" =&gt; Generated type (see {@link ObjectRule})
+   * <li>"type":"string" =&gt; {@link java.lang.String} (or alternative based
+   * on presence of "format", see {@link FormatRule})
+   * </ul>
+   *
+   * @param nodeName the name of the node for which this "type" rule applies
+   * @param node the node for which this "type" rule applies
+   * @param parent the parent node
+   * @param jClassContainer the package into which any newly generated type may be placed
+   * @return the Java type which, after reading the details of the given schema node, most appropriately matches the "type" specified
+   */
+  @Override
+  public JType apply(String nodeName, JsonNode node, JsonNode parent, JClassContainer jClassContainer, Schema schema) {
+
+    String propertyTypeName = getTypeName(node);
+
+    JType type;
+
+    if (propertyTypeName.equals("object") || node.has("properties") && node.path("properties").size() > 0) {
+
+      type = ruleFactory.getObjectRule().apply(nodeName, node, parent, jClassContainer.getPackage(), schema);
+    } else if (node.has("existingJavaType")) {
+      String typeName = node.path("existingJavaType").asText();
+
+      if (isPrimitive(typeName, jClassContainer.owner())) {
+        type = primitiveType(typeName, jClassContainer.owner());
+      } else {
+        type = resolveType(jClassContainer, typeName);
+      }
+    } else if (propertyTypeName.equals("string")) {
+
+      type = jClassContainer.owner().ref(String.class);
+    } else if (propertyTypeName.equals("number")) {
+
+      type = getNumberType(jClassContainer.owner(), ruleFactory.getGenerationConfig());
+    } else if (propertyTypeName.equals("integer")) {
+
+      type = getIntegerType(jClassContainer.owner(), node, ruleFactory.getGenerationConfig());
+    } else if (propertyTypeName.equals("boolean")) {
+
+      type = unboxIfNecessary(jClassContainer.owner().ref(Boolean.class), ruleFactory.getGenerationConfig());
+    } else if (propertyTypeName.equals("array")) {
+
+      type = ruleFactory.getArrayRule().apply(nodeName, node, parent, jClassContainer.getPackage(), schema);
+    } else {
+
+      type = jClassContainer.owner().ref(Object.class);
     }
 
-    /**
-     * Applies this schema rule to take the required code generation steps.
-     * <p>
-     * When applied, this rule reads the details of the given node to determine
-     * the appropriate Java type to return. This may be a newly generated type,
-     * it may be a primitive type or other type such as {@link java.lang.String}
-     * or {@link java.lang.Object}.
-     * <p>
-     * JSON schema types and their Java type equivalent:
-     * <ul>
-     * <li>"type":"any" =&gt; {@link java.lang.Object}
-     * <li>"type":"array" =&gt; Either {@link java.util.Set} or
-     * {@link java.util.List}, see {@link ArrayRule}
-     * <li>"type":"boolean" =&gt; <code>boolean</code>
-     * <li>"type":"integer" =&gt; <code>int</code>
-     * <li>"type":"null" =&gt; {@link java.lang.Object}
-     * <li>"type":"number" =&gt; <code>double</code>
-     * <li>"type":"object" =&gt; Generated type (see {@link ObjectRule})
-     * <li>"type":"string" =&gt; {@link java.lang.String} (or alternative based
-     * on presence of "format", see {@link FormatRule})
-     * </ul>
-     *
-     * @param nodeName
-     *            the name of the node for which this "type" rule applies
-     * @param node
-     *            the node for which this "type" rule applies
-     * @param parent
-     *            the parent node
-     * @param jClassContainer
-     *            the package into which any newly generated type may be placed
-     * @return the Java type which, after reading the details of the given
-     *         schema node, most appropriately matches the "type" specified
-     */
-    @Override
-    public JType apply(String nodeName, JsonNode node, JsonNode parent, JClassContainer jClassContainer, Schema schema) {
-
-        String propertyTypeName = getTypeName(node);
-
-        JType type;
-
-        if (propertyTypeName.equals("object") || node.has("properties") && node.path("properties").size() > 0) {
-
-            type = ruleFactory.getObjectRule().apply(nodeName, node, parent, jClassContainer.getPackage(), schema);
-        } else if (node.has("existingJavaType")) {
-            String typeName = node.path("existingJavaType").asText();
-
-            if (isPrimitive(typeName, jClassContainer.owner())) {
-                type = primitiveType(typeName, jClassContainer.owner());
-            } else {
-                type = resolveType(jClassContainer, typeName);
-            }
-        } else if (propertyTypeName.equals("string")) {
-
-            type = jClassContainer.owner().ref(String.class);
-        } else if (propertyTypeName.equals("number")) {
-
-            type = getNumberType(jClassContainer.owner(), ruleFactory.getGenerationConfig());
-        } else if (propertyTypeName.equals("integer")) {
-
-            type = getIntegerType(jClassContainer.owner(), node, ruleFactory.getGenerationConfig());
-        } else if (propertyTypeName.equals("boolean")) {
-
-            type = unboxIfNecessary(jClassContainer.owner().ref(Boolean.class), ruleFactory.getGenerationConfig());
-        } else if (propertyTypeName.equals("array")) {
-
-            type = ruleFactory.getArrayRule().apply(nodeName, node, parent, jClassContainer.getPackage(), schema);
-        } else {
-
-            type = jClassContainer.owner().ref(Object.class);
-        }
-
-        if (!node.has("javaType") && !node.has("existingJavaType") && node.has("format")) {
-            type = ruleFactory.getFormatRule().apply(nodeName, node.get("format"), node, type, schema);
-        } else if (!node.has("javaType") && !node.has("existingJavaType") && propertyTypeName.equals("string") && node.has("media")) {
-            type = ruleFactory.getMediaRule().apply(nodeName, node.get("media"), node, type, schema);
-        }
-
-        return type;
+    if (!node.has("javaType") && !node.has("existingJavaType") && node.has("format")) {
+      type = ruleFactory.getFormatRule().apply(nodeName, node.get("format"), node, type, schema);
+    } else if (!node.has("javaType") && !node.has("existingJavaType") && propertyTypeName.equals("string") && node.has("media")) {
+      type = ruleFactory.getMediaRule().apply(nodeName, node.get("media"), node, type, schema);
     }
 
-    private String getTypeName(JsonNode node) {
-        if (node.has("type") && node.get("type").isArray() && node.get("type").size() > 0) {
-            for (JsonNode jsonNode : node.get("type")) {
-                String typeName = jsonNode.asText();
-                if (!typeName.equals("null")) {
-                    return typeName;
-                }
-            }
-        }
+    return type;
+  }
 
-        if (node.has("type") && node.get("type").isTextual()) {
-            return node.get("type").asText();
+  private String getTypeName(JsonNode node) {
+    if (node.has("type") && node.get("type").isArray() && node.get("type").size() > 0) {
+      for (JsonNode jsonNode : node.get("type")) {
+        String typeName = jsonNode.asText();
+        if (!typeName.equals("null")) {
+          return typeName;
         }
-
-        return DEFAULT_TYPE_NAME;
+      }
     }
 
-    private JType unboxIfNecessary(JType type, GenerationConfig config) {
-        if (config.isUsePrimitives()) {
-            return type.unboxify();
-        } else {
-            return type;
-        }
+    if (node.has("type") && node.get("type").isTextual()) {
+      return node.get("type").asText();
     }
 
-    /**
-     * Returns the JType for an integer field. Handles type lookup and unboxing.
-     */
-    private JType getIntegerType(JCodeModel owner, JsonNode node, GenerationConfig config) {
-
-        if (config.isUseBigIntegers()) {
-            return unboxIfNecessary(owner.ref(BigInteger.class), config);
-        } else if (config.isUseLongIntegers() ||
-                node.has("minimum") && node.get("minimum").isLong() ||
-                node.has("maximum") && node.get("maximum").isLong()) {
-            return unboxIfNecessary(owner.ref(Long.class), config);
-        } else {
-            return unboxIfNecessary(owner.ref(Integer.class), config);
-        }
+    return DEFAULT_TYPE_NAME;
+  }
 
+  private JType unboxIfNecessary(JType type, GenerationConfig config) {
+    if (config.isUsePrimitives()) {
+      return type.unboxify();
+    } else {
+      return type;
+    }
+  }
+
+  /**
+   * Returns the JType for an integer field. Handles type lookup and unboxing.
+   */
+  private JType getIntegerType(JCodeModel owner, JsonNode node, GenerationConfig config) {
+
+    if (config.isUseBigIntegers()) {
+      return unboxIfNecessary(owner.ref(BigInteger.class), config);
+    } else if (config.isUseLongIntegers() || node.has("minimum") && node.get("minimum").isLong() || node.has("maximum") && node.get("maximum")
+        .isLong()) {
+      return unboxIfNecessary(owner.ref(Long.class), config);
+    } else {
+      return unboxIfNecessary(owner.ref(Integer.class), config);
     }
 
-    /**
-     * Returns the JType for a number field. Handles type lookup and unboxing.
-     */
-    private JType getNumberType(JCodeModel owner, GenerationConfig config) {
-
-        if (config.isUseBigDecimals()) {
-            return unboxIfNecessary(owner.ref(BigDecimal.class), config);
-        } else if (config.isUseDoubleNumbers()) {
-            return unboxIfNecessary(owner.ref(Double.class), config);
-        } else {
-            return unboxIfNecessary(owner.ref(Float.class), config);
-        }
+  }
+
+  /**
+   * Returns the JType for a number field. Handles type lookup and unboxing.
+   */
+  private JType getNumberType(JCodeModel owner, GenerationConfig config) {
 
+    if (config.isUseBigDecimals()) {
+      return unboxIfNecessary(owner.ref(BigDecimal.class), config);
+    } else if (config.isUseDoubleNumbers()) {
+      return unboxIfNecessary(owner.ref(Double.class), config);
+    } else {
+      return unboxIfNecessary(owner.ref(Float.class), config);
     }
 
+  }
+
 }
diff --git a/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/util/NameHelper.java b/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/util/NameHelper.java
index d3a4cb4d..0a519ab3 100644
--- a/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/util/NameHelper.java
+++ b/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/util/NameHelper.java
@@ -16,15 +16,21 @@
 
 package org.jsonschema2pojo.util;
 
-import static java.lang.Character.*;
-import static javax.lang.model.SourceVersion.*;
-import static org.apache.commons.lang3.StringUtils.*;
-
-import org.apache.commons.lang3.text.WordUtils;
-import org.jsonschema2pojo.GenerationConfig;
+import static java.lang.Character.isDigit;
+import static java.lang.Character.toLowerCase;
+import static javax.lang.model.SourceVersion.isKeyword;
+import static org.apache.commons.lang3.StringUtils.capitalize;
+import static org.apache.commons.lang3.StringUtils.containsAny;
+import static org.apache.commons.lang3.StringUtils.remove;
 
 import com.fasterxml.jackson.databind.JsonNode;
+import com.sun.codemodel.JClass;
+import com.sun.codemodel.JClassAlreadyExistsException;
+import com.sun.codemodel.JDefinedClass;
+import com.sun.codemodel.JPackage;
 import com.sun.codemodel.JType;
+import org.apache.commons.lang3.text.WordUtils;
+import org.jsonschema2pojo.GenerationConfig;
 
 public class NameHelper {
 
@@ -213,4 +219,46 @@ public class NameHelper {
         jsonPropertyName = capitalizeTrailingWords(jsonPropertyName);
         return jsonPropertyName;
     }
+
+    public String getBuilderClassName(JClass outterClass) {
+        return outterClass.name() + "Builder";
+    }
+
+    public String getUniqueClassName(String nodeName, JsonNode node, JPackage _package) {
+        return makeUnique(getClassName(nodeName, node, _package), _package);
+    }
+
+    public String getClassName(String nodeName, JsonNode node, JPackage _package) {
+        String prefix = generationConfig.getClassNamePrefix();
+        String suffix = generationConfig.getClassNameSuffix();
+        String fieldName = getClassName(nodeName, node);
+        String capitalizedFieldName = capitalize(fieldName);
+        String fullFieldName = createFullFieldName(capitalizedFieldName, prefix, suffix);
+
+        String className = replaceIllegalCharacters(fullFieldName);
+        return normalizeName(className);
+    }
+
+    private String createFullFieldName(String nodeName, String prefix, String suffix) {
+        String returnString = nodeName;
+        if (prefix != null) {
+            returnString = prefix + returnString;
+        }
+
+        if (suffix != null) {
+            returnString = returnString + suffix;
+        }
+
+        return returnString;
+    }
+
+    private String makeUnique(String className, JPackage _package) {
+        try {
+            JDefinedClass _class = _package._class(className);
+            _package.remove(_class);
+            return className;
+        } catch (JClassAlreadyExistsException e) {
+            return makeUnique(MakeUniqueClassName.makeUnique(className), _package);
+        }
+    }
 }
diff --git a/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/util/ReflectionHelper.java b/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/util/ReflectionHelper.java
new file mode 100644
index 00000000..4fff8313
--- /dev/null
+++ b/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/util/ReflectionHelper.java
@@ -0,0 +1,162 @@
+/**
+ * Copyright Â© 2010-2017 Nokia
+ *
+ * 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 org.jsonschema2pojo.util;
+
+import static org.jsonschema2pojo.util.TypeUtil.resolveType;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.sun.codemodel.JClass;
+import com.sun.codemodel.JDefinedClass;
+import com.sun.codemodel.JFieldVar;
+import com.sun.codemodel.JPackage;
+import com.sun.codemodel.JType;
+import java.lang.reflect.Modifier;
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Spliterator;
+import java.util.Spliterators;
+import java.util.stream.StreamSupport;
+import org.jsonschema2pojo.Schema;
+import org.jsonschema2pojo.rules.RuleFactory;
+
+public class ReflectionHelper {
+
+  private RuleFactory ruleFactory;
+
+  public ReflectionHelper(RuleFactory ruleFactory) {
+    this.ruleFactory = ruleFactory;
+  }
+
+  public JType getSuperType(String nodeName, JsonNode node, JPackage jPackage, Schema schema) {
+    if (node.has("extends") && node.has("extendsJavaClass")) {
+      throw new IllegalStateException("'extends' and 'extendsJavaClass' defined simultaneously");
+    }
+
+    JType superType = jPackage.owner().ref(Object.class);
+    Schema superTypeSchema = getSuperSchema(node, schema, false);
+    if (superTypeSchema != null) {
+      superType = ruleFactory.getSchemaRule().apply(nodeName + "Parent", node.get("extends"), node, jPackage, superTypeSchema);
+    } else if (node.has("extendsJavaClass")) {
+      superType = resolveType(jPackage, node.get("extendsJavaClass").asText());
+    }
+
+    return superType;
+  }
+
+  public Schema getSuperSchema(JsonNode node, Schema schema, boolean followRefs) {
+    if (node.has("extends")) {
+      String path;
+      if (schema.getId().getFragment() == null) {
+        path = "#extends";
+      } else {
+        path = "#" + schema.getId().getFragment() + "/extends";
+      }
+
+      Schema superSchema = ruleFactory.getSchemaStore().create(schema, path, ruleFactory.getGenerationConfig().getRefFragmentPathDelimiters());
+
+      if (followRefs) {
+        superSchema = resolveSchemaRefsRecursive(superSchema);
+      }
+
+      return superSchema;
+    }
+    return null;
+  }
+
+  /**
+   * This is recursive with searchClassAndSuperClassesForField
+   */
+  public JFieldVar searchSuperClassesForField(String property, JDefinedClass jclass) {
+    JClass superClass = jclass._extends();
+    JDefinedClass definedSuperClass = definedClassOrNullFromType(superClass);
+    if (definedSuperClass == null) {
+      return null;
+    }
+    return searchClassAndSuperClassesForField(property, definedSuperClass);
+  }
+
+  public JDefinedClass getBuilderClass(JDefinedClass target) {
+    String builderClassname = ruleFactory.getNameHelper().getBuilderClassName(target);
+
+    return StreamSupport.stream(Spliterators.spliteratorUnknownSize(target.classes(), Spliterator.ORDERED), false)
+        .filter(definedClass -> definedClass.name().equals(builderClassname)).findFirst().orElse(null);
+  }
+
+  public JDefinedClass getBuilderClass(JClass target) {
+    String builderClassname = ruleFactory.getNameHelper().getBuilderClassName(target);
+    return getAllPackageClasses(target._package()).stream().filter(definedClass -> definedClass.name().equals(builderClassname)).findFirst()
+        .orElse(null);
+  }
+
+  public boolean isFinal(JType superType) {
+    try {
+      Class<?> javaClass = Class.forName(superType.fullName());
+      return Modifier.isFinal(javaClass.getModifiers());
+    } catch (ClassNotFoundException e) {
+      return false;
+    }
+  }
+
+  public JFieldVar searchClassAndSuperClassesForField(String property, JDefinedClass jclass) {
+    Map<String, JFieldVar> fields = jclass.fields();
+    JFieldVar field = fields.get(property);
+    if (field == null) {
+      return searchSuperClassesForField(property, jclass);
+    }
+    return field;
+  }
+
+  private JDefinedClass definedClassOrNullFromType(JType type) {
+    if (type == null || type.isPrimitive()) {
+      return null;
+    }
+    JClass fieldClass = type.boxify();
+    JPackage jPackage = fieldClass._package();
+    return this._getClass(fieldClass.name(), jPackage);
+  }
+
+  private JDefinedClass _getClass(String name, JPackage _package) {
+    return getAllPackageClasses(_package).stream().filter(definedClass -> definedClass.name().equals(name)).findFirst()
+        .orElseThrow(() -> new NoClassDefFoundError(name));
+  }
+
+  private Collection<JDefinedClass> getAllPackageClasses(JPackage _package) {
+    LinkedList<JDefinedClass> result = new LinkedList<>();
+    StreamSupport.stream(Spliterators.spliteratorUnknownSize(_package.classes(), Spliterator.ORDERED), false)
+        .forEach(_class -> result.addAll(getAllClassClasses(_class)));
+    return result;
+  }
+
+  private Collection<JDefinedClass> getAllClassClasses(JDefinedClass _class) {
+    LinkedList<JDefinedClass> result = new LinkedList<>();
+    result.add(_class);
+
+    _class.classes().forEachRemaining(result::add);
+    return result;
+  }
+
+  private Schema resolveSchemaRefsRecursive(Schema schema) {
+    JsonNode schemaNode = schema.getContent();
+    if (schemaNode.has("$ref")) {
+      schema = ruleFactory.getSchemaStore()
+          .create(schema, schemaNode.get("$ref").asText(), ruleFactory.getGenerationConfig().getRefFragmentPathDelimiters());
+      return resolveSchemaRefsRecursive(schema);
+    }
+    return schema;
+  }
+
+}
diff --git a/jsonschema2pojo-gradle-plugin/pom.xml b/jsonschema2pojo-gradle-plugin/pom.xml
index 5f0df0b2..7eea2678 100644
--- a/jsonschema2pojo-gradle-plugin/pom.xml
+++ b/jsonschema2pojo-gradle-plugin/pom.xml
@@ -5,7 +5,7 @@
     <parent>
         <artifactId>jsonschema2pojo</artifactId>
         <groupId>org.jsonschema2pojo</groupId>
-        <version>1.0.1-SNAPSHOT</version>
+        <version>1.0.0-beta2-SNAPSHOT</version>
     </parent>
 
     <artifactId>jsonschema2pojo-gradle-plugin</artifactId>
diff --git a/jsonschema2pojo-gradle-plugin/src/main/groovy/org/jsonschema2pojo/gradle/JsonSchemaExtension.groovy b/jsonschema2pojo-gradle-plugin/src/main/groovy/org/jsonschema2pojo/gradle/JsonSchemaExtension.groovy
index 55c68b79..4b570103 100644
--- a/jsonschema2pojo-gradle-plugin/src/main/groovy/org/jsonschema2pojo/gradle/JsonSchemaExtension.groovy
+++ b/jsonschema2pojo-gradle-plugin/src/main/groovy/org/jsonschema2pojo/gradle/JsonSchemaExtension.groovy
@@ -45,6 +45,7 @@ public class JsonSchemaExtension implements GenerationConfig {
   Class<? extends Annotator> customAnnotator
   Class<? extends RuleFactory> customRuleFactory
   boolean generateBuilders
+  boolean useInnerClassBuilders
   boolean includeGetters
   boolean includeSetters
   boolean includeAdditionalProperties
@@ -95,6 +96,7 @@ public class JsonSchemaExtension implements GenerationConfig {
   public JsonSchemaExtension() {
     // See DefaultGenerationConfig
     generateBuilders = false
+    useInnerClassBuilders = false
     usePrimitives = false
     sourceFiles = []
     targetPackage = ''
@@ -254,6 +256,7 @@ public class JsonSchemaExtension implements GenerationConfig {
        |sourceSortOrder = ${sourceSortOrder}
        |targetLanguage = ${targetLanguage}
        |formatTypeMapping = ${formatTypeMapping}
+       |useInnerClassBuilders = ${useInnerClassBuilders}
      """.stripMargin()
   }
   
diff --git a/jsonschema2pojo-maven-plugin/pom.xml b/jsonschema2pojo-maven-plugin/pom.xml
index 825af4da..3092044f 100644
--- a/jsonschema2pojo-maven-plugin/pom.xml
+++ b/jsonschema2pojo-maven-plugin/pom.xml
@@ -5,7 +5,7 @@
     <parent>
         <artifactId>jsonschema2pojo</artifactId>
         <groupId>org.jsonschema2pojo</groupId>
-        <version>1.0.1-SNAPSHOT</version>
+        <version>1.0.0-beta2-SNAPSHOT</version>
     </parent>
 
     <artifactId>jsonschema2pojo-maven-plugin</artifactId>
diff --git a/jsonschema2pojo-maven-plugin/src/main/java/org/jsonschema2pojo/maven/Jsonschema2PojoMojo.java b/jsonschema2pojo-maven-plugin/src/main/java/org/jsonschema2pojo/maven/Jsonschema2PojoMojo.java
index b049cd4a..4a065a3b 100644
--- a/jsonschema2pojo-maven-plugin/src/main/java/org/jsonschema2pojo/maven/Jsonschema2PojoMojo.java
+++ b/jsonschema2pojo-maven-plugin/src/main/java/org/jsonschema2pojo/maven/Jsonschema2PojoMojo.java
@@ -745,6 +745,14 @@ public class Jsonschema2PojoMojo extends AbstractMojo implements GenerationConfi
      */
     private Map<String, String> formatTypeMapping = new HashMap<>();
 
+    /**
+     * @parameter property="jsonschema2pojo.useInnerClassBuilders"
+     *            default-value="false"
+     * @since 1.0.0
+     */
+    private boolean useInnerClassBuilders = false;
+
+
     /**
      * Executes the plugin, to read the given source and behavioural properties
      * and generate POJOs. The current implementation acts as a wrapper around
@@ -1159,4 +1167,9 @@ public class Jsonschema2PojoMojo extends AbstractMojo implements GenerationConfi
     public Map<String, String> getFormatTypeMapping() {
         return formatTypeMapping;
     }
+
+    @Override
+    public boolean isUseInnerClassBuilders() {
+        return useInnerClassBuilders;
+    }
 }
diff --git a/jsonschema2pojo-scalagen/pom.xml b/jsonschema2pojo-scalagen/pom.xml
index 4a289dee..0dc40eb6 100644
--- a/jsonschema2pojo-scalagen/pom.xml
+++ b/jsonschema2pojo-scalagen/pom.xml
@@ -6,12 +6,12 @@
     <parent>
         <artifactId>jsonschema2pojo</artifactId>
         <groupId>org.jsonschema2pojo</groupId>
-        <version>1.0.1-SNAPSHOT</version>
+        <version>1.0.0-beta2-SNAPSHOT</version>
     </parent>
 
     <groupId>org.jsonschema2pojo</groupId>
     <artifactId>jsonschema2pojo-scalagen</artifactId>
-    <version>1.0.1-SNAPSHOT</version>
+    <version>1.0.0-beta2-SNAPSHOT</version>
 
     <properties>
         <commons.collections.version>4.01</commons.collections.version>
diff --git a/pom.xml b/pom.xml
index 0bace5a4..199bc919 100644
--- a/pom.xml
+++ b/pom.xml
@@ -10,7 +10,7 @@
 
     <groupId>org.jsonschema2pojo</groupId>
     <artifactId>jsonschema2pojo</artifactId>
-    <version>1.0.1-SNAPSHOT</version>
+    <version>1.0.0-beta2-SNAPSHOT</version>
 
     <packaging>pom</packaging>
     <name>jsonschema2pojo</name>


Test patch:
diff --git a/jsonschema2pojo-core/src/test/resources/schema/child.json b/jsonschema2pojo-core/src/test/resources/schema/child.json
new file mode 100644
index 00000000..7c8c259b
--- /dev/null
+++ b/jsonschema2pojo-core/src/test/resources/schema/child.json
@@ -0,0 +1,15 @@
+{
+  "$id": "https://example.com/child.schema.json",
+  "$schema": "http://json-schema.org/draft-07/schema#",
+  "description" : "A schema definition for an object which can be inherited",
+  "javaType" : "com.example.package.GeneratedChildType",
+  "type" : "object",
+  "extends" : {
+    "$ref" : "parent.json"
+  },
+  "properties" : {
+    "childProperty" : { "type" : "string" }
+  },
+  "dependencies" : {
+  }
+}
\ No newline at end of file
diff --git a/jsonschema2pojo-core/src/test/resources/schema/parent.json b/jsonschema2pojo-core/src/test/resources/schema/parent.json
new file mode 100644
index 00000000..f2a9b7d7
--- /dev/null
+++ b/jsonschema2pojo-core/src/test/resources/schema/parent.json
@@ -0,0 +1,12 @@
+{
+  "$id": "https://example.com/address.schema.json",
+  "$schema": "http://json-schema.org/draft-07/schema#",
+  "description" : "A schema definition for an object which can be inherited",
+  "javaType" : "com.example.package.GeneratedParentType",
+  "type" : "object",
+  "properties" : {
+    "parentProperty" : { "type" : "string" }
+  },
+  "dependencies" : {
+  }
+}
\ No newline at end of file
diff --git a/jsonschema2pojo-integration-tests/pom.xml b/jsonschema2pojo-integration-tests/pom.xml
index 0c92facb..2b911cd1 100644
--- a/jsonschema2pojo-integration-tests/pom.xml
+++ b/jsonschema2pojo-integration-tests/pom.xml
@@ -5,7 +5,7 @@
     <parent>
         <artifactId>jsonschema2pojo</artifactId>
         <groupId>org.jsonschema2pojo</groupId>
-        <version>1.0.1-SNAPSHOT</version>
+        <version>1.0.0-beta2-SNAPSHOT</version>
     </parent>
 
     <artifactId>jsonschema2pojo-integration-tests</artifactId>
diff --git a/jsonschema2pojo-integration-tests/src/test/java/org/jsonschema2pojo/integration/config/UseInnerClassBuildersIT.java b/jsonschema2pojo-integration-tests/src/test/java/org/jsonschema2pojo/integration/config/UseInnerClassBuildersIT.java
new file mode 100644
index 00000000..680fd2fb
--- /dev/null
+++ b/jsonschema2pojo-integration-tests/src/test/java/org/jsonschema2pojo/integration/config/UseInnerClassBuildersIT.java
@@ -0,0 +1,227 @@
+/**
+ * Copyright © 2010-2017 Nokia
+ *
+ * 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 org.jsonschema2pojo.integration.config;
+
+import static org.hamcrest.Matchers.not;
+import static org.jsonschema2pojo.integration.util.CodeGenerationHelper.config;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.stream.Stream;
+import org.apache.commons.lang.StringUtils;
+import org.hamcrest.Matcher;
+import org.jsonschema2pojo.integration.util.FileSearchMatcher;
+import org.jsonschema2pojo.integration.util.Jsonschema2PojoRule;
+import org.junit.Rule;
+import org.junit.Test;
+
+@SuppressWarnings("rawtypes")
+public class UseInnerClassBuildersIT {
+
+  @Rule
+  public Jsonschema2PojoRule schemaRule = new Jsonschema2PojoRule();
+
+  private static Matcher<File> containsText(String searchText) {
+    return new FileSearchMatcher(searchText);
+  }
+
+  /**
+   * This test asserts that no methods containing 'with' appear in the generated code using the default configuration
+   */
+  @Test
+  public void noBuilderMethodsByDefault() {
+    File outputDirectory = schemaRule.generate("/schema.useInnerClassBuilders/child.json", "com.example", config());
+
+    assertThat(outputDirectory, not(containsText("with")));
+  }
+
+  /**
+   * This method confirms that if you choose to generate builders, but don't indicate that useInnerBuilders is true, they will be generated using the
+   * chaining setters instead of the inner classes
+   */
+  @Test(expected = ClassNotFoundException.class)
+  public void defaultBuilderIsChainedSetters() throws ClassNotFoundException {
+    ClassLoader resultsClassLoader = schemaRule.generateAndCompile("/schema.useInnerClassBuilders/child.json", "com.example",
+        config("generateBuilders", true));
+
+    Class<?> childClass = resultsClassLoader.loadClass("com.example.Child");
+    boolean containsWithMethod = Stream.of(childClass.getMethods())
+        .map(Method::getName)
+        .anyMatch(methodName -> StringUtils.contains(methodName, "with"));
+
+    assertTrue("Generated class missing any builders at all", containsWithMethod);
+
+    resultsClassLoader.loadClass("com.example.Child.ChildBuilder");
+  }
+
+  /**
+   * This method confirms that if you choose to use inner class builders then the chaining setters will be removed from the generated class
+   */
+  @Test
+  public void innerBuildersRemoveChainedSetters() throws ClassNotFoundException {
+    ClassLoader resultsClassLoader = schemaRule.generateAndCompile("/schema.useInnerClassBuilders/child.json", "com.example",
+        config("generateBuilders", true, "useInnerClassBuilders", true));
+
+    Class<?> childClass = resultsClassLoader.loadClass("com.example.Child");
+    boolean containsWithMethod = Stream.of(childClass.getMethods())
+        .map(Method::getName)
+        .anyMatch(methodName -> StringUtils.contains(methodName, "with"));
+
+    assertFalse("Generated contains unexpected builders", containsWithMethod);
+
+    assertNotNull(resultsClassLoader.loadClass("com.example.Child$ChildBuilder"));
+  }
+
+  /**
+   * This methods confirms that the builders can be constructed using the empty constructor and possess a 'build' method that will return a non-null
+   * object
+   */
+  @Test
+  public void innerBuildersInvokeBuild()
+      throws ClassNotFoundException, IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchMethodException {
+    ClassLoader resultsClassLoader = schemaRule.generateAndCompile("/schema.useInnerClassBuilders/child.json", "com.example",
+        config("generateBuilders", true, "useInnerClassBuilders", true));
+
+    Class<?> builderClass = resultsClassLoader.loadClass("com.example.Child$ChildBuilder");
+    Method buildMethod = builderClass.getMethod("build");
+
+    Object builder = builderClass.newInstance();
+    assertNotNull(builder);
+
+    assertNotNull(buildMethod.invoke(builder));
+  }
+
+  /**
+   * This method walks through invoking the various incremental 'with' methods to build out an entire object, then invokes build on the constructed
+   * object and confirms all values on the object match those provided to the with methods
+   */
+  @Test
+  public void innerBuildersBuildObjectIncrementally()
+      throws ClassNotFoundException, IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchMethodException {
+    ClassLoader resultsClassLoader = schemaRule.generateAndCompile("/schema.useInnerClassBuilders/child.json", "com.example",
+        config("generateBuilders", true, "useInnerClassBuilders", true));
+
+    Class<?> builderClass = resultsClassLoader.loadClass("com.example.Child$ChildBuilder");
+    Method buildMethod = builderClass.getMethod("build");
+    Method withChildProperty = builderClass.getMethod("withChildProperty", Integer.class);
+    Method withParentProperty = builderClass.getMethod("withParentProperty", String.class);
+    Method withSharedProperty = builderClass.getMethod("withSharedProperty", String.class);
+
+    int childProperty = 1;
+    String parentProperty = "parentProperty";
+    String sharedProperty = "sharedProperty";
+
+    Object builder = builderClass.newInstance();
+    withChildProperty.invoke(builder, childProperty);
+    withParentProperty.invoke(builder, parentProperty);
+    withSharedProperty.invoke(builder, sharedProperty);
+    Object childObject = buildMethod.invoke(builder);
+
+    Class<?> childClass = resultsClassLoader.loadClass("com.example.Child");
+    Method getChildProperty = childClass.getMethod("getChildProperty");
+    Method getParentProperty = childClass.getMethod("getParentProperty");
+    Method getSharedProperty = childClass.getMethod("getSharedProperty");
+
+    assertEquals(childProperty, getChildProperty.invoke(childObject));
+    assertEquals(parentProperty, getParentProperty.invoke(childObject));
+    assertEquals(sharedProperty, getSharedProperty.invoke(childObject));
+  }
+
+  /**
+   * This method confirms that by default the only constructor available to a builder is the empty argument constructor
+   */
+  @Test
+  public void innerBuilderExtraConstructorsRequireConfig() throws ClassNotFoundException {
+    ClassLoader resultsClassLoader = schemaRule.generateAndCompile("/schema.useInnerClassBuilders/child.json", "com.example",
+        config("generateBuilders", true, "useInnerClassBuilders", true));
+
+    Class<?> builderClass = resultsClassLoader.loadClass("com.example.Child$ChildBuilder");
+    assertEquals(1, builderClass.getConstructors().length);
+
+    Constructor<?> constructor = builderClass.getConstructors()[0];
+    assertEquals(0, constructor.getParameterCount());
+  }
+
+  /**
+   * This method confirms that if constructors are enabled then a builder constructor which takes all properties will be created
+   */
+  @Test
+  public void innerBuilderWithAllPropertyConstructor()
+      throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException {
+    ClassLoader resultsClassLoader = schemaRule.generateAndCompile("/schema.useInnerClassBuilders/child.json", "com.example",
+        config("generateBuilders", true, "useInnerClassBuilders", true, "includeConstructors", true));
+
+    Class<?> builderClass = resultsClassLoader.loadClass("com.example.Child$ChildBuilder");
+    Constructor<?> constructor = builderClass.getConstructor(Integer.class, String.class, String.class);
+    Method buildMethod = builderClass.getMethod("build");
+
+    int childProperty = 1;
+    String parentProperty = "parentProperty";
+    String sharedProperty = "sharedProperty";
+
+    Object builder = constructor.newInstance(childProperty, sharedProperty, parentProperty);
+    Object childObject = buildMethod.invoke(builder);
+
+    Class<?> childClass = resultsClassLoader.loadClass("com.example.Child");
+    Method getChildProperty = childClass.getMethod("getChildProperty");
+    Method getParentProperty = childClass.getMethod("getParentProperty");
+    Method getSharedProperty = childClass.getMethod("getSharedProperty");
+
+    assertEquals(childProperty, getChildProperty.invoke(childObject));
+    assertEquals(parentProperty, getParentProperty.invoke(childObject));
+    assertEquals(sharedProperty, getSharedProperty.invoke(childObject));
+  }
+
+  /**
+   * This method confirms that if constructors are enabled and constructorsRequiredPropertiesOnly is set to true then a only a builder constructor
+   * with the required properties will be created
+   */
+  @Test
+  public void innerBuilderWithRequiredPropertyConstructor()
+      throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException {
+    ClassLoader resultsClassLoader = schemaRule.generateAndCompile("/schema.useInnerClassBuilders/child.json", "com.example",
+        config("generateBuilders", true, "useInnerClassBuilders", true, "includeConstructors", true, "constructorsRequiredPropertiesOnly", true));
+
+    Class<?> builderClass = resultsClassLoader.loadClass("com.example.Child$ChildBuilder");
+    Constructor<?> constructor = builderClass.getConstructor(String.class);
+    Method buildMethod = builderClass.getMethod("build");
+    Method withChildProperty = builderClass.getMethod("withChildProperty", Integer.class);
+    Method withSharedProperty = builderClass.getMethod("withSharedProperty", String.class);
+
+    int childProperty = 1;
+    String parentProperty = "parentProperty";
+    String sharedProperty = "sharedProperty";
+
+    Object builder = constructor.newInstance(parentProperty);
+    withChildProperty.invoke(builder, childProperty);
+    withSharedProperty.invoke(builder, sharedProperty);
+    Object childObject = buildMethod.invoke(builder);
+
+    Class<?> childClass = resultsClassLoader.loadClass("com.example.Child");
+    Method getChildProperty = childClass.getMethod("getChildProperty");
+    Method getParentProperty = childClass.getMethod("getParentProperty");
+    Method getSharedProperty = childClass.getMethod("getSharedProperty");
+
+    assertEquals(childProperty, getChildProperty.invoke(childObject));
+    assertEquals(parentProperty, getParentProperty.invoke(childObject));
+    assertEquals(sharedProperty, getSharedProperty.invoke(childObject));
+  }
+}
diff --git a/jsonschema2pojo-integration-tests/src/test/resources/schema.useInnerClassBuilders/child.json b/jsonschema2pojo-integration-tests/src/test/resources/schema.useInnerClassBuilders/child.json
new file mode 100644
index 00000000..1a71d461
--- /dev/null
+++ b/jsonschema2pojo-integration-tests/src/test/resources/schema.useInnerClassBuilders/child.json
@@ -0,0 +1,10 @@
+{
+  "type" : "object",
+  "extends" : {
+    "$ref" : "parent.json"
+  },
+  "properties" : {
+    "childProperty" : { "type" : "integer"},
+    "sharedProperty" : { "type" : "string" }
+  }
+}
\ No newline at end of file
diff --git a/jsonschema2pojo-integration-tests/src/test/resources/schema.useInnerClassBuilders/parent.json b/jsonschema2pojo-integration-tests/src/test/resources/schema.useInnerClassBuilders/parent.json
new file mode 100644
index 00000000..99779bab
--- /dev/null
+++ b/jsonschema2pojo-integration-tests/src/test/resources/schema.useInnerClassBuilders/parent.json
@@ -0,0 +1,8 @@
+{
+  "type" : "object",
+  "properties" : {
+    "parentProperty" : { "type" : "string" },
+    "sharedProperty" : { "type" : "string" }
+  },
+  "required" : ["parentProperty"]
+}
\ No newline at end of file