diff --git a/core/modules/file/migration_templates/d6_upload.yml b/core/modules/file/migration_templates/d6_upload.yml
index 8289d56..a497099 100644
--- a/core/modules/file/migration_templates/d6_upload.yml
+++ b/core/modules/file/migration_templates/d6_upload.yml
@@ -9,7 +9,7 @@ process:
   vid: vid
   type: type
   upload:
-    plugin: iterator
+    plugin: sub_process
     source: upload
     process:
       target_id:
diff --git a/core/modules/filter/migration_templates/d6_filter_format.yml b/core/modules/filter/migration_templates/d6_filter_format.yml
index a5f4388..d6bf7f2 100644
--- a/core/modules/filter/migration_templates/d6_filter_format.yml
+++ b/core/modules/filter/migration_templates/d6_filter_format.yml
@@ -11,7 +11,7 @@ process:
   name: name
   cache: cache
   filters:
-    plugin: iterator
+    plugin: sub_process
     source: filters
     key: '@id'
     process:
diff --git a/core/modules/filter/migration_templates/d7_filter_format.yml b/core/modules/filter/migration_templates/d7_filter_format.yml
index c0710b5..3bac484 100644
--- a/core/modules/filter/migration_templates/d7_filter_format.yml
+++ b/core/modules/filter/migration_templates/d7_filter_format.yml
@@ -10,7 +10,7 @@ process:
   cache: cache
   weight: weight
   filters:
-    plugin: iterator
+    plugin: sub_process
     source: filters
     key: '@id'
     process:
diff --git a/core/modules/image/migration_templates/d7_image_styles.yml b/core/modules/image/migration_templates/d7_image_styles.yml
index 7dd8977..b2b14b0 100644
--- a/core/modules/image/migration_templates/d7_image_styles.yml
+++ b/core/modules/image/migration_templates/d7_image_styles.yml
@@ -8,7 +8,7 @@ process:
   name: name
   label: label
   effects:
-    plugin: iterator
+    plugin: sub_process
     source: effects
     process:
       id: name
diff --git a/core/modules/migrate/src/Plugin/migrate/process/Iterator.php b/core/modules/migrate/src/Plugin/migrate/process/Iterator.php
index d85abfd..5400e18 100644
--- a/core/modules/migrate/src/Plugin/migrate/process/Iterator.php
+++ b/core/modules/migrate/src/Plugin/migrate/process/Iterator.php
@@ -2,65 +2,20 @@
 
 namespace Drupal\migrate\Plugin\migrate\process;
 
