diff --git a/core/modules/image/src/ImageServiceProvider.php b/core/modules/image/src/ImageServiceProvider.php
new file mode 100644
index 0000000..de34726
--- /dev/null
+++ b/core/modules/image/src/ImageServiceProvider.php
@@ -0,0 +1,59 @@
+<?php
+
+namespace Drupal\image;
+
+use Drupal\Core\DependencyInjection\ContainerBuilder;
+use Drupal\Core\DependencyInjection\ServiceProviderInterface;
+use Drupal\image\Normalizer\ImageItemHalNormalizer;
+use Drupal\image\Normalizer\ImageItemNormalizer;
+use Symfony\Component\DependencyInjection\Definition;
+use Symfony\Component\DependencyInjection\Reference;
+
+/**
+ * Provides additional normalizer services for the "image" field type.
+ *
+ * These services are not added via image.services.yml because the service
+ * classes extend classes from the Hal and Serialization modules. They also have
+ * no use without these modules. Additionally the
+ * image.normalizer.hal.image_item service cannot be added unless
+ * Serialization is enabled because the service requires services provided by
+ * the module. If the services are not available a
+ * Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException will
+ * be thrown from \Drupal\Core\DrupalKernel::compileContainer.
+ */
+class ImageServiceProvider implements ServiceProviderInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function register(ContainerBuilder $container) {
+    $modules = $container->getParameter('container.modules');
+    if (isset($modules['serialization'])) {
+      // Add an ImageItem normalizer.
+      $service_definition = new Definition(ImageItemNormalizer::class, [
+        new Reference('renderer'),
+      ]);
+      // Priority should be higher than
+      // serializer.normalizer.entity_reference_field_item but lower than
+      // serializer.normalizer.entity_reference_item.hal.
+      $service_definition->addTag('normalizer', ['priority' => 9]);
+      $container->setDefinition('image.normalizer.image_item', $service_definition);
+
+      if (isset($modules['hal'])) {
+        // Add an ImageItem normalizer.
+        $service_definition = new Definition(ImageItemHalNormalizer::class, [
+          new Reference('hal.link_manager'),
+          new Reference('serializer.entity_resolver'),
+          new Reference('renderer'),
+        ]);
+        // Priority should be higher than
+        // serializer.normalizer.entity_reference_item.hal which is 10.
+        // Priority of 20 gives the ability to have other normalizers between
+        // this one and serializer.normalizer.entity_reference_item.hal.
+        $service_definition->addTag('normalizer', ['priority' => 20]);
+        $container->setDefinition('image.normalizer.hal.image_item', $service_definition);
+      }
+    }
+  }
+
+}
diff --git a/core/modules/image/src/Normalizer/ImageItemHalNormalizer.php b/core/modules/image/src/Normalizer/ImageItemHalNormalizer.php
new file mode 100644
index 0000000..941ea94
--- /dev/null
+++ b/core/modules/image/src/Normalizer/ImageItemHalNormalizer.php
@@ -0,0 +1,58 @@
+<?php
+
+namespace Drupal\image\Normalizer;
+
+use Drupal\Core\Render\RendererInterface;
+use Drupal\hal\Normalizer\EntityReferenceItemNormalizer;
+use Drupal\image\Plugin\Field\FieldType\ImageItem;
+use Drupal\hal\LinkManager\LinkManagerInterface;
+use Drupal\serialization\EntityResolver\EntityResolverInterface;
+
+/**
+ * Decorator for ImageItem HAL normalizer providing URLs to image styles.
+ */
+class ImageItemHalNormalizer extends EntityReferenceItemNormalizer {
+
+  use ImageItemNormalizerTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $supportedInterfaceOrClass = ImageItem::class;
+
+  /**
+   * The renderer.
+   *
+   * @var \Drupal\Core\Render\RendererInterface
+   */
+  protected $renderer;
+
+  /**
+   * Constructs an ImageItemHalNormalizer object.
+   *
+   * @param \Drupal\hal\LinkManager\LinkManagerInterface $link_manager
+   *   The hypermedia link manager.
+   * @param \Drupal\serialization\EntityResolver\EntityResolverInterface $entity_Resolver
+   *   The entity resolver.
+   * @param \Drupal\Core\Render\RendererInterface $renderer
+   *   The renderer.
+   */
+  public function __construct(LinkManagerInterface $link_manager, EntityResolverInterface $entity_Resolver, RendererInterface $renderer) {
+    parent::__construct($link_manager, $entity_Resolver);
+    $this->renderer = $renderer;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function normalize($field_item, $format = NULL, array $context = []) {
+    /* @var \Drupal\image\Plugin\Field\FieldType\ImageItem $field_item */
+    $normalization = parent::normalize($field_item, $format, $context);
+    if (!$field_item->isEmpty()) {
+      $field_key = array_keys($normalization['_embedded'])[0];
+      $this->decorateWithImageStyles($field_item, $normalization['_embedded'][$field_key][0], $context);
+    }
+    return $normalization;
+  }
+
+}
diff --git a/core/modules/image/src/Normalizer/ImageItemNormalizer.php b/core/modules/image/src/Normalizer/ImageItemNormalizer.php
new file mode 100644
index 0000000..93fc3df
--- /dev/null
+++ b/core/modules/image/src/Normalizer/ImageItemNormalizer.php
@@ -0,0 +1,51 @@
+<?php
+
+namespace Drupal\image\Normalizer;
+
+use Drupal\Core\Render\RendererInterface;
+use Drupal\image\Plugin\Field\FieldType\ImageItem;
+use Drupal\serialization\Normalizer\EntityReferenceFieldItemNormalizer;
+
+/**
+ * Decorator for ImageItem normalizer providing URLs to image styles.
+ */
+class ImageItemNormalizer extends EntityReferenceFieldItemNormalizer {
+
+  use ImageItemNormalizerTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $supportedInterfaceOrClass = ImageItem::class;
+
+  /**
+   * The renderer.
+   *
+   * @var \Drupal\Core\Render\RendererInterface
+   */
+  protected $renderer;
+
+  /**
+   * Constructs an ImageItemNormalizer object.
+   *
+   * @param \Drupal\Core\Render\RendererInterface $renderer
+   *   The renderer.
+   */
+  public function __construct(RendererInterface $renderer) {
+    $this->renderer = $renderer;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function normalize($field_item, $format = NULL, array $context = []) {
+    /* @var \Drupal\image\Plugin\Field\FieldType\ImageItem $field_item */
+    $normalization = parent::normalize($field_item, $format, $context);
+    if (!$field_item->isEmpty()) {
+      $this->decorateWithImageStyles($field_item, $normalization, $context);
+    }
+
+    return $normalization;
+  }
+
+}
diff --git a/core/modules/image/src/Normalizer/ImageItemNormalizerTrait.php b/core/modules/image/src/Normalizer/ImageItemNormalizerTrait.php
new file mode 100644
index 0000000..e908e5be
--- /dev/null
+++ b/core/modules/image/src/Normalizer/ImageItemNormalizerTrait.php
@@ -0,0 +1,48 @@
+<?php
+
+namespace Drupal\image\Normalizer;
+
+use Drupal\file\Entity\File;
+use Drupal\image\Entity\ImageStyle;
+use Drupal\image\Plugin\Field\FieldType\ImageItem;
+
+/**
+ * A trait providing ItemItem normalizing decorating methods.
+ */
+trait ImageItemNormalizerTrait {
+
+  /**
+   * Adds image style information to normalized ImageItem field data.
+   *
+   * @param \Drupal\image\Plugin\Field\FieldType\ImageItem $item
+   *   The image field item.
+   * @param array $normalization
+   *   The image field normalization to add image style information to.
+   * @param array $context
+   *   Context options for the normalizer.
+   */
+  protected function decorateWithImageStyles(ImageItem $item, array &$normalization, array $context) {
+    /** @var \Drupal\file\FileInterface $image */
+    if ($image = File::load($item->target_id)) {
+      $uri = $image->getFileUri();
+      /** @var \Drupal\image\ImageStyleInterface[] $styles */
+      $styles = ImageStyle::loadMultiple();
+      $normalization['image_styles'] = [];
+      foreach ($styles as $id => $style) {
+        if ($style->supportsUri($uri)) {
+          $dimensions = ['width' => $item->width, 'height' => $item->height];
+          $style->transformDimensions($dimensions, $uri);
+          $normalization['image_styles'][$id] = [
+            'url' => file_url_transform_relative($style->buildUrl($uri)),
+            'height' => empty($dimensions['height']) ? NULL : $dimensions['height'],
+            'width' => empty($dimensions['width']) ? NULL : $dimensions['width'],
+          ];
+          if (!empty($context['cacheability'])) {
+            $context['cacheability']->addCacheableDependency($style);
+          }
+        }
+      }
+    }
+  }
+
+}
diff --git a/core/modules/image/tests/src/Kernel/Normalizer/ImageItemHalNormalizerTest.php b/core/modules/image/tests/src/Kernel/Normalizer/ImageItemHalNormalizerTest.php
new file mode 100644
index 0000000..7e4cad8
--- /dev/null
+++ b/core/modules/image/tests/src/Kernel/Normalizer/ImageItemHalNormalizerTest.php
@@ -0,0 +1,28 @@
+<?php
+
+namespace Drupal\Tests\image\Kernel\Normalizer;
+
+/**
+ * @coversDefaultClass \Drupal\image\Normalizer\ImageItemHalNormalizer
+ * @group image
+ */
+class ImageItemHalNormalizerTest extends ImageItemNormalizerTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $format = 'hal_json';
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = ['hal'];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getNormalizedImageStyles(array $normalization) {
+    return array_pop($normalization['_embedded'])[0]['image_styles'];
+  }
+
+}
diff --git a/core/modules/image/tests/src/Kernel/Normalizer/ImageItemNormalizerTest.php b/core/modules/image/tests/src/Kernel/Normalizer/ImageItemNormalizerTest.php
new file mode 100644
index 0000000..774afed
--- /dev/null
+++ b/core/modules/image/tests/src/Kernel/Normalizer/ImageItemNormalizerTest.php
@@ -0,0 +1,23 @@
+<?php
+
+namespace Drupal\Tests\image\Kernel\Normalizer;
+
+/**
+ * @coversDefaultClass \Drupal\image\Normalizer\ImageItemNormalizer
+ * @group image
+ */
+class ImageItemNormalizerTest extends ImageItemNormalizerTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $format = 'json';
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getNormalizedImageStyles(array $normalization) {
+    return $normalization['image_test'][0]['image_styles'];
+  }
+
+}
diff --git a/core/modules/image/tests/src/Kernel/Normalizer/ImageItemNormalizerTestBase.php b/core/modules/image/tests/src/Kernel/Normalizer/ImageItemNormalizerTestBase.php
new file mode 100644
index 0000000..6084afc
--- /dev/null
+++ b/core/modules/image/tests/src/Kernel/Normalizer/ImageItemNormalizerTestBase.php
@@ -0,0 +1,167 @@
+<?php
+
+namespace Drupal\Tests\image\Kernel\Normalizer;
+
+use Drupal\Core\Cache\Cache;
+use Drupal\Core\Cache\CacheableMetadata;
+use Drupal\Core\Field\FieldStorageDefinitionInterface;
+use Drupal\entity_test\Entity\EntityTest;
+use Drupal\field\Entity\FieldConfig;
+use Drupal\field\Entity\FieldStorageConfig;
+use Drupal\file\Entity\File;
+use Drupal\image\Entity\ImageStyle;
+use Drupal\KernelTests\KernelTestBase;
+
+/**
+ * Base class for ItemItemNormalizer testing.
+ */
+abstract class ImageItemNormalizerTestBase extends KernelTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = ['system', 'entity_test', 'serialization', 'image', 'field', 'user', 'file'];
+
+  /**
+   * The serializer.
+   *
+   * @var \Symfony\Component\Serializer\SerializerInterface
+   */
+  protected $serializer;
+
+  /**
+   * An image for testing.
+   *
+   * @var \Drupal\file\FileInterface
+   */
+  protected $image;
+
+  /**
+   * The format whose normalization to test.
+   *
+   * @var string
+   */
+  protected $format;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    $this->serializer = $this->container->get('serializer');
+
+    $this->installEntitySchema('entity_test');
+    $this->installEntitySchema('user');
+    $this->installEntitySchema('file');
+    $this->installConfig('system');
+    $this->installConfig('image');
+    $this->installSchema('file', ['file_usage']);
+
+    FieldStorageConfig::create([
+      'entity_type' => 'entity_test',
+      'field_name' => 'image_test',
+      'type' => 'image',
+      'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
+    ])->save();
+    FieldConfig::create([
+      'entity_type' => 'entity_test',
+      'field_name' => 'image_test',
+      'bundle' => 'entity_test',
+      'settings' => [
+        'file_extensions' => 'jpg',
+      ],
+    ])->save();
+
+    // Set upscale to TRUE in all image style scale effects.
+    /** @var \Drupal\image\Entity\ImageStyle $image_style */
+    foreach (ImageStyle::loadMultiple() as $image_style) {
+      foreach ($image_style->getEffects() as $effect) {
+        $config = $effect->getConfiguration();
+        $config['data']['upscale'] = TRUE;
+        $effect->setConfiguration($config);
+      }
+      $image_style->save();
+    }
+
+    file_unmanaged_copy(\Drupal::root() . '/core/misc/druplicon.png', 'public://example.jpg');
+    $this->image = File::create([
+      'uri' => 'public://example.jpg',
+    ]);
+    $this->image->save();
+  }
+
+  /**
+   * Test that the decorator provides additional image style information.
+   *
+   * @see \Drupal\image\Normalizer\ImageItemNormalizerTrait::decorateWithImageStyles()
+   *
+   * @covers ::normalize
+   */
+  public function testNormalize() {
+    // Create a test entity with the image field set.
+    $original_entity = EntityTest::create();
+    $original_entity->image_test->target_id = $this->image->id();
+    $original_entity->image_test->alt = $alt = $this->randomMachineName();
+    $original_entity->image_test->title = $title = $this->randomMachineName();
+    $original_entity->name->value = $this->randomMachineName();
+    $original_entity->save();
+
+    $entity = clone $original_entity;
+    $cacheable_metadata = new CacheableMetadata();
+    $normalization = $this->serializer->normalize($entity, $this->format, ['cacheability' => $cacheable_metadata]);
+
+    $normalized_image_styles = $this->getNormalizedImageStyles($normalization);
+    $this->assertEquals($this->getExpectedCacheability(), $cacheable_metadata);
+    $expect_dimensions = [
+      'large' => [
+        'width' => '422',
+        'height' => '480',
+      ],
+      'medium' => [
+        'width' => '194',
+        'height' => '220',
+      ],
+      'thumbnail' => [
+        'width' => '88',
+        'height' => '100',
+      ],
+    ];
+    $this->assertEquals(array_keys($expect_dimensions), array_keys($normalized_image_styles));
+
+    foreach ($normalized_image_styles as $image_style_id => $image_style_dimensions) {
+      $this->assertContains("files/styles/$image_style_id/public/example.jpg", $image_style_dimensions['url']);
+      $this->assertEquals($expect_dimensions[$image_style_id]['height'], $image_style_dimensions['height'], "Style $image_style_id matches height.");
+      $this->assertEquals($expect_dimensions[$image_style_id]['width'], $image_style_dimensions['width'], "Style $image_style_id matches width.");
+    }
+  }
+
+  /**
+   * Gets normalized image styles from normalized entity.
+   *
+   * @param array $normalization
+   *   The normalized entity.
+   *
+   * @return array
+   *   The normalized image styles.
+   */
+  abstract protected function getNormalizedImageStyles(array $normalization);
+
+  /**
+   * Gets the expected bubbled cacheability metadata.
+   *
+   * @return \Drupal\Core\Cache\CacheableMetadata
+   *   The expected cacheability metadata.
+   */
+  protected function getExpectedCacheability() {
+    $cacheability = new CacheableMetadata();
+    $cache_tags = [];
+    /** @var \Drupal\image\ImageStyleInterface $image_style */
+    foreach (ImageStyle::loadMultiple() as $image_style) {
+      $cache_tags = Cache::mergeTags($cache_tags, $image_style->getCacheTags());
+    }
+    $cacheability->setCacheTags($cache_tags);
+    return $cacheability;
+  }
+
+}
diff --git a/core/modules/rest/src/EventSubscriber/ResourceResponseSubscriber.php b/core/modules/rest/src/EventSubscriber/ResourceResponseSubscriber.php
index 5cb887b..fcade94 100644
--- a/core/modules/rest/src/EventSubscriber/ResourceResponseSubscriber.php
+++ b/core/modules/rest/src/EventSubscriber/ResourceResponseSubscriber.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\rest\EventSubscriber;
 
