diff --git a/core/lib/Drupal/Core/Cache/CacheableMetadata.php b/core/lib/Drupal/Core/Cache/CacheableMetadata.php
new file mode 100644
index 0000000000000000000000000000000000000000..04088b4384979843f2d6f8d4d162fd5a4d2dce30
--- /dev/null
+++ b/core/lib/Drupal/Core/Cache/CacheableMetadata.php
@@ -0,0 +1,132 @@
+<?php
+/**
+ * @file
+ * Contains \Drupal\Core\CacheableMetadata
+ */
+
+namespace Drupal\Core\Cache;
+
+/**
+ * Defines a generic class for passing cacheability metadata.
+ *
+ * @ingroup cache
+ */
+class CacheableMetadata implements CacheableDependencyInterface {
+
+  /**
+   * Cache contexts.
+   *
+   * @var string[]
+   */
+  protected $contexts = [];
+
+  /**
+   * Cache tags.
+   *
+   * @var string[]
+   */
+  protected $tags = [];
+
+  /**
+   * Cache max-age.
+   *
+   * @var int
+   */
+  protected $maxAge = Cache::PERMANENT;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCacheTags() {
+    return $this->tags;
+  }
+
+  /**
+   * Adds cache tags.
+   *
+   * @param string[] $cache_tags
+   *   The cache tags to be added.
+   *
+   * @return $this
+   */
+  public function addCacheTags(array $cache_tags) {
+    $this->tags = Cache::mergeTags($this->tags, $cache_tags);
+    return $this;
+  }
+
+  /**
+   * Sets cache tags.
+   *
+   * @param string[] $cache_tags
+   *   The cache tags to be associated.
+   *
+   * @return $this
+   */
+  public function setCacheTags(array $cache_tags) {
+    $this->tags = $cache_tags;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCacheContexts() {
+    return $this->contexts;
+  }
+
+  /**
+   * Adds cache contexts.
+   *
+   * @param string[] $cache_contexts
+   *   The cache contexts to be added.
+   *
+   * @return $this
+   */
+  public function addCacheContexts(array $cache_contexts) {
+    $this->contexts = Cache::mergeContexts($this->contexts, $cache_contexts);
+    return $this;
+  }
+
+  /**
+   * Sets cache contexts.
+   *
+   * @param string[] $cache_contexts
+   *   The cache contexts to be associated.
+   *
+   * @return $this
+   */
+  public function setCacheContexts(array $cache_contexts) {
+    $this->contexts = $cache_contexts;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCacheMaxAge() {
+    return $this->maxAge;
+  }
+
+  /**
+   * Sets the maximum age (in seconds).
+   *
+   * Defaults to Cache::PERMANENT
+   *
+   * @param int $max_age
+   *   The max age to associate.
+   *
+   * @return $this
+   *
+   * @throws \InvalidArgumentException
+   *   If a non-integer value is supplied.
+   */
+  public function setCacheMaxAge($max_age) {
+    if (!is_int($max_age)) {
+      throw new \InvalidArgumentException('$max_age must be an integer');
+    }
+
+    $this->maxAge = $max_age;
+    return $this;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Render/BubbleableMetadata.php b/core/lib/Drupal/Core/Render/BubbleableMetadata.php
index bf7812e618a66a5dd04688066f0ccf238544c68f..b51c055bd7e52926c2866fa283d1044521842dfc 100644
--- a/core/lib/Drupal/Core/Render/BubbleableMetadata.php
+++ b/core/lib/Drupal/Core/Render/BubbleableMetadata.php
@@ -10,13 +10,14 @@
 use Drupal\Component\Utility\NestedArray;
 use Drupal\Core\Cache\Cache;
 use Drupal\Core\Cache\CacheableDependencyInterface;
+use Drupal\Core\Cache\CacheableMetadata;
 
 /**
  * Value object used for bubbleable rendering metadata.
  *
  * @see \Drupal\Core\Render\RendererInterface::render()
  */
-class BubbleableMetadata implements CacheableDependencyInterface {
+class BubbleableMetadata extends CacheableMetadata {
 
   /**
    * Cache contexts.
@@ -132,106 +133,6 @@ public static function createFromObject($object) {
     return $meta;
   }
 
-  /**
-   * Gets cache tags.
-   *
-   * @return string[]
-   */
-  public function getCacheTags() {
-    return $this->tags;
-  }
-
-  /**
-   * Adds cache tags.
-   *
-   * @param string[] $cache_tags
-   *   The cache tags to be added.
-   *
-   * @return $this
-   */
-  public function addCacheTags(array $cache_tags) {
-    $this->tags = Cache::mergeTags($this->tags, $cache_tags);
-    return $this;
-  }
-
-  /**
-   * Sets cache tags.
-   *
-   * @param string[] $cache_tags
-   *   The cache tags to be associated.
-   *
-   * @return $this
-   */
-  public function setCacheTags(array $cache_tags) {
-    $this->tags = $cache_tags;
-    return $this;
-  }
-
-  /**
-   * Gets cache contexts.
-   *
-   * @return string[]
-   */
-  public function getCacheContexts() {
-    return $this->contexts;
-  }
-
-  /**
-   * Adds cache contexts.
-   *
-   * @param string[] $cache_contexts
-   *   The cache contexts to be added.
-   *
-   * @return $this
-   */
-  public function addCacheContexts(array $cache_contexts) {
-    $this->contexts = Cache::mergeContexts($this->contexts, $cache_contexts);
-    return $this;
-  }
-
-  /**
-   * Sets cache contexts.
-   *
-   * @param string[] $cache_contexts
-   *   The cache contexts to be associated.
-   *
-   * @return $this
-   */
-  public function setCacheContexts(array $cache_contexts) {
-    $this->contexts = $cache_contexts;
-    return $this;
-  }
-
-  /**
-   * Gets the maximum age (in seconds).
-   *
-   * @return int
-   */
-  public function getCacheMaxAge() {
-    return $this->maxAge;
-  }
-
-  /**
-   * Sets the maximum age (in seconds).
-   *
-   * Defaults to Cache::PERMANENT
-   *
-   * @param int $max_age
-   *   The max age to associate.
-   *
-   * @return $this
-   *
-   * @throws \InvalidArgumentException
-   */
-  public function setCacheMaxAge($max_age) {
-    if (!is_int($max_age)) {
-      throw new \InvalidArgumentException('$max_age must be an integer');
-    }
-
-    $this->maxAge = $max_age;
-    return $this;
-  }
-
   /**
    * Gets assets.
    *
diff --git a/core/tests/Drupal/Tests/Core/Cache/CacheableMetadataTest.php b/core/tests/Drupal/Tests/Core/Cache/CacheableMetadataTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..7918d6093992a89560a13f0f8df0645d2a52fe68
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Cache/CacheableMetadataTest.php
@@ -0,0 +1,72 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\Core\Cache\CacheableMetadataTest.
+ */
+
+namespace Drupal\Tests\Core\Cache;
+
+use Drupal\Core\Cache\CacheableMetadata;
+use Drupal\Tests\UnitTestCase;
+use Drupal\Core\Render\Element;
+
+/**
+ * @coversDefaultClass \Drupal\Core\Cache\CacheableMetadata
+ * @group Cache
+ */
+class CacheableMetadataTest extends UnitTestCase {
+
+  /**
+   * This delegates to Cache::mergeTags(), so just a basic test.
+   *
+   * @covers ::addCacheTags
+   */
+  public function testAddCacheTags() {
+    $metadata = new CacheableMetadata();
+    $add_expected = [
+      [ [], [] ],
+      [ ['foo:bar'], ['foo:bar'] ],
+      [ ['foo:baz'], ['foo:bar', 'foo:baz'] ],
+      [ ['axx:first', 'foo:baz'], ['axx:first', 'foo:bar', 'foo:baz'] ],
+      [ [], ['axx:first', 'foo:bar', 'foo:baz'] ],
+      [ ['axx:first'], ['axx:first', 'foo:bar', 'foo:baz'] ],
+    ];
+
+    foreach ($add_expected as $data) {
+      list($add, $expected) = $data;
+      $metadata->addCacheTags($add);
+      $this->assertEquals($expected, $metadata->getCacheTags());
+    }
+  }
+
+  /**
+   * Test valid and invalid values as max age.
+   *
+   * @covers ::setCacheMaxAge
+   * @dataProvider providerSetCacheMaxAge
+   */
+  public function testSetCacheMaxAge($data, $expect_exception) {
+    $metadata = new CacheableMetadata();
+    if ($expect_exception) {
+      $this->setExpectedException('\InvalidArgumentException');
+    }
+    $metadata->setCacheMaxAge($data);
+    $this->assertEquals($data, $metadata->getCacheMaxAge());
+  }
+
+  /**
+   * Data provider for testSetCacheMaxAge.
+   */
+  public function providerSetCacheMaxAge() {
+   return [
+     [0 , FALSE],
+     ['http', TRUE],
+     ['0', TRUE],
+     [new \stdClass(), TRUE],
+     [300, FALSE],
+     [[], TRUE],
+     [8.0, TRUE]
+   ];
+  }
+}