diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc
index 0dd19d5..2bd343f 100644
--- a/core/includes/bootstrap.inc
+++ b/core/includes/bootstrap.inc
@@ -2477,6 +2477,14 @@ function drupal_container(Container $new_container = NULL, $rebuild = FALSE) {
     $container
       ->register('keyvalue.database', 'Drupal\Core\KeyValueStore\KeyValueDatabaseFactory')
       ->addArgument(new Reference('database'));
+
+    // Register the expirable KeyValueStore factory.
+    $container
+      ->register('keyvalue.expirable', 'Drupal\Core\KeyValueStore\KeyValueExpirableFactory')
+      ->addArgument(new Reference('service_container'));
+    $container
+      ->register('keyvalue.expirable.database', 'Drupal\Core\KeyValueStore\KeyValueDatabaseExpirableFactory')
+      ->addArgument(new Reference('database'));
   }
   return $container;
 }
diff --git a/core/includes/form.inc b/core/includes/form.inc
index 6956f04..daa1506 100644
--- a/core/includes/form.inc
+++ b/core/includes/form.inc
@@ -504,9 +504,10 @@ function form_get_cache($form_build_id, &$form_state) {
 
     global $user;
     if ((isset($form['#cache_token']) && drupal_valid_token($form['#cache_token'])) || (!isset($form['#cache_token']) && !$user->uid)) {
-      if ($cached = cache('form')->get('form_state_' . $form_build_id)) {
+      $stored_form_state = drupal_container()->get('keyvalue.expirable')->get('form_state')->get($form_build_id);
+      if ($stored_form_state) {
         // Re-populate $form_state for subsequent rebuilds.
-        $form_state = $cached->data + $form_state;
+        $form_state = $stored_form_state + $form_state;
 
         // If the original form is contained in include files, load the files.
         // @see form_load_include()
@@ -543,7 +544,7 @@ function form_set_cache($form_build_id, $form, $form_state) {
 
   // Cache form state.
   if ($data = array_diff_key($form_state, array_flip(form_state_keys_no_cache()))) {
-    cache('form')->set('form_state_' . $form_build_id, $data, REQUEST_TIME + $expire);
+    drupal_container()->get('keyvalue.expirable')->get('form_state')->setWithExpire($form_build_id, $data, $expire);
   }
 }
 
@@ -882,7 +883,7 @@ function drupal_process_form($form_id, &$form, &$form_state) {
       $config = config('system.performance');
       if (!$config->get('cache.page.enabled') && !empty($form_state['values']['form_build_id'])) {
         cache('form')->delete('form_' . $form_state['values']['form_build_id']);
-        cache('form')->delete('form_state_' . $form_state['values']['form_build_id']);
+        drupal_container()->get('keyvalue.expirable')->get('form_state')->delete($form_state['values']['form_build_id']);
       }
 
       // If batches were set in the submit handlers, we process them now,
diff --git a/core/lib/Drupal/Core/KeyValueStore/DatabaseStorageExpirable.php b/core/lib/Drupal/Core/KeyValueStore/DatabaseStorageExpirable.php
index e989470..76ef7fb 100644
--- a/core/lib/Drupal/Core/KeyValueStore/DatabaseStorageExpirable.php
+++ b/core/lib/Drupal/Core/KeyValueStore/DatabaseStorageExpirable.php
@@ -59,7 +59,7 @@ public function __construct($collection, Connection $connection, $table = 'key_v
    * Performs garbage collection as needed when destructing the storage object.
    */
   public function __destruct() {
-    if ($this->needsGarbageCollection) {
+    if ($this->needsGarbageCollection && $this->connection->schema()->tableExists($this->table)) {
       $this->garbageCollection();
     }
   }
