diff --git a/batch_example/batch_example.info.yml b/batch_example/batch_example.info.yml
new file mode 100644
index 0000000..5eb2939
--- /dev/null
+++ b/batch_example/batch_example.info.yml
@@ -0,0 +1,8 @@
+name: Batch Example
+type: module
+description: An example outlining how a module can define batch operations.
+package: Example modules
+core: 8.x
+dependencies:
+  - examples
+  - toolbar
diff --git a/batch_example/batch_example.install b/batch_example/batch_example.install
new file mode 100644
index 0000000..857b4d7
--- /dev/null
+++ b/batch_example/batch_example.install
@@ -0,0 +1,83 @@
+<?php
+
+/**
+ * @file
+ * Install, update, and uninstall functions for the batch_example module.
+ */
+
+/**
+ * Example of batch-driven update function.
+ *
+ * Because some update functions may require the batch API, the $sandbox
+ * provides a place to store state. When $sandbox['#finished'] == TRUE,
+ * calls to this update function are completed.
+ *
+ * The $sandbox param provides a way to store data during multiple invocations.
+ * When the $sandbox['#finished'] == 1, execution is complete.
+ *
+ * This dummy 'update' function changes no state in the system. It simply
+ * loads each node.
+ *
+ * To make this update function run again and again, execute the query
+ * "update system set schema_version = 0 where name = 'batch_example';"
+ * and then run /update.php.
+ *
+ * @ingroup batch_example
+ */
+function batch_example_update_8001(&$sandbox) {
+  // Use the sandbox at your convenience to store the information needed
+  // to track progression between successive calls to the function.
+  if (!isset($sandbox['progress'])) {
+    // The count of nodes visited so far.
+    $sandbox['progress'] = 0;
+    // Total nodes that must be visited.
+    $sandbox['max'] = db_query('SELECT COUNT(nid) FROM {node}')->fetchField();
+    // A place to store messages during the run.
+    $sandbox['messages'] = [];
+    // Last node read via the query.
+    $sandbox['current_node'] = -1;
+  }
+
+  // Process nodes by groups of 10 (arbitrary value).
+  // When a group is processed, the batch update engine determines
+  // whether it should continue processing in the same request or provide
+  // progress feedback to the user and wait for the next request.
+  $limit = 10;
+
+  // Retrieve the next group of nids.
+  $query = db_select('node', 'n');
+  $query->fields('n', ['nid']);
+  $result = $query
+    ->where('n.nid > :nid', [':nid' => $sandbox['current_node']])
+    ->range(0, $limit)
+    ->orderBy('n.nid', 'ASC')
+    ->execute();
+  foreach ($result as $row) {
+    // Here we actually perform a dummy 'update' on the current node.
+    $node = db_query('SELECT nid FROM {node} WHERE nid = :nid', [':nid' => $row->nid])->fetchField();
+
+    // Update our progress information.
+    $sandbox['progress']++;
+    $sandbox['current_node'] = $row->nid;
+  }
+
+  // Set the "finished" status, to tell batch engine whether this function
+  // needs to run again. If you set a float, this will indicate the progress
+  // of the batch so the progress bar will update.
+  $sandbox['#finished'] = ($sandbox['progress'] >= $sandbox['max']) ? TRUE : ($sandbox['progress'] / $sandbox['max']);
+
+  // Set up a per-run message; Make a copy of $sandbox so we can change it.
+  // This is simply a debugging stanza to illustrate how to capture status
+  // from each pass through hook_update_N().
+  $sandbox_status = $sandbox;
+  // Don't want them in the output.
+  unset($sandbox_status['messages']);
+  $sandbox['messages'][] = t('$sandbox=') . print_r($sandbox_status, TRUE);
+
+  if ($sandbox['#finished']) {
+    // hook_update_N() may optionally return a string which will be displayed
+    // to the user.
+    $final_message = '<ul><li>' . implode('</li><li>', $sandbox['messages']) . "</li></ul>";
+    return t('The batch_example demonstration update did what it was supposed to do: @message', ['@message' => $final_message]);
+  }
+}
diff --git a/batch_example/batch_example.links.menu.yml b/batch_example/batch_example.links.menu.yml
new file mode 100644
index 0000000..272cebb
--- /dev/null
+++ b/batch_example/batch_example.links.menu.yml
@@ -0,0 +1,5 @@
+# Define default links for this module.
+batch_example.form:
+  title: Batch API Examples
+  description: Batch examples using Drupal Batch API.
+  route_name: batch_example.form
diff --git a/batch_example/batch_example.module b/batch_example/batch_example.module
new file mode 100644
index 0000000..c7873ed
--- /dev/null
+++ b/batch_example/batch_example.module
@@ -0,0 +1,133 @@
+<?php
+
+/**
+ * @file
+ * Outlines how a module can use the Batch API.
+ */
+
+/**
+ * @defgroup batch_example Example: Batch API
+ * @ingroup examples
+ * @{
+ * Outlines how a module can use the Batch API.
+ *
+ * Batches allow heavy processing to be spread out over several page
+ * requests, ensuring that the processing does not get interrupted
+ * because of a PHP timeout, while allowing the user to receive feedback
+ * on the progress of the ongoing operations. It also can reduce out of memory
+ * situations.
+ *
+ * The @link batch_example.install .install file @endlink also shows how the
+ * Batch API can be used to handle long-running hook_update_N() functions.
+ *
+ * Two harmless batches are defined:
+ * - batch 1: Load the node with the lowest nid 100 times.
+ * - batch 2: Load all nodes, 20 times and uses a progressive op, loading nodes
+ *   by groups of 5.
+ *
+ * @see batch
+ */
+
+/**
+ * Batch operation for batch 1: one at a time.
+ *
+ * This is the function that is called on each operation in batch 1.
+ */
+function batch_example_op_1($id, $operation_details, &$context) {
+  // Simulate long process by waiting 1/50th of a second.
+  usleep(20000);
+
+  // Store some results for post-processing in the 'finished' callback.
+  // The contents of 'results' will be available as $results in the
+  // 'finished' function (in this example, batch_example_finished()).
+  $context['results'][] = $id;
+
+  // Optional message displayed under the progressbar.
+  $context['message'] = t('Running Batch "@id" @details',
+    ['@id' => $id, '@details' => $operation_details]
+  );
+}
+
+/**
+ * Batch operation for batch 2: five at a time.
+ *
+ * This is the function that is called on each operation in batch 2.
+ *
+ * After each group of 5 control is returned to the batch API for later
+ * continuation.
+ */
+function batch_example_op_2($operation_details, &$context) {
+  // Use the $context['sandbox'] at your convenience to store the
+  // information needed to track progression between successive calls.
+  if (empty($context['sandbox'])) {
+    $context['sandbox'] = [];
+    $context['sandbox']['progress'] = 0;
+    $context['sandbox']['current_node'] = 0;
+
+    // Save node count for the termination message.
+    $context['sandbox']['max'] = 30;
+  }
+
+  // Process in groups of 5 (arbitrary value).
+  // When a group of five is processed, the batch update engine determines
+  // whether it should continue processing in the same request or provide
+  // progress feedback to the user and wait for the next request.
+  // That way even though we're already processing at the operation level
+  // the operation itself is interruptible.
+  $limit = 5;
+
+  // Retrieve the next group.
+  $result = range($context['sandbox']['current_node'] + 1, $context['sandbox']['current_node'] + 1 + $limit);
+
+  foreach ($result as $row) {
+    // Here we actually perform our dummy 'processing' on the current node.
+    usleep(20000);
+
+    // Store some results for post-processing in the 'finished' callback.
+    // The contents of 'results' will be available as $results in the
+    // 'finished' function (in this example, batch_example_finished()).
+    $context['results'][] = $row . ' ' . $operation_details;
+
+    // Update our progress information.
+    $context['sandbox']['progress']++;
+    $context['sandbox']['current_node'] = $row;
+    $context['message'] = t('Running Batch "@id" @details',
+      ['@id' => $row, '@details' => $operation_details]
+    );
+  }
+
+  // Inform the batch engine that we are not finished,
+  // and provide an estimation of the completion level we reached.
+  if ($context['sandbox']['progress'] != $context['sandbox']['max']) {
+    $context['finished'] = ($context['sandbox']['progress'] >= $context['sandbox']['max']);
+  }
+}
+
+/**
+ * Batch 'finished' callback used by both batch 1 and batch 2.
+ */
+function batch_example_finished($success, $results, $operations) {
+  if ($success) {
+    // Here we could do something meaningful with the results.
+    // We just display the number of nodes we processed...
+    drupal_set_message(t('@count results processed.', ['@count' => count($results)]));
+    drupal_set_message(t('The final result was "%final"', ['%final' => end($results)]));
+  }
+  else {
+    // An error occurred.
+    // $operations contains the operations that remained unprocessed.
+    $error_operation = reset($operations);
+    drupal_set_message(
+      t('An error occurred while processing @operation with arguments : @args',
+        [
+          '@operation' => $error_operation[0],
+          '@args' => print_r($error_operation[0], TRUE),
+        ]
+      )
+    );
+  }
+}
+
+/**
+ * @} End of "defgroup batch_example".
+ */
diff --git a/batch_example/batch_example.routing.yml b/batch_example/batch_example.routing.yml
new file mode 100644
index 0000000..a3450a7
--- /dev/null
+++ b/batch_example/batch_example.routing.yml
@@ -0,0 +1,7 @@
+batch_example.form:
+  path: 'examples/batch_example'
+  defaults:
+    _form: '\Drupal\batch_example\Form\BatchExampleForm'
+    _title: 'Demo of Batch processing'
+  requirements:
+    _permission: 'access content'
diff --git a/batch_example/src/Form/BatchExampleForm.php b/batch_example/src/Form/BatchExampleForm.php
new file mode 100644
index 0000000..476fdda
--- /dev/null
+++ b/batch_example/src/Form/BatchExampleForm.php
@@ -0,0 +1,148 @@
+<?php
+
+namespace Drupal\batch_example\Form;
+
+use Drupal\Core\Form\FormBase;
+use Drupal\Core\Form\FormStateInterface;
+
+/**
+ * Form with examples on how to use cache.
+ */
+class BatchExampleForm extends FormBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'batch_example_form';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state) {
+
+    $form['description'] = [
+      '#type' => 'markup',
+      '#markup' => t('This example offers two different batches. The first does 1000 identical operations, each completed in on run; the second does 20 operations, but each takes more than one run to operate if there are more than 5 nodes.'),
+    ];
+    $form['batch'] = [
+      '#type' => 'select',
+      '#title' => 'Choose batch',
+      '#options' => [
+        'batch_1' => t('batch 1 - 1000 operations'),
+        'batch_2' => t('batch 2 - 20 operations.'),
+      ],
+    ];
+    $form['submit'] = [
+      '#type' => 'submit',
+      '#value' => 'Go',
+    ];
+
+    return $form;
+
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    // Gather our form value.
+    $value = $form_state->getValues()['batch'];
+    // Set the batch, using convenience methods.
+    $batch = [];
+    switch ($value) {
+      case 'batch_1':
+        $batch = $this->generateBatch1();
+        break;
+
+      case 'batch_2':
+        $batch = $this->generateBatch2();
+        break;
+    }
+    batch_set($batch);
+  }
+
+  /**
+   * Generate Batch 1.
+   *
+   * Batch 1 will process one item at a time.
+   *
+   * This creates an operations array defining what batch 1 should do, including
+   * what it should do when it's finished. In this case, each operation is the
+   * same and by chance even has the same $nid to operate on, but we could have
+   * a mix of different types of operations in the operations array.
+   */
+  public function generateBatch1() {
+    $num_operations = 1000;
+    drupal_set_message(t('Creating an array of @num operations', ['@num' => $num_operations]));
+
+    $operations = [];
+    // Set up an operations array with 1000 elements, each doing function
+    // batch_example_op_1.
+    // Each operation in the operations array means at least one new HTTP
+    // request, running Drupal from scratch to accomplish the operation. If the
+    // operation returns with $context['finished'] != TRUE, then it will be
+    // called again.
+    // In this example, $context['finished'] is always TRUE.
+    for ($i = 0; $i < $num_operations; $i++) {
+      // Each operation is an array consisting of
+      // - The function to call.
+      // - An array of arguments to that function.
+      $operations[] = [
+        'batch_example_op_1',
+        [
+          $i + 1,
+          t('(Operation @operation)', ['@operation' => $i]),
+        ],
+      ];
+    }
+    $batch = [
+      'title' => t('Creating an array of @num operations', ['@num' => $num_operations]),
+      'operations' => $operations,
+      'finished' => 'batch_example_finished',
+    ];
+    return $batch;
+  }
+
+  /**
+   * Generate Batch 2.
+   *
+   * Batch 2 will process five items at a time.
+   *
+   * This creates an operations array defining what batch 2 should do, including
+   * what it should do when it's finished. In this case, each operation is the
+   * same and by chance even has the same $nid to operate on, but we could have
+   * a mix of different types of operations in the operations array.
+   */
+  public function generateBatch2() {
+    $num_operations = 20;
+
+    $operations = [];
+    // 20 operations, each one loads all nodes.
+    for ($i = 0; $i < $num_operations; $i++) {
+      $operations[] = [
+        'batch_example_op_2',
+        [t('(Operation @operation)', ['@operation' => $i])],
+      ];
+    }
+    $batch = [
+      'operations' => $operations,
+      'finished' => 'batch_example_finished',
+      // @current, @remaining, @total, @percentage, @estimate and @elapsed.
+      // These placeholders are replaced with actual values in _batch_process(),
+      // using strtr() instead of t(). The values are determined based on the
+      // number of operations in the 'operations' array (above), NOT by the
+      // number of nodes that will be processed. In this example, there are 20
+      // operations, so @total will always be 20, even though there are multiple
+      // nodes per operation.
+      // Defaults to t('Completed @current of @total.').
+      'title' => t('Processing batch 2'),
+      'init_message' => t('Batch 2 is starting.'),
+      'progress_message' => t('Processed @current out of @total.'),
+      'error_message' => t('Batch 2 has encountered an error.'),
+    ];
+    return $batch;
+  }
+
+}
diff --git a/batch_example/tests/src/functional/BatchExampleWebTest.php b/batch_example/tests/src/functional/BatchExampleWebTest.php
new file mode 100644
index 0000000..bf83616
--- /dev/null
+++ b/batch_example/tests/src/functional/BatchExampleWebTest.php
@@ -0,0 +1,40 @@
+<?php
+
+namespace Drupal\Tests\batch_example\Functional;
+
+use Drupal\Tests\BrowserTestBase;
+
+/**
+ * Functional tests for the Batch Example module.
+ *
+ * @group fapi_example
+ *
+ * @ingroup batch_example
+ */
+class BatchExampleWebTest extends BrowserTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  static public $modules = ['node', 'batch_example'];
+
+  /**
+   * Login user and test both batch examples.
+   */
+  public function testBatchExampleBasic() {
+    // Login the admin user.
+    $web_user = $this->drupalCreateUser(['access content']);
+    $this->drupalLogin($web_user);
+
+    // Launch Batch 1.
+    $this->drupalPostForm('examples/batch_example', ['batch' => 'batch_1'], t('Go'));
+    // Check that 1000 operations were performed.
+    $this->assertText('1000 results processed');
+
+    // Launch Batch 2.
+    $this->drupalPostForm('examples/batch_example', ['batch' => 'batch_2'], t('Go'));
+    // Check that 600 operations were performed.
+    $this->assertText('600 results processed');
+  }
+
+}
diff --git a/examples.module b/examples.module
index d5676fa..8f732da 100644
--- a/examples.module
+++ b/examples.module
@@ -35,6 +35,7 @@ function examples_toolbar() {
   // First, build an array of all example modules and their routes.
   // We resort to this hard-coded way so as not to muck up each example.
   $examples = [
+    'batch_example' => 'batch_example.form',
     'block_example' => 'block_example.description',
     'cache_example' => 'cache_example.description',
     'config_entity_example' => 'entity.robot.list',
