diff --git a/core/includes/ajax.inc b/core/includes/ajax.inc
index 3f2389f..788dd04 100644
--- a/core/includes/ajax.inc
+++ b/core/includes/ajax.inc
@@ -224,85 +224,6 @@
  */
 
 /**
- * Renders a commands array into JSON.
- *
- * @param $commands
- *   A list of macro commands generated by the use of ajax_command_*()
- *   functions.
- */
-function ajax_render($commands = array()) {
-  // Ajax responses aren't rendered with html.html.twig, so we have to call
-  // drupal_get_css() and drupal_get_js() here, in order to have new files added
-  // during this request to be loaded by the page. We only want to send back
-  // files that the page hasn't already loaded, so we implement simple diffing
-  // logic using array_diff_key().
-  foreach (array('css', 'js') as $type) {
-    // It is highly suspicious if $_POST['ajax_page_state'][$type] is empty,
-    // since the base page ought to have at least one JS file and one CSS file
-    // loaded. It probably indicates an error, and rather than making the page
-    // reload all of the files, instead we return no new files.
-    if (empty($_POST['ajax_page_state'][$type])) {
-      $items[$type] = array();
-    }
-    else {
-      $function = 'drupal_add_' . $type;
-      $items[$type] = $function();
-      drupal_alter($type, $items[$type]);
-      // @todo Inline CSS and JS items are indexed numerically. These can't be
-      //   reliably diffed with array_diff_key(), since the number can change
-      //   due to factors unrelated to the inline content, so for now, we strip
-      //   the inline items from Ajax responses, and can add support for them
-      //   when drupal_add_css() and drupal_add_js() are changed to use a hash
-      //   of the inline content as the array key.
-      foreach ($items[$type] as $key => $item) {
-        if (is_numeric($key)) {
-          unset($items[$type][$key]);
-        }
-      }
-      // Ensure that the page doesn't reload what it already has.
-      $items[$type] = array_diff_key($items[$type], $_POST['ajax_page_state'][$type]);
-    }
-  }
-
-  // Render the HTML to load these files, and add AJAX commands to insert this
-  // HTML in the page. We pass TRUE as the $skip_alter argument to prevent the
-  // data from being altered again, as we already altered it above. Settings are
-  // handled separately, afterwards.
-  if (isset($items['js']['settings'])) {
-    unset($items['js']['settings']);
-  }
-  $styles = drupal_get_css($items['css'], TRUE);
-  $scripts_footer = drupal_get_js('footer', $items['js'], TRUE);
-  $scripts_header = drupal_get_js('header', $items['js'], TRUE);
-
-  $extra_commands = array();
-  if (!empty($styles)) {
-    $extra_commands[] = ajax_command_add_css($styles);
-  }
-  if (!empty($scripts_header)) {
-    $extra_commands[] = ajax_command_prepend('head', $scripts_header);
-  }
-  if (!empty($scripts_footer)) {
-    $extra_commands[] = ajax_command_append('body', $scripts_footer);
-  }
-  if (!empty($extra_commands)) {
-    $commands = array_merge($extra_commands, $commands);
-  }
-
-  // Now add a command to merge changes and additions to drupalSettings.
-  $scripts = drupal_add_js();
-  if (!empty($scripts['settings'])) {
-    $settings = drupal_merge_js_settings($scripts['settings']['data']);
-    array_unshift($commands, ajax_command_settings($settings, TRUE));
-  }
-
-  // Allow modules to alter any Ajax response.
-  drupal_alter('ajax_render', $commands);
-
-  return drupal_json_encode($commands);
-}
-
-/**
  * Theme callback: Returns the correct theme for an Ajax request.
  *
  * Many different pages can invoke an Ajax request to system/ajax or another
@@ -339,72 +260,6 @@ function ajax_base_page_theme() {
 }
 
 /**
- * Converts the return value of a page callback into an Ajax commands array.
- *
- * @param $page_callback_result
- *   The result of a page callback. Can be one of:
- *   - NULL: to indicate no content.
- *   - An integer menu status constant: to indicate an error condition.
- *   - A string of HTML content.
- *   - A renderable array of content.
- *
- * @return
- *   An Ajax commands array that can be passed to ajax_render().
- */
-function ajax_prepare_response($page_callback_result) {
-  $commands = array();
-  if (!isset($page_callback_result)) {
-    // Simply delivering an empty commands array is sufficient. This results
-    // in the Ajax request being completed, but nothing being done to the page.
-  }
-  elseif (is_int($page_callback_result)) {
-    switch ($page_callback_result) {
-      case MENU_NOT_FOUND:
-        $commands[] = ajax_command_alert(t('The requested page could not be found.'));
-        break;
-
-      case MENU_ACCESS_DENIED:
-        $commands[] = ajax_command_alert(t('You are not authorized to access this page.'));
-        break;
-
-      case MENU_SITE_OFFLINE:
-        $commands[] = ajax_command_alert(filter_xss_admin(t(\Drupal::config('system.maintenance')->get('message'), array('@site' => \Drupal::config('system.site')->get('name')))));
-        break;
-    }
-  }
-  elseif (is_array($page_callback_result) && isset($page_callback_result['#type']) && ($page_callback_result['#type'] == 'ajax')) {
-    // Complex Ajax callbacks can return a result that contains an error message
-    // or a specific set of commands to send to the browser.
-    $page_callback_result += element_info('ajax');
-    $error = $page_callback_result['#error'];
-    if (isset($error) && $error !== FALSE) {
-      if ((empty($error) || $error === TRUE)) {
-        $error = t('An error occurred while handling the request: The server received invalid input.');
-      }
-      $commands[] = ajax_command_alert($error);
-    }
-    else {
-      $commands = $page_callback_result['#commands'];
-    }
-  }
-  else {
-    // Like normal page callbacks, simple Ajax callbacks can return HTML
-    // content, as a string or render array. This HTML is inserted in some
-    // relationship to #ajax['wrapper'], as determined by which jQuery DOM
-    // manipulation method is used. The method used is specified by
-    // #ajax['method']. The default method is 'replaceWith', which completely
-    // replaces the old wrapper element and its content with the new HTML.
-    $html = is_string($page_callback_result) ? $page_callback_result : drupal_render($page_callback_result);
-    $commands[] = ajax_command_insert(NULL, $html);
-    // Add the status messages inside the new content's wrapper element, so that
-    // on subsequent Ajax requests, it is treated as old content.
-    $commands[] = ajax_command_prepend(NULL, theme('status_messages'));
-  }
-
-  return $commands;
-}
-
-/**
  * Form element processing handler for the #ajax form property.
  *
  * @param $element
@@ -594,266 +449,3 @@ function ajax_pre_render_element($element) {
 /**
  * @} End of "defgroup ajax".
  */
