diff --git a/core/lib/Drupal/Core/EventSubscriber/FinishResponseSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/FinishResponseSubscriber.php
index 4007003..7da48aa 100644
--- a/core/lib/Drupal/Core/EventSubscriber/FinishResponseSubscriber.php
+++ b/core/lib/Drupal/Core/EventSubscriber/FinishResponseSubscriber.php
@@ -115,13 +115,22 @@ public function onRespond(FilterResponseEvent $event) {
     $response->headers->set('X-Content-Type-Options', 'nosniff', FALSE);
     $response->headers->set('X-Frame-Options', 'SAMEORIGIN', FALSE);
 
+    // If the current response isn't an implementation of the
+    // CacheableResponseInterface, we're trusting that a Response is either
+    // explicitly not cacheable or that caching headers are already set from
+    // another place.
+    if (!($response instanceof CacheableResponseInterface)) {
+      if (!$this->isCacheControlCustomized($response)) {
+        $this->setResponseNotCacheable($response, $request);
+      }
+      return;
+    }
+
     // Expose the cache contexts and cache tags associated with this page in a
     // X-Drupal-Cache-Contexts and X-Drupal-Cache-Tags header respectively.
-    if ($response instanceof CacheableResponseInterface) {
-      $response_cacheability = $response->getCacheableMetadata();
-      $response->headers->set('X-Drupal-Cache-Tags', implode(' ', $response_cacheability->getCacheTags()));
-      $response->headers->set('X-Drupal-Cache-Contexts', implode(' ', $this->cacheContextsManager->optimizeTokens($response_cacheability->getCacheContexts())));
-    }
+    $response_cacheability = $response->getCacheableMetadata();
+    $response->headers->set('X-Drupal-Cache-Tags', implode(' ', $response_cacheability->getCacheTags()));
+    $response->headers->set('X-Drupal-Cache-Contexts', implode(' ', $this->cacheContextsManager->optimizeTokens($response_cacheability->getCacheContexts())));
 
     $is_cacheable = ($this->requestPolicy->check($request) === RequestPolicyInterface::ALLOW) && ($this->responsePolicy->check($response, $request) !== ResponsePolicyInterface::DENY);
 
diff --git a/core/modules/image/src/Controller/ImageStyleDownloadController.php b/core/modules/image/src/Controller/ImageStyleDownloadController.php
index 2b2d89f..4ad6dc6 100644
--- a/core/modules/image/src/Controller/ImageStyleDownloadController.php
+++ b/core/modules/image/src/Controller/ImageStyleDownloadController.php
@@ -118,7 +118,15 @@ public function deliver(Request $request, $scheme, ImageStyleInterface $image_st
     // control access to the file.
     if ($scheme == 'private') {
       if (file_exists($derivative_uri)) {
-        return parent::download($request, $scheme);
+        $response = parent::download($request, $scheme);
+
+        if ($response instanceof BinaryFileResponse) {
+          $response->headers->add([
+            'Cache-Control' => 'private',
+            'Expires' => \DateTime::createFromFormat('j-M-Y H:i:s T', '19-Nov-1978 05:00:00 GMT')->format('D, d M Y H:i:s T'),
+          ]);
+        }
+        return $response;
       }
       else {
         $headers = $this->moduleHandler()->invokeAll('file_download', array($image_uri));
@@ -172,6 +180,8 @@ public function deliver(Request $request, $scheme, ImageStyleInterface $image_st
       $headers += array(
         'Content-Type' => $image->getMimeType(),
         'Content-Length' => $image->getFileSize(),
+        'Cache-Control' => 'private',
+        'Expires' => \DateTime::createFromFormat('j-M-Y H:i:s T', '19-Nov-1978 05:00:00 GMT')->format('D, d M Y H:i:s T'),
       );
       return new BinaryFileResponse($uri, 200, $headers);
     }
diff --git a/core/modules/page_cache/src/Tests/PageCacheTest.php b/core/modules/page_cache/src/Tests/PageCacheTest.php
index e97a1b4..e00b947 100644
--- a/core/modules/page_cache/src/Tests/PageCacheTest.php
+++ b/core/modules/page_cache/src/Tests/PageCacheTest.php
@@ -406,4 +406,50 @@ public function testFormImmutability() {
     $this->assertText("Immutable: FALSE", "Form is not immutable,");
   }
 
+  /**
+   * Tests cacheability of a CacheableResponse
+   *
+   * Tests the difference between having a controller return a normal Response
+   * object versus returning a Response object that implements the
+   * CacheableResponseInterface.
+   */
+  public function testCacheableResponseResponses() {
+    $config = $this->config('system.performance');
+    $config->set('cache.page.max_age', 300);
+    $config->save();
+
+    // Try to fill the cache.
+    $this->drupalGet('/system-test/respond-reponse');
+    $this->assertFalse(in_array('X-Drupal-Cache', $this->drupalGetHeaders()), 'Drupal page cache header not found');
+    $this->assertEqual($this->drupalGetHeader('Cache-Control'), 'must-revalidate, no-cache, post-check=0, pre-check=0, private', 'Cache-Control header was sent');
+
+    // Still not cached, uncacheable response.
+    $this->drupalGet('/system-test/respond-reponse');
+    $this->assertFalse(in_array('X-Drupal-Cache', $this->drupalGetHeaders()), 'Drupal page cache header not found');
+    $this->assertEqual($this->drupalGetHeader('Cache-Control'), 'must-revalidate, no-cache, post-check=0, pre-check=0, private', 'Cache-Control header was sent');
+
+    // Try to fill the cache.
+    $this->drupalGet('/system-test/respond-cacheable-reponse');
+    $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS', 'Page was not cached.');
+    $this->assertEqual($this->drupalGetHeader('Cache-Control'), 'max-age=300, public', 'Cache-Control header was sent.');
+
+    // Should be cached now.
+    $this->drupalGet('/system-test/respond-cacheable-reponse');
+    $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT', 'Page was cached.');
+    $this->assertEqual($this->drupalGetHeader('Cache-Control'), 'max-age=300, public', 'Cache-Control header was sent.');
+
+    // Uninstall page cache. This should flush all caches so the next call to a
+    // previously cache page should be a miss now.
+    $this->container->get('module_installer')
+      ->uninstall(['page_cache']);
+
+    // Try to fill the cache.
+    $this->drupalGet('/system-test/respond-reponse');
+    $this->assertEqual($this->drupalGetHeader('Cache-Control'), 'must-revalidate, no-cache, post-check=0, pre-check=0, private', 'Cache-Control header was sent');
+
+    // Still not cached, uncacheable response.
+    $this->drupalGet('/system-test/respond-reponse');
+    $this->assertEqual($this->drupalGetHeader('Cache-Control'), 'must-revalidate, no-cache, post-check=0, pre-check=0, private', 'Cache-Control header was sent');
+  }
+
 }
diff --git a/core/modules/rest/src/RequestHandler.php b/core/modules/rest/src/RequestHandler.php
index ee4b890..9cb413e 100644
--- a/core/modules/rest/src/RequestHandler.php
+++ b/core/modules/rest/src/RequestHandler.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\rest;
 
+use Drupal\Core\Cache\CacheableResponse;
 use Drupal\Core\Render\RenderContext;
 use Drupal\Core\Routing\RouteMatchInterface;
 use Symfony\Component\DependencyInjection\ContainerAwareInterface;
@@ -67,7 +68,7 @@ public function handle(RouteMatchInterface $route_match, Request $request) {
         catch (UnexpectedValueException $e) {
           $error['error'] = $e->getMessage();
           $content = $serializer->serialize($error, $format);
-          return new Response($content, 400, array('Content-Type' => $request->getMimeType($format)));
+          return new CacheableResponse($content, 400, ['Content-Type' => $request->getMimeType($format)]);
         }
       }
       else {
@@ -100,7 +101,7 @@ public function handle(RouteMatchInterface $route_match, Request $request) {
       // Add the default content type, but only if the headers from the
       // exception have not specified it already.
       $headers = $e->getHeaders() + array('Content-Type' => $request->getMimeType($format));
-      return new Response($content, $e->getStatusCode(), $headers);
+      return new CacheableResponse($content, $e->getStatusCode(), $headers);
     }
 
     // Serialize the outgoing data for the response, if available.
diff --git a/core/modules/shortcut/src/Tests/ShortcutSetsTest.php b/core/modules/shortcut/src/Tests/ShortcutSetsTest.php
index bc3c5f9..3c5ca82 100644
--- a/core/modules/shortcut/src/Tests/ShortcutSetsTest.php
+++ b/core/modules/shortcut/src/Tests/ShortcutSetsTest.php
@@ -148,8 +148,7 @@ function testShortcutSetSwitchCreate() {
   function testShortcutSetSwitchNoSetName() {
     $edit = array('set' => 'new');
     $this->drupalPostForm('user/' . $this->adminUser->id() . '/shortcuts', $edit, t('Change set'));
-    $this->assertRaw('1 error has been found:');
-    $this->assertRaw('<a href="#edit-label">Label</a>');
+    $this->assertRaw('1 error has been found: <a href="#edit-label">Label</a>');
     $current_set = shortcut_current_displayed_set($this->adminUser);
     $this->assertEqual($current_set->id(), $this->set->id(), 'Attempting to switch to a new shortcut set without providing a set name does not succeed.');
     $this->assertFieldByXPath("//input[@name='label' and contains(concat(' ', normalize-space(@class), ' '), ' error ')]", NULL, 'The new set label field has the error class');
diff --git a/core/modules/system/src/FileDownloadController.php b/core/modules/system/src/FileDownloadController.php
index a65342d..8344826 100644
--- a/core/modules/system/src/FileDownloadController.php
+++ b/core/modules/system/src/FileDownloadController.php
@@ -52,17 +52,11 @@ public function download(Request $request, $scheme = 'private') {
       // Let other modules provide headers and controls access to the file.
       $headers = $this->moduleHandler()->invokeAll('file_download', array($uri));
 
-      foreach ($headers as $result) {
-        if ($result == -1) {
-          throw new AccessDeniedHttpException();
-        }
+      if (in_array(-1, $headers) || empty($headers)) {
+        throw new AccessDeniedHttpException();
       }
 
-      if (count($headers)) {
-        return new BinaryFileResponse($uri, 200, $headers);
-      }
-
-      throw new AccessDeniedHttpException();
+      return new BinaryFileResponse($uri, 200, $headers);
     }
 
     throw new NotFoundHttpException();
diff --git a/core/modules/system/tests/modules/system_test/src/Controller/PageCacheAcceptHeaderController.php b/core/modules/system/tests/modules/system_test/src/Controller/PageCacheAcceptHeaderController.php
index c24d7c5..a77c472 100644
--- a/core/modules/system/tests/modules/system_test/src/Controller/PageCacheAcceptHeaderController.php
+++ b/core/modules/system/tests/modules/system_test/src/Controller/PageCacheAcceptHeaderController.php
@@ -7,9 +7,9 @@
 
 namespace Drupal\system_test\Controller;
 
+use Drupal\Core\Cache\CacheableJsonResponse;
+use Drupal\Core\Cache\CacheableResponse;
 use Symfony\Component\HttpFoundation\Request;
-use Symfony\Component\HttpFoundation\JsonResponse;
-use Symfony\Component\HttpFoundation\Response;
 
 /**
  * Defines a controller to respond the page cache accept header test.
@@ -26,10 +26,10 @@ class PageCacheAcceptHeaderController {
    */
   public function content(Request $request) {
     if ($request->getRequestFormat() === 'json') {
-      return new JsonResponse(array('content' => 'oh hai this is json'));
+      return new CacheableJsonResponse(['content' => 'oh hai this is json']);
     }
     else {
-      return new Response("<p>oh hai this is html.</p>");
+      return new CacheableResponse("<p>oh hai this is html.</p>");
     }
   }
 }
diff --git a/core/modules/system/tests/modules/system_test/src/Controller/SystemTestController.php b/core/modules/system/tests/modules/system_test/src/Controller/SystemTestController.php
index 15ab478..3ab4d5e 100644
--- a/core/modules/system/tests/modules/system_test/src/Controller/SystemTestController.php
+++ b/core/modules/system/tests/modules/system_test/src/Controller/SystemTestController.php
@@ -8,6 +8,7 @@
 namespace Drupal\system_test\Controller;
 
 use Drupal\Core\Access\AccessResult;
+use Drupal\Core\Cache\CacheableResponse;
 use Drupal\Core\Controller\ControllerBase;
 use Drupal\Core\Render\RendererInterface;
 use Drupal\Core\Render\SafeString;
@@ -259,13 +260,21 @@ public function authorizeInit($page_title) {
    */
   public function setHeader(Request $request) {
     $query = $request->query->all();
-    $response = new Response();
+    $response = new CacheableResponse();
     $response->headers->set($query['name'], $query['value']);
     $response->setContent($this->t('The following header was set: %name: %value', array('%name' => $query['name'], '%value' => $query['value'])));
 
     return $response;
   }
 
+  public function respondWithReponse(Request $request) {
+    return new Response('test');
+  }
+
+  public function respondWithCacheableReponse(Request $request) {
+    return new CacheableResponse('test');
+  }
+
   /**
    * A simple page callback which adds a register shutdown function.
    */
diff --git a/core/modules/system/tests/modules/system_test/system_test.routing.yml b/core/modules/system/tests/modules/system_test/system_test.routing.yml
index ecb9921..94a1bef 100644
--- a/core/modules/system/tests/modules/system_test/system_test.routing.yml
+++ b/core/modules/system/tests/modules/system_test/system_test.routing.yml
@@ -129,3 +129,17 @@ system_test.permission_dependent_route_access:
     _controller: '\Drupal\system_test\Controller\SystemTestController::mainContentFallback'
   requirements:
     _permission: 'pet llamas'
+
+system_test.respond_response:
+  path: '/system-test/respond-reponse'
+  defaults:
+    _controller: '\Drupal\system_test\Controller\SystemTestController::respondWithReponse'
+  requirements:
+    _access: 'TRUE'
+
+system_test.respond_cacheable_response:
+  path: '/system-test/respond-cacheable-reponse'
+  defaults:
+    _controller: '\Drupal\system_test\Controller\SystemTestController::respondWithCacheableReponse'
+  requirements:
+    _access: 'TRUE'
