diff --git a/core/modules/rest/src/EventSubscriber/ResourceResponseSubscriber.php b/core/modules/rest/src/EventSubscriber/ResourceResponseSubscriber.php
index 86935ea..df77281 100644
--- a/core/modules/rest/src/EventSubscriber/ResourceResponseSubscriber.php
+++ b/core/modules/rest/src/EventSubscriber/ResourceResponseSubscriber.php
@@ -196,8 +196,9 @@ protected function flattenResponse(ResourceResponseInterface $response) {
    * {@inheritdoc}
    */
   public static function getSubscribedEvents() {
-    // Run shortly before \Drupal\Core\EventSubscriber\FinishResponseSubscriber.
-    $events[KernelEvents::RESPONSE][] = ['onResponse', 5];
+    // Run before \Drupal\dynamic_page_cache\EventSubscriber\DynamicPageCacheSubscriber
+    // (priority 100), so that Dynamic Page Cache can cache flattened responses.
+    $events[KernelEvents::RESPONSE][] = ['onResponse', 128];
     return $events;
   }
 
diff --git a/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php
index a55ae92..5bcf5fe 100644
--- a/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php
+++ b/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php
@@ -4,11 +4,13 @@
 
 use Drupal\Component\Utility\NestedArray;
 use Drupal\Core\Cache\Cache;
+use Drupal\Core\Cache\CacheableResponseInterface;
 use Drupal\Core\Config\Entity\ConfigEntityInterface;
 use Drupal\Core\Entity\FieldableEntityInterface;
 use Drupal\Core\Url;
 use Drupal\field\Entity\FieldConfig;
 use Drupal\field\Entity\FieldStorageConfig;
+use Drupal\rest\ResourceResponseInterface;
 use Drupal\Tests\rest\Functional\ResourceTestBase;
 use GuzzleHttp\RequestOptions;
 use Psr\Http\Message\ResponseInterface;
@@ -386,6 +388,18 @@ public function testGet() {
     // 200 for well-formed HEAD request.
     $response = $this->request('HEAD', $url, $request_options);
     $this->assertResourceResponse(200, '', $response);
+    // @todo Entity resources with URLs that begin with '/admin/' are marked as
+    //   administrative (see https://www.drupal.org/node/2874938), which
+    //   excludes them from Dynamic Page Cache (see
+    //   https://www.drupal.org/node/2877528). When either of those issues is
+    //   fixed, remove the if-test and the 'else' block.
+    if (strpos($this->entity->getEntityType()->getLinkTemplate('canonical'), '/admin/') !== 0) {
+      $this->assertTrue($response->hasHeader('X-Drupal-Dynamic-Cache'));
+      $this->assertSame(['MISS'], $response->getHeader('X-Drupal-Dynamic-Cache'));
+    }
+    else {
+      $this->assertFalse($response->hasHeader('X-Drupal-Dynamic-Cache'));
+    }
     if (!$this->account) {
       $this->assertSame(['MISS'], $response->getHeader('X-Drupal-Cache'));
     }
@@ -395,13 +409,44 @@ public function testGet() {
     $head_headers = $response->getHeaders();
 
     // 200 for well-formed GET request. Page Cache hit because of HEAD request.
+    // Same for Dynamic Page Cache hit.
     $response = $this->request('GET', $url, $request_options);
     $this->assertResourceResponse(200, FALSE, $response);
-    if (!static::$auth) {
-      $this->assertSame(['HIT'], $response->getHeader('X-Drupal-Cache'));
+    // @todo Entity resources with URLs that begin with '/admin/' are marked as
+    //   administrative (see https://www.drupal.org/node/2874938), which
+    //   excludes them from Dynamic Page Cache (see
+    //   https://www.drupal.org/node/2877528). When either of those issues is
+    //   fixed, remove the if-test and the 'else' block.
+    if (strpos($this->entity->getEntityType()->getLinkTemplate('canonical'), '/admin/') !== 0) {
+      $this->assertTrue($response->hasHeader('X-Drupal-Dynamic-Cache'));
+      if (!static::$auth) {
+        $this->assertSame(['HIT'], $response->getHeader('X-Drupal-Cache'));
+        $this->assertSame(['MISS'], $response->getHeader('X-Drupal-Dynamic-Cache'));
+      }
+      else {
+        $this->assertFalse($response->hasHeader('X-Drupal-Cache'));
+        $this->assertSame(['HIT'], $response->getHeader('X-Drupal-Dynamic-Cache'));
+        // Assert that Dynamic Page Cache did not store a ResourceResponse object,
+        // which needs serialization after every cache hit. Instead, it should
+        // contain a flattened response. Otherwise performance suffers.
+        // @see \Drupal\rest\EventSubscriber\ResourceResponseSubscriber::flattenResponse()
+        $cache_items = $this->container->get('database')
+          ->query("SELECT cid, data FROM {cache_dynamic_page_cache} WHERE cid LIKE :pattern", [
+            ':pattern' => '%[route]=rest.%',
+          ])
+          ->fetchAllAssoc('cid');
+        foreach ($cache_items as $cid => $cache_item) {
+          $cached_data = unserialize($cache_item->data);
+          if (!isset($cached_data['#cache_redirect'])) {
+            $cached_response = $cached_data['#response'];
+            $this->assertNotInstanceOf(ResourceResponseInterface::class, $cached_response);
+            $this->assertInstanceOf(CacheableResponseInterface::class, $cached_response);
+          }
+        }
+      }
     }
     else {
-      $this->assertFalse($response->hasHeader('X-Drupal-Cache'));
+      $this->assertFalse($response->hasHeader('X-Drupal-Dynamic-Cache'));
     }
     $cache_tags_header_value = $response->getHeader('X-Drupal-Cache-Tags')[0];
     $this->assertEquals($this->getExpectedCacheTags(), empty($cache_tags_header_value) ? [] : explode(' ', $cache_tags_header_value));