+use Drupal\Core\Cache\CacheableMetadata;
 use Drupal\Core\Cache\CacheableResponse;
 use Drupal\Core\Cache\CacheableResponseInterface;
 use Drupal\Core\Render\RenderContext;
@@ -126,11 +127,18 @@ public function getResponseFormat(RouteMatchInterface $route_match, Request $req
   /**
    * Renders a resource response body.
    *
-   * Serialization can invoke rendering (e.g., generating URLs), but the
-   * serialization API does not provide a mechanism to collect the
-   * bubbleable metadata associated with that (e.g., language and other
-   * contexts), so instead, allow those to "leak" and collect them here in
-   * a render context.
+   * During serialization, encoders and normalizers are able to explicitly
+   * bubble cacheability metadata via the 'cacheability' key-value pair in the
+   * received context. This bubbled cacheability metadata will be applied to the
+   * the response.
+   *
+   * In prior versions of Drupal 8, we allowed implicit bubbling of cacheability
+   * metadata because there was no explicit cacheability metadata bubbling API.
+   * To maintain backwards compatibility, we continue to support this, but
+   * support for this will be dropped in Drupal 9.0.0. This is especially useful
+   * when interacting with APIs that implicitly invoke rendering (for example:
+   * generating URLs): this allows those to "leak", and we collect their bubbled
+   * cacheability metadata automatically in a render context.
    *
    * @param \Symfony\Component\HttpFoundation\Request $request
    *   The request object.
@@ -150,14 +158,25 @@ protected function renderResponseBody(Request $request, ResourceResponseInterfac
 
     // If there is data to send, serialize and set it as the response body.
     if ($data !== NULL) {
+      $serialization_context = [
+        'request' => $request,
+        'cacheability' => new CacheableMetadata(),
+      ];
+
+      // @deprecated In Drupal 8.4.0, will be removed before Drupal 9.0.0. Use
+      // explicit cacheability metadata bubbling instead. (The wrapping call to
+      // executeInRenderContext() will be removed before Drupal 9.0.0.)
       $context = new RenderContext();
       $output = $this->renderer
-        ->executeInRenderContext($context, function () use ($serializer, $data, $format) {
-          return $serializer->serialize($data, $format);
+        ->executeInRenderContext($context, function() use ($serializer, $data, $format, $serialization_context) {
+          return $serializer->serialize($data, $format, $serialization_context);
         });
-
-      if ($response instanceof CacheableResponseInterface && !$context->isEmpty()) {
-        $response->addCacheableDependency($context->pop());
+      if ($response instanceof CacheableResponseInterface) {
+        if (!$context->isEmpty()) {
+          @trigger_error('Implicit cacheability metadata bubbling (onto the global render context) in normalizers is deprecated since Drupal 8.4.0 and will be removed in Drupal 9.0.0. Use the "cacheability" serialization context instead, for explicit cacheability metadata bubbling.', E_USER_DEPRECATED);
+          $response->addCacheableDependency($context->pop());
+        }
+        $response->addCacheableDependency($serialization_context['cacheability']);
       }
 
       $response->setContent($output);
