diff --git a/config/src/main/java/com/typesafe/config/Optional.java b/config/src/main/java/com/typesafe/config/Optional.java
new file mode 100644
index 00000000..4645ed5f
--- /dev/null
+++ b/config/src/main/java/com/typesafe/config/Optional.java
@@ -0,0 +1,14 @@
+package com.typesafe.config;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Allows an config property to be {@code null}.
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Optional {
+
+}
diff --git a/config/src/main/java/com/typesafe/config/impl/ConfigBeanImpl.java b/config/src/main/java/com/typesafe/config/impl/ConfigBeanImpl.java
index 6f425a95..e794132e 100644
--- a/config/src/main/java/com/typesafe/config/impl/ConfigBeanImpl.java
+++ b/config/src/main/java/com/typesafe/config/impl/ConfigBeanImpl.java
@@ -4,6 +4,7 @@ import java.beans.BeanInfo;
 import java.beans.IntrospectionException;
 import java.beans.Introspector;
 import java.beans.PropertyDescriptor;
+import java.lang.reflect.Field;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.lang.reflect.ParameterizedType;
@@ -21,6 +22,7 @@ import com.typesafe.config.ConfigException;
 import com.typesafe.config.ConfigMemorySize;
 import com.typesafe.config.ConfigValue;
 import com.typesafe.config.ConfigValueType;
