diff --git a/src/Plugin/migrate/process/Service.php b/src/Plugin/migrate/process/Service.php
new file mode 100644
index 0000000000000000000000000000000000000000..cd91028096ce61d7a683eccb9a63161e892b48a2
--- /dev/null
+++ b/src/Plugin/migrate/process/Service.php
@@ -0,0 +1,56 @@
+<?php
+
+namespace Drupal\migrate_plus\Plugin\migrate\process;
+
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\migrate\Plugin\migrate\process\Callback;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Provides a plugin to use a callable from a service class.
+ *
+ * Example:
+ *
+ * @code
+ * process:
+ *   filemime:
+ *     plugin: service
+ *     service: file.mime_type.guesser
+ *     method: guessMimeType
+ *     source: filename
+ * @endcode
+ *
+ * All options for the callback plugin can be used, except for 'callable',
+ * which will be ignored.
+ *
+ * @see \Drupal\migrate\Plugin\migrate\process\Callback
+ *
+ * @MigrateProcessPlugin(
+ *   id = "service"
+ * )
+ */
+class Service extends Callback implements ContainerFactoryPluginInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+    if (!isset($configuration['service'])) {
+      throw new \InvalidArgumentException('The "service" must be set.');
+    }
+    if (!isset($configuration['method'])) {
+      throw new \InvalidArgumentException('The "method" must be set.');
+    }
+    if (!$container->has($configuration['service'])) {
+      throw new \InvalidArgumentException(sprintf('You have requested the non-existent service "%s".', $configuration['service']));
+    }
+    $service = $container->get($configuration['service']);
+    if (!method_exists($service, $configuration['method'])) {
+      throw new \InvalidArgumentException(sprintf('The "%s" service has no method "%s".', $configuration['service'], $configuration['method']));
+    }
+
+    $configuration['callable'] = [$service, $configuration['method']];
+    return new static($configuration, $plugin_id, $plugin_definition);
+  }
+
+}
diff --git a/tests/src/Kernel/Plugin/migrate/process/ServiceTest.php b/tests/src/Kernel/Plugin/migrate/process/ServiceTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..59086865d208cba9d8874790336d187352a6e13f
--- /dev/null
+++ b/tests/src/Kernel/Plugin/migrate/process/ServiceTest.php
@@ -0,0 +1,118 @@
+<?php
+
+namespace Drupal\Tests\migrate_plus\Kernel\Plugin\migrate\process;
+
+use Drupal\KernelTests\KernelTestBase;
+use Drupal\migrate\MigrateExecutableInterface;
+use Drupal\migrate\Row;
+
+/**
+ * Tests the service plugin.
+ *
+ * @coversDefaultClass \Drupal\migrate_plus\Plugin\migrate\process\Service
+ * @group migrate_plus
+ */
+class ServiceTest extends KernelTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = [
+    'migrate',
+    'migrate_plus',
+    'system',
+  ];
+
+  /**
+   * The process plugin manager.
+   *
+   * @var \Drupal\migrate\Plugin\MigratePluginManagerInterface
+   */
+  protected $pluginManager;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp(): void {
+    parent::setUp();
+
+    $this->pluginManager = $this->container->get('plugin.manager.migrate.process');
+  }
+
+  /**
+   * Tests using a service.
+   *
+   * @covers ::create
+   */
+  public function testValidConfig(): void {
+    /** @var \Drupal\migrate\MigrateExecutableInterface $executable */
+    $executable = $this->prophesize(MigrateExecutableInterface::class)->reveal();
+    $row = new Row([], []);
+    $configuration = [
+      'service' => 'email.validator',
+      'method' => 'isValid',
+    ];
+    /** @var \Drupal\migrate_plus\Plugin\migrate\process\Service $service */
+    $service = $this->pluginManager->createInstance('service', $configuration);
+
+    // Test a valid email address.
+    $value = 'drupal@example.com';
+    $bool = $service->transform($value, $executable, $row, 'destination_property');
+    $this->assertEquals(TRUE, $bool);
+
+    // Test an invalid email address.
+    $value = 'drupal_example.com';
+    $bool = $service->transform($value, $executable, $row, 'destination_property');
+    $this->assertEquals(FALSE, $bool);
+  }
+
+  /**
+   * Tests configuration validation.
+   *
+   * @param string[] $configuration
+   *   The configuration for the service plugin. The expected keys are 'service'
+   *   and 'method'.
+   * @param string $message
+   *   The expected exception message.
+   *
+   * @covers ::create
+   *
+   * @dataProvider providerConfig
+   */
+  public function testInvalidConfig(array $configuration, $message): void {
+    $this->expectException(\InvalidArgumentException::class);
+    $this->expectExceptionMessage($message);
+    $this->pluginManager->createInstance('service', $configuration);
+  }
+
+  /**
+   * Data provider for testInvalidConfig.
+   */
+  public function providerConfig() {
+    return [
+      'missing service name' => [
+        'configuration' => [],
+        'message' => 'The "service" must be set.',
+      ],
+      'missing method name' => [
+        'configuration' => ['service' => 'email.validator'],
+        'message' => 'The "method" must be set.',
+      ],
+      'invalid service name' => [
+        'configuration' => [
+          'service' => 'no shirt no shoes no service',
+          'method' => 'isValid',
+        ],
+        'message' => 'You have requested the non-existent service "no shirt no shoes no service".',
+      ],
+      'invalid method name' => [
+        'configuration' => [
+          'service' => 'email.validator',
+          'method' => 'noSuchMethod',
+        ],
+        'message' => 'The "email.validator" service has no method "noSuchMethod".',
+      ],
+    ];
+  }
+
+}