diff --git a/core/lib/Drupal/Core/Entity/EntityViewBuilder.php b/core/lib/Drupal/Core/Entity/EntityViewBuilder.php
index c069ded..9a42fde 100644
--- a/core/lib/Drupal/Core/Entity/EntityViewBuilder.php
+++ b/core/lib/Drupal/Core/Entity/EntityViewBuilder.php
@@ -185,7 +185,11 @@ protected function getBuildDefaults(EntityInterface $entity, $view_mode, $langco
    *   (optional) For which language the entity should be prepared, defaults to
    *   the current content language.
    */
-  protected function alterBuild(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display, $view_mode, $langcode = NULL) { }
+  protected function alterBuild(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display, $view_mode, $langcode = NULL) {
+    if (isset($build['#cache'])) {
+      $build['#cache']['granularity'] = max($build['#cache']['granularity'], $display->getCacheGranularity());
+    }
+  }
 
   /**
    * {@inheritdoc}
diff --git a/core/lib/Drupal/Core/Field/FormatterBase.php b/core/lib/Drupal/Core/Field/FormatterBase.php
index 1b4287f..81f7c75 100644
--- a/core/lib/Drupal/Core/Field/FormatterBase.php
+++ b/core/lib/Drupal/Core/Field/FormatterBase.php
@@ -132,6 +132,13 @@ public function settingsSummary() {
   public function prepareView(array $entities_items) { }
 
   /**
+   * @todo Add to FormatterInterface.
+   */
+  public function getCacheGranularity() {
+    return NULL;
+  }
+
+  /**
    * Returns the array of field settings.
    *
    * @return array
diff --git a/core/modules/entity/lib/Drupal/entity/Entity/EntityViewDisplay.php b/core/modules/entity/lib/Drupal/entity/Entity/EntityViewDisplay.php
index e4d3880..b9bf623 100644
--- a/core/modules/entity/lib/Drupal/entity/Entity/EntityViewDisplay.php
+++ b/core/modules/entity/lib/Drupal/entity/Entity/EntityViewDisplay.php
@@ -255,4 +255,17 @@ public function buildMultiple(array $entities) {
     return $build;
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function getCacheGranularity() {
+    $max_granularity = NULL;
+    foreach ($this->getFieldDefinitions() as $field_name => $definition) {
+      if ($formatter = $this->getRenderer($field_name)) {
+        $max_granularity = max($max_granularity, $formatter->getCacheGranularity());
+      }
+    }
+    return $max_granularity;
+  }
+
 }
diff --git a/core/modules/field/tests/modules/field_test/lib/Drupal/field_test/Plugin/Field/FieldFormatter/TestFieldPerUserFormatter.php b/core/modules/field/tests/modules/field_test/lib/Drupal/field_test/Plugin/Field/FieldFormatter/TestFieldPerUserFormatter.php
new file mode 100644
index 0000000..741923a
--- /dev/null
+++ b/core/modules/field/tests/modules/field_test/lib/Drupal/field_test/Plugin/Field/FieldFormatter/TestFieldPerUserFormatter.php
@@ -0,0 +1,48 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\entity_test\Plugin\field\formatter\TestFieldPerUserFormatter.
+ */
+
+namespace Drupal\field_test\Plugin\Field\FieldFormatter;
+
+use Drupal\Core\Field\FormatterBase;
+use Drupal\Core\Field\FieldItemListInterface;
+
+/**
+ * Plugin implementation of the 'field_test_per_user' formatter.
+ *
+ * @FieldFormatter(
+ *   id = "field_test_per_user",
+ *   label = @Translation("Per user"),
+ *   description = @Translation("Per user"),
+ *   field_types = {
+ *     "test_field"
+ *   },
+ *   weight = 10,
+ * )
+ */
+class TestFieldPerUserFormatter extends FormatterBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function viewElements(FieldItemListInterface $items) {
+    $elements = array();
+
+    foreach ($items as $delta => $item) {
+      $elements[$delta] = array('#markup' => $item->value . ' ' . \Drupal::currentUser()->getUsername());
+    }
+
+    return $elements;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCacheGranularity() {
+    return DRUPAL_CACHE_PER_USER;
+  }
+
+}
diff --git a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityViewCacheKeyTest.php b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityViewCacheKeyTest.php
new file mode 100644
index 0000000..059fc1e
--- /dev/null
+++ b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityViewCacheKeyTest.php
@@ -0,0 +1,106 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\system\Tests\Entity\EntityViewCacheKeyTest.
+ */
+
+namespace Drupal\system\Tests\Entity;
+
+use Drupal\Core\Entity\EntityInterface;
+
+/**
+ * Tests the render cache key returned by the entity view builder.
+ */
+class EntityViewCacheKeyTest extends EntityUnitTestBase {
+
+  /**
+   * @var \Drupal\user\UserInterface
+   */
+  protected $testUser;
+
+  /**
+   * @var \Drupal\Core\Entity\EntityInterface
+   */
+  protected $testEntity;
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('entity_reference', 'field_test');
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Entity render cache keys',
+      'description' => 'Tests the render cache key returned by the entity view builder.',
+      'group' => 'Entity API',
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    parent::setUp();
+    $this->installConfig(array('entity_test'));
+
+    // Create a test field and instance and add it to be displayed in the 'full' view mode.
+    entity_create('field_config', array(
+      'name' => 'test_field',
+      'entity_type' => 'entity_test',
+      'type' => 'test_field'
+    ))->save();
+    entity_create('field_instance_config', array(
+      'field_name' => 'test_field',
+      'entity_type' => 'entity_test',
+      'bundle' => 'entity_test',
+    ))->save();
+    entity_get_display('entity_test', 'entity_test', 'full')
+      ->setComponent('test_field', array('type' => 'field_test_default'))
+      ->save();
+
+    // Create the entity to be viewed.
+    $this->testEntity = $this->entityManager->getStorageController('entity_test')->create(array(
+      'bundle' => 'entity_test',
+      'name' => $this->randomName(),
+    ));
+    $this->testEntity->save();
+
+    // Create a user for viewing the entity.
+    $this->testUser = entity_create('user', array('uid' => 2));
+  }
+
+  /**
+   * Tests cache key generated when no fields require extra cache granularity.
+   */
+  public function testNoCacheAffectingFields() {
+    $cid = $this->getCacheKey($this->testEntity, 'full');
+    $this->assertIdentical($cid, 'entity_view:entity_test:1:full::r.authenticated');
+  }
+
+  /**
+   * Tests cache key generated when a field formatter requires per-user caching.
+   */
+  public function testPerUserFieldFormatter() {
+    entity_get_display('entity_test', 'entity_test', 'full')
+      ->setComponent('test_field', array('type' => 'field_test_per_user'))
+      ->save();
+    $cid = $this->getCacheKey($this->testEntity, 'full');
+    $this->assertIdentical($cid, 'entity_view:entity_test:1:full::u.2');
+  }
+
+  /**
+   * Builds a view of the entity and returns the cache key.
+   */
+  protected function getCacheKey(EntityInterface $entity, $view_mode) {
+    $current_user = $this->container->get('current_user');
+    $this->container->set('current_user', $this->testUser);
+    $build = $this->entityManager->getViewBuilder($entity->getEntityTypeId())->view($entity, $view_mode);
+    $cid = drupal_render_cid_create($build);
+    $this->container->set('current_user', $current_user);
+    return $cid;
+  }
+
+}
