diff --git a/src/main/java/com/typesafe/config/Config.java b/src/main/java/com/typesafe/config/Config.java
index 2eb6f834..04518802 100644
--- a/src/main/java/com/typesafe/config/Config.java
+++ b/src/main/java/com/typesafe/config/Config.java
@@ -1,9 +1,16 @@
 package com.typesafe.config;
 
+import java.io.File;
+import java.io.Reader;
+import java.net.URL;
+import java.util.Collection;
+import java.util.Map;
+import java.util.Properties;
 import java.util.concurrent.TimeUnit;
 
 import com.typesafe.config.impl.ConfigImpl;
 import com.typesafe.config.impl.ConfigUtil;
+import com.typesafe.config.impl.Parseable;
 
 /**
  * This class holds some global static methods for the config package.
@@ -11,23 +18,125 @@ import com.typesafe.config.impl.ConfigUtil;
 public final class Config {
 
     /**
-     * Loads a configuration for the given root path. The root path should be a
-     * short word that scopes the package being configured; typically it's the
+     * Loads a configuration for the given root path in a "standard" way.
+     * Oversimplified, if your root path is foo.bar then this will load files
+     * from the classpath: foo-bar.conf, foo-bar.json, foo-bar.properties,
+     * foo-bar-reference.conf, foo-bar-reference.json,
+     * foo-bar-reference.properties. It will override all those files with any
+     * system properties that begin with "foo.bar.", as well.
+     *
+     * The root path should be a path expression, usually just a single short
+     * word, that scopes the package being configured; typically it's the
      * package name or something similar. System properties overriding values in
      * the configuration will have to be prefixed with the root path. The root
      * path may have periods in it if you like but other punctuation or
      * whitespace will probably cause you headaches. Example root paths: "akka",
      * "sbt", "jsoup", "heroku", "mongo", etc.
      *
-     * This object will already be resolved (substitutions have already been
-     * processed).
+     * The loaded object will already be resolved (substitutions have already
+     * been processed). As a result, if you add more fallbacks then they won't
+     * be seen by substitutions. Substitutions are the "${foo.bar}" syntax. If
+     * you want to parse additional files or something then you need to use
+     * loadWithoutResolving().
      *
      * @param rootPath
      *            the configuration "domain"
      * @return configuration object for the requested root path
      */
     public static ConfigRoot load(String rootPath) {
-        return ConfigImpl.loadConfig(rootPath);
+        return loadWithoutResolving(rootPath).resolve();
+    }
+
+    /**
+     * Like load() but does not resolve the object, so you can go ahead and add
+     * more fallbacks and stuff and have them seen by substitutions when you do
+     * call {@link ConfigRoot.resolve()}.
+     *
+     * @param rootPath
+     * @return
+     */
+    public static ConfigRoot loadWithoutResolving(String rootPath) {
+        ConfigRoot system = systemPropertiesRoot(rootPath);
+
+        ConfigValue mainFiles = parse(rootPath, ConfigParseOptions.defaults());
+        ConfigValue referenceFiles = parse(rootPath + ".reference",
+                ConfigParseOptions.defaults());
+
+        return system.withFallbacks(mainFiles, referenceFiles);
+    }
+
+    public static ConfigRoot emptyRoot(String rootPath) {
+        return ConfigImpl.emptyRoot(rootPath);
+    }
+
+    public static ConfigRoot systemPropertiesRoot(String rootPath) {
+        return ConfigImpl.systemPropertiesRoot(rootPath);
+    }
+
+    public static ConfigObject systemProperties() {
+        return ConfigImpl.systemPropertiesAsConfig();
+    }
+
+    public static ConfigObject systemEnvironment() {
+        return ConfigImpl.envVariablesAsConfig();
+    }
+
+    public static ConfigObject parse(Properties properties,
+            ConfigParseOptions options) {
+        return ConfigImpl.parse(properties,
+                options.withFallbackOriginDescription("properties"));
+    }
+
+    public static ConfigObject parse(Reader reader, ConfigParseOptions options) {
+        Parseable p = Parseable.newReader(reader);
+        return ConfigImpl.parse(p,
+                options.withFallbackOriginDescription("Reader"));
+    }
+
+    public static ConfigObject parse(URL url, ConfigParseOptions options) {
+        Parseable p = Parseable.newURL(url);
+        return ConfigImpl.parse(p,
+                options.withFallbackOriginDescription(url.toExternalForm()));
+    }
+
+    public static ConfigObject parse(File file, ConfigParseOptions options) {
+        Parseable p = Parseable.newFile(file);
+        return ConfigImpl.parse(p,
+                options.withFallbackOriginDescription(file.getPath()));
+    }
+
+    public static ConfigObject parse(Class<?> klass, String resource,
+            ConfigParseOptions options) {
+        Parseable p = Parseable.newResource(klass, resource);
+        return ConfigImpl.parse(p,
+                options.withFallbackOriginDescription(resource));
+    }
+
+    /**
+     * Parses classpath resources corresponding to this path expression.
+     * Essentially if the path is "foo.bar" then the resources are
+     * "/foo-bar.conf", "/foo-bar.json", and "/foo-bar.properties". If more than
+     * one of those exists, they are merged.
+     *
+     * @param path
+     * @param options
+     * @return
+     */
+    public static ConfigObject parse(String path, ConfigParseOptions options) {
+        // null originDescription is allowed in parseResourcesForPath
+        return ConfigImpl.parseResourcesForPath(path, options);
+    }
+
+    public static ConfigValue fromAnyRef(Object object) {
+        throw new UnsupportedOperationException("not implemented yet");
+    }
+
+    public static ConfigObject fromMap(Map<String, ? extends Object> values) {
+        throw new UnsupportedOperationException("not implemented yet");
+    }
+
+    public static ConfigList fromCollection(Collection<? extends Object> values) {
+        throw new UnsupportedOperationException("not implemented yet");
     }
 
     private static String getUnits(String s) {
diff --git a/src/main/java/com/typesafe/config/ConfigObject.java b/src/main/java/com/typesafe/config/ConfigObject.java
index 1371c751..2f3743d5 100644
--- a/src/main/java/com/typesafe/config/ConfigObject.java
+++ b/src/main/java/com/typesafe/config/ConfigObject.java
@@ -35,6 +35,9 @@ public interface ConfigObject extends ConfigValue, Map<String, ConfigValue> {
     @Override
     ConfigObject withFallback(ConfigValue other);
 
+    @Override
+    ConfigObject withFallbacks(ConfigValue... others);
+
     boolean getBoolean(String path);
 
     Number getNumber(String path);
diff --git a/src/main/java/com/typesafe/config/ConfigParseOptions.java b/src/main/java/com/typesafe/config/ConfigParseOptions.java
new file mode 100644
index 00000000..818e3f46
--- /dev/null
+++ b/src/main/java/com/typesafe/config/ConfigParseOptions.java
@@ -0,0 +1,90 @@
+package com.typesafe.config;
+
+
+public final class ConfigParseOptions {
+    final ConfigSyntax syntax;
+    final String originDescription;
+    final boolean allowMissing;
+
+    protected ConfigParseOptions(ConfigSyntax syntax, String originDescription,
+            boolean allowMissing) {
+        this.syntax = syntax;
+        this.originDescription = originDescription;
+        this.allowMissing = allowMissing;
+    }
+
+    public static ConfigParseOptions defaults() {
+        return new ConfigParseOptions(null, null, true);
+    }
+
+    /**
+     * Set the file format. If set to null, try to guess from any available
+     * filename extension; if guessing fails, assume ConfigSyntax.CONF.
+     *
+     * @param syntax
+     * @return
+     */
+    public ConfigParseOptions setSyntax(ConfigSyntax syntax) {
+        if (this.syntax == syntax)
+            return this;
+        else
+            return new ConfigParseOptions(syntax, this.originDescription,
+                    this.allowMissing);
+    }
+
+    public ConfigSyntax getSyntax() {
+        return syntax;
+    }
+
+    /**
+     * Set a description for the thing being parsed. In most cases this will be
+     * set up for you to something like the filename, but if you provide just an
+     * input stream you might want to improve on it. Set to null to allow the
+     * library to come up with something automatically.
+     *
+     * @param originDescription
+     * @return
+     */
+    public ConfigParseOptions setOriginDescription(String originDescription) {
+        if (this.originDescription == originDescription)
+            return this;
+        else if (this.originDescription != null && originDescription != null
+                && this.originDescription.equals(originDescription))
+            return this;
+        else
+            return new ConfigParseOptions(this.syntax, originDescription,
+                    this.allowMissing);
+    }
+
+    public String getOriginDescription() {
+        return originDescription;
+    }
+
+    /** this is package-private, not public API */
+    ConfigParseOptions withFallbackOriginDescription(String originDescription) {
+        if (this.originDescription == null)
+            return setOriginDescription(originDescription);
+        else
+            return this;
+    }
+
+    /**
+     * Set to false to throw an exception if the item being parsed (for example
+     * a file) is missing. Set to true to just return an empty document in that
+     * case.
+     *
+     * @param allowMissing
+     * @return
+     */
+    public ConfigParseOptions setAllowMissing(boolean allowMissing) {
+        if (this.allowMissing == allowMissing)
+            return this;
+        else
+            return new ConfigParseOptions(this.syntax, this.originDescription,
+                    allowMissing);
+    }
+
+    public boolean getAllowMissing() {
+        return allowMissing;
+    }
+}
diff --git a/src/main/java/com/typesafe/config/ConfigResolveOptions.java b/src/main/java/com/typesafe/config/ConfigResolveOptions.java
new file mode 100644
index 00000000..91e3320c
--- /dev/null
+++ b/src/main/java/com/typesafe/config/ConfigResolveOptions.java
@@ -0,0 +1,36 @@
+package com.typesafe.config;
+
+public final class ConfigResolveOptions {
+    private final boolean useSystemProperties;
+    private final boolean useSystemEnvironment;
+
+    private ConfigResolveOptions(boolean useSystemProperties,
+            boolean useSystemEnvironment) {
+        this.useSystemProperties = useSystemProperties;
+        this.useSystemEnvironment = useSystemEnvironment;
+    }
+
+    public static ConfigResolveOptions defaults() {
+        return new ConfigResolveOptions(true, true);
+    }
+
+    public static ConfigResolveOptions noSystem() {
+        return new ConfigResolveOptions(false, false);
+    }
+
+    public ConfigResolveOptions setUseSystemProperties(boolean value) {
+        return new ConfigResolveOptions(value, useSystemEnvironment);
+    }
+
+    public ConfigResolveOptions setUseSystemEnvironment(boolean value) {
+        return new ConfigResolveOptions(useSystemProperties, value);
+    }
+
+    public boolean getUseSystemProperties() {
+        return useSystemProperties;
+    }
+
+    public boolean getUseSystemEnvironment() {
+        return useSystemEnvironment;
+    }
+}
diff --git a/src/main/java/com/typesafe/config/ConfigRoot.java b/src/main/java/com/typesafe/config/ConfigRoot.java
index 62e54d40..6e3f3695 100644
--- a/src/main/java/com/typesafe/config/ConfigRoot.java
+++ b/src/main/java/com/typesafe/config/ConfigRoot.java
@@ -10,12 +10,24 @@ public interface ConfigRoot extends ConfigObject {
      * Returns a replacement root object with all substitutions (the
      * "${foo.bar}" syntax) resolved. Substitutions are looked up in this root
      * object. A configuration value tree must be resolved before you can use
-     * it.
-     *
+     * it. This method uses ConfigResolveOptions.defaults().
+     * 
      * @return an immutable object with substitutions resolved
      */
     ConfigRoot resolve();
 
+    ConfigRoot resolve(ConfigResolveOptions options);
+
     @Override
     ConfigRoot withFallback(ConfigValue fallback);
+
+    @Override
+    ConfigRoot withFallbacks(ConfigValue... fallbacks);
+
+    /**
+     * Gets the global app name that this root represents.
+     *
+     * @return the app's root config path
+     */
+    String rootPath();
 }
diff --git a/src/main/java/com/typesafe/config/ConfigSyntax.java b/src/main/java/com/typesafe/config/ConfigSyntax.java
new file mode 100644
index 00000000..c1c68d07
--- /dev/null
+++ b/src/main/java/com/typesafe/config/ConfigSyntax.java
@@ -0,0 +1,5 @@
+package com.typesafe.config;
+
+public enum ConfigSyntax {
+    JSON, CONF, PROPERTIES;
+}
diff --git a/src/main/java/com/typesafe/config/ConfigValue.java b/src/main/java/com/typesafe/config/ConfigValue.java
index 0e0152fc..f830a8d0 100644
--- a/src/main/java/com/typesafe/config/ConfigValue.java
+++ b/src/main/java/com/typesafe/config/ConfigValue.java
@@ -1,6 +1,5 @@
 package com.typesafe.config;
 
-
 /**
  * Interface implemented by any configuration value. From the perspective of
  * users of this interface, the object is immutable. It is therefore safe to use
@@ -42,4 +41,13 @@ public interface ConfigValue {
      *         used)
      */
     ConfigValue withFallback(ConfigValue other);
+
+    /**
+     * Convenience method just calls withFallback() on each of the values;
+     * earlier values in the list win over later ones.
+     * 
+     * @param fallbacks
+     * @return a version of the object with the requested fallbacks merged in
+     */
+    ConfigValue withFallbacks(ConfigValue... fallbacks);
 }
diff --git a/src/main/java/com/typesafe/config/impl/AbstractConfigObject.java b/src/main/java/com/typesafe/config/impl/AbstractConfigObject.java
index 4344ca59..2c4d2621 100644
--- a/src/main/java/com/typesafe/config/impl/AbstractConfigObject.java
+++ b/src/main/java/com/typesafe/config/impl/AbstractConfigObject.java
@@ -16,7 +16,7 @@ import com.typesafe.config.ConfigException;
 import com.typesafe.config.ConfigList;
 import com.typesafe.config.ConfigObject;
 import com.typesafe.config.ConfigOrigin;
-import com.typesafe.config.ConfigRoot;
+import com.typesafe.config.ConfigResolveOptions;
 import com.typesafe.config.ConfigValue;
 import com.typesafe.config.ConfigValueType;
 
@@ -38,14 +38,20 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements
      *
      * @return a config root
      */
-    protected ConfigRoot asRoot() {
-        return new RootConfigObject(this);
+    protected ConfigRootImpl asRoot(Path rootPath) {
+        return new RootConfigObject(this, rootPath);
     }
 
-    protected static ConfigRoot resolve(ConfigRoot root) {
+    protected static ConfigRootImpl resolve(ConfigRootImpl root) {
+        return resolve(root, ConfigResolveOptions.defaults());
+    }
+
+    protected static ConfigRootImpl resolve(ConfigRootImpl root,
+            ConfigResolveOptions options) {
         AbstractConfigValue resolved = SubstitutionResolver.resolve(
-                (AbstractConfigValue) root, (AbstractConfigObject) root);
-        return ((AbstractConfigObject) resolved).asRoot();
+                (AbstractConfigValue) root, (AbstractConfigObject) root,
+                options);
+        return ((AbstractConfigObject) resolved).asRoot(root.rootPathObject());
     }
 
     /**
@@ -58,12 +64,12 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements
     protected abstract AbstractConfigValue peek(String key);
 
     protected AbstractConfigValue peek(String key,
-            SubstitutionResolver resolver,
-            int depth, boolean withFallbacks) {
+            SubstitutionResolver resolver, int depth,
+            ConfigResolveOptions options) {
         AbstractConfigValue v = peek(key);
 
         if (v != null && resolver != null) {
-            v = resolver.resolve(v, depth, withFallbacks);
+            v = resolver.resolve(v, depth, options);
         }
 
         return v;
@@ -75,18 +81,18 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements
      * resolver != null.
      */
     protected ConfigValue peekPath(Path path, SubstitutionResolver resolver,
-            int depth,
-            boolean withFallbacks) {
-        return peekPath(this, path, resolver, depth, withFallbacks);
+            int depth, ConfigResolveOptions options) {
+        return peekPath(this, path, resolver, depth, options);
     }
 
     private static ConfigValue peekPath(AbstractConfigObject self, Path path,
-            SubstitutionResolver resolver, int depth, boolean withFallbacks) {
+            SubstitutionResolver resolver, int depth,
+            ConfigResolveOptions options) {
         String key = path.first();
         Path next = path.remainder();
 
         if (next == null) {
-            ConfigValue v = self.peek(key, resolver, depth, withFallbacks);
+            ConfigValue v = self.peek(key, resolver, depth, options);
             return v;
         } else {
             // it's important to ONLY resolve substitutions here, not
@@ -98,13 +104,12 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements
             ConfigValue v = self.peek(key);
 
             if (v instanceof ConfigSubstitution && resolver != null) {
-                v = resolver.resolve((AbstractConfigValue) v, depth,
-                        withFallbacks);
+                v = resolver.resolve((AbstractConfigValue) v, depth, options);
             }
 
             if (v instanceof AbstractConfigObject) {
                 return peekPath((AbstractConfigObject) v, next, resolver,
-                        depth, withFallbacks);
+                        depth, options);
             } else {
                 return null;
             }
@@ -177,6 +182,11 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements
                 originalPath);
     }
 
+    @Override
+    public AbstractConfigObject withFallbacks(ConfigValue... others) {
+        return (AbstractConfigObject) super.withFallbacks(others);
+    }
+
     @Override
     public AbstractConfigObject withFallback(ConfigValue other) {
         if (other instanceof Unmergeable) {
@@ -291,7 +301,7 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements
     @Override
     AbstractConfigObject resolveSubstitutions(final SubstitutionResolver resolver,
             final int depth,
-            final boolean withFallbacks) {
+            final ConfigResolveOptions options) {
         if (resolveStatus() == ResolveStatus.RESOLVED)
             return this;
 
@@ -299,7 +309,7 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements
 
             @Override
             public AbstractConfigValue modifyChild(AbstractConfigValue v) {
-                return resolver.resolve(v, depth, withFallbacks);
+                return resolver.resolve(v, depth, options);
             }
 
         }, ResolveStatus.RESOLVED);
diff --git a/src/main/java/com/typesafe/config/impl/AbstractConfigValue.java b/src/main/java/com/typesafe/config/impl/AbstractConfigValue.java
index 12b6f3b6..12349eb3 100644
--- a/src/main/java/com/typesafe/config/impl/AbstractConfigValue.java
+++ b/src/main/java/com/typesafe/config/impl/AbstractConfigValue.java
@@ -1,6 +1,7 @@
 package com.typesafe.config.impl;
 
 import com.typesafe.config.ConfigOrigin;
+import com.typesafe.config.ConfigResolveOptions;
 import com.typesafe.config.ConfigValue;
 
 abstract class AbstractConfigValue implements ConfigValue {
@@ -24,13 +25,13 @@ abstract class AbstractConfigValue implements ConfigValue {
      * @param depth
      *            the number of substitutions followed in resolving the current
      *            one
-     * @param withFallbacks
+     * @param options
      *            whether to look at system props and env vars
      * @return a new value if there were changes, or this if no changes
      */
     AbstractConfigValue resolveSubstitutions(SubstitutionResolver resolver,
             int depth,
-            boolean withFallbacks) {
+            ConfigResolveOptions options) {
         return this;
     }
 
@@ -63,6 +64,15 @@ abstract class AbstractConfigValue implements ConfigValue {
         return this;
     }
 
+    @Override
+    public AbstractConfigValue withFallbacks(ConfigValue... fallbacks) {
+        AbstractConfigValue merged = this;
+        for (ConfigValue f : fallbacks) {
+            merged = merged.withFallback(f);
+        }
+        return merged;
+    }
+
     AbstractConfigValue transformed(ConfigTransformer transformer) {
         return this;
     }
diff --git a/src/main/java/com/typesafe/config/impl/ConfigDelayedMerge.java b/src/main/java/com/typesafe/config/impl/ConfigDelayedMerge.java
index 414eb6d3..ecde179a 100644
--- a/src/main/java/com/typesafe/config/impl/ConfigDelayedMerge.java
+++ b/src/main/java/com/typesafe/config/impl/ConfigDelayedMerge.java
@@ -6,6 +6,7 @@ import java.util.List;
 
 import com.typesafe.config.ConfigException;
 import com.typesafe.config.ConfigOrigin;
+import com.typesafe.config.ConfigResolveOptions;
 import com.typesafe.config.ConfigValue;
 import com.typesafe.config.ConfigValueType;
 
@@ -46,21 +47,20 @@ final class ConfigDelayedMerge extends AbstractConfigValue implements
 
     @Override
     AbstractConfigValue resolveSubstitutions(SubstitutionResolver resolver,
-            int depth, boolean withFallbacks) {
-        return resolveSubstitutions(stack, resolver, depth, withFallbacks);
+            int depth, ConfigResolveOptions options) {
+        return resolveSubstitutions(stack, resolver, depth, options);
     }
 
     // static method also used by ConfigDelayedMergeObject
     static AbstractConfigValue resolveSubstitutions(
             List<AbstractConfigValue> stack, SubstitutionResolver resolver,
-            int depth, boolean withFallbacks) {
+            int depth, ConfigResolveOptions options) {
         // to resolve substitutions, we need to recursively resolve
         // the stack of stuff to merge, and then merge the stack.
         List<AbstractConfigObject> toMerge = new ArrayList<AbstractConfigObject>();
 
         for (AbstractConfigValue v : stack) {
-            AbstractConfigValue resolved = resolver.resolve(v, depth,
-                    withFallbacks);
+            AbstractConfigValue resolved = resolver.resolve(v, depth, options);
 
             if (resolved instanceof AbstractConfigObject) {
                 toMerge.add((AbstractConfigObject) resolved);
diff --git a/src/main/java/com/typesafe/config/impl/ConfigDelayedMergeObject.java b/src/main/java/com/typesafe/config/impl/ConfigDelayedMergeObject.java
index f75a8c2a..7220b88f 100644
--- a/src/main/java/com/typesafe/config/impl/ConfigDelayedMergeObject.java
+++ b/src/main/java/com/typesafe/config/impl/ConfigDelayedMergeObject.java
@@ -8,7 +8,7 @@ import java.util.Set;
 
 import com.typesafe.config.ConfigException;
 import com.typesafe.config.ConfigOrigin;
-import com.typesafe.config.ConfigRoot;
+import com.typesafe.config.ConfigResolveOptions;
 import com.typesafe.config.ConfigValue;
 
 // This is just like ConfigDelayedMerge except we know statically
@@ -37,30 +37,56 @@ class ConfigDelayedMergeObject extends AbstractConfigObject implements
     }
 
     final private static class Root extends ConfigDelayedMergeObject implements
-            ConfigRoot {
-        Root(ConfigDelayedMergeObject original) {
+            ConfigRootImpl {
+        final private Path rootPath;
+
+        Root(ConfigDelayedMergeObject original, Path rootPath) {
             super(original.origin(), original.stack);
+            this.rootPath = rootPath;
         }
 
         @Override
-        protected Root asRoot() {
-            return this;
+        protected Root asRoot(Path newRootPath) {
+            if (newRootPath.equals(this.rootPath))
+                return this;
+            else
+                return new Root(this, newRootPath);
         }
 
         @Override
-        public ConfigRoot resolve() {
+        public ConfigRootImpl resolve() {
             return resolve(this);
         }
 
+        @Override
+        public ConfigRootImpl resolve(ConfigResolveOptions options) {
+            return resolve(this, options);
+        }
+
         @Override
         public Root withFallback(ConfigValue value) {
-            return super.withFallback(value).asRoot();
+            return super.withFallback(value).asRoot(rootPath);
+        }
+
+        @Override
+        public Root withFallbacks(ConfigValue... values) {
+            return super.withFallbacks(values).asRoot(rootPath);
+        }
+
+        @Override
+        public String rootPath() {
+            return rootPath.render();
+        }
+
+        @Override
+        public Path rootPathObject() {
+            return rootPath;
         }
     }
 
     @Override
-    protected Root asRoot() {
-        return new Root(this);
+    protected Root asRoot(Path rootPath) {
+        return new Root(this, rootPath);
     }
 
     @Override
@@ -74,10 +100,10 @@ class ConfigDelayedMergeObject extends AbstractConfigObject implements
 
     @Override
     AbstractConfigObject resolveSubstitutions(SubstitutionResolver resolver,
-            int depth, boolean withFallbacks) {
+            int depth, ConfigResolveOptions options) {
         AbstractConfigValue merged = ConfigDelayedMerge.resolveSubstitutions(
                 stack, resolver, depth,
-                withFallbacks);
+                options);
         if (merged instanceof AbstractConfigObject) {
             return (AbstractConfigObject) merged;
         } else {
@@ -122,6 +148,11 @@ class ConfigDelayedMergeObject extends AbstractConfigObject implements
         }
     }
 
+    @Override
+    public ConfigDelayedMergeObject withFallbacks(ConfigValue... others) {
+        return (ConfigDelayedMergeObject) super.withFallbacks(others);
+    }
+
     @Override
     public Collection<AbstractConfigValue> unmergedValues() {
         return stack;
diff --git a/src/main/java/com/typesafe/config/impl/ConfigImpl.java b/src/main/java/com/typesafe/config/impl/ConfigImpl.java
index 74b91cbc..b54495bb 100644
--- a/src/main/java/com/typesafe/config/impl/ConfigImpl.java
+++ b/src/main/java/com/typesafe/config/impl/ConfigImpl.java
@@ -1,71 +1,185 @@
 package com.typesafe.config.impl;
 
-import java.util.ArrayList;
 import java.util.HashMap;
-import java.util.List;
 import java.util.Map;
+import java.util.Properties;
 
 import com.typesafe.config.ConfigException;
 import com.typesafe.config.ConfigObject;
+import com.typesafe.config.ConfigOrigin;
+import com.typesafe.config.ConfigParseOptions;
 import com.typesafe.config.ConfigRoot;
+import com.typesafe.config.ConfigSyntax;
+import com.typesafe.config.ConfigValue;
 
 /** This is public but is only supposed to be used by the "config" package */
 public class ConfigImpl {
-    public static ConfigRoot loadConfig(String rootPath) {
-        ConfigTransformer transformer = withExtraTransformer(null);
-
-        AbstractConfigObject system = null;
-        try {
-            system = systemPropertiesConfig().getObject(rootPath);
-        } catch (ConfigException e) {
-            // no system props in the requested root path
-        }
-        List<AbstractConfigObject> stack = new ArrayList<AbstractConfigObject>();
-
-        // higher-priority configs are first
-        if (system != null)
-            stack.add(system.transformed(transformer));
-
-        // this is a conceptual placeholder for a customizable
-        // object that the app might be able to pass in.
-        IncludeHandler includer = defaultIncluder();
-
-        stack.add(includer.include(rootPath).transformed(
-                transformer));
-
-        AbstractConfigObject merged = AbstractConfigObject.merge(stack);
-
-        ConfigRoot resolved = merged.asRoot().resolve();
-
-        return resolved;
-    }
-
-    static ConfigObject getEnvironmentAsConfig() {
-        // This should not need to create a new config object
-        // as long as the transformer is just the default transformer.
-        return envVariablesConfig().transformed(withExtraTransformer(null));
-    }
-
-    static ConfigObject getSystemPropertiesAsConfig() {
-        // This should not need to create a new config object
-        // as long as the transformer is just the default transformer.
-        return systemPropertiesConfig().transformed(withExtraTransformer(null));
-    }
-
-    private static ConfigTransformer withExtraTransformer(
-            ConfigTransformer extraTransformer) {
-        // idea is to avoid creating a new, unique transformer if there's no
-        // extraTransformer
-        if (extraTransformer != null) {
-            List<ConfigTransformer> transformerStack = new ArrayList<ConfigTransformer>();
-            transformerStack.add(defaultConfigTransformer());
-            transformerStack.add(extraTransformer);
-            return new StackTransformer(transformerStack);
+    private static AbstractConfigObject forceParsedToObject(ConfigValue value) {
+        if (value instanceof AbstractConfigObject) {
+            return (AbstractConfigObject) value;
         } else {
-            return defaultConfigTransformer();
+            throw new ConfigException.WrongType(value.origin(), "",
+                    "object at file root", value.valueType().name());
         }
     }
 
+    static AbstractConfigValue parseValue(Parseable parseable,
+            ConfigParseOptions options) {
+        ConfigOrigin origin = new SimpleConfigOrigin(
+                options.getOriginDescription());
+        return Parser.parse(parseable, origin, options);
+    }
+
+    /** For use ONLY by library internals, DO NOT TOUCH not guaranteed ABI */
+    public static ConfigObject parse(Parseable parseable,
+            ConfigParseOptions options) {
+        return forceParsedToObject(parseValue(parseable, options));
+    }
+
+    private static String makeResourceBasename(Path path) {
+        StringBuilder sb = new StringBuilder("/");
+        String next = path.first();
+        Path remaining = path.remainder();
+        while (next != null) {
+            sb.append(next);
+            sb.append('-');
+
+            if (remaining == null)
+                break;
+
+            next = remaining.first();
+            remaining = remaining.remainder();
+        }
+        sb.setLength(sb.length() - 1); // chop extra hyphen
+        return sb.toString();
+    }
+
+    // ConfigParseOptions has a package-private method for this but I don't want
+    // to make it public
+    static ConfigOrigin originWithSuffix(ConfigParseOptions options,
+            String suffix) {
+        return new SimpleConfigOrigin(options.getOriginDescription() + suffix);
+    }
+
+    static String syntaxSuffix(ConfigSyntax syntax) {
+        switch (syntax) {
+        case PROPERTIES:
+            return ".properties";
+        case CONF:
+            return ".conf";
+        case JSON:
+            return ".json";
+        }
+        throw new ConfigException.BugOrBroken("not a valid ConfigSyntax: "
+                + syntax);
+    }
+
+    static AbstractConfigObject loadForResource(Class<?> loadClass,
+            String basename, ConfigSyntax syntax, ConfigParseOptions options) {
+        String suffix = syntaxSuffix(syntax);
+        String resource = basename + suffix;
+
+        // we want null rather than empty object if missingness is allowed,
+        // so we can handle it.
+        if (options.getAllowMissing()
+                && loadClass.getResource(resource) == null) {
+            return null;
+        } else {
+            return forceParsedToObject(Parser.parse(
+                    Parseable.newResource(loadClass, resource),
+                    originWithSuffix(options, suffix),
+                    options.setSyntax(syntax)));
+        }
+    }
+
+    static AbstractConfigObject checkAllowMissing(AbstractConfigObject obj,
+            ConfigOrigin origin, ConfigParseOptions options) {
+        if (obj == null) {
+            if (options.getAllowMissing()) {
+                return SimpleConfigObject.emptyMissing(origin);
+            } else {
+                throw new ConfigException.IO(origin,
+                        "Resource not found on classpath");
+            }
+        } else {
+            return obj;
+        }
+    }
+
+    /** For use ONLY by library internals, DO NOT TOUCH not guaranteed ABI */
+    public static ConfigObject parseResourcesForPath(String expression,
+            ConfigParseOptions baseOptions) {
+        Path path = Parser.parsePath(expression);
+        String basename = makeResourceBasename(path);
+
+        Class<?> loadClass = ConfigImpl.class;
+
+        ConfigParseOptions options;
+        if (baseOptions.getOriginDescription() != null)
+            options = baseOptions;
+        else
+            options = baseOptions.setOriginDescription(basename);
+
+        if (options.getSyntax() != null) {
+            ConfigSyntax syntax = options.getSyntax();
+            AbstractConfigObject obj = loadForResource(loadClass, basename,
+                    syntax, options);
+            return checkAllowMissing(obj, originWithSuffix(options, syntaxSuffix(syntax)), options);
+        } else {
+            // we want to try all three then
+
+            ConfigParseOptions allowMissing = options.setAllowMissing(true);
+            AbstractConfigObject conf = loadForResource(loadClass, basename,
+                    ConfigSyntax.CONF, allowMissing);
+            AbstractConfigObject json = loadForResource(loadClass, basename,
+                    ConfigSyntax.JSON, allowMissing);
+            AbstractConfigObject props = loadForResource(loadClass, basename,
+                    ConfigSyntax.PROPERTIES, allowMissing);
+
+            ConfigOrigin baseOrigin = new SimpleConfigOrigin(options
+                    .getOriginDescription());
+
+            if (!options.getAllowMissing() && conf == null && json == null && props == null) {
+                throw new ConfigException.IO(baseOrigin,
+                        "No config files {.conf,.json,.properties} found on classpath");
+            }
+
+            AbstractConfigObject merged = SimpleConfigObject
+                    .empty(baseOrigin);
+
+            if (conf != null)
+                merged = merged.withFallback(conf);
+            if (json != null)
+                merged = merged.withFallback(json);
+            if (props != null)
+                merged = merged.withFallback(props);
+
+            return merged;
+        }
+    }
+
+    /** For use ONLY by library internals, DO NOT TOUCH not guaranteed ABI */
+    public static ConfigRoot emptyRoot(String rootPath) {
+        return SimpleConfigObject.empty().asRoot(Parser.parsePath(rootPath));
+    }
+
+    /** For use ONLY by library internals, DO NOT TOUCH not guaranteed ABI */
+    public static ConfigRoot systemPropertiesRoot(String rootPath) {
+        Path path = Parser.parsePath(rootPath);
+        try {
+            return systemPropertiesAsConfig().getObject(rootPath).asRoot(path);
+        } catch (ConfigException.Missing e) {
+            return SimpleConfigObject.empty().asRoot(path);
+        }
+    }
+
+    /** For use ONLY by library internals, DO NOT TOUCH not guaranteed ABI */
+    public static ConfigObject parse(Properties properties,
+            ConfigParseOptions options) {
+        return Loader
+                .fromProperties(options.getOriginDescription(), properties);
+    }
+
     private static ConfigTransformer defaultTransformer = null;
 
     synchronized static ConfigTransformer defaultConfigTransformer() {
@@ -92,7 +206,8 @@ public class ConfigImpl {
 
     private static AbstractConfigObject systemProperties = null;
 
-    synchronized static AbstractConfigObject systemPropertiesConfig() {
+    /** For use ONLY by library internals, DO NOT TOUCH not guaranteed ABI */
+    public synchronized static AbstractConfigObject systemPropertiesAsConfig() {
         if (systemProperties == null) {
             systemProperties = loadSystemProperties();
         }
@@ -110,7 +225,8 @@ public class ConfigImpl {
 
     private static AbstractConfigObject envVariables = null;
 
-    synchronized static AbstractConfigObject envVariablesConfig() {
+    /** For use ONLY by library internals, DO NOT TOUCH not guaranteed ABI */
+    public synchronized static AbstractConfigObject envVariablesAsConfig() {
         if (envVariables == null) {
             envVariables = loadEnvVariables();
         }
diff --git a/src/main/java/com/typesafe/config/impl/ConfigRootImpl.java b/src/main/java/com/typesafe/config/impl/ConfigRootImpl.java
new file mode 100644
index 00000000..a55ed055
--- /dev/null
+++ b/src/main/java/com/typesafe/config/impl/ConfigRootImpl.java
@@ -0,0 +1,11 @@
+package com.typesafe.config.impl;
+
+import com.typesafe.config.ConfigRoot;
+import com.typesafe.config.ConfigValue;
+
+interface ConfigRootImpl extends ConfigRoot {
+    Path rootPathObject();
+
+    @Override
+    ConfigRootImpl withFallbacks(ConfigValue... fallbacks);
+}
diff --git a/src/main/java/com/typesafe/config/impl/ConfigSubstitution.java b/src/main/java/com/typesafe/config/impl/ConfigSubstitution.java
index 20b1a89e..ff1350d6 100644
--- a/src/main/java/com/typesafe/config/impl/ConfigSubstitution.java
+++ b/src/main/java/com/typesafe/config/impl/ConfigSubstitution.java
@@ -7,6 +7,7 @@ import java.util.List;
 
 import com.typesafe.config.ConfigException;
 import com.typesafe.config.ConfigOrigin;
+import com.typesafe.config.ConfigResolveOptions;
 import com.typesafe.config.ConfigValue;
 import com.typesafe.config.ConfigValueType;
 
@@ -85,7 +86,7 @@ final class ConfigSubstitution extends AbstractConfigValue implements
     private ConfigValue findInObject(AbstractConfigObject root,
             SubstitutionResolver resolver, /* null if we should not have refs */
             Path subst, int depth,
-            boolean withFallbacks) {
+ ConfigResolveOptions options) {
         if (depth > MAX_DEPTH) {
             throw new ConfigException.BadValue(origin(), subst.render(),
                     "Substitution ${" + subst.render()
@@ -93,7 +94,7 @@ final class ConfigSubstitution extends AbstractConfigValue implements
         }
 
         ConfigValue result = root.peekPath(subst, resolver, depth,
-                    withFallbacks);
+ options);
 
         if (result instanceof ConfigSubstitution) {
             throw new ConfigException.BugOrBroken(
@@ -108,31 +109,34 @@ final class ConfigSubstitution extends AbstractConfigValue implements
     }
 
     private ConfigValue resolve(SubstitutionResolver resolver, Path subst,
-            int depth, boolean withFallbacks) {
+            int depth, ConfigResolveOptions options) {
         ConfigValue result = findInObject(resolver.root(), resolver, subst,
-                depth, withFallbacks);
-        if (withFallbacks) {
-            // when looking up system props and env variables,
-            // we don't want the prefix that was added when
-            // we were included in another file.
-            Path unprefixed = subst.subPath(prefixLength);
-            if (result == null) {
-                result = findInObject(ConfigImpl.systemPropertiesConfig(),
-                        null, unprefixed, depth, withFallbacks);
-            }
-            if (result == null) {
-                result = findInObject(ConfigImpl.envVariablesConfig(), null,
-                        unprefixed, depth, withFallbacks);
-            }
+                depth, options);
+
+        // when looking up system props and env variables,
+        // we don't want the prefix that was added when
+        // we were included in another file.
+        Path unprefixed = subst.subPath(prefixLength);
+
+        if (result == null && options.getUseSystemProperties()) {
+            result = findInObject(ConfigImpl.systemPropertiesAsConfig(), null,
+                    unprefixed, depth, options);
         }
+
+        if (result == null && options.getUseSystemEnvironment()) {
+                result = findInObject(ConfigImpl.envVariablesAsConfig(), null,
+                    unprefixed, depth, options);
+        }
+
         if (result == null) {
             result = new ConfigNull(origin());
         }
+
         return result;
     }
 
     private ConfigValue resolve(SubstitutionResolver resolver, int depth,
-            boolean withFallbacks) {
+            ConfigResolveOptions options) {
         if (pieces.size() > 1) {
             // need to concat everything into a string
             StringBuilder sb = new StringBuilder();
@@ -140,8 +144,7 @@ final class ConfigSubstitution extends AbstractConfigValue implements
                 if (p instanceof String) {
                     sb.append((String) p);
                 } else {
-                    ConfigValue v = resolve(resolver, (Path) p,
-                            depth, withFallbacks);
+                    ConfigValue v = resolve(resolver, (Path) p, depth, options);
                     switch (v.valueType()) {
                     case NULL:
                         // nothing; becomes empty string
@@ -162,19 +165,18 @@ final class ConfigSubstitution extends AbstractConfigValue implements
             if (!(pieces.get(0) instanceof Path))
                 throw new ConfigException.BugOrBroken(
                         "ConfigSubstitution should never contain a single String piece");
-            return resolve(resolver, (Path) pieces.get(0), depth,
-                    withFallbacks);
+            return resolve(resolver, (Path) pieces.get(0), depth, options);
         }
     }
 
     @Override
     AbstractConfigValue resolveSubstitutions(SubstitutionResolver resolver,
             int depth,
-            boolean withFallbacks) {
+            ConfigResolveOptions options) {
         // only ConfigSubstitution adds to depth here, because the depth
         // is the substitution depth not the recursion depth
         AbstractConfigValue resolved = (AbstractConfigValue) resolve(resolver,
-                depth + 1, withFallbacks);
+                depth + 1, options);
         return resolved;
     }
 
diff --git a/src/main/java/com/typesafe/config/impl/DelegatingConfigObject.java b/src/main/java/com/typesafe/config/impl/DelegatingConfigObject.java
index 801723c2..033fadd8 100644
--- a/src/main/java/com/typesafe/config/impl/DelegatingConfigObject.java
+++ b/src/main/java/com/typesafe/config/impl/DelegatingConfigObject.java
@@ -29,6 +29,14 @@ abstract class DelegatingConfigObject extends AbstractConfigObject {
         return underlying.resolveStatus();
     }
 
+    @Override
+    final protected ConfigRootImpl asRoot(Path rootPath) {
+        return asRoot(underlying, rootPath);
+    }
+
+    protected abstract ConfigRootImpl asRoot(AbstractConfigObject underlying,
+            Path rootPath);
+
     @Override
     public boolean containsKey(Object key) {
         return underlying.containsKey(key);
@@ -54,6 +62,11 @@ abstract class DelegatingConfigObject extends AbstractConfigObject {
         return underlying.withFallback(value);
     }
 
+    @Override
+    public AbstractConfigObject withFallbacks(ConfigValue... values) {
+        return underlying.withFallbacks(values);
+    }
+
     @Override
     public boolean containsValue(Object value) {
         return underlying.containsValue(value);
diff --git a/src/main/java/com/typesafe/config/impl/Loader.java b/src/main/java/com/typesafe/config/impl/Loader.java
index 0061f1a6..9f7ea06f 100644
--- a/src/main/java/com/typesafe/config/impl/Loader.java
+++ b/src/main/java/com/typesafe/config/impl/Loader.java
@@ -14,7 +14,13 @@ import java.util.Properties;
 
 import com.typesafe.config.ConfigException;
 import com.typesafe.config.ConfigOrigin;
+import com.typesafe.config.ConfigParseOptions;
 
+/**
+ * FIXME this file needs to die; the load() part should use the code in
+ * ConfigImpl instead and the properties stuff should be in its own file.
+ * 
+ */
 final class Loader {
     static AbstractConfigObject load(String name, IncludeHandler includer) {
         List<AbstractConfigObject> stack = new ArrayList<AbstractConfigObject>();
@@ -65,7 +71,10 @@ final class Loader {
             }
             return fromProperties(url.toExternalForm(), props);
         } else {
-            return forceParsedToObject(Parser.parse(url, includer));
+            return forceParsedToObject(Parser.parse(Parseable.newURL(url),
+                    new SimpleConfigOrigin(url.toExternalForm()),
+                    ConfigParseOptions.defaults(),
+                    includer));
         }
     }
 
diff --git a/src/main/java/com/typesafe/config/impl/Parseable.java b/src/main/java/com/typesafe/config/impl/Parseable.java
new file mode 100644
index 00000000..465c4a86
--- /dev/null
+++ b/src/main/java/com/typesafe/config/impl/Parseable.java
@@ -0,0 +1,227 @@
+package com.typesafe.config.impl;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FilterReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.io.StringReader;
+import java.io.UnsupportedEncodingException;
+import java.net.URL;
+
+import com.typesafe.config.ConfigException;
+import com.typesafe.config.ConfigSyntax;
+
+/**
+ * This is public but it's only for use by the config package; DO NOT TOUCH. The
+ * point of this class is to avoid "propagating" each overload on
+ * "thing which can be parsed" through multiple interfaces. Most interfaces can
+ * have just one overload that takes a Parseable.
+ */
+public abstract class Parseable {
+    protected Parseable() {
+    }
+
+    // the general idea is that any work should be in here, not in the
+    // constructor,
+    // so that exceptions are thrown from the public parse() function and not
+    // from the creation of the Parseable. Essentially this is a lazy field.
+    // The parser should close the reader when it's done with it.
+    // ALSO, IMPORTANT: if the file or URL is not found, this must throw.
+    // to support the "allow missing" feature.
+    abstract Reader reader() throws IOException;
+
+    ConfigSyntax guessSyntax() {
+        return null;
+    }
+
+    @Override
+    public String toString() {
+        return getClass().getSimpleName();
+    }
+
+    private static ConfigSyntax syntaxFromExtension(String name) {
+        if (name.endsWith(".json"))
+            return ConfigSyntax.JSON;
+        else if (name.endsWith(".conf"))
+            return ConfigSyntax.CONF;
+        else if (name.endsWith(".properties"))
+            return ConfigSyntax.PROPERTIES;
+        else
+            return null;
+    }
+
+    private static Reader readerFromStream(InputStream input) {
+        try {
+            // well, this is messed up. If we aren't going to close
+            // the passed-in InputStream then we have no way to
+            // close these readers. So maybe we should not have an
+            // InputStream version, only a Reader version.
+            Reader reader = new InputStreamReader(input, "UTF-8");
+            return new BufferedReader(reader);
+        } catch (UnsupportedEncodingException e) {
+            throw new ConfigException.BugOrBroken(
+                    "Java runtime does not support UTF-8", e);
+        }
+    }
+
+    private static Reader doNotClose(Reader input) {
+        return new FilterReader(input) {
+            @Override
+            public void close() {
+                // NOTHING.
+            }
+        };
+    }
+
+    private final static class ParseableInputStream extends Parseable {
+        final private InputStream input;
+
+        ParseableInputStream(InputStream input) {
+            this.input = input;
+        }
+
+        @Override
+        Reader reader() {
+            return doNotClose(readerFromStream(input));
+        }
+    }
+
+    /**
+     * note that we will never close this stream; you have to do it when parsing
+     * is complete.
+     */
+    public static Parseable newInputStream(InputStream input) {
+        return new ParseableInputStream(input);
+    }
+
+    private final static class ParseableReader extends Parseable {
+        final private Reader reader;
+
+        ParseableReader(Reader reader) {
+            this.reader = reader;
+        }
+
+        @Override
+        Reader reader() {
+            return reader;
+        }
+    }
+
+    /**
+     * note that we will never close this reader; you have to do it when parsing
+     * is complete.
+     */
+    public static Parseable newReader(Reader reader) {
+        return new ParseableReader(doNotClose(reader));
+    }
+
+    private final static class ParseableString extends Parseable {
+        final private String input;
+
+        ParseableString(String input) {
+            this.input = input;
+        }
+
+        @Override
+        Reader reader() {
+            return new StringReader(input);
+        }
+    }
+
+    public static Parseable newString(String input) {
+        return new ParseableString(input);
+    }
+
+    private final static class ParseableURL extends Parseable {
+        final private URL input;
+
+        ParseableURL(URL input) {
+            this.input = input;
+        }
+
+        @Override
+        Reader reader() throws IOException {
+            InputStream stream = input.openStream();
+            return readerFromStream(stream);
+        }
+
+        @Override
+        ConfigSyntax guessSyntax() {
+            return syntaxFromExtension(input.getPath());
+        }
+
+        @Override
+        public String toString() {
+            return getClass().getSimpleName() + "(" + input.toExternalForm()
+                    + ")";
+        }
+    }
+
+    public static Parseable newURL(URL input) {
+        return new ParseableURL(input);
+    }
+
+    private final static class ParseableFile extends Parseable {
+        final private File input;
+
+        ParseableFile(File input) {
+            this.input = input;
+        }
+
+        @Override
+        Reader reader() throws IOException {
+            InputStream stream = new FileInputStream(input);
+            return readerFromStream(stream);
+        }
+
+        @Override
+        ConfigSyntax guessSyntax() {
+            return syntaxFromExtension(input.getName());
+        }
+
+        @Override
+        public String toString() {
+            return getClass().getSimpleName() + "(" + input.getPath() + ")";
+        }
+    }
+
+    public static Parseable newFile(File input) {
+        return new ParseableFile(input);
+    }
+
+    private final static class ParseableResource extends Parseable {
+        final private Class<?> klass;
+        final private String resource;
+
+        ParseableResource(Class<?> klass, String resource) {
+            this.klass = klass;
+            this.resource = resource;
+        }
+
+        @Override
+        Reader reader() throws IOException {
+            InputStream stream = klass.getResourceAsStream(resource);
+            return readerFromStream(stream);
+        }
+
+        @Override
+        ConfigSyntax guessSyntax() {
+            return syntaxFromExtension(resource);
+        }
+
+        @Override
+        public String toString() {
+            return getClass().getSimpleName() + "(" + resource + ","
+                    + klass.getName()
+                    + ")";
+        }
+    }
+
+    public static Parseable newResource(Class<?> klass, String resource) {
+        return new ParseableResource(klass, resource);
+    }
+}
diff --git a/src/main/java/com/typesafe/config/impl/Parser.java b/src/main/java/com/typesafe/config/impl/Parser.java
index 5fc453ea..16fde62c 100644
--- a/src/main/java/com/typesafe/config/impl/Parser.java
+++ b/src/main/java/com/typesafe/config/impl/Parser.java
@@ -1,15 +1,8 @@
 package com.typesafe.config.impl;
 
-import java.io.BufferedReader;
-import java.io.File;
 import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
 import java.io.Reader;
 import java.io.StringReader;
-import java.io.UnsupportedEncodingException;
-import java.net.MalformedURLException;
-import java.net.URL;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
@@ -22,94 +15,52 @@ import java.util.Stack;
 
 import com.typesafe.config.ConfigException;
 import com.typesafe.config.ConfigOrigin;
+import com.typesafe.config.ConfigParseOptions;
+import com.typesafe.config.ConfigSyntax;
 import com.typesafe.config.ConfigValueType;
 
 final class Parser {
-    /**
-     * Parses an input stream, which must be in UTF-8 encoding and should not be
-     * buffered because we'll use a BufferedReader instead. Does not close the
-     * stream; you have to arrange to do that yourself.
-     */
-    static AbstractConfigValue parse(SyntaxFlavor flavor, ConfigOrigin origin,
-            InputStream input, IncludeHandler includer) {
-        try {
-            // well, this is messed up. If we aren't going to close
-            // the passed-in InputStream then we have no way to
-            // close these readers. So maybe we should not have an
-            // InputStream version, only a Reader version.
-            Reader reader = new InputStreamReader(input, "UTF-8");
-            reader = new BufferedReader(reader);
-            return parse(flavor, origin, reader,
-                    includer);
-        } catch (UnsupportedEncodingException e) {
-            throw new ConfigException.BugOrBroken(
-                    "Java runtime does not support UTF-8", e);
+
+    static AbstractConfigValue parse(Parseable input, ConfigOrigin origin,
+            ConfigParseOptions options) {
+        return parse(input, origin, options, ConfigImpl.defaultIncluder());
+    }
+
+    static AbstractConfigValue parse(Parseable input, ConfigOrigin origin,
+            ConfigParseOptions baseOptions, IncludeHandler includer) {
+        ConfigSyntax syntax = baseOptions.getSyntax();
+        if (syntax == null) {
+            syntax = input.guessSyntax();
         }
-    }
-
-    static AbstractConfigValue parse(SyntaxFlavor flavor, ConfigOrigin origin,
-            Reader input, IncludeHandler includer) {
-        Iterator<Token> tokens = Tokenizer.tokenize(origin, input, flavor);
-        return parse(flavor, origin, tokens, includer);
-    }
-
-    static AbstractConfigValue parse(SyntaxFlavor flavor, ConfigOrigin origin,
-            String input, IncludeHandler includer) {
-        return parse(flavor, origin, new StringReader(input), includer);
-    }
-
-    private static SyntaxFlavor flavorFromExtension(String name,
-            ConfigOrigin origin) {
-        if (name.endsWith(".json"))
-            return SyntaxFlavor.JSON;
-        else if (name.endsWith(".conf"))
-            return SyntaxFlavor.CONF;
-        else
-            throw new ConfigException.IO(origin, "Unknown filename extension");
-    }
-
-    static AbstractConfigValue parse(File f, IncludeHandler includer) {
-        return parse(null, f, includer);
-    }
-
-    static AbstractConfigValue parse(SyntaxFlavor flavor, File f,
-            IncludeHandler includer) {
-        ConfigOrigin origin = new SimpleConfigOrigin(f.getPath());
-        try {
-            return parse(flavor, origin, f.toURI().toURL(), includer);
-        } catch (MalformedURLException e) {
-            throw new ConfigException.IO(origin,
-                    "failed to create url from file path", e);
+        if (syntax == null) {
+            syntax = ConfigSyntax.CONF;
         }
-    }
+        ConfigParseOptions options = baseOptions.setSyntax(syntax);
 
-    static AbstractConfigValue parse(URL url, IncludeHandler includer) {
-        return parse(null, url, includer);
-    }
-
-    static AbstractConfigValue parse(SyntaxFlavor flavor, URL url,
-            IncludeHandler includer) {
-        ConfigOrigin origin = new SimpleConfigOrigin(url.toExternalForm());
-        return parse(flavor, origin, url, includer);
-    }
-
-    static AbstractConfigValue parse(SyntaxFlavor flavor, ConfigOrigin origin,
-            URL url, IncludeHandler includer) {
-        AbstractConfigValue result = null;
         try {
-            InputStream stream = url.openStream();
+            Reader reader = input.reader();
             try {
-                result = parse(
-                        flavor != null ? flavor : flavorFromExtension(
-                                url.getPath(), origin), origin, stream,
-                        includer);
+                Iterator<Token> tokens = Tokenizer.tokenize(origin, reader,
+                        syntax);
+                return parse(tokens, origin, options, includer);
             } finally {
-                stream.close();
+                reader.close();
             }
         } catch (IOException e) {
-            throw new ConfigException.IO(origin, "failed to read url", e);
+            if (options.getAllowMissing()) {
+                return SimpleConfigObject.emptyMissing(origin);
+            } else {
+                throw new ConfigException.IO(origin, e.getMessage(), e);
+            }
         }
-        return result;
+    }
+
+    private static AbstractConfigValue parse(Iterator<Token> tokens,
+            ConfigOrigin origin, ConfigParseOptions options,
+            IncludeHandler includer) {
+        ParseContext context = new ParseContext(options.getSyntax(), origin,
+                tokens, includer);
+        return context.parse();
     }
 
     static private final class ParseContext {
@@ -117,11 +68,11 @@ final class Parser {
         final private Stack<Token> buffer;
         final private Iterator<Token> tokens;
         final private IncludeHandler includer;
-        final private SyntaxFlavor flavor;
+        final private ConfigSyntax flavor;
         final private ConfigOrigin baseOrigin;
         final private LinkedList<Path> pathStack;
 
-        ParseContext(SyntaxFlavor flavor, ConfigOrigin origin,
+        ParseContext(ConfigSyntax flavor, ConfigOrigin origin,
                 Iterator<Token> tokens, IncludeHandler includer) {
             lineNumber = 0;
             buffer = new Stack<Token>();
@@ -140,7 +91,7 @@ final class Parser {
                 t = buffer.pop();
             }
 
-            if (flavor == SyntaxFlavor.JSON) {
+            if (flavor == ConfigSyntax.JSON) {
                 if (Tokens.isUnquotedText(t)) {
                     throw parseError("Token not allowed in valid JSON: '"
                         + Tokens.getUnquotedText(t) + "'");
@@ -172,7 +123,7 @@ final class Parser {
         // either a newline or a comma. The iterator
         // is left just after the comma or the newline.
         private boolean checkElementSeparator() {
-            if (flavor == SyntaxFlavor.JSON) {
+            if (flavor == ConfigSyntax.JSON) {
                 Token t = nextTokenIgnoringNewline();
                 if (t == Tokens.COMMA) {
                     return true;
@@ -206,7 +157,7 @@ final class Parser {
         // value.
         private void consolidateValueTokens() {
             // this trick is not done in JSON
-            if (flavor == SyntaxFlavor.JSON)
+            if (flavor == ConfigSyntax.JSON)
                 return;
 
             List<Token> values = null; // create only if we have value tokens
@@ -351,7 +302,7 @@ final class Parser {
         }
 
         private Path parseKey(Token token) {
-            if (flavor == SyntaxFlavor.JSON) {
+            if (flavor == ConfigSyntax.JSON) {
                 if (Tokens.isValueWithType(token, ConfigValueType.STRING)) {
                     String key = (String) Tokens.getValue(token).unwrapped();
                     return Path.newKey(key);
@@ -422,7 +373,7 @@ final class Parser {
         }
 
         private boolean isKeyValueSeparatorToken(Token t) {
-            if (flavor == SyntaxFlavor.JSON) {
+            if (flavor == ConfigSyntax.JSON) {
                 return t == Tokens.COLON;
             } else {
                 return t == Tokens.COLON || t == Tokens.EQUALS;
@@ -437,7 +388,7 @@ final class Parser {
             while (true) {
                 Token t = nextTokenIgnoringNewline();
                 if (t == Tokens.CLOSE_CURLY) {
-                    if (flavor == SyntaxFlavor.JSON && afterComma) {
+                    if (flavor == ConfigSyntax.JSON && afterComma) {
                         throw parseError("expecting a field name after comma, got a close brace }");
                     } else if (!hadOpenCurly) {
                         throw parseError("unbalanced close brace '}' with no open brace");
@@ -446,7 +397,7 @@ final class Parser {
                 } else if (t == Tokens.END && !hadOpenCurly) {
                     putBack(t);
                     break;
-                } else if (flavor != SyntaxFlavor.JSON && isIncludeKeyword(t)) {
+                } else if (flavor != ConfigSyntax.JSON && isIncludeKeyword(t)) {
                     parseInclude(values);
 
                     afterComma = false;
@@ -459,7 +410,7 @@ final class Parser {
 
                     Token valueToken;
                     AbstractConfigValue newValue;
-                    if (flavor == SyntaxFlavor.CONF
+                    if (flavor == ConfigSyntax.CONF
                             && afterKey == Tokens.OPEN_CURLY) {
                         // can omit the ':' or '=' before an object value
                         valueToken = afterKey;
@@ -488,7 +439,7 @@ final class Parser {
                             // if the value is an object (or substitution that
                             // could become an object).
 
-                            if (flavor == SyntaxFlavor.JSON) {
+                            if (flavor == ConfigSyntax.JSON) {
                                 throw parseError("JSON does not allow duplicate fields: '"
                                     + key
                                     + "' was already seen at "
@@ -499,7 +450,7 @@ final class Parser {
                         }
                         values.put(key, newValue);
                     } else {
-                        if (flavor == SyntaxFlavor.JSON) {
+                        if (flavor == ConfigSyntax.JSON) {
                             throw new ConfigException.BugOrBroken(
                                     "somehow got multi-element path in JSON mode");
                         }
@@ -593,7 +544,7 @@ final class Parser {
                     values.add(parseObject(true));
                 } else if (t == Tokens.OPEN_SQUARE) {
                     values.add(parseArray());
-                } else if (flavor != SyntaxFlavor.JSON
+                } else if (flavor != ConfigSyntax.JSON
                         && t == Tokens.CLOSE_SQUARE) {
                     // we allow one trailing comma
                     putBack(t);
@@ -620,7 +571,7 @@ final class Parser {
             } else if (t == Tokens.OPEN_SQUARE) {
                 result = parseArray();
             } else {
-                if (flavor == SyntaxFlavor.JSON) {
+                if (flavor == ConfigSyntax.JSON) {
                     if (t == Tokens.END) {
                         throw parseError("Empty document");
                     } else {
@@ -646,13 +597,6 @@ final class Parser {
         }
     }
 
-    private static AbstractConfigValue parse(SyntaxFlavor flavor,
-            ConfigOrigin origin, Iterator<Token> tokens, IncludeHandler includer) {
-        ParseContext context = new ParseContext(flavor, origin, tokens,
-                includer);
-        return context.parse();
-    }
-
     static class Element {
         StringBuilder sb;
         // an element can be empty if it has a quoted empty string "" in it
@@ -762,7 +706,7 @@ final class Parser {
 
         try {
             Iterator<Token> tokens = Tokenizer.tokenize(apiOrigin, reader,
-                    SyntaxFlavor.CONF);
+                    ConfigSyntax.CONF);
             tokens.next(); // drop START
             return parsePathExpression(tokens, apiOrigin);
         } finally {
diff --git a/src/main/java/com/typesafe/config/impl/RootConfigObject.java b/src/main/java/com/typesafe/config/impl/RootConfigObject.java
index f6a7ff6b..a1d8156a 100644
--- a/src/main/java/com/typesafe/config/impl/RootConfigObject.java
+++ b/src/main/java/com/typesafe/config/impl/RootConfigObject.java
@@ -1,35 +1,61 @@
 package com.typesafe.config.impl;
 
-import com.typesafe.config.ConfigRoot;
+import com.typesafe.config.ConfigResolveOptions;
 import com.typesafe.config.ConfigValue;
 
 final class RootConfigObject extends DelegatingConfigObject implements
-        ConfigRoot {
+        ConfigRootImpl {
 
-    RootConfigObject(AbstractConfigObject underlying) {
+    final private Path rootPath;
+
+    RootConfigObject(AbstractConfigObject underlying, Path rootPath) {
         super(underlying.transformer, underlying);
+        this.rootPath = rootPath;
     }
 
     @Override
-    protected ConfigRoot asRoot() {
-        return this;
+    protected ConfigRootImpl asRoot(AbstractConfigObject underlying,
+            Path newRootPath) {
+        if (newRootPath.equals(this.rootPath))
+            return this;
+        else
+            return new RootConfigObject(underlying, newRootPath);
     }
 
     @Override
     public RootConfigObject newCopy(AbstractConfigObject underlying,
             ConfigTransformer newTransformer, ResolveStatus newStatus) {
         return new RootConfigObject(underlying.newCopy(newTransformer,
-                newStatus));
+                newStatus), rootPath);
     }
 
     @Override
-    public ConfigRoot resolve() {
-        return ((AbstractConfigObject) SubstitutionResolver.resolve(this, this))
-                .asRoot();
+    public ConfigRootImpl resolve() {
+        return resolve(this);
+    }
+
+    @Override
+    public ConfigRootImpl resolve(ConfigResolveOptions options) {
+        return resolve(this, options);
     }
 
     @Override
     public RootConfigObject withFallback(ConfigValue value) {
-        return new RootConfigObject(super.withFallback(value));
+        return new RootConfigObject(super.withFallback(value), rootPath);
+    }
+
+    @Override
+    public RootConfigObject withFallbacks(ConfigValue... values) {
+        return new RootConfigObject(super.withFallbacks(values), rootPath);
+    }
+
+    @Override
+    public Path rootPathObject() {
+        return rootPath;
+    }
+
+    @Override
+    public String rootPath() {
+        return rootPath.render();
     }
 }
diff --git a/src/main/java/com/typesafe/config/impl/SimpleConfigList.java b/src/main/java/com/typesafe/config/impl/SimpleConfigList.java
index f02c2402..aa341eb5 100644
--- a/src/main/java/com/typesafe/config/impl/SimpleConfigList.java
+++ b/src/main/java/com/typesafe/config/impl/SimpleConfigList.java
@@ -9,6 +9,7 @@ import java.util.ListIterator;
 import com.typesafe.config.ConfigException;
 import com.typesafe.config.ConfigList;
 import com.typesafe.config.ConfigOrigin;
+import com.typesafe.config.ConfigResolveOptions;
 import com.typesafe.config.ConfigValue;
 import com.typesafe.config.ConfigValueType;
 
@@ -84,14 +85,14 @@ final class SimpleConfigList extends AbstractConfigValue implements ConfigList {
 
     @Override
     SimpleConfigList resolveSubstitutions(final SubstitutionResolver resolver,
-            final int depth, final boolean withFallbacks) {
+            final int depth, final ConfigResolveOptions options) {
         if (resolved)
             return this;
 
         return modify(new Modifier() {
             @Override
             public AbstractConfigValue modifyChild(AbstractConfigValue v) {
-                return resolver.resolve(v, depth, withFallbacks);
+                return resolver.resolve(v, depth, options);
             }
 
         }, ResolveStatus.RESOLVED);
diff --git a/src/main/java/com/typesafe/config/impl/SimpleConfigObject.java b/src/main/java/com/typesafe/config/impl/SimpleConfigObject.java
index 1c2a0ba2..83ca5d91 100644
--- a/src/main/java/com/typesafe/config/impl/SimpleConfigObject.java
+++ b/src/main/java/com/typesafe/config/impl/SimpleConfigObject.java
@@ -117,4 +117,15 @@ final class SimpleConfigObject extends AbstractConfigObject {
         return new SimpleConfigObject(new SimpleConfigOrigin("empty config"),
                 Collections.<String, AbstractConfigValue> emptyMap());
     }
+
+    final static SimpleConfigObject empty(ConfigOrigin origin) {
+        return new SimpleConfigObject(origin,
+                Collections.<String, AbstractConfigValue> emptyMap());
+    }
+
+    final static SimpleConfigObject emptyMissing(ConfigOrigin baseOrigin) {
+        return new SimpleConfigObject(new SimpleConfigOrigin(
+                baseOrigin.description() + " (not found)"),
+                Collections.<String, AbstractConfigValue> emptyMap());
+    }
 }
diff --git a/src/main/java/com/typesafe/config/impl/SubstitutionResolver.java b/src/main/java/com/typesafe/config/impl/SubstitutionResolver.java
index fd321143..1ec58ce6 100644
--- a/src/main/java/com/typesafe/config/impl/SubstitutionResolver.java
+++ b/src/main/java/com/typesafe/config/impl/SubstitutionResolver.java
@@ -4,6 +4,7 @@ import java.util.IdentityHashMap;
 import java.util.Map;
 
 import com.typesafe.config.ConfigException;
+import com.typesafe.config.ConfigResolveOptions;
 
 /**
  * This exists because we have to memoize resolved substitutions as we go
@@ -21,13 +22,12 @@ final class SubstitutionResolver {
     }
 
     AbstractConfigValue resolve(AbstractConfigValue original, int depth,
-            boolean withFallbacks) {
+            ConfigResolveOptions options) {
         if (memos.containsKey(original)) {
             return memos.get(original);
         } else {
             AbstractConfigValue resolved = original.resolveSubstitutions(this,
-                    depth,
-                    withFallbacks);
+                    depth, options);
             if (resolved.resolveStatus() != ResolveStatus.RESOLVED)
                 throw new ConfigException.BugOrBroken(
                         "resolveSubstitutions() did not give us a resolved object");
@@ -40,19 +40,9 @@ final class SubstitutionResolver {
         return this.root;
     }
 
-    private static AbstractConfigValue resolve(AbstractConfigValue value,
-            AbstractConfigObject root, boolean withFallbacks) {
-        SubstitutionResolver resolver = new SubstitutionResolver(root);
-        return resolver.resolve(value, 0, withFallbacks);
-    }
-
     static AbstractConfigValue resolve(AbstractConfigValue value,
-            AbstractConfigObject root) {
-        return resolve(value, root, true /* withFallbacks */);
-    }
-
-    static AbstractConfigValue resolveWithoutFallbacks(
-            AbstractConfigValue value, AbstractConfigObject root) {
-        return resolve(value, root, false /* withFallbacks */);
+            AbstractConfigObject root, ConfigResolveOptions options) {
+        SubstitutionResolver resolver = new SubstitutionResolver(root);
+        return resolver.resolve(value, 0, options);
     }
 }
diff --git a/src/main/java/com/typesafe/config/impl/SyntaxFlavor.java b/src/main/java/com/typesafe/config/impl/SyntaxFlavor.java
deleted file mode 100644
index 868b413a..00000000
--- a/src/main/java/com/typesafe/config/impl/SyntaxFlavor.java
+++ /dev/null
@@ -1,5 +0,0 @@
-package com.typesafe.config.impl;
-
-enum SyntaxFlavor {
-    JSON, CONF
-}
diff --git a/src/main/java/com/typesafe/config/impl/Tokenizer.java b/src/main/java/com/typesafe/config/impl/Tokenizer.java
index b0b28c7e..8b5c8442 100644
--- a/src/main/java/com/typesafe/config/impl/Tokenizer.java
+++ b/src/main/java/com/typesafe/config/impl/Tokenizer.java
@@ -10,14 +10,15 @@ import java.util.Queue;
 
 import com.typesafe.config.ConfigException;
 import com.typesafe.config.ConfigOrigin;
+import com.typesafe.config.ConfigSyntax;
 
 final class Tokenizer {
     /**
      * Tokenizes a Reader. Does not close the reader; you have to arrange to do
      * that after you're done with the returned iterator.
      */
-    static Iterator<Token> tokenize(ConfigOrigin origin, Reader input, SyntaxFlavor flavor) {
-        return new TokenIterator(origin, input, flavor != SyntaxFlavor.JSON);
+    static Iterator<Token> tokenize(ConfigOrigin origin, Reader input, ConfigSyntax flavor) {
+        return new TokenIterator(origin, input, flavor != ConfigSyntax.JSON);
     }
 
     private static class TokenIterator implements Iterator<Token> {
diff --git a/src/test/scala/com/typesafe/config/impl/ConfParserTest.scala b/src/test/scala/com/typesafe/config/impl/ConfParserTest.scala
index 53283842..4f4af876 100644
--- a/src/test/scala/com/typesafe/config/impl/ConfParserTest.scala
+++ b/src/test/scala/com/typesafe/config/impl/ConfParserTest.scala
@@ -11,7 +11,10 @@ import scala.collection.JavaConverters._
 class ConfParserTest extends TestUtils {
 
     def parseWithoutResolving(s: String) = {
-        Parser.parse(SyntaxFlavor.CONF, new SimpleConfigOrigin("test conf string"), s, includer())
+        val options = ConfigParseOptions.defaults().
+            setOriginDescription("test conf string").
+            setSyntax(ConfigSyntax.CONF);
+        ConfigImpl.parseValue(Parseable.newString(s), options);
     }
 
     def parse(s: String) = {
@@ -21,7 +24,7 @@ class ConfParserTest extends TestUtils {
         // interpolating arrays into strings
         tree match {
             case obj: AbstractConfigObject =>
-                SubstitutionResolver.resolveWithoutFallbacks(tree, obj)
+                SubstitutionResolver.resolve(tree, obj, ConfigResolveOptions.noSystem())
             case _ =>
                 tree
         }
diff --git a/src/test/scala/com/typesafe/config/impl/ConfigSubstitutionTest.scala b/src/test/scala/com/typesafe/config/impl/ConfigSubstitutionTest.scala
index 5255204a..f84ddfd0 100644
--- a/src/test/scala/com/typesafe/config/impl/ConfigSubstitutionTest.scala
+++ b/src/test/scala/com/typesafe/config/impl/ConfigSubstitutionTest.scala
@@ -4,21 +4,26 @@ import org.junit.Assert._
 import org.junit._
 import com.typesafe.config.ConfigValue
 import com.typesafe.config.ConfigException
+import com.typesafe.config.ConfigResolveOptions
 
 class ConfigSubstitutionTest extends TestUtils {
 
     private def resolveWithoutFallbacks(v: AbstractConfigObject) = {
-        SubstitutionResolver.resolveWithoutFallbacks(v, v).asInstanceOf[AbstractConfigObject]
+        val options = ConfigResolveOptions.noSystem()
+        SubstitutionResolver.resolve(v, v, options).asInstanceOf[AbstractConfigObject]
     }
     private def resolveWithoutFallbacks(s: ConfigSubstitution, root: AbstractConfigObject) = {
-        SubstitutionResolver.resolveWithoutFallbacks(s, root)
+        val options = ConfigResolveOptions.noSystem()
+        SubstitutionResolver.resolve(s, root, options)
     }
 
     private def resolve(v: AbstractConfigObject) = {
-        SubstitutionResolver.resolve(v, v).asInstanceOf[AbstractConfigObject]
+        val options = ConfigResolveOptions.defaults()
+        SubstitutionResolver.resolve(v, v, options).asInstanceOf[AbstractConfigObject]
     }
     private def resolve(s: ConfigSubstitution, root: AbstractConfigObject) = {
-        SubstitutionResolver.resolve(s, root)
+        val options = ConfigResolveOptions.defaults()
+        SubstitutionResolver.resolve(s, root, options)
     }
 
     private val simpleObject = {
diff --git a/src/test/scala/com/typesafe/config/impl/ConfigTest.scala b/src/test/scala/com/typesafe/config/impl/ConfigTest.scala
index 801165a5..4d917c8e 100644
--- a/src/test/scala/com/typesafe/config/impl/ConfigTest.scala
+++ b/src/test/scala/com/typesafe/config/impl/ConfigTest.scala
@@ -8,6 +8,7 @@ import com.typesafe.config.ConfigObject
 import com.typesafe.config.ConfigException
 import java.util.concurrent.TimeUnit
 import scala.collection.JavaConverters._
+import com.typesafe.config.ConfigResolveOptions
 
 class ConfigTest extends TestUtils {
 
@@ -212,13 +213,17 @@ class ConfigTest extends TestUtils {
         }
     }
 
+    private def resolveNoSystem(v: AbstractConfigValue, root: AbstractConfigObject) = {
+        SubstitutionResolver.resolve(v, root, ConfigResolveOptions.noSystem())
+    }
+
     @Test
     def mergeSubstitutedValues() {
         val obj1 = parseObject("""{ "a" : { "x" : 1, "z" : 4 }, "c" : ${a} }""")
         val obj2 = parseObject("""{ "b" : { "y" : 2, "z" : 5 }, "c" : ${b} }""")
 
         val merged = merge(obj1, obj2)
-        val resolved = SubstitutionResolver.resolveWithoutFallbacks(merged, merged) match {
+        val resolved = resolveNoSystem(merged, merged) match {
             case x: ConfigObject => x
         }
 
@@ -234,7 +239,7 @@ class ConfigTest extends TestUtils {
         val obj2 = parseObject("""{ "b" : { "y" : 2, "z" : 5 }, "c" : ${b} }""")
 
         val merged = merge(obj1, obj2)
-        val resolved = SubstitutionResolver.resolveWithoutFallbacks(merged, merged) match {
+        val resolved = resolveNoSystem(merged, merged) match {
             case x: ConfigObject => x
         }
 
@@ -243,7 +248,7 @@ class ConfigTest extends TestUtils {
         assertEquals(42, resolved.getInt("c.z"))
 
         val merged2 = merge(obj2, obj1)
-        val resolved2 = SubstitutionResolver.resolveWithoutFallbacks(merged2, merged2) match {
+        val resolved2 = resolveNoSystem(merged2, merged2) match {
             case x: ConfigObject => x
         }
 
@@ -268,13 +273,13 @@ class ConfigTest extends TestUtils {
         // that's been overridden, and thus not end up with a cycle as long
         // as we override the problematic link in the cycle.
         val e = intercept[ConfigException.BadValue] {
-            val v = SubstitutionResolver.resolveWithoutFallbacks(subst("foo"), cycleObject)
+            val v = resolveNoSystem(subst("foo"), cycleObject)
         }
         assertTrue(e.getMessage().contains("cycle"))
 
         val fixUpCycle = parseObject(""" { "a" : { "b" : { "c" : 57 } } } """)
         val merged = merge(fixUpCycle, cycleObject)
-        val v = SubstitutionResolver.resolveWithoutFallbacks(subst("foo"), merged)
+        val v = resolveNoSystem(subst("foo"), merged)
         assertEquals(intValue(57), v);
     }
 
@@ -284,14 +289,14 @@ class ConfigTest extends TestUtils {
         // we have to evaluate the substitution to see if it's an object to merge,
         // so we don't avoid the cycle.
         val e = intercept[ConfigException.BadValue] {
-            val v = SubstitutionResolver.resolveWithoutFallbacks(subst("foo"), cycleObject)
+            val v = resolveNoSystem(subst("foo"), cycleObject)
         }
         assertTrue(e.getMessage().contains("cycle"))
 
         val fixUpCycle = parseObject(""" { "a" : { "b" : { "c" : { "q" : "u" } } } } """)
         val merged = merge(fixUpCycle, cycleObject)
         val e2 = intercept[ConfigException.BadValue] {
-            val v = SubstitutionResolver.resolveWithoutFallbacks(subst("foo"), merged)
+            val v = resolveNoSystem(subst("foo"), merged)
         }
         assertTrue(e2.getMessage().contains("cycle"))
     }
@@ -303,7 +308,7 @@ class ConfigTest extends TestUtils {
         val obj3 = parseObject("""{ "c" : { "z" : 3, "q" : 6 }, "j" : ${c} }""")
 
         associativeMerge(Seq(obj1, obj2, obj3)) { merged =>
-            val resolved = SubstitutionResolver.resolveWithoutFallbacks(merged, merged) match {
+            val resolved = resolveNoSystem(merged, merged) match {
                 case x: ConfigObject => x
             }
 
@@ -322,7 +327,7 @@ class ConfigTest extends TestUtils {
         val obj3 = parseObject("""{ "c" : { "z" : 3, "q" : 6 }, "j" : ${c} }""")
 
         associativeMerge(Seq(obj1, obj2, obj3)) { merged =>
-            val resolved = SubstitutionResolver.resolveWithoutFallbacks(merged, merged) match {
+            val resolved = resolveNoSystem(merged, merged) match {
                 case x: ConfigObject => x
             }
 
@@ -340,7 +345,7 @@ class ConfigTest extends TestUtils {
         val obj3 = parseObject("""{ "c" : { "z" : 3, "q" : 6 }, "j" : ${c} }""")
 
         associativeMerge(Seq(obj1, obj2, obj3)) { merged =>
-            val resolved = SubstitutionResolver.resolveWithoutFallbacks(merged, merged) match {
+            val resolved = resolveNoSystem(merged, merged) match {
                 case x: ConfigObject => x
             }
 
@@ -360,7 +365,7 @@ class ConfigTest extends TestUtils {
         val obj4 = parseObject("""{ "c" : { "z" : 4, "q" : 8 }, "j" : ${c} }""")
 
         associativeMerge(Seq(obj1, obj2, obj3, obj4)) { merged =>
-            val resolved = SubstitutionResolver.resolveWithoutFallbacks(merged, merged) match {
+            val resolved = resolveNoSystem(merged, merged) match {
                 case x: ConfigObject => x
             }
 
diff --git a/src/test/scala/com/typesafe/config/impl/ConfigValueTest.scala b/src/test/scala/com/typesafe/config/impl/ConfigValueTest.scala
index 82781f36..0099288e 100644
--- a/src/test/scala/com/typesafe/config/impl/ConfigValueTest.scala
+++ b/src/test/scala/com/typesafe/config/impl/ConfigValueTest.scala
@@ -75,7 +75,7 @@ class ConfigValueTest extends TestUtils {
         checkNotEqualObjects(a, c)
         checkNotEqualObjects(b, c)
 
-        val root = a.asRoot()
+        val root = a.asRoot(path("foo"))
         checkEqualObjects(a, root)
         checkNotEqualObjects(root, b)
         checkNotEqualObjects(root, c)
diff --git a/src/test/scala/com/typesafe/config/impl/EquivalentsTest.scala b/src/test/scala/com/typesafe/config/impl/EquivalentsTest.scala
index 94623f3e..5d881e8c 100644
--- a/src/test/scala/com/typesafe/config/impl/EquivalentsTest.scala
+++ b/src/test/scala/com/typesafe/config/impl/EquivalentsTest.scala
@@ -34,18 +34,20 @@ class EquivalentsTest extends TestUtils {
                 // for purposes of these tests, substitutions are only
                 // against the same file's root, and without looking at
                 // system prop or env variable fallbacks.
-                SubstitutionResolver.resolveWithoutFallbacks(v, v)
+                SubstitutionResolver.resolve(v, v, ConfigResolveOptions.noSystem())
             case v =>
                 v
         }
     }
 
-    private def parse(flavor: SyntaxFlavor, f: File) = {
-        postParse(Parser.parse(flavor, f, includer()))
+    private def parse(flavor: ConfigSyntax, f: File) = {
+        val options = ConfigParseOptions.defaults().setSyntax(flavor)
+        postParse(Config.parse(f, options))
     }
 
     private def parse(f: File) = {
-        postParse(Parser.parse(f, includer()))
+        val options = ConfigParseOptions.defaults()
+        postParse(Config.parse(f, options))
     }
 
     // would like each "equivNN" directory to be a suite and each file in the dir
@@ -75,7 +77,7 @@ class EquivalentsTest extends TestUtils {
                 // check that all .json files can be parsed as .conf,
                 // i.e. .conf must be a superset of JSON
                 if (testFile.getName().endsWith(".json")) {
-                    val parsedAsConf = parse(SyntaxFlavor.CONF, testFile)
+                    val parsedAsConf = parse(ConfigSyntax.CONF, testFile)
                     describeFailure(testFile.getPath() + " parsed as .conf") {
                         assertEquals(original, parsedAsConf)
                     }
diff --git a/src/test/scala/com/typesafe/config/impl/JsonTest.scala b/src/test/scala/com/typesafe/config/impl/JsonTest.scala
index c85d22ee..184a2c7f 100644
--- a/src/test/scala/com/typesafe/config/impl/JsonTest.scala
+++ b/src/test/scala/com/typesafe/config/impl/JsonTest.scala
@@ -12,11 +12,17 @@ import java.util.Collections
 class JsonTest extends TestUtils {
 
     def parse(s: String): ConfigValue = {
-        Parser.parse(SyntaxFlavor.JSON, new SimpleConfigOrigin("test json string"), s, includer())
+        val options = ConfigParseOptions.defaults().
+            setOriginDescription("test json string").
+            setSyntax(ConfigSyntax.JSON);
+        ConfigImpl.parseValue(Parseable.newString(s), options);
     }
 
     def parseAsConf(s: String): ConfigValue = {
-        Parser.parse(SyntaxFlavor.CONF, new SimpleConfigOrigin("test conf string"), s, includer())
+        val options = ConfigParseOptions.defaults().
+            setOriginDescription("test conf string").
+            setSyntax(ConfigSyntax.CONF);
+        ConfigImpl.parseValue(Parseable.newString(s), options);
     }
 
     private[this] def toLift(value: ConfigValue): lift.JValue = {
diff --git a/src/test/scala/com/typesafe/config/impl/TestUtils.scala b/src/test/scala/com/typesafe/config/impl/TestUtils.scala
index ad5d8ce4..aeb25d97 100644
--- a/src/test/scala/com/typesafe/config/impl/TestUtils.scala
+++ b/src/test/scala/com/typesafe/config/impl/TestUtils.scala
@@ -5,6 +5,9 @@ import org.junit._
 import com.typesafe.config.ConfigOrigin
 import java.io.Reader
 import java.io.StringReader
+import com.typesafe.config.ConfigParseOptions
+import com.typesafe.config.Config
+import com.typesafe.config.ConfigSyntax
 
 abstract trait TestUtils {
     protected def intercept[E <: Throwable: Manifest](block: => Unit): E = {
@@ -319,7 +322,10 @@ abstract trait TestUtils {
     protected def doubleValue(d: Double) = new ConfigDouble(fakeOrigin(), d, null)
 
     protected def parseObject(s: String) = {
-        Parser.parse(SyntaxFlavor.CONF, new SimpleConfigOrigin("test string"), s, includer()).asInstanceOf[AbstractConfigObject]
+        val options = ConfigParseOptions.defaults().
+            setOriginDescription("test string").
+            setSyntax(ConfigSyntax.CONF);
+        Config.parse(new StringReader(s), options).asInstanceOf[AbstractConfigObject]
     }
 
     protected def subst(ref: String) = {
@@ -354,7 +360,7 @@ abstract trait TestUtils {
     def tokenKeySubstitution(s: String) = tokenSubstitution(tokenString(s))
 
     def tokenize(origin: ConfigOrigin, input: Reader): java.util.Iterator[Token] = {
-        Tokenizer.tokenize(origin, input, SyntaxFlavor.CONF)
+        Tokenizer.tokenize(origin, input, ConfigSyntax.CONF)
     }
 
     def tokenize(input: Reader): java.util.Iterator[Token] = {