+import com.typesafe.config.Optional;
 
 /**
  * Internal implementation detail, not ABI stable, do not touch.
@@ -90,7 +92,9 @@ public class ConfigBeanImpl {
                     if (configValue != null) {
                         SimpleConfig.checkValid(path, expectedType, configValue, problems);
                     } else {
-                        SimpleConfig.addMissing(problems, expectedType, path, config.origin());
+                        if (!isOptionalProperty(clazz, beanProp)) {
+                            SimpleConfig.addMissing(problems, expectedType, path, config.origin());
+                        }
                     }
                 }
             }
@@ -105,7 +109,17 @@ public class ConfigBeanImpl {
                 Method setter = beanProp.getWriteMethod();
                 Type parameterType = setter.getGenericParameterTypes()[0];
                 Class<?> parameterClass = setter.getParameterTypes()[0];
-                Object unwrapped = getValue(clazz, parameterType, parameterClass, config, originalNames.get(beanProp.getName()));
+                String configPropName = originalNames.get(beanProp.getName());
+                // Is the property key missing in the config?
+                if (configPropName == null) {
+                    // If so, continue if the field is marked as @{link Optional}
+                    if (isOptionalProperty(clazz, beanProp)) {
+                        continue;
+                    }
+                    // Otherwise, raise a {@link Missing} exception right here
+                    throw new ConfigException.Missing(beanProp.getName());
+                }
+                Object unwrapped = getValue(clazz, parameterType, parameterClass, config, configPropName);
                 setter.invoke(bean, unwrapped);
             }
             return bean;
@@ -252,4 +266,24 @@ public class ConfigBeanImpl {
 
         return false;
     }
+
+    private static boolean isOptionalProperty(Class beanClass, PropertyDescriptor beanProp) {
+        Field field = getField(beanClass, beanProp.getName());
+        return (field.getAnnotationsByType(Optional.class).length > 0);
+    }
+
+    private static Field getField(Class beanClass, String fieldName) {
+        try {
+            Field field = beanClass.getDeclaredField(fieldName);
+            field.setAccessible(true);
+            return field;
+        } catch (NoSuchFieldException e) {
+            // Don't give up yet. Try to look for field in super class, if any.
+        }
+        beanClass = beanClass.getSuperclass();
+        if (beanClass == null) {
+            return null;
+        }
+        return getField(beanClass, fieldName);
+    }
 }
diff --git a/config/src/test/java/beanconfig/ObjectsConfig.java b/config/src/test/java/beanconfig/ObjectsConfig.java
new file mode 100644
index 00000000..f3009c96
--- /dev/null
+++ b/config/src/test/java/beanconfig/ObjectsConfig.java
@@ -0,0 +1,68 @@
+package beanconfig;
+
+
+import com.typesafe.config.Optional;
+
+public class ObjectsConfig {
+    public static class ValueObject {
+        @Optional
+        private String optionalValue;
+        private String mandatoryValue;
+
+        public String getMandatoryValue() {
+          return mandatoryValue;
+        }
+
+        public void setMandatoryValue(String mandatoryValue) {
+          this.mandatoryValue = mandatoryValue;
+        }
+
+        public String getOptionalValue() {
+          return optionalValue;
+        }
+
+        public void setOptionalValue(String optionalValue) {
+          this.optionalValue = optionalValue;
+        }
+    }
+
+    private ValueObject valueObject;
+
+    public ValueObject getValueObject() {
+
+        return valueObject;
+    }
+
+    public void setValueObject(ValueObject valueObject) {
+        this.valueObject = valueObject;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (!(o instanceof ObjectsConfig)) {
+            return false;
+        }
+
+        ObjectsConfig that = (ObjectsConfig) o;
+
+        return !(getValueObject() != null ? !getValueObject().equals(that.getValueObject()) : that.getValueObject() != null);
+
+    }
+
+    @Override
+    public int hashCode() {
+        return getValueObject() != null ? getValueObject().hashCode() : 0;
+    }
+
+
+    @Override
+    public String toString() {
+        final StringBuffer sb = new StringBuffer("ObjectsConfig{");
+        sb.append("innerType=").append(valueObject);
+        sb.append('}');
+        return sb.toString();
+    }
+}
diff --git a/config/src/test/resources/beanconfig/beanconfig01.conf b/config/src/test/resources/beanconfig/beanconfig01.conf
index 11adc078..fae411af 100644
--- a/config/src/test/resources/beanconfig/beanconfig01.conf
+++ b/config/src/test/resources/beanconfig/beanconfig01.conf
@@ -92,5 +92,10 @@
       "configValue" : "hello world",
       "list" : [1,2,3],
       "unwrappedMap" : ${validation}
+    },
+    "objects" : {
+      "valueObject": {
+        "mandatoryValue": "notNull"
+      }
     }
 }
diff --git a/config/src/test/scala/com/typesafe/config/impl/ConfigBeanFactoryTest.scala b/config/src/test/scala/com/typesafe/config/impl/ConfigBeanFactoryTest.scala
index 64b3f0c4..755ab2ae 100644
--- a/config/src/test/scala/com/typesafe/config/impl/ConfigBeanFactoryTest.scala
+++ b/config/src/test/scala/com/typesafe/config/impl/ConfigBeanFactoryTest.scala
@@ -162,6 +162,25 @@ class ConfigBeanFactoryTest extends TestUtils {
         assertEquals(42, beanConfig.getUnwrappedMap.get("should-be-boolean"))
     }
 
+    @Test
+    def testOptionalProperties() {
+        val beanConfig: ObjectsConfig = ConfigBeanFactory.create(loadConfig().getConfig("objects"), classOf[ObjectsConfig])
+        assertNotNull(beanConfig)
+        assertNotNull(beanConfig.getValueObject)
+        assertNull(beanConfig.getValueObject.getOptionalValue)
+        assertEquals("notNull", beanConfig.getValueObject.getMandatoryValue)
+    }
+
+    @Test
+    def testNotAnOptionalProperty(): Unit = {
+        val e = intercept[ConfigException.ValidationFailed] {
+            ConfigBeanFactory.create(parseConfig("{valueObject: {}}"), classOf[ObjectsConfig])
+        }
+        assertTrue("missing value error", e.getMessage.contains("No setting"))
+        assertTrue("error about the right property", e.getMessage.contains("mandatoryValue"))
+
+    }
+
     @Test
     def testNotABeanField() {
         val e = intercept[ConfigException.BadBean] {