diff --git a/core/core.services.yml b/core/core.services.yml
index 12498b7..ea6ea3e 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -845,3 +845,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 a9ea984..9e457ce 100644
--- a/core/includes/bootstrap.inc
+++ b/core/includes/bootstrap.inc
@@ -1159,7 +1159,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
@@ -1178,23 +1178,17 @@ 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();
-    }
+  /** @var \Drupal\Core\Messenger\MessengerInterface $messenger */
+  $messenger = \Drupal::service('messenger');
 
-    if ($repeat || !in_array($message, $_SESSION['messages'][$type])) {
-      $_SESSION['messages'][$type][] = $message;
-    }
-
-    // Mark this page as being uncacheable.
-    drupal_page_is_cacheable(FALSE);
-  }
+  $messenger->addMessage($message, $type, $repeat);
 
-  // Messages not set when DB connection fails.
-  return isset($_SESSION['messages']) ? $_SESSION['messages'] : NULL;
+  return $messenger->getMessages();
 }
 
 /**
@@ -1221,12 +1215,24 @@ 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()) {
     if ($type) {
       if ($clear_queue) {
-        unset($_SESSION['messages'][$type]);
+        $messenger->deleteMessagesByType($type);
       }
       if (isset($messages[$type])) {
         return array($type => $messages[$type]);
@@ -1234,7 +1240,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/Messenger/Messenger.php b/core/lib/Drupal/Core/Messenger/Messenger.php
new file mode 100644
index 0000000..073ebc4
--- /dev/null
+++ b/core/lib/Drupal/Core/Messenger/Messenger.php
@@ -0,0 +1,61 @@
+<?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 ($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/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());
+  }
+
+}
