diff --git a/core/modules/book/book.module b/core/modules/book/book.module
index e57ef54..22b9ea9 100644
--- a/core/modules/book/book.module
+++ b/core/modules/book/book.module
@@ -187,14 +187,6 @@ function book_menu() {
     'route_name' => 'book_render',
     'type' => MENU_SUGGESTED_ITEM,
   );
-  $items['book/export/%/%node'] = array(
-    'page callback' => 'book_export',
-    'page arguments' => array(2, 3),
-    'access callback' => 'book_export_access',
-    'access arguments' => array(3),
-    'type' => MENU_CALLBACK,
-    'file' => 'book.pages.inc',
-  );
   $items['node/%node/outline'] = array(
     'title' => 'Outline',
     'page callback' => 'book_outline',
@@ -218,16 +210,6 @@ function book_menu() {
 }
 
 /**
- * Access callback: Determines if the book export page is accessible.
- *
- * @param \Drupal\node\Entity\EntityInterface $node
- *   The node whose export page is to be viewed.
- */
-function book_export_access(EntityInterface $node) {
-  return user_access('access printer-friendly version') && node_access('view', $node);
-}
-
-/**
  * Access callback: Determines if the outline tab is accessible.
  *
  * Path:
@@ -1145,68 +1127,6 @@ function template_preprocess_book_export_html(&$variables) {
 }
 
 /**
- * Traverses the book tree to build printable or exportable output.
- *
- * During the traversal, the $visit_func() callback is applied to each node and
- * is called recursively for each child of the node (in weight, title order).
- *
- * @param $tree
- *   A subtree of the book menu hierarchy, rooted at the current page.
- * @param $visit_func
- *   A function callback to be called upon visiting a node in the tree.
- *
- * @return
- *   The output generated in visiting each node.
- */
-function book_export_traverse($tree, $visit_func) {
-  $output = '';
-
-  foreach ($tree as $data) {
-    // Note- access checking is already performed when building the tree.
-    if ($node = node_load($data['link']['nid'])) {
-      $children = '';
-
-      if ($data['below']) {
-        $children = book_export_traverse($data['below'], $visit_func);
-      }
-
-      if (!empty($visit_func)) {
-        $output .= call_user_func($visit_func, $node, $children);
-      }
-      else {
-        // Use the default function.
-        $output .= book_node_export($node, $children);
-      }
-    }
-  }
-
-  return $output;
-}
-
-/**
- * Generates printer-friendly HTML for a node.
- *
- * @param \Drupal\Core\Entity\EntityInterface $node
- *   The node that will be output.
- * @param $children
- *   (optional) All the rendered child nodes within the current node. Defaults
- *   to an empty string.
- *
- * @return
- *   The HTML generated for the given node.
- *
- * @see book_export_traverse()
- */
-function book_node_export(EntityInterface $node, $children = '') {
-  $build = node_view($node, 'print');
-  unset($build['#theme']);
-  // @todo Rendering should happen in the template using render().
-  $node->rendered = drupal_render($build);
-  $book_node_export_html = array('#theme' => 'book_node_export_html', '#node' => $node, '#children' => $children );
-  return drupal_render($book_node_export_html);
-}
-
-/**
  * Prepares variables for single node export templates.
  *
  * Default template: book-node-export-html.html.twig.
diff --git a/core/modules/book/book.pages.inc b/core/modules/book/book.pages.inc
index 31410a7..d05a855 100644
--- a/core/modules/book/book.pages.inc
+++ b/core/modules/book/book.pages.inc
@@ -10,81 +10,6 @@
 use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
 
 /**
- * Page callback: Generates representations of a book page and its children.
- *
- * The function delegates the generation of output to helper functions. The
- * function name is derived by prepending 'book_export_' to the given output
- * type. So, e.g., a type of 'html' results in a call to the function
- * book_export_html().
- *
- * @param $type
- *   A string encoding the type of output requested. The following types are
- *   currently supported in book module:
- *   - html: Printer-friendly HTML.
- *   Other types may be supported in contributed modules.
- * @param \Drupal\node\Entity\EntityInterface $node
- *   The node to export.
- *
- * @return
- *   A string representing the node and its children in the book hierarchy in a
- *   format determined by the $type parameter.
- *
- * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
- *
- * @see book_menu()
- */
-function book_export($type, EntityInterface $node) {
-  $type = drupal_strtolower($type);
-
-  $export_function = 'book_export_' . $type;
-
-  if (function_exists($export_function)) {
-    print call_user_func($export_function, $node);
-  }
-  else {
-    drupal_set_message(t('Unknown export format.'));
-    throw new NotFoundHttpException();
-  }
-}
-
-/**
- * Generates HTML for export when invoked by book_export().
- *
- * The given node is embedded to its absolute depth in a top level section. For
- * example, a child node with depth 2 in the hierarchy is contained in
- * (otherwise empty) <div> elements corresponding to depth 0 and depth 1.
- * This is intended to support WYSIWYG output - e.g., level 3 sections always
- * look like level 3 sections, no matter their depth relative to the node
- * selected to be exported as printer-friendly HTML.
- *
- * @param \Drupal\node\Entity\Node
- *   The node to export.
- *
- * @return
- *   A string containing HTML representing the node and its children in
- *   the book hierarchy.
- *
- * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
- * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
- */
-function book_export_html(EntityInterface $node) {
-  if (user_access('access printer-friendly version')) {
-    if (isset($node->book)) {
-      $tree = book_menu_subtree_data($node->book);
-      $contents = book_export_traverse($tree, 'book_node_export');
-      $book_exported_html = array('#theme' => 'book_export_html', '#title' => $node->label(), '#contents' => $contents, '#depth' => $node->book['depth']);
-      return drupal_render($book_exported_html);
-    }
-    else {
-      throw new NotFoundHttpException();
-    }
-  }
-  else {
-    throw new AccessDeniedHttpException();
-  }
-}
-
-/**
  * Page callback: Shows the outline form for a single node.
  *
  * @param \Drupal\Core\Entity\EntityInterface $node
diff --git a/core/modules/book/book.routing.yml b/core/modules/book/book.routing.yml
index 0ce057a..b44ccfb 100644
--- a/core/modules/book/book.routing.yml
+++ b/core/modules/book/book.routing.yml
@@ -18,3 +18,13 @@ book_settings:
     _form: 'Drupal\book\Form\BookSettingsForm'
   requirements:
     _permission: 'administer site configuration'
+
+book_export:
+  pattern: '/book/export/{type}/{node}'
+  defaults:
+    _controller: '\Drupal\book\Controller\BookController::bookExport'
+  options:
+    _access_mode: 'ALL'
+  requirements:
+    _permission: 'access printer-friendly version'
+    _entity_access: 'node.view'
diff --git a/core/modules/book/book.services.yml b/core/modules/book/book.services.yml
index 9d8c140..042df96 100644
--- a/core/modules/book/book.services.yml
+++ b/core/modules/book/book.services.yml
@@ -2,3 +2,6 @@ services:
   book.manager:
     class: Drupal\book\BookManager
     arguments: ['@database', '@plugin.manager.entity']
+  book.export:
+    class: Drupal\book\BookExport
+    arguments: ['@plugin.manager.entity']
diff --git a/core/modules/book/lib/Drupal/book/BookExport.php b/core/modules/book/lib/Drupal/book/BookExport.php
new file mode 100644
index 0000000..62a5148
--- /dev/null
+++ b/core/modules/book/lib/Drupal/book/BookExport.php
@@ -0,0 +1,140 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\book\BookExport.
+ */
+
+namespace Drupal\book;
+
+use Drupal\Core\Entity\EntityManager;
+use Drupal\node\NodeInterface;
+use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
+
+/**
+ * Provides methods for exporting book to different formats.
+ *
+ * If you would like to add another format, swap this class in container.
+ */
+class BookExport {
+
+  /**
+   * The node storage controller.
+   *
+   * @var \Drupal\Core\Entity\EntityStorageControllerInterface
+   */
+  protected $nodeStorage;
+
+  /**
+   * The node render controller.
+   *
+   * @var \Drupal\Core\Entity\EntityRenderControllerInterface
+   */
+  protected $nodeRender;
+
+  /**
+   * Constructs a BookExport object.
+   *
+   * @param \Drupal\Core\Entity\EntityManager $entityManager
+   *   The entity manager.
+   */
+  public function __construct(EntityManager $entityManager) {
+    $this->nodeStorage = $entityManager->getStorageController('node');
+    $this->nodeRender = $entityManager->getRenderController('node');
+  }
+
+  /**
+   * Generates HTML for export when invoked by book_export().
+   *
+   * The given node is embedded to its absolute depth in a top level section. For
+   * example, a child node with depth 2 in the hierarchy is contained in
+   * (otherwise empty) <div> elements corresponding to depth 0 and depth 1.
+   * This is intended to support WYSIWYG output - e.g., level 3 sections always
+   * look like level 3 sections, no matter their depth relative to the node
+   * selected to be exported as printer-friendly HTML.
+   *
+   * @param \Drupal\node\NodeInterface $node
+   *   The node to export.
+   *
+   * @throws \Exception
+   *   Thrown when the node was not attached to a book.
+   *
+   * @return array
+   *   A render array representing the HTML for a node and its children in the
+   *   book hierarchy.
+   */
+  public function bookExportHtml(NodeInterface $node) {
+    if (!isset($node->book)) {
+      throw new \Exception();
+    }
+
+    $tree = book_menu_subtree_data($node->book);
+    $contents = $this->exportTraverse($tree, array($this, 'bookNodeExport'));
+    return array(
+      '#theme' => 'book_export_html',
+      '#title' => $node->label(),
+      '#contents' => $contents,
+      '#depth' => $node->book['depth'],
+    );
+  }
+
+  /**
+   * Traverses the book tree to build printable or exportable output.
+   *
+   * During the traversal, the callback is applied to each node and is called
+   * recursively for each child of the node (in weight, title order).
+   *
+   * @param array $tree
+   *   A subtree of the book menu hierarchy, rooted at the current page.
+   * @param callable $callable
+   *   A callback to be called upon visiting a node in the tree.
+   *
+   * @return string
+   *   The output generated in visiting each node.
+   */
+  protected function exportTraverse(array $tree, $callable) {
+    // If there is no valid callable, use the default callback.
+    $callable = !empty($callable) ? $callable : array($this, 'bookNodeExport');
+
+    $output = '';
+    foreach ($tree as $data) {
+      // Note- access checking is already performed when building the tree.
+      if ($node = $this->nodeStorage->load($data['link']['nid'])) {
+        $children = $data['below'] ? $this->exportTraverse($data['below'], $callable) : '';
+
+        $callable_output = call_user_func($callable, $node, $children);
+        $output .= drupal_render($callable_output);
+      }
+    }
+
+    return $output;
+  }
+
+  /**
+   * Generates printer-friendly HTML for a node.
+   *
+   * @param \Drupal\node\NodeInterface $node
+   *   The node that will be output.
+   * @param string $children
+   *   (optional) All the rendered child nodes within the current node. Defaults
+   *   to an empty string.
+   *
+   * @return array
+   *   A render array for the exported HTML of a given node.
+   *
+   * @see \Drupal\book\BookExport::exportTraverse()
+   */
+  protected function bookNodeExport(NodeInterface $node, $children = '') {
+    $build = $this->nodeRender->view($node, 'print', NULL);
+    unset($build['#theme']);
+
+    // @todo Rendering should happen in the template using render().
+    $node->rendered = drupal_render($build);
+    return array(
+      '#theme' => 'book_node_export_html',
+      '#node' => $node,
+      '#children' => $children,
+    );
+  }
+
+}
diff --git a/core/modules/book/lib/Drupal/book/Controller/BookController.php b/core/modules/book/lib/Drupal/book/Controller/BookController.php
index 7b64e82..5a71c45 100644
--- a/core/modules/book/lib/Drupal/book/Controller/BookController.php
+++ b/core/modules/book/lib/Drupal/book/Controller/BookController.php
@@ -1,4 +1,5 @@
 <?php
+
 /**
  * @file
  * Contains \Drupal\book\Controller\BookController.
@@ -6,10 +7,14 @@
 
 namespace Drupal\book\Controller;
 
+use Drupal\book\BookManager;
+use Drupal\book\BookExport;
 use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
+use Drupal\node\NodeInterface;
+use Symfony\Component\DependencyInjection\Container;
 use Symfony\Component\DependencyInjection\ContainerInterface;
-
-use Drupal\book\BookManager;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
 
 /**
  * Controller routines for book routes.
@@ -17,24 +22,40 @@
 class BookController implements ContainerInjectionInterface {
 
   /**
-   * Book Manager Service.
+   * The book manager.
    *
    * @var \Drupal\book\BookManager
    */
   protected $bookManager;
 
   /**
-   * Injects BookManager Service.
+   * The book export service.
+   *
+   * @var \Drupal\book\BookExport
    */
-  public static function create(ContainerInterface $container) {
-    return new static($container->get('book.manager'));
-  }
+  protected $bookExport;
 
   /**
    * Constructs a BookController object.
+   *
+   * @param \Drupal\book\BookManager $bookManager
+   *   The book manager.
+   * @param \Drupal\book\BookExport $bookExport
+   *   The book export service.
    */
-  public function __construct(BookManager $bookManager) {
+  public function __construct(BookManager $bookManager, BookExport $bookExport) {
     $this->bookManager = $bookManager;
+    $this->bookExport = $bookExport;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('book.manager'),
+      $container->get('book.export')
+    );
   }
 
   /**
@@ -48,7 +69,6 @@ public function adminOverview() {
     $rows = array();
 
     $headers = array(t('Book'), t('Operations'));
-
     // Add any recognized books to the table list.
     foreach ($this->bookManager->getAllBooks() as $book) {
       $row = array(
@@ -67,8 +87,12 @@ public function adminOverview() {
       );
       $rows[] = $row;
     }
-    $table = array('#theme' => 'table', '#header' => $headers, '#rows' => $rows, '#empty' => t('No books available.'));
-    return drupal_render($table);
+    return array(
+      '#theme' => 'table',
+      '#header' => $headers,
+      '#rows' => $rows,
+      '#empty' => t('No books available.'),
+    );
   }
 
   /**
@@ -82,8 +106,45 @@ public function bookRender() {
     foreach ($this->bookManager->getAllBooks() as $book) {
       $book_list[] = l($book['title'], $book['href'], $book['options']);
     }
-    $item_list = array('#theme' => 'item_list' , '#items' => $book_list);
-    return drupal_render($item_list);
+    return array(
+      '#theme' => 'item_list',
+      '#items' => $book_list,
+    );
+  }
+
+  /**
+   * Generates representations of a book page and its children.
+   *
+   * The method delegates the generation of output to helper methods. The method
+   * name is derived by prepending 'bookExport' to the camelized form of given
+   * output type. For example, a type of 'html' results in a call to the method
+   * bookExportHtml().
+   *
+   * @param string $type
+   *   A string encoding the type of output requested. The following types are
+   *   currently supported in book module:
+   *   - html: Printer-friendly HTML.
+   *   Other types may be supported in contributed modules.
+   * @param \Drupal\node\NodeInterface $node
+   *   The node to export.
+   *
+   * @return array
+   *   A render array representing the node and its children in the book
+   *   hierarchy in a format determined by the $type parameter.
+   *
+   * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
+   */
+  public function bookExport($type, NodeInterface $node) {
+    $method = 'bookExport' . Container::camelize($type);
+
+    // @todo Convert the custom export functionality to serializer.
+    if (!method_exists($this->bookExport, $method)) {
+      drupal_set_message(t('Unknown export format.'));
+      throw new NotFoundHttpException();
+    }
+
+    $exported_book = $this->bookExport->{$method}($node);
+    return new Response(drupal_render($exported_book));
   }
 
 }