-use Drupal\migrate\ProcessPluginBase;
-use Drupal\migrate\MigrateExecutableInterface;
-use Drupal\migrate\Row;
+@trigger_error('The ' . __NAMESPACE__ . '\Iterator is deprecated in
+Drupal 8.4.x and will be removed before Drupal 9.0.0. Instead, use ' . __NAMESPACE__ . '\SubProcess', E_USER_DEPRECATED);
 
 /**
- * This plugin iterates and processes an array.
+ * Iterates and processes an associative array.
  *
- * @link https://www.drupal.org/node/2135345 Online handbook documentation for iterator process plugin @endlink
+ * @deprecated in Drupal 8.4.x and will be removed in Drupal 9.0.x. Use
+ *   \Drupal\migrate\Plugin\migrate\process\SubProcess instead.
+ *
+ * @see https://www.drupal.org/node/2880427
  *
  * @MigrateProcessPlugin(
  *   id = "iterator",
  *   handle_multiples = TRUE
  * )
  */
-class Iterator extends ProcessPluginBase {
-
-  /**
-   * Runs a process pipeline on each destination property per list item.
-   */
-  public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
-    $return = [];
-    if (!is_null($value)) {
-      foreach ($value as $key => $new_value) {
-        $new_row = new Row($new_value, []);
-        $migrate_executable->processRow($new_row, $this->configuration['process']);
-        $destination = $new_row->getDestination();
-        if (array_key_exists('key', $this->configuration)) {
-          $key = $this->transformKey($key, $migrate_executable, $new_row);
-        }
-        $return[$key] = $destination;
-      }
-    }
-    return $return;
-  }
-
-  /**
-   * Runs the process pipeline for the current key.
-   *
-   * @param string|int $key
-   *   The current key.
-   * @param \Drupal\migrate\MigrateExecutableInterface $migrate_executable
-   *   The migrate executable helper class.
-   * @param \Drupal\migrate\Row $row
-   *   The current row after processing.
-   *
-   * @return mixed
-   *   The transformed key.
-   */
-  protected function transformKey($key, MigrateExecutableInterface $migrate_executable, Row $row) {
-    $process = ['key' => $this->configuration['key']];
-    $migrate_executable->processRow($row, $process, $key);
-    return $row->getDestinationProperty('key');
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function multiple() {
-    return TRUE;
-  }
-
-}
+class Iterator extends SubProcess {}
diff --git a/core/modules/migrate/src/Plugin/migrate/process/SubProcess.php b/core/modules/migrate/src/Plugin/migrate/process/SubProcess.php
new file mode 100644
index 0000000..cb11fc6
--- /dev/null
+++ b/core/modules/migrate/src/Plugin/migrate/process/SubProcess.php
@@ -0,0 +1,163 @@
+<?php
+
+namespace Drupal\migrate\Plugin\migrate\process;
+
+use Drupal\migrate\ProcessPluginBase;
+use Drupal\migrate\MigrateExecutableInterface;
+use Drupal\migrate\Row;
+
+/**
+ * Runs an array of arrays through its own process pipeline.
+ *
+ * The sub_process plugin accepts an array of associative arrays and runs each
+ * one through its own process pipeline, producing a newly keyed associative
+ * array of transformed values.
+ *
+ * Available configuration keys:
+ *   - process: the plugin(s) that will process each element of the source.
+ *   - key: runs the process pipeline for the key to determine a new dynamic
+ *     name.
+ *
+ * Examples:
+ *
+ * @code
+ * source: Array
+ * (
+ *   [upload] => Array
+ *     (
+ *       [0] => Array
+ *         (
+ *           [fid] => 1
+ *           [list] => 0
+ *           [description] => "File number 1"
+ *         )
+ *       [1] => Array
+ *         (
+ *           [fid] => 2
+ *           [list] => 1
+ *           [description] => "File number 2"
+ *         )
+ *     )
+ * )
+ * ...
+ * @endcode
+ *
+ * The sub_process process plugin will take these arrays one at a time and run
+ * its own process over each one:
+ *
+ * @code
+ * process:
+ *   upload:
+ *     plugin: sub_process
+ *     source: upload
+ *     process:
+ *       target_id:
+ *         plugin: migration_lookup
+ *         migration: d6_file
+ *         source: fid
+ *       display: list
+ *       description: description
+ * @endcode
+ *
+ * In this case, each item in the upload array will be processed by the
+ * sub_process process plugin. The target_id will be found by looking up the
+ * destination value from a previous migration. The display and description
+ * fields will simply be mapped.
+ *
+ * In the next example, normally the array returned from sub_process will have
+ * its original keys. If you need to change the key, it is possible for the
+ * returned array to be keyed by one of the transformed values in the sub-array.
+ *
+ * @code
+ * source: Array
+ * (
+ *   [format] => 1
+ *   [name] => Filtered HTML
+ * ...
+ *   [filters] => Array
+ *     (
+ *       [0] => Array
+ *         (
+ *           [module] => filter
+ *           [delta] => 2
+ *           [weight] => 0
+ *         )
+ *       [1] => Array
+ *         (
+ *           [module] => filter
+ *           [delta] => 0
+ *           [weight] => 1
+ *         )
+ *    )
+ * )
+ * ...
+ *
+ * process:
+ *   filters:
+ *     plugin: iterator
+ *     source: filters
+ *     key: "@id"
+ *     process:
+ *       id:
+ *         plugin: static_map
+ *         bypass: true
+ *         map:
+ *           filter:
+ *             ...
+ * @endcode
+ *
+ * @see \Drupal\migrate\Plugin\MigrateProcessInterface
+ *
+ * @MigrateProcessPlugin(
+ *   id = "sub_process",
+ *   handle_multiples = TRUE
+ * )
+ */
+class SubProcess extends ProcessPluginBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
+    $return = [];
+    if (is_array($value) || $value instanceof \Traversable) {
+      foreach ($value as $key => $new_value) {
+        $new_row = new Row($new_value, []);
+        $migrate_executable->processRow($new_row, $this->configuration['process']);
+        $destination = $new_row->getDestination();
+        if (array_key_exists('key', $this->configuration)) {
+          $key = $this->transformKey($key, $migrate_executable, $new_row);
+        }
+        $return[$key] = $destination;
+      }
+    }
+    return $return;
+  }
+
+  /**
+   * Runs the process pipeline for the key to determine its dynamic name.
+   *
+   * @param string|int $key
+   *   The current key.
+   * @param \Drupal\migrate\MigrateExecutableInterface $migrate_executable
+   *   The migrate executable helper class.
+   * @param \Drupal\migrate\Row $row
+   *   The current row after processing.
+   *
+   * @return mixed
+   *   The transformed key.
+   */
+  protected function transformKey($key, MigrateExecutableInterface $migrate_executable, Row $row) {
+    $process = ['key' => $this->configuration['key']];
+    $migrate_executable->processRow($row, $process, $key);
+    return $row->getDestinationProperty('key');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function multiple() {
+    return TRUE;
+  }
+
+}
diff --git a/core/modules/migrate/tests/src/Unit/process/IteratorTest.php b/core/modules/migrate/tests/src/Unit/process/IteratorTest.php
index 993fb92..46daade 100644
--- a/core/modules/migrate/tests/src/Unit/process/IteratorTest.php
+++ b/core/modules/migrate/tests/src/Unit/process/IteratorTest.php
@@ -31,6 +31,8 @@ class IteratorTest extends MigrateTestCase {
 
   /**
    * Tests the iterator process plugin.
+   *
+   * @group legacy
    */
   public function testIterator() {
     $migration = $this->getMigration();
diff --git a/core/modules/migrate/tests/src/Unit/process/IteratorTest.php b/core/modules/migrate/tests/src/Unit/process/SubProcessTest.php
similarity index 78%
copy from core/modules/migrate/tests/src/Unit/process/IteratorTest.php
copy to core/modules/migrate/tests/src/Unit/process/SubProcessTest.php
index 993fb92..dfcdec3 100644
--- a/core/modules/migrate/tests/src/Unit/process/IteratorTest.php
+++ b/core/modules/migrate/tests/src/Unit/process/SubProcessTest.php
@@ -4,21 +4,21 @@
 
 use Drupal\migrate\MigrateExecutable;
 use Drupal\migrate\Plugin\migrate\process\Get;
-use Drupal\migrate\Plugin\migrate\process\Iterator;
+use Drupal\migrate\Plugin\migrate\process\SubProcess;
 use Drupal\migrate\Row;
 use Drupal\Tests\migrate\Unit\MigrateTestCase;
 
 /**
- * Tests the iterator process plugin.
+ * Tests the sub_process process plugin.
  *
  * @group migrate
  */
-class IteratorTest extends MigrateTestCase {
+class SubProcessTest extends MigrateTestCase {
 
   /**
-   * The iterator plugin being tested.
+   * The sub_process plugin being tested.
    *
-   * @var \Drupal\migrate\Plugin\migrate\process\TestIterator
+   * @var \Drupal\migrate\Plugin\migrate\process\SubProcess
    */
   protected $plugin;
 
@@ -30,11 +30,11 @@ class IteratorTest extends MigrateTestCase {
   ];
 
   /**
-   * Tests the iterator process plugin.
+   * Tests the sub_process process plugin.
    */
-  public function testIterator() {
+  public function testSubProcess() {
     $migration = $this->getMigration();
-    // Set up the properties for the iterator.
+    // Set up the properties for the sub_process.
     $configuration = [
       'process' => [
         'foo' => 'source_foo',
@@ -42,15 +42,15 @@ public function testIterator() {
       ],
       'key' => '@id',
     ];
-    $plugin = new Iterator($configuration, 'iterator', []);
+    $plugin = new SubProcess($configuration, 'sub_process', []);
     // Manually create the plugins. Migration::getProcessPlugins does this
     // normally but the plugin system is not available.
     foreach ($configuration['process'] as $destination => $source) {
-      $iterator_plugins[$destination][] = new Get(['source' => $source], 'get', []);
+      $sub_process_plugins[$destination][] = new Get(['source' => $source], 'get', []);
     }
     $migration->expects($this->at(1))
       ->method('getProcessPlugins')
-      ->will($this->returnValue($iterator_plugins));
+      ->willReturn($sub_process_plugins);
     // Set up the key plugins.
     $key_plugin['key'][] = new Get(['source' => '@id'], 'get', []);
     $migration->expects($this->at(2))
