diff --git a/core/lib/Drupal/Component/Plugin/Context/Context.php b/core/lib/Drupal/Component/Plugin/Context/Context.php
index b117a72..85ab94e 100644
--- a/core/lib/Drupal/Component/Plugin/Context/Context.php
+++ b/core/lib/Drupal/Component/Plugin/Context/Context.php
@@ -26,14 +26,17 @@ class Context implements ContextInterface {
   /**
    * The definition to which a context must conform.
    *
-   * @var array
+   * @var \Drupal\Component\Plugin\Context\ContextDefinitionInterface
    */
   protected $contextDefinition;
 
   /**
    * Sets the contextDefinition for us without needing to call the setter.
+   *
+   * @param \Drupal\Component\Plugin\Context\ContextDefinitionInterface $context_definition
+   *   The context definition.
    */
-  public function __construct(array $context_definition) {
+  public function __construct(ContextDefinitionInterface $context_definition) {
     $this->contextDefinition = $context_definition;
   }
 
@@ -52,9 +55,9 @@ public function getContextValue() {
   }
 
   /**
-   * Implements \Drupal\Component\Plugin\Context\ContextInterface::setContextDefinition().
+   * {@inheritdoc}
    */
-  public function setContextDefinition(array $context_definition) {
+  public function setContextDefinition(ContextDefinitionInterface $context_definition) {
     $this->contextDefinition = $context_definition;
   }
 
diff --git a/core/lib/Drupal/Component/Plugin/Context/ContextDefinitionInterface.php b/core/lib/Drupal/Component/Plugin/Context/ContextDefinitionInterface.php
new file mode 100644
index 0000000..3d2337b
--- /dev/null
+++ b/core/lib/Drupal/Component/Plugin/Context/ContextDefinitionInterface.php
@@ -0,0 +1,158 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Component\Plugin\Context\ContextDefinitionInterface.
+ */
+
+namespace Drupal\Component\Plugin\Context;
+
+/**
+ * Interface for context definitions.
+ */
+interface ContextDefinitionInterface {
+
+  /**
+   * Returns a human readable label.
+   *
+   * @return string
+   *   The label.
+   */
+  public function getLabel();
+
+  /**
+   * Sets the human readable label.
+   *
+   * @param string $label
+   *   The label to set.
+   *
+   * @return $this
+   */
+  public function setLabel($label);
+
+  /**
+   * Returns a human readable description.
+   *
+   * @return string|null
+   *   The description, or NULL if no description is available.
+   */
+  public function getDescription();
+
+  /**
+   * Sets the human readable description.
+   *
+   * @param string|null $description
+   *   The description to set.
+   *
+   * @return $this
+   */
+  public function setDescription($description);
+
+  /**
+   * Returns the data type needed by the context.
+   *
+   * If the context is multiple-valued, this represents the type of each value.
+   *
+   * @return string
+   *   The data type.
+   */
+  public function getDataType();
+
+  /**
+   * Sets the data type needed by the context.
+   *
+   * @param string $data_type
+   *   The data type to set.
+   *
+   * @return $this
+   */
+  public function setDataType($data_type);
+
+  /**
+   * Returns whether the data is multi-valued, i.e. a list of data items.
+   *
+   * @return bool
+   *   Whether the data is multi-valued; i.e. a list of data items.
+   */
+  public function isMultiple();
+
+  /**
+   * Sets whether the data is multi-valued.
+   *
+   * @param bool $multiple
+   *   (optional) Whether the data is multi-valued. Defaults to TRUE.
+   *
+   * @return $this
+   */
+  public function setMultiple($multiple = TRUE);
+
+  /**
+   * Determines whether the context is required.
+   *
+   * For required data a non-NULL value is mandatory.
+   *
+   * @return bool
+   *   Whether a data value is required.
+   */
+  public function isRequired();
+
+  /**
+   * Sets whether the data is required.
+   *
+   * @param bool $required
+   *   (optional) Whether the data is multi-valued. Defaults to TRUE.
+   *
+   * @return $this
+   */
+  public function setRequired($required = TRUE);
+
+  /**
+   * Returns an array of validation constraints.
+   *
+   * @return array
+   *   An array of validation constraint definitions, keyed by constraint name.
+   *   Each constraint definition can be used for instantiating
+   *   \Symfony\Component\Validator\Constraint objects.
+   */
+  public function getConstraints();
+
+  /**
+   * Sets the array of validation constraints.
+   *
+   * NOTE: This will override any previously set constraints. In most cases
+   * ContextDefinitionInterface::addConstraint() should be used instead.
+   *
+   * @param array $constraints
+   *   The array of constraints.
+   *
+   * @return $this
+   *
+   * @see self::addConstraint()
+   */
+  public function setConstraints(array $constraints);
+
+  /**
+   * Adds a validation constraint.
+   *
+   * @param string $constraint_name
+   *   The name of the constraint to add, i.e. its plugin id.
+   * @param array|null $options
+   *   The constraint options as required by the constraint plugin, or NULL.
+   *
+   * @return $this
+   */
+  public function addConstraint($constraint_name, $options = NULL);
+
+  /**
+   * Returns a validation constraint.
+   *
+   * @param string $constraint_name
+   *   The name of the the constraint, i.e. its plugin id.
+   *
+   * @return array
+   *   A validation constraint definition which can be used for instantiating a
+   *   \Symfony\Component\Validator\Constraint object.
+   */
+  public function getConstraint($constraint_name);
+
+}
diff --git a/core/lib/Drupal/Component/Plugin/Context/ContextInterface.php b/core/lib/Drupal/Component/Plugin/Context/ContextInterface.php
index c5cd743..f22202b 100644
--- a/core/lib/Drupal/Component/Plugin/Context/ContextInterface.php
+++ b/core/lib/Drupal/Component/Plugin/Context/ContextInterface.php
@@ -33,13 +33,11 @@ public function getContextValue();
   /**
    * Sets the definition that the context must conform to.
    *
-   * @param array $contextDefinition
+   * @param \Drupal\Component\Plugin\Context\ContextDefinitionInterface $context_definition
    *   A defining characteristic representation of the context against which
-   *   that context can be validated. This is typically an array having a
-   *   class name set under the 'class' key, but it could be extended to support
-   *   other notations.
+   *   that context can be validated.
    */
-  public function setContextDefinition(array $contextDefinition);
+  public function setContextDefinition(ContextDefinitionInterface $context_definition);
 
   /**
    * Gets the provided definition that the context must conform to.
diff --git a/core/lib/Drupal/Core/Condition/ConditionManager.php b/core/lib/Drupal/Core/Condition/ConditionManager.php
index f6f7641..e847411 100644
--- a/core/lib/Drupal/Core/Condition/ConditionManager.php
+++ b/core/lib/Drupal/Core/Condition/ConditionManager.php
@@ -44,6 +44,14 @@ public function __construct(\Traversable $namespaces, CacheBackendInterface $cac
    */
   public function createInstance($plugin_id, array $configuration = array()) {
     $plugin = $this->factory->createInstance($plugin_id, $configuration);
+
+    // If we receive any context values via config set it into the plugin.
+    if (!empty($configuration['context'])) {
+      foreach ($configuration['context'] as $name => $context) {
+        $plugin->setContextValue($name, $context);
+      }
+    }
+
     return $plugin->setExecutableManager($this);
   }
 
diff --git a/core/lib/Drupal/Core/Condition/ConditionPluginBase.php b/core/lib/Drupal/Core/Condition/ConditionPluginBase.php
index 279ef0f..341feec 100644
--- a/core/lib/Drupal/Core/Condition/ConditionPluginBase.php
+++ b/core/lib/Drupal/Core/Condition/ConditionPluginBase.php
@@ -17,6 +17,13 @@
   /**
    * {@inheritdoc}
    */
+  public static function contextDefinitions() {
+    return [];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
   public function __construct(array $configuration, $plugin_id, $plugin_definition) {
     parent::__construct($configuration, $plugin_id, $plugin_definition);
 
diff --git a/core/lib/Drupal/Core/Plugin/Context/Context.php b/core/lib/Drupal/Core/Plugin/Context/Context.php
index 2d79d6f..b1ab0f1 100644
--- a/core/lib/Drupal/Core/Plugin/Context/Context.php
+++ b/core/lib/Drupal/Core/Plugin/Context/Context.php
@@ -8,84 +8,85 @@
 namespace Drupal\Core\Plugin\Context;
 
 use Drupal\Component\Plugin\Context\Context as ComponentContext;
-use Drupal\Core\TypedData\ComplexDataInterface;
-use Drupal\Core\TypedData\DataDefinition;
-use Drupal\Core\TypedData\ListInterface;
+use Drupal\Core\Entity\ContentEntityInterface;
 use Drupal\Core\TypedData\TypedDataInterface;
+use Drupal\Core\TypedData\TypedDataTrait;
 
 /**
  * A Drupal specific context wrapper class.
- *
- * The validate method is specifically overridden in order to support typed
- * data definitions instead of just class names in the contextual definitions
- * of plugins that extend ContextualPluginBase.
  */
-class Context extends ComponentContext {
+class Context extends ComponentContext implements ContextInterface {
+
+  use TypedDataTrait;
+
+  /**
+   * The data associated with the context.
+   *
+   * @var \Drupal\Core\TypedData\TypedDataInterface
+   */
+  protected $contextData;
 
   /**
-   * Overrides \Drupal\Component\Plugin\Context\Context::getContextValue().
+   * {@inheritdoc}
    */
   public function getContextValue() {
-    $typed_value = parent::getContextValue();
-    // If the typed data is complex, pass it on as typed data. Else pass on its
-    // plain value, such that e.g. a string will be directly returned as PHP
-    // string.
-    $is_complex = $typed_value instanceof ComplexDataInterface;
-    if (!$is_complex && $typed_value instanceof ListInterface) {
-      $is_complex = $typed_value[0] instanceof ComplexDataInterface;
+    if (!isset($this->contextData)) {
+      return NULL;
     }
-    if ($typed_value instanceof TypedDataInterface && !$is_complex) {
-      return $typed_value->getValue();
+    // Special case entities.
+    // @todo: Remove once entities do not implemented TypedDataInterface.
+    if ($this->contextData instanceof ContentEntityInterface) {
+      return $this->contextData;
     }
-    return $typed_value;
+    return $this->contextData->getValue();
   }
 
   /**
-   * Implements \Drupal\Component\Plugin\Context\ContextInterface::setContextValue().
+   * {@inheritdoc}
    */
   public function setContextValue($value) {
-    // Make sure the value set is a typed data object.
-    if (!empty($this->contextDefinition['type']) && !$value instanceof TypedDataInterface) {
-      $value = \Drupal::typedDataManager()->create(new DataDefinition($this->contextDefinition), $value);
+    if ($value instanceof TypedDataInterface) {
+      return $this->setContextData($value);
+    }
+    else {
+      return $this->setContextData($this->getTypedDataManager()->create($this->contextDefinition->getDataDefinition(), $value));
     }
-    parent::setContextValue($value);
   }
 
   /**
-   * Gets the context value as typed data object.
-   *
-   * parent::getContextValue() does not do all the processing required to
-   * return plain value of a TypedData object. This class overrides that method
-   * to return the appropriate values from TypedData objects, but the object
-   * itself can be useful as well, so this method is provided to allow for
-   * access to the TypedData object. Since parent::getContextValue() already
-   * does all the processing we need, we simply proxy to it here.
-   *
-   * @return \Drupal\Core\TypedData\TypedDataInterface
+   * {@inheritdoc}
    */
-  public function getTypedContext() {
-    return parent::getContextValue();
+  public function getConstraints() {
+    return $this->contextDefinition->getConstraints();
   }
 
   /**
-   * Implements \Drupal\Component\Plugin\Context\ContextInterface::getConstraints().
+   * {@inheritdoc}
    */
-  public function getConstraints() {
-    if (!empty($this->contextDefinition['type'])) {
-      // If we do have typed data, leverage it for getting constraints.
-      return $this->getTypedContext()->getConstraints();
-    }
-    return parent::getConstraints();
+  public function getContextData() {
+    return $this->contextData;
   }
 
   /**
-   * Overrides \Drupal\Component\Plugin\Context\Context::getConstraints().
+   * {@inheritdoc}
+   */
+  public function setContextData(TypedDataInterface $data) {
+    $this->contextData = $data;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getContextDefinition() {
+    return $this->contextDefinition;
+  }
+
+  /**
+   * {@inheritdoc}
    */
   public function validate() {
-    // If the context is typed data, defer to its validation.
-    if (!empty($this->contextDefinition['type'])) {
-      return $this->getTypedContext()->validate();
-    }
-    return parent::validate();
+    return $this->getContextData()->validate();
   }
+
 }
diff --git a/core/lib/Drupal/Core/Plugin/Context/ContextDefinition.php b/core/lib/Drupal/Core/Plugin/Context/ContextDefinition.php
new file mode 100644
index 0000000..c737157
--- /dev/null
+++ b/core/lib/Drupal/Core/Plugin/Context/ContextDefinition.php
@@ -0,0 +1,215 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Plugin\Context\ContextDefinition.
+ */
+
+namespace Drupal\Core\Plugin\Context;
+
+use Drupal\Core\TypedData\TypedDataTrait;
+
+/**
+ * Defines a class for context definitions.
+ */
+class ContextDefinition implements ContextDefinitionInterface {
+
+  use TypedDataTrait;
+
+  /**
+   * The data type of the data.
+   *
+   * @return string
+   *   The data type.
+   */
+  protected $dataType;
+
+  /**
+   * The human-readable label.
+   *
+   * @return string
+   *   The label.
+   */
+  protected $label;
+
+  /**
+   * The human-readable description.
+   *
+   * @return string|null
+   *   The description, or NULL if no description is available.
+   */
+  protected $description;
+
+  /**
+   * Whether the data is multi-valued, i.e. a list of data items.
+   *
+   * @var bool
+   */
+  protected $isMultiple = FALSE;
+
+  /**
+   * Determines whether a data value is required.
+   *
+   * @var bool
+   *   Whether a data value is required.
+   */
+  protected $isRequired = TRUE;
+
+  /**
+   * An array of constraints.
+   *
+   * @var array[]
+   */
+  protected $constraints = [];
+
+  /**
+   * Creates a new context definition.
+   *
+   * @param string $data_type
+   *   The data type for which to create the context definition. Defaults to
+   *   'any'.
+   *
+   * @return static
+   *   The created context definition object.
+   */
+  public static function create($data_type = 'any') {
+    return new static(
+      $data_type
+    );
+  }
+
+  /**
+   * Constructs a new context definition object.
+   *
+   * @param string $data_type
+   *   The required data type.
+   */
+  public function __construct($data_type = 'any') {
+    $this->dataType = $data_type;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDataType() {
+    return $this->dataType;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setDataType($data_type) {
+    $this->dataType = $data_type;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getLabel() {
+    return $this->label;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setLabel($label) {
+    $this->label = $label;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDescription() {
+    return $this->description;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setDescription($description) {
+    $this->description = $description;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isMultiple() {
+    return $this->isMultiple;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setMultiple($multiple = TRUE) {
+    $this->isMultiple = $multiple;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isRequired() {
+    return $this->isRequired;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setRequired($required = TRUE) {
+    $this->isRequired = $required;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getConstraints() {
+    // @todo Apply defaults.
+    return $this->constraints;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getConstraint($constraint_name) {
+    $constraints = $this->getConstraints();
+    return isset($constraints[$constraint_name]) ? $constraints[$constraint_name] : NULL;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setConstraints(array $constraints) {
+    $this->constraints = $constraints;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function addConstraint($constraint_name, $options = NULL) {
+    $this->constraints[$constraint_name] = $options;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDataDefinition() {
+    if ($this->isMultiple()) {
+      $definition = $this->getTypedDataManager()->createListDataDefinition($this->getDataType());
+    }
+    else {
+      $definition = $this->getTypedDataManager()->createDataDefinition($this->getDataType());
+    }
+    $definition->setLabel($this->getLabel())
+      ->setDescription($this->getDescription())
+      ->setRequired($this->isRequired())
+      ->setConstraints($this->getConstraints());
+    return $definition;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Plugin/Context/ContextDefinitionInterface.php b/core/lib/Drupal/Core/Plugin/Context/ContextDefinitionInterface.php
new file mode 100644
index 0000000..14765f0
--- /dev/null
+++ b/core/lib/Drupal/Core/Plugin/Context/ContextDefinitionInterface.php
@@ -0,0 +1,25 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Component\Plugin\Context\ContextDefinitionInterface.
+ */
+
+namespace Drupal\Core\Plugin\Context;
+
+use \Drupal\Component\Plugin\Context\ContextDefinitionInterface as ComponentContextDefinitionInterface;
+
+/**
+ * Interface for context definitions.
+ */
+interface ContextDefinitionInterface extends ComponentContextDefinitionInterface {
+
+  /**
+   * Returns the data definition of the defined context.
+   *
+   * @return \Drupal\Core\TypedData\DataDefinitionInterface
+   *   The data definition object.
+   */
+  public function getDataDefinition();
+
+}
diff --git a/core/lib/Drupal/Core/Plugin/Context/ContextInterface.php b/core/lib/Drupal/Core/Plugin/Context/ContextInterface.php
new file mode 100644
index 0000000..bb38617
--- /dev/null
+++ b/core/lib/Drupal/Core/Plugin/Context/ContextInterface.php
@@ -0,0 +1,35 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Plugin\Context\ContextInterface.
+ */
+
+namespace Drupal\Core\Plugin\Context;
+
+use Drupal\Component\Plugin\Context\ContextInterface as ComponentContextInterface;
+use Drupal\Core\TypedData\TypedDataInterface;
+
+/**
+ * Interface for context.
+ */
+interface ContextInterface extends ComponentContextInterface {
+
+  /**
+   * Gets the context value as typed data object.
+   *
+   * @return \Drupal\Core\TypedData\TypedDataInterface
+   */
+  public function getContextData();
+
+  /**
+   * Sets the context value as typed data object.
+   *
+   * @param \Drupal\Core\TypedData\TypedDataInterface $data
+   *   The context value as a typed data object.
+   *
+   * @return $this
+   */
+  public function setContextData(TypedDataInterface $data);
+
+}
diff --git a/core/lib/Drupal/Core/Plugin/ContextAwarePluginBase.php b/core/lib/Drupal/Core/Plugin/ContextAwarePluginBase.php
index 43d3a66..17460d2 100644
--- a/core/lib/Drupal/Core/Plugin/ContextAwarePluginBase.php
+++ b/core/lib/Drupal/Core/Plugin/ContextAwarePluginBase.php
@@ -7,21 +7,19 @@
 
 namespace Drupal\Core\Plugin;
 
-use Drupal\Component\Plugin\ContextAwarePluginBase as ComponentContextAwarePluginBase;
+use Drupal\Component\Plugin\Exception\ContextException;
 use Drupal\Core\Plugin\Context\Context;
-use Drupal\Component\Plugin\Discovery\DiscoveryInterface;
-use Drupal\Core\StringTranslation\StringTranslationTrait;
+use Drupal\Core\TypedData\TypedDataTrait;
 use Symfony\Component\DependencyInjection\ContainerInterface;
+use Drupal\Core\Plugin\Context\ContextInterface;
+use Symfony\Component\Validator\ConstraintViolationList;
 
 /**
- * Drupal specific class for plugins that use context.
- *
- * This class specifically overrides setContextValue to use the core version of
- * the Context class. This code is exactly the same as what is in Component
- * ContextAwarePluginBase but it is using a different Context class.
+ * Base class for plugins that are context aware.
  */
-abstract class ContextAwarePluginBase extends ComponentContextAwarePluginBase {
-  use StringTranslationTrait;
+abstract class ContextAwarePluginBase extends PluginBase implements ContextAwarePluginInterface {
+
+  use TypedDataTrait;
 
   /**
    * An array of service IDs keyed by property name used for serialization.
@@ -34,35 +32,111 @@
   protected $_serviceIds = array();
 
   /**
-   * Override of \Drupal\Component\Plugin\ContextAwarePluginBase::__construct().
+   * The contexts of this plugin.
+   *
+   * @var \Drupal\Core\Plugin\Context\ContextInterface[]
+   */
+  protected $contexts;
+
+  /**
+   * The context definitions of this plugin.
+   *
+   * @var \Drupal\Core\Plugin\Context\ContextDefinitionInterface[]
+   */
+  protected $contextDefinitions;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getContextDefinitions() {
+    if (!isset($this->contextDefinitions)) {
+      $this->contextDefinitions = static::contextDefinitions($this->getTypedDataManager());
+    }
+    return $this->contextDefinitions;
+  }
+
+  /**
+   * {@inheritdoc}
    */
-  public function __construct(array $configuration, $plugin_id, $plugin_definition) {
-    $context = array();
-    if (isset($configuration['context'])) {
-      $context = $configuration['context'];
-      unset($configuration['context']);
+  public function getContextDefinition($name) {
+    $definitions = $this->getContextDefinitions();
+    if (empty($definitions[$name])) {
+      throw new ContextException("The $name context is not a valid context.");
     }
-    parent::__construct($configuration, $plugin_id, $plugin_definition);
-    foreach ($context as $key => $value) {
-      $context_definition = $this->getContextDefinition($key);
-      $this->context[$key] = new Context($context_definition);
-      $this->context[$key]->setContextValue($value);
+    return $definitions[$name];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getContexts() {
+    // Make sure all context objects are initialized.
+    foreach ($this->getContextDefinitions() as $name => $definition) {
+      $this->getContext($name);
     }
+    return $this->contexts;
   }
 
   /**
-   * Override of \Drupal\Component\Plugin\ContextAwarePluginBase::setContextValue().
+   * {@inheritdoc}
+   */
+  public function getContext($name) {
+    // Check for a valid context value.
+    if (!isset($this->contexts[$name])) {
+      $this->contexts[$name] = new Context($this->getContextDefinition($name));
+    }
+    return $this->contexts[$name];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setContext($name, ContextInterface $context) {
+    $this->contexts[$name] = $context;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getContextValues() {
+    $values = [];
+    foreach ($this->getContextDefinitions() as $name => $definition) {
+      $values[$name] = isset($this->contexts[$name]) ? $this->contexts[$name]->getContextValue() : NULL;
+    }
+    return $values;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getContextValue($name) {
+    return $this->getContext($name)->getContextValue();
+  }
+
+  /**
+   * {@inheritdoc}
    */
   public function setContextValue($name, $value) {
-    $context_definition = $this->getContextDefinition($name);
-    // Use the Drupal specific context class.
-    $this->context[$name] = new Context($context_definition);
-    $this->context[$name]->setContextValue($value);
+    $this->getContext($name)->setContextValue($value);
     return $this;
   }
 
   /**
    * {@inheritdoc}
+   */
+  public function validateContexts() {
+    $violations = new ConstraintViolationList();
+    // @todo: Implement symfony validator API to let the validator traverse
+    // and set property paths accordingly.
+
+    foreach ($this->getContexts() as $context) {
+      $violations->addAll($context->validate());
+    }
+    return $violations;
+  }
+
+  /**
+   * {@inheritdoc}
    *
    * @todo Remove when Drupal\Core\DependencyInjection\DependencySerialization
    *   is converted to a trait in https://drupal.org/node/2208115.
diff --git a/core/lib/Drupal/Core/Plugin/ContextAwarePluginInterface.php b/core/lib/Drupal/Core/Plugin/ContextAwarePluginInterface.php
new file mode 100644
index 0000000..e4dae54
--- /dev/null
+++ b/core/lib/Drupal/Core/Plugin/ContextAwarePluginInterface.php
@@ -0,0 +1,37 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Plugin\ContextAwarePluginInterface.
+ */
+
+namespace Drupal\Core\Plugin;
+
+use Drupal\Component\Plugin\ContextAwarePluginInterface as ComponentContextAwarePluginInterface;
+use Drupal\Core\Plugin\Context\ContextInterface;
+use Drupal\Core\TypedData\TypedDataManager;
+
+/**
+ * Interface for context-aware plugins.
+ */
+interface ContextAwarePluginInterface extends ComponentContextAwarePluginInterface {
+
+  /**
+   * Set a context on this plugin.
+   *
+   * @param string $name
+   *   The name of the context in the plugin configuration.
+   * @param \Drupal\Core\Plugin\Context\ContextInterface $context
+   *   The context object to set.
+   */
+  public function setContext($name, ContextInterface $context);
+
+  /**
+   * Defines the needed context of this plugin.
+   *
+   * @return \Drupal\Core\Plugin\Context\ContextDefinitionInterface[]
+   *   The array of context definitions, keyed by context name.
+   */
+  public static function contextDefinitions();
+
+}
diff --git a/core/lib/Drupal/Core/TypedData/TypedDataTrait.php b/core/lib/Drupal/Core/TypedData/TypedDataTrait.php
new file mode 100644
index 0000000..737a86a
--- /dev/null
+++ b/core/lib/Drupal/Core/TypedData/TypedDataTrait.php
@@ -0,0 +1,49 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\TypedData\TypedDataTrait.
+ */
+
+namespace Drupal\Core\TypedData;
+
+/**
+ * Wrapper methods for classes that needs typed data manager object.
+ */
+trait TypedDataTrait {
+
+  /**
+   * The typed data manager used for creating the data types.
+   *
+   * @var \Drupal\Core\TypedData\TypedDataManager
+   */
+  protected $typedDataManager;
+
+  /**
+   * Sets the typed data manager.
+   *
+   * @param \Drupal\Core\TypedData\TypedDataManager $typed_data_manager
+   *   The typed data manager.
+   *
+   * @return $this
+   */
+  public function setTypedDataManager(TypedDataManager $typed_data_manager) {
+    $this->typedDataManager = $typed_data_manager;
+    return $this;
+  }
+
+  /**
+   * Gets the typed data manager.
+   *
+   * @return \Drupal\Core\TypedData\TypedDataManager
+   *   The typed data manager.
+   */
+  public function getTypedDataManager() {
+    if (empty($this->typedDataManager)) {
+      $this->typedDataManager = \Drupal::typedDataManager();
+    }
+
+    return $this->typedDataManager;
+  }
+
+}
diff --git a/core/modules/block/src/BlockBase.php b/core/modules/block/src/BlockBase.php
index 994b12e..47e10a7 100644
--- a/core/modules/block/src/BlockBase.php
+++ b/core/modules/block/src/BlockBase.php
@@ -8,12 +8,10 @@
 namespace Drupal\block;
 
 use Drupal\Core\Plugin\ContextAwarePluginBase;
-use Drupal\block\BlockInterface;
 use Drupal\Component\Utility\Unicode;
 use Drupal\Component\Utility\NestedArray;
 use Drupal\Core\Language\LanguageInterface;
 use Drupal\Core\Cache\Cache;
-use Drupal\Core\Cache\CacheableInterface;
 use Drupal\Core\Session\AccountInterface;
 
 /**
@@ -30,6 +28,13 @@
   /**
    * {@inheritdoc}
    */
+  public static function contextDefinitions() {
+    return [];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
   public function label() {
     if (!empty($this->configuration['label'])) {
       return $this->configuration['label'];
diff --git a/core/modules/language/src/Plugin/Condition/Language.php b/core/modules/language/src/Plugin/Condition/Language.php
index e6268c2..9fc91d9 100644
--- a/core/modules/language/src/Plugin/Condition/Language.php
+++ b/core/modules/language/src/Plugin/Condition/Language.php
@@ -9,18 +9,15 @@
 
 use Drupal\Core\Condition\ConditionPluginBase;
 use Drupal\Core\Language\LanguageInterface;
+use Drupal\Core\Plugin\Context\ContextDefinition;
+use Drupal\Core\TypedData\TypedDataManager;
 
 /**
  * Provides a 'Language' condition.
  *
  * @Condition(
  *   id = "language",
- *   label = @Translation("Language"),
- *   context = {
- *     "language" = {
- *       "type" = "language"
- *     }
- *   }
+ *   label = @Translation("Language")
  * )
  */
 class Language extends ConditionPluginBase {
@@ -28,6 +25,16 @@ class Language extends ConditionPluginBase {
   /**
    * {@inheritdoc}
    */
+  public static function contextDefinitions() {
+    $definitions['language'] = ContextDefinition::create('language')
+      ->setLabel(t('Language'));
+
+    return $definitions;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
   public function buildConfigurationForm(array $form, array &$form_state) {
     $form = parent::buildConfigurationForm($form, $form_state);
     if (\Drupal::languageManager()->isMultilingual()) {
diff --git a/core/modules/node/src/Plugin/Condition/NodeType.php b/core/modules/node/src/Plugin/Condition/NodeType.php
index 5b093bf..d0843e7 100644
--- a/core/modules/node/src/Plugin/Condition/NodeType.php
+++ b/core/modules/node/src/Plugin/Condition/NodeType.php
@@ -8,18 +8,14 @@
 namespace Drupal\node\Plugin\Condition;
 
 use Drupal\Core\Condition\ConditionPluginBase;
+use Drupal\Core\Plugin\Context\ContextDefinition;
 
 /**
  * Provides a 'Node Type' condition.
  *
  * @Condition(
  *   id = "node_type",
- *   label = @Translation("Node Bundle"),
- *   context = {
- *     "node" = {
- *       "type" = "entity:node"
- *     }
- *   }
+ *   label = @Translation("Node Bundle")
  * )
  */
 class NodeType extends ConditionPluginBase {
@@ -27,6 +23,16 @@ class NodeType extends ConditionPluginBase {
   /**
    * {@inheritdoc}
    */
+  public static function contextDefinitions() {
+    $definitions['node'] = ContextDefinition::create('entity:node')
+      ->setLabel(t('Node'));
+
+    return $definitions;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
   public function buildConfigurationForm(array $form, array &$form_state) {
     $form = parent::buildConfigurationForm($form, $form_state);
     $options = array();
diff --git a/core/modules/system/src/Tests/Plugin/ContextPluginTest.php b/core/modules/system/src/Tests/Plugin/ContextPluginTest.php
index cd7dbfe..4c1f255 100644
--- a/core/modules/system/src/Tests/Plugin/ContextPluginTest.php
+++ b/core/modules/system/src/Tests/Plugin/ContextPluginTest.php
@@ -7,14 +7,15 @@
 
 namespace Drupal\system\Tests\Plugin;
 
-use Drupal\simpletest\DrupalUnitTestBase;
+use Drupal\Component\Plugin\Exception\ContextException;
+use Drupal\Core\Plugin\Context\ContextDefinition;
 use Drupal\plugin_test\Plugin\MockBlockManager;
-use Drupal\Component\Plugin\Exception\PluginException;
+use Drupal\simpletest\KernelTestBase;
 
 /**
  * Tests that context aware plugins function correctly.
  */
-class ContextPluginTest extends DrupalUnitTestBase {
+class ContextPluginTest extends KernelTestBase {
 
   public static $modules = array('system', 'user', 'node', 'field', 'filter', 'text');
 
@@ -36,51 +37,27 @@ function testContext() {
     // Create a node, add it as context, catch the exception.
     $node = entity_create('node', array('title' => $name, 'type' => 'page'));
 
-    // Try to get a valid context that has not been set.
+    // Try to get context that is missing its definition.
     try {
-      $plugin->getContext('user');
+      $plugin->getContextDefinition('not_exists');
       $this->fail('The user context should not yet be set.');
     }
-    catch (PluginException $e) {
-      $this->assertEqual($e->getMessage(), 'The user context is not yet set.');
+    catch (ContextException $e) {
+      $this->assertEqual($e->getMessage(), 'The not_exists context is not a valid context.');
     }
 
-    // Try to get an invalid context.
-    try {
-      $plugin->getContext('node');
-      $this->fail('The node context should not be a valid context.');
-    }
-    catch (PluginException $e) {
-      $this->assertEqual($e->getMessage(), 'The node context is not a valid context.');
-    }
+    // Test the getContextDefinitions() method.
+    $user_context_definition = ContextDefinition::create('entity:user')->setLabel(t('User'));
+    $this->assertEqual($plugin->getContextDefinitions()['user']->getLabel(), $user_context_definition->getLabel());
 
-    // Try to get a valid context value that has not been set.
-    try {
-      $plugin->getContextValue('user');
-      $this->fail('The user context should not yet be set.');
-    }
-    catch (PluginException $e) {
-      $this->assertEqual($e->getMessage(), 'The user context is not yet set.');
-    }
+    // Test the getContextDefinition() method for a valid context.
+    $this->assertEqual($plugin->getContextDefinition('user')->getLabel(), $user_context_definition->getLabel());
 
-    // Try to call a method of the plugin that requires context before it has
-    // been set.
-    try {
-      $plugin->getTitle();
-      $this->fail('The user context should not yet be set.');
-    }
-    catch (PluginException $e) {
-      $this->assertEqual($e->getMessage(), 'The user context is not yet set.');
-    }
+    // Try to get a context with valid definition.
+    $this->assertNotNull($plugin->getContext('user'), 'Succeeded to get a context with a valid definition.');
 
-    // Try to get a context value that is not valid.
-    try {
-      $plugin->getContextValue('node');
-      $this->fail('The node context should not be a valid context.');
-    }
-    catch (PluginException $e) {
-      $this->assertEqual($e->getMessage(), 'The node context is not a valid context.');
-    }
+    // Try to get a value of a valid context, while this value has not been set.
+    $this->assertNull($plugin->getContextValue('user'), 'Requesting a non-set value of a valid context should return NULL.');
 
     // Try to pass the wrong class type as a context value.
     $plugin->setContextValue('user', $node);
@@ -91,77 +68,14 @@ function testContext() {
     // its methods work as expected.
     $user = entity_create('user', array('name' => $name));
     $plugin->setContextValue('user', $user);
-    $this->assertEqual($user->label(), $plugin->getTitle());
-
-    // Test the getContextDefinitions() method.
-    $this->assertIdentical($plugin->getContextDefinitions(), array('user' => array('class' => 'Drupal\user\UserInterface')));
-
-    // Test the getContextDefinition() method for a valid context.
-    $this->assertEqual($plugin->getContextDefinition('user'), array('class' => 'Drupal\user\UserInterface'));
-
-    // Test the getContextDefinition() method for an invalid context.
-    try {
-      $plugin->getContextDefinition('node');
-      $this->fail('The node context should not be a valid context.');
-    }
-    catch (PluginException $e) {
-      $this->assertEqual($e->getMessage(), 'The node context is not a valid context.');
-    }
-
-    // Test typed data context plugins.
-    $typed_data_plugin = $manager->createInstance('string_context');
-
-    // Try to get a valid context value that has not been set.
-    try {
-      $typed_data_plugin->getContextValue('string');
-      $this->fail('The string context should not yet be set.');
-    }
-    catch (PluginException $e) {
-      $this->assertEqual($e->getMessage(), 'The string context is not yet set.');
-    }
-
-    // Try to call a method of the plugin that requires a context value before
-    // it has been set.
-    try {
-      $typed_data_plugin->getTitle();
-      $this->fail('The string context should not yet be set.');
-    }
-    catch (PluginException $e) {
-      $this->assertEqual($e->getMessage(), 'The string context is not yet set.');
-    }
 
-    // Set the context value appropriately and check the title.
-    $typed_data_plugin->setContextValue('string', $name);
-    $this->assertEqual($name, $typed_data_plugin->getTitle());
+    $this->assertEqual($plugin->getContextValue('user')->getName(), $user->getName());
+    $this->assertEqual($user->label(), $plugin->getTitle());
 
     // Test Complex compound context handling.
     $complex_plugin = $manager->createInstance('complex_context');
-
-    // With no contexts set, try to get the contexts.
-    try {
-      $complex_plugin->getContexts();
-      $this->fail('There should not be any contexts set yet.');
-    }
-    catch (PluginException $e) {
-      $this->assertEqual($e->getMessage(), 'There are no set contexts.');
-    }
-
-    // With no contexts set, try to get the context values.
-    $values = $complex_plugin->getContextValues();
-    $this->assertIdentical(array_filter($values), array(), 'There are no set contexts.');
-
-    // Set the user context value.
     $complex_plugin->setContextValue('user', $user);
 
-    // With only the user context set, try to get the contexts.
-    try {
-      $complex_plugin->getContexts();
-      $this->fail('The node context should not yet be set.');
-    }
-    catch (PluginException $e) {
-      $this->assertEqual($e->getMessage(), 'The node context is not yet set.');
-    }
-
     // With only the user context set, try to get the context values.
     $values = $complex_plugin->getContextValues();
     $this->assertNull($values['node'], 'The node context is not yet set.');
diff --git a/core/modules/system/src/Tests/Plugin/PluginTestBase.php b/core/modules/system/src/Tests/Plugin/PluginTestBase.php
index 3a92dbd..fc406bb 100644
--- a/core/modules/system/src/Tests/Plugin/PluginTestBase.php
+++ b/core/modules/system/src/Tests/Plugin/PluginTestBase.php
@@ -81,24 +81,14 @@ public function setUp() {
       'user_name' => array(
         'label' => 'User name',
         'class' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\MockUserNameBlock',
-        'context' => array(
-          'user' => array('class' => 'Drupal\user\UserInterface')
-        ),
       ),
       'string_context' => array(
         'label' => 'String typed data',
         'class' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\TypedDataStringBlock',
-        'context' => array(
-          'string' => array('type' => 'string'),
-        ),
       ),
       'complex_context' => array(
         'label' => 'Complex context',
         'class' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\MockComplexContextBlock',
-        'context' => array(
-          'user' => array('class' => 'Drupal\user\UserInterface'),
-          'node' => array('class' => 'Drupal\node\NodeInterface'),
-        ),
       ),
     );
     $this->defaultsTestPluginExpectedDefinitions = array(
diff --git a/core/modules/system/tests/modules/plugin_test/src/Plugin/MockBlockManager.php b/core/modules/system/tests/modules/plugin_test/src/Plugin/MockBlockManager.php
index cd3d030..7f5c252 100644
--- a/core/modules/system/tests/modules/plugin_test/src/Plugin/MockBlockManager.php
+++ b/core/modules/system/tests/modules/plugin_test/src/Plugin/MockBlockManager.php
@@ -76,28 +76,18 @@ public function __construct() {
     $this->discovery->setDefinition('user_name', array(
       'label' => t('User name'),
       'class' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\MockUserNameBlock',
-      'context' => array(
-        'user' => array('class' => 'Drupal\user\UserInterface')
-      ),
     ));
 
     // A block plugin that requires a typed data string context to function.
     $this->discovery->setDefinition('string_context', array(
       'label' => t('String typed data'),
       'class' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\TypedDataStringBlock',
-      'context' => array(
-        'string' => array('type' => 'string'),
-      ),
     ));
 
     // A complex context plugin that requires both a user and node for context.
     $this->discovery->setDefinition('complex_context', array(
       'label' => t('Complex context'),
       'class' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\MockComplexContextBlock',
-      'context' => array(
-        'user' => array('class' => 'Drupal\user\UserInterface'),
-        'node' => array('class' => 'Drupal\node\NodeInterface'),
-      ),
     ));
 
     // In addition to finding all of the plugins available for a type, a plugin
diff --git a/core/modules/system/tests/modules/plugin_test/src/Plugin/plugin_test/mock_block/MockComplexContextBlock.php b/core/modules/system/tests/modules/plugin_test/src/Plugin/plugin_test/mock_block/MockComplexContextBlock.php
index 1cb657a..eb6b8bc 100644
--- a/core/modules/system/tests/modules/plugin_test/src/Plugin/plugin_test/mock_block/MockComplexContextBlock.php
+++ b/core/modules/system/tests/modules/plugin_test/src/Plugin/plugin_test/mock_block/MockComplexContextBlock.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\plugin_test\Plugin\plugin_test\mock_block;
 
+use Drupal\Core\Plugin\Context\ContextDefinition;
 use Drupal\Core\Plugin\ContextAwarePluginBase;
 
 /**
@@ -16,6 +17,17 @@
  */
 class MockComplexContextBlock extends ContextAwarePluginBase {
 
+  /**
+   * {@inheritdoc}
+   */
+  public static function contextDefinitions() {
+    $definitions['user'] = ContextDefinition::create('entity:user')
+      ->setLabel(t('User'));
+    $definitions['node'] = ContextDefinition::create('entity:node')
+      ->setLabel(t('Node'));
+    return $definitions;
+  }
+
   public function getTitle() {
     $user = $this->getContextValue('user');
     $node = $this->getContextValue('node');
diff --git a/core/modules/system/tests/modules/plugin_test/src/Plugin/plugin_test/mock_block/MockUserNameBlock.php b/core/modules/system/tests/modules/plugin_test/src/Plugin/plugin_test/mock_block/MockUserNameBlock.php
index 9150167..e214588 100644
--- a/core/modules/system/tests/modules/plugin_test/src/Plugin/plugin_test/mock_block/MockUserNameBlock.php
+++ b/core/modules/system/tests/modules/plugin_test/src/Plugin/plugin_test/mock_block/MockUserNameBlock.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\plugin_test\Plugin\plugin_test\mock_block;
 
+use Drupal\Core\Plugin\Context\ContextDefinition;
 use Drupal\Core\Plugin\ContextAwarePluginBase;
 
 /**
@@ -16,6 +17,14 @@
  */
 class MockUserNameBlock extends ContextAwarePluginBase {
 
+  /**
+   * {@inheritdoc}
+   */
+  public static function contextDefinitions() {
+    $definitions['user'] = ContextDefinition::create('entity:user')->setLabel(t('User'));
+    return $definitions;
+  }
+
   public function getTitle() {
     $user = $this->getContextValue('user');
     return $user->label();
diff --git a/core/modules/system/tests/modules/plugin_test/src/Plugin/plugin_test/mock_block/TypedDataStringBlock.php b/core/modules/system/tests/modules/plugin_test/src/Plugin/plugin_test/mock_block/TypedDataStringBlock.php
index 14e0ab3..f911bfd 100644
--- a/core/modules/system/tests/modules/plugin_test/src/Plugin/plugin_test/mock_block/TypedDataStringBlock.php
+++ b/core/modules/system/tests/modules/plugin_test/src/Plugin/plugin_test/mock_block/TypedDataStringBlock.php
@@ -17,6 +17,13 @@
  */
 class TypedDataStringBlock extends ContextAwarePluginBase {
 
+  /**
+   * {@inheritdoc}
+   */
+  public static function contextDefinitions() {
+    return [];
+  }
+
   public function getTitle() {
     return $this->getContextValue('string');
   }
diff --git a/core/modules/user/src/Plugin/Condition/UserRole.php b/core/modules/user/src/Plugin/Condition/UserRole.php
index e9740da..150489d 100644
--- a/core/modules/user/src/Plugin/Condition/UserRole.php
+++ b/core/modules/user/src/Plugin/Condition/UserRole.php
@@ -8,18 +8,14 @@
 namespace Drupal\user\Plugin\Condition;
 
 use Drupal\Core\Condition\ConditionPluginBase;
+use Drupal\Core\Plugin\Context\ContextDefinition;
 
 /**
  * Provides a 'User Role' condition.
  *
  * @Condition(
  *   id = "user_role",
- *   label = @Translation("User Role"),
- *   context = {
- *     "user" = {
- *       "type" = "entity:user"
- *     }
- *   }
+ *   label = @Translation("User Role")
  * )
  */
 class UserRole extends ConditionPluginBase {
@@ -27,6 +23,16 @@ class UserRole extends ConditionPluginBase {
   /**
    * {@inheritdoc}
    */
+  public static function contextDefinitions() {
+    $definitions['user'] = ContextDefinition::create('entity:user')
+      ->setLabel(t('User'));
+
+    return $definitions;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
   public function buildConfigurationForm(array $form, array &$form_state) {
     $form = parent::buildConfigurationForm($form, $form_state);
     $form['roles'] = array(
