diff --git a/core/modules/contact/contact.module b/core/modules/contact/contact.module
index 4957052..1fbcea1 100644
--- a/core/modules/contact/contact.module
+++ b/core/modules/contact/contact.module
@@ -97,31 +97,23 @@ function contact_menu() {
 
   $items['contact'] = array(
     'title' => 'Contact',
-    'page callback' => 'contact_site_page',
-    'access arguments' => array('access site-wide contact form'),
+    'route_name' => 'contact_site_page',
     'menu_name' => 'footer',
     'type' => MENU_SUGGESTED_ITEM,
-    'file' => 'contact.pages.inc',
   );
   $items['contact/%contact_category'] = array(
     'title' => 'Contact category form',
     'title callback' => 'entity_page_label',
     'title arguments' => array(1),
-    'page callback' => 'contact_site_page',
-    'page arguments' => array(1),
+    'route_name' => 'contact_site_page',
     'access arguments' => array('access site-wide contact form'),
     'type' => MENU_VISIBLE_IN_BREADCRUMB,
-    'file' => 'contact.pages.inc',
   );
   $items['user/%user/contact'] = array(
     'title' => 'Contact',
-    'page callback' => 'contact_personal_page',
-    'page arguments' => array(1),
+    'route_name' => 'contact_personal_page',
     'type' => MENU_LOCAL_TASK,
-    'access callback' => '_contact_personal_tab_access',
-    'access arguments' => array(1),
     'weight' => 2,
-    'file' => 'contact.pages.inc',
   );
   return $items;
 }
