diff --git a/core/core.services.yml b/core/core.services.yml
index 640440b..2f6d90f 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -175,6 +175,9 @@ services:
   plugin.manager.menu.local_task:
     class: Drupal\Core\Menu\LocalTaskManager
     arguments: ['@container.namespaces', '@controller_resolver', '@request', '@router.route_provider', '@module_handler']
+  plugin.manager.queue.item:
+    class: Drupal\Core\Queue\QueuePluginManager
+    arguments: ['@container.namespaces', '@module_handler']
   request:
     class: Symfony\Component\HttpFoundation\Request
     # @TODO the synthetic setting must be uncommented whenever drupal_session_initialize()
diff --git a/core/includes/common.inc b/core/includes/common.inc
index a4805c0..9250b43 100644
--- a/core/includes/common.inc
+++ b/core/includes/common.inc
@@ -3229,8 +3229,8 @@ function drupal_cron_run() {
 
   $return = FALSE;
   // Grab the defined cron queues.
-  $queues = module_invoke_all('queue_info');
-  drupal_alter('queue_info', $queues);
+  $manager = Drupal::service('plugin.manager.queue.item');
+  $queues = $manager->getDefinitions();
 
   // Try to acquire cron lock.
   if (!lock()->acquire('cron', 240.0)) {
@@ -3270,11 +3270,11 @@ function drupal_cron_run() {
 
   foreach ($queues as $queue_name => $info) {
     if (isset($info['cron'])) {
-      $callback = $info['worker callback'];
-      $end = time() + (isset($info['cron']['time']) ? $info['cron']['time'] : 15);
+      $queue_item = $manager->createInstance($queue_name);
+      $end = time() + $info['cron']['time'];
       $queue = Drupal::queue($queue_name);
       while (time() < $end && ($item = $queue->claimItem())) {
-        call_user_func_array($callback, array($item->data));
+        $queue_item->processItem($item->data);
         $queue->deleteItem($item);
       }
     }
diff --git a/core/lib/Drupal/Core/Annotation/QueuePlugin.php b/core/lib/Drupal/Core/Annotation/QueuePlugin.php
new file mode 100644
index 0000000..c30bf2d
--- /dev/null
+++ b/core/lib/Drupal/Core/Annotation/QueuePlugin.php
@@ -0,0 +1,60 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Annotation\QueuePlugin.
+ */
+
+namespace Drupal\Core\Annotation;
+
+use Drupal\Component\Annotation\Plugin;
+
+/**
+ * Declare queues holding items that need to be run periodically.
+ *
+ * While there can be only one hook_cron() process running at the same time,
+ * there can be any number of processes defined here running. Because of
+ * this, long running tasks are much better suited for this API. Items queued
+ * in hook_cron() might be processed in the same cron run if there are not many
+ * items in the queue, otherwise it might take several requests, which can be
+ * run in parallel.
+ *
+ * You can create queues, add items to them, claim them, etc without declaring
+ * the queue in this hook if you want, however, you need to take care of
+ * processing the items in the queue in that case.
+ *
+ * @see hook_cron()
+ * @see hook_queue_info_alter()
+ * @see \Drupal\Core\Queue\QueuePluginInterface
+ * @see \Drupal\Core\Queue\QueuePluginManager
+ *
+ * @Annotation
+ */
+class QueuePlugin extends Plugin {
+
+  /**
+   * The plugin ID.
+   *
+   * @var string
+   */
+  public $id;
+
+  /**
+   * The human-readable title of the plugin.
+   *
+   * @ingroup plugin_translatable
+   *
+   * @var \Drupal\Core\Annotation\Translation
+   */
+  public $title;
+
+  /**
+   * An associative array containing the optional key:
+   *   - time: (optional) How much time Drupal cron should spend on calling
+   *     this worker in seconds. Defaults to 15.
+   *
+   * @var array (optional)
+   */
+  public $cron;
+
+}
diff --git a/core/lib/Drupal/Core/Queue/QueuePluginBase.php b/core/lib/Drupal/Core/Queue/QueuePluginBase.php
new file mode 100644
index 0000000..c3b4593
--- /dev/null
+++ b/core/lib/Drupal/Core/Queue/QueuePluginBase.php
@@ -0,0 +1,17 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\Core\Queue\QueuePluginBase.
+ */
+
+namespace Drupal\Core\Queue;
+
+use Drupal\Component\Plugin\PluginBase;
+
+/**
+ * @todo.
+ */
+abstract class QueuePluginBase extends PluginBase implements QueuePluginInterface {
+
+}
diff --git a/core/lib/Drupal/Core/Queue/QueuePluginInterface.php b/core/lib/Drupal/Core/Queue/QueuePluginInterface.php
new file mode 100644
index 0000000..3e211d1
--- /dev/null
+++ b/core/lib/Drupal/Core/Queue/QueuePluginInterface.php
@@ -0,0 +1,22 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\Core\Queue\QueuePluginInterface.
+ */
+
+namespace Drupal\Core\Queue;
+
+use Drupal\Component\Plugin\PluginInspectionInterface;
+
+/**
+ * @todo.
+ */
+interface QueuePluginInterface extends PluginInspectionInterface {
+
+  /**
+   * @param mixed $data
+   */
+  public function processItem($data);
+
+}
diff --git a/core/lib/Drupal/Core/Queue/QueuePluginManager.php b/core/lib/Drupal/Core/Queue/QueuePluginManager.php
new file mode 100644
index 0000000..fa4a4d1
--- /dev/null
+++ b/core/lib/Drupal/Core/Queue/QueuePluginManager.php
@@ -0,0 +1,46 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Queue\QueuePluginManager.
+ */
+
+namespace Drupal\Core\Queue;
+
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Plugin\DefaultPluginManager;
+
+/**
+ * Defines the queue item manager.
+ */
+class QueuePluginManager extends DefaultPluginManager {
+
+  /**
+   * Constructs an QueuePluginManager object.
+   *
+   * @param \Traversable $namespaces
+   *   An object that implements \Traversable which contains the root paths
+   *   keyed by the corresponding namespace to look for plugin implementations.
+   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
+   *   The module handler.
+   */
+  public function __construct(\Traversable $namespaces, ModuleHandlerInterface $module_handler) {
+    parent::__construct('QueuePlugin', $namespaces, array(), 'Drupal\Core\Annotation\QueuePlugin');
+    $this->alterInfo($module_handler, 'queue_info');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function processDefinition(&$definition, $plugin_id) {
+    parent::processDefinition($definition, $plugin_id);
+
+    // Assign a default time if a cron is specified.
+    if (isset($definition['cron'])) {
+      $definition['cron'] += array(
+        'time' => 15,
+      );
+    }
+  }
+
+}
diff --git a/core/modules/aggregator/aggregator.module b/core/modules/aggregator/aggregator.module
index 416571d..f44e60a 100644
--- a/core/modules/aggregator/aggregator.module
+++ b/core/modules/aggregator/aggregator.module
@@ -287,20 +287,6 @@ function aggregator_cron() {
 }
 
 /**
- * Implements hook_queue_info().
- */
-function aggregator_queue_info() {
-  $queues['aggregator_feeds'] = array(
-    'title' => t('Aggregator refresh'),
-    'worker callback' => 'aggregator_refresh',
-    'cron' => array(
-      'time' => 60,
-    ),
-  );
-  return $queues;
-}
-
-/**
  * Adds/edits/deletes aggregator categories.
  *
  * @param $edit
diff --git a/core/modules/aggregator/lib/Drupal/aggregator/Plugin/QueuePlugin/AggregatorRefresh.php b/core/modules/aggregator/lib/Drupal/aggregator/Plugin/QueuePlugin/AggregatorRefresh.php
new file mode 100644
index 0000000..3523ef9
--- /dev/null
+++ b/core/modules/aggregator/lib/Drupal/aggregator/Plugin/QueuePlugin/AggregatorRefresh.php
@@ -0,0 +1,31 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\aggregator\Plugin\QueuePlugin\AggregatorRefresh.
+ */
+
+namespace Drupal\aggregator\Plugin\QueuePlugin;
+
+use Drupal\Core\Annotation\QueuePlugin;
+use Drupal\Core\Annotation\Translation;
+use Drupal\Core\Queue\QueuePluginBase;
+
+/**
+ * @QueuePlugin(
+ *   id = "aggregator_feeds",
+ *   title = @Translation("Aggregator refresh"),
+ *   cron = {"time" = 60}
+ * )
+ */
+class AggregatorRefresh extends QueuePluginBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function processItem($data) {
+    // @todo Convert once aggregator_refresh() is converted to a service.
+    aggregator_refresh($data);
+  }
+
+}
diff --git a/core/modules/locale/lib/Drupal/locale/Plugin/QueuePlugin/LocaleTranslation.php b/core/modules/locale/lib/Drupal/locale/Plugin/QueuePlugin/LocaleTranslation.php
new file mode 100644
index 0000000..0a52ece
--- /dev/null
+++ b/core/modules/locale/lib/Drupal/locale/Plugin/QueuePlugin/LocaleTranslation.php
@@ -0,0 +1,122 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\locale\Plugin\QueuePlugin\LocaleTranslation.
+ */
+
+namespace Drupal\locale\Plugin\QueuePlugin;
+
+use Drupal\Core\Annotation\QueuePlugin;
+use Drupal\Core\Annotation\Translation;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\Core\Queue\QueueInterface;
+use Drupal\Core\Queue\QueuePluginBase;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * @QueuePlugin(
+ *   id = "locale_translation",
+ *   title = @Translation("Update translations"),
+ *   cron = {"time" = 30}
+ * )
+ */
+class LocaleTranslation extends QueuePluginBase implements ContainerFactoryPluginInterface {
+
+  /**
+   * The module handler.
+   *
+   * @var \Drupal\Core\Extension\ModuleHandlerInterface
+   */
+  protected $moduleHandler;
+
+  /**
+   * The queue object.
+   *
+   * @var \Drupal\Core\Queue\QueueInterface
+   */
+  protected $queue;
+
+  /**
+   * Constructs a new LocaleTranslation object.
+   *
+   * @param array $configuration
+   *   A configuration array containing information about the plugin instance.
+   * @param string $plugin_id
+   *   The plugin_id for the plugin instance.
+   * @param array $plugin_definition
+   *   The plugin implementation definition.
+   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
+   *   The module handler.
+   * @param \Drupal\Core\Queue\QueueInterface $queue
+   *   The queue object.
+   */
+  public function __construct(array $configuration, $plugin_id, array $plugin_definition, ModuleHandlerInterface $module_handler, QueueInterface $queue) {
+    parent::__construct($configuration, $plugin_id, $plugin_definition);
+
+    $this->moduleHandler = $module_handler;
+    $this->queue = $queue;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, array $plugin_definition) {
+    return new static(
+      $configuration,
+      $plugin_id,
+      $plugin_definition,
+      $container->get('module_handler'),
+      $container->get('queue')->get('locale_translation', TRUE)
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   *
+   * The translation update functions executed here are batch operations which
+   * are also used in translation update batches. The batch functions may need
+   * to be executed multiple times to complete their task, typically this is the
+   * translation import function. When a batch function is not finished, a new
+   * queue task is created and added to the end of the queue. The batch context
+   * data is needed to continue the batch task is stored in the queue with the
+   * queue data.
+   */
+  public function processItem($data) {
+    $this->moduleHandler->loadInclude('locale', 'batch.inc');
+    list($function, $args) = $data;
+
+    // We execute batch operation functions here to check, download and import
+    // the translation files. Batch functions use a context variable as last
+    // argument which is passed by reference. When a batch operation is called
+    // for the first time a default batch context is created. When called
+    // iterative (usually the batch import function) the batch context is passed
+    // through via the queue and is part of the $data.
+    $last = count($args) - 1;
+    if (!is_array($args[$last]) || !isset($args[$last]['finished'])) {
+      $batch_context = array(
+        'sandbox'  => array(),
+        'results'  => array(),
+        'finished' => 1,
+        'message'  => '',
+      );
+    }
+    else {
+      $batch_context = $args[$last];
+      unset ($args[$last]);
+    }
+    $args = array_merge($args, array(&$batch_context));
+
+    // Call the batch operation function.
+    call_user_func_array($function, $args);
+
+    // If the batch operation is not finished we create a new queue task to
+    // continue the task. This is typically the translation import task.
+    if ($batch_context['finished'] < 1) {
+      unset($batch_context['strings']);
+      $this->queue->createItem(array($function, $args));
+    }
+  }
+
+}
diff --git a/core/modules/locale/locale.module b/core/modules/locale/locale.module
index 8d6ff20..ad82882 100644
--- a/core/modules/locale/locale.module
+++ b/core/modules/locale/locale.module
@@ -430,13 +430,12 @@ function locale_themes_disabled($themes) {
  * Implements hook_cron().
  *
  * @see drupal_cron_run()
- * @see locale_queue_info()
+ * @see \Drupal\locale\Plugin\QueuePlugin\LocaleTranslation
  */
 function locale_cron() {
   // Update translations only when an update frequency was set by the admin
   // and a translatable language was set.
-  // Update tasks are added to the queue here but processed by Drupal's cron
-  // using the cron worker defined in locale_queue_info().
+  // Update tasks are added to the queue here but processed by Drupal's cron.
   if ($frequency = config('locale.settings')->get('translation.update_interval_days') && locale_translatable_language_list()) {
     module_load_include('translation.inc', 'locale');
     locale_cron_fill_queue();
@@ -444,75 +443,6 @@ function locale_cron() {
 }
 
 /**
- * Implements hook_queue_info().
- */
-function locale_queue_info() {
-  $queues['locale_translation'] = array(
-    'title' => t('Update translations'),
-    'worker callback' => 'locale_translation_worker',
-    'cron' => array(
-      'time' => 30,
-    ),
-  );
-  return $queues;
-}
-
-/**
- * Callback: Executes interface translation queue tasks.
- *
- * The translation update functions executed here are batch operations which
- * are also used in translation update batches. The batch functions may need to
- * be executed multiple times to complete their task, typically this is the
- * translation import function. When a batch function is not finished, a new
- * queue task is created and added to the end of the queue. The batch context
- * data is needed to continue the batch task is stored in the queue with the
- * queue data.
- *
- * @param array $data
- * Queue data array containing:
- *   - Function name.
- *   - Array of function arguments. Optionally contains the batch context data.
- *
- * @see locale_queue_info()
- */
-function locale_translation_worker($data) {
-  module_load_include('batch.inc', 'locale');
-  list($function, $args) = $data;
-
-  // We execute batch operation functions here to check, download and import the
-  // translation files. Batch functions use a context variable as last argument
-  // which is passed by reference. When a batch operation is called for the
-  // first time a default batch context is created. When called iterative
-  // (usually the batch import function) the batch context is passed through via
-  // the queue and is part of the $data.
-  $last = count($args) - 1;
-  if (!is_array($args[$last]) || !isset($args[$last]['finished'])) {
-    $batch_context = array(
-      'sandbox'  => array(),
-      'results'  => array(),
-      'finished' => 1,
-      'message'  => '',
-    );
-  }
-  else {
-    $batch_context = $args[$last];
-    unset ($args[$last]);
-  }
-  $args = array_merge($args, array(&$batch_context));
-
-  // Call the batch operation function.
-  call_user_func_array($function, $args);
-
-  // If the batch operation is not finished we create a new queue task to
-  // continue the task. This is typically the translation import task.
-  if ($batch_context['finished'] < 1) {
-    unset($batch_context['strings']);
-    $queue = Drupal::queue('locale_translation', TRUE);
-    $queue->createItem(array($function, $args));
-  }
-}
-
-/**
  * Imports translations when new modules or themes are installed.
  *
  * This function will start a batch to import translations for the added
diff --git a/core/modules/system/system.api.php b/core/modules/system/system.api.php
index a8dcd13..8fa6434 100644
--- a/core/modules/system/system.api.php
+++ b/core/modules/system/system.api.php
@@ -107,8 +107,8 @@ function hook_admin_paths_alter(&$paths) {
  * Long-running tasks and tasks that could time out, such as retrieving remote
  * data, sending email, and intensive file tasks, should use the queue API
  * instead of executing the tasks directly. To do this, first define one or
- * more queues via hook_queue_info(). Then, add items that need to be
- * processed to the defined queues.
+ * more queues via a \Drupal\Core\Annotation\QueuePlugin plugin. Then, add items
+ * that need to be processed to the defined queues.
  */
 function hook_cron() {
   // Short-running operation example, not using a queue:
@@ -144,47 +144,6 @@ function hook_data_type_info_alter(&$data_types) {
 }
 
 /**
- * Declare queues holding items that need to be run periodically.
- *
- * While there can be only one hook_cron() process running at the same time,
- * there can be any number of processes defined here running. Because of
- * this, long running tasks are much better suited for this API. Items queued
- * in hook_cron() might be processed in the same cron run if there are not many
- * items in the queue, otherwise it might take several requests, which can be
- * run in parallel.
- *
- * You can create queues, add items to them, claim them, etc without declaring
- * the queue in this hook if you want, however, you need to take care of
- * processing the items in the queue in that case.
- *
- * @return
- *   An associative array where the key is the queue name and the value is
- *   again an associative array. Possible keys are:
- *   - 'worker callback': A PHP callable to call. It will be called
- *     with one argument, the item created via
- *     Drupal\Core\Queue\QueueInterface::createItem() in hook_cron().
- *   - 'cron': (optional) An associative array containing the optional key:
- *     - 'time': (optional) How much time Drupal cron should spend on calling
- *       this worker in seconds. Defaults to 15.
- *     If the cron key is not defined, the queue will not be processed by cron,
- *     and must be processed by other means.
- *
- * @see hook_cron()
- * @see hook_queue_info_alter()
- */
-function hook_queue_info() {
-  $queues['aggregator_feeds'] = array(
-    'title' => t('Aggregator refresh'),
-    'worker callback' => array('Drupal\my_module\MyClass', 'aggregatorRefresh'),
-    // Only needed if this queue should be processed by cron.
-    'cron' => array(
-      'time' => 60,
-    ),
-  );
-  return $queues;
-}
-
-/**
  * Alter cron queue information before cron runs.
  *
  * Called by drupal_cron_run() to allow modules to alter cron queue settings
@@ -193,7 +152,8 @@ function hook_queue_info() {
  * @param array $queues
  *   An array of cron queue information.
  *
- * @see hook_queue_info()
+ * @see \Drupal\Core\QueuePlugin\QueuePluginInterface
+ * @see \Drupal\Core\Annotation\QueuePlugin
  * @see drupal_cron_run()
  */
 function hook_queue_info_alter(&$queues) {