diff --git a/core/modules/node/lib/Drupal/node/Entity/Node.php b/core/modules/node/lib/Drupal/node/Entity/Node.php
index e44fa17..99063bb 100644
--- a/core/modules/node/lib/Drupal/node/Entity/Node.php
+++ b/core/modules/node/lib/Drupal/node/Entity/Node.php
@@ -11,6 +11,8 @@
 use Drupal\Core\Entity\EntityStorageControllerInterface;
 use Drupal\Core\Entity\Annotation\EntityType;
 use Drupal\Core\Annotation\Translation;
+use Drupal\Core\Language\Language;
+use Drupal\Core\Session\AccountInterface;
 use Drupal\node\NodeInterface;
 
 /**
@@ -140,6 +142,40 @@ public function getType() {
   /**
    * {@inheritdoc}
    */
+  public function access($operation = 'view', AccountInterface $account = NULL) {
+    if ($operation == 'create') {
+      return parent::access($operation, $account);
+    }
+
+    return \Drupal::entityManager()
+      ->getAccessController($this->entityType)
+      ->access($this, $operation, $this->prepareLangcode(), $account);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function prepareLangcode() {
+    $langcode = $this->language()->id;
+    // If the Language module is enabled, try to use the language from content
+    // negotiation.
+    if (\Drupal::moduleHandler()->moduleExists('language')) {
+      // Load languages the node exists in.
+      $node_translations = $this->getTranslationLanguages();
+      // Load the language from content negotiation.
+      $content_negotiation_langcode = \Drupal::languageManager()->getLanguage(Language::TYPE_CONTENT)->id;
+      // If there is a translation available, use it.
+      if (isset($node_translations[$content_negotiation_langcode])) {
+        $langcode = $content_negotiation_langcode;
+      }
+    }
+    return $langcode;
+  }
+
+
+  /**
+   * {@inheritdoc}
+   */
   public function getTitle() {
     return $this->get('title')->value;
   }
diff --git a/core/modules/node/lib/Drupal/node/NodeInterface.php b/core/modules/node/lib/Drupal/node/NodeInterface.php
index 12d334b..34f7274 100644
--- a/core/modules/node/lib/Drupal/node/NodeInterface.php
+++ b/core/modules/node/lib/Drupal/node/NodeInterface.php
@@ -187,4 +187,12 @@ public function getRevisionAuthor();
    */
   public function setRevisionAuthorId($uid);
 
+  /**
+   * Prepares the langcode for a node.
+   *
+   * @return string
+   *   The langcode for this node.
+   */
+  public function prepareLangcode();
+
 }
diff --git a/core/modules/node/node.module b/core/modules/node/node.module
index 105d2c9..6d3708d 100644
--- a/core/modules/node/node.module
+++ b/core/modules/node/node.module
@@ -2006,19 +2006,7 @@ function node_access($op, $node, $account = NULL, $langcode = NULL) {
 
   // If no language code was provided, default to the node's langcode.
   if (empty($langcode)) {
-    $langcode = $node->language()->id;
-    // If the Language module is enabled, try to use the language from content
-    // negotiation.
-    if (module_exists('language')) {
-      // Load languages the node exists in.
-      $node_translations = $node->getTranslationLanguages();
-      // Load the language from content negotiation.
-      $content_negotiation_langcode = language(Language::TYPE_CONTENT)->id;
-      // If there is a translation available, use it.
-      if (isset($node_translations[$content_negotiation_langcode])) {
-        $langcode = $content_negotiation_langcode;
-      }
-    }
+    $langcode = $node->prepareLangcode();
   }
   return $access_controller->access($node, $op, $langcode, $account);
 }