diff --git a/core/modules/contact/contact.pages.inc b/core/modules/contact/contact.pages.inc
deleted file mode 100644
index 5ee9fb7..0000000
--- a/core/modules/contact/contact.pages.inc
+++ /dev/null
@@ -1,102 +0,0 @@
-<?php
-
-/**
- * @file
- * Page callbacks for the Contact module.
- */
-
-use Drupal\contact\Plugin\Core\Entity\Category;
-use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
-use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
-
-/**
- * Page callback: Presents the site-wide contact form.
- *
- * @param Drupal\contact\Plugin\Core\Entity\Category $category
- *   (optional) The contact category to use.
- *
- * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
- * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
- *
- * @see contact_menu()
- * @see contact_site_form_submit()
- * @ingroup forms
- */
-function contact_site_page(Category $category = NULL) {
-  // Check if flood control has been activated for sending e-mails.
-  if (!user_access('administer contact forms')) {
-    contact_flood_control();
-  }
-
-  if (!isset($category)) {
-    $categories = entity_load_multiple('contact_category');
-    $default_category = config('contact.settings')->get('default_category');
-    if (isset($categories[$default_category])) {
-      $category = $categories[$default_category];
-    }
-    // If there are no categories, do not display the form.
-    else {
-      if (user_access('administer contact forms')) {
-        drupal_set_message(t('The contact form has not been configured. <a href="@add">Add one or more categories</a> to the form.', array('@add' => url('admin/structure/contact/add'))), 'error');
-        return array();
-      }
-      else {
-        throw new NotFoundHttpException();
-      }
-    }
-  }
-  $message = entity_create('contact_message', array(
-    'category' => $category->id(),
-  ));
-  return entity_get_form($message);
-}
-
-/**
- * Page callback: Form constructor for the personal contact form.
- *
- * @param $recipient
- *   The account for which a personal contact form should be generated.
- *
- * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
- *
- * @see contact_menu()
- * @see contact_personal_form_submit()
- *
- * @ingroup forms
- */
-function contact_personal_page($recipient) {
-  global $user;
-
-  // Check if flood control has been activated for sending e-mails.
-  if (!user_access('administer contact forms') && !user_access('administer users')) {
-    contact_flood_control();
-  }
-
-  drupal_set_title(t('Contact @username', array('@username' => user_format_name($recipient))), PASS_THROUGH);
-
-  $message = entity_create('contact_message', array(
-    'recipient' => $recipient,
-  ));
-  return entity_get_form($message);
-}
-
-/**
- * Throws an exception if the current user is not allowed to submit a contact form.
- *
- * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
- *
- * @see contact_site_page()
- * @see contact_personal_page()
- */
-function contact_flood_control() {
-  $config = config('contact.settings');
-  $limit = $config->get('flood.limit');
-  $interval = $config->get('flood.interval');
-  if (!Drupal::service('flood')->isAllowed('contact', $limit, $interval)) {
-    drupal_set_message(t("You cannot send more than %limit messages in @interval. Try again later.", array(
-      '%limit' => $limit,
-      '@interval' => format_interval($interval),
-    )), 'error');
-    throw new AccessDeniedHttpException();
-  }
-}
diff --git a/core/modules/contact/contact.routing.yml b/core/modules/contact/contact.routing.yml
new file mode 100644
index 0000000..274c1d2
--- /dev/null
+++ b/core/modules/contact/contact.routing.yml
@@ -0,0 +1,14 @@
+contact_site_page:
+  pattern: 'contact/{contact_category}'
+  defaults:
+    _content: '\Drupal\contact\Controller\ContactPageController::contactSitePage'
+    contact_category: 0
+  requirements:
+    _permission: 'access site-wide contact form'
+
+contact_personal_page:
+  pattern: 'user/{user}/contact'
+  defaults:
+    _content: '\Drupal\contact\Controller\ContactPageController::contactPersonalPage'
+  requirements:
+    _contact_personal_tab_access: 'TRUE'
diff --git a/core/modules/contact/lib/Drupal/contact/Access/ContactPageAccess.php b/core/modules/contact/lib/Drupal/contact/Access/ContactPageAccess.php
new file mode 100644
index 0000000..8f6afbc
--- /dev/null
+++ b/core/modules/contact/lib/Drupal/contact/Access/ContactPageAccess.php
@@ -0,0 +1,89 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\contact\Access\ContactPageAccess.
+ */
+
+namespace Drupal\contact\Access;
+
+use Drupal\Core\Access\AccessCheckInterface;
+use Symfony\Component\Routing\Route;
+use Symfony\Component\HttpFoundation\Request;
+use Drupal\Core\Config\Config;
+
+/**
+ * Access check for test routes.
+ */
+class ContactPageAccess implements AccessCheckInterface {
+
+  /**
+   * Config Management Object.
+   *
+   * @var \Drupal\Core\Config\Config
+   */
+  protected $config;
+
+  /**
+   * User Data Service.
+   *
+   * @var \Drupal\user\UserData;
+   */
+  protected $userData;
+
+  /**
+   * Constructor for ContactPageAccess.
+   */
+  public function __construct(Config $config, UserData $userData) {
+    $this->config = $config;
+    $this->userData = $userData;
+  }
+  /**
+   * Implements AccessCheckInterface::applies().
+   */
+  public function applies(Route $route) {
+    return array_key_exists('_contact_personal_tab_access', $route->getRequirements());
+  }
+
+  /**
+   * Implements AccessCheckInterface::access().
+   */
+  public function access(Route $route, Request $request) {
+    global $user;
+    $account = $request->attributes->get('user');
+
+    // Anonymous users cannot have contact forms.
+    if (!$account->uid) {
+      return FALSE;
+    }
+
+    // Users may not contact themselves.
+    if ($user->uid == $account->uid) {
+      return FALSE;
+    }
+
+    // User administrators should always have access to personal contact forms.
+    if (user_access('administer users')) {
+      return TRUE;
+    }
+
+    // If requested user has been blocked, do not allow users to contact them.
+    if (empty($account->status)) {
+      return FALSE;
+    }
+
+    // If the requested user has disabled their contact form, do not allow users
+    // to contact them.
+    $account_data = $this->userData->get('contact', $account->id(), 'enabled');
+    if (isset($account_data) && empty($account_data)) {
+      return FALSE;
+    }
+    // If the requested user did not save a preference yet, deny access if the
+    // configured default is disabled.
+    elseif (!$this->config('contact.settings')->get('user_default_enabled')) {
+      return FALSE;
+    }
+
+    return user_access('access user contact forms');
+  }
+}
diff --git a/core/modules/contact/lib/Drupal/contact/ContactBundle.php b/core/modules/contact/lib/Drupal/contact/ContactBundle.php
new file mode 100644
index 0000000..5f5743d
--- /dev/null
+++ b/core/modules/contact/lib/Drupal/contact/ContactBundle.php
@@ -0,0 +1,28 @@
+<?php
+
+/**
+ * @file
+ * Definition of \Drupal\contact\ContactBundle.
+ */
+
+namespace Drupal\contact;
+
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Reference;
+use Symfony\Component\HttpKernel\Bundle\Bundle;
+
+/**
+ * Registers a dynamic route provider.
+ */
+class ContactBundle extends Bundle {
+
+  /**
+   * Overrides Symfony\Component\HttpKernel\Bundle\Bundle::build().
+   */
+  public function build(ContainerBuilder $container) {
+    $container->register('access_check.contact', 'Drupal\contact\Access\ContactPageAccess')
+      ->addArgument(new Reference('database'))
+      ->addArgument(new Reference('user.data'))
+      ->addTag('access_check');
+  }
+}
diff --git a/core/modules/contact/lib/Drupal/contact/Controller/ContactPageController.php b/core/modules/contact/lib/Drupal/contact/Controller/ContactPageController.php
new file mode 100644
index 0000000..f7aa3c9
--- /dev/null
+++ b/core/modules/contact/lib/Drupal/contact/Controller/ContactPageController.php
@@ -0,0 +1,145 @@
+<?php
+namespace Drupal\contact\Controller;
+
+use Drupal\Core\ControllerInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Drupal\Core\Flood\FloodInterface;
+use \Drupal\Core\Config\Config;
+
+use Drupal\contact\Plugin\Core\Entity\Category;
+use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
+use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
+
+/**
+ * Controller routines for contact routes.
+ */
+class ContactPageController implements ControllerInterface {
+
+  /**
+   * Flood Control Object.
+   *
+   * @var \Drupal\Core\Flood\FloodInterface
+   */
+  protected $flood;
+
+  /**
+   * Config Manager Object.
+   *
+   * @var \Drupal\Core\Config\Config
+   */
+  protected $config;
+
+  /**
+   * Injects database service.
+   *
+   * @param \Symfony\Component\DependencyInjection\ContainerInterface
+   *   Symfony Container Interface.
+   */
+  public static function create(ContainerInterface $container) {
+    return new static($container->get('flood'), $container->get('config'));
+  }
+
+  /**
+   * Constructs a ContactController object.
+   *
+   * @param \Drupal\Core\Flood\FloodInterface $database
+   *   Database connection.
+   */
+  public function __construct(FloodInterface $flood, Config $config) {
+    $this->flood = $flood;
+    $this->config = $config;
+  }
+  /**
+   * Presents the site-wide contact form.
+   *
+   * @param Drupal\contact\Plugin\Core\Entity\Category $category
+   *   (optional) The contact category to use.
+   *
+   * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
+   * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
+   *
+   * @see contact_menu()
+   * @see contact_site_form_submit()
+   * @ingroup forms
+   */
+  public function contactSitePage(Category $contact_category = NULL) {
+    $category = $contact_category;
+    // Check if flood control has been activated for sending e-mails.
+    if (!user_access('administer contact forms')) {
+      $this->contactFloodControl();
+    }
+    if (!isset($category)) {
+      $categories = entity_load_multiple('contact_category');
+      $default_category = config('contact.settings')->get('default_category');
+      if (isset($categories[$default_category])) {
+        $category = $categories[$default_category];
+      }
+      // If there are no categories, do not display the form.
+      else {
+        if (user_access('administer contact forms')) {
+          drupal_set_message(t('The contact form has not been configured. <a href="@add">Add one or more categories</a> to the form.', array('@add' => url('admin/structure/contact/add'))), 'error');
+          return array();
+        }
+        else {
+          throw new NotFoundHttpException();
+        }
+      }
+    }
+    $message = entity_create('contact_message', array(
+      'category' => $category->id(),
+    ));
+    return entity_get_form($message);
+  }
+
+  /**
+   * Form constructor for the personal contact form.
+   *
+   * @param $recipient
+   *   The account for which a personal contact form should be generated.
+   *
+   * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
+   *
+   * @see contact_menu()
+   * @see contact_personal_form_submit()
+   *
+   * @ingroup forms
+   */
+  public function contactPersonalPage($user) {
+    $recipient = $user;
+    global $user;
+
+    // Check if flood control has been activated for sending e-mails.
+    if (!user_access('administer contact forms') && !user_access('administer users')) {
+      $this->contactFloodControl();
+    }
+
+    drupal_set_title(t('Contact @username', array('@username' => user_format_name($recipient))), PASS_THROUGH);
+
+    $message = entity_create('contact_message', array(
+      'recipient' => $recipient,
+    ));
+    return entity_get_form($message);
+  }
+
+/**
+ * Throws an exception if the current user is not allowed to submit a contact form.
+ *
+ * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
+ *
+ * @see \Drupal\contact\Controlelr\ContactSitePage()
+ * @see \Drupal\contact\Controlelr\ContactPersonalPage()
+ */
+protected function contactFloodControl() {
+  $config = $this->config('contact.settings');
+  $limit = $config->get('flood.limit');
+  $interval = $config->get('flood.interval');
+  if (!$this->flood->isAllowed('contact', $limit, $interval)) {
+    drupal_set_message(t("You cannot send more than %limit messages in @interval. Try again later.", array(
+      '%limit' => $limit,
+      '@interval' => format_interval($interval),
+    )), 'error');
+    throw new AccessDeniedHttpException();
+  }
+}
+
+}
