diff --git a/core/includes/ajax.inc b/core/includes/ajax.inc
index c67e981..0234129 100644
--- a/core/includes/ajax.inc
+++ b/core/includes/ajax.inc
@@ -5,6 +5,8 @@
  * Functions for use with Drupal's Ajax framework.
  */
 
+use Drupal\Component\Utility\NestedArray;
+
 /**
  * @defgroup ajax Ajax framework
  * @{
@@ -290,7 +292,7 @@ function ajax_render($commands = array()) {
   $scripts = drupal_add_js();
   if (!empty($scripts['settings'])) {
     $settings = $scripts['settings'];
-    array_unshift($commands, ajax_command_settings(call_user_func_array('array_merge_recursive', $settings['data']), TRUE));
+    array_unshift($commands, ajax_command_settings(NestedArray::mergeDeepArray($settings['data']), TRUE));
   }
 
   // Allow modules to alter any Ajax response.
diff --git a/core/includes/file.inc b/core/includes/file.inc
index ee707e3..48b59cf 100644
--- a/core/includes/file.inc
+++ b/core/includes/file.inc
@@ -1348,19 +1348,11 @@ function file_download() {
   $uri = $scheme . '://' . $target;
   if (file_stream_wrapper_valid_scheme($scheme) && file_exists($uri)) {
     // Let other modules provide headers and controls access to the file.
-    // module_invoke_all() uses array_merge_recursive() which merges header
-    // values into a new array. To avoid that and allow modules to override
-    // headers instead, use array_merge() to merge the returned arrays.
-    $headers = array();
-    foreach (module_implements('file_download') as $module) {
-      $function = $module . '_file_download';
-      $result = $function($uri);
+    $headers = module_invoke_all('file_download', $uri);
+    foreach ($headers as $result) {
       if ($result == -1) {
         throw new AccessDeniedHttpException();
       }
-      if (isset($result) && is_array($result)) {
-        $headers = array_merge($headers, $result);
-      }
     }
     if (count($headers)) {
       return file_transfer($uri, $headers);
diff --git a/core/includes/module.inc b/core/includes/module.inc
index 9be96cd..f6d47b6 100644
--- a/core/includes/module.inc
+++ b/core/includes/module.inc
@@ -6,6 +6,7 @@
  */
 
 use Drupal\Component\Graph\Graph;
+use Drupal\Component\Utility\NestedArray;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
@@ -921,7 +922,7 @@ function module_hook_info() {
         if (function_exists($function)) {
           $result = $function();
           if (isset($result) && is_array($result)) {
-            $hook_info = array_merge_recursive($hook_info, $result);
+            $hook_info = NestedArray::mergeDeep($hook_info, $result);
           }
         }
       }
