diff --git a/migrate_example_advanced/config/install/migrate_plus.migration.weather_soap.yml b/migrate_example_advanced/config/install/migrate_plus.migration.weather_soap.yml
new file mode 100755
index 0000000000000000000000000000000000000000..da4252153efaa47b044141dbb561e4a2e7eb6325
--- /dev/null
+++ b/migrate_example_advanced/config/install/migrate_plus.migration.weather_soap.yml
@@ -0,0 +1,36 @@
+# This migration demonstrates importing from SOAP/WSDL.
+id: weather_soap
+label: SOAP service providing weather.
+migration_group: wine
+source:
+  # We use the SOAP parser source plugin.
+  plugin: url
+  data_fetcher_plugin: http # Ignored - SoapClient does the fetching.
+  data_parser_plugin: soap
+  # URL of a WSDL endpoint.
+  urls: http://www.webservicex.net/globalweather.asmx?WSDL
+  # The function to call on the service, and the parameters to pass.
+  function: GetCitiesByCountry
+  parameters:
+    CountryName: Spain
+  response_type: xml
+  item_selector: /NewDataSet/Table
+  fields:
+    -
+      name: Country
+      label: Country
+      selector: Country
+    -
+      name: City
+      label: City
+      selector: City
+  ids:
+    City:
+      type: string
+process:
+  vid:
+    plugin: default_value
+    default_value: migrate_example_wine_varieties
+  name: City
+destination:
+  plugin: entity:taxonomy_term
diff --git a/src/Plugin/migrate_plus/data_parser/Soap.php b/src/Plugin/migrate_plus/data_parser/Soap.php
new file mode 100755
index 0000000000000000000000000000000000000000..60db902046d7c65622d61ef3f5be1cac5277badd
--- /dev/null
+++ b/src/Plugin/migrate_plus/data_parser/Soap.php
@@ -0,0 +1,123 @@
+<?php
+
+namespace Drupal\migrate_plus\Plugin\migrate_plus\data_parser;
+
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\migrate\MigrateException;
+use Drupal\migrate_plus\DataParserPluginBase;
+
+/**
+ * Obtain SOAP data for migration.
+ *
+ * @DataParser(
+ *   id = "soap",
+ *   title = @Translation("SOAP")
+ * )
+ */
+class Soap extends DataParserPluginBase implements ContainerFactoryPluginInterface {
+
+  /**
+   * Iterator over the SOAP data.
+   *
+   * @var \Iterator
+   */
+  protected $iterator;
+
+  /**
+   * Method to call on the SOAP service.
+   *
+   * @var string
+   */
+  protected $function;
+
+  /**
+   * Parameters to pass to the SOAP service function.
+   *
+   * @var array
+   */
+  protected $parameters;
+
+  /**
+   * Form of the function response - 'xml', 'object', or 'array'.
+   *
+   * @var string
+   */
+  protected $responseType;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function __construct(array $configuration, $plugin_id, $plugin_definition) {
+    parent::__construct($configuration, $plugin_id, $plugin_definition);
+    $this->function = $configuration['function'];
+    $this->parameters = $configuration['parameters'];
+    $this->responseType = $configuration['response_type'];
+  }
+
+  /**
+   * {@inheritdoc}
+   *
+   * @throws \SoapFault
+   *   If there's an error in a SOAP call.
+   * @throws \Drupal\migrate\MigrateException
+   *   If we can't resolve the SOAP function or its response property.
+   */
+  protected function openSourceUrl($url) {
+    // Will throw SoapFault if there's
+    $client = new \SoapClient($url);
+    // Determine the response property name.
+    $function_found = FALSE;
+    foreach ($client->__getFunctions() as $function_signature) {
+      // E.g., "GetWeatherResponse GetWeather(GetWeather $parameters)".
+      $response_type = strtok($function_signature, ' ');
+      $function_name = strtok('(');
+      if (strcasecmp($function_name, $this->function) === 0) {
+        $function_found = TRUE;
+        foreach ($client->__getTypes() as $type_info) {
+          // E.g., "struct GetWeatherResponse {\n string GetWeatherResult;\n}".
+          if (preg_match('|struct (.*?) {\s*[a-z]+ (.*?);|is', $type_info, $matches)) {
+            if ($matches[1] == $response_type) {
+              $response_property = $matches[2];
+            }
+          }
+        }
+        break;
+      }
+    }
+    if (!$function_found) {
+      throw new MigrateException("SOAP function {$this->function} not found.");
+    }
+    elseif (!isset($response_property)) {
+      throw new MigrateException("Response property not found for SOAP function {$this->function}.");
+    }
+    $response = $client->{$this->function}($this->parameters);
+    $response_value = $response->$response_property;
+    switch ($this->responseType) {
+      case 'xml':
+        $xml = simplexml_load_string($response_value);
+        $this->iterator = new \ArrayIterator($xml->xpath($this->itemSelector));
+        break;
+      case 'object':
+        $this->iterator = new \ArrayIterator($response_value->{$this->itemSelector});
+        break;
+      case 'array':
+        $this->iterator = new \ArrayIterator($response_value[$this->itemSelector]);
+        break;
+    }
+    return TRUE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function fetchNextRow() {
+    $current = $this->iterator->current();
+    if ($current) {
+      foreach ($this->fieldSelectors() as $field_name => $selector) {
+        $this->currentItem[$field_name] = $current->$selector;
+      }
+      $this->iterator->next();
+    }
+  }
+
+}