diff --git a/core/core.services.yml b/core/core.services.yml
index c5a13a4..7e474e2 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -313,9 +313,11 @@ services:
     arguments: ['@config.storage', 'config/schema']
   config.typed:
     class: Drupal\Core\Config\TypedConfigManager
-    arguments: ['@config.storage', '@config.storage.schema', '@cache.discovery', '@module_handler']
+    arguments: ['@config.storage', '@config.storage.schema', '@cache.discovery', '@module_handler', '@class_resolver']
     tags:
       - { name: plugin_manager_cache_clear }
+    calls:
+      - [setValidationConstraintManager, ['@validation.constraint']]
   context.handler:
     class: Drupal\Core\Plugin\Context\ContextHandler
     arguments: ['@typed_data_manager']
diff --git a/core/lib/Drupal/Core/Config/Schema/Mapping.php b/core/lib/Drupal/Core/Config/Schema/Mapping.php
index cf02dd4..8646c92 100644
--- a/core/lib/Drupal/Core/Config/Schema/Mapping.php
+++ b/core/lib/Drupal/Core/Config/Schema/Mapping.php
@@ -6,6 +6,7 @@
  */
 
 namespace Drupal\Core\Config\Schema;
+use Drupal\Core\TypedData\ComplexDataInterface;
 
 /**
  * Defines a mapping configuration element.
@@ -20,7 +21,7 @@
  * Read https://www.drupal.org/node/1905070 for more details about configuration
  * schema, types and type resolution.
  */
