diff --git a/core/core.services.yml b/core/core.services.yml
index 4c1025d..ded185d 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -883,3 +883,5 @@ services:
     arguments: ['@module_handler']
     tags:
       - { name: mime_type_guesser }
+  messenger:
+    class: Drupal\Core\Messenger\Messenger
diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc
index 145a671..b1366b8 100644
--- a/core/includes/bootstrap.inc
+++ b/core/includes/bootstrap.inc
@@ -875,7 +875,7 @@ function watchdog($type, $message, array $variables = array(), $severity = WATCH
  *
  * @param string $message
  *   (optional) The translated message to be displayed to the user. For
- *   consistency with other messages, it should begin with a capital letter and
+ *   consistency with other messages it should begin with a capital letter and
  *   end with a period.
  * @param string $type
  *   (optional) The message's type. Defaults to 'status'. These values are
@@ -913,26 +913,16 @@ function watchdog($type, $message, array $variables = array(), $severity = WATCH
  *
  * @see drupal_get_messages()
  * @see theme_status_messages()
+ *
+ *
+ * @deprecated Deprecated as of Drupal 8.0.
+ *   Use \Drupal::service('messenger')->addMessage() instead.
  */
 function drupal_set_message($message = NULL, $type = 'status', $repeat = FALSE) {
-  if ($message) {
-    if (!isset($_SESSION['messages'][$type])) {
-      $_SESSION['messages'][$type] = array();
-    }
-
-    if ($repeat || !in_array($message, $_SESSION['messages'][$type])) {
-      $_SESSION['messages'][$type][] = array(
-        'safe' => SafeMarkup::isSafe($message),
-        'message' => $message,
-      );
-    }
-
-    // Mark this page as being uncacheable.
-    drupal_page_is_cacheable(FALSE);
-  }
-
-  // Messages not set when DB connection fails.
-  return isset($_SESSION['messages']) ? $_SESSION['messages'] : NULL;
+  /** @var \Drupal\Core\Messenger\MessengerInterface $messenger */
+  $messenger = \Drupal::service('messenger');
+  $messenger->addMessage($message, $type, $repeat);
+  return $messenger->getMessages();
 }
 
 /**
@@ -959,9 +949,22 @@ function drupal_set_message($message = NULL, $type = 'status', $repeat = FALSE)
  *
  * @see drupal_set_message()
  * @see theme_status_messages()
+ *
+ *
+ * @deprecated Deprecated as of Drupal 8.0.
+ *   Use \Drupal::service('messenger')->getMessages() or
+ *   \Drupal::service('messenger')->getMessagesByType() instead.
  */
 function drupal_get_messages($type = NULL, $clear_queue = TRUE) {
-  if ($messages = drupal_set_message()) {
+  // Workaround for Drush.
+  if (!\Drupal::hasService('messenger')) {
+    return array();
+  }
+  /** @var \Drupal\Core\Messenger\MessengerInterface $messenger */
+  $messenger = \Drupal::service('messenger');
+
+  if ($messages = $messenger->getMessages()) {
+
     foreach ($messages as $message_type => $message_typed_messages) {
       foreach ($message_typed_messages as $key => $message) {
         if ($message['safe']) {
@@ -972,7 +975,7 @@ function drupal_get_messages($type = NULL, $clear_queue = TRUE) {
     }
     if ($type) {
       if ($clear_queue) {
-        unset($_SESSION['messages'][$type]);
+        $messenger->deleteMessagesByType($type);
       }
       if (isset($messages[$type])) {
         return array($type => $messages[$type]);
@@ -980,7 +983,7 @@ function drupal_get_messages($type = NULL, $clear_queue = TRUE) {
     }
     else {
       if ($clear_queue) {
-        unset($_SESSION['messages']);
+        $messenger->deleteMessages();
       }
       return $messages;
     }
diff --git a/core/lib/Drupal/Core/EventSubscriber/SpecialAttributesRouteSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/SpecialAttributesRouteSubscriber.php
index 8556561..bf62c68 100644
--- a/core/lib/Drupal/Core/EventSubscriber/SpecialAttributesRouteSubscriber.php
+++ b/core/lib/Drupal/Core/EventSubscriber/SpecialAttributesRouteSubscriber.php
@@ -8,6 +8,7 @@
 namespace Drupal\Core\EventSubscriber;
 
 use Drupal\Component\Utility\String;
+use Drupal\Core\Messenger\MessengerTrait;
 use Drupal\Core\Routing\RouteBuildEvent;
 use Drupal\Core\Routing\RouteSubscriberBase;
 use Symfony\Cmf\Component\Routing\RouteObjectInterface;
@@ -17,6 +18,7 @@
  * Provides a route subscriber which checks for invalid pattern variables.
  */
 class SpecialAttributesRouteSubscriber extends RouteSubscriberBase {
+  use MessengerTrait;
 
   /**
    * {@inheritdoc}
@@ -37,7 +39,7 @@ protected function alterRoutes(RouteCollection $collection) {
     foreach ($collection->all() as $route) {
       if ($not_allowed_variables = array_intersect($route->compile()->getVariables(), $special_variables)) {
         $placeholders = array('@variables' => implode(', ', $not_allowed_variables));
-        drupal_set_message(String::format('The following variables are reserved names by drupal: @variables', $placeholders));
+        $this->addMessage(String::format('The following variables are reserved names by drupal: @variables', $placeholders));
         watchdog('error', 'The following variables are reserved names by drupal: @variables', $placeholders);
         return FALSE;
       }
diff --git a/core/lib/Drupal/Core/Messenger/Messenger.php b/core/lib/Drupal/Core/Messenger/Messenger.php
new file mode 100644
index 0000000..f7dac0b
--- /dev/null
+++ b/core/lib/Drupal/Core/Messenger/Messenger.php
@@ -0,0 +1,64 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Messenger\Messenger.
+ */
+
+namespace Drupal\Core\Messenger;
+
+/**
+ * Provides a session-based messenger.
+ *
+ * @todo Use Symfony's session components once http://drupal.org/node/1858196
+ *   has been fixed.
+ */
+class Messenger implements MessengerInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function addMessage($message, $type = self::STATUS, $repeat = FALSE) {
+    if (!isset($_SESSION)) {
+      $_SESSION = array();
+    }
+    if ($repeat || !array_key_exists('messages', $_SESSION) || !array_key_exists($type, $_SESSION['messages']) || !in_array($message, $_SESSION['messages'][$type])) {
+      $_SESSION['messages'][$type][] = $message;
+    }
+
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getMessages() {
+    return isset($_SESSION['messages']) ? $_SESSION['messages'] : array();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getMessagesByType($type) {
+    return isset($_SESSION['messages']) && isset($_SESSION['messages'][$type]) ? $_SESSION['messages'][$type] : array();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function deleteMessages() {
+    unset($_SESSION['messages']);
+
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function deleteMessagesByType($type) {
+    unset($_SESSION['messages'][$type]);
+
+    return $this;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Messenger/MessengerInterface.php b/core/lib/Drupal/Core/Messenger/MessengerInterface.php
new file mode 100644
index 0000000..044b16c
--- /dev/null
+++ b/core/lib/Drupal/Core/Messenger/MessengerInterface.php
@@ -0,0 +1,81 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Messenger\MessengerInterface.
+ */
+
+namespace Drupal\Core\Messenger;
+
+interface MessengerInterface {
+
+  /**
+   * A status message.
+   */
+  const STATUS = 'status';
+
+  /**
+   * A warning.
+   */
+  const WARNING = 'warning';
+
+  /**
+   * An error.
+   */
+  const ERROR = 'error';
+
+  /**
+   * Adds a new message to the queue.
+   *
+   * @param string $message
+   *   (optional) The translated message to be displayed to the user. For
+   *   consistency with other messages, it should begin with a capital letter
+   *   and end with a period.
+   * @param string $type
+   *   (optional) The message's type. Either self::STATUS, self::WARNING, or
+   *   self::ERROR.
+   * @param bool $repeat
+   *   (optional) If this is FALSE and the message is already set, then the
+   *   message won't be repeated. Defaults to FALSE.
+   *
+   * @return $this
+   */
+  public function addMessage($message, $type = self::STATUS, $repeat = FALSE);
+
+  /**
+   * Gets all messages.
+   *
+   * @return array[]
+   *   Keys are message types and values are indexed arrays of messages. Message
+   *   types are either self::STATUS, self::WARNING, or self::ERROR.
+   */
+  public function getMessages();
+
+  /**
+   * Gets all messages of a certain type.
+   *
+   * @param string $type
+   *   The messages' type. Either self::STATUS, self::WARNING, or self::ERROR.
+   *
+   * @return string[]
+   */
+  public function getMessagesByType($type);
+
+  /**
+   * Deletes all messages.
+   *
+   * @return $this
+   */
+  public function deleteMessages();
+
+  /**
+   * Deletes all messages of a certain type.
+   *
+   * @param string $type
+   *   The messages' type. Either self::STATUS, self::WARNING, or self::ERROR.
+   *
+   * @return $this
+   */
+  public function deleteMessagesByType($type);
+
+}
diff --git a/core/lib/Drupal/Core/Messenger/MessengerTrait.php b/core/lib/Drupal/Core/Messenger/MessengerTrait.php
new file mode 100644
index 0000000..628e18e
--- /dev/null
+++ b/core/lib/Drupal/Core/Messenger/MessengerTrait.php
@@ -0,0 +1,68 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Messenger\MessengerTrait.
+ */
+
+namespace Drupal\Core\Messenger;
+
+/**
+ * Wrapper methods for \Drupal\Core\Messenger\Messenger.
+ *
+ * This utility trait should only be used in application-level code, such as
+ * classes that would implement ContainerInjectionInterface. Services registered
+ * in the Container should not use this trait but inject the appropriate service
+ * directly for easier testing.
+ */
+trait MessengerTrait {
+
+  /**
+   * The messenger service.
+   *
+   * @var \Drupal\Core\Messenger\MessengerInterface
+   */
+  protected $messenger;
+
+  /**
+   * Adds a new message to the queue.
+   *
+   * See \Drupal\Core\Messenger\MessengerInterface::addMessage() for details.
+   *
+   * @return $this
+   */
+  protected function addMessage($message, $type = MessengerInterface::STATUS, $repeat = FALSE) {
+    $this->getMessenger()->addMessage($message, $type, $repeat);
+    return $this;
+  }
+
+  /**
+   * Gets the messenger service.
+   *
+   * @return \Drupal\Core\Messenger\MessengerInterface
+   *   The messenger service.
+   */
+  protected function getMessenger() {
+    if (!$this->messenger) {
+      $this->messenger = \Drupal::service('messenger');
+    }
+
+    return $this->messenger;
+  }
+
+  /**
+   * Sets the messenger service to use.
+   *
+   * @param \Drupal\Core\Messenger\MessengerInterface $messenger
+   *   The messenger service.
+   *
+   * @return $this
+   */
+  public function setMessenger(MessengerInterface $messenger) {
+    $this->messenger = $messenger;
+
+    return $this;
+  }
+
+
+}
diff --git a/core/tests/Drupal/Tests/Core/EventSubscriber/SpecialAttributesRouteSubscriberTest.php b/core/tests/Drupal/Tests/Core/EventSubscriber/SpecialAttributesRouteSubscriberTest.php
index cdab44f..9ca8902 100644
--- a/core/tests/Drupal/Tests/Core/EventSubscriber/SpecialAttributesRouteSubscriberTest.php
+++ b/core/tests/Drupal/Tests/Core/EventSubscriber/SpecialAttributesRouteSubscriberTest.php
@@ -34,6 +34,8 @@ protected function setUp() {
     parent::setUp();
 
     $this->specialAttributesRouteSubscriber = new SpecialAttributesRouteSubscriber();
+    $messenger = $this->getMock('\Drupal\Core\Messenger\MessengerInterface');
+    $this->specialAttributesRouteSubscriber->setMessenger($messenger);
   }
 
   /**
@@ -112,8 +114,4 @@ public function testOnRouteBuildingInvalidVariables(Route $route) {
     function watchdog($type, $message, array $args = array()) {
     }
   }
-  if (!function_exists('drupal_set_message')) {
-    function drupal_set_message($type = NULL, $message = '') {
-    }
-  }
 }
diff --git a/core/tests/Drupal/Tests/Core/Messenger/MessengerTest.php b/core/tests/Drupal/Tests/Core/Messenger/MessengerTest.php
new file mode 100644
index 0000000..1a042bc
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Messenger/MessengerTest.php
@@ -0,0 +1,99 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\Core\Messenger\MessengerTest.
+ */
+
+namespace Drupal\Tests\Core\Messenger;
+
+use Drupal\Core\Messenger\Messenger;
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * @coversDefaultClass \Drupal\Core\Messenger\Messenger
+ */
+class MessengerTest extends UnitTestCase {
+
+  /**
+   * The messenger under test.
+   *
+   * @var \Drupal\Core\Messenger\Messenger
+   */
+  protected $messenger;
+
+  /**
+   * A copy of any existing session data to restore after the test.
+   *
+   * @var array
+   */
+  protected $existingSession;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getInfo() {
+    return array(
+      'description' => '',
+      'name' => '\Drupal\Core\Messenger\Messenger unit test',
+      'group' => 'Messenger',
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    $this->messenger = new Messenger();
+
+    $this->existingSession = isset($_SESSION) ? $_SESSION : NULL;
+    $_SESSION = array();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function tearDown() {
+    if ($this->existingSession !== NULL) {
+      $_SESSION = $this->existingSession;
+    }
+    else {
+      unset($_SESSION);
+    }
+  }
+
+  /**
+   * Tests the messenger.
+   */
+  public function testMessenger() {
+    $message_a = $this->randomName();
+    $type_a = $this->randomName();
+    $message_b = $this->randomName();
+    $type_b = $this->randomName();
+
+    // Test that if there are no messages, the default is an empty array.
+    $this->assertEquals($this->messenger->getMessages(), array());
+
+    // Test that adding a message returns the messenger and that the message can
+    // be retrieved.
+    $this->assertEquals($this->messenger->addMessage($message_a, $type_a), $this->messenger);
+    $this->messenger->addMessage($message_a, $type_a);
+    $this->messenger->addMessage($message_a, $type_a, TRUE);
+    $this->messenger->addMessage($message_b, $type_b, TRUE);
+    $this->assertEquals($this->messenger->getMessages(), array(
+      $type_a => array($message_a, $message_a),
+      $type_b => array($message_b),
+    ));
+
+    // Test deleting messages of a certain type.
+    $this->assertEquals($this->messenger->deleteMessagesByType($type_a), $this->messenger);
+    $this->assertEquals($this->messenger->getMessages(), array(
+      $type_b => array($message_b),
+    ));
+
+    // Test deleting all messages.
+    $this->assertEquals($this->messenger->deleteMessages(), $this->messenger);
+    $this->assertEquals($this->messenger->getMessages(), array());
+  }
+
+}
diff --git a/core/tests/Drupal/Tests/Core/Messenger/MessengerTraitTest.php b/core/tests/Drupal/Tests/Core/Messenger/MessengerTraitTest.php
new file mode 100644
index 0000000..8a7fa9c
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Messenger/MessengerTraitTest.php
@@ -0,0 +1,85 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\Core\Messenger\MessengerTraitTest.
+ */
+
+namespace Drupal\Tests\Core\Messenger;
+
+use Drupal\Core\Messenger\MessengerTrait;
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * @coversDefaultClass \Drupal\Core\Messenger\MessengerTrait
+ */
+class MessengerTraitTest extends UnitTestCase {
+
+  /**
+   * A reflection of self::$messenger.
+   *
+   * @var \ReflectionClass
+   */
+  protected $reflection;
+
+  /**
+   * The mock under test that uses MessengerTraitTest.
+   *
+   * @var object
+   * @see PHPUnit_Framework_MockObject_Generator::getObjectForTrait()
+   */
+  protected $messengerTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getInfo() {
+    return array(
+      'name' => 'Messenger trait',
+      'description' => 'Tests the messenger trait.',
+      'group' => 'Messenger',
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    $this->messengerTrait = $this->getObjectForTrait('\Drupal\Core\Messenger\MessengerTrait');
+
+    $this->reflection = new \ReflectionClass(get_class($this->messengerTrait));
+  }
+
+  /**
+   * @covers ::addMessage
+   */
+  public function testAddMessage() {
+    $stub = $this->getMock('\Drupal\Core\Messenger\MessengerInterface');
+    $this->messengerTrait->setMessenger($stub);
+
+    $message_string = 'Beep boop';
+
+    $stub->expects($this->once())
+         ->method('addMessage')
+         ->with($this->equalTo($message_string));
+
+    $method = $this->reflection->getMethod('addMessage');
+    $method->setAccessible(TRUE);
+
+    $this->assertEquals($this->messengerTrait, $method->invoke($this->messengerTrait, $message_string));
+  }
+
+  /**
+   * @covers ::getMessenger
+   */
+  public function testGetMessenger() {
+    $stub = $this->getMock('\Drupal\Core\Messenger\MessengerInterface');
+    $this->messengerTrait->setMessenger($stub);
+
+    $method = $this->reflection->getMethod('getMessenger');
+    $method->setAccessible(TRUE);
+
+    $this->assertSame($stub, $method->invoke($this->messengerTrait));
+  }
+
+}
