diff --git a/src/main/java/com/typesafe/config/impl/AbstractConfigObject.java b/src/main/java/com/typesafe/config/impl/AbstractConfigObject.java
index 8bf81f5b..d9d41e3f 100644
--- a/src/main/java/com/typesafe/config/impl/AbstractConfigObject.java
+++ b/src/main/java/com/typesafe/config/impl/AbstractConfigObject.java
@@ -106,50 +106,57 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements
             boolean ignoresFallbacks);
 
     @Override
-    public AbstractConfigObject withFallback(ConfigMergeable mergeable) {
-        ConfigValue other = mergeable.toValue();
+    protected AbstractConfigObject newCopy(boolean ignoresFallbacks) {
+            return newCopy(resolveStatus(), ignoresFallbacks);
+    }
 
-        if (ignoresFallbacks()) {
-            return this;
-        } else if (other instanceof Unmergeable) {
-            List<AbstractConfigValue> stack = new ArrayList<AbstractConfigValue>();
-            stack.add(this);
-            stack.addAll(((Unmergeable) other).unmergedValues());
-            return new ConfigDelayedMergeObject(mergeOrigins(stack), stack);
-        } else if (other instanceof AbstractConfigObject) {
-            AbstractConfigObject fallback = (AbstractConfigObject) other;
-            if (fallback.isEmpty()) {
-                return this; // nothing to do
-            } else {
-                boolean allResolved = true;
-                Map<String, AbstractConfigValue> merged = new HashMap<String, AbstractConfigValue>();
-                Set<String> allKeys = new HashSet<String>();
-                allKeys.addAll(this.keySet());
-                allKeys.addAll(fallback.keySet());
-                for (String key : allKeys) {
-                    AbstractConfigValue first = this.peek(key);
-                    AbstractConfigValue second = fallback.peek(key);
-                    AbstractConfigValue kept;
-                    if (first == null)
-                        kept = second;
-                    else if (second == null)
-                        kept = first;
-                    else
-                        kept = first.withFallback(second);
-                    merged.put(key, kept);
-                    if (kept.resolveStatus() == ResolveStatus.UNRESOLVED)
-                        allResolved = false;
-                }
-                return new SimpleConfigObject(mergeOrigins(this, fallback),
-                        merged, ResolveStatus.fromBoolean(allResolved),
-                        ignoresFallbacks());
-            }
+    @Override
+    protected final AbstractConfigObject mergedWithTheUnmergeable(Unmergeable fallback) {
+        if (ignoresFallbacks())
+            throw new ConfigException.BugOrBroken("should not be reached");
+
+        List<AbstractConfigValue> stack = new ArrayList<AbstractConfigValue>();
+        if (this instanceof Unmergeable) {
+            stack.addAll(((Unmergeable) this).unmergedValues());
         } else {
-            // falling back to a non-object doesn't merge anything, and also
-            // prohibits merging any objects that we fall back to later.
-            // so we have to switch to ignoresFallbacks mode.
-            return newCopy(resolveStatus(), true /* ignoresFallbacks */);
+            stack.add(this);
         }
+        stack.addAll(fallback.unmergedValues());
+        return new ConfigDelayedMergeObject(mergeOrigins(stack), stack,
+                ((AbstractConfigValue) fallback).ignoresFallbacks());
+    }
+
+    @Override
+    protected AbstractConfigObject mergedWithObject(AbstractConfigObject fallback) {
+        if (ignoresFallbacks())
+            throw new ConfigException.BugOrBroken("should not be reached");
+
+        boolean allResolved = true;
+        Map<String, AbstractConfigValue> merged = new HashMap<String, AbstractConfigValue>();
+        Set<String> allKeys = new HashSet<String>();
+        allKeys.addAll(this.keySet());
+        allKeys.addAll(fallback.keySet());
+        for (String key : allKeys) {
+            AbstractConfigValue first = this.peek(key);
+            AbstractConfigValue second = fallback.peek(key);
+            AbstractConfigValue kept;
+            if (first == null)
+                kept = second;
+            else if (second == null)
+                kept = first;
+            else
+                kept = first.withFallback(second);
+            merged.put(key, kept);
+            if (kept.resolveStatus() == ResolveStatus.UNRESOLVED)
+                allResolved = false;
+        }
+        return new SimpleConfigObject(mergeOrigins(this, fallback), merged,
+                ResolveStatus.fromBoolean(allResolved), fallback.ignoresFallbacks());
+    }
+
+    @Override
+    public AbstractConfigObject withFallback(ConfigMergeable mergeable) {
+        return (AbstractConfigObject) super.withFallback(mergeable);
     }
 
     static ConfigOrigin mergeOrigins(
@@ -169,7 +176,9 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements
             if (desc.startsWith(prefix))
                 desc = desc.substring(prefix.length());
 
-            if (v instanceof ConfigObject && ((ConfigObject) v).isEmpty()) {
+            if (v instanceof AbstractConfigObject
+                    && ((AbstractConfigObject) v).resolveStatus() == ResolveStatus.RESOLVED
+                    && ((ConfigObject) v).isEmpty()) {
                 // don't include empty files or the .empty()
                 // config in the description, since they are
                 // likely to be "implementation details"
diff --git a/src/main/java/com/typesafe/config/impl/AbstractConfigValue.java b/src/main/java/com/typesafe/config/impl/AbstractConfigValue.java
index f4cf9e75..2125cf72 100644
--- a/src/main/java/com/typesafe/config/impl/AbstractConfigValue.java
+++ b/src/main/java/com/typesafe/config/impl/AbstractConfigValue.java
@@ -3,6 +3,7 @@
  */
 package com.typesafe.config.impl;
 
+import com.typesafe.config.ConfigException;
 import com.typesafe.config.ConfigMergeable;
 import com.typesafe.config.ConfigOrigin;
 import com.typesafe.config.ConfigResolveOptions;
@@ -75,6 +76,10 @@ abstract class AbstractConfigValue implements ConfigValue {
         return this;
     }
 
+    protected AbstractConfigValue newCopy(boolean ignoresFallbacks) {
+        return this;
+    }
+
     // this is virtualized rather than a field because only some subclasses
     // really need to store the boolean, and they may be able to pack it
     // with another boolean to save space.
@@ -82,9 +87,50 @@ abstract class AbstractConfigValue implements ConfigValue {
         return true;
     }
 
+    private ConfigException badMergeException() {
+        if (ignoresFallbacks())
+            throw new ConfigException.BugOrBroken(
+                    "method should not have been called with ignoresFallbacks=true"
+                            + getClass().getSimpleName());
+        else
+            throw new ConfigException.BugOrBroken("should override this in "
+                    + getClass().getSimpleName());
+    }
+
+    protected AbstractConfigValue mergedWithTheUnmergeable(Unmergeable fallback) {
+        throw badMergeException();
+    }
+
+    protected AbstractConfigValue mergedWithObject(AbstractConfigObject fallback) {
+        throw badMergeException();
+    }
+
     @Override
-    public AbstractConfigValue withFallback(ConfigMergeable other) {
-        return this;
+    public AbstractConfigValue withFallback(ConfigMergeable mergeable) {
+        if (ignoresFallbacks()) {
+            return this;
+        } else {
+            ConfigValue other = mergeable.toValue();
+
+            if (other instanceof Unmergeable) {
+                return mergedWithTheUnmergeable((Unmergeable) other);
+            } else if (other instanceof AbstractConfigObject) {
+                AbstractConfigObject fallback = (AbstractConfigObject) other;
+                if (fallback.resolveStatus() == ResolveStatus.RESOLVED && fallback.isEmpty()) {
+                    if (fallback.ignoresFallbacks())
+                        return newCopy(true /* ignoresFallbacks */);
+                    else
+                        return this;
+                } else {
+                    return mergedWithObject((AbstractConfigObject) other);
+                }
+            } else {
+                // falling back to a non-object doesn't merge anything, and also
+                // prohibits merging any objects that we fall back to later.
+                // so we have to switch to ignoresFallbacks mode.
+                return newCopy(true /* ignoresFallbacks */);
+            }
+        }
     }
 
     protected boolean canEqual(Object other) {
diff --git a/src/main/java/com/typesafe/config/impl/ConfigDelayedMerge.java b/src/main/java/com/typesafe/config/impl/ConfigDelayedMerge.java
index 3b93b56f..0c20aa57 100644
--- a/src/main/java/com/typesafe/config/impl/ConfigDelayedMerge.java
+++ b/src/main/java/com/typesafe/config/impl/ConfigDelayedMerge.java
@@ -8,10 +8,8 @@ import java.util.Collection;
 import java.util.List;
 
 import com.typesafe.config.ConfigException;
-import com.typesafe.config.ConfigMergeable;
 import com.typesafe.config.ConfigOrigin;
 import com.typesafe.config.ConfigResolveOptions;
-import com.typesafe.config.ConfigValue;
 import com.typesafe.config.ConfigValueType;
 
 /**
@@ -29,8 +27,8 @@ final class ConfigDelayedMerge extends AbstractConfigValue implements
     final private List<AbstractConfigValue> stack;
     final private boolean ignoresFallbacks;
 
-    private ConfigDelayedMerge(ConfigOrigin origin,
-            List<AbstractConfigValue> stack, boolean ignoresFallbacks) {
+    ConfigDelayedMerge(ConfigOrigin origin, List<AbstractConfigValue> stack,
+            boolean ignoresFallbacks) {
         super(origin);
         this.stack = stack;
         this.ignoresFallbacks = ignoresFallbacks;
@@ -38,6 +36,11 @@ final class ConfigDelayedMerge extends AbstractConfigValue implements
             throw new ConfigException.BugOrBroken(
                     "creating empty delayed merge value");
 
+        for (AbstractConfigValue v : stack) {
+            if (v instanceof ConfigDelayedMerge || v instanceof ConfigDelayedMergeObject)
+                throw new ConfigException.BugOrBroken(
+                        "placed nested DelayedMerge in a ConfigDelayedMerge, should have consolidated stack");
+        }
     }
 
     ConfigDelayedMerge(ConfigOrigin origin, List<AbstractConfigValue> stack) {
@@ -67,18 +70,19 @@ final class ConfigDelayedMerge extends AbstractConfigValue implements
             List<AbstractConfigValue> stack, SubstitutionResolver resolver,
             int depth, ConfigResolveOptions options) {
         // to resolve substitutions, we need to recursively resolve
-        // the stack of stuff to merge, and then merge the stack.
-        List<AbstractConfigValue> toMerge = new ArrayList<AbstractConfigValue>();
+        // the stack of stuff to merge, and merge the stack so
+        // we won't be a delayed merge anymore.
 
+        AbstractConfigValue merged = null;
         for (AbstractConfigValue v : stack) {
             AbstractConfigValue resolved = resolver.resolve(v, depth, options);
-            toMerge.add(resolved);
+            if (merged == null)
+                merged = resolved;
+            else
+                merged = merged.withFallback(resolved);
         }
 
-        // we shouldn't have a delayed merge object with an empty stack, so
-        // it should be safe to ignore the toMerge.isEmpty case.
-        return ConfigImpl.merge(AbstractConfigValue.class, toMerge.get(0),
-                toMerge.subList(1, toMerge.size()));
+        return merged;
     }
 
     @Override
@@ -101,29 +105,31 @@ final class ConfigDelayedMerge extends AbstractConfigValue implements
     }
 
     @Override
-    public AbstractConfigValue withFallback(ConfigMergeable mergeable) {
-        ConfigValue other = mergeable.toValue();
+    protected final ConfigDelayedMerge mergedWithTheUnmergeable(Unmergeable fallback) {
+        if (ignoresFallbacks)
+            throw new ConfigException.BugOrBroken("should not be reached");
 
-        if (ignoresFallbacks) {
-            return this;
-        } else if (other instanceof AbstractConfigObject
-                || other instanceof Unmergeable) {
-            // if we turn out to be an object, and the fallback also does,
-            // then a merge may be required; delay until we resolve.
-            List<AbstractConfigValue> newStack = new ArrayList<AbstractConfigValue>();
-            newStack.addAll(stack);
-            if (other instanceof Unmergeable)
-                newStack.addAll(((Unmergeable) other).unmergedValues());
-            else
-                newStack.add((AbstractConfigValue) other);
-            return new ConfigDelayedMerge(
-                    AbstractConfigObject.mergeOrigins(newStack), newStack,
-                    ignoresFallbacks);
-        } else {
-            // if the other is not an object, there won't be anything
-            // to merge with, so we are it even if we are an object.
-            return new ConfigDelayedMerge(origin(), stack, true /* ignoresFallbacks */);
-        }
+        // if we turn out to be an object, and the fallback also does,
+        // then a merge may be required; delay until we resolve.
+        List<AbstractConfigValue> newStack = new ArrayList<AbstractConfigValue>();
+        newStack.addAll(stack);
+        newStack.addAll(fallback.unmergedValues());
+        return new ConfigDelayedMerge(AbstractConfigObject.mergeOrigins(newStack), newStack,
+                ((AbstractConfigValue) fallback).ignoresFallbacks());
+    }
+
+    @Override
+    protected final ConfigDelayedMerge mergedWithObject(AbstractConfigObject fallback) {
+        if (ignoresFallbacks)
+            throw new ConfigException.BugOrBroken("should not be reached");
+
+        // if we turn out to be an object, and the fallback also does,
+        // then a merge may be required; delay until we resolve.
+        List<AbstractConfigValue> newStack = new ArrayList<AbstractConfigValue>();
+        newStack.addAll(stack);
+        newStack.add(fallback);
+        return new ConfigDelayedMerge(AbstractConfigObject.mergeOrigins(newStack), newStack,
+                fallback.ignoresFallbacks());
     }
 
     @Override
diff --git a/src/main/java/com/typesafe/config/impl/ConfigDelayedMergeObject.java b/src/main/java/com/typesafe/config/impl/ConfigDelayedMergeObject.java
index 73051aa5..6f381afc 100644
--- a/src/main/java/com/typesafe/config/impl/ConfigDelayedMergeObject.java
+++ b/src/main/java/com/typesafe/config/impl/ConfigDelayedMergeObject.java
@@ -28,8 +28,8 @@ class ConfigDelayedMergeObject extends AbstractConfigObject implements
         this(origin, stack, false /* ignoresFallbacks */);
     }
 
-    private ConfigDelayedMergeObject(ConfigOrigin origin,
-            List<AbstractConfigValue> stack, boolean ignoresFallbacks) {
+    ConfigDelayedMergeObject(ConfigOrigin origin, List<AbstractConfigValue> stack,
+            boolean ignoresFallbacks) {
         super(origin);
         this.stack = stack;
         this.ignoresFallbacks = ignoresFallbacks;
@@ -40,10 +40,16 @@ class ConfigDelayedMergeObject extends AbstractConfigObject implements
         if (!(stack.get(0) instanceof AbstractConfigObject))
             throw new ConfigException.BugOrBroken(
                     "created a delayed merge object not guaranteed to be an object");
+
+        for (AbstractConfigValue v : stack) {
+            if (v instanceof ConfigDelayedMerge || v instanceof ConfigDelayedMergeObject)
+                throw new ConfigException.BugOrBroken(
+                        "placed nested DelayedMerge in a ConfigDelayedMergeObject, should have consolidated stack");
+        }
     }
 
     @Override
-    public ConfigDelayedMergeObject newCopy(ResolveStatus status,
+    protected ConfigDelayedMergeObject newCopy(ResolveStatus status,
             boolean ignoresFallbacks) {
         if (status != resolveStatus())
             throw new ConfigException.BugOrBroken(
@@ -86,30 +92,22 @@ class ConfigDelayedMergeObject extends AbstractConfigObject implements
     }
 
     @Override
-    public ConfigDelayedMergeObject withFallback(ConfigMergeable mergeable) {
-        ConfigValue other = mergeable.toValue();
+    protected ConfigDelayedMergeObject mergedWithObject(AbstractConfigObject fallback) {
+        if (ignoresFallbacks)
+            throw new ConfigException.BugOrBroken("should not be reached");
 
-        if (ignoresFallbacks) {
-            return this;
-        } else if (other instanceof AbstractConfigObject
-                || other instanceof Unmergeable) {
-            // since we are an object, and the fallback could be,
-            // then a merge may be required; delay until we resolve.
-            List<AbstractConfigValue> newStack = new ArrayList<AbstractConfigValue>();
-            newStack.addAll(stack);
-            if (other instanceof Unmergeable)
-                newStack.addAll(((Unmergeable) other).unmergedValues());
-            else
-                newStack.add((AbstractConfigValue) other);
-            return new ConfigDelayedMergeObject(
-                    AbstractConfigObject.mergeOrigins(newStack), newStack,
-                    ignoresFallbacks);
-        } else {
-            // if the other is not an object, there won't be anything
-            // to merge with but we do need to ignore any fallbacks
-            // that occur after it.
-            return newCopy(resolveStatus(), true /* ignoresFallbacks */);
-        }
+        // since we are an object, and the fallback is, we'll need to
+        // merge the fallback once we resolve.
+        List<AbstractConfigValue> newStack = new ArrayList<AbstractConfigValue>();
+        newStack.addAll(stack);
+        newStack.add(fallback);
+        return new ConfigDelayedMergeObject(AbstractConfigObject.mergeOrigins(newStack), newStack,
+                fallback.ignoresFallbacks());
+    }
+
+    @Override
+    public ConfigDelayedMergeObject withFallback(ConfigMergeable mergeable) {
+        return (ConfigDelayedMergeObject) super.withFallback(mergeable);
     }
 
     @Override
diff --git a/src/main/java/com/typesafe/config/impl/ConfigImpl.java b/src/main/java/com/typesafe/config/impl/ConfigImpl.java
index a8ee038f..8c2bd01e 100644
--- a/src/main/java/com/typesafe/config/impl/ConfigImpl.java
+++ b/src/main/java/com/typesafe/config/impl/ConfigImpl.java
@@ -5,7 +5,6 @@ package com.typesafe.config.impl;
 
 import java.io.File;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Iterator;
@@ -16,7 +15,6 @@ import com.typesafe.config.Config;
 import com.typesafe.config.ConfigException;
 import com.typesafe.config.ConfigIncludeContext;
 import com.typesafe.config.ConfigIncluder;
-import com.typesafe.config.ConfigMergeable;
 import com.typesafe.config.ConfigObject;
 import com.typesafe.config.ConfigOrigin;
 import com.typesafe.config.ConfigParseOptions;
@@ -28,21 +26,6 @@ import com.typesafe.config.ConfigValue;
 /** This is public but is only supposed to be used by the "config" package */
 public class ConfigImpl {
 
-    static <T extends ConfigMergeable> T merge(Class<T> klass, T first,
-            ConfigMergeable... others) {
-        List<ConfigMergeable> stack = Arrays.asList(others);
-        return merge(klass, first, stack);
-    }
-
-    static <T extends ConfigMergeable> T merge(Class<T> klass, T first,
-            List<? extends ConfigMergeable> stack) {
-        ConfigMergeable merged = first;
-        for (ConfigMergeable fallback : stack) {
-            merged = merged.withFallback(fallback);
-        }
-        return klass.cast(merged);
-    }
-
     private interface NameSource {
         ConfigParseable nameToParseable(String name);
     }
diff --git a/src/main/java/com/typesafe/config/impl/ConfigSubstitution.java b/src/main/java/com/typesafe/config/impl/ConfigSubstitution.java
index 87492e94..2c5531e7 100644
--- a/src/main/java/com/typesafe/config/impl/ConfigSubstitution.java
+++ b/src/main/java/com/typesafe/config/impl/ConfigSubstitution.java
@@ -9,7 +9,6 @@ import java.util.Collections;
 import java.util.List;
 
 import com.typesafe.config.ConfigException;
-import com.typesafe.config.ConfigMergeable;
 import com.typesafe.config.ConfigOrigin;
 import com.typesafe.config.ConfigResolveOptions;
 import com.typesafe.config.ConfigValue;
@@ -57,28 +56,41 @@ final class ConfigSubstitution extends AbstractConfigValue implements
     }
 
     @Override
-    public AbstractConfigValue withFallback(ConfigMergeable mergeable) {
-        ConfigValue other = mergeable.toValue();
+    protected ConfigSubstitution newCopy(boolean ignoresFallbacks) {
+        return new ConfigSubstitution(origin(), pieces, prefixLength, ignoresFallbacks);
+    }
 
-        if (ignoresFallbacks) {
-            return this;
-        } else if (other instanceof AbstractConfigObject
-                || other instanceof Unmergeable) {
-            // if we turn out to be an object, and the fallback also does,
-            // then a merge may be required; delay until we resolve.
-            List<AbstractConfigValue> newStack = new ArrayList<AbstractConfigValue>();
-            newStack.add(this);
-            if (other instanceof Unmergeable)
-                newStack.addAll(((Unmergeable) other).unmergedValues());
-            else
-                newStack.add((AbstractConfigValue) other);
-            return new ConfigDelayedMerge(
-                    AbstractConfigObject.mergeOrigins(newStack), newStack);
-        } else {
-            // if the other is not an object, there won't be anything
-            // to merge with, so we are it even if we are an object.
-            return new ConfigSubstitution(origin(), pieces, prefixLength, true /* ignoresFallbacks */);
-        }
+    @Override
+    protected boolean ignoresFallbacks() {
+        return ignoresFallbacks;
+    }
+
+    @Override
+    protected AbstractConfigValue mergedWithTheUnmergeable(Unmergeable fallback) {
+        if (ignoresFallbacks)
+            throw new ConfigException.BugOrBroken("should not be reached");
+
+        // if we turn out to be an object, and the fallback also does,
+        // then a merge may be required; delay until we resolve.
+        List<AbstractConfigValue> newStack = new ArrayList<AbstractConfigValue>();
+        newStack.add(this);
+        newStack.addAll(fallback.unmergedValues());
+        return new ConfigDelayedMerge(AbstractConfigObject.mergeOrigins(newStack), newStack,
+                ((AbstractConfigValue) fallback).ignoresFallbacks());
+    }
+
+    @Override
+    protected AbstractConfigValue mergedWithObject(AbstractConfigObject fallback) {
+        if (ignoresFallbacks)
+            throw new ConfigException.BugOrBroken("should not be reached");
+
+        // if we turn out to be an object, and the fallback also does,
+        // then a merge may be required; delay until we resolve.
+        List<AbstractConfigValue> newStack = new ArrayList<AbstractConfigValue>();
+        newStack.add(this);
+        newStack.add(fallback);
+        return new ConfigDelayedMerge(AbstractConfigObject.mergeOrigins(newStack), newStack,
+                fallback.ignoresFallbacks());
     }
 
     @Override
diff --git a/src/main/java/com/typesafe/config/impl/RootConfig.java b/src/main/java/com/typesafe/config/impl/RootConfig.java
index de0c9343..723b9fad 100644
--- a/src/main/java/com/typesafe/config/impl/RootConfig.java
+++ b/src/main/java/com/typesafe/config/impl/RootConfig.java
@@ -35,14 +35,14 @@ final class RootConfig extends SimpleConfig implements ConfigRoot {
         // if the object is already resolved then we should end up returning
         // "this" here, since asRoot() should return this if the path
         // is unchanged.
-        SimpleConfig resolved = resolvedObject(options).toConfig();
-        return resolved.asRoot(rootPath);
+        AbstractConfigObject resolved = resolvedObject(options);
+        return newRootIfObjectChanged(this, resolved);
     }
 
     @Override
     public RootConfig withFallback(ConfigMergeable value) {
         // this can return "this" if the withFallback does nothing
-        return super.withFallback(value).asRoot(rootPath);
+        return newRootIfObjectChanged(this, super.withFallback(value).toObject());
     }
 
     Path rootPathObject() {
diff --git a/src/main/java/com/typesafe/config/impl/SimpleConfig.java b/src/main/java/com/typesafe/config/impl/SimpleConfig.java
index 7c5c61e8..0f166fab 100644
--- a/src/main/java/com/typesafe/config/impl/SimpleConfig.java
+++ b/src/main/java/com/typesafe/config/impl/SimpleConfig.java
@@ -59,6 +59,13 @@ class SimpleConfig implements Config {
         return new RootConfig(underlying, newRootPath);
     }
 
+    static protected RootConfig newRootIfObjectChanged(RootConfig self, AbstractConfigObject underlying) {
+        if (underlying == self.object)
+            return self;
+        else
+            return new RootConfig(underlying, self.rootPathObject());
+    }
+
     protected AbstractConfigObject resolvedObject(ConfigResolveOptions options) {
         AbstractConfigValue resolved = SubstitutionResolver.resolve(object,
                 object, options);
diff --git a/src/main/java/com/typesafe/config/impl/SimpleConfigObject.java b/src/main/java/com/typesafe/config/impl/SimpleConfigObject.java
index 3534fca0..88b2b909 100644
--- a/src/main/java/com/typesafe/config/impl/SimpleConfigObject.java
+++ b/src/main/java/com/typesafe/config/impl/SimpleConfigObject.java
@@ -45,10 +45,8 @@ final class SimpleConfigObject extends AbstractConfigObject {
     }
 
     @Override
-    public SimpleConfigObject newCopy(ResolveStatus newStatus,
-            boolean ignoresFallbacks) {
-        return new SimpleConfigObject(origin(), value, newStatus,
-                ignoresFallbacks);
+    protected SimpleConfigObject newCopy(ResolveStatus newStatus, boolean newIgnoresFallbacks) {
+        return new SimpleConfigObject(origin(), value, newStatus, newIgnoresFallbacks);
     }
 
     @Override
diff --git a/src/test/scala/com/typesafe/config/impl/ConfigTest.scala b/src/test/scala/com/typesafe/config/impl/ConfigTest.scala
index fcf68ade..71aaa810 100644
--- a/src/test/scala/com/typesafe/config/impl/ConfigTest.scala
+++ b/src/test/scala/com/typesafe/config/impl/ConfigTest.scala
@@ -15,6 +15,7 @@ import com.typesafe.config.ConfigResolveOptions
 import java.io.File
 import com.typesafe.config.ConfigParseOptions
 import com.typesafe.config.ConfigFactory
+import com.typesafe.config.ConfigMergeable
 
 class ConfigTest extends TestUtils {
 
@@ -83,7 +84,10 @@ class ConfigTest extends TestUtils {
         val trees = Seq(m(allObjects: _*)) ++ makeTrees(allObjects)
         for (tree <- trees) {
             // if this fails, we were not associative.
-            assertEquals(trees(0), tree)
+            if (!trees(0).equals(tree))
+                throw new AssertionError("Merge was not associative, " +
+                    "verify that it should not be, then don't use associativeMerge " +
+                    "for this one.\none: " + trees(0) + "\ntwo: " + tree)
         }
 
         for (tree <- trees) {
@@ -445,6 +449,61 @@ class ConfigTest extends TestUtils {
         }
     }
 
+    private def ignoresFallbacks(m: ConfigMergeable) = {
+        m match {
+            case v: AbstractConfigValue =>
+                v.ignoresFallbacks()
+            case c: SimpleConfig =>
+                c.toObject.ignoresFallbacks()
+        }
+    }
+
+    private def testIgnoredMergesDoNothing(nonEmpty: ConfigMergeable) {
+        // falling back to a primitive once should switch us to "ignoreFallbacks" mode
+        // and then twice should "return this". Falling back to an empty object should
+        // return this unless the empty object was ignoreFallbacks and then we should
+        // "catch" its ignoreFallbacks.
+
+        // some of what this tests is just optimization, not API contract (withFallback
+        // can return a new object anytime it likes) but want to be sure we do the
+        // optimizations.
+
+        val empty = SimpleConfigObject.empty(null)
+        val primitive = intValue(42)
+        val emptyIgnoringFallbacks = empty.withFallback(primitive)
+        val nonEmptyIgnoringFallbacks = nonEmpty.withFallback(primitive)
+
+        assertEquals(false, empty.ignoresFallbacks())
+        assertEquals(true, primitive.ignoresFallbacks())
+        assertEquals(true, emptyIgnoringFallbacks.ignoresFallbacks())
+        assertEquals(false, ignoresFallbacks(nonEmpty))
+        assertEquals(true, ignoresFallbacks(nonEmptyIgnoringFallbacks))
+
+        assertTrue(nonEmpty ne nonEmptyIgnoringFallbacks)
+        assertTrue(empty ne emptyIgnoringFallbacks)
+
+        // falling back from primitive just returns this
+        assertTrue(primitive eq primitive.withFallback(empty))
+        assertTrue(primitive eq primitive.withFallback(nonEmpty))
+        assertTrue(primitive eq primitive.withFallback(nonEmptyIgnoringFallbacks))
+
+        // falling back again from an ignoreFallbacks should be a no-op, return this
+        assertTrue(nonEmptyIgnoringFallbacks eq nonEmptyIgnoringFallbacks.withFallback(empty))
+        assertTrue(nonEmptyIgnoringFallbacks eq nonEmptyIgnoringFallbacks.withFallback(primitive))
+        assertTrue(emptyIgnoringFallbacks eq emptyIgnoringFallbacks.withFallback(empty))
+        assertTrue(emptyIgnoringFallbacks eq emptyIgnoringFallbacks.withFallback(primitive))
+    }
+
+    @Test
+    def ignoredMergesDoNothing() {
+        val conf = parseConfig("{ a : 1 }")
+        testIgnoredMergesDoNothing(conf)
+
+        // ConfigRoot mode uses a little different codepath
+        val root = conf.asRoot(path("whatever"))
+        testIgnoredMergesDoNothing(root)
+    }
+
     @Test
     def test01Getting() {
         val conf = ConfigFactory.load("test01")