diff --git a/config/src/main/java/com/typesafe/config/impl/ConfigSubstitution.java b/config/src/main/java/com/typesafe/config/impl/ConfigSubstitution.java
index 440caf6d..7d4f68ec 100644
--- a/config/src/main/java/com/typesafe/config/impl/ConfigSubstitution.java
+++ b/config/src/main/java/com/typesafe/config/impl/ConfigSubstitution.java
@@ -133,54 +133,7 @@ final class ConfigSubstitution extends AbstractConfigValue implements
         return pieces;
     }
 
-    /** resolver is null if we should not have refs */
-    private AbstractConfigValue findInObject(final AbstractConfigObject root,
-            final SubstitutionResolver resolver, final SubstitutionExpression subst,
-            final ResolveContext context) throws NotPossibleToResolve {
-        return context.traversing(this, subst, new ResolveContext.Resolver() {
-            @Override
-            public AbstractConfigValue call() throws NotPossibleToResolve {
-                return root.peekPath(subst.path(), resolver, context);
-            }
-        });
-    }
 
-    private AbstractConfigValue resolve(final SubstitutionResolver resolver,
-            final SubstitutionExpression subst, final ResolveContext context)
-            throws NotPossibleToResolve {
-        // First we look up the full path, which means relative to the
-        // included file if we were not a root file
-        AbstractConfigValue result = findInObject(resolver.root(), resolver, subst, context);
-
-        if (result == null) {
-            // Then we want to check relative to the root file. We don't
-            // want the prefix we were included at to be used when looking
-            // up env variables either.
-            SubstitutionExpression unprefixed = subst
-                    .changePath(subst.path().subPath(prefixLength));
-
-            if (result == null && prefixLength > 0) {
-                result = findInObject(resolver.root(), resolver, unprefixed, context);
-            }
-
-            if (result == null && context.options().getUseSystemEnvironment()) {
-                result = findInObject(ConfigImpl.envVariablesAsConfigObject(), null, unprefixed,
-                        context);
-            }
-        }
-
-        if (result != null) {
-            final AbstractConfigValue unresolved = result;
-            result = context.traversing(this, subst, new ResolveContext.Resolver() {
-                @Override
-                public AbstractConfigValue call() throws NotPossibleToResolve {
-                    return resolver.resolve(unresolved, context);
-                }
-            });
-        }
-
-        return result;
-    }
 
     private static ResolveReplacer undefinedReplacer = new ResolveReplacer() {
         @Override
@@ -201,7 +154,8 @@ final class ConfigSubstitution extends AbstractConfigValue implements
 
                 // to concat into a string we have to do a full resolve,
                 // so unrestrict the context
-                AbstractConfigValue v = resolve(resolver, exp, context.unrestricted());
+                AbstractConfigValue v = context.source().lookupSubst(resolver,
+                        context.unrestricted(), this, exp, prefixLength);
 
                 if (v == null) {
                     if (exp.optional()) {
@@ -233,7 +187,8 @@ final class ConfigSubstitution extends AbstractConfigValue implements
                     "ConfigSubstitution should never contain a single String piece");
 
         SubstitutionExpression exp = (SubstitutionExpression) pieces.get(0);
-        AbstractConfigValue v = resolve(resolver, exp, context);
+        AbstractConfigValue v = context.source().lookupSubst(resolver, context, this, exp,
+                prefixLength);
 
         if (v == null && !exp.optional()) {
             throw new ConfigException.UnresolvedSubstitution(origin(), exp.toString());
diff --git a/config/src/main/java/com/typesafe/config/impl/ResolveContext.java b/config/src/main/java/com/typesafe/config/impl/ResolveContext.java
index bb17fc5e..05e65b9e 100644
--- a/config/src/main/java/com/typesafe/config/impl/ResolveContext.java
+++ b/config/src/main/java/com/typesafe/config/impl/ResolveContext.java
@@ -15,6 +15,9 @@ import com.typesafe.config.impl.AbstractConfigValue.SelfReferential;
 import com.typesafe.config.impl.ResolveReplacer.Undefined;
 
 final class ResolveContext {
+    // this is unfortunately mutable so should only be shared among
+    // ResolveContext in the same traversal.
+    final private ResolveSource source;
     // Resolves that we have already begun (for cycle detection).
     // SubstitutionResolver separately memoizes completed resolves.
     // this set is unfortunately mutable and the user of ResolveContext
@@ -30,19 +33,21 @@ final class ResolveContext {
     // given replacement instead.
     final private Map<MemoKey, LinkedList<ResolveReplacer>> replacements;
 
-    ResolveContext(LinkedList<Set<MemoKey>> traversedStack, ConfigResolveOptions options,
-            Path restrictToChild,
+    ResolveContext(ResolveSource source, LinkedList<Set<MemoKey>> traversedStack,
+            ConfigResolveOptions options, Path restrictToChild,
             Map<MemoKey, LinkedList<ResolveReplacer>> replacements) {
+        this.source = source;
         this.traversedStack = traversedStack;
         this.options = options;
         this.restrictToChild = restrictToChild;
         this.replacements = replacements;
     }
 
-    ResolveContext(ConfigResolveOptions options, Path restrictToChild) {
+    ResolveContext(AbstractConfigObject root, ConfigResolveOptions options, Path restrictToChild) {
         // LinkedHashSet keeps the traversal order which is at least useful
         // in error messages if nothing else
-        this(new LinkedList<Set<MemoKey>>(Collections.singletonList(new LinkedHashSet<MemoKey>())),
+        this(new ResolveSource(root), new LinkedList<Set<MemoKey>>(
+                Collections.singletonList(new LinkedHashSet<MemoKey>())),
                 options, restrictToChild, new HashMap<MemoKey, LinkedList<ResolveReplacer>>());
     }
 
@@ -125,6 +130,10 @@ final class ResolveContext {
             return stack.peek().replace();
     }
 
+    ResolveSource source() {
+        return source;
+    }
+
     ConfigResolveOptions options() {
         return options;
     }
@@ -141,7 +150,7 @@ final class ResolveContext {
         if (restrictTo == restrictToChild)
             return this;
         else
-            return new ResolveContext(traversedStack, options, restrictTo, replacements);
+            return new ResolveContext(source, traversedStack, options, restrictTo, replacements);
     }
 
     ResolveContext unrestricted() {
diff --git a/config/src/main/java/com/typesafe/config/impl/ResolveSource.java b/config/src/main/java/com/typesafe/config/impl/ResolveSource.java
new file mode 100644
index 00000000..bab97a43
--- /dev/null
+++ b/config/src/main/java/com/typesafe/config/impl/ResolveSource.java
@@ -0,0 +1,64 @@
+package com.typesafe.config.impl;
+
+import com.typesafe.config.impl.AbstractConfigValue.NotPossibleToResolve;
+
+/**
+ * This class is the source for values for a substitution like ${foo}.
+ */
+final class ResolveSource {
+    final private AbstractConfigObject root;
+
+    ResolveSource(AbstractConfigObject root) {
+        this.root = root;
+    }
+
+    /** resolver is null if we should not have refs */
+    static private AbstractConfigValue findInObject(final AbstractConfigObject obj,
+            final SubstitutionResolver resolver, final ResolveContext context,
+            ConfigSubstitution traversed, final SubstitutionExpression subst)
+            throws NotPossibleToResolve {
+        return context.traversing(traversed, subst, new ResolveContext.Resolver() {
+            @Override
+            public AbstractConfigValue call() throws NotPossibleToResolve {
+                return obj.peekPath(subst.path(), resolver, context);
+            }
+        });
+    }
+
+    AbstractConfigValue lookupSubst(final SubstitutionResolver resolver,
+            final ResolveContext context, ConfigSubstitution traversed,
+            final SubstitutionExpression subst, int prefixLength) throws NotPossibleToResolve {
+        // First we look up the full path, which means relative to the
+        // included file if we were not a root file
+        AbstractConfigValue result = findInObject(root, resolver, context, traversed, subst);
+
+        if (result == null) {
+            // Then we want to check relative to the root file. We don't
+            // want the prefix we were included at to be used when looking
+            // up env variables either.
+            SubstitutionExpression unprefixed = subst
+                    .changePath(subst.path().subPath(prefixLength));
+
+            if (result == null && prefixLength > 0) {
+                result = findInObject(root, resolver, context, traversed, unprefixed);
+            }
+
+            if (result == null && context.options().getUseSystemEnvironment()) {
+                result = findInObject(ConfigImpl.envVariablesAsConfigObject(), null, context,
+                        traversed, unprefixed);
+            }
+        }
+
+        if (result != null) {
+            final AbstractConfigValue unresolved = result;
+            result = context.traversing(traversed, subst, new ResolveContext.Resolver() {
+                @Override
+                public AbstractConfigValue call() throws NotPossibleToResolve {
+                    return resolver.resolve(unresolved, context);
+                }
+            });
+        }
+
+        return result;
+    }
+}
diff --git a/config/src/main/java/com/typesafe/config/impl/SubstitutionResolver.java b/config/src/main/java/com/typesafe/config/impl/SubstitutionResolver.java
index d2e2a701..10f73a21 100644
--- a/config/src/main/java/com/typesafe/config/impl/SubstitutionResolver.java
+++ b/config/src/main/java/com/typesafe/config/impl/SubstitutionResolver.java
@@ -17,13 +17,11 @@ import com.typesafe.config.impl.ResolveReplacer.Undefined;
  * of values or whole trees of values as we follow chains of substitutions.
  */
 final class SubstitutionResolver {
-    final private AbstractConfigObject root;
     // note that we can resolve things to undefined (represented as Java null,
     // rather than ConfigNull) so this map can have null values.
     final private Map<MemoKey, AbstractConfigValue> memos;
 
-    SubstitutionResolver(AbstractConfigObject root) {
-        this.root = root;
+    SubstitutionResolver() {
         this.memos = new HashMap<MemoKey, AbstractConfigValue>();
     }
 
@@ -97,22 +95,18 @@ final class SubstitutionResolver {
         }
     }
 
-    AbstractConfigObject root() {
-        return this.root;
-    }
-
     static AbstractConfigValue resolve(AbstractConfigValue value, AbstractConfigObject root,
             ConfigResolveOptions options, Path restrictToChildOrNull) throws NotPossibleToResolve {
-        SubstitutionResolver resolver = new SubstitutionResolver(root);
-        ResolveContext context = new ResolveContext(options, restrictToChildOrNull);
+        SubstitutionResolver resolver = new SubstitutionResolver();
+        ResolveContext context = new ResolveContext(root, options, restrictToChildOrNull);
 
         return resolver.resolve(value, context);
     }
 
     static AbstractConfigValue resolveWithExternalExceptions(AbstractConfigValue value,
             AbstractConfigObject root, ConfigResolveOptions options) {
-        SubstitutionResolver resolver = new SubstitutionResolver(root);
-        ResolveContext context = new ResolveContext(options, null /* restrictToChild */);
+        SubstitutionResolver resolver = new SubstitutionResolver();
+        ResolveContext context = new ResolveContext(root, options, null /* restrictToChild */);
 
         try {
             return resolver.resolve(value, context);