diff --git a/core/lib/Drupal/Component/Plugin/Context/Context.php b/core/lib/Drupal/Component/Plugin/Context/Context.php
index b117a729cd393249e7bd078a614943bdf14cfe53..f30158333211da1c812d7dac30aeb637af27ae29 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;
   }
 
@@ -48,13 +51,22 @@ public function setContextValue($value) {
    * Implements \Drupal\Component\Plugin\Context\ContextInterface::getContextValue().
    */
   public function getContextValue() {
+    // Support optional contexts.
+    if (!isset($this->contextValue)) {
+      $definition = $this->getContextDefinition();
+      if ($definition->isRequired()) {
+        $type = $definition->getDataType();
+        throw new ContextException(sprintf("The %s context is required and not present.", $type));
+      }
+      return NULL;
+    }
     return $this->contextValue;
   }
 
   /**
-   * 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 0000000000000000000000000000000000000000..3d2337bcd458e56f80cf4d91f6ed1d063fb2fdf4
--- /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 c5cd7434f7f8082ac52c1a878f292aabb6209293..f22202b3f0d873424566b81f2c9f231fc843969a 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/Component/Plugin/ContextAwarePluginBase.php b/core/lib/Drupal/Component/Plugin/ContextAwarePluginBase.php
index f04f07d4a39d37795943cdda6c3c9bdb46bb8262..b6846305fdab5e401f8e521907463a9603ba0357 100644
--- a/core/lib/Drupal/Component/Plugin/ContextAwarePluginBase.php
+++ b/core/lib/Drupal/Component/Plugin/ContextAwarePluginBase.php
@@ -7,10 +7,10 @@
 
 namespace Drupal\Component\Plugin;
 
-use Drupal\Component\Plugin\Exception\PluginException;
+use Drupal\Component\Plugin\Context\ContextInterface;
+use Drupal\Component\Plugin\Exception\ContextException;
 use Drupal\Component\Plugin\Context\Context;
 use Symfony\Component\Validator\ConstraintViolationList;
-use Drupal\Component\Plugin\Discovery\DiscoveryInterface;
 
 /**
  * Base class for plugins that are context aware.
@@ -20,7 +20,7 @@ abstract class ContextAwarePluginBase extends PluginBase implements ContextAware
   /**
    * The data objects representing the context of this plugin.
    *
-   * @var array
+   * @var \Drupal\Component\Plugin\Context\ContextInterface[]
    */
   protected $context;
 
@@ -55,7 +55,7 @@ public function __construct(array $configuration, $plugin_id, $plugin_definition
   }
 
   /**
-   * Implements \Drupal\Component\Plugin\ContextAwarePluginInterface::getContextDefinitions().
+   * {@inheritdoc}
    */
   public function getContextDefinitions() {
     $definition = $this->getPluginDefinition();
@@ -63,50 +63,47 @@ public function getContextDefinitions() {
   }
 
   /**
-   * Implements \Drupal\Component\Plugin\ContextAwarePluginInterface::getContextDefinition().
+   * {@inheritdoc}
    */
   public function getContextDefinition($name) {
     $definition = $this->getPluginDefinition();
     if (empty($definition['context'][$name])) {
-      throw new PluginException("The $name context is not a valid context.");
+      throw new ContextException(sprintf("The %s context is not a valid context.", $name));
     }
     return $definition['context'][$name];
   }
 
   /**
-   * Implements \Drupal\Component\Plugin\ContextAwarePluginInterface::getContexts().
+   * {@inheritdoc}
    */
   public function getContexts() {
-    $definitions = $this->getContextDefinitions();
-    if ($definitions && empty($this->context)) {
-      throw new PluginException("There are no set contexts.");
-    }
-    $contexts = array();
-    foreach (array_keys($definitions) as $name) {
-      if (empty($this->context[$name])) {
-        throw new PluginException("The $name context is not yet set.");
-      }
-      $contexts[$name] = $this->context[$name];
+    // Make sure all context objects are initialized.
+    foreach ($this->getContextDefinitions() as $name => $definition) {
+      $this->getContext($name);
     }
-    return $contexts;
+    return $this->context;
   }
 
   /**
-   * Implements \Drupal\Component\Plugin\ContextAwarePluginInterface::getContext().
+   * {@inheritdoc}
    */
   public function getContext($name) {
-    // Check for a valid context definition.
-    $this->getContextDefinition($name);
     // Check for a valid context value.
     if (!isset($this->context[$name])) {
-      throw new PluginException("The $name context is not yet set.");
+      $this->context[$name] = new Context($this->getContextDefinition($name));
     }
-
     return $this->context[$name];
   }
 
   /**
-   * Implements \Drupal\Component\Plugin\ContextAwarePluginInterface::getContextValues().
+   * {@inheritdoc}
+   */
+  public function setContext($name, ContextInterface $context) {
+    $this->context[$name] = $context;
+  }
+
+  /**
+   * {@inheritdoc}
    */
   public function getContextValues() {
     $values = array();
@@ -117,36 +114,30 @@ public function getContextValues() {
   }
 
   /**
-   * Implements \Drupal\Component\Plugin\ContextAwarePluginInterface::getContextValue().
+   * {@inheritdoc}
    */
   public function getContextValue($name) {
     return $this->getContext($name)->getContextValue();
   }
 
   /**
-   * Implements \Drupal\Component\Plugin\ContextAwarePluginInterface::setContextValue().
+   * {@inheritdoc}
    */
   public function setContextValue($name, $value) {
-    $context_definition = $this->getContextDefinition($name);
-    $this->context[$name] = new Context($context_definition);
-    $this->context[$name]->setContextValue($value);
+    $this->getContext($name)->setContextValue($value);
     return $this;
   }
 
   /**
-   * Implements \Drupal\Component\Plugin\ContextAwarePluginInterface::valdidateContexts().
+   * {@inheritdoc}
    */
   public function validateContexts() {
     $violations = new ConstraintViolationList();
     // @todo: Implement symfony validator API to let the validator traverse
     // and set property paths accordingly.
 
-    foreach ($this->getContextDefinitions() as $name => $definition) {
-      // Validate any set values.
-      if (isset($this->context[$name])) {
-        $violations->addAll($this->context[$name]->validate());
-      }
-      // @todo: If no value is set, make sure any mapping is validated.
+    foreach ($this->getContexts() as $context) {
+      $violations->addAll($context->validate());
     }
     return $violations;
   }
diff --git a/core/lib/Drupal/Component/Plugin/ContextAwarePluginInterface.php b/core/lib/Drupal/Component/Plugin/ContextAwarePluginInterface.php
index f29119da6f8f1980f617d60fb2827ad0359c5551..9cb56a1881fbbb5bf6d1a900b26865e835648f23 100644
--- a/core/lib/Drupal/Component/Plugin/ContextAwarePluginInterface.php
+++ b/core/lib/Drupal/Component/Plugin/ContextAwarePluginInterface.php
@@ -7,7 +7,7 @@
 
 namespace Drupal\Component\Plugin;
 
-use Drupal\Component\Plugin\Exception\PluginException;
+use \Drupal\Component\Plugin\Context\ContextInterface;
 
 /**
  * Interface for defining context aware plugins.
@@ -36,7 +36,7 @@ public function getContextDefinitions();
    * @throws \Drupal\Component\Plugin\Exception\PluginException
    *   If the requested context is not defined.
    *
-   * @return array
+   * @return \Drupal\Component\Plugin\Context\ContextDefinitionInterface.
    *   The definition against which the context value must validate.
    */
   public function getContextDefinition($name);
@@ -89,6 +89,16 @@ public function getContextValues();
    */
   public function getContextValue($name);
 
+  /**
+   * Set a context on this plugin.
+   *
+   * @param string $name
+   *   The name of the context in the plugin configuration.
+   * @param \Drupal\Component\Plugin\Context\ContextInterface $context
+   *   The context object to set.
+   */
+  public function setContext($name, ContextInterface $context);
+
   /**
    * Sets the value for a defined context.
    *
diff --git a/core/lib/Drupal/Core/Annotation/ContextDefinition.php b/core/lib/Drupal/Core/Annotation/ContextDefinition.php
new file mode 100644
index 0000000000000000000000000000000000000000..25bc27278a163232dba1196b10593fa5faca9203
--- /dev/null
+++ b/core/lib/Drupal/Core/Annotation/ContextDefinition.php
@@ -0,0 +1,110 @@
+<?php
+/**
+ * @file
+ * Contains \Drupal\Core\Annotation\ContextDefinition.
+ */
+
+namespace Drupal\Core\Annotation;
+
+/**
+ * @defgroup plugin_context context definition plugin metadata
+ *
+ * @{
+ * When providing plugin annotations, contexts can be defined to support UI
+ * interactions through providing limits, and mapping contexts to appropriate
+ * plugins. Context definitions can be provided as such:
+ * @code
+ *   context = {
+ *     "node" = @ContextDefinition("entity:node")
+ *   }
+ * @endcode
+ * Remove spaces after @ in your actual plugin - these are put into this sample
+ * code so that it is not recognized as an annotation.
+ *
+ * To add a label to a context definition use the "label" key:
+ * @code
+ *   context = {
+ *     "node" = @ContextDefinition("entity:node", label = @Translation("Node"))
+ *   }
+ * @endcode
+ *
+ * Contexts are required unless otherwise specified. To make an optional
+ * context use the "required" key:
+ * @code
+ *   context = {
+ *     "node" = @ContextDefinition("entity:node", required = FALSE, label = @Translation("Node"))
+ *   }
+ * @endcode
+ *
+ * To define multiple contexts, simply provide different key names in the
+ * context array:
+ * @code
+ *   context = {
+ *     "artist" = @ContextDefinition("entity:node", label = @Translation("Artist")),
+ *     "album" = @ContextDefinition("entity:node", label = @Translation("Album"))
+ *   }
+ * @endcode
+ * @}
+ */
+use Drupal\Component\Annotation\Plugin;
+
+/**
+ * Defines a context definition annotation object.
+ *
+ * Some plugins require various data contexts in order to function. This class
+ * supports that need by allowing the contexts to be easily defined within an
+ * annotation and return a ContextDefinitionInterface implementing class.
+ *
+ * @Annotation
+ *
+ * @ingroup plugin_context
+ */
+class ContextDefinition extends Plugin {
+
+  /**
+   * The ContextDefinitionInterface object.
+   *
+   * @var \Drupal\Core\Plugin\Context\ContextDefinitionInterface
+   */
+  protected $definition;
+
+  /**
+   * Constructs a new context definition object.
+   *
+   * @param array $values
+   *   An associative array with the following keys:
+   *   - value: The required data type.
+   *   - label: (optional) The UI label of this context definition.
+   *   - required: (optional) Whether the context definition is required.
+   *   - multiple: (optional) Whether the context definition is multivalue.
+   *   - description: (optional) The UI description of this context definition.
+   *   - class: (optional) A custom ContextDefinitionInterface class.
+   *
+   * @throws \Exception
+   *   Thrown when the class key is specified with a non
+   *   ContextDefinitionInterface implementing class.
+   */
+  public function __construct(array $values) {
+    $values += array(
+      'required' => TRUE,
+      'multiple' => FALSE,
+      'label' => NULL,
+      'description' => NULL,
+    );
+    if (isset($values['class']) && !in_array('Drupal\Core\Plugin\Context\ContextDefinitionInterface', class_implements($values['class']))) {
+      throw new \Exception('ContextDefinition class must implement \Drupal\Core\Plugin\Context\ContextDefinitionInterface.');
+    }
+    $class = isset($values['class']) ? $values['class'] : 'Drupal\Core\Plugin\Context\ContextDefinition';
+    $this->definition = new $class($values['value'], $values['label'], $values['required'], $values['multiple'], $values['description']);
+  }
+
+  /**
+   * Returns the value of an annotation.
+   *
+   * @return \Drupal\Core\Plugin\Context\ContextDefinitionInterface
+   */
+  public function get() {
+    return $this->definition;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Condition/ConditionAccessResolverTrait.php b/core/lib/Drupal/Core/Condition/ConditionAccessResolverTrait.php
index 35587ebb4507896b9b57a1e6b6dbbfebd16ade31..528e662674d304ad0ea097f56c064344bb4df317 100644
--- a/core/lib/Drupal/Core/Condition/ConditionAccessResolverTrait.php
+++ b/core/lib/Drupal/Core/Condition/ConditionAccessResolverTrait.php
@@ -7,7 +7,7 @@
 
 namespace Drupal\Core\Condition;
 
-use Drupal\Component\Plugin\Exception\PluginException;
+use Drupal\Component\Plugin\Exception\ContextException;
 
 /**
  * Resolves a set of conditions.
@@ -30,7 +30,7 @@ protected function resolveConditions($conditions, $condition_logic) {
       try {
         $pass = $condition->execute();
       }
-      catch (PluginException $e) {
+      catch (ContextException $e) {
         // If a condition is missing context, consider that a fail.
         $pass = FALSE;
       }
diff --git a/core/lib/Drupal/Core/Condition/ConditionManager.php b/core/lib/Drupal/Core/Condition/ConditionManager.php
index e47dd287229d0661e36705be98fc717bc03a2566..db0fa43bb49ca07d8662ce3c21f8b745549db8e7 100644
--- a/core/lib/Drupal/Core/Condition/ConditionManager.php
+++ b/core/lib/Drupal/Core/Condition/ConditionManager.php
@@ -50,6 +50,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 221e3b5f0e8c57be8bfe8dfae40219de923f860d..a36c7707571ca7760322b6e7c2409c5b26662ef0 100644
--- a/core/lib/Drupal/Core/Condition/ConditionPluginBase.php
+++ b/core/lib/Drupal/Core/Condition/ConditionPluginBase.php
@@ -20,6 +20,13 @@
  */
 abstract class ConditionPluginBase extends ExecutablePluginBase implements ConditionInterface {
 
+  /**
+   * {@inheritdoc}
+   */
+  public static function contextDefinitions() {
+    return [];
+  }
+
   /**
    * {@inheritdoc}
    */
diff --git a/core/lib/Drupal/Core/Plugin/Context/Context.php b/core/lib/Drupal/Core/Plugin/Context/Context.php
index 2d79d6f441dfab8f0638d6879622e39ce961655e..0366b717f2ee94afad05d7f35bb4f411a8a5ef2c 100644
--- a/core/lib/Drupal/Core/Plugin/Context/Context.php
+++ b/core/lib/Drupal/Core/Plugin/Context/Context.php
@@ -8,84 +8,99 @@
 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\Component\Plugin\Exception\ContextException;
+use Drupal\Component\Utility\String;
+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;
 
   /**
-   * Overrides \Drupal\Component\Plugin\Context\Context::getContextValue().
+   * The data associated with the context.
+   *
+   * @var \Drupal\Core\TypedData\TypedDataInterface
+   */
+  protected $contextData;
+
+  /**
+   * The definition to which a context must conform.
+   *
+   * @var \Drupal\Core\Plugin\Context\ContextDefinitionInterface
+   */
+  protected $contextDefinition;
+
+  /**
+   * {@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)) {
+      $definition = $this->getContextDefinition();
+      if ($definition->isRequired()) {
+        $type = $definition->getDataType();
+        throw new ContextException(String::format("The @type context is required and not present.", array('@type' => $type)));
+      }
+      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;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setContextData(TypedDataInterface $data) {
+    $this->contextData = $data;
+    return $this;
   }
 
   /**
-   * Overrides \Drupal\Component\Plugin\Context\Context::getConstraints().
+   * {@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 0000000000000000000000000000000000000000..93a639f6eec07963f6adbaeba62e3b94987495a8
--- /dev/null
+++ b/core/lib/Drupal/Core/Plugin/Context/ContextDefinition.php
@@ -0,0 +1,227 @@
+<?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.
+   * @param mixed string|null $label
+   *   The label of this context definition for the UI.
+   * @param bool $required
+   *   Whether the context definition is required.
+   * @param bool $multiple
+   *   Whether the context definition is multivalue.
+   * @param mixed string|null $description
+   *   The description of this context definition for the UI.
+   */
+  public function __construct($data_type = 'any', $label = NULL, $required = TRUE, $multiple = FALSE, $description = NULL) {
+    $this->dataType = $data_type;
+    $this->label = $label;
+    $this->isRequired = $required;
+    $this->isMultiple = $multiple;
+    $this->description = $description;
+  }
+
+  /**
+   * {@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 0000000000000000000000000000000000000000..3472c94d7f5a835528b960527f82cc18911d0f00
--- /dev/null
+++ b/core/lib/Drupal/Core/Plugin/Context/ContextDefinitionInterface.php
@@ -0,0 +1,25 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\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/ContextHandler.php b/core/lib/Drupal/Core/Plugin/Context/ContextHandler.php
index 672c61366a5f358da604ebf71ea12c2c73239e43..100bf2901dae8575109ad14b42d5c2858dc6b8b0 100644
--- a/core/lib/Drupal/Core/Plugin/Context/ContextHandler.php
+++ b/core/lib/Drupal/Core/Plugin/Context/ContextHandler.php
@@ -8,7 +8,7 @@
 namespace Drupal\Core\Plugin\Context;
 
 use Drupal\Component\Plugin\ConfigurablePluginInterface;
-use Drupal\Component\Plugin\Context\ContextInterface;
+use Drupal\Component\Plugin\Context\ContextInterface as ComponentContextInterface;
 use Drupal\Component\Plugin\ContextAwarePluginInterface;
 use Drupal\Component\Plugin\Exception\ContextException;
 use Drupal\Component\Utility\String;
@@ -97,7 +97,7 @@ public function checkRequirements(array $contexts, array $requirements) {
    * {@inheritdoc}
    */
   public function getMatchingContexts(array $contexts, DataDefinitionInterface $definition) {
-    return array_filter($contexts, function (ContextInterface $context) use ($definition) {
+    return array_filter($contexts, function (ComponentContextInterface $context) use ($definition) {
       // @todo getContextDefinition() should return a DataDefinitionInterface.
       $context_definition = new DataDefinition($context->getContextDefinition());
 
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 0000000000000000000000000000000000000000..bb3861737af08c7777a26b879bd1447b701534b4
--- /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 1f5fc7e8d944f427ed67a22d6ee94bf308ce9d1e..1fec19384259ab74f001056b06b3f819a4a1002e 100644
--- a/core/lib/Drupal/Core/Plugin/ContextAwarePluginBase.php
+++ b/core/lib/Drupal/Core/Plugin/ContextAwarePluginBase.php
@@ -8,48 +8,45 @@
 namespace Drupal\Core\Plugin;
 
 use Drupal\Component\Plugin\ContextAwarePluginBase as ComponentContextAwarePluginBase;
+use Drupal\Component\Plugin\Exception\ContextException;
 use Drupal\Core\DependencyInjection\DependencySerializationTrait;
 use Drupal\Core\Plugin\Context\Context;
-use Drupal\Component\Plugin\Discovery\DiscoveryInterface;
 use Drupal\Core\StringTranslation\StringTranslationTrait;
+use Drupal\Core\TypedData\TypedDataTrait;
+use Drupal\Component\Plugin\Context\ContextInterface as ComponentContextInterface;
+use Drupal\Core\Plugin\Context\ContextInterface;
 
 /**
- * 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 TypedDataTrait;
   use StringTranslationTrait;
   use DependencySerializationTrait;
 
   /**
-   * Override of \Drupal\Component\Plugin\ContextAwarePluginBase::__construct().
+   * {@inheritdoc}
+   *
+   * This code is identical to the Component in order to pick up a different
+   * Context class.
    */
-  public function __construct(array $configuration, $plugin_id, $plugin_definition) {
-    $context = array();
-    if (isset($configuration['context'])) {
-      $context = $configuration['context'];
-      unset($configuration['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);
+  public function getContext($name) {
+    // Check for a valid context value.
+    if (!isset($this->context[$name])) {
+      $this->context[$name] = new Context($this->getContextDefinition($name));
     }
+    return $this->context[$name];
   }
 
   /**
-   * Override of \Drupal\Component\Plugin\ContextAwarePluginBase::setContextValue().
+   * {@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);
-    return $this;
+  public function setContext($name, ComponentContextInterface $context) {
+    // Check that the context passed is an instance of our extended interface.
+    if (!$context instanceof ContextInterface) {
+      throw new ContextException("Passed $name context must be an instance of \\Drupal\\Core\\Plugin\\Context\\ContextInterface");
+    }
+    parent::setContext($name, $context);
   }
 
 }
diff --git a/core/lib/Drupal/Core/TypedData/TypedDataTrait.php b/core/lib/Drupal/Core/TypedData/TypedDataTrait.php
new file mode 100644
index 0000000000000000000000000000000000000000..737a86a25e44595e9bcbea381ba0d4fc9ada3c8a
--- /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 258d23d7d13114a7422a96d1bf2d8e56c7142a84..813da27f085010cf8131a3360bab9314a5dda6e3 100644
--- a/core/modules/block/src/BlockBase.php
+++ b/core/modules/block/src/BlockBase.php
@@ -13,12 +13,10 @@
 use Drupal\Core\Condition\ConditionAccessResolverTrait;
 use Drupal\Core\Condition\ConditionPluginBag;
 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;
 
 /**
@@ -48,6 +46,13 @@ abstract class BlockBase extends ContextAwarePluginBase implements BlockPluginIn
    */
   protected $conditionPluginManager;
 
+  /**
+   * {@inheritdoc}
+   */
+  public static function contextDefinitions() {
+    return [];
+  }
+
   /**
    * {@inheritdoc}
    */
diff --git a/core/modules/block/src/EventSubscriber/CurrentLanguageContext.php b/core/modules/block/src/EventSubscriber/CurrentLanguageContext.php
index 054d0de805a255d9cc6e4497fe6a6f231338ba98..458417295c0f8382dc8b2744136c4be3bfe76352 100644
--- a/core/modules/block/src/EventSubscriber/CurrentLanguageContext.php
+++ b/core/modules/block/src/EventSubscriber/CurrentLanguageContext.php
@@ -9,6 +9,7 @@
 
 use Drupal\Core\Language\LanguageManagerInterface;
 use Drupal\Core\Plugin\Context\Context;
+use Drupal\Core\Plugin\Context\ContextDefinition;
 use Drupal\Core\StringTranslation\StringTranslationTrait;
 
 /**
@@ -39,10 +40,7 @@ public function __construct(LanguageManagerInterface $language_manager) {
    * {@inheritdoc}
    */
   protected function determineBlockContext() {
-    $context = new Context(array(
-      'type' => 'language',
-      'label' => $this->t('Current language'),
-    ));
+    $context = new Context(new ContextDefinition('language', $this->t('Current language')));
     $context->setContextValue($this->languageManager->getCurrentLanguage());
     $this->addContext('language', $context);
   }
diff --git a/core/modules/block/src/EventSubscriber/CurrentUserContext.php b/core/modules/block/src/EventSubscriber/CurrentUserContext.php
index 14565aad913997bf22a649cc5ca41b2ab0399a13..2a5a19ebc85112afcf0d155721271a35ca2d6bd4 100644
--- a/core/modules/block/src/EventSubscriber/CurrentUserContext.php
+++ b/core/modules/block/src/EventSubscriber/CurrentUserContext.php
@@ -9,6 +9,7 @@
 
 use Drupal\Core\Entity\EntityManagerInterface;
 use Drupal\Core\Plugin\Context\Context;
+use Drupal\Core\Plugin\Context\ContextDefinition;
 use Drupal\Core\Session\AccountInterface;
 use Drupal\Core\StringTranslation\StringTranslationTrait;
 
@@ -52,10 +53,7 @@ public function __construct(AccountInterface $account, EntityManagerInterface $e
   protected function determineBlockContext() {
     $current_user = $this->userStorage->load($this->account->id());
 
-    $context = new Context(array(
-      'type' => 'entity:user',
-      'label' => $this->t('Current user'),
-    ));
+    $context = new Context(new ContextDefinition('entity:user', $this->t('Current user')));
     $context->setContextValue($current_user);
     $this->addContext('current_user', $context);
   }
diff --git a/core/modules/block/src/EventSubscriber/NodeRouteContext.php b/core/modules/block/src/EventSubscriber/NodeRouteContext.php
index adadbe828c12d02efa086f593f931baa1bea1ce1..c1bd2bf273e87a426388c3c7cbd066c6ba9f600e 100644
--- a/core/modules/block/src/EventSubscriber/NodeRouteContext.php
+++ b/core/modules/block/src/EventSubscriber/NodeRouteContext.php
@@ -8,6 +8,7 @@
 namespace Drupal\block\EventSubscriber;
 
 use Drupal\Core\Plugin\Context\Context;
+use Drupal\Core\Plugin\Context\ContextDefinition;
 use Drupal\Core\Routing\RouteMatchInterface;
 use Drupal\node\Entity\Node;
 
@@ -38,7 +39,7 @@ public function __construct(RouteMatchInterface $route_match) {
    */
   protected function determineBlockContext() {
     if (($route_object = $this->routeMatch->getRouteObject()) && ($route_contexts = $route_object->getOption('parameters')) && isset($route_contexts['node'])) {
-      $context = new Context($route_contexts['node']);
+      $context = new Context(new ContextDefinition($route_contexts['node']['type']));
       if ($node = $this->routeMatch->getParameter('node')) {
         $context->setContextValue($node);
       }
@@ -46,7 +47,7 @@ protected function determineBlockContext() {
     }
     elseif ($this->routeMatch->getRouteName() == 'node.add') {
       $node_type = $this->routeMatch->getParameter('node_type');
-      $context = new Context(array('type' => 'entity:node'));
+      $context = new Context(new ContextDefinition('entity:node'));
       $context->setContextValue(Node::create(array('type' => $node_type->id())));
       $this->addContext('node', $context);
     }
diff --git a/core/modules/language/src/Plugin/Condition/Language.php b/core/modules/language/src/Plugin/Condition/Language.php
index 41dab719c5f655256a451d06245a88097f266cb5..46a99cb11b822178762089cd642a4a86a70b9667 100644
--- a/core/modules/language/src/Plugin/Condition/Language.php
+++ b/core/modules/language/src/Plugin/Condition/Language.php
@@ -17,11 +17,10 @@
  *   id = "language",
  *   label = @Translation("Language"),
  *   context = {
- *     "language" = {
- *       "type" = "language"
- *     }
+ *     "language" = @ContextDefinition("language", label = @Translation("Language"))
  *   }
  * )
+ *
  */
 class Language extends ConditionPluginBase {
 
diff --git a/core/modules/node/src/Plugin/Condition/NodeType.php b/core/modules/node/src/Plugin/Condition/NodeType.php
index 66e57e3120c931be45518456a2d009af2df8e27e..7973e69593e4980aa06874582b299d2b659325bd 100644
--- a/core/modules/node/src/Plugin/Condition/NodeType.php
+++ b/core/modules/node/src/Plugin/Condition/NodeType.php
@@ -16,11 +16,10 @@
  *   id = "node_type",
  *   label = @Translation("Node Bundle"),
  *   context = {
- *     "node" = {
- *       "type" = "entity:node"
- *     }
+ *     "node" = @ContextDefinition("entity:node", label = @Translation("Node"))
  *   }
  * )
+ *
  */
 class NodeType extends ConditionPluginBase {
 
diff --git a/core/modules/system/src/Tests/Plugin/ContextPluginTest.php b/core/modules/system/src/Tests/Plugin/ContextPluginTest.php
index cd7dbfe8a0b1c49dcbb2f41e43f949332c04443a..b52dd0f58028fc15e98ae706138dc5accf4aa592 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,50 +37,31 @@ 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 to get a value of a valid context, while this value has not been set.
     try {
-      $plugin->getContextValue('node');
-      $this->fail('The node context should not be a valid context.');
+      $plugin->getContextValue('user');
     }
-    catch (PluginException $e) {
-      $this->assertEqual($e->getMessage(), 'The node context is not a valid context.');
+    catch(ContextException $e) {
+      $this->assertIdentical("The entity:user context is required and not present.", $e->getMessage(), 'Requesting a non-set value of a required context should throw a context exception.');
     }
 
     // Try to pass the wrong class type as a context value.
@@ -87,81 +69,22 @@ function testContext() {
     $violations = $plugin->validateContexts();
     $this->assertTrue(!empty($violations), 'The provided context value does not pass validation.');
 
-    // Set an appropriate context value appropriately and check to make sure
-    // its methods work as expected.
+    // Set an appropriate context value and check to make sure 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.');
-    }
+    $this->assertEqual($plugin->getContextValue('user')->getName(), $user->getName());
+    $this->assertEqual($user->label(), $plugin->getTitle());
 
-    // Set the context value appropriately and check the title.
-    $typed_data_plugin->setContextValue('string', $name);
-    $this->assertEqual($name, $typed_data_plugin->getTitle());
+    // Test Optional context handling.
+    $plugin = $manager->createInstance('user_name_optional');
+    $this->assertNull($plugin->getContextValue('user'), 'Requesting a non-set value of a valid context should return NULL.');
 
     // 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/DerivativeTest.php b/core/modules/system/src/Tests/Plugin/DerivativeTest.php
index 8feeff62d887d72462995b65998b957f7d91d10d..0d634f57da705766b401978cba5b1441b25067bc 100644
--- a/core/modules/system/src/Tests/Plugin/DerivativeTest.php
+++ b/core/modules/system/src/Tests/Plugin/DerivativeTest.php
@@ -25,11 +25,11 @@ public static function getInfo() {
    */
   function testDerivativeDecorator() {
     // Ensure that getDefinitions() returns the expected definitions.
-    $this->assertIdentical($this->mockBlockManager->getDefinitions(), $this->mockBlockExpectedDefinitions);
+    $this->assertEqual($this->mockBlockManager->getDefinitions(), $this->mockBlockExpectedDefinitions);
 
     // Ensure that getDefinition() returns the expected definition.
     foreach ($this->mockBlockExpectedDefinitions as $id => $definition) {
-      $this->assertIdentical($this->mockBlockManager->getDefinition($id), $definition);
+      $this->assertEqual($this->mockBlockManager->getDefinition($id), $definition);
     }
 
     // Ensure that NULL is returned as the definition of a non-existing base
diff --git a/core/modules/system/src/Tests/Plugin/PluginTestBase.php b/core/modules/system/src/Tests/Plugin/PluginTestBase.php
index 3a92dbdc22a9c6875c6864f689f5a08068783f8b..3508a053c555bfe62a80bbe8de32fae0a4fa3e2a 100644
--- a/core/modules/system/src/Tests/Plugin/PluginTestBase.php
+++ b/core/modules/system/src/Tests/Plugin/PluginTestBase.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\system\Tests\Plugin;
 
+use Drupal\Core\Plugin\Context\ContextDefinition;
 use Drupal\simpletest\UnitTestBase;
 use Drupal\plugin_test\Plugin\TestPluginManager;
 use Drupal\plugin_test\Plugin\MockBlockManager;
@@ -82,22 +83,26 @@ public function setUp() {
         'label' => 'User name',
         'class' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\MockUserNameBlock',
         'context' => array(
-          'user' => array('class' => 'Drupal\user\UserInterface')
+          'user' => new ContextDefinition('entity:user', 'User'),
+        ),
+      ),
+      'user_name_optional' => array(
+        'label' => 'User name optional',
+        'class' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\MockUserNameBlock',
+        'context' => array(
+          'user' => new ContextDefinition('entity:user', 'User', FALSE),
         ),
       ),
       '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'),
+          'user' => new ContextDefinition('entity:user', 'User'),
+          'node' => new ContextDefinition('entity:node', 'Node'),
         ),
       ),
     );
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 cd3d0306ae211ca03f5bd9d7f786c080627e47f5..6de31cd8655ed29fae04c20e31434271e2b4a4bf 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
@@ -11,6 +11,7 @@
 use Drupal\Component\Plugin\Discovery\StaticDiscovery;
 use Drupal\Component\Plugin\Discovery\DerivativeDiscoveryDecorator;
 use Drupal\Component\Plugin\Factory\ReflectionFactory;
+use Drupal\Core\Plugin\Context\ContextDefinition;
 
 /**
  * Defines a plugin manager used by Plugin API derivative unit tests.
@@ -77,7 +78,16 @@ public function __construct() {
       'label' => t('User name'),
       'class' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\MockUserNameBlock',
       'context' => array(
-        'user' => array('class' => 'Drupal\user\UserInterface')
+        'user' => new ContextDefinition('entity:user', t('User')),
+      ),
+    ));
+
+    // An optional context version of the previous block plugin.
+    $this->discovery->setDefinition('user_name_optional', array(
+      'label' => t('User name optional'),
+      'class' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\MockUserNameBlock',
+      'context' => array(
+        'user' => new ContextDefinition('entity:user', t('User'), FALSE),
       ),
     ));
 
@@ -85,9 +95,6 @@ public function __construct() {
     $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.
@@ -95,8 +102,8 @@ public function __construct() {
       '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'),
+        'user' => new ContextDefinition('entity:user', t('User')),
+        'node' => new ContextDefinition('entity:node', t('Node')),
       ),
     ));
 
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 1cb657a4a61da8dd62cae6cac2e5a825ff61d3be..8041c3a59812b57c548eb4a2055159844a725baa 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;
 
 /**
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 9150167e5278a7840965326fb72d2ff547ac3788..07c7a798347dadd76dd3491530beb07ce6bccaca 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;
 
 /**
diff --git a/core/modules/user/src/Plugin/Condition/UserRole.php b/core/modules/user/src/Plugin/Condition/UserRole.php
index cd38ee3686f510539d812d67e4c7f2c35970588b..872b18a7b1d8f9f780702c0c32e812f0c95daadc 100644
--- a/core/modules/user/src/Plugin/Condition/UserRole.php
+++ b/core/modules/user/src/Plugin/Condition/UserRole.php
@@ -8,6 +8,7 @@
 namespace Drupal\user\Plugin\Condition;
 
 use Drupal\Core\Condition\ConditionPluginBase;
+use Drupal\Core\Plugin\Context\ContextDefinition;
 
 /**
  * Provides a 'User Role' condition.
@@ -16,11 +17,10 @@
  *   id = "user_role",
  *   label = @Translation("User Role"),
  *   context = {
- *     "user" = {
- *       "type" = "entity:user"
- *     }
+ *     "user" = @ContextDefinition("entity:user", label = @Translation("User"))
  *   }
  * )
+ *
  */
 class UserRole extends ConditionPluginBase {
 
diff --git a/core/tests/Drupal/Tests/Core/Condition/ConditionAccessResolverTraitTest.php b/core/tests/Drupal/Tests/Core/Condition/ConditionAccessResolverTraitTest.php
index dc676c5641c61898f938c4c88d02e43793f459b0..8081ea8c7ae02394bb4419d7a8ea9dde9a181e02 100644
--- a/core/tests/Drupal/Tests/Core/Condition/ConditionAccessResolverTraitTest.php
+++ b/core/tests/Drupal/Tests/Core/Condition/ConditionAccessResolverTraitTest.php
@@ -7,8 +7,8 @@
 
 namespace Drupal\Tests\Core\Condition;
 
+use Drupal\Component\Plugin\Exception\ContextException;
 use Drupal\Core\Condition\ConditionAccessResolverTrait;
-use Drupal\Component\Plugin\Exception\PluginException;
 use Drupal\Tests\UnitTestCase;
 
 /**
@@ -58,7 +58,7 @@ public function providerTestResolveConditions() {
     $condition_exception = $this->getMock('Drupal\Core\Condition\ConditionInterface');
     $condition_exception->expects($this->any())
       ->method('execute')
-      ->will($this->throwException(new PluginException()));
+      ->will($this->throwException(new ContextException()));
 
     $conditions = array();
     $data[] = array($conditions, 'and', TRUE);