-
-/**
- * @defgroup ajax_commands Ajax framework commands
- * @{
- * Functions to create various Ajax commands.
- *
- * These functions can be used to create arrays for use with the
- * ajax_render() function.
- */
-
-/**
- * Creates a Drupal Ajax 'alert' command.
- *
- * The 'alert' command instructs the client to display a JavaScript alert
- * dialog box.
- *
- * This command is implemented by Drupal.AjaxCommands.prototype.alert()
- * defined in misc/ajax.js.
- *
- * @param $text
- *   The message string to display to the user.
- *
- * @return
- *   An array suitable for use with the ajax_render() function.
- */
-function ajax_command_alert($text) {
-  return array(
-    'command' => 'alert',
-    'text' => $text,
-  );
-}
-
-/**
- * Creates a Drupal Ajax 'insert' command using the method in #ajax['method'].
- *
- * This command instructs the client to insert the given HTML using whichever
- * jQuery DOM manipulation method has been specified in the #ajax['method']
- * variable of the element that triggered the request.
- *
- * This command is implemented by Drupal.AjaxCommands.prototype.insert()
- * defined in misc/ajax.js.
- *
- * @param $selector
- *   A jQuery selector string. If the command is a response to a request from
- *   an #ajax form element then this value can be NULL.
- * @param $html
- *   The data to use with the jQuery method.
- * @param $settings
- *   An optional array of settings that will be used for this command only.
- *
- * @return
- *   An array suitable for use with the ajax_render() function.
- */
-function ajax_command_insert($selector, $html, $settings = NULL) {
-  return array(
-    'command' => 'insert',
-    'method' => NULL,
-    'selector' => $selector,
-    'data' => $html,
-    'settings' => $settings,
-  );
-}
-
-/**
- * Creates a Drupal Ajax 'insert/prepend' command.
- *
- * The 'insert/prepend' command instructs the client to use jQuery's prepend()
- * method to prepend the given HTML content to the inside each element matched
- * by the given selector.
- *
- * This command is implemented by Drupal.AjaxCommands.prototype.insert()
- * defined in misc/ajax.js.
- *
- * @param $selector
- *   A jQuery selector string. If the command is a response to a request from
- *   an #ajax form element then this value can be NULL.
- * @param $html
- *   The data to use with the jQuery prepend() method.
- * @param $settings
- *   An optional array of settings that will be used for this command only.
- *
- * @return
- *   An array suitable for use with the ajax_render() function.
- *
- * @see http://docs.jquery.com/Manipulation/prepend#content
- */
-function ajax_command_prepend($selector, $html, $settings = NULL) {
-  return array(
-    'command' => 'insert',
-    'method' => 'prepend',
-    'selector' => $selector,
-    'data' => $html,
-    'settings' => $settings,
-  );
-}
-
-/**
- * Creates a Drupal Ajax 'insert/append' command.
- *
- * The 'insert/append' command instructs the client to use jQuery's append()
- * method to append the given HTML content to the inside of each element matched
- * by the given selector.
- *
- * This command is implemented by Drupal.AjaxCommands.prototype.insert()
- * defined in misc/ajax.js.
- *
- * @param $selector
- *   A jQuery selector string. If the command is a response to a request from
- *   an #ajax form element then this value can be NULL.
- * @param $html
- *   The data to use with the jQuery append() method.
- * @param $settings
- *   An optional array of settings that will be used for this command only.
- *
- * @return
- *   An array suitable for use with the ajax_render() function.
- *
- * @see http://docs.jquery.com/Manipulation/append#content
- */
-function ajax_command_append($selector, $html, $settings = NULL) {
-  return array(
-    'command' => 'insert',
-    'method' => 'append',
-    'selector' => $selector,
-    'data' => $html,
-    'settings' => $settings,
-  );
-}
-
-/**
- * Creates a Drupal Ajax 'remove' command.
- *
- * The 'remove' command instructs the client to use jQuery's remove() method
- * to remove each of elements matched by the given selector, and everything
- * within them.
- *
- * This command is implemented by Drupal.AjaxCommands.prototype.remove()
- * defined in misc/ajax.js.
- *
- * @param $selector
- *   A jQuery selector string. If the command is a response to a request from
- *   an #ajax form element then this value can be NULL.
- *
- * @return
- *   An array suitable for use with the ajax_render() function.
- *
- * @see http://docs.jquery.com/Manipulation/remove#expr
- */
-function ajax_command_remove($selector) {
-  return array(
-    'command' => 'remove',
-    'selector' => $selector,
-  );
-}
-
-/**
- * Creates a Drupal Ajax 'changed' command.
- *
- * This command instructs the client to mark each of the elements matched by the
- * given selector as 'ajax-changed'.
- *
- * This command is implemented by Drupal.AjaxCommands.prototype.changed()
- * defined in misc/ajax.js.
- *
- * @param $selector
- *   A jQuery selector string. If the command is a response to a request from
- *   an #ajax form element then this value can be NULL.
- * @param $asterisk
- *   An optional CSS selector which must be inside $selector. If specified,
- *   an asterisk will be appended to the HTML inside the $asterisk selector.
- *
- * @return
- *   An array suitable for use with the ajax_render() function.
- */
-function ajax_command_changed($selector, $asterisk = '') {
-  return array(
-    'command' => 'changed',
-    'selector' => $selector,
-    'asterisk' => $asterisk,
-  );
-}
-
-/**
- * Creates a Drupal Ajax 'css' command.
- *
- * The 'css' command will instruct the client to use the jQuery css() method
- * to apply the CSS arguments to elements matched by the given selector.
- *
- * This command is implemented by Drupal.AjaxCommands.prototype.css()
- * defined in misc/ajax.js.
- *
- * @param $selector
- *   A jQuery selector string. If the command is a response to a request from
- *   an #ajax form element then this value can be NULL.
- * @param $argument
- *   An array of key/value pairs to set in the CSS for the selector.
- *
- * @return
- *   An array suitable for use with the ajax_render() function.
- *
- * @see http://docs.jquery.com/CSS/css#properties
- */
-function ajax_command_css($selector, $argument) {
-  return array(
-    'command' => 'css',
-    'selector' => $selector,
-    'argument' => $argument,
-  );
-}
-
-/**
- * Creates a Drupal Ajax 'settings' command.
- *
- * The 'settings' command instructs the client either to use the given array as
- * the settings for ajax-loaded content or to extend drupalSettings with the
- * given array, depending on the value of the $merge parameter.
- *
- * This command is implemented by Drupal.AjaxCommands.prototype.settings()
- * defined in misc/ajax.js.
- *
- * @param $argument
- *   An array of key/value pairs to add to the settings. This will be utilized
- *   for all commands after this if they do not include their own settings
- *   array.
- * @param $merge
- *   Whether or not the passed settings in $argument should be merged into the
- *   global drupalSettings on the page. By default (FALSE), the settings that
- *   are passed to Drupal.attachBehaviors will not include the global
- *   drupalSettings.
- *
- * @return
- *   An array suitable for use with the ajax_render() function.
- */
-function ajax_command_settings($argument, $merge = FALSE) {
-  return array(
-    'command' => 'settings',
-    'settings' => $argument,
-    'merge' => $merge,
-  );
-}
-
-/**
- * Creates a Drupal Ajax 'add_css' command.
- *
- * This method will add css via ajax in a cross-browser compatible way.
- *
- * This command is implemented by Drupal.AjaxCommands.prototype.add_css()
- * defined in misc/ajax.js.
- *
- * @param $styles
- *   A string that contains the styles to be added.
- *
- * @return
- *   An array suitable for use with the ajax_render() function.
- *
- * @see misc/ajax.js
- */
-function ajax_command_add_css($styles) {
-  return array(
-    'command' => 'add_css',
-    'data' => $styles,
-  );
-}
diff --git a/core/modules/system/lib/Drupal/system/Tests/Ajax/AjaxTestBase.php b/core/modules/system/lib/Drupal/system/Tests/Ajax/AjaxTestBase.php
index a196f85..e9f1ca1 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Ajax/AjaxTestBase.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Ajax/AjaxTestBase.php
@@ -24,22 +24,22 @@
   /**
    * Asserts the array of Ajax commands contains the searched command.
    *
-   * The Ajax framework, via the ajax_render() function, returns an array of
-   * commands. This array sometimes includes commands automatically provided by
-   * the framework in addition to commands returned by a particular page
-   * callback. During testing, we're usually interested that a particular
-   * command is present, and don't care whether other commands precede or
-   * follow the one we're interested in. Additionally, the command we're
-   * interested in may include additional data that we're not interested in.
-   * Therefore, this function simply asserts that one of the commands in
-   * $haystack contains all of the keys and values in $needle. Furthermore, if
-   * $needle contains a 'settings' key with an array value, we simply assert
-   * that all keys and values within that array are present in the command we're
-   * checking, and do not consider it a failure if the actual command contains
-   * additional settings that aren't part of $needle.
+   * An AjaxResponse object stores an array of Ajax commands This array
+   * sometimes includes commands automatically provided by the framework in
+   * addition to commands returned by a particular controller. During testing,
+   * we're usually interested that a particular command is present, and don't
+   * care whether other commands precede or follow the one we're interested in.
+   * Additionally, the command we're interested in may include additional data
+   * that we're not interested in. Therefore, this function simply asserts that
+   * one of the commands in $haystack contains all of the keys and values in
+   * $needle. Furthermore, if $needle contains a 'settings' key with an array
+   * value, we simply assert that all keys and values within that array are
+   * present in the command we're checking, and do not consider it a failure if
+   * the actual command contains additional settings that aren't part of
+   * $needle.
    *
    * @param $haystack
-   *   An array of Ajax commands returned by the server.
+   *   An array of rendered Ajax commands returned by the server.
    * @param $needle
    *   Array of info we're expecting in one of those commands.
    * @param $message
diff --git a/core/modules/system/lib/Drupal/system/Tests/Ajax/FrameworkTest.php b/core/modules/system/lib/Drupal/system/Tests/Ajax/FrameworkTest.php
index e84a7d9..f0e9894 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Ajax/FrameworkTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Ajax/FrameworkTest.php
@@ -27,7 +27,7 @@ public static function getInfo() {
   }
 
   /**
-   * Ensures ajax_render() returns JavaScript settings from the page request.
+   * Ensures AjaxController adds JavaScript settings from the page request.
    */
   public function testAJAXRender() {
     // Verify that settings command is generated when JavaScript settings are
@@ -80,7 +80,7 @@ public function testOrder() {
   }
 
   /**
-   * Tests behavior of ajax_render_error().
+   * Tests behavior of an error alert command.
    */
   public function testAJAXRenderError() {
     // Verify custom error message.
diff --git a/core/modules/system/lib/Drupal/system/Tests/Routing/RouterTest.php b/core/modules/system/lib/Drupal/system/Tests/Routing/RouterTest.php
index e15ea62..11f24d3 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Routing/RouterTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Routing/RouterTest.php
@@ -178,7 +178,6 @@ public function testUserAccount() {
    *   that is dependent on fixes to the Ajax system. Re-enable this test once
    *   http://drupal.org/node/1938980 is fixed.
    */
-  /*
   public function testControllerResolutionAjax() {
     // This will fail with a JSON parse error if the request is not routed to
     // The correct controller.
@@ -188,6 +187,4 @@ public function testControllerResolutionAjax() {
 
     $this->assertRaw('abcde', 'Correct body was found.');
   }
-  */
-
 }
diff --git a/core/modules/system/system.api.php b/core/modules/system/system.api.php
index abda14e..4bf6793 100644
--- a/core/modules/system/system.api.php
+++ b/core/modules/system/system.api.php
@@ -401,17 +401,19 @@ function hook_css_alter(&$css) {
 }
 
 /**
- * Alter the commands that are sent to the user through the Ajax framework.
+ * Alter the Ajax command data that are sent to the client through the Ajax
+ * framework.
  *
- * @param $commands
- *   An array of all commands that will be sent to the user.
+ * @param $data
+ *   An array of all the rendered commands that will be sent to the client.
  *
- * @see ajax_render()
+ * @see AjaxResponse::ajaxRender()
  */
-function hook_ajax_render_alter($commands) {
+function hook_ajax_render_alter(&$data) {
   // Inject any new status messages into the content area.
   $status_messages = array('#theme' => 'status_messages');
-  $commands[] = ajax_command_prepend('#block-system-main .content', drupal_render($status_messages));
+  $command = new PrependCommand('#block-system-main .content', drupal_render($status_messages));
+  $data[] = $command->render();
 }
 
 /**