-class Mapping extends ArrayElement {
+class Mapping extends ArrayElement implements ComplexDataInterface {
 
   /**
    * {@inheritdoc}
@@ -31,4 +32,24 @@ protected function getElementDefinition($key) {
     return $this->buildDataDefinition($definition, $value, $key);
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function set($property_name, $value, $notify = TRUE) {
+    $this->value[$property_name] = $value;
+    // @todo notify?
+    return $this;
+  }
+
+  public function getProperties($include_computed = FALSE) {
+    $properties = array();
+    foreach (array_keys($this->value) as $name) {
+      $definition = $this->getElementDefinition($name);
+      if ($include_computed || !$definition->isComputed()) {
+        $properties[$name] = $this->get($name);
+      }
+    }
+    return $properties;
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Config/Schema/Sequence.php b/core/lib/Drupal/Core/Config/Schema/Sequence.php
index 0c0900a..9cf5186 100644
--- a/core/lib/Drupal/Core/Config/Schema/Sequence.php
+++ b/core/lib/Drupal/Core/Config/Schema/Sequence.php
@@ -6,6 +6,7 @@
  */
 
 namespace Drupal\Core\Config\Schema;
+use Drupal\Core\TypedData\ListInterface;
 
 /**
  * Defines a configuration element of type Sequence.
@@ -16,7 +17,7 @@
  * Read https://www.drupal.org/node/1905070 for more details about configuration
  * schema, types and type resolution.
  */
-class Sequence extends ArrayElement {
+class Sequence extends ArrayElement implements ListInterface {
 
   /**
    * {@inheritdoc}
@@ -34,4 +35,79 @@ protected function getElementDefinition($key) {
     return $this->buildDataDefinition($definition, $value, $key);
   }
 
+  public function getItemDefinition() {
+    // @todo.
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function set($index, $value) {
+    $this->value[$index] = $value;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function first() {
+    reset($this->value);
+    return current($this->value);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function appendItem($value = NULL) {
+    $this->value[] = $value;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function removeItem($index) {
+    unset($this->value[$index]);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function filter($callback) {
+    return array_filter($this->value, $callback);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function count() {
+    return count($this->value);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function offsetExists($offset) {
+    return isset($this->value[$offset]);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function offsetGet($offset) {
+    return $this->get($offset);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function offsetSet($offset, $value) {
+    return $this->set($offset, $value);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function offsetUnset($offset) {
+    unset($this->elements[$offset]);
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Config/TypedConfigManager.php b/core/lib/Drupal/Core/Config/TypedConfigManager.php
index 1181953..35c8e78 100644
--- a/core/lib/Drupal/Core/Config/TypedConfigManager.php
+++ b/core/lib/Drupal/Core/Config/TypedConfigManager.php
@@ -11,6 +11,7 @@
 use Drupal\Core\Cache\CacheBackendInterface;
 use Drupal\Core\Config\Schema\ConfigSchemaAlterException;
 use Drupal\Core\Config\Schema\ConfigSchemaDiscovery;
+use Drupal\Core\DependencyInjection\ClassResolverInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\TypedData\TypedDataManager;
 
@@ -49,13 +50,18 @@ class TypedConfigManager extends TypedDataManager implements TypedConfigManagerI
    *   The storage object to use for reading schema data
    * @param \Drupal\Core\Cache\CacheBackendInterface $cache
    *   The cache backend to use for caching the definitions.
+   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
+   *   The module handler.
+   * @param \Drupal\Core\DependencyInjection\ClassResolverInterface $class_resolver
+   *   The class resolver.
    */
-  public function __construct(StorageInterface $configStorage, StorageInterface $schemaStorage, CacheBackendInterface $cache, ModuleHandlerInterface $module_handler) {
+  public function __construct(StorageInterface $configStorage, StorageInterface $schemaStorage, CacheBackendInterface $cache, ModuleHandlerInterface $module_handler, ClassResolverInterface $class_resolver) {
     $this->configStorage = $configStorage;
     $this->schemaStorage = $schemaStorage;
     $this->setCacheBackend($cache, 'typed_config_definitions');
     $this->alterInfo('config_schema_info');
     $this->moduleHandler = $module_handler;
+    $this->classResolver = $class_resolver;
   }
 
   /**
@@ -184,6 +190,7 @@ protected function getDefinitionWithReplacements($base_plugin_id, array $replace
     $definition += array(
       'definition_class' => '\Drupal\Core\TypedData\DataDefinition',
       'type' => $type,
+      'unwrap_for_canonical_representation' => TRUE,
     );
     return $definition;
   }
diff --git a/core/lib/Drupal/Core/TypedData/DataDefinition.php b/core/lib/Drupal/Core/TypedData/DataDefinition.php
index 1ef7c49..743fe70 100644
--- a/core/lib/Drupal/Core/TypedData/DataDefinition.php
+++ b/core/lib/Drupal/Core/TypedData/DataDefinition.php
@@ -263,7 +263,7 @@ public function setSetting($setting_name, $value) {
    */
   public function getConstraints() {
     $constraints = isset($this->definition['constraints']) ? $this->definition['constraints'] : array();
-    $constraints += \Drupal::typedDataManager()->getDefaultConstraints($this);
+    $constraints += $this->getTypedDataManager()->getDefaultConstraints($this);
     return $constraints;
   }
 
diff --git a/core/lib/Drupal/Core/Validation/ConstraintManager.php b/core/lib/Drupal/Core/Validation/ConstraintManager.php
index 631a3d1..47d291a 100644
--- a/core/lib/Drupal/Core/Validation/ConstraintManager.php
+++ b/core/lib/Drupal/Core/Validation/ConstraintManager.php
@@ -12,6 +12,9 @@
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Plugin\DefaultPluginManager;
 use Drupal\Core\StringTranslation\TranslatableMarkup;
+use Symfony\Component\Validator\Constraints\Choice;
+use Symfony\Component\Validator\Constraints\EqualTo;
+use Symfony\Component\Validator\Constraints\GreaterThan;
 
 /**
  * Constraint plugin manager.
diff --git a/core/modules/config/tests/config_test/config/install/config_test.validation.yml b/core/modules/config/tests/config_test/config/install/config_test.validation.yml
new file mode 100644
index 0000000..47b397d
--- /dev/null
+++ b/core/modules/config/tests/config_test/config/install/config_test.validation.yml
@@ -0,0 +1,7 @@
+llama: meh
+cat:
+  type: kitten
+  count: 2
+giraffe:
+  hum1: hum1
+  hum2: hum2
diff --git a/core/modules/config/tests/config_test/config/schema/config_test.schema.yml b/core/modules/config/tests/config_test/config/schema/config_test.schema.yml
index 6cad91b..91f4fd6 100644
--- a/core/modules/config/tests/config_test/config/schema/config_test.schema.yml
+++ b/core/modules/config/tests/config_test/config/schema/config_test.schema.yml
@@ -152,3 +152,30 @@ config_test.foo:
 
 config_test.bar:
   type: config_test.foo
+
+config_test.validation:
+  type: config_object
+  label: 'Configuration type'
+  mapping:
+    llama:
+      type: string
+      constraints:
+        Callback:
+          callback: [\Drupal\config_test\ConfigValidation, validateLlama]
+    cat:
+      type: mapping
+      mapping:
+        type:
+          type: string
+          constraints:
+            Callback:
+              callback: [\Drupal\config_test\ConfigValidation, validateCats]
+        count:
+          type: integer
+          constraints:
+            Callback:
+              callback: [\Drupal\config_test\ConfigValidation, validateCatCount]
+    giraffe:
+      type: sequence
+      sequence:
+        type: string
diff --git a/core/modules/config/tests/config_test/src/ConfigValidation.php b/core/modules/config/tests/config_test/src/ConfigValidation.php
new file mode 100644
index 0000000..19729e2
--- /dev/null
+++ b/core/modules/config/tests/config_test/src/ConfigValidation.php
@@ -0,0 +1,25 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\config_test\ConfigValidation.
+ */
+
+namespace Drupal\config_test;
+
+
+class ConfigValidation {
+
+  public static function validateLlama($object, $context) {
+    return $object === 'meh';
+  }
+
+  public static function validateCats($object, $context) {
+    return in_array($object, ['kitten', 'cats', 'nyans']);
+  }
+
+  public static function validateCatCount($object, $context) {
+    return $object > 1;
+  }
+
+}
diff --git a/core/modules/system/config/schema/system.schema.yml b/core/modules/system/config/schema/system.schema.yml
index e34d375..15b3a08 100644
--- a/core/modules/system/config/schema/system.schema.yml
+++ b/core/modules/system/config/schema/system.schema.yml
@@ -7,6 +7,8 @@ system.site:
     uuid:
       type: string
       label: 'Site UUID'
+      constraints:
+        NotNull: []
     name:
       type: label
       label: 'Site name'
diff --git a/core/tests/Drupal/KernelTests/Config/ConfigValidationTest.php b/core/tests/Drupal/KernelTests/Config/ConfigValidationTest.php
new file mode 100644
index 0000000..f75dd8b
--- /dev/null
+++ b/core/tests/Drupal/KernelTests/Config/ConfigValidationTest.php
@@ -0,0 +1,119 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\KernelTests\Config\ConfigValidationTest.
+ */
+
+namespace Drupal\KernelTests\Config;
+
+use Drupal\Core\TypedData\ComplexDataInterface;
+use Drupal\Core\TypedData\ListInterface;
+use Drupal\Core\TypedData\Type\IntegerInterface;
+use Drupal\Core\TypedData\Type\StringInterface;
+use Drupal\KernelTests\KernelTestBase;
+use Symfony\Component\Validator\ConstraintViolationListInterface;
+
+/**
+ * Tests config validation mechanism.
+ *
+ * @group config
+ */
+class ConfigValidationTest extends KernelTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = ['config_test'];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    $this->installConfig('config_test');
+  }
+
+  /**
+   * Verifies that the Typed Data API is implemented correctly.
+   */
+  public function testTypedDataAPI() {
+
+    /** @var \Drupal\Core\Config\TypedConfigManagerInterface $typed_config_manager */
+    $typed_config_manager = \Drupal::service('config.typed');
+    /** @var \Drupal\Core\Config\Schema\TypedConfigInterface $typed_config */
+    $typed_config = $typed_config_manager->get('config_test.validation');
+
+    // Test a primitive.
+    $string_data = $typed_config->get('llama');
+    $this->assertInstanceOf(StringInterface::class, $string_data);
+    $this->assertEquals('meh', $string_data->getValue());
+
+    // Test complex data.
+    $mapping = $typed_config->get('cat');
+    /** @var \Drupal\Core\TypedData\ComplexDataInterface $mapping */
+    $this->assertInstanceOf(ComplexDataInterface::class, $mapping);
+    $this->assertInstanceOf(StringInterface::class, $mapping->get('type'));
+    $this->assertEquals('kitten', $mapping->get('type')->getValue());
+    $this->assertInstanceOf(IntegerInterface::class, $mapping->get('count'));
+    $this->assertEquals(2, $mapping->get('count')->getValue());
+    // Verify the metadata is available.
+    $definitions = $mapping->getDataDefinition()->getPropertyDefinitions();
+    $this->assertArrayHasKey('type', $definitions);
+    $this->assertArrayHasKey('count', $definitions);
+    $this->assertEquals('string', $definitions['type']->getDataType());
+    $this->assertEquals('integer', $definitions['count']->getDataType());
+
+    // Test accessing sequences as list data. Other than sequences, lists do
+    // not support keys. The items appear just with numeric indices.
+    $sequence = $typed_config->get('giraffe');
+    /** @var \Drupal\Core\TypedData\ListInterface $sequence */
+    $this->assertInstanceOf(ListInterface::class, $sequence);
+    $this->assertInstanceOf(StringInterface::class, $sequence->get(0));
+    $this->assertEquals('hum1', $sequence->get(0)->getValue());
+    $this->assertEquals('hum2', $sequence->get(1)->getValue());
+    $this->assertEquals(2, $sequence->count());
+    // Verify the item metadata is available.
+    $definition = $sequence->getDataDefinition()->getItemDefinition();
+    $this->assertEquals('string', $definition->getDataType());
+  }
+
+
+  public function testSimpleConfigValidation() {
+    $config = \Drupal::configFactory()->getEditable('config_test.validation');
+    /** @var \Drupal\Core\Config\TypedConfigManagerInterface $typed_config_manager */
+    $typed_config_manager = \Drupal::service('config.typed');
+    /** @var \Drupal\Core\Config\Schema\TypedConfigInterface $typed_config */
+    $typed_config = $typed_config_manager->get('config_test.validation');
+
+    $result = $typed_config->validate();
+    $this->assertInstanceOf(ConstraintViolationListInterface::class, $result);
+    $this->assertEmpty($result);
+
+    // Test constraints on primitive types.
+    $config->set('llama', 'muh');
+    $config->save();
+
+    $typed_config = $typed_config_manager->get('config_test.validation');
+    $result = $typed_config->validate();
+    // 1. llama is no longer a string and 2. its not meh anymore.
+    $this->assertCount(2, $result);
+
+    // Test constraints on mapping
+    $config->set('cat.type', 'nyans');
+    $config->save();
+
+    $typed_config = $typed_config_manager->get('config_test.validation');
+    $result = $typed_config->validate();
+    $this->assertEmpty($result);
+
+    $config->set('cat.type', 'miaus');
+    $config->save();
+
+    $typed_config = $typed_config_manager->get('config_test.validation');
+    $result = $typed_config->validate();
+    $this->assertCount(2, $result);
+  }
+
+}
