diff --git a/core/lib/Drupal/Core/Entity/EntityAccessController.php b/core/lib/Drupal/Core/Entity/EntityAccessController.php
index c747cca..e1b6565 100644
--- a/core/lib/Drupal/Core/Entity/EntityAccessController.php
+++ b/core/lib/Drupal/Core/Entity/EntityAccessController.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\Core\Entity;
 
+use Drupal\Core\Access\AccessInterface;
 use Drupal\Core\Field\FieldItemListInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Field\FieldDefinitionInterface;
@@ -16,7 +17,7 @@
 /**
  * Defines a default implementation for entity access controllers.
  */
-class EntityAccessController implements EntityAccessControllerInterface {
+class EntityAccessController implements AccessInterface, EntityAccessControllerInterface {
 
   /**
    * Stores calculcated access check results.
@@ -84,10 +85,15 @@ public function access(EntityInterface $entity, $operation, $langcode = Language
       $this->moduleHandler->invokeAll($entity->entityType() . '_access', array($entity, $operation, $account, $langcode))
     );
 
-    if (($return = $this->processAccessHookResults($access)) === NULL) {
-      // No module had an opinion about the access, so let's the access
+    $return = $this->processAccessHookResults($access);
+    if ($return == self::DENY) {
+      // No module had an opinion about the access, so let the access
       // controller check create access.
-      $return = (bool) $this->checkAccess($entity, $operation, $langcode, $account);
+      $return = $this->checkAccess($entity, $operation, $langcode, $account);
+    }
+    else {
+      // Convert to a boolean.
+      $return = $return == self::ALLOW;
     }
     return $this->setCache($return, $entity->uuid(), $operation, $langcode, $account);
   }
@@ -100,19 +106,18 @@ public function access(EntityInterface $entity, $operation, $langcode = Language
    * @param array $access
    *   An array of access results of the fired access hook.
    *
-   * @return bool|null
-   *   Returns FALSE if access should be denied, TRUE if access should be
-   *   granted and NULL if no module denied access.
+   * @return string
+   *   self::ALLOW, self::DENY, or self::KILL.
    */
   protected function processAccessHookResults(array $access) {
-    if (in_array(FALSE, $access, TRUE)) {
-      return FALSE;
+    if (in_array(self::KILL, $access)) {
+      return self::KILL;
     }
-    elseif (in_array(TRUE, $access, TRUE)) {
-      return TRUE;
+    elseif (in_array(self::ALLOW, $access)) {
+      return self::ALLOW;
     }
     else {
-      return;
+      return self::DENY;
     }
   }
 
@@ -132,17 +137,10 @@ protected function processAccessHookResults(array $access) {
    * @param \Drupal\Core\Session\AccountInterface $account
    *   The user for which to check access.
    *
-   * @return bool|null
-   *   TRUE if access was granted, FALSE if access was denied and NULL if access
-   *   could not be determined.
+   * @return bool
    */
   protected function checkAccess(EntityInterface $entity, $operation, $langcode, AccountInterface $account) {
-    if (!empty($this->entityInfo['admin_permission'])) {
-      return $account->hasPermission($this->entityInfo['admin_permission']);
-    }
-    else {
-      return NULL;
-    }
+    return !empty($this->entityInfo['admin_permission']) && $account->hasPermission($this->entityInfo['admin_permission']);
   }
 
   /**
@@ -231,10 +229,15 @@ public function createAccess($entity_bundle = NULL, AccountInterface $account =
       $this->moduleHandler->invokeAll($this->entityType . '_create_access', array($account, $context['langcode']))
     );
 
-    if (($return = $this->processAccessHookResults($access)) === NULL) {
-      // No module had an opinion about the access, so let's the access
+    $return = $this->processAccessHookResults($access);
+    if ($return == self::DENY) {
+      // No module had an opinion about the access, so let the access
       // controller check create access.
-      $return = (bool) $this->checkCreateAccess($account, $context, $entity_bundle);
+      $return = $this->checkCreateAccess($account, $context, $entity_bundle);
+    }
+    else {
+      // Convert to a boolean.
+      $return = $return == self::ALLOW;
     }
     return $this->setCache($return, $cid, 'create', $context['langcode'], $account);
   }
@@ -253,17 +256,10 @@ public function createAccess($entity_bundle = NULL, AccountInterface $account =
    *   (optional) The bundle of the entity. Required if the entity supports
    *   bundles, defaults to NULL otherwise.
    *
-   * @return bool|null
-   *   TRUE if access was granted, FALSE if access was denied and NULL if access
-   *   could not be determined.
+   * @return bool
    */
   protected function checkCreateAccess(AccountInterface $account, array $context, $entity_bundle = NULL) {
-    if (!empty($this->entityInfo['admin_permission'])) {
-      return $account->hasPermission($this->entityInfo['admin_permission']);
-    }
-    else {
-      return NULL;
-    }
+    return !empty($this->entityInfo['admin_permission']) && $account->hasPermission($this->entityInfo['admin_permission']);
   }
 
   /**
@@ -315,17 +311,10 @@ public function fieldAccess($operation, FieldDefinitionInterface $field_definiti
       'account' => $account,
     );
     $this->moduleHandler->alter('entity_field_access', $grants, $context);
+    $return = $this->processAccessHookResults($grants);
 
-    // One grant being FALSE is enough to deny access immediately.
-    if (in_array(FALSE, $grants, TRUE)) {
-      return FALSE;
-    }
-    // At least one grant has the explicit opinion to allow access.
-    if (in_array(TRUE, $grants, TRUE)) {
-      return TRUE;
-    }
-    // All grants are NULL and have no opinion - deny access in that case.
-    return FALSE;
+    // Convert to a boolean.
+    return $return == self::ALLOW;
   }
 
 }
diff --git a/core/modules/field/tests/modules/field_test/field_test.field.inc b/core/modules/field/tests/modules/field_test/field_test.field.inc
index f889d85..e7bbae6 100644
--- a/core/modules/field/tests/modules/field_test/field_test.field.inc
+++ b/core/modules/field/tests/modules/field_test/field_test.field.inc
@@ -5,6 +5,7 @@
  * Defines a field type and its formatters and widgets.
  */
 
+use Drupal\Core\Access\AccessInterface;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Field\FieldDefinitionInterface;
 use Drupal\Core\Field\FieldItemListInterface;
@@ -39,14 +40,14 @@ function field_test_default_value(EntityInterface $entity, $field, $instance) {
  */
 function field_test_entity_field_access($operation, FieldDefinitionInterface $field_definition, AccountInterface $account, FieldItemListInterface $items = NULL) {
   if ($field_definition->getFieldName() == "field_no_{$operation}_access") {
-    return FALSE;
+    return AccessInterface::KILL;
   }
 
   // Only grant view access to test_view_field fields when the user has
   // 'view test_view_field content' permission.
   if ($field_definition->getFieldName() == 'test_view_field' && $operation == 'view' && !$account->hasPermission('view test_view_field content')) {
-    return FALSE;
+    return AccessInterface::KILL;
   }
 
-  return TRUE;
+  return AccessInterface::ALLOW;
 }
diff --git a/core/modules/file/file.api.php b/core/modules/file/file.api.php
index 9ebbc95..a462ed4 100644
--- a/core/modules/file/file.api.php
+++ b/core/modules/file/file.api.php
@@ -220,7 +220,7 @@ function hook_file_delete(Drupal\file\FileInterface $file) {
  */
 function hook_file_download_access($field, Drupal\Core\Entity\EntityInterface $entity, Drupal\file\FileInterface $file) {
   if ($entity->entityType() == 'node') {
-    return $entity->access('view');
+    return $entity->access('view') ? $entity::ALLOW : $entity::DENY;
   }
 }
 
diff --git a/core/modules/node/node.api.php b/core/modules/node/node.api.php
index ba67aca..2be139e 100644
--- a/core/modules/node/node.api.php
+++ b/core/modules/node/node.api.php
@@ -522,73 +522,6 @@ function hook_node_load($nodes, $types) {
   }
 }
 
-/**
- * Controls access to a node.
- *
- * Modules may implement this hook if they want to have a say in whether or not
- * a given user has access to perform a given operation on a node.
- *
- * The administrative account (user ID #1) always passes any access check, so
- * this hook is not called in that case. Users with the "bypass node access"
- * permission may always view and edit content through the administrative
- * interface.
- *
- * Note that not all modules will want to influence access on all node types. If
- * your module does not want to actively grant or block access, return
- * NODE_ACCESS_IGNORE or simply return nothing. Blindly returning FALSE will
- * break other node access modules.
- *
- * Also note that this function isn't called for node listings (e.g., RSS feeds,
- * the default home page at path 'node', a recent content block, etc.) See
- * @link node_access Node access rights @endlink for a full explanation.
- *
- * @param \Drupal\Core\Entity\EntityInterface|string $node
- *   Either a node entity or the machine name of the content type on which to
- *   perform the access check.
- * @param string $op
- *   The operation to be performed. Possible values:
- *   - "create"
- *   - "delete"
- *   - "update"
- *   - "view"
- * @param object $account
- *   The user object to perform the access check operation on.
- * @param object $langcode
- *   The language code to perform the access check operation on.
- *
- * @return string
- *   - NODE_ACCESS_ALLOW: if the operation is to be allowed.
- *   - NODE_ACCESS_DENY: if the operation is to be denied.
- *   - NODE_ACCESS_IGNORE: to not affect this operation at all.
- *
- * @ingroup node_access
- */
-function hook_node_access(\Drupal\node\NodeInterface $node, $op, $account, $langcode) {
-  $type = is_string($node) ? $node : $node->getType();
-
-  $configured_types = node_permissions_get_configured_types();
-  if (isset($configured_types[$type])) {
-    if ($op == 'create' && user_access('create ' . $type . ' content', $account)) {
-      return NODE_ACCESS_ALLOW;
-    }
-
-    if ($op == 'update') {
-      if (user_access('edit any ' . $type . ' content', $account) || (user_access('edit own ' . $type . ' content', $account) && ($account->id() == $node->getAuthorId()))) {
-        return NODE_ACCESS_ALLOW;
-      }
-    }
-
-    if ($op == 'delete') {
-      if (user_access('delete any ' . $type . ' content', $account) || (user_access('delete own ' . $type . ' content', $account) && ($account->id() == $node->getAuthorId()))) {
-        return NODE_ACCESS_ALLOW;
-      }
-    }
-  }
-
-  // Returning nothing from this function would have the same effect.
-  return NODE_ACCESS_IGNORE;
-}
-
 
 /**
  * Act on a node object about to be shown on the add/edit form.
diff --git a/core/modules/node/node.module b/core/modules/node/node.module
index faf8217..3fee222 100644
--- a/core/modules/node/node.module
+++ b/core/modules/node/node.module
@@ -54,30 +54,6 @@
 const NODE_STICKY = 1;
 
 /**
- * Denotes that access is allowed for a node.
- *
- * Modules should return this value from hook_node_access() to allow access to a
- * node.
- */
-const NODE_ACCESS_ALLOW = TRUE;
-
-/**
- * Denotes that access is denied for a node.
- *
- * Modules should return this value from hook_node_access() to deny access to a
- * node.
- */
-const NODE_ACCESS_DENY = FALSE;
-
-/**
- * Denotes that access is unaffected for a node.
- *
- * Modules should return this value from hook_node_access() to indicate no
- * effect on node access.
- */
-const NODE_ACCESS_IGNORE = NULL;
-
-/**
  * Implements hook_help().
  */
 function node_help($path, $arg) {
@@ -1496,14 +1472,11 @@ function node_form_system_themes_admin_form_submit($form, &$form_state) {
  * users have unrestricted access to all nodes. user 1 will always pass this
  * check.
  *
- * Next, all implementations of hook_node_access() will be called. Each
- * implementation may explicitly allow, explicitly deny, or ignore the access
- * request. If at least one module says to deny the request, it will be rejected.
- * If no modules deny the request and at least one says to allow it, the request
- * will be permitted.
+ * Next, all implementations of hook_ENTITY_TYPE_access() (effectively
+ * hook_node_access() will be called.
  *
- * If all modules ignore the access request, then the node_access table is used
- * to determine access. All node access modules are queried using
+ * If no module allows or kills the access request, then the {node_access} table
+ * is used to determine access. All node access modules are queried using
  * hook_node_grants() to assemble a list of "grant IDs" for the user. This list
  * is compared against the table. If any row contains the node ID in question
  * (or 0, which stands for "all nodes"), one of the grant IDs returned, and a
@@ -1513,25 +1486,16 @@ function node_form_system_themes_admin_form_submit($form, &$form_state) {
  *
  * In node listings (lists of nodes generated from a select query, such as the
  * default home page at path 'node', an RSS feed, a recent content block, etc.),
- * the process above is followed except that hook_node_access() is not called on
- * each node for performance reasons and for proper functioning of the pager
- * system. When adding a node listing to your module, be sure to use a dynamic
- * query created by db_select() and add a tag of "node_access". This will allow
- * modules dealing with node access to ensure only nodes to which the user has
- * access are retrieved, through the use of hook_query_TAG_alter().
- *
- * Note: Even a single module returning NODE_ACCESS_DENY from hook_node_access()
- * will block access to the node. Therefore, implementers should take care to
- * not deny access unless they really intend to. Unless a module wishes to
- * actively deny access it should return NODE_ACCESS_IGNORE (or simply return
- * nothing) to allow other modules or the node_access table to control access.
- *
- * To see how to write a node access module of your own, see
- * node_access_example.module.
+ * the process above is followed except that hook_ENTITY_TYPE_access() is not
+ * called on each node for performance reasons and for proper functioning of the
+ * pager system. When adding a node listing to your module, be sure to use a
+ * dynamic query created by db_select() and add a tag of "node_access". This
+ * will allow modules dealing with node access to ensure only nodes to which the
+ * user has access are retrieved, through the use of hook_query_TAG_alter().
  */
 
 /**
- * Implements hook_node_access().
+ * Implements hook_ENTITY_TYPE_access().
  */
 function node_node_access($node, $op, $account) {
   $type = $node->bundle();
@@ -1539,23 +1503,23 @@ function node_node_access($node, $op, $account) {
   $configured_types = node_permissions_get_configured_types();
   if (isset($configured_types[$type])) {
     if ($op == 'create' && user_access('create ' . $type . ' content', $account)) {
-      return NODE_ACCESS_ALLOW;
+      return $node::ALLOW;
     }
 
     if ($op == 'update') {
       if (user_access('edit any ' . $type . ' content', $account) || (user_access('edit own ' . $type . ' content', $account) && ($account->id() == $node->getAuthorId()))) {
-        return NODE_ACCESS_ALLOW;
+        return $node::ALLOW;
       }
     }
 
     if ($op == 'delete') {
       if (user_access('delete any ' . $type . ' content', $account) || (user_access('delete own ' . $type . ' content', $account) && ($account->id() == $node->getAuthorId()))) {
-        return NODE_ACCESS_ALLOW;
+        return $node::ALLOW;
       }
     }
   }
 
-  return NODE_ACCESS_IGNORE;
+  return $node::DENY;
 }
 
 /**
@@ -1608,8 +1572,8 @@ function node_list_permissions($type) {
  * node_permissions_$type variable to 0. Core does not provide an interface for
  * doing so. However, contrib modules may exclude their own nodes in
  * hook_install(). Alternatively, contrib modules may configure all node types
- * at once, or decide to apply some other hook_node_access() implementation to
- * some or all node types.
+ * at once, or decide to apply some other hook_ENTITY_TYPE_access()
+ * implementation to some or all node types.
  *
  * @return
  *   An array of node types managed by this module.
diff --git a/core/modules/node/tests/modules/node_access_test/node_access_test.module b/core/modules/node/tests/modules/node_access_test/node_access_test.module
index 12800b1..a3f821d 100644
--- a/core/modules/node/tests/modules/node_access_test/node_access_test.module
+++ b/core/modules/node/tests/modules/node_access_test/node_access_test.module
@@ -150,13 +150,13 @@ function _node_access_test_node_write(EntityInterface $node) {
 }
 
 /**
- * Implements hook_node_access().
+ * Implements hook_ENTITY_TYPE_access().
  */
 function node_access_test_node_access($node, $op, $account, $langcode) {
   $secret_catalan = \Drupal::state()->get('node_access_test_secret_catalan') ?: 0;
   if ($secret_catalan && $langcode == 'ca') {
     // Make all Catalan content secret.
-    return NODE_ACCESS_DENY;
+    return $node::KILL;
   }
-  return NODE_ACCESS_IGNORE;
+  return $node::DENY;
 }
diff --git a/core/modules/system/entity.api.php b/core/modules/system/entity.api.php
index 8414b72..12458a6 100644
--- a/core/modules/system/entity.api.php
+++ b/core/modules/system/entity.api.php
@@ -5,6 +5,7 @@
  * Hooks provided the Entity module.
  */
 
+use Drupal\Core\Access\AccessInterface;
 use Drupal\Core\Entity\ContentEntityInterface;
 use Drupal\Core\Field\FieldDefinition;
 
@@ -25,14 +26,13 @@
  * @param string $langcode
  *    The code of the language $entity is accessed in.
  *
- * @return bool|null
- *   A boolean to explicitly allow or deny access, or NULL to neither allow nor
- *   deny access.
+ * @return string
+ *   $entity::ALLOW, $entity::DENY, or $entity::KILL.
  *
  * @see \Drupal\Core\Entity\EntityAccessController
  */
 function hook_entity_access(\Drupal\Core\Entity\EntityInterface $entity, $operation, \Drupal\Core\Session\AccountInterface $account, $langcode) {
-  return NULL;
+  return $entity::DENY;
 }
 
 /**
@@ -47,14 +47,13 @@ function hook_entity_access(\Drupal\Core\Entity\EntityInterface $entity, $operat
  * @param string $langcode
  *    The code of the language $entity is accessed in.
  *
- * @return bool|null
- *   A boolean to explicitly allow or deny access, or NULL to neither allow nor
- *   deny access.
+ * @return string
+ *   $entity::ALLOW, $entity::DENY, or $entity::KILL.
  *
  * @see \Drupal\Core\Entity\EntityAccessController
  */
 function hook_ENTITY_TYPE_access(\Drupal\Core\Entity\EntityInterface $entity, $operation, \Drupal\Core\Session\AccountInterface $account, $langcode) {
-  return NULL;
+  return $entity::DENY;
 }
 
 /**
@@ -65,14 +64,13 @@ function hook_ENTITY_TYPE_access(\Drupal\Core\Entity\EntityInterface $entity, $o
  * @param string $langcode
  *    The code of the language $entity is accessed in.
  *
- * @return bool|null
- *   A boolean to explicitly allow or deny access, or NULL to neither allow nor
- *   deny access.
+ * @return string
+ *   $entity::ALLOW, $entity::DENY, or $entity::KILL.
  *
  * @see \Drupal\Core\Entity\EntityAccessController
  */
 function hook_entity_create_access(\Drupal\Core\Session\AccountInterface $account, $langcode) {
-  return NULL;
+  return $entity::DENY;
 }
 
 /**
@@ -83,14 +81,13 @@ function hook_entity_create_access(\Drupal\Core\Session\AccountInterface $accoun
  * @param string $langcode
  *    The code of the language $entity is accessed in.
  *
- * @return bool|null
- *   A boolean to explicitly allow or deny access, or NULL to neither allow nor
- *   deny access.
+ * @return string
+ *   $entity::ALLOW, $entity::DENY, or $entity::KILL.
  *
  * @see \Drupal\Core\Entity\EntityAccessController
  */
 function hook_ENTITY_TYPE_create_access(\Drupal\Core\Session\AccountInterface $account, $langcode) {
-  return NULL;
+  return $entity::DENY;
 }
 
 /**
@@ -715,13 +712,12 @@ function hook_entity_operation_alter(array &$operations, \Drupal\Core\Entity\Ent
  *   (optional) The entity field object on which the operation is to be
  *   performed.
  *
- * @return bool|null
- *   TRUE if access should be allowed, FALSE if access should be denied and NULL
- *   if the implementation has no opinion.
+ * @return string
+ *   One of the \Drupal\Core\Access\AccessInterface constants.
  */
 function hook_entity_field_access($operation, \Drupal\Core\Field\FieldDefinitionInterface $field_definition, \Drupal\Core\Session\AccountInterface $account, \Drupal\Core\Field\FieldItemListInterface $items = NULL) {
   if ($field_definition->getFieldName() == 'field_of_interest' && $operation == 'edit') {
-    return user_access('update field of interest', $account);
+    return $account->hasPermission('update field of interest') ? AccessInterface::ALLOW : AccessInterface::DENY;
   }
 }
 
@@ -734,7 +730,8 @@ function hook_entity_field_access($operation, \Drupal\Core\Field\FieldDefinition
  * @param array $grants
  *   An array of grants gathered by hook_entity_field_access(). The array is
  *   keyed by the module that defines the field's access control; the values are
- *   grant responses for each module (Boolean or NULL).
+ *   grant responses for each module (\Drupal\Core\Access\AccessInterface
+ *   constants).
  * @param array $context
  *   Context array on the performed operation with the following keys:
  *   - operation: The operation to be performed (string).
@@ -747,12 +744,12 @@ function hook_entity_field_access($operation, \Drupal\Core\Field\FieldDefinition
  */
 function hook_entity_field_access_alter(array &$grants, array $context) {
   $field_definition = $context['field_definition'];
-  if ($field_definition->getFieldName() == 'field_of_interest' && $grants['node'] === FALSE) {
+  if ($field_definition->getFieldName() == 'field_of_interest' && $grants['node'] === AccessInterface::KILL) {
     // Override node module's restriction to no opinion. We don't want to
     // provide our own access hook, we only want to take out node module's part
     // in the access handling of this field. We also don't want to switch node
     // module's grant to TRUE, because the grants of other modules should still
     // decide on their own if this field is accessible or not.
-    $grants['node'] = NULL;
+    $grants['node'] = AccessInterface::DENY;
   }
 }
diff --git a/core/modules/system/tests/modules/entity_test/entity_test.module b/core/modules/system/tests/modules/entity_test/entity_test.module
index 9c2b0b9..d2e0d8f 100644
--- a/core/modules/system/tests/modules/entity_test/entity_test.module
+++ b/core/modules/system/tests/modules/entity_test/entity_test.module
@@ -5,6 +5,7 @@
  * Test module for the entity API providing several entity types for testing.
  */
 
+use Drupal\Core\Access\AccessInterface;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Field\FieldDefinitionInterface;
 use Drupal\Core\Field\FieldItemListInterface;
@@ -400,13 +401,14 @@ function entity_test_entity_field_access($operation, FieldDefinitionInterface $f
   if ($field_definition->getFieldName() == 'field_test_text') {
     if ($items) {
       if ($items[0]->value == 'no access value') {
-        return FALSE;
+        return AccessInterface::KILL;
       }
       elseif ($operation == 'delete' && $items[0]->value == 'no delete access value') {
-        return FALSE;
+        return AccessInterface::KILL;
       }
     }
   }
+  return AccessInterface::DENY;
 }
 
 /**