@@ -1002,7 +1003,7 @@ function module_invoke_all($hook) {
     if (function_exists($function)) {
       $result = call_user_func_array($function, $args);
       if (isset($result) && is_array($result)) {
-        $return = array_merge_recursive($return, $result);
+        $return = NestedArray::mergeDeep($return, $result);
       }
       elseif (isset($result)) {
         $return[] = $result;
diff --git a/core/lib/Drupal/Core/Ajax/AjaxResponse.php b/core/lib/Drupal/Core/Ajax/AjaxResponse.php
index 492665e..516325b 100644
--- a/core/lib/Drupal/Core/Ajax/AjaxResponse.php
+++ b/core/lib/Drupal/Core/Ajax/AjaxResponse.php
@@ -9,6 +9,7 @@
 
 use Symfony\Component\HttpFoundation\JsonResponse;
 use Symfony\Component\HttpFoundation\Request;
+use Drupal\Component\Utility\NestedArray;
 
 /**
  * JSON response object for AJAX requests.
@@ -134,7 +135,7 @@ protected function ajaxRender(Request $request) {
     $scripts = drupal_add_js();
     if (!empty($scripts['settings'])) {
       $settings = $scripts['settings'];
-      $this->addCommand(new SettingsCommand(call_user_func_array('array_merge_recursive', $settings['data']), TRUE), TRUE);
+      $this->addCommand(new SettingsCommand(NestedArray::mergeDeepArray($settings['data']), TRUE), TRUE);
     }
 
     $commands = $this->commands;
diff --git a/core/lib/Drupal/Core/Entity/DatabaseStorageController.php b/core/lib/Drupal/Core/Entity/DatabaseStorageController.php
index a9f08ab..ac7e4c5 100644
--- a/core/lib/Drupal/Core/Entity/DatabaseStorageController.php
+++ b/core/lib/Drupal/Core/Entity/DatabaseStorageController.php
@@ -11,6 +11,7 @@
 use Drupal\Core\Entity\Query\QueryInterface;
 use Exception;
 use Drupal\Component\Uuid\Uuid;
+use Drupal\Component\Utility\NestedArray;
 
 
 /**
@@ -678,9 +679,9 @@ public function getFieldDefinitions(array $constraints) {
 
       // Invoke hooks.
       $result = module_invoke_all($this->entityType . '_property_info');
-      $this->entityFieldInfo = array_merge_recursive($this->entityFieldInfo, $result);
+      $this->entityFieldInfo = NestedArray::mergeDeep($this->entityFieldInfo, $result);
       $result = module_invoke_all('entity_field_info', $this->entityType);
-      $this->entityFieldInfo = array_merge_recursive($this->entityFieldInfo, $result);
+      $this->entityFieldInfo = NestedArray::mergeDeep($this->entityFieldInfo, $result);
 
       $hooks = array('entity_field_info', $this->entityType . '_property_info');
       drupal_alter($hooks, $this->entityFieldInfo, $this->entityType);
diff --git a/core/modules/edit/edit.module b/core/modules/edit/edit.module
index e3beec0..f47285e 100644
--- a/core/modules/edit/edit.module
+++ b/core/modules/edit/edit.module
@@ -13,6 +13,7 @@
 
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\edit\Form\EditFieldForm;
+use Drupal\Component\Utility\NestedArray;
 
 /**
  * Implements hook_custom_theme().
@@ -65,7 +66,7 @@ function edit_toolbar() {
 
   // Include the attachments and settings for all available editors.
   $attachments = drupal_container()->get('edit.editor.selector')->getAllEditorAttachments();
-  $tab['edit']['tray']['#attached'] = array_merge_recursive($tab['edit']['tray']['#attached'], $attachments);
+  $tab['edit']['tray']['#attached'] = NestedArray::mergeDeep($tab['edit']['tray']['#attached'], $attachments);
 
   return $tab;
 }
diff --git a/core/modules/file/file.module b/core/modules/file/file.module
index 1590f4e..e2d0d08 100644
--- a/core/modules/file/file.module
+++ b/core/modules/file/file.module
@@ -776,7 +776,7 @@ function file_ajax_upload() {
 
   $output = theme('status_messages') . drupal_render($form);
   $js = drupal_add_js();
-  $settings = call_user_func_array('array_merge_recursive', $js['settings']['data']);
+  $settings = NestedArray::mergeDeepArray($js['settings']['data']);
 
   $response = new AjaxResponse();
   return $response->addCommand(new ReplaceCommand(NULL, $output, $settings));
diff --git a/core/modules/node/lib/Drupal/node/NodeFormController.php b/core/modules/node/lib/Drupal/node/NodeFormController.php
index 4f5315e..57a8834 100644
--- a/core/modules/node/lib/Drupal/node/NodeFormController.php
+++ b/core/modules/node/lib/Drupal/node/NodeFormController.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\node;
 
+use Drupal\Component\Utility\NestedArray;
 use Drupal\Core\Datetime\DrupalDateTime;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\EntityFormController;
@@ -91,7 +92,7 @@ public function form(array $form, array &$form_state, EntityInterface $node) {
     //   handlers to the form buttons below. Remove hook_form() entirely.
     $function = node_hook($node->type, 'form');
     if ($function && ($extra = $function($node, $form_state))) {
-      $form = array_merge_recursive($form, $extra);
+      $form = NestedArray::mergeDeep($form, $extra);
     }
     // If the node type has a title, and the node type form defined no special
     // weight for it, we default to a weight of -5 for consistency.
diff --git a/core/modules/rdf/lib/Drupal/rdf/Tests/GetNamespacesTest.php b/core/modules/rdf/lib/Drupal/rdf/Tests/GetNamespacesTest.php
index 4a68e52..a133230 100644
--- a/core/modules/rdf/lib/Drupal/rdf/Tests/GetNamespacesTest.php
+++ b/core/modules/rdf/lib/Drupal/rdf/Tests/GetNamespacesTest.php
@@ -52,8 +52,8 @@ function testGetRdfNamespaces() {
     $this->assertTrue(!empty($element), 'Two prefixes can be assigned the same namespace.');
 
     $element = $this->xpath('//html[contains(@prefix, :prefix_binding)]', array(
-      ':prefix_binding' => 'dc: ',
+      ':prefix_binding' => 'dc: http://purl.org/dc/terms/',
     ));
-    $this->assertTrue(empty($element), 'A prefix with conflicting namespaces is discarded.');
+    $this->assertTrue(!empty($element), 'When a prefix has conflicting namespaces, the first declared one is used.');
   }
 }
diff --git a/core/modules/rdf/lib/Drupal/rdf/Tests/GetRdfNamespacesTest.php b/core/modules/rdf/lib/Drupal/rdf/Tests/GetRdfNamespacesTest.php
index 997e41a..362a416 100644
--- a/core/modules/rdf/lib/Drupal/rdf/Tests/GetRdfNamespacesTest.php
+++ b/core/modules/rdf/lib/Drupal/rdf/Tests/GetRdfNamespacesTest.php
@@ -39,6 +39,6 @@ function testGetRdfNamespaces() {
     $this->assertEqual($ns['rdfs'], 'http://www.w3.org/2000/01/rdf-schema#', 'A prefix declared once is included.');
     $this->assertEqual($ns['foaf'], 'http://xmlns.com/foaf/0.1/', 'The same prefix declared in several implementations of hook_rdf_namespaces() is valid as long as all the namespaces are the same.');
     $this->assertEqual($ns['foaf1'], 'http://xmlns.com/foaf/0.1/', 'Two prefixes can be assigned the same namespace.');
-    $this->assertTrue(!isset($ns['dc']), 'A prefix with conflicting namespaces is discarded.');
+    $this->assertEqual($ns['dc'], 'http://purl.org/dc/terms/', 'When a prefix has conflicting namespaces, the first declared one is used.');
   }
 }
diff --git a/core/modules/rdf/rdf.module b/core/modules/rdf/rdf.module
index 264a090..ffc3a0b 100644
--- a/core/modules/rdf/rdf.module
+++ b/core/modules/rdf/rdf.module
@@ -5,6 +5,7 @@
  * Enables semantically enriched output for Drupal sites in the form of RDFa.
  */
 
+use Drupal\Component\Utility\NestedArray;
 use Drupal\Core\Template\Attribute;
 
 /**
@@ -99,27 +100,16 @@ function rdf_rdf_namespaces() {
  * implement it.
  */
 function rdf_get_namespaces() {
-  $rdf_namespaces = module_invoke_all('rdf_namespaces');
-  // module_invoke_all() uses array_merge_recursive() which might return nested
-  // arrays if several modules redefine the same prefix multiple times. We need
-  // to ensure the array of namespaces is flat and only contains strings as
-  // URIs.
-  foreach ($rdf_namespaces as $prefix => $uri) {
-    if (is_array($uri)) {
-      if (count(array_unique($uri)) == 1) {
-        // All namespaces declared for this prefix are the same, merge them all
-        // into a single namespace.
-        $rdf_namespaces[$prefix] = $uri[0];
-      }
-      else {
-        // There are conflicting namespaces for this prefix, do not include
-        // duplicates in order to avoid asserting any inaccurate RDF
-        // statements.
-        unset($rdf_namespaces[$prefix]);
-      }
+  $namespaces = array();
+  // In order to resolve duplicate namespaces by using the earliest defined
+  // namespace, do not use module_invoke_all().
+  foreach (module_implements('rdf_namespaces') as $module) {
+    $function = $module . '_rdf_namespaces';
+    if (function_exists($function)) {
+      $namespaces = NestedArray::mergeDeep($function(), $namespaces);
     }
   }
-  return $rdf_namespaces;
+  return $namespaces;
 }
 
 /**
@@ -703,7 +693,7 @@ function rdf_preprocess_username(&$variables) {
   // (see http://www.w3.org/TR/rdfa-syntax/#rdfa-attributes).
   // Therefore, merge rather than override so as not to clobber values set by
   // earlier preprocess functions.
-  $variables['attributes'] = array_merge_recursive($variables['attributes'], $attributes);
+  $variables['attributes'] = NestedArray::mergeDeep($variables['attributes'], $attributes);
 }
 
 /**
diff --git a/core/modules/system/lib/Drupal/system/Tests/Ajax/CommandsTest.php b/core/modules/system/lib/Drupal/system/Tests/Ajax/CommandsTest.php
index 5a455b7..60ef056 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Ajax/CommandsTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Ajax/CommandsTest.php
@@ -158,6 +158,14 @@ function testAjaxCommands() {
     );
     $this->assertCommand($commands, $expected, "'settings' AJAX command issued with correct data");
 
+     // Test that the settings command merges settings properly.
+     $commands = $this->drupalPostAJAX($form_path, $edit, array('op' => t("AJAX 'settings' command with setting merging")));
+     $expected = array(
+       'command' => 'settings',
+       'settings' => array('ajax_forms_test' => array('foo' => 9001)),
+     );
+     $this->assertCommand($commands, $expected, "'settings' AJAX command with setting merging");
+
     // Tests the 'add_css' command.
     $commands = $this->drupalPostAJAX($form_path, $edit, array('op' => t("AJAX 'add_css' command")));
     $expected = array(
diff --git a/core/modules/system/tests/modules/ajax_forms_test/ajax_forms_test.module b/core/modules/system/tests/modules/ajax_forms_test/ajax_forms_test.module
index 96288f9..12c87dd 100644
--- a/core/modules/system/tests/modules/ajax_forms_test/ajax_forms_test.module
+++ b/core/modules/system/tests/modules/ajax_forms_test/ajax_forms_test.module
@@ -245,6 +245,18 @@ function ajax_forms_test_ajax_commands_form($form, &$form_state) {
     ),
   );
 
+  // Tests the 'settings' command with a callback which sets the same
+  // setting multiple times. This is used to check that settings are
+  // merged properly (e.g., array_merge_recursive() merges settings
+  // incorrectly, #1356170).
+  $form['settings_command_with_merging_example'] = array(
+    '#type' => 'submit',
+    '#value' => t("AJAX 'settings' command with setting merging"),
+    '#ajax' => array(
+      'callback' => 'ajax_forms_test_advanced_commands_settings_with_merging_callback',
+    ),
+  );
+
   $form['submit'] = array(
     '#type' => 'submit',
     '#value' => t('Submit'),
@@ -408,6 +420,16 @@ function ajax_forms_test_advanced_commands_add_css_callback($form, $form_state)
 }
 
 /**
+ * Ajax callback for 'settings' but with setting overrides.
+ */
+function ajax_forms_test_advanced_commands_settings_with_merging_callback($form, $form_state) {
+  drupal_add_js(array('ajax_forms_test' => array('foo' => 42)), 'setting');
+  drupal_add_js(array('ajax_forms_test' => array('foo' => 9001)), 'setting');
+
+  return array('#type' => 'ajax', '#commands' => array());
+}
+
+/**
  * Form constructor for AJAX validation form.
  *
  * This form and its related submit and callback functions demonstrate
diff --git a/core/modules/translation/translation.pages.inc b/core/modules/translation/translation.pages.inc
index 7a9880c..53b91c8 100644
--- a/core/modules/translation/translation.pages.inc
+++ b/core/modules/translation/translation.pages.inc
@@ -5,6 +5,7 @@
  * User page callbacks for the Translation module.
  */
 
+use Drupal\Component\Utility\NestedArray;
 use Drupal\node\Plugin\Core\Entity\Node;
 
 /**
@@ -71,7 +72,7 @@ function translation_node_overview(Node $node) {
           $options['add'] = array(
             'title' => t('add translation'),
           ) + $links->links[$langcode];
-          $options['add'] = array_merge_recursive($options['add'], $query);
+          $options['add'] = NestedArray::mergeDeep($options['add'], $query);
         }
       }
       $status = t('Not translated');
diff --git a/core/modules/user/user.module b/core/modules/user/user.module
index aa5d085..e952607 100644
--- a/core/modules/user/user.module
+++ b/core/modules/user/user.module
@@ -933,6 +933,9 @@ function template_process_username(&$variables) {
     // to use the former unless they want to add attributes on the link only.
     // If a link is being rendered, these need to be merged. Some attributes are
     // themselves arrays, so the merging needs to be recursive.
+    // This purposefully does not use
+    // \Drupal\Component\Utility\NestedArray::mergeDeep() for performance
+    // reasons, since it is potentially called very often.
     $variables['link_options']['attributes'] = array_merge_recursive($variables['link_attributes'], $variables['attributes']);
   }
 }
