diff --git a/core/includes/ajax.inc b/core/includes/ajax.inc
index df3ce78..cb85967 100644
--- a/core/includes/ajax.inc
+++ b/core/includes/ajax.inc
@@ -101,7 +101,7 @@
  * @code
  * $format_value = \Drupal\Component\Utility\NestedArray::getValue(
  *   $form_state->getValues(),
- *   $form_state['triggering_element']['#array_parents']);
+ *   $form_state->getTriggeringElement()['#array_parents']);
  * @endcode
  *
  * Once you have processed the input, you have your choice of returning HTML
diff --git a/core/includes/form.inc b/core/includes/form.inc
index 7d5875e..7cca4b6 100644
--- a/core/includes/form.inc
+++ b/core/includes/form.inc
@@ -148,7 +148,7 @@ function form_state_values_clean(FormStateInterface $form_state) {
   // Remove button values.
   // form_builder() collects all button elements in a form. We remove the button
   // value separately for each button element.
-  foreach ($form_state['buttons'] as $button) {
+  foreach ($form_state->getButtons() as $button) {
     // Remove this button's value from the submitted form values by finding
     // the value corresponding to this button.
     // We iterate over the #parents of this button and move a reference to
diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc
index f49b521..ba0108c 100644
--- a/core/includes/install.core.inc
+++ b/core/includes/install.core.inc
@@ -806,18 +806,15 @@ function install_tasks_to_display($install_state) {
 function install_get_form($form_id, array &$install_state) {
   // Ensure the form will not redirect, since install_run_tasks() uses a custom
   // redirection logic.
-  $form_state = new FormState(array(
-    'build_info' => array(
-      'args' => array(&$install_state),
-    ),
-    'no_redirect' => TRUE,
-  ));
+  $form_state = (new FormState())
+    ->addBuildInfo('args', [&$install_state])
+    ->disableRedirect();
   $form_builder = \Drupal::formBuilder();
   if ($install_state['interactive']) {
     $form = $form_builder->buildForm($form_id, $form_state);
     // If the form submission was not successful, the form needs to be rendered,
     // which means the task is not complete yet.
-    if (empty($form_state['executed'])) {
+    if (!$form_state->isExecuted()) {
       $install_state['task_not_complete'] = TRUE;
       return $form;
     }
@@ -827,7 +824,7 @@ function install_get_form($form_id, array &$install_state) {
     // values taken from the installation state.
     $install_form_id = $form_builder->getFormId($form_id, $form_state);
     if (!empty($install_state['forms'][$install_form_id])) {
-      $form_state->set('values', $install_state['forms'][$install_form_id]);
+      $form_state->setValues($install_state['forms'][$install_form_id]);
     }
     $form_builder->submitForm($form_id, $form_state);
 
diff --git a/core/lib/Drupal/Core/Block/BlockBase.php b/core/lib/Drupal/Core/Block/BlockBase.php
index 7d66cf4..c9db77a 100644
--- a/core/lib/Drupal/Core/Block/BlockBase.php
+++ b/core/lib/Drupal/Core/Block/BlockBase.php
@@ -356,9 +356,8 @@ public function validateConfigurationForm(array &$form, FormStateInterface $form
 
     foreach ($this->getVisibilityConditions() as $condition_id => $condition) {
       // Allow the condition to validate the form.
-      $condition_values = new FormState(array(
-        'values' => $form_state->getValue(array('visibility', $condition_id)),
-      ));
+      $condition_values = (new FormState())
+        ->setValues($form_state->getValue(['visibility', $condition_id]));
       $condition->validateConfigurationForm($form, $condition_values);
       // Update the original form values.
       $form_state->setValue(array('visibility', $condition_id), $condition_values['values']);
@@ -389,9 +388,8 @@ public function submitConfigurationForm(array &$form, FormStateInterface $form_s
       $this->configuration['cache'] = $form_state->getValue('cache');
       foreach ($this->getVisibilityConditions() as $condition_id => $condition) {
         // Allow the condition to submit the form.
-        $condition_values = new FormState(array(
-          'values' => $form_state->getValue(array('visibility', $condition_id)),
-        ));
+        $condition_values = (new FormState())
+          ->setValues($form_state->getValue(['visibility', $condition_id]));
         $condition->submitConfigurationForm($form, $condition_values);
         // Update the original form values.
         $form_state->setValue(array('visibility', $condition_id), $condition_values['values']);
diff --git a/core/lib/Drupal/Core/Controller/FormController.php b/core/lib/Drupal/Core/Controller/FormController.php
index 95e3eb5..a9b7a08 100644
--- a/core/lib/Drupal/Core/Controller/FormController.php
+++ b/core/lib/Drupal/Core/Controller/FormController.php
@@ -76,7 +76,7 @@ public function getContentResult(Request $request) {
 
     // Remove $form and $form_state from the arguments, and re-index them.
     unset($args[0], $args[1]);
-    $form_state['build_info']['args'] = array_values($args);
+    $form_state->addBuildInfo('args', array_values($args));
 
     return $this->formBuilder->buildForm($form_object, $form_state);
   }
diff --git a/core/lib/Drupal/Core/Datetime/Element/Datetime.php b/core/lib/Drupal/Core/Datetime/Element/Datetime.php
index 583372b..df0847e 100644
--- a/core/lib/Drupal/Core/Datetime/Element/Datetime.php
+++ b/core/lib/Drupal/Core/Datetime/Element/Datetime.php
@@ -330,7 +330,7 @@ public static function processDatetime(&$element, FormStateInterface $form_state
    */
   public static function validateDatetime(&$element, FormStateInterface $form_state, &$complete_form) {
     $input_exists = FALSE;
-    $input = NestedArray::getValue($form_state['values'], $element['#parents'], $input_exists);
+    $input = NestedArray::getValue($form_state->getValues(), $element['#parents'], $input_exists);
     if ($input_exists) {
 
       $title = !empty($element['#title']) ? $element['#title'] : '';
diff --git a/core/lib/Drupal/Core/Entity/ContentEntityForm.php b/core/lib/Drupal/Core/Entity/ContentEntityForm.php
index 87883bd..d65c8c9 100644
--- a/core/lib/Drupal/Core/Entity/ContentEntityForm.php
+++ b/core/lib/Drupal/Core/Entity/ContentEntityForm.php
@@ -64,7 +64,7 @@ public function validate(array $form, FormStateInterface $form_state) {
 
     // @todo Remove this.
     // Execute legacy global validation handlers.
-    unset($form_state['validate_handlers']);
+    $form_state->setValidateHandlers(NULL);
     form_execute_handlers('validate', $form, $form_state);
   }
 
@@ -87,13 +87,13 @@ protected function init(FormStateInterface $form_state) {
    * {@inheritdoc}
    */
   public function getFormLangcode(FormStateInterface $form_state) {
-    if (empty($form_state['langcode'])) {
+    if (!$form_state->has('langcode')) {
       // Imply a 'view' operation to ensure users edit entities in the same
       // language they are displayed. This allows to keep contextual editing
       // working also for multilingual entities.
-      $form_state['langcode'] = $this->entityManager->getTranslationFromContext($this->entity)->language()->id;
+      $form_state->set('langcode', $this->entityManager->getTranslationFromContext($this->entity)->language()->id);
     }
-    return $form_state['langcode'];
+    return $form_state->get('langcode');
   }
 
   /**
@@ -124,14 +124,14 @@ protected function copyFormValuesToEntity(EntityInterface $entity, array $form,
    * {@inheritdoc}
    */
   public function getFormDisplay(FormStateInterface $form_state) {
-    return isset($form_state['form_display']) ? $form_state['form_display'] : NULL;
+    return $form_state->get('form_display');
   }
 
   /**
    * {@inheritdoc}
    */
   public function setFormDisplay(EntityFormDisplayInterface $form_display, FormStateInterface $form_state) {
-    $form_state['form_display'] = $form_display;
+    $form_state->set('form_display', $form_display);
     return $this;
   }
 
diff --git a/core/lib/Drupal/Core/Entity/EntityForm.php b/core/lib/Drupal/Core/Entity/EntityForm.php
index 41f1c30..dd3e9a1 100644
--- a/core/lib/Drupal/Core/Entity/EntityForm.php
+++ b/core/lib/Drupal/Core/Entity/EntityForm.php
@@ -89,7 +89,7 @@ public function getFormId() {
   public function buildForm(array $form, FormStateInterface $form_state) {
     // During the initial form build, add this form object to the form state and
     // allow for initial preparation before form building and processing.
-    if (!isset($form_state['entity_form_initialized'])) {
+    if (!$form_state->has('entity_form_initialized')) {
       $this->init($form_state);
     }
 
@@ -116,7 +116,7 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
    */
   protected function init(FormStateInterface $form_state) {
     // Flag that this form has been initialized.
-    $form_state['entity_form_initialized'] = TRUE;
+    $form_state->set('entity_form_initialized', TRUE);
 
     // Prepare the entity to be presented in the entity form.
     $this->prepareEntity();
@@ -239,7 +239,7 @@ public function validate(array $form, FormStateInterface $form_state) {
     $this->updateFormLangcode($form_state);
     // @todo Remove this.
     // Execute legacy global validation handlers.
-    unset($form_state['validate_handlers']);
+    $form_state->setValidateHandlers(NULL);
     form_execute_handlers('validate', $form, $form_state);
   }
 
@@ -300,7 +300,7 @@ public function isDefaultFormLangcode(FormStateInterface $form_state) {
   protected function updateFormLangcode(FormStateInterface $form_state) {
     // Update the form language as it might have changed.
     if ($form_state->hasValue('langcode') && $this->isDefaultFormLangcode($form_state)) {
-      $form_state['langcode'] = $form_state->getValue('langcode');
+      $form_state->set('langcode', $form_state->getValue('langcode'));
     }
   }
 
diff --git a/core/lib/Drupal/Core/Entity/EntityFormBuilder.php b/core/lib/Drupal/Core/Entity/EntityFormBuilder.php
index 3a825be..e12d279 100644
--- a/core/lib/Drupal/Core/Entity/EntityFormBuilder.php
+++ b/core/lib/Drupal/Core/Entity/EntityFormBuilder.php
@@ -49,11 +49,7 @@ public function getForm(EntityInterface $entity, $operation = 'default', array $
     $form_object = $this->entityManager->getFormObject($entity->getEntityTypeId(), $operation);
     $form_object->setEntity($entity);
 
-    $form_state = new FormState($form_state_additions);
-    $form_state['build_info']['callback_object'] = $form_object;
-    $form_state['build_info']['base_form_id'] = $form_object->getBaseFormID();
-    $form_state['build_info'] += array('args' => array());
-
+    $form_state = (new FormState())->setFormState($form_state_additions);
     return $this->formBuilder->buildForm($form_object, $form_state);
   }
 
diff --git a/core/lib/Drupal/Core/Entity/EntityFormBuilderInterface.php b/core/lib/Drupal/Core/Entity/EntityFormBuilderInterface.php
index 584595f..889ed6b 100644
--- a/core/lib/Drupal/Core/Entity/EntityFormBuilderInterface.php
+++ b/core/lib/Drupal/Core/Entity/EntityFormBuilderInterface.php
@@ -30,8 +30,8 @@
    *   langcode. Defaults to an empty array.
    *
    * @code
-   *   $form_state['langcode'] = $langcode;
-   *   $form = \Drupal::service('entity.form_builder')->getForm($entity, 'default', $form_state);
+   *   $form_state_additions['langcode'] = $langcode;
+   *   $form = \Drupal::service('entity.form_builder')->getForm($entity, 'default', $form_state_additions);
    * @endcode
    *
    * @return array
diff --git a/core/lib/Drupal/Core/Field/FieldItemList.php b/core/lib/Drupal/Core/Field/FieldItemList.php
index 7f0890d..c77d1d9 100644
--- a/core/lib/Drupal/Core/Field/FieldItemList.php
+++ b/core/lib/Drupal/Core/Field/FieldItemList.php
@@ -356,7 +356,7 @@ public static function processDefaultValue($default_value, ContentEntityInterfac
    *   A Widget object.
    */
   protected function defaultValueWidget(FormStateInterface $form_state) {
-    if (!isset($form_state['default_value_widget'])) {
+    if (!$form_state->has('default_value_widget')) {
       $entity = $this->getEntity();
 
       // Force a non-required widget.
@@ -371,10 +371,10 @@ protected function defaultValueWidget(FormStateInterface $form_state) {
         $widget = \Drupal::service('plugin.manager.field.widget')->getInstance(array('field_definition' => $this->getFieldDefinition()));
       }
 
-      $form_state['default_value_widget'] = $widget;
+      $form_state->set('default_value_widget', $widget);
     }
 
-    return $form_state['default_value_widget'];
+    return $form_state->get('default_value_widget');
   }
 
 }
diff --git a/core/lib/Drupal/Core/Field/WidgetBase.php b/core/lib/Drupal/Core/Field/WidgetBase.php
index 50f67a5..aa285e2 100644
--- a/core/lib/Drupal/Core/Field/WidgetBase.php
+++ b/core/lib/Drupal/Core/Field/WidgetBase.php
@@ -207,8 +207,7 @@ protected function formMultipleElements(FieldItemListInterface $items, array &$f
       );
 
       // Add 'add more' button, if not working with a programmed form.
-      if ($cardinality == FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED && empty($form_state['programmed'])) {
-
+      if ($cardinality == FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED && !$form_state->isProgrammed()) {
         $id_prefix = implode('-', array_merge($parents, array($field_name)));
         $wrapper_id = drupal_html_id($id_prefix . '-add-more-wrapper');
         $elements['#prefix'] = '<div id="' . $wrapper_id . '">';
@@ -254,7 +253,7 @@ public static function afterBuild(array $element, FormStateInterface $form_state
    * Submission handler for the "Add another item" button.
    */
   public static function addMoreSubmit(array $form, FormStateInterface $form_state) {
-    $button = $form_state['triggering_element'];
+    $button = $form_state->getTriggeringElement();
 
     // Go one level up in the form, to the widgets container.
     $element = NestedArray::getValue($form, array_slice($button['#array_parents'], 0, -1));
@@ -266,7 +265,7 @@ public static function addMoreSubmit(array $form, FormStateInterface $form_state
     $field_state['items_count']++;
     static::setWidgetState($parents, $field_name, $form_state, $field_state);
 
-    $form_state['rebuild'] = TRUE;
+    $form_state->setRebuild();
   }
 
   /**
@@ -276,7 +275,7 @@ public static function addMoreSubmit(array $form, FormStateInterface $form_state
    * by the form submission.
    */
   public static function addMoreAjax(array $form, FormStateInterface $form_state) {
-    $button = $form_state['triggering_element'];
+    $button = $form_state->getTriggeringElement();
 
     // Go one level up in the form, to the widgets container.
     $element = NestedArray::getValue($form, array_slice($button['#array_parents'], 0, -1));
@@ -439,14 +438,14 @@ public function flagErrors(FieldItemListInterface $items, ConstraintViolationLis
    * {@inheritdoc}
    */
   public static function getWidgetState(array $parents, $field_name, FormStateInterface $form_state) {
-    return NestedArray::getValue($form_state['storage'], static::getWidgetStateParents($parents, $field_name));
+    return NestedArray::getValue($form_state->getStorage(), static::getWidgetStateParents($parents, $field_name));
   }
 
   /**
    * {@inheritdoc}
    */
   public static function setWidgetState(array $parents, $field_name, FormStateInterface $form_state, array $field_state) {
-    NestedArray::setValue($form_state['storage'], static::getWidgetStateParents($parents, $field_name), $field_state);
+    NestedArray::setValue($form_state->getStorage(), static::getWidgetStateParents($parents, $field_name), $field_state);
   }
 
   /**
diff --git a/core/lib/Drupal/Core/FileTransfer/Form/FileTransferAuthorizeForm.php b/core/lib/Drupal/Core/FileTransfer/Form/FileTransferAuthorizeForm.php
index dfc2a8f..e163c04 100644
--- a/core/lib/Drupal/Core/FileTransfer/Form/FileTransferAuthorizeForm.php
+++ b/core/lib/Drupal/Core/FileTransfer/Form/FileTransferAuthorizeForm.php
@@ -140,7 +140,7 @@ public function buildForm(array $form, FormStateInterface $form_state) {
   public function validateForm(array &$form, FormStateInterface $form_state) {
     // Only validate the form if we have collected all of the user input and are
     // ready to proceed with updating or installing.
-    if ($form_state['triggering_element']['#name'] != 'process_updates') {
+    if ($form_state->getTriggeringElement()['#name'] != 'process_updates') {
       return;
     }
 
@@ -169,7 +169,7 @@ public function validateForm(array &$form, FormStateInterface $form_state) {
    */
   public function submitForm(array &$form, FormStateInterface $form_state) {
     $form_connection_settings = $form_state->getValue('connection_settings');
-    switch ($form_state['triggering_element']['#name']) {
+    switch ($form_state->getTriggeringElement()['#name']) {
       case 'process_updates':
 
         // Save the connection settings to the DB.
@@ -214,11 +214,11 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
         break;
 
       case 'enter_connection_settings':
-        $form_state['rebuild'] = TRUE;
+        $form_state->setRebuild();
         break;
 
       case 'change_connection_type':
-        $form_state['rebuild'] = TRUE;
+        $form_state->setRebuild();
         $form_state->unsetValue(array('connection_settings', 'authorize_filetransfer_default'));
         break;
     }
diff --git a/core/lib/Drupal/Core/Form/BaseFormIdInterface.php b/core/lib/Drupal/Core/Form/BaseFormIdInterface.php
index 3576758..c6710c2 100644
--- a/core/lib/Drupal/Core/Form/BaseFormIdInterface.php
+++ b/core/lib/Drupal/Core/Form/BaseFormIdInterface.php
@@ -10,7 +10,7 @@
 /**
  * Provides an interface for a Form that has a base form ID.
  *
- * This will become the $form_state['build_info']['base_form_id'] used to
+ * This will become the $form_state->getBaseInfo()['base_form_id'] used to
  * generate the name of hook_form_BASE_FORM_ID_alter().
  */
 interface BaseFormIdInterface extends FormInterface {
diff --git a/core/lib/Drupal/Core/Form/FormBuilder.php b/core/lib/Drupal/Core/Form/FormBuilder.php
index 3417549..20b47f6 100644
--- a/core/lib/Drupal/Core/Form/FormBuilder.php
+++ b/core/lib/Drupal/Core/Form/FormBuilder.php
@@ -157,7 +157,7 @@ public function getFormId($form_arg, FormStateInterface &$form_state) {
     }
 
     // Add the $form_arg as the callback object and determine the form ID.
-    $form_state->addBuildInfo('callback_object', $form_arg);
+    $form_state->setFormObject($form_arg);
     if ($form_arg instanceof BaseFormIdInterface) {
       $form_state->addBuildInfo('base_form_id', $form_arg->getBaseFormID());
     }
@@ -188,7 +188,7 @@ public function buildForm($form_id, FormStateInterface &$form_state) {
     $input = $form_state->getUserInput();
     if (!isset($input)) {
       $request = $this->requestStack->getCurrentRequest();
-      $input = $form_state['method'] == 'get' ? $request->query->all() : $request->request->all();
+      $input = $form_state->getMethod()== 'get' ? $request->query->all() : $request->request->all();
       $form_state->setUserInput($input);
     }
 
@@ -227,7 +227,7 @@ public function buildForm($form_id, FormStateInterface &$form_state) {
       // self::setCache() removes uncacheable $form_state keys (see properties
       // in \Drupal\Core\Form\FormState) in order for multi-step forms to work
       // properly. This means that form processing logic for single-step forms
-      // using $form_state['cache'] may depend on data stored in those keys
+      // using $form_state->isCached() may depend on data stored in those keys
       // during self::retrieveForm()/self::prepareForm(), but form processing
       // should not depend on whether the form is cached or not, so $form_state
       // is adjusted to match what it would be after a
@@ -239,7 +239,9 @@ public function buildForm($form_id, FormStateInterface &$form_state) {
       // - temporary: Any assigned data is expected to survives within the same
       //   page request.
       if ($check_cache) {
-        $cache_form_state = $form_state->getCacheableArray(array('always_process', 'temporary'));
+        $cache_form_state = $form_state->getCacheableArray();
+        $cache_form_state['always_process'] = $form_state->getAlwaysProcess();
+        $cache_form_state['temporary'] = $form_state->getTemporary();
         $form_state = $form_state_before_retrieval;
         $form_state->setFormState($cache_form_state);
       }
@@ -280,6 +282,8 @@ public function buildForm($form_id, FormStateInterface &$form_state) {
    */
   public function rebuildForm($form_id, FormStateInterface &$form_state, $old_form = NULL) {
     $form = $this->retrieveForm($form_id, $form_state);
+    // All rebuilt forms will be cached.
+    $form_state->setCached();
 
     // If only parts of the form will be returned to the browser (e.g., Ajax or
     // RIA clients), re-use the old #build_id to not require client-side code to
@@ -288,7 +292,8 @@ public function rebuildForm($form_id, FormStateInterface &$form_state, $old_form
     // build's data in the form cache; also allowing the user to go back to an
     // earlier build, make changes, and re-submit.
     // @see self::prepareForm()
-    if (isset($old_form['#build_id']) && !empty($form_state['rebuild_info']['copy']['#build_id'])) {
+    $rebuild_info = $form_state->getRebuildInfo();
+    if (isset($old_form['#build_id']) && !empty($rebuild_info['copy']['#build_id'])) {
       $form['#build_id'] = $old_form['#build_id'];
     }
     else {
@@ -298,7 +303,7 @@ public function rebuildForm($form_id, FormStateInterface &$form_state, $old_form
     // #action defaults to request_uri(), but in case of Ajax and other partial
     // rebuilds, the form is submitted to an alternate URL, and the original
     // #action needs to be retained.
-    if (isset($old_form['#action']) && !empty($form_state['rebuild_info']['copy']['#action'])) {
+    if (isset($old_form['#action']) && !empty($rebuild_info['copy']['#action'])) {
       $form['#action'] = $old_form['#action'];
     }
 
@@ -308,13 +313,13 @@ public function rebuildForm($form_id, FormStateInterface &$form_state, $old_form
     // cached is the $form structure before it passes through
     // self::doBuildForm(), so we need to do it here.
     // @todo For Drupal 8, find a way to avoid this code duplication.
-    if (empty($form_state['no_cache'])) {
+    if ($form_state->isCached()) {
       $this->setCache($form['#build_id'], $form, $form_state);
     }
 
     // Clear out all group associations as these might be different when
     // re-rendering the form.
-    $form_state->set('groups', array());
+    $form_state->setGroups([]);
 
     // Return a fully built form that is ready for rendering.
     return $this->doBuildForm($form_id, $form, $form_state);
@@ -338,7 +343,8 @@ public function setCache($form_build_id, $form, FormStateInterface $form_state)
    * {@inheritdoc}
    */
   public function submitForm($form_arg, FormStateInterface &$form_state) {
-    if (!isset($form_state['build_info']['args'])) {
+    $build_info = $form_state->getBuildInfo();
+    if (empty($build_info['args'])) {
       $args = func_get_args();
       // Remove $form and $form_state from the arguments.
       unset($args[0], $args[1]);
@@ -351,15 +357,15 @@ public function submitForm($form_arg, FormStateInterface &$form_state) {
     // there).
     $form_state->setUserInput($form_state->getValues());
 
-    $form_state->set('programmed', TRUE);
+    $form_state->setProgrammed();
 
     $form_id = $this->getFormId($form_arg, $form_state);
     $form = $this->retrieveForm($form_id, $form_state);
     // Programmed forms are always submitted.
-    $form_state->set('submitted', TRUE);
+    $form_state->setSubmitted();
 
     // Reset form validation.
-    $form_state->set('must_validate', TRUE);
+    $form_state->setValidationEnforced();
     $form_state->clearErrors();
 
     $this->prepareForm($form_id, $form, $form_state);
@@ -376,9 +382,10 @@ public function retrieveForm($form_id, FormStateInterface &$form_state) {
     // We save two copies of the incoming arguments: one for modules to use
     // when mapping form ids to constructor functions, and another to pass to
     // the constructor function itself.
-    $args = $form_state['build_info']['args'];
+    $build_info = $form_state->getBuildInfo();
+    $args = $build_info['args'];
 
-    $callback = array($form_state['build_info']['callback_object'], 'buildForm');
+    $callback = [$form_state->getFormObject(), 'buildForm'];
 
     $form = array();
     // Assign a default CSS class name based on $form_id.
@@ -386,8 +393,8 @@ public function retrieveForm($form_id, FormStateInterface &$form_state) {
     // form constructor function to override or remove the default class.
     $form['#attributes']['class'][] = Html::getClass($form_id);
     // Same for the base form ID, if any.
-    if (isset($form_state['build_info']['base_form_id'])) {
-      $form['#attributes']['class'][] = Html::getClass($form_state['build_info']['base_form_id']);
+    if (isset($build_info['base_form_id'])) {
+      $form['#attributes']['class'][] = Html::getClass($build_info['base_form_id']);
     }
 
     // We need to pass $form_state by reference in order for forms to modify it,
@@ -410,10 +417,10 @@ public function retrieveForm($form_id, FormStateInterface &$form_state) {
    * {@inheritdoc}
    */
   public function processForm($form_id, &$form, FormStateInterface &$form_state) {
-    $form_state->set('values', array());
+    $form_state->setValues([]);
 
     // With GET, these forms are always submitted if requested.
-    if ($form_state['method'] == 'get' && !empty($form_state['always_process'])) {
+    if ($form_state->getMethod() == 'get' && $form_state->getAlwaysProcess()) {
       $input = $form_state->getUserInput();
       if (!isset($input['form_build_id'])) {
         $input['form_build_id'] = $form['#build_id'];
@@ -435,7 +442,7 @@ public function processForm($form_id, &$form, FormStateInterface &$form_state) {
     $form = $this->doBuildForm($form_id, $form, $form_state);
 
     // Only process the input if we have a correct form submission.
-    if ($form_state['process_input']) {
+    if ($form_state->isProcessingInput()) {
       // Form constructors may explicitly set #token to FALSE when cross site
       // request forgery is irrelevant to the form, such as search forms.
       if (isset($form['#token']) && $form['#token'] === FALSE) {
@@ -447,8 +454,9 @@ public function processForm($form_id, &$form, FormStateInterface &$form_state) {
       // submit button is not taken account. Therefore, check whether there is
       // exactly one submit button in the form, and if so, automatically use it
       // as triggering_element.
-      if ($form_state['programmed'] && !isset($form_state['triggering_element']) && count($form_state['buttons']) == 1) {
-        $form_state->set('triggering_element', reset($form_state['buttons']));
+      $buttons = $form_state->getButtons();
+      if ($form_state->isProgrammed() && !$form_state->getTriggeringElement() && count($buttons) == 1) {
+        $form_state->setTriggeringElement(reset($buttons));
       }
       $this->formValidator->validateForm($form_id, $form, $form_state);
 
@@ -462,35 +470,35 @@ public function processForm($form_id, &$form, FormStateInterface &$form_state) {
         Html::resetSeenIds();
       }
 
-      if (!$form_state['rebuild'] && !FormState::hasAnyErrors()) {
+      if (!$form_state->getRebuild() && !FormState::hasAnyErrors()) {
         if ($submit_response = $this->formSubmitter->doSubmitForm($form, $form_state)) {
           return $submit_response;
         }
       }
 
       // Don't rebuild or cache form submissions invoked via self::submitForm().
-      if (!empty($form_state['programmed'])) {
+      if ($form_state->isProgrammed()) {
         return;
       }
 
-      // If $form_state['rebuild'] has been set and input has been processed
+      // If $form_state->getRebuild() has been set and input has been processed
       // without validation errors, we are in a multi-step workflow that is not
       // yet complete. A new $form needs to be constructed based on the changes
       // made to $form_state during this request. Normally, a submit handler
-      // sets $form_state['rebuild'] if a fully executed form requires another
-      // step. However, for forms that have not been fully executed (e.g., Ajax
-      // submissions triggered by non-buttons), there is no submit handler to
-      // set $form_state['rebuild']. It would not make sense to redisplay the
-      // identical form without an error for the user to correct, so we also
-      // rebuild error-free non-executed forms, regardless of
-      // $form_state['rebuild'].
+      // sets $form_state->getRebuild() if a fully executed form requires
+      // another step. However, for forms that have not been fully executed
+      // (e.g., Ajax submissions triggered by non-buttons), there is no submit
+      // handler to set $form_state->getRebuild(). It would not make sense to
+      // redisplay the identical form without an error for the user to correct,
+      // so we also rebuild error-free non-executed forms, regardless of
+      // $form_state->getRebuild().
       // @todo Simplify this logic; considering Ajax and non-HTML front-ends,
       //   along with element-level #submit properties, it makes no sense to
       //   have divergent form execution based on whether the triggering element
       //   has #executes_submit_callback set to TRUE.
-      if (($form_state['rebuild'] || !$form_state['executed']) && !FormState::hasAnyErrors()) {
+      if (($form_state->getRebuild() || !$form_state->isExecuted()) && !FormState::hasAnyErrors()) {
         // Form building functions (e.g., self::handleInputElement()) may use
-        // $form_state['rebuild'] to determine if they are running in the
+        // $form_state->getRebuild() to determine if they are running in the
         // context of a rebuild, so ensure it is set.
         $form_state->setRebuild();
         $form = $this->rebuildForm($form_id, $form_state, $form);
@@ -498,13 +506,13 @@ public function processForm($form_id, &$form, FormStateInterface &$form_state) {
     }
 
     // After processing the form, the form builder or a #process callback may
-    // have set $form_state['cache'] to indicate that the form and form state
-    // shall be cached. But the form may only be cached if the 'no_cache'
-    // property is not set to TRUE. Only cache $form as it was prior to
-    // self::doBuildForm(), because self::doBuildForm() must run for each
-    // request to accommodate new user input. Rebuilt forms are not cached here,
-    // because self::rebuildForm() already takes care of that.
-    if (!$form_state['rebuild'] && $form_state['cache'] && empty($form_state['no_cache'])) {
+    // have called $form_state->setCached() to indicate that the form and form
+    // state shall be cached. But the form may only be cached if
+    // $form_state->disableCache() is not called. Only cache $form as it was
+    // prior to self::doBuildForm(), because self::doBuildForm() must run for
+    // each request to accommodate new user input. Rebuilt forms are not cached
+    // here, because self::rebuildForm() already takes care of that.
+    if (!$form_state->getRebuild() && $form_state->isCached()) {
       $this->setCache($form['#build_id'], $unprocessed_form, $form_state);
     }
   }
@@ -516,10 +524,9 @@ public function prepareForm($form_id, &$form, FormStateInterface &$form_state) {
     $user = $this->currentUser();
 
     $form['#type'] = 'form';
-    $form_state->set('programmed', isset($form_state['programmed']) ? $form_state['programmed'] : FALSE);
 
     // Fix the form method, if it is 'get' in $form_state, but not in $form.
-    if ($form_state->get('method') == 'get' && !isset($form['#method'])) {
+    if ($form_state->getMethod() == 'get' && !isset($form['#method'])) {
       $form['#method'] = 'get';
     }
 
@@ -551,7 +558,7 @@ public function prepareForm($form_id, &$form, FormStateInterface &$form_state) {
     // since tokens are session-bound and forms displayed to anonymous users are
     // very likely cached, we cannot assign a token for them.
     // During installation, there is no $user yet.
-    if ($user && $user->isAuthenticated() && !$form_state['programmed']) {
+    if ($user && $user->isAuthenticated() && !$form_state->isProgrammed()) {
       // Form constructors may explicitly set #token to FALSE when cross site
       // request forgery is irrelevant to the form, such as search forms.
       if (isset($form['#token']) && $form['#token'] === FALSE) {
@@ -592,22 +599,23 @@ public function prepareForm($form_id, &$form, FormStateInterface &$form_state) {
     $form['#validate'][] = '::validateForm';
     $form['#submit'][] = '::submitForm';
 
+    $build_info = $form_state->getBuildInfo();
     // If no #theme has been set, automatically apply theme suggestions.
     // theme_form() itself is in #theme_wrappers and not #theme. Therefore, the
     // #theme function only has to care for rendering the inner form elements,
     // not the form itself.
     if (!isset($form['#theme'])) {
       $form['#theme'] = array($form_id);
-      if (isset($form_state['build_info']['base_form_id'])) {
-        $form['#theme'][] = $form_state['build_info']['base_form_id'];
+      if (isset($build_info['base_form_id'])) {
+        $form['#theme'][] = $build_info['base_form_id'];
       }
     }
 
     // Invoke hook_form_alter(), hook_form_BASE_FORM_ID_alter(), and
     // hook_form_FORM_ID_alter() implementations.
     $hooks = array('form');
-    if (isset($form_state['build_info']['base_form_id'])) {
-      $hooks[] = 'form_' . $form_state['build_info']['base_form_id'];
+    if (isset($build_info['base_form_id'])) {
+      $hooks[] = 'form_' . $build_info['base_form_id'];
     }
     $hooks[] = 'form_' . $form_id;
     $this->moduleHandler->alter($hooks, $form, $form_state, $form_id);
@@ -690,11 +698,11 @@ public function doBuildForm($form_id, &$element, FormStateInterface &$form_state
       // for programmed forms coming from self::submitForm(), or if the form_id
       // coming from the POST data is set and matches the current form_id.
       $input = $form_state->getUserInput();
-      if ($form_state['programmed'] || (!empty($input) && (isset($input['form_id']) && ($input['form_id'] == $form_id)))) {
-        $form_state->set('process_input', TRUE);
+      if ($form_state->isProgrammed() || (!empty($input) && (isset($input['form_id']) && ($input['form_id'] == $form_id)))) {
+        $form_state->setProcessInput();
       }
       else {
-        $form_state->set('process_input', FALSE);
+        $form_state->setProcessInput(FALSE);
       }
 
       // All form elements should have an #array_parents property.
@@ -791,14 +799,14 @@ public function doBuildForm($form_id, &$element, FormStateInterface &$form_state
     // If there is a file element, we need to flip a flag so later the
     // form encoding can be set.
     if (isset($element['#type']) && $element['#type'] == 'file') {
-      $form_state->set('has_file_element', TRUE);
+      $form_state->setHasFileElement();
     }
 
     // Final tasks for the form element after self::doBuildForm() has run for
     // all other elements.
     if (isset($element['#type']) && $element['#type'] == 'form') {
       // If there is a file element, we set the form encoding.
-      if (isset($form_state['has_file_element'])) {
+      if ($form_state->hasFileElement()) {
         $element['#attributes']['enctype'] = 'multipart/form-data';
       }
 
@@ -808,24 +816,26 @@ public function doBuildForm($form_id, &$element, FormStateInterface &$form_state
       // though the user clicked the first button. Therefore, to be as
       // consistent as we can be across browsers, if no 'triggering_element' has
       // been identified yet, default it to the first button.
-      if (!$form_state['programmed'] && !isset($form_state['triggering_element']) && !empty($form_state['buttons'])) {
-        $form_state->set('triggering_element', $form_state['buttons'][0]);
+      $buttons = $form_state->getButtons();
+      if (!$form_state->isProgrammed() && !$form_state->getTriggeringElement() && !empty($buttons)) {
+        $form_state->setTriggeringElement($buttons[0]);
       }
 
-      $triggering_element = $form_state->get('triggering_element');
+      $triggering_element = $form_state->getTriggeringElement();
       // If the triggering element specifies "button-level" validation and
       // submit handlers to run instead of the default form-level ones, then add
       // those to the form state.
-      foreach (array('validate', 'submit') as $type) {
-        if (isset($triggering_element['#' . $type])) {
-          $form_state->set($type . '_handlers', $triggering_element['#' . $type]);
-        }
+      if (isset($triggering_element['#validate'])) {
+        $form_state->setValidateHandlers($triggering_element['#validate']);
+      }
+      if (isset($triggering_element['#submit'])) {
+        $form_state->setSubmitHandlers($triggering_element['#submit']);
       }
 
       // If the triggering element executes submit handlers, then set the form
       // state key that's needed for those handlers to run.
       if (!empty($triggering_element['#executes_submit_callback'])) {
-        $form_state->set('submitted', TRUE);
+        $form_state->setSubmitted();
       }
 
       // Special processing if the triggering element is a button.
@@ -899,7 +909,7 @@ protected function handleInputElement($form_id, &$element, FormStateInterface &$
     // #access=FALSE on an element usually allow access for some users, so forms
     // submitted with self::submitForm() may bypass access restriction and be
     // treated as high-privilege users instead.
-    $process_input = empty($element['#disabled']) && (($form_state['programmed'] && $form_state['programmed_bypass_access_check']) || ($form_state['process_input'] && (!isset($element['#access']) || $element['#access'])));
+    $process_input = empty($element['#disabled']) && (($form_state->isProgrammed() && $form_state->isBypassingProgrammedAccessChecks()) || ($form_state->isProcessingInput() && (!isset($element['#access']) || $element['#access'])));
 
     // Set the element's #value property.
     if (!isset($element['#value']) && !array_key_exists('#value', $element)) {
@@ -925,7 +935,7 @@ protected function handleInputElement($form_id, &$element, FormStateInterface &$
         // use default values for the latter, if required. Programmatically
         // submitted forms can submit explicit NULL values when calling
         // self::submitForm() so we do not modify FormState::$input for them.
-        if (!$input_exists && !$form_state['rebuild'] && !$form_state['programmed']) {
+        if (!$input_exists && !$form_state->getRebuild() && !$form_state->isProgrammed()) {
           // Add the necessary parent keys to FormState::$input and sets the
           // element's input value to NULL.
           NestedArray::setValue($form_state->getUserInput(), $element['#parents'], NULL);
@@ -967,7 +977,7 @@ protected function handleInputElement($form_id, &$element, FormStateInterface &$
     if ($process_input) {
       // Detect if the element triggered the submission via Ajax.
       if ($this->elementTriggeredScriptedSubmission($element, $form_state)) {
-        $form_state->set('triggering_element', $element);
+        $form_state->setTriggeringElement($element);
       }
 
       // If the form was submitted by the browser rather than via Ajax, then it
@@ -979,11 +989,11 @@ protected function handleInputElement($form_id, &$element, FormStateInterface &$
         // form_state_values_clean() and for the self::doBuildForm() code that
         // handles a form submission containing no button information in
         // \Drupal::request()->request.
-        $buttons = $form_state->get('buttons');
+        $buttons = $form_state->getButtons();
         $buttons[] = $element;
-        $form_state->set('buttons', $buttons);
+        $form_state->setButtons($buttons);
         if ($this->buttonWasClicked($element, $form_state)) {
-          $form_state->set('triggering_element', $element);
+          $form_state->setTriggeringElement($element);
         }
       }
     }
@@ -1031,10 +1041,10 @@ protected function elementTriggeredScriptedSubmission($element, FormStateInterfa
    * textfield (self::doBuildForm() has extra code for that).
    *
    * Because this function contains only part of the logic needed to determine
-   * $form_state['triggering_element'], it should not be called from anywhere
+   * $form_state->getTriggeringElement(), it should not be called from anywhere
    * other than within the Form API. Form validation and submit handlers needing
    * to know which button was clicked should get that information from
-   * $form_state['triggering_element'].
+   * $form_state->getTriggeringElement().
    */
   protected function buttonWasClicked($element, FormStateInterface &$form_state) {
     // First detect normal 'vanilla' button clicks. Traditionally, all standard
diff --git a/core/lib/Drupal/Core/Form/FormBuilderInterface.php b/core/lib/Drupal/Core/Form/FormBuilderInterface.php
index 3e97b49..b9fefa4 100644
--- a/core/lib/Drupal/Core/Form/FormBuilderInterface.php
+++ b/core/lib/Drupal/Core/Form/FormBuilderInterface.php
@@ -42,7 +42,7 @@ public function getFormId($form_arg, FormStateInterface &$form_state);
    *   function. For example, the node_edit form requires that a node object is
    *   passed in here when it is called. These are available to implementations
    *   of hook_form_alter() and hook_form_FORM_ID_alter() as the array
-   *   $form_state['build_info']['args'].
+   *   $form_state->getBuildInfo()['args'].
    *
    * @return array
    *   The form array.
@@ -79,7 +79,7 @@ public function buildForm($form_id, FormStateInterface &$form_state);
    * This is the key function for making multi-step forms advance from step to
    * step. It is called by self::processForm() when all user input processing,
    * including calling validation and submission handlers, for the request is
-   * finished. If a validate or submit handler set $form_state['rebuild'] to
+   * finished. If a validate or submit handler set $form_state->getRebuild() to
    * TRUE, and if other conditions don't preempt a rebuild from happening, then
    * this function is called to generate a new $form, the next step in the form
    * workflow, to be returned for rendering.
@@ -98,7 +98,7 @@ public function buildForm($form_id, FormStateInterface &$form_state);
    *   (optional) A previously built $form. Used to retain the #build_id and
    *   #action properties in Ajax callbacks and similar partial form rebuilds.
    *   The only properties copied from $old_form are the ones which both exist
-   *   in $old_form and for which $form_state['rebuild_info']['copy'][PROPERTY]
+   *   in $old_form and for which $form_state->getRebuildInfo()['copy'][PROPERTY]
    *   is TRUE. If $old_form is not passed, the entire $form is rebuilt freshly.
    *   'rebuild_info' needs to be a separate top-level property next to
    *   'build_info', since the contained data must not be cached.
@@ -148,8 +148,8 @@ public function rebuildForm($form_id, FormStateInterface &$form_state, $old_form
    *   @endcode
    *   would be called via self::submitForm() as follows:
    *   @code
-   *   $form_state->set('values', $my_form_values);
-   *   $form_state['build_info']['args'] = array(&$object);
+   *   $form_state->setValues($my_form_values);
+   *   $form_state->addBuildInfo('args', [&$object]);
    *   drupal_form_submit('mymodule_form', $form_state);
    *   @endcode
    * For example:
@@ -161,7 +161,7 @@ public function rebuildForm($form_id, FormStateInterface &$form_state, $old_form
    * $values['pass']['pass1'] = 'password';
    * $values['pass']['pass2'] = 'password';
    * $values['op'] = t('Create new account');
-   * $form_state->set('values', $values);
+   * $form_state->setValues($values);
    * drupal_form_submit('user_register_form', $form_state);
    * @endcode
    */
@@ -293,7 +293,7 @@ public function prepareForm($form_id, &$form, FormStateInterface &$form_state);
    *   the next submission needs to be processed, a multi-step workflow is
    *   needed. This is most commonly implemented with a submit handler setting
    *   persistent data within $form_state based on *validated* values in
-   *   $form_state->getValues() and setting $form_state['rebuild']. The form
+   *   $form_state->getValues() and setting $form_state->getRebuild(). The form
    *   building functions must then be implemented to use the $form_state data
    *   to rebuild the form with the structure appropriate for the new state.
    * - Where user input must affect the rendering of the form without affecting
diff --git a/core/lib/Drupal/Core/Form/FormCache.php b/core/lib/Drupal/Core/Form/FormCache.php
index 9ba893d..93da48c 100644
--- a/core/lib/Drupal/Core/Form/FormCache.php
+++ b/core/lib/Drupal/Core/Form/FormCache.php
@@ -95,8 +95,9 @@ protected function loadCachedFormState($form_build_id, FormStateInterface $form_
 
       // If the original form is contained in include files, load the files.
       // @see \Drupal\Core\Form\FormStateInterface::loadInclude()
-      $form_state['build_info'] += array('files' => array());
-      foreach ($form_state['build_info']['files'] as $file) {
+      $build_info = $form_state->getBuildInfo();
+      $build_info += ['files' => []];
+      foreach ($build_info['files'] as $file) {
         if (is_array($file)) {
           $file += array('type' => 'inc', 'name' => $file['module']);
           $this->moduleHandler->loadInclude($file['module'], $file['type'], $file['name']);
@@ -109,9 +110,10 @@ protected function loadCachedFormState($form_build_id, FormStateInterface $form_
       // for this request.
       // @todo Ensure we are not storing an excessively large string list
       //   in: https://www.drupal.org/node/2295823
-      $form_state['build_info'] += array('safe_strings' => array());
-      SafeMarkup::setMultiple($form_state['build_info']['safe_strings']);
-      unset($form_state['build_info']['safe_strings']);
+      $build_info += ['safe_strings' => []];
+      SafeMarkup::setMultiple($build_info['safe_strings']);
+      unset($build_info['safe_strings']);
+      $form_state->setBuildInfo($build_info);
     }
   }
 
diff --git a/core/lib/Drupal/Core/Form/FormState.php b/core/lib/Drupal/Core/Form/FormState.php
index 5bed64a..92f5d14 100644
--- a/core/lib/Drupal/Core/Form/FormState.php
+++ b/core/lib/Drupal/Core/Form/FormState.php
@@ -13,10 +13,8 @@
 
 /**
  * Stores information about the state of a form.
- *
- * @todo Remove usage of \ArrayAccess in https://www.drupal.org/node/2310255.
  */
-class FormState implements FormStateInterface, \ArrayAccess {
+class FormState implements FormStateInterface {
 
   /**
    * Tracks if any errors have been set on any form.
@@ -26,13 +24,6 @@ class FormState implements FormStateInterface, \ArrayAccess {
   protected static $anyErrors = FALSE;
 
   /**
-   * The internal storage of the form state.
-   *
-   * @var array
-   */
-  protected $internalStorage = array();
-
-  /**
    * The complete form structure.
    *
    * #process, #after_build, #element_validate, and other handlers being invoked
@@ -92,12 +83,12 @@ class FormState implements FormStateInterface, \ArrayAccess {
    * re-submit the form). However, if 'rebuild' has been set to TRUE, then a new
    * copy of the form is immediately built and sent to the browser, instead of a
    * redirect. This is used for multi-step forms, such as wizards and
-   * confirmation forms. Normally, $form_state['rebuild'] is set by a submit
-   * handler, since its is usually logic within a submit handler that determines
-   * whether a form is done or requires another step. However, a validation
-   * handler may already set $form_state['rebuild'] to cause the form processing
-   * to bypass submit handlers and rebuild the form instead, even if there are
-   * no validation errors.
+   * confirmation forms. Normally, self::$rebuild is set by a submit handler,
+   * since its is usually logic within a submit handler that determines whether
+   * a form is done or requires another step. However, a validation handler may
+   * already set self::$rebuild to cause the form processing to bypass submit
+   * handlers and rebuild the form instead, even if there are no validation
+   * errors.
    *
    * This property is uncacheable.
    *
@@ -217,10 +208,6 @@ class FormState implements FormStateInterface, \ArrayAccess {
   /**
    * If TRUE and the method is GET, a form_id is not necessary.
    *
-   * This should only be used on RESTful GET forms that do NOT write data, as
-   * this could lead to security issues. It is useful so that searches do not
-   * need to have a form_id in their query arguments to trigger the search.
-   *
    * This property is uncacheable.
    *
    * @var bool
@@ -383,6 +370,13 @@ class FormState implements FormStateInterface, \ArrayAccess {
   /**
    * Stores which errors should be limited during validation.
    *
+   * An array of "sections" within which user input must be valid. If the
+   * element is within one of these sections, the error must be recorded.
+   * Otherwise, it can be suppressed. self::$limit_validation_errors can be an
+   * empty array, in which case all errors are suppressed. For example, a
+   * "Previous" button might want its submit action to be triggered even if none
+   * of the submitted values are valid.
+   *
    * This property is uncacheable.
    *
    * @var array|null
@@ -394,37 +388,30 @@ class FormState implements FormStateInterface, \ArrayAccess {
    *
    * This property is uncacheable.
    *
-   * @var array|null
+   * @var array
    */
-  protected $validate_handlers;
+  protected $validate_handlers = [];
 
   /**
    * Stores the gathered submission handlers.
    *
    * This property is uncacheable.
    *
-   * @var array|null
+   * @var array
    */
-  protected $submit_handlers;
-
-  /**
-   * Constructs a \Drupal\Core\Form\FormState object.
-   *
-   * @param array $form_state_additions
-   *   (optional) An associative array used to build the current state of the
-   *   form. Use this to pass additional information to the form, such as the
-   *   langcode. Defaults to an empty array.
-   */
-  public function __construct(array $form_state_additions = array()) {
-    $this->setFormState($form_state_additions);
-  }
+  protected $submit_handlers = [];
 
   /**
    * {@inheritdoc}
    */
   public function setFormState(array $form_state_additions) {
     foreach ($form_state_additions as $key => $value) {
-      $this->set($key, $value);
+      if (property_exists($this, $key)) {
+        $this->{$key} = $value;
+      }
+      else {
+        $this->set($key, $value);
+      }
     }
     return $this;
   }
@@ -432,11 +419,344 @@ public function setFormState(array $form_state_additions) {
   /**
    * {@inheritdoc}
    */
+  public function setAlwaysProcess($always_process = TRUE) {
+    $this->always_process = (bool) $always_process;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getAlwaysProcess() {
+    return $this->always_process;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setButtons(array $buttons) {
+    $this->buttons = $buttons;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getButtons() {
+    return $this->buttons;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setCached($cache = TRUE) {
+    $this->cache = (bool) $cache;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isCached() {
+    return empty($this->no_cache) && $this->cache;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function disableCache() {
+    $this->no_cache = TRUE;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setExecuted() {
+    $this->executed = TRUE;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isExecuted() {
+    return $this->executed;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setGroups(array $groups) {
+    $this->groups = $groups;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function &getGroups() {
+    return $this->groups;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setHasFileElement($has_file_element = TRUE) {
+    $this->has_file_element = (bool) $has_file_element;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function hasFileElement() {
+    return $this->has_file_element;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setLimitValidationErrors($limit_validation_errors) {
+    $this->limit_validation_errors = $limit_validation_errors;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getLimitValidationErrors() {
+    return $this->limit_validation_errors;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setMethod($method) {
+    $this->method = $method;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getMethod() {
+    return $this->method;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setValidationEnforced($must_validate = TRUE) {
+    $this->must_validate = (bool) $must_validate;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isValidationEnforced() {
+    return $this->must_validate;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function disableRedirect($no_redirect = TRUE) {
+    $this->no_redirect = (bool) $no_redirect;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isRedirectDisabled() {
+   return $this->no_redirect;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setProcessInput($process_input = TRUE) {
+    $this->process_input = (bool) $process_input;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isProcessingInput() {
+    return $this->process_input;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setProgrammed($programmed = TRUE) {
+    $this->programmed = (bool) $programmed;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isProgrammed() {
+    return $this->programmed;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setProgrammedBypassAccessCheck($programmed_bypass_access_check = TRUE) {
+    $this->programmed_bypass_access_check = (bool) $programmed_bypass_access_check;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isBypassingProgrammedAccessChecks() {
+    return $this->programmed_bypass_access_check;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setRebuildInfo(array $rebuild_info) {
+    $this->rebuild_info = $rebuild_info;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getRebuildInfo() {
+    return $this->rebuild_info;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function addRebuildInfo($property, $value) {
+    $rebuild_info = $this->getRebuildInfo();
+    $rebuild_info[$property] = $value;
+    $this->setRebuildInfo($rebuild_info);
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setStorage(array $storage) {
+    $this->storage = $storage;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function &getStorage() {
+    return $this->storage;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setSubmitHandlers($submit_handlers) {
+    $this->submit_handlers = $submit_handlers;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getSubmitHandlers() {
+    return $this->submit_handlers;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setSubmitted() {
+    $this->submitted = TRUE;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isSubmitted() {
+    return $this->submitted;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setTemporary(array $temporary) {
+    $this->temporary = $temporary;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getTemporary() {
+    return $this->temporary;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setTriggeringElement($triggering_element) {
+    $this->triggering_element = $triggering_element;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function &getTriggeringElement() {
+    return $this->triggering_element;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setValidateHandlers($validate_handlers) {
+    $this->validate_handlers = $validate_handlers;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getValidateHandlers() {
+    return $this->validate_handlers;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setValidationComplete($validation_complete = TRUE) {
+    $this->validation_complete = (bool) $validation_complete;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isValidationComplete() {
+    return $this->validation_complete;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
   public function loadInclude($module, $type, $name = NULL) {
     if (!isset($name)) {
       $name = $module;
     }
-    $build_info = $this->get('build_info');
+    $build_info = $this->getBuildInfo();
     if (!isset($build_info['files']["$module:$name.$type"])) {
       // Only add successfully included files to the form state.
       if ($result = $this->moduleLoadInclude($module, $type, $name)) {
@@ -445,7 +765,7 @@ public function loadInclude($module, $type, $name = NULL) {
           'module' => $module,
           'name' => $name,
         );
-        $this->set('build_info', $build_info);
+        $this->setBuildInfo($build_info);
         return $result;
       }
     }
@@ -455,22 +775,20 @@ public function loadInclude($module, $type, $name = NULL) {
   /**
    * {@inheritdoc}
    */
-  public function getCacheableArray($allowed_keys = array()) {
-    $cacheable_array = array(
-      'build_info' => $this->build_info,
-      'response' => $this->response,
+  public function getCacheableArray() {
+    return [
+      'build_info' => $this->getBuildInfo(),
+      'response' => $this->getResponse(),
+      'programmed' => $this->isProgrammed(),
+      'programmed_bypass_access_check' => $this->isBypassingProgrammedAccessChecks(),
+      'process_input' => $this->isProcessingInput(),
+      'has_file_element' => $this->hasFileElement(),
+      'storage' => $this->getStorage(),
+      // Use the properties directly, since self::isCached() combines them and
+      // cannot be relied upon.
       'cache' => $this->cache,
       'no_cache' => $this->no_cache,
-      'programmed' => $this->programmed,
-      'programmed_bypass_access_check' => $this->programmed_bypass_access_check,
-      'process_input' => $this->process_input,
-      'has_file_element' => $this->has_file_element,
-      'storage' => $this->storage,
-    ) + $this->internalStorage;
-    foreach ($allowed_keys as $allowed_key) {
-      $cacheable_array[$allowed_key] = $this->get($allowed_key);
-    }
-    return $cacheable_array;
+    ];
   }
 
   /**
@@ -488,81 +806,19 @@ public function &getCompleteForm() {
     return $this->complete_form;
   }
 
-  /**
-   * {@inheritdoc}
-   *
-   * @deprecated in Drupal 8.0.x, might be removed before Drupal 8.0.0.
-   */
-  public function offsetExists($offset) {
-    return isset($this->{$offset}) || isset($this->internalStorage[$offset]);
-  }
-
-  /**
-   * {@inheritdoc}
-   *
-   * @deprecated in Drupal 8.0.x, might be removed before Drupal 8.0.0.
-   */
-  public function &offsetGet($offset) {
-    $value = &$this->get($offset);
-    return $value;
-  }
-
-  /**
-   * {@inheritdoc}
-   *
-   * @deprecated in Drupal 8.0.x, might be removed before Drupal 8.0.0.
-   */
-  public function offsetSet($offset, $value) {
-    $this->set($offset, $value);
-  }
-
-  /**
-   * {@inheritdoc}
-   *
-   * @deprecated in Drupal 8.0.x, might be removed before Drupal 8.0.0.
-   */
-  public function offsetUnset($offset) {
-    if (property_exists($this, $offset)) {
-      $this->{$offset} = NULL;
-    }
-    unset($this->internalStorage[$offset]);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function setIfNotExists($property, $value) {
-    if (!$this->has($property)) {
-      $this->set($property, $value);
-    }
-    return $this;
-  }
-
-  /**
+  /**g
    * {@inheritdoc}
    */
   public function &get($property) {
-    if (property_exists($this, $property)) {
-      return $this->{$property};
-    }
-    else {
-      if (!isset($this->internalStorage[$property])) {
-        $this->internalStorage[$property] = NULL;
-      }
-      return $this->internalStorage[$property];
-    }
+    $value = &NestedArray::getValue($this->storage, (array) $property);
+    return $value;
   }
 
   /**
    * {@inheritdoc}
    */
   public function set($property, $value) {
-    if (property_exists($this, $property)) {
-      $this->{$property} = $value;
-    }
-    else {
-      $this->internalStorage[$property] = $value;
-    }
+    NestedArray::setValue($this->storage, (array) $property, $value, TRUE);
     return $this;
   }
 
@@ -570,20 +826,33 @@ public function set($property, $value) {
    * {@inheritdoc}
    */
   public function has($property) {
-    if (property_exists($this, $property)) {
-      return $this->{$property} !== NULL;
-    }
+    $exists = NULL;
+    NestedArray::getValue($this->storage, (array) $property, $exists);
+    return $exists;
+  }
 
-    return array_key_exists($property, $this->internalStorage);
+  /**
+   * {@inheritdoc}
+   */
+  public function setBuildInfo(array $build_info) {
+    $this->build_info = $build_info;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getBuildInfo() {
+    return $this->build_info;
   }
 
   /**
    * {@inheritdoc}
    */
   public function addBuildInfo($property, $value) {
-    $build_info = $this->get('build_info');
+    $build_info = $this->getBuildInfo();
     $build_info[$property] = $value;
-    $this->set('build_info', $build_info);
+    $this->setBuildInfo($build_info);
     return $this;
   }
 
@@ -624,6 +893,14 @@ public function &getValue($key, $default = NULL) {
   /**
    * {@inheritdoc}
    */
+  public function setValues(array $values) {
+    $this->values = $values;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
   public function setValue($key, $value) {
     NestedArray::setValue($this->getValues(), (array) $key, $value, TRUE);
     return $this;
@@ -658,7 +935,7 @@ public function isValueEmpty($key) {
   /**
    * {@inheritdoc}
    */
-  public function setValueForElement($element, $value) {
+  public function setValueForElement(array $element, $value) {
     return $this->setValue($element['#parents'], $value);
   }
 
@@ -666,13 +943,20 @@ public function setValueForElement($element, $value) {
    * {@inheritdoc}
    */
   public function setResponse(Response $response) {
-    $this->set('response', $response);
+    $this->response = $response;
     return $this;
   }
 
   /**
    * {@inheritdoc}
    */
+  public function getResponse() {
+    return $this->response;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
   public function setRedirect($route_name, array $route_parameters = array(), array $options = array()) {
     $url = new Url($route_name, $route_parameters, $options);
     return $this->setRedirectUrl($url);
@@ -682,7 +966,7 @@ public function setRedirect($route_name, array $route_parameters = array(), arra
    * {@inheritdoc}
    */
   public function setRedirectUrl(Url $url) {
-    $this->set('redirect', $url);
+    $this->redirect = $url;
     return $this;
   }
 
@@ -692,19 +976,19 @@ public function setRedirectUrl(Url $url) {
   public function getRedirect() {
     // Skip redirection for form submissions invoked via
     // \Drupal\Core\Form\FormBuilderInterface::submitForm().
-    if ($this->get('programmed')) {
+    if ($this->isProgrammed()) {
       return FALSE;
     }
     // Skip redirection if rebuild is activated.
-    if ($this->get('rebuild')) {
+    if ($this->getRebuild()) {
       return FALSE;
     }
     // Skip redirection if it was explicitly disallowed.
-    if ($this->get('no_redirect')) {
+    if ($this->no_redirect) {
       return FALSE;
     }
 
-    return $this->get('redirect');
+    return $this->redirect;
   }
 
   /**
@@ -728,22 +1012,15 @@ public static function hasAnyErrors() {
    * {@inheritdoc}
    */
   public function setErrorByName($name, $message = '') {
-    if ($this->get('validation_complete')) {
+    if ($this->isValidationComplete()) {
       throw new \LogicException('Form errors cannot be set after form validation has finished.');
     }
 
     $errors = $this->getErrors();
     if (!isset($errors[$name])) {
       $record = TRUE;
-      $limit_validation_errors = $this->get('limit_validation_errors');
+      $limit_validation_errors = $this->getLimitValidationErrors();
       if ($limit_validation_errors !== NULL) {
-        // #limit_validation_errors is an array of "sections" within which user
-        // input must be valid. If the element is within one of these sections,
-        // the error must be recorded. Otherwise, it can be suppressed.
-        // #limit_validation_errors can be an empty array, in which case all
-        // errors are suppressed. For example, a "Previous" button might want
-        // its submit action to be triggered even if none of the submitted
-        // values are valid.
         $record = FALSE;
         foreach ($limit_validation_errors as $section) {
           // Exploding by '][' reconstructs the element's #parents. If the
@@ -761,7 +1038,7 @@ public function setErrorByName($name, $message = '') {
       }
       if ($record) {
         $errors[$name] = $message;
-        $this->set('errors', $errors);
+        $this->errors = $errors;
         static::setAnyErrors();
         if ($message) {
           $this->drupalSetMessage($message, 'error');
@@ -775,7 +1052,7 @@ public function setErrorByName($name, $message = '') {
   /**
    * {@inheritdoc}
    */
-  public function setError(&$element, $message = '') {
+  public function setError(array &$element, $message = '') {
     $this->setErrorByName(implode('][', $element['#parents']), $message);
     return $this;
   }
@@ -784,14 +1061,14 @@ public function setError(&$element, $message = '') {
    * {@inheritdoc}
    */
   public function clearErrors() {
-    $this->set('errors', array());
+    $this->errors = [];
     static::setAnyErrors(FALSE);
   }
 
   /**
    * {@inheritdoc}
    */
-  public function getError($element) {
+  public function getError(array $element) {
     if ($errors = $this->getErrors($this)) {
       $parents = array();
       foreach ($element['#parents'] as $parent) {
@@ -808,24 +1085,30 @@ public function getError($element) {
    * {@inheritdoc}
    */
   public function getErrors() {
-    return $this->get('errors');
+    return $this->errors;
   }
 
   /**
    * {@inheritdoc}
    */
   public function setRebuild($rebuild = TRUE) {
-    $this->set('rebuild', $rebuild);
+    $this->rebuild = $rebuild;
     return $this;
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function getRebuild() {
+    return $this->rebuild;
+  }
 
   /**
    * {@inheritdoc}
    */
   public function prepareCallback($callback) {
     if (is_string($callback) && substr($callback, 0, 2) == '::') {
-      $callback = array($this->get('build_info')['callback_object'], substr($callback, 2));
+      $callback = [$this->getFormObject(), substr($callback, 2)];
     }
     return $callback;
   }
@@ -833,9 +1116,16 @@ public function prepareCallback($callback) {
   /**
    * {@inheritdoc}
    */
+  public function setFormObject(FormInterface $form_object) {
+    $this->addBuildInfo('callback_object', $form_object);
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
   public function getFormObject() {
-    $build_info = $this->get('build_info');
-    return $build_info['callback_object'];
+    return $this->getBuildInfo()['callback_object'];
   }
 
   /**
diff --git a/core/lib/Drupal/Core/Form/FormStateInterface.php b/core/lib/Drupal/Core/Form/FormStateInterface.php
index fe8fc91..f58322e 100644
--- a/core/lib/Drupal/Core/Form/FormStateInterface.php
+++ b/core/lib/Drupal/Core/Form/FormStateInterface.php
@@ -96,18 +96,6 @@ public function getCacheableArray();
   public function setFormState(array $form_state_additions);
 
   /**
-   * Sets a value to an arbitrary property if it does not exist yet.
-   *
-   * @param string $property
-   *   The property to use for the value.
-   * @param mixed $value
-   *   The data to store.
-   *
-   * @return $this
-   */
-  public function setIfNotExists($property, $value);
-
-  /**
    * Sets a response for this form.
    *
    * If a response is set, it will be used during processing and returned
@@ -121,6 +109,17 @@ public function setIfNotExists($property, $value);
   public function setResponse(Response $response);
 
   /**
+   * Gets a response for this form.
+   *
+   * If a response is set, it will be used during processing and returned
+   * directly. The form will not be rebuilt or redirected.
+   *
+   * @return \Symfony\Component\HttpFoundation\Response|null
+   *   The response to return, or NULL.
+   */
+  public function getResponse();
+
+  /**
    * Sets the redirect for the form.
    *
    * @param string $route_name
@@ -165,10 +164,31 @@ public function setRedirectUrl(Url $url);
   public function getRedirect();
 
   /**
+   * Sets the entire set of arbitrary data.
+   *
+   * @param array $storage
+   *   The entire set of arbitrary data to store for this form.
+   *
+   * @return $this
+   */
+  public function setStorage(array $storage);
+
+  /**
+   * Returns the entire set of arbitrary data.
+   *
+   * @return array
+   *   The entire set of arbitrary data to store for this form.
+   */
+  public function &getStorage();
+
+  /**
    * Gets any arbitrary property.
    *
-   * @param string $property
-   *   The property to retrieve.
+   * @param string|array $property
+   *   Properties are often stored as multi-dimensional associative arrays. If
+   *   $property is a string, it will return $storage[$property]. If $property is
+   *   an array, each element of the array will be used as a nested key. If
+   *   $property = ['foo', 'bar'] it will return $storage['foo']['bar'].
    *
    * @return mixed
    *   A reference to the value for that property, or NULL if the property does
@@ -179,8 +199,12 @@ public function &get($property);
   /**
    * Sets a value to an arbitrary property.
    *
-   * @param string $property
-   *   The property to use for the value.
+   * @param string|array $property
+   *   Properties are often stored as multi-dimensional associative arrays. If
+   *   $property is a string, it will use $storage[$property] = $value. If
+   *   $property is an array, each element of the array will be used as a nested
+   *   key. If $property = ['foo', 'bar'] it will use
+   *   $storage['foo']['bar'] = $value.
    * @param mixed $value
    *   The value to set.
    *
@@ -189,12 +213,40 @@ public function &get($property);
   public function set($property, $value);
 
   /**
+   * Determines if an arbitrary property is present.
+   *
    * @param string $property
-   *   The property to use for the value.
+   *   Properties are often stored as multi-dimensional associative arrays. If
+   *   $property is a string, it will return isset($storage[$property]). If
+   *   $property is an array, each element of the array will be used as a nested
+   *   key. If $property = ['foo', 'bar'] it will return
+   *   isset($storage['foo']['bar']).
    */
   public function has($property);
 
   /**
+   * Sets the build info for the form.
+   *
+   * @param array $build_info
+   *   An array of build info.
+   *
+   * @return $this
+   *
+   * @see \Drupal\Core\Form\FormState::$build_info
+   */
+  public function setBuildInfo(array $build_info);
+
+  /**
+   * Returns the build info for the form.
+   *
+   * @return array
+   *   An array of build info.
+   *
+   * @see \Drupal\Core\Form\FormState::$build_info
+   */
+  public function getBuildInfo();
+
+  /**
    * Adds a value to the build info.
    *
    * @param string $property
@@ -253,6 +305,19 @@ public function &getValues();
   public function &getValue($key, $default = NULL);
 
   /**
+   * Sets the submitted form values.
+   *
+   * This should be avoided, since these values have been validated already. Use
+   * self::setUserInput() instead.
+   *
+   * @param array $values
+   *   The multi-dimensional associative array of form values.
+   *
+   * @return $this
+   */
+  public function setValues(array $values);
+
+  /**
    * Sets the submitted form value for a specific key.
    *
    * @param string|array $key
@@ -335,7 +400,7 @@ public function isValueEmpty($key);
    *
    * @return $this
    */
-  public function setValueForElement($element, $value);
+  public function setValueForElement(array $element, $value);
 
   /**
    * Determines if any forms have any errors.
@@ -448,7 +513,7 @@ public function setErrorByName($name, $message = '');
    *
    * @return $this
    */
-  public function setError(&$element, $message = '');
+  public function setError(array &$element, $message = '');
 
   /**
    * Clears all errors against all form elements made by self::setErrorByName().
@@ -475,7 +540,7 @@ public function getErrors();
    * @return string|null
    *   Either the error message for this element or NULL if there are no errors.
    */
-  public function getError($element);
+  public function getError(array $element);
 
   /**
    * Sets the form to be rebuilt after processing.
@@ -488,6 +553,14 @@ public function getError($element);
   public function setRebuild($rebuild = TRUE);
 
   /**
+   * Determines if the form should be rebuilt after processing.
+   *
+   * @return bool
+   *   TRUE if the form should be rebuilt, FALSE otherwise.
+   */
+  public function getRebuild();
+
+  /**
    * Converts support notations for a form callback to a valid callable.
    *
    * Specifically, supports methods on the form/callback object as strings when
@@ -509,4 +582,406 @@ public function prepareCallback($callback);
    */
   public function getFormObject();
 
+  /**
+   * Sets the form object that is responsible for building this form.
+   *
+   * @param \Drupal\Core\Form\FormInterface $form_object
+   *   The form object.
+   *
+   * @return $this
+   */
+  public function setFormObject(FormInterface $form_object);
+
+  /**
+   * Sets this form to always be processed.
+   *
+   * This should only be used on RESTful GET forms that do NOT write data, as
+   * this could lead to security issues. It is useful so that searches do not
+   * need to have a form_id in their query arguments to trigger the search.
+   *
+   * @param bool $always_process
+   *   TRUE if the form should always be processed, FALSE otherwise.
+   *
+   * @return $this
+   */
+  public function setAlwaysProcess($always_process = TRUE);
+
+  /**
+   * Determines if this form should always be processed.
+   *
+   * @return bool
+   *   TRUE if the form should always be processed, FALSE otherwise.
+   */
+  public function getAlwaysProcess();
+
+  /**
+   * Stores the submit and button elements for the form.
+   *
+   * @param array $buttons
+   *   The submit and button elements.
+   *
+   * @return $this
+   */
+  public function setButtons(array $buttons);
+
+  /**
+   * Returns the submit and button elements for the form.
+   *
+   * @return array
+   *   The submit and button elements.
+   */
+  public function getButtons();
+
+  /**
+   * Sets this form to be cached.
+   *
+   * @param bool $cache
+   *   TRUE if the form should be cached, FALSE otherwise.
+   *
+   * @return $this
+   */
+  public function setCached($cache = TRUE);
+
+  /**
+   * Determines if the form should be cached.
+   *
+   * @return bool
+   *   TRUE if the form should be cached, FALSE otherwise.
+   */
+  public function isCached();
+
+  /**
+   * Prevents the form from being cached.
+   *
+   * @return $this
+   */
+  public function disableCache();
+
+  /**
+   * Sets that the form was submitted and has been processed and executed.
+   *
+   * @return $this
+   */
+  public function setExecuted();
+
+  /**
+   * Determines if the form was submitted and has been processed and executed.
+   *
+   * @return bool
+   *   TRUE if the form was submitted and has been processed and executed.
+   */
+  public function isExecuted();
+
+  /**
+   * Sets references to details elements to render them within vertical tabs.
+   *
+   * @param array $groups
+   *   References to details elements to render them within vertical tabs.
+   *
+   * @return $this
+   */
+  public function setGroups(array $groups);
+
+  /**
+   * Returns references to details elements to render them within vertical tabs.
+   *
+   * @return array
+   */
+  public function getGroups();
+
+  /**
+   * Sets that this form has a file element.
+   *
+   * @param bool $has_file_element
+   *   Whether this form has a file element.
+   *
+   * @return $this
+   */
+  public function setHasFileElement($has_file_element = TRUE);
+
+  /**
+   * Returns whether this form has a file element.
+   *
+   * @return bool
+   *   Whether this form has a file element.
+   */
+  public function hasFileElement();
+
+  /**
+   * Sets the limited validation error sections.
+   *
+   * @param array|null $limit_validation_errors
+   *   The limited validation error sections.
+   *
+   * @return $this
+   *
+   * @see \Drupal\Core\Form\FormState::$limit_validation_errors
+   */
+  public function setLimitValidationErrors($limit_validation_errors);
+
+  /**
+   * Retrieves the limited validation error sections.
+   *
+   * @return array|null
+   *   The limited validation error sections.
+   *
+   * @see \Drupal\Core\Form\FormState::$limit_validation_errors
+   */
+  public function getLimitValidationErrors();
+
+  /**
+   * Sets the HTTP form method.
+   *
+   * @param string $method
+   *   The HTTP form method.
+   *
+   * @see \Drupal\Core\Form\FormState::$method
+   *
+   * @return $this
+   */
+  public function setMethod($method);
+
+  /**
+   * Returns the HTTP form method.
+   *
+   * @return string
+   *   The HTTP form method.
+   *
+   * @see \Drupal\Core\Form\FormState::$method
+   */
+  public function getMethod();
+
+  /**
+   * Enforces that validation is run.
+   *
+   * @param bool $must_validate
+   *   If TRUE, validation will always be run.
+   *
+   * @return $this
+   */
+  public function setValidationEnforced($must_validate = TRUE);
+
+  /**
+   * Checks if validation is enforced.
+   *
+   * @return bool
+   *   If TRUE, validation will always be run.
+   */
+  public function isValidationEnforced();
+
+  /**
+   * Prevents the form from redirecting.
+   *
+   * @param bool $no_redirect
+   *  If TRUE, the form will not redirect.
+   *
+   * @return $this
+   */
+  public function disableRedirect($no_redirect = TRUE);
+
+  /**
+   * Determines if redirecting has been prevented.
+   *
+   * @return bool
+   *  If TRUE, the form will not redirect.
+   */
+  public function isRedirectDisabled();
+
+  /**
+   * Sets that the form should process input.
+   *
+   * @param bool $process_input
+   *   If TRUE, the form input will be processed.
+   *
+   * @return $this
+   */
+  public function setProcessInput($process_input = TRUE);
+
+  /**
+   * Determines if the form input will be processed.
+   *
+   * @return bool
+   *   If TRUE, the form input will be processed.
+   */
+  public function isProcessingInput();
+
+  /**
+   * Sets that this form was submitted programmatically.
+   *
+   * @param bool $programmed
+   *   If TRUE, the form was submitted programmatically.
+   *
+   * @return $this
+   */
+  public function setProgrammed($programmed = TRUE);
+
+  /**
+   * Returns if this form was submitted programmatically.
+   *
+   * @return bool
+   *   If TRUE, the form was submitted programmatically.
+   */
+  public function isProgrammed();
+
+  /**
+   * Sets if this form submission should bypass #access.
+   *
+   * @param bool $programmed_bypass_access_check
+   *   If TRUE, programmatic form submissions are processed without taking
+   *   #access into account.
+   *
+   * @return $this
+   *
+   * @see \Drupal\Core\Form\FormState::$programmed_bypass_access_check
+   */
+  public function setProgrammedBypassAccessCheck($programmed_bypass_access_check = TRUE);
+
+  /**
+   * Determines if this form submission should bypass #access.
+   *
+   * @return bool
+   *
+   * @see \Drupal\Core\Form\FormState::$programmed_bypass_access_check
+   */
+  public function isBypassingProgrammedAccessChecks();
+
+  /**
+   * Sets the rebuild info.
+   *
+   * @param array $rebuild_info
+   *   The rebuild info.
+   *
+   * @return $this
+   *
+   * @see \Drupal\Core\Form\FormState::$rebuild_info
+   */
+  public function setRebuildInfo(array $rebuild_info);
+
+  /**
+   * Gets the rebuild info.
+   *
+   * @return array
+   *   The rebuild info.
+   *
+   * @see \Drupal\Core\Form\FormState::$rebuild_info
+   */
+  public function getRebuildInfo();
+
+  /**
+   * Adds a value to the rebuild info.
+   *
+   * @param string $property
+   *   The property to use for the value.
+   * @param mixed $value
+   *   The value to set.
+   *
+   * @return $this
+   */
+  public function addRebuildInfo($property, $value);
+
+  /**
+   * Sets the submit handlers.
+   *
+   * @param array|null $submit_handlers
+   *   An array of submit handlers, or NULL if there are none.
+   *
+   * @return $this
+   */
+  public function setSubmitHandlers($submit_handlers);
+
+  /**
+   * Gets the submit handlers.
+   *
+   * @return array|null
+   *   An array of submit handlers, or NULL if there are none.
+   */
+  public function getSubmitHandlers();
+
+  /**
+   * Sets that the form has been submitted.
+   *
+   * @return $this
+   */
+  public function setSubmitted();
+
+  /**
+   * Determines if the form has been submitted.
+   *
+   * @return bool
+   *   TRUE if the form has been submitted, FALSE otherwise.
+   */
+  public function isSubmitted();
+
+  /**
+   * Sets temporary data.
+   *
+   * @param array $temporary
+   *   Temporary data accessible during the current page request only.
+   *
+   * @return $this
+   */
+  public function setTemporary(array $temporary);
+
+  /**
+   * Gets temporary data.
+   *
+   * @return array
+   *   Temporary data accessible during the current page request only.
+   */
+  public function getTemporary();
+
+  /**
+   * Sets the form element that triggered submission.
+   *
+   * @param array|null $triggering_element
+   *   The form element that triggered submission, of NULL if there is none.
+   *
+   * @return $this
+   */
+  public function setTriggeringElement($triggering_element);
+
+  /**
+   * Gets the form element that triggered submission.
+   *
+   * @return array|null
+   *   The form element that triggered submission, of NULL if there is none.
+   */
+  public function &getTriggeringElement();
+
+  /**
+   * Sets the validate handlers.
+   *
+   * @param array|null $validate_handlers
+   *   An array of validate handlers, or NULL if there are none.
+   *
+   * @return $this
+   */
+  public function setValidateHandlers($validate_handlers);
+
+  /**
+   * Gets the validate handlers.
+   *
+   * @return array|null
+   *   An array of validate handlers, or NULL if there are none.
+   */
+  public function getValidateHandlers();
+
+  /**
+   * Sets that validation has been completed.
+   *
+   * @param bool $validation_complete
+   *   TRUE if validation is complete, FALSE otherwise.
+   *
+   * @return $this
+   */
+  public function setValidationComplete($validation_complete = TRUE);
+
+  /**
+   * Determines if validation has been completed.
+   *
+   * @return bool
+   *   TRUE if validation is complete, FALSE otherwise.
+   */
+  public function isValidationComplete();
+
 }
diff --git a/core/lib/Drupal/Core/Form/FormSubmitter.php b/core/lib/Drupal/Core/Form/FormSubmitter.php
index ab7e76e..b975a70 100644
--- a/core/lib/Drupal/Core/Form/FormSubmitter.php
+++ b/core/lib/Drupal/Core/Form/FormSubmitter.php
@@ -48,7 +48,7 @@ public function __construct(RequestStack $request_stack, UrlGeneratorInterface $
    * {@inheritdoc}
    */
   public function doSubmitForm(&$form, FormStateInterface &$form_state) {
-    if (!$form_state['submitted']) {
+    if (!$form_state->isSubmitted()) {
       return;
     }
 
@@ -63,7 +63,7 @@ public function doSubmitForm(&$form, FormStateInterface &$form_state) {
       // Store $form_state information in the batch definition.
       $batch['form_state'] = $form_state;
 
-      $batch['progressive'] = !$form_state['programmed'];
+      $batch['progressive'] = !$form_state->isProgrammed();
       $response = batch_process();
       if ($batch['progressive']) {
         return $response;
@@ -76,15 +76,15 @@ public function doSubmitForm(&$form, FormStateInterface &$form_state) {
     }
 
     // Set a flag to indicate the the form has been processed and executed.
-    $form_state['executed'] = TRUE;
+    $form_state->setExecuted();
 
     // If no response has been set, process the form redirect.
-    if (!$form_state->has('response') && $redirect = $this->redirectForm($form_state)) {
+    if (!$form_state->getResponse() && $redirect = $this->redirectForm($form_state)) {
       $form_state->setResponse($redirect);
     }
 
     // If there is a response was set, return it instead of continuing.
-    if (($response = $form_state->get('response')) && $response instanceof Response) {
+    if (($response = $form_state->getResponse()) && $response instanceof Response) {
       return $response;
     }
   }
@@ -94,16 +94,11 @@ public function doSubmitForm(&$form, FormStateInterface &$form_state) {
    */
   public function executeSubmitHandlers(&$form, FormStateInterface &$form_state) {
     // If there was a button pressed, use its handlers.
-    if (!empty($form_state['submit_handlers'])) {
-      $handlers = $form_state['submit_handlers'];
-    }
+    $handlers = $form_state->getSubmitHandlers();
     // Otherwise, check for a form-level handler.
-    elseif (!empty($form['#submit'])) {
+    if (!$handlers && !empty($form['#submit'])) {
       $handlers = $form['#submit'];
     }
-    else {
-      $handlers = array();
-    }
 
     foreach ($handlers as $callback) {
       // Check if a previous _submit handler has set a batch, but make sure we
diff --git a/core/lib/Drupal/Core/Form/FormValidator.php b/core/lib/Drupal/Core/Form/FormValidator.php
index 5efedb0..504a606 100644
--- a/core/lib/Drupal/Core/Form/FormValidator.php
+++ b/core/lib/Drupal/Core/Form/FormValidator.php
@@ -68,16 +68,11 @@ public function __construct(RequestStack $request_stack, TranslationInterface $s
    */
   public function executeValidateHandlers(&$form, FormStateInterface &$form_state) {
     // If there was a button pressed, use its handlers.
-    if (isset($form_state['validate_handlers'])) {
-      $handlers = $form_state['validate_handlers'];
-    }
+    $handlers = $form_state->getValidateHandlers();
     // Otherwise, check for a form-level handler.
-    elseif (isset($form['#validate'])) {
+    if (!$handlers && isset($form['#validate'])) {
       $handlers = $form['#validate'];
     }
-    else {
-      $handlers = array();
-    }
 
     foreach ($handlers as $callback) {
       call_user_func_array($form_state->prepareCallback($callback), array(&$form, &$form_state));
@@ -90,12 +85,12 @@ public function executeValidateHandlers(&$form, FormStateInterface &$form_state)
   public function validateForm($form_id, &$form, FormStateInterface &$form_state) {
     // If this form is flagged to always validate, ensure that previous runs of
     // validation are ignored.
-    if (!empty($form_state['must_validate'])) {
-      $form_state['validation_complete'] = FALSE;
+    if ($form_state->isValidationEnforced()) {
+      $form_state->setValidationComplete(FALSE);
     }
 
     // If this form has completed validation, do not validate again.
-    if (!empty($form_state['validation_complete'])) {
+    if ($form_state->isValidationComplete()) {
       return;
     }
 
@@ -138,9 +133,10 @@ public function validateForm($form_id, &$form, FormStateInterface &$form_state)
   protected function handleErrorsWithLimitedValidation(&$form, FormStateInterface &$form_state, $form_id) {
     // If validation errors are limited then remove any non validated form values,
     // so that only values that passed validation are left for submit callbacks.
-    if (isset($form_state['triggering_element']['#limit_validation_errors']) && $form_state['triggering_element']['#limit_validation_errors'] !== FALSE) {
+    $triggering_element = $form_state->getTriggeringElement();
+    if (isset($triggering_element['#limit_validation_errors']) && $triggering_element['#limit_validation_errors'] !== FALSE) {
       $values = array();
-      foreach ($form_state['triggering_element']['#limit_validation_errors'] as $section) {
+      foreach ($triggering_element['#limit_validation_errors'] as $section) {
         // If the section exists within $form_state->getValues(), even if the
         // value is NULL, copy it to $values.
         $section_exists = NULL;
@@ -153,13 +149,13 @@ protected function handleErrorsWithLimitedValidation(&$form, FormStateInterface
       // allow the value of the clicked button to be retained in its normal
       // $form_state->getValues() locations, even if these locations are not
       // included in #limit_validation_errors.
-      if (!empty($form_state['triggering_element']['#is_button'])) {
-        $button_value = $form_state['triggering_element']['#value'];
+      if (!empty($triggering_element['#is_button'])) {
+        $button_value = $triggering_element['#value'];
 
         // Like all input controls, the button value may be in the location
         // dictated by #parents. If it is, copy it to $values, but do not
         // override what may already be in $values.
-        $parents = $form_state['triggering_element']['#parents'];
+        $parents = $triggering_element['#parents'];
         if (!NestedArray::keyExists($values, $parents) && NestedArray::getValue($form_state->getValues(), $parents) === $button_value) {
           NestedArray::setValue($values, $parents, $button_value);
         }
@@ -168,12 +164,12 @@ protected function handleErrorsWithLimitedValidation(&$form, FormStateInterface
         // $form_state->getValue(BUTTON_NAME). If it's still there, after
         // validation handlers have run, copy it to $values, but do not override
         // what may already be in $values.
-        $name = $form_state['triggering_element']['#name'];
+        $name = $triggering_element['#name'];
         if (!isset($values[$name]) && $form_state->getValue($name) === $button_value) {
           $values[$name] = $button_value;
         }
       }
-      $form_state->set('values', $values);
+      $form_state->setValues($values);
     }
   }
 
@@ -191,7 +187,7 @@ protected function finalizeValidation(&$form, FormStateInterface &$form_state, $
     // After validation, loop through and assign each element its errors.
     $this->setElementErrorsFromFormState($form, $form_state);
     // Mark this form as validated.
-    $form_state['validation_complete'] = TRUE;
+    $form_state->setValidationComplete();
   }
 
   /**
@@ -209,7 +205,7 @@ protected function finalizeValidation(&$form, FormStateInterface &$form_state, $
    *   an explicit copy of the values for the sake of simplicity. Validation
    *   handlers can also $form_state to pass information on to submit handlers.
    *   For example:
-   *     $form_state['data_for_submission'] = $data;
+   *     $form_state->set('data_for_submission', $data);
    *   This technique is useful when validation requires file parsing,
    *   web service requests, or other expensive requests that should
    *   not be repeated in the submission step.
@@ -233,7 +229,7 @@ protected function doValidateForm(&$elements, FormStateInterface &$form_state, $
       }
 
       // Set up the limited validation for errors.
-      $form_state['limit_validation_errors'] = $this->determineLimitValidationErrors($form_state);
+      $form_state->setLimitValidationErrors($this->determineLimitValidationErrors($form_state));
 
       // Make sure a value is passed when the field is required.
       if (isset($elements['#needs_validation']) && $elements['#required']) {
@@ -293,7 +289,7 @@ protected function doValidateForm(&$elements, FormStateInterface &$form_state, $
     // Done validating this element, so turn off error suppression.
     // self::doValidateForm() turns it on again when starting on the next
     // element, if it's still appropriate to do so.
-    $form_state['limit_validation_errors'] = NULL;
+    $form_state->setLimitValidationErrors(NULL);
   }
 
   /**
@@ -307,7 +303,7 @@ protected function doValidateForm(&$elements, FormStateInterface &$form_state, $
    *   an explicit copy of the values for the sake of simplicity. Validation
    *   handlers can also $form_state to pass information on to submit handlers.
    *   For example:
-   *     $form_state['data_for_submission'] = $data;
+   *     $form_state->set('data_for_submission', $data);
    *   This technique is useful when validation requires file parsing,
    *   web service requests, or other expensive requests that should
    *   not be repeated in the submission step.
@@ -374,8 +370,9 @@ protected function determineLimitValidationErrors(FormStateInterface &$form_stat
     // is ignored if submit handlers will run, but the element doesn't have a
     // #submit property, because it's too large a security risk to have any
     // invalid user input when executing form-level submit handlers.
-    if (isset($form_state['triggering_element']['#limit_validation_errors']) && ($form_state['triggering_element']['#limit_validation_errors'] !== FALSE) && !($form_state['submitted'] && !isset($form_state['triggering_element']['#submit']))) {
-      return $form_state['triggering_element']['#limit_validation_errors'];
+    $triggering_element = $form_state->getTriggeringElement();
+    if (isset($triggering_element['#limit_validation_errors']) && ($triggering_element['#limit_validation_errors'] !== FALSE) && !($form_state->isSubmitted() && !isset($triggering_element['#submit']))) {
+      return $triggering_element['#limit_validation_errors'];
     }
     // If submit handlers won't run (due to the submission having been
     // triggered by an element whose #executes_submit_callback property isn't
@@ -386,7 +383,7 @@ protected function determineLimitValidationErrors(FormStateInterface &$form_stat
     // types, #limit_validation_errors defaults to FALSE (via
     // system_element_info()), so that full validation is their default
     // behavior.
-    elseif (isset($form_state['triggering_element']) && !isset($form_state['triggering_element']['#limit_validation_errors']) && !$form_state['submitted']) {
+    elseif ($triggering_element && !isset($triggering_element['#limit_validation_errors']) && !$form_state->isSubmitted()) {
       return array();
     }
     // As an extra security measure, explicitly turn off error suppression if
diff --git a/core/lib/Drupal/Core/Form/FormValidatorInterface.php b/core/lib/Drupal/Core/Form/FormValidatorInterface.php
index 061cf93..6f1cc52 100644
--- a/core/lib/Drupal/Core/Form/FormValidatorInterface.php
+++ b/core/lib/Drupal/Core/Form/FormValidatorInterface.php
@@ -47,7 +47,7 @@ public function executeValidateHandlers(&$form, FormStateInterface &$form_state)
    *   an explicit copy of the values for the sake of simplicity. Validation
    *   handlers can also use $form_state to pass information on to submit
    *   handlers. For example:
-   *     $form_state['data_for_submission'] = $data;
+   *     $form_state->set('data_for_submission', $data);
    *   This technique is useful when validation requires file parsing,
    *   web service requests, or other expensive requests that should
    *   not be repeated in the submission step.
diff --git a/core/lib/Drupal/Core/Installer/Form/SelectLanguageForm.php b/core/lib/Drupal/Core/Installer/Form/SelectLanguageForm.php
index 5a00f24..79d9082 100644
--- a/core/lib/Drupal/Core/Installer/Form/SelectLanguageForm.php
+++ b/core/lib/Drupal/Core/Installer/Form/SelectLanguageForm.php
@@ -93,8 +93,9 @@ public function buildForm(array $form, FormStateInterface $form_state, $install_
    * {@inheritdoc}
    */
   public function submitForm(array &$form, FormStateInterface $form_state) {
-    $install_state = &$form_state['build_info']['args'][0];
-    $install_state['parameters']['langcode'] = $form_state->getValue('langcode');
+    $build_info = $form_state->getBuildInfo();
+    $build_info['args'][0]['parameters']['langcode'] = $form_state->getValue('langcode');
+    $form_state->setBuildInfo($build_info);
   }
 
 }
diff --git a/core/lib/Drupal/Core/Installer/Form/SiteSettingsForm.php b/core/lib/Drupal/Core/Installer/Form/SiteSettingsForm.php
index 48fadf6..8cf65c9 100644
--- a/core/lib/Drupal/Core/Installer/Form/SiteSettingsForm.php
+++ b/core/lib/Drupal/Core/Installer/Form/SiteSettingsForm.php
@@ -126,7 +126,7 @@ public function validateForm(array &$form, FormStateInterface $form_state) {
     $database['namespace'] = substr($install_namespace, 0, strrpos($install_namespace, '\\'));
     $database['driver'] = $driver;
 
-    $form_state['storage']['database'] = $database;
+    $form_state->set('database', $database);
     $errors = install_database_errors($database, $form_state->getValue('settings_file'));
     foreach ($errors as $name => $message) {
       $form_state->setErrorByName($name, $message);
@@ -141,7 +141,7 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
 
     // Update global settings array and save.
     $settings = array();
-    $database = $form_state['storage']['database'];
+    $database = $form_state->get('database');
     $settings['databases']['default']['default'] = (object) array(
       'value'    => $database,
       'required' => TRUE,
diff --git a/core/lib/Drupal/Core/Render/Element/MachineName.php b/core/lib/Drupal/Core/Render/Element/MachineName.php
index 6301f35..6b9a29b 100644
--- a/core/lib/Drupal/Core/Render/Element/MachineName.php
+++ b/core/lib/Drupal/Core/Render/Element/MachineName.php
@@ -142,7 +142,7 @@ public static function processMachineName(&$element, FormStateInterface $form_st
     // complete form in $form_state. By reference, because we may need to append
     // a #field_suffix that will hold the live preview.
     $key_exists = NULL;
-    $source = NestedArray::getValue($form_state['complete_form'], $element['#machine_name']['source'], $key_exists);
+    $source = NestedArray::getValue($form_state->getCompleteForm(), $element['#machine_name']['source'], $key_exists);
     if (!$key_exists) {
       return $element;
     }
@@ -160,7 +160,7 @@ public static function processMachineName(&$element, FormStateInterface $form_st
       $source['#field_suffix'] = SafeMarkup::set($source['#field_suffix'] . ' <small id="' . $suffix_id . '">&nbsp;</small>');
 
       $parents = array_merge($element['#machine_name']['source'], array('#field_suffix'));
-      NestedArray::setValue($form_state['complete_form'], $parents, $source['#field_suffix']);
+      NestedArray::setValue($form_state->getCompleteForm(), $parents, $source['#field_suffix']);
     }
 
     $js_settings = array(
diff --git a/core/lib/Drupal/Core/Render/Element/PasswordConfirm.php b/core/lib/Drupal/Core/Render/Element/PasswordConfirm.php
index f3ea4e8..9bc32b7 100644
--- a/core/lib/Drupal/Core/Render/Element/PasswordConfirm.php
+++ b/core/lib/Drupal/Core/Render/Element/PasswordConfirm.php
@@ -82,7 +82,7 @@ public static function validatePasswordConfirm(&$element, FormStateInterface $fo
         $form_state->setError($element, t('The specified passwords do not match.'));
       }
     }
-    elseif ($element['#required'] && !empty($form_state['input'])) {
+    elseif ($element['#required'] && $form_state->getUserInput()) {
       $form_state->setError($element, t('Password field is required.'));
     }
 
diff --git a/core/lib/Drupal/Core/Render/Element/RenderElement.php b/core/lib/Drupal/Core/Render/Element/RenderElement.php
index 3cbe18c..c3a74d8 100644
--- a/core/lib/Drupal/Core/Render/Element/RenderElement.php
+++ b/core/lib/Drupal/Core/Render/Element/RenderElement.php
@@ -128,7 +128,7 @@ public static function preRenderGroup($element) {
   public static function processAjaxForm(&$element, FormStateInterface $form_state, &$complete_form) {
     $element = ajax_pre_render_element($element);
     if (!empty($element['#ajax_processed'])) {
-      $form_state['cache'] = TRUE;
+      $form_state->setCached();
     }
     return $element;
   }
@@ -156,14 +156,15 @@ public static function processGroup(&$element, FormStateInterface $form_state, &
 
     // Each details element forms a new group. The #type 'vertical_tabs' basically
     // only injects a new details element.
-    $form_state['groups'][$parents]['#group_exists'] = TRUE;
-    $element['#groups'] = &$form_state['groups'];
+    $groups = &$form_state->getGroups();
+    $groups[$parents]['#group_exists'] = TRUE;
+    $element['#groups'] = &$groups;
 
     // Process vertical tabs group member details elements.
     if (isset($element['#group'])) {
       // Add this details element to the defined group (by reference).
       $group = $element['#group'];
-      $form_state['groups'][$group][] = &$element;
+      $groups[$group][] = &$element;
     }
 
     return $element;
diff --git a/core/lib/Drupal/Core/Render/Element/Table.php b/core/lib/Drupal/Core/Render/Element/Table.php
index 191de68..adbb165 100644
--- a/core/lib/Drupal/Core/Render/Element/Table.php
+++ b/core/lib/Drupal/Core/Render/Element/Table.php
@@ -219,7 +219,8 @@ public static function processTable(&$element, FormStateInterface $form_state, &
   public static function validateTable(&$element, FormStateInterface $form_state, &$complete_form) {
     // Skip this validation if the button to submit the form does not require
     // selected table row data.
-    if (empty($form_state['triggering_element']['#tableselect'])) {
+    $trigerring_element = $form_state->getTriggeringElement();
+    if (empty($trigerring_element['#tableselect'])) {
       return;
     }
     if ($element['#multiple']) {
diff --git a/core/modules/aggregator/tests/src/Unit/Plugin/AggregatorPluginSettingsBaseTest.php b/core/modules/aggregator/tests/src/Unit/Plugin/AggregatorPluginSettingsBaseTest.php
index c10d3ad..ed5e0b8 100644
--- a/core/modules/aggregator/tests/src/Unit/Plugin/AggregatorPluginSettingsBaseTest.php
+++ b/core/modules/aggregator/tests/src/Unit/Plugin/AggregatorPluginSettingsBaseTest.php
@@ -74,7 +74,10 @@ protected function setUp() {
    */
   public function testSettingsForm() {
     // Emulate a form state of a sumbitted form.
-    $form_state = new FormState(array('values' => array('dummy_length' => '', 'aggregator_allowed_html_tags' => '')));
+    $form_state = (new FormState())->setValues([
+      'dummy_length' => '',
+      'aggregator_allowed_html_tags' => '',
+    ]);
 
     $test_processor = $this->getMock(
       'Drupal\aggregator_test\Plugin\aggregator\processor\TestProcessor',
diff --git a/core/modules/block/src/BlockForm.php b/core/modules/block/src/BlockForm.php
index e3d442d..82c6cc7 100644
--- a/core/modules/block/src/BlockForm.php
+++ b/core/modules/block/src/BlockForm.php
@@ -61,7 +61,7 @@ public function form(array $form, FormStateInterface $form_state) {
     if (!$theme = $entity->get('theme')) {
       $theme = $this->config('system.theme')->get('default');
     }
-    $form_state['block_theme'] = $theme;
+    $form_state->set('block_theme', $theme);
 
     $form['#tree'] = TRUE;
     $form['settings'] = $entity->getPlugin()->buildConfigurationForm(array(), $form_state);
@@ -149,9 +149,7 @@ public function validate(array $form, FormStateInterface $form_state) {
 
     // The Block Entity form puts all block plugin form elements in the
     // settings form element, so just pass that to the block for validation.
-    $settings = new FormState(array(
-      'values' => $form_state->getValue('settings')
-    ));
+    $settings = (new FormState())->setValues($form_state->getValue('settings'));
     // Call the plugin validate handler.
     $this->entity->getPlugin()->validateConfigurationForm($form, $settings);
     // Update the original form values.
@@ -168,9 +166,7 @@ public function submit(array $form, FormStateInterface $form_state) {
     // The Block Entity form puts all block plugin form elements in the
     // settings form element, so just pass that to the block for submission.
     // @todo Find a way to avoid this manipulation.
-    $settings = new FormState(array(
-      'values' => $form_state->getValue('settings'),
-    ));
+    $settings = (new FormState())->setValues($form_state->getValue('settings'));
 
     // Call the plugin submit handler.
     $entity->getPlugin()->submitConfigurationForm($form, $settings);
diff --git a/core/modules/block_content/src/BlockContentForm.php b/core/modules/block_content/src/BlockContentForm.php
index d919da0..5a83473 100644
--- a/core/modules/block_content/src/BlockContentForm.php
+++ b/core/modules/block_content/src/BlockContentForm.php
@@ -210,7 +210,7 @@ public function save(array $form, FormStateInterface $form_state) {
 
     if ($block->id()) {
       $form_state->setValue('id', $block->id());
-      $form_state['id'] = $block->id();
+      $form_state->set('id', $block->id());
       if ($insert) {
         if (!$theme = $block->getTheme()) {
           $theme = $this->config('system.theme')->get('default');
@@ -231,7 +231,7 @@ public function save(array $form, FormStateInterface $form_state) {
       // In the unlikely case something went wrong on save, the block will be
       // rebuilt and block form redisplayed.
       drupal_set_message($this->t('The block could not be saved.'), 'error');
-      $form_state['rebuild'] = TRUE;
+      $form_state->setRebuild();
     }
   }
 
diff --git a/core/modules/book/book.module b/core/modules/book/book.module
index 5e87f43..e67b279 100644
--- a/core/modules/book/book.module
+++ b/core/modules/book/book.module
@@ -219,7 +219,7 @@ function book_node_builder($entity_type, NodeInterface $entity, &$form, FormStat
 function book_pick_book_nojs_submit($form, FormStateInterface $form_state) {
   $node = $form_state->getFormObject()->getEntity();
   $node->book = $form_state->getValue('book');
-  $form_state['rebuild'] = TRUE;
+  $form_state->setRebuild();
 }
 
 /**
diff --git a/core/modules/ckeditor/src/Plugin/CKEditorPlugin/DrupalImage.php b/core/modules/ckeditor/src/Plugin/CKEditorPlugin/DrupalImage.php
index e6e54b8..b0c88bf 100644
--- a/core/modules/ckeditor/src/Plugin/CKEditorPlugin/DrupalImage.php
+++ b/core/modules/ckeditor/src/Plugin/CKEditorPlugin/DrupalImage.php
@@ -86,7 +86,7 @@ public function settingsForm(array $form, FormStateInterface $form_state, Editor
    */
   function validateImageUploadSettings(array $element, FormStateInterface $form_state) {
     $settings = &$form_state->getValue(array('editor', 'settings', 'plugins', 'drupalimage', 'image_upload'));
-    $form_state['editor']->setImageUploadSettings($settings);
+    $form_state->get('editor')->setImageUploadSettings($settings);
     $form_state->unsetValue(array('editor', 'settings', 'plugins', 'drupalimage'));
   }
 
diff --git a/core/modules/color/color.module b/core/modules/color/color.module
index 7010ddf..a0c0896 100644
--- a/core/modules/color/color.module
+++ b/core/modules/color/color.module
@@ -47,7 +47,8 @@ function color_theme() {
  * Implements hook_form_FORM_ID_alter().
  */
 function color_form_system_theme_settings_alter(&$form, FormStateInterface $form_state) {
-  if (isset($form_state['build_info']['args'][0]) && ($theme = $form_state['build_info']['args'][0]) && color_get_info($theme) && function_exists('gd_info')) {
+  $build_info = $form_state->getBuildInfo();
+  if (isset($build_info['args'][0]) && ($theme = $build_info['args'][0]) && color_get_info($theme) && function_exists('gd_info')) {
     $form['color'] = array(
       '#type' => 'details',
       '#title' => t('Color scheme'),
@@ -303,7 +304,8 @@ function color_palette_color_value($element, $input = FALSE, FormStateInterface
     // Start with the provided value for this textfield, and validate that if
     // necessary, falling back on the default value.
     $value = Textfield::valueCallback($element, $input, $form_state);
-    if (!$value || !isset($form_state['complete form']['#token']) || color_valid_hexadecimal_string($value) || \Drupal::csrfToken()->validate($form_state->getValue('form_token'), $form_state['complete form']['#token'])) {
+    $complete_form = $form_state->getCompleteForm();
+    if (!$value || !isset($complete_form['#token']) || color_valid_hexadecimal_string($value) || \Drupal::csrfToken()->validate($form_state->getValue('form_token'), $complete_form['#token'])) {
       return $value;
     }
     else {
diff --git a/core/modules/comment/src/CommentForm.php b/core/modules/comment/src/CommentForm.php
index 075e463..83fa356 100644
--- a/core/modules/comment/src/CommentForm.php
+++ b/core/modules/comment/src/CommentForm.php
@@ -98,8 +98,9 @@ public function form(array $form, FormStateInterface $form_state) {
       $form['#action'] = url('comment/reply/' . $entity->getEntityTypeId() . '/' . $entity->id() . '/' . $field_name);
     }
 
-    if (isset($form_state['comment_preview'])) {
-      $form += $form_state['comment_preview'];
+    $comment_preview = $form_state->get('comment_preview');
+    if (isset($comment_preview)) {
+      $form += $comment_preview;
     }
 
     $form['author'] = array();
@@ -115,7 +116,7 @@ public function form(array $form, FormStateInterface $form_state) {
     if ($is_admin) {
       $author = $comment->getAuthorName();
       $status = $comment->getStatus();
-      if (empty($form_state['comment_preview'])) {
+      if (empty($comment_preview)) {
         $form['#title'] = $this->t('Edit comment %title', array(
           '%title' => $comment->getSubject(),
         ));
@@ -238,7 +239,7 @@ protected function actions(array $form, FormStateInterface $form_state) {
 
     // Only show the save button if comment previews are optional or if we are
     // already previewing the submission.
-    $element['submit']['#access'] = ($comment->id() && $this->currentUser->hasPermission('administer comments')) || $preview_mode != DRUPAL_REQUIRED || isset($form_state['comment_preview']);
+    $element['submit']['#access'] = ($comment->id() && $this->currentUser->hasPermission('administer comments')) || $preview_mode != DRUPAL_REQUIRED || $form_state->get('comment_preview');
 
     $element['preview'] = array(
       '#type' => 'submit',
@@ -348,10 +349,10 @@ public function submit(array $form, FormStateInterface $form_state) {
    *   The current state of the form.
    */
   public function preview(array &$form, FormStateInterface $form_state) {
-    $comment = $this->entity;
-    $form_state['comment_preview'] = comment_preview($comment, $form_state);
-    $form_state['comment_preview']['#title'] = $this->t('Preview comment');
-    $form_state['rebuild'] = TRUE;
+    $comment_preview = comment_preview($this->entity, $form_state);
+    $comment_preview['#title'] = $this->t('Preview comment');
+    $form_state->set('comment_preview', $comment_preview);
+    $form_state->setRebuild();
   }
 
   /**
diff --git a/core/modules/config/src/Form/ConfigSingleExportForm.php b/core/modules/config/src/Form/ConfigSingleExportForm.php
index 6a22b16..8e30e6f 100644
--- a/core/modules/config/src/Form/ConfigSingleExportForm.php
+++ b/core/modules/config/src/Form/ConfigSingleExportForm.php
@@ -123,10 +123,10 @@ public function buildForm(array $form, FormStateInterface $form_state, $config_t
       '#suffix' => '</div>',
     );
     if ($config_type && $config_name) {
-      $fake_form_state = new FormState(array('values' => array(
+      $fake_form_state = (new FormState())->setValues([
         'config_type' => $config_type,
         'config_name' => $config_name,
-      )));
+      ]);
       $form['export'] = $this->updateExport($form, $fake_form_state);
     }
     return $form;
diff --git a/core/modules/config/src/Form/ConfigSingleImportForm.php b/core/modules/config/src/Form/ConfigSingleImportForm.php
index 5150746..5d7a541 100644
--- a/core/modules/config/src/Form/ConfigSingleImportForm.php
+++ b/core/modules/config/src/Form/ConfigSingleImportForm.php
@@ -236,7 +236,7 @@ public function validateForm(array &$form, FormStateInterface $form_state) {
   public function submitForm(array &$form, FormStateInterface $form_state) {
     // If this form has not yet been confirmed, store the values and rebuild.
     if (!$this->data) {
-      $form_state['rebuild'] = TRUE;
+      $form_state->setRebuild();
       $this->data = $form_state->getValues();
       return;
     }
diff --git a/core/modules/config/src/Form/ConfigSync.php b/core/modules/config/src/Form/ConfigSync.php
index 70c0c73..05682a0 100644
--- a/core/modules/config/src/Form/ConfigSync.php
+++ b/core/modules/config/src/Form/ConfigSync.php
@@ -179,7 +179,7 @@ public function buildForm(array $form, FormStateInterface $form_state) {
     }
     else {
       // Store the comparer for use in the submit.
-      $form_state['storage_comparer'] = $storage_comparer;
+      $form_state->set('storage_comparer', $storage_comparer);
     }
 
     // Add the AJAX library to the form for dialog support.
@@ -273,7 +273,7 @@ public function buildForm(array $form, FormStateInterface $form_state) {
    */
   public function submitForm(array &$form, FormStateInterface $form_state) {
     $config_importer = new ConfigImporter(
-      $form_state['storage_comparer'],
+      $form_state->get('storage_comparer'),
       $this->eventDispatcher,
       $this->configManager,
       $this->lock,
diff --git a/core/modules/config_translation/src/Form/ConfigTranslationFormBase.php b/core/modules/config_translation/src/Form/ConfigTranslationFormBase.php
index d3d63be..d195d24 100644
--- a/core/modules/config_translation/src/Form/ConfigTranslationFormBase.php
+++ b/core/modules/config_translation/src/Form/ConfigTranslationFormBase.php
@@ -173,9 +173,9 @@ public function buildForm(array $form, FormStateInterface $form_state, Request $
     $this->languageManager->setConfigOverrideLanguage($this->language);
 
     // Add some information to the form state for easier form altering.
-    $form_state['config_translation_mapper'] = $this->mapper;
-    $form_state['config_translation_language'] = $this->language;
-    $form_state['config_translation_source_language'] = $this->sourceLanguage;
+    $form_state->set('config_translation_mapper', $this->mapper);
+    $form_state->set('config_translation_language', $this->language);
+    $form_state->set('config_translation_source_language', $this->sourceLanguage);
 
     $form['#attached']['library'][] = 'config_translation/drupal.config_translation.admin';
 
diff --git a/core/modules/config_translation/src/FormElement/DateFormat.php b/core/modules/config_translation/src/FormElement/DateFormat.php
index 9076d2a..b19c66c 100644
--- a/core/modules/config_translation/src/FormElement/DateFormat.php
+++ b/core/modules/config_translation/src/FormElement/DateFormat.php
@@ -58,7 +58,7 @@ public function getFormElement(DataDefinitionInterface $definition, LanguageInte
   public static function ajaxSample(array $form, FormStateInterface $form_state) {
     $response = new AjaxResponse();
 
-    $format_value = NestedArray::getValue($form_state->getValues(), $form_state['triggering_element']['#array_parents']);
+    $format_value = NestedArray::getValue($form_state->getValues(), $form_state->getTriggeringElement()['#array_parents']);
     if (!empty($format_value)) {
       // Format the date with a custom date format with the given pattern.
       // The object is not instantiated in an Ajax context, so $this->t()
diff --git a/core/modules/contact/src/MessageForm.php b/core/modules/contact/src/MessageForm.php
index ddadc15..20e6559 100644
--- a/core/modules/contact/src/MessageForm.php
+++ b/core/modules/contact/src/MessageForm.php
@@ -168,7 +168,7 @@ public function actions(array $form, FormStateInterface $form_state) {
   public function preview(array $form, FormStateInterface $form_state) {
     $message = $this->entity;
     $message->preview = TRUE;
-    $form_state['rebuild'] = TRUE;
+    $form_state->setRebuild();
   }
 
   /**
diff --git a/core/modules/content_translation/content_translation.module b/core/modules/content_translation/content_translation.module
index dddad7c..b506079 100644
--- a/core/modules/content_translation/content_translation.module
+++ b/core/modules/content_translation/content_translation.module
@@ -603,7 +603,7 @@ function content_translation_entity_extra_field_info() {
  * Implements hook_form_FORM_ID_alter() for 'field_ui_instance_edit_form'.
  */
 function content_translation_form_field_ui_field_instance_edit_form_alter(array &$form, FormStateInterface $form_state) {
-  $instance = $form_state['instance'];
+  $instance = $form_state->get('instance');
   $bundle_is_translatable = content_translation_enabled($instance->entity_type, $instance->bundle);
 
   $form['instance']['translatable'] = array(
@@ -674,11 +674,12 @@ function content_translation_element_info_alter(&$type) {
  *   The current state of the form.
  */
 function content_translation_enable_widget($entity_type, $bundle, array &$form, FormStateInterface $form_state) {
-  $key = $form_state['content_translation']['key'];
-  if (!isset($form_state['language'][$key])) {
-    $form_state['language'][$key] = array();
+  $key = $form_state->get(['content_translation', 'key']);
+  if (!$context = $form_state->get(['language', $key])) {
+    $context = [];
   }
-  $form_state['language'][$key] += array('entity_type' => $entity_type, 'bundle' => $bundle);
+  $context += ['entity_type' => $entity_type, 'bundle' => $bundle];
+  $form_state->set(['language', $key], $context);
   $element = content_translation_language_configuration_element_process(array('#name' => $key), $form_state, $form);
   unset($element['content_translation']['#element_validate']);
   return $element;
@@ -695,8 +696,9 @@ function content_translation_enable_widget($entity_type, $bundle, array &$form,
  */
 function content_translation_language_configuration_element_process(array $element, FormStateInterface $form_state, array &$form) {
   if (empty($element['#content_translation_skip_alter']) && \Drupal::currentUser()->hasPermission('administer content translation')) {
-    $form_state['content_translation']['key'] = $element['#name'];
-    $context = $form_state['language'][$element['#name']];
+    $key = $element['#name'];
+    $form_state->set(['content_translation', 'key'], $key);
+    $context = $form_state->get(['language', $key]);
 
     $element['content_translation'] = array(
       '#type' => 'checkbox',
@@ -721,7 +723,7 @@ function content_translation_language_configuration_element_process(array $eleme
  * @see content_translation_language_configuration_element_submit()
  */
 function content_translation_language_configuration_element_validate($element, FormStateInterface $form_state, array $form) {
-  $key = $form_state['content_translation']['key'];
+  $key = $form_state->get(['content_translation', 'key']);
   $values = $form_state->getValue($key);
   if (!$values['language_show'] && $values['content_translation'] && \Drupal::languageManager()->isLanguageLocked($values['langcode'])) {
     foreach (\Drupal::languageManager()->getLanguages(LanguageInterface::STATE_LOCKED) as $language) {
@@ -742,8 +744,8 @@ function content_translation_language_configuration_element_validate($element, F
  * @see content_translation_language_configuration_element_validate()
  */
 function content_translation_language_configuration_element_submit(array $form, FormStateInterface $form_state) {
-  $key = $form_state['content_translation']['key'];
-  $context = $form_state['language'][$key];
+  $key = $form_state->get(['content_translation', 'key']);
+  $context = $form_state->get(['language', $key]);
   $enabled = $form_state->getValue(array($key, 'content_translation'));
 
   if (content_translation_enabled($context['entity_type'], $context['bundle']) != $enabled) {
diff --git a/core/modules/content_translation/src/ContentTranslationHandler.php b/core/modules/content_translation/src/ContentTranslationHandler.php
index 60eb6f3..dcc8746 100644
--- a/core/modules/content_translation/src/ContentTranslationHandler.php
+++ b/core/modules/content_translation/src/ContentTranslationHandler.php
@@ -77,7 +77,10 @@ public function getTranslationAccess(EntityInterface $entity, $op) {
    * {@inheritdoc}
    */
   public function getSourceLangcode(FormStateInterface $form_state) {
-    return isset($form_state['content_translation']['source']) ? $form_state['content_translation']['source']->id : FALSE;
+    if ($source = $form_state->get(['content_translation', 'source'])) {
+      return $source->id;
+    }
+    return FALSE;
   }
 
   /**
@@ -315,7 +318,7 @@ public function entityFormSharedElements($element, FormStateInterface $form_stat
           // If we are displaying a multilingual entity form we need to provide
           // translatability clues, otherwise the shared form elements should be
           // hidden.
-          if (empty($form_state['content_translation']['translation_form'])) {
+          if (!$form_state->get(['content_translation', 'translation_form'])) {
             $this->addTranslatabilityClue($element[$key]);
           }
           else {
diff --git a/core/modules/datetime/src/Plugin/Field/FieldType/DateTimeFieldItemList.php b/core/modules/datetime/src/Plugin/Field/FieldType/DateTimeFieldItemList.php
index 5963db0..e78a194 100644
--- a/core/modules/datetime/src/Plugin/Field/FieldType/DateTimeFieldItemList.php
+++ b/core/modules/datetime/src/Plugin/Field/FieldType/DateTimeFieldItemList.php
@@ -69,7 +69,7 @@ public function defaultValuesForm(array &$form, FormStateInterface $form_state)
    * {@inheritdoc}
    */
   public function defaultValuesFormValidate(array $element, array &$form, FormStateInterface $form_state) {
-    if ($form_state['values']['default_value_input']['default_date_type'] == static::DEFAULT_VALUE_CUSTOM) {
+    if ($form_state->getValue(['default_value_input', 'default_date_type']) == static::DEFAULT_VALUE_CUSTOM) {
       $is_strtotime = @strtotime($form_state->getValue(array('default_value_input', 'default_date')));
       if (!$is_strtotime) {
         $form_state->setErrorByName('default_value_input][default_date', t('The relative date value entered is invalid.'));
diff --git a/core/modules/editor/editor.module b/core/modules/editor/editor.module
index ae20c49..adda014 100644
--- a/core/modules/editor/editor.module
+++ b/core/modules/editor/editor.module
@@ -10,7 +10,6 @@
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Render\Element;
 use Drupal\Core\Routing\RouteMatchInterface;
-use Drupal\editor\Entity\Editor;
 use Drupal\Component\Utility\NestedArray;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\filter\FilterFormatInterface;
@@ -90,11 +89,11 @@ function editor_form_filter_admin_overview_alter(&$form, FormStateInterface $for
  * Implements hook_form_BASE_FORM_ID_alter() for 'filter_format_form'.
  */
 function editor_form_filter_format_form_alter(&$form, FormStateInterface $form_state) {
-  if (!isset($form_state['editor'])) {
+  if (!$editor = $form_state->get('editor')) {
     $format_id = $form_state->getFormObject()->getEntity()->id();
-    $form_state['editor'] = editor_load($format_id);
+    $editor = editor_load($format_id);
+    $form_state->set('editor', $editor);
   }
-  $editor = $form_state['editor'];
 
   // Associate a text editor with this text format.
   $manager = \Drupal::service('plugin.manager.editor');
@@ -167,20 +166,20 @@ function editor_form_filter_format_form_alter(&$form, FormStateInterface $form_s
  * Button submit handler for filter_format_form()'s 'editor_configure' button.
  */
 function editor_form_filter_admin_format_editor_configure($form, FormStateInterface $form_state) {
-  $editor = $form_state['editor'];
+  $editor = $form_state->get('editor');
   if ($editor_value = $form_state->getValue(array('editor', 'editor'))) {
     if ($editor_value === '') {
-      $form_state['editor'] = FALSE;
+      $form_state->set('editor', FALSE);
     }
     elseif (empty($editor) || $editor_value !== $editor->getEditor()) {
       $editor = entity_create('editor', array(
         'format' => $form_state->getFormObject()->getEntity()->id(),
         'editor' => $editor_value,
       ));
-      $form_state['editor'] = $editor;
+      $form_state->set('editor', $editor);
     }
   }
-  $form_state['rebuild'] = TRUE;
+  $form_state->setRebuild();
 }
 
 /**
@@ -195,7 +194,7 @@ function editor_form_filter_admin_form_ajax($form, FormStateInterface $form_stat
  */
 function editor_form_filter_admin_format_validate($form, FormStateInterface $form_state) {
   // This validate handler is not applicable when using the 'Configure' button.
-  if ($form_state['triggering_element']['#name'] === 'editor_configure') {
+  if ($form_state->getTriggeringElement()['#name'] === 'editor_configure') {
     return;
   }
 
@@ -203,7 +202,7 @@ function editor_form_filter_admin_format_validate($form, FormStateInterface $for
   // 'Configure' button won't be clicked automatically. So, when the user has
   // selected a text editor and has then clicked 'Save configuration', we should
   // point out that the user must still configure the text editor.
-  if ($form_state->getValue(array('editor', 'editor')) !== '' && empty($form_state['editor'])) {
+  if ($form_state->getValue(['editor', 'editor']) !== '' && !$form_state->get('editor')) {
     $form_state->setErrorByName('editor][editor', t('You must configure the selected text editor.'));
   }
 }
@@ -220,12 +219,12 @@ function editor_form_filter_admin_format_submit($form, FormStateInterface $form_
   }
 
   // Create a new editor or update the existing editor.
-  if (!empty($form_state['editor'])) {
+  if ($editor = $form_state->get('editor')) {
     // Ensure the text format is set: when creating a new text format, this
     // would equal the empty string.
-    $form_state['editor']->set('format', $format_id);
-    $form_state['editor']->setSettings($form_state->getValue(array('editor', 'settings')));
-    $form_state['editor']->save();
+    $editor->set('format', $format_id);
+    $editor->setSettings($form_state->getValue(['editor', 'settings']));
+    $editor->save();
   }
 }
 
diff --git a/core/modules/editor/src/Form/EditorImageDialog.php b/core/modules/editor/src/Form/EditorImageDialog.php
index 342f283..be51dc4 100644
--- a/core/modules/editor/src/Form/EditorImageDialog.php
+++ b/core/modules/editor/src/Form/EditorImageDialog.php
@@ -37,11 +37,11 @@ public function getFormId() {
   public function buildForm(array $form, FormStateInterface $form_state, FilterFormat $filter_format = NULL) {
     // The default values are set directly from \Drupal::request()->request,
     // provided by the editor plugin opening the dialog.
-    if (!isset($form_state['image_element'])) {
+    if (!$image_element = $form_state->get('image_element')) {
       $user_input = $form_state->getUserInput();
-      $form_state['image_element'] = isset($user_input['editor_object']) ? $user_input['editor_object'] : array();
+      $image_element = isset($user_input['editor_object']) ? $user_input['editor_object'] : [];
+      $form_state->set('image_element', $image_element);
     }
-    $image_element = $form_state['image_element'];
 
     $form['#tree'] = TRUE;
     $form['#attached']['library'][] = 'editor/drupal.editor.dialog';
diff --git a/core/modules/entity_reference/entity_reference.module b/core/modules/entity_reference/entity_reference.module
index 75ab68c..2fcfecd 100644
--- a/core/modules/entity_reference/entity_reference.module
+++ b/core/modules/entity_reference/entity_reference.module
@@ -159,8 +159,7 @@ function _entity_reference_element_validate_filter(&$element, FormStateInterface
  * @see entity_reference_field_instance_settings_form()
  */
 function entity_reference_settings_ajax($form, FormStateInterface $form_state) {
-  $trigger = $form_state['triggering_element'];
-  return NestedArray::getValue($form, $trigger['#ajax']['element']);
+  return NestedArray::getValue($form, $form_state->getTriggeringElement()['#ajax']['element']);
 }
 
 /**
@@ -169,7 +168,7 @@ function entity_reference_settings_ajax($form, FormStateInterface $form_state) {
  * @see entity_reference_field_instance_settings_form()
  */
 function entity_reference_settings_ajax_submit($form, FormStateInterface $form_state) {
-  $form_state['rebuild'] = TRUE;
+  $form_state->setRebuild();
 }
 
 /**
diff --git a/core/modules/entity_reference/src/ConfigurableEntityReferenceItem.php b/core/modules/entity_reference/src/ConfigurableEntityReferenceItem.php
index 954fbde..f4fd800 100644
--- a/core/modules/entity_reference/src/ConfigurableEntityReferenceItem.php
+++ b/core/modules/entity_reference/src/ConfigurableEntityReferenceItem.php
@@ -178,7 +178,7 @@ public function settingsForm(array &$form, FormStateInterface $form_state, $has_
    * {@inheritdoc}
    */
   public function instanceSettingsForm(array $form, FormStateInterface $form_state) {
-    $instance = $form_state['instance'];
+    $instance = $form_state->get('instance');
 
     // Get all selection plugins for this entity type.
     $selection_plugins = \Drupal::service('plugin.manager.entity_reference.selection')->getSelectionGroups($this->getSetting('target_type'));
@@ -251,7 +251,7 @@ public function instanceSettingsForm(array $form, FormStateInterface $form_state
   public static function instanceSettingsFormValidate(array $form, FormStateInterface $form_state) {
     if ($form_state->hasValue('instance')) {
       $form_state->unsetValue(array('instance', 'settings', 'handler_submit'));
-      $form_state['instance']->settings = $form_state->getValue(array('instance', 'settings'));
+      $form_state->get('instance')->settings = $form_state->getValue(['instance', 'settings']);
     }
   }
 
diff --git a/core/modules/field/field.module b/core/modules/field/field.module
index 87ba4f7..4207d28 100644
--- a/core/modules/field/field.module
+++ b/core/modules/field/field.module
@@ -349,10 +349,11 @@ function field_form_config_admin_import_form_alter(&$form, FormStateInterface $f
   // Only display the message when there is a storage comparer available and the
   // form is not submitted.
   $user_input = $form_state->getUserInput();
-  if (isset($form_state['storage_comparer']) && empty($user_input)) {
+  $storage_comparer = $form_state->get('storage_comparer');
+  if ($storage_comparer && empty($user_input)) {
     $field_storages = \Drupal\field\ConfigImporterFieldPurger::getFieldStoragesToPurge(
-      $form_state['storage_comparer']->getSourceStorage()->read('core.extension'),
-      $form_state['storage_comparer']->getChangelist('delete')
+      $storage_comparer->getSourceStorage()->read('core.extension'),
+      $storage_comparer->getChangelist('delete')
     );
     if ($field_storages) {
       foreach ($field_storages as $field) {
diff --git a/core/modules/field/src/Plugin/views/field/Field.php b/core/modules/field/src/Plugin/views/field/Field.php
index 0bd93b2..fb38edf 100644
--- a/core/modules/field/src/Plugin/views/field/Field.php
+++ b/core/modules/field/src/Plugin/views/field/Field.php
@@ -675,7 +675,7 @@ public function buildGroupByForm(&$form, FormStateInterface $form_state) {
 
   public function submitGroupByForm(&$form, FormStateInterface $form_state) {
     parent::submitGroupByForm($form, $form_state);
-    $item = &$form_state['handler']->options;
+    $item = &$form_state->get('handler')->options;
 
     // Add settings for "field API" fields.
     $item['group_column'] = $form_state->getValue(array('options', 'group_column'));
diff --git a/core/modules/field/src/Tests/FieldAttachOtherTest.php b/core/modules/field/src/Tests/FieldAttachOtherTest.php
index d41808f..978ea0e 100644
--- a/core/modules/field/src/Tests/FieldAttachOtherTest.php
+++ b/core/modules/field/src/Tests/FieldAttachOtherTest.php
@@ -331,7 +331,7 @@ function testEntityFormDisplayExtractFormValues() {
     $values_2[1]['value'] = 0;
 
     // Pretend the form has been built.
-    $form_state['build_info']['callback_object'] = \Drupal::entityManager()->getFormObject($entity_type, 'default');
+    $form_state->setFormObject(\Drupal::entityManager()->getFormObject($entity_type, 'default'));
     \Drupal::formBuilder()->prepareForm('field_test_entity_form', $form, $form_state);
     drupal_process_form('field_test_entity_form', $form, $form_state);
     $form_state->setValue($this->fieldTestData->field_name, $values);
diff --git a/core/modules/field/tests/modules/field_test/src/Form/NestedEntityTestForm.php b/core/modules/field/tests/modules/field_test/src/Form/NestedEntityTestForm.php
index b1f9d1c..7e07f1a 100644
--- a/core/modules/field/tests/modules/field_test/src/Form/NestedEntityTestForm.php
+++ b/core/modules/field/tests/modules/field_test/src/Form/NestedEntityTestForm.php
@@ -29,13 +29,15 @@ public function getFormId() {
    */
   public function buildForm(array $form, FormStateInterface $form_state, EntityInterface $entity_1 = NULL, EntityInterface $entity_2 = NULL) {
     // First entity.
-    $form_state['entity_1'] = $entity_1;
-    $form_state['form_display_1'] = EntityFormDisplay::collectRenderDisplay($entity_1, 'default');
-    $form_state['form_display_1']->buildForm($entity_1, $form, $form_state);
+    $form_state->set('entity_1', $entity_1);
+    $form_display_1 = EntityFormDisplay::collectRenderDisplay($entity_1, 'default');
+    $form_state->set('form_display_1', $form_display_1);
+    $form_display_1->buildForm($entity_1, $form, $form_state);
 
     // Second entity.
-    $form_state['entity_2'] = $entity_2;
-    $form_state['form_display_2'] = EntityFormDisplay::collectRenderDisplay($entity_2, 'default');
+    $form_state->set('entity_2', $entity_2);
+    $form_display_2 = EntityFormDisplay::collectRenderDisplay($entity_2, 'default');
+    $form_state->set('form_display_2', $form_display_2);
     $form['entity_2'] = array(
       '#type' => 'details',
       '#title' => t('Second entity'),
@@ -44,7 +46,7 @@ public function buildForm(array $form, FormStateInterface $form_state, EntityInt
       '#weight' => 50,
     );
 
-    $form_state['form_display_2']->buildForm($entity_2, $form['entity_2'], $form_state);
+    $form_display_2->buildForm($entity_2, $form['entity_2'], $form_state);
 
     $form['save'] = array(
       '#type' => 'submit',
@@ -59,15 +61,15 @@ public function buildForm(array $form, FormStateInterface $form_state, EntityInt
    * {@inheritdoc]
    */
   public function validateForm(array &$form, FormStateInterface $form_state) {
-    $entity_1 = $form_state['entity_1'];
+    $entity_1 = $form_state->get('entity_1');
     /** @var \Drupal\Core\Entity\Display\EntityFormDisplayInterface $form_display_1 */
-    $form_display_1 = $form_state['form_display_1'];
+    $form_display_1 = $form_state->get('form_display_1');
     $form_display_1->extractFormValues($entity_1, $form, $form_state);
     $form_display_1->validateFormValues($entity_1, $form, $form_state);
 
-    $entity_2 = $form_state['entity_2'];
+    $entity_2 = $form_state->get('entity_2');
     /** @var \Drupal\Core\Entity\Display\EntityFormDisplayInterface $form_display_2 */
-    $form_display_2 = $form_state['form_display_2'];
+    $form_display_2 = $form_state->get('form_display_2');
     $form_display_2->extractFormValues($entity_2, $form['entity_2'], $form_state);
     $form_display_2->validateFormValues($entity_2, $form['entity_2'], $form_state);
   }
@@ -77,11 +79,11 @@ public function validateForm(array &$form, FormStateInterface $form_state) {
    */
   public function submitForm(array &$form, FormStateInterface $form_state) {
     /** @var \Drupal\Core\Entity\EntityInterface $entity_1 */
-    $entity_1 = $form_state['entity_1'];
+    $entity_1 = $form_state->get('entity_1');
     $entity_1->save();
 
     /** @var \Drupal\Core\Entity\EntityInterface $entity_2 */
-    $entity_2 = $form_state['entity_2'];
+    $entity_2 = $form_state->get('entity_2');
     $entity_2->save();
 
     drupal_set_message($this->t('test_entities @id_1 and @id_2 have been updated.', array('@id_1' => $entity_1->id(), '@id_2' => $entity_2->id())));
diff --git a/core/modules/field_ui/field_ui.module b/core/modules/field_ui/field_ui.module
index 4cd3bce..06764d0 100644
--- a/core/modules/field_ui/field_ui.module
+++ b/core/modules/field_ui/field_ui.module
@@ -200,7 +200,7 @@ function field_ui_entity_operation(EntityInterface $entity) {
  * @see field_ui_form_node_type_form_alter()
  */
 function field_ui_form_node_type_form_submit($form, FormStateInterface $form_state) {
-  if ($form_state['triggering_element']['#parents'][0] === 'save_continue' && $route_info = FieldUI::getOverviewRouteInfo('node', $form_state->getValue('type'))) {
+  if ($form_state->getTriggeringElement()['#parents'][0] === 'save_continue' && $route_info = FieldUI::getOverviewRouteInfo('node', $form_state->getValue('type'))) {
     $form_state->setRedirectUrl($route_info);
   }
 }
diff --git a/core/modules/field_ui/src/DisplayOverviewBase.php b/core/modules/field_ui/src/DisplayOverviewBase.php
index 1bad243..0f46d5c 100644
--- a/core/modules/field_ui/src/DisplayOverviewBase.php
+++ b/core/modules/field_ui/src/DisplayOverviewBase.php
@@ -322,11 +322,12 @@ protected function buildFieldRow(FieldDefinitionInterface $field_definition, Ent
     if ($display_type = $form_state->getValue(array('fields', $field_name, 'type'))) {
       $display_options['type'] = $display_type;
     }
-    if (isset($form_state['plugin_settings'][$field_name]['settings'])) {
-      $display_options['settings'] = $form_state['plugin_settings'][$field_name]['settings'];
+    $plugin_settings = $form_state->get('plugin_settings');
+    if (isset($plugin_settings[$field_name]['settings'])) {
+      $display_options['settings'] = $plugin_settings[$field_name]['settings'];
     }
-    if (isset($form_state['plugin_settings'][$field_name]['third_party_settings'])) {
-      $display_options['third_party_settings'] = $form_state['plugin_settings'][$field_name]['third_party_settings'];
+    if (isset($plugin_settings[$field_name]['third_party_settings'])) {
+      $display_options['third_party_settings'] = $plugin_settings[$field_name]['third_party_settings'];
     }
 
     // Get the corresponding plugin object.
@@ -343,7 +344,7 @@ protected function buildFieldRow(FieldDefinitionInterface $field_definition, Ent
       '#field_name' => $field_name,
     );
 
-    if ($form_state['plugin_settings_edit'] == $field_name) {
+    if ($form_state->get('plugin_settings_edit') == $field_name) {
       // We are currently editing this field's plugin settings. Display the
       // settings form and submit buttons.
       $field_row['plugin']['settings_edit_form'] = array();
@@ -519,12 +520,13 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
         // Get plugin settings. They lie either directly in submitted form
         // values (if the whole form was submitted while some plugin settings
         // were being edited), or have been persisted in $form_state.
+        $plugin_settings = $form_state->get('plugin_settings');
         $settings = array();
         if (isset($values['settings_edit_form']['settings'])) {
           $settings = $values['settings_edit_form']['settings'];
         }
-        elseif (isset($form_state['plugin_settings'][$field_name]['settings'])) {
-          $settings = $form_state['plugin_settings'][$field_name]['settings'];
+        elseif (isset($plugin_settings[$field_name]['settings'])) {
+          $settings = $plugin_settings[$field_name]['settings'];
         }
         elseif ($current_options = $display->getComponent($field_name)) {
           $settings = $current_options['settings'];
@@ -533,8 +535,8 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
         if (isset($values['settings_edit_form']['third_party_settings'])) {
           $third_party_settings = $values['settings_edit_form']['third_party_settings'];
         }
-        elseif (isset($form_state['plugin_settings'][$field_name]['third_party_settings'])) {
-          $third_party_settings = $form_state['plugin_settings'][$field_name]['third_party_settings'];
+        elseif (isset($plugin_settings[$field_name]['third_party_settings'])) {
+          $third_party_settings = $plugin_settings[$field_name]['third_party_settings'];
         }
         elseif (($current_options = $display->getComponent($field_name)) && isset($current_options['third_party_settings'])) {
           $third_party_settings = $current_options['third_party_settings'];
@@ -609,51 +611,52 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
    * Form submission handler for multistep buttons.
    */
   public function multistepSubmit($form, FormStateInterface $form_state) {
-    $trigger = $form_state['triggering_element'];
+    $trigger = $form_state->getTriggeringElement();
     $op = $trigger['#op'];
 
     switch ($op) {
       case 'edit':
         // Store the field whose settings are currently being edited.
         $field_name = $trigger['#field_name'];
-        $form_state['plugin_settings_edit'] = $field_name;
+        $form_state->set('plugin_settings_edit', $field_name);
         break;
 
       case 'update':
         // Store the saved settings, and set the field back to 'non edit' mode.
         $field_name = $trigger['#field_name'];
         if ($plugin_settings = $form_state->getValue(array('fields', $field_name, 'settings_edit_form', 'settings'))) {
-          $form_state['plugin_settings'][$field_name]['settings'] = $plugin_settings;
+          $form_state->set(['plugin_settings', $field_name, 'settings'], $plugin_settings);
         }
         if ($plugin_third_party_settings = $form_state->getValue(array('fields', $field_name, 'settings_edit_form', 'third_party_settings'))) {
-          $form_state['plugin_settings'][$field_name]['third_party_settings'] = $plugin_third_party_settings;
+          $form_state->set(['plugin_settings', $field_name, 'third_party_settings'], $plugin_third_party_settings);
         }
-        unset($form_state['plugin_settings_edit']);
+        $form_state->set('plugin_settings_edit', NULL);
         break;
 
       case 'cancel':
         // Set the field back to 'non edit' mode.
-        unset($form_state['plugin_settings_edit']);
+        $form_state->set('plugin_settings_edit', NULL);
         break;
 
       case 'refresh_table':
         // If the currently edited field is one of the rows to be refreshed, set
         // it back to 'non edit' mode.
         $updated_rows = explode(' ', $form_state->getValue('refresh_rows'));
-        if (isset($form_state['plugin_settings_edit']) && in_array($form_state['plugin_settings_edit'], $updated_rows)) {
-          unset($form_state['plugin_settings_edit']);
+        $plugin_settings_edit = $form_state->get('plugin_settings_edit');
+        if (plugin_settings_edit && in_array($plugin_settings_edit, $updated_rows)) {
+          $form_state->set('plugin_settings_edit', NULL);
         }
         break;
     }
 
-    $form_state['rebuild'] = TRUE;
+    $form_state->setRebuild();
   }
 
   /**
    * Ajax handler for multistep buttons.
    */
   public function multistepAjax($form, FormStateInterface $form_state) {
-    $trigger = $form_state['triggering_element'];
+    $trigger = $form_state->getTriggeringElement();
     $op = $trigger['#op'];
 
     // Pick the elements that need to receive the ajax-new-content effect.
diff --git a/core/modules/field_ui/src/FieldOverview.php b/core/modules/field_ui/src/FieldOverview.php
index 6d5f15f..0d2a315 100644
--- a/core/modules/field_ui/src/FieldOverview.php
+++ b/core/modules/field_ui/src/FieldOverview.php
@@ -414,7 +414,7 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
         $destinations[] = array('route_name' => 'field_ui.instance_edit_' . $this->entity_type, 'route_parameters' => $route_parameters);
 
         // Store new field information for any additional submit handlers.
-        $form_state['fields_added']['_add_new_field'] = $values['field_name'];
+        $form_state->set(['fields_added', '_add_new_field'], $values['field_name']);
       }
       catch (\Exception $e) {
         $error = TRUE;
@@ -464,7 +464,7 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
             ),
           );
           // Store new field information for any additional submit handlers.
-          $form_state['fields_added']['_add_existing_field'] = $instance['field_name'];
+          $form_state->set(['fields_added', '_add_existing_field'], $instance['field_name']);
         }
         catch (\Exception $e) {
           $error = TRUE;
diff --git a/core/modules/field_ui/src/Form/FieldInstanceEditForm.php b/core/modules/field_ui/src/Form/FieldInstanceEditForm.php
index e8d4469..a670399 100644
--- a/core/modules/field_ui/src/Form/FieldInstanceEditForm.php
+++ b/core/modules/field_ui/src/Form/FieldInstanceEditForm.php
@@ -64,7 +64,8 @@ public function getFormId() {
    * {@inheritdoc}
    */
   public function buildForm(array $form, FormStateInterface $form_state, FieldInstanceConfigInterface $field_instance_config = NULL) {
-    $this->instance = $form_state['instance'] = $field_instance_config;
+    $this->instance = $field_instance_config;
+    $form_state->set('instance', $field_instance_config);
 
     $bundle = $this->instance->bundle;
     $entity_type = $this->instance->entity_type;
diff --git a/core/modules/field_ui/src/Form/FieldStorageEditForm.php b/core/modules/field_ui/src/Form/FieldStorageEditForm.php
index 903a614..edb6d22 100644
--- a/core/modules/field_ui/src/Form/FieldStorageEditForm.php
+++ b/core/modules/field_ui/src/Form/FieldStorageEditForm.php
@@ -76,7 +76,8 @@ public static function create(ContainerInterface $container) {
    * {@inheritdoc}
    */
   public function buildForm(array $form, FormStateInterface $form_state, FieldInstanceConfigInterface $field_instance_config = NULL) {
-    $this->instance = $form_state['instance'] = $field_instance_config;
+    $this->instance = $field_instance_config;
+    $form_state->set('instance', $field_instance_config);
     $form['#title'] = $this->instance->label();
 
     $field_storage = $this->instance->getFieldStorageDefinition();
diff --git a/core/modules/field_ui/src/OverviewBase.php b/core/modules/field_ui/src/OverviewBase.php
index 7132eda..8ee99a4 100644
--- a/core/modules/field_ui/src/OverviewBase.php
+++ b/core/modules/field_ui/src/OverviewBase.php
@@ -78,15 +78,17 @@ public static function create(ContainerInterface $container) {
   public function buildForm(array $form, FormStateInterface $form_state, $entity_type_id = NULL, $bundle = NULL) {
     $entity_type = $this->entityManager->getDefinition($entity_type_id);
     $this->bundleEntityType = $entity_type->getBundleEntityType();
-    if (!isset($form_state['bundle'])) {
+    $stored_bundle = $form_state->get('bundle');
+    if (!$stored_bundle) {
       if (!$bundle) {
         $bundle = $this->getRequest()->attributes->get('_raw_variables')->get($this->bundleEntityType);
       }
-      $form_state['bundle'] = $bundle;
+      $stored_bundle = $bundle;
+      $form_state->set('bundle', $bundle);
     }
 
     $this->entity_type = $entity_type_id;
-    $this->bundle = $form_state['bundle'];
+    $this->bundle = $stored_bundle;
   }
 
   /**
diff --git a/core/modules/file/file.module b/core/modules/file/file.module
index e17ab46..104ef86 100644
--- a/core/modules/file/file.module
+++ b/core/modules/file/file.module
@@ -1331,7 +1331,7 @@ function file_managed_file_validate(&$element, FormStateInterface $form_state) {
   // If referencing an existing file, only allow if there are existing
   // references. This prevents unmanaged files from being deleted if this
   // item were to be deleted.
-  $clicked_button = end($form_state['triggering_element']['#parents']);
+  $clicked_button = end($form_state->getTriggeringElement()['#parents']);
   if ($clicked_button != 'remove_button' && !empty($element['fids']['#value'])) {
     $fids = $element['fids']['#value'];
     foreach ($fids as $fid) {
@@ -1368,7 +1368,7 @@ function file_managed_file_validate(&$element, FormStateInterface $form_state) {
 function file_managed_file_submit($form, FormStateInterface $form_state) {
   // Determine whether it was the upload or the remove button that was clicked,
   // and set $element to the managed_file element that contains that button.
-  $parents = $form_state['triggering_element']['#array_parents'];
+  $parents = $form_state->getTriggeringElement()['#array_parents'];
   $button_key = array_pop($parents);
   $element = NestedArray::getValue($form, $parents);
 
@@ -1418,10 +1418,10 @@ function file_managed_file_submit($form, FormStateInterface $form_state) {
   // Set the form to rebuild so that $form is correctly updated in response to
   // processing the file removal. Since this function did not change $form_state
   // if the upload button was clicked, a rebuild isn't necessary in that
-  // situation and setting $form_state['no_redirect'] to TRUE would suffice.
+  // situation and calling $form_state->disableRedirect() would suffice.
   // However, we choose to always rebuild, to keep the form processing workflow
   // consistent between the two buttons.
-  $form_state['rebuild'] = TRUE;
+  $form_state->setRebuild();
 }
 
 /**
diff --git a/core/modules/file/src/Plugin/Field/FieldWidget/FileWidget.php b/core/modules/file/src/Plugin/Field/FieldWidget/FileWidget.php
index fa5c28e..0a8ea88 100644
--- a/core/modules/file/src/Plugin/Field/FieldWidget/FileWidget.php
+++ b/core/modules/file/src/Plugin/Field/FieldWidget/FileWidget.php
@@ -132,7 +132,7 @@ protected function formMultipleElements(FieldItemListInterface $items, array &$f
     }
 
     $empty_single_allowed = ($cardinality == 1 && $delta == 0);
-    $empty_multiple_allowed = ($cardinality == FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED || $delta < $cardinality) && empty($form_state['programmed']);
+    $empty_multiple_allowed = ($cardinality == FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED || $delta < $cardinality) && !$form_state->isProgrammed();
 
     // Add one more empty row for new uploads except when this is a programmed
     // multiple form as it is not necessary.
@@ -491,11 +491,10 @@ public static function submit($form, FormStateInterface $form_state) {
     // avoid a mismatch between old and new deltas. The rebuilt elements will
     // have #default_value set appropriately for the current state of the field,
     // so nothing is lost in doing this.
-    $parents = array_slice($form_state['triggering_element']['#parents'], 0, -2);
+    $button = $form_state->getTriggeringElement();
+    $parents = array_slice($button['#parents'], 0, -2);
     NestedArray::setValue($form_state->getUserInput(), $parents, NULL);
 
-    $button = $form_state['triggering_element'];
-
     // Go one level up in the form, to the widgets container.
     $element = NestedArray::getValue($form, array_slice($button['#array_parents'], 0, -1));
     $field_name = $element['#field_name'];
diff --git a/core/modules/history/src/Plugin/views/filter/HistoryUserTimestamp.php b/core/modules/history/src/Plugin/views/filter/HistoryUserTimestamp.php
index 70866c8..f25f53c 100644
--- a/core/modules/history/src/Plugin/views/filter/HistoryUserTimestamp.php
+++ b/core/modules/history/src/Plugin/views/filter/HistoryUserTimestamp.php
@@ -44,7 +44,7 @@ protected function valueForm(&$form, FormStateInterface $form_state) {
     // Only present a checkbox for the exposed filter itself. There's no way
     // to tell the difference between not checked and the default value, so
     // specifying the default value via the views UI is meaningless.
-    if (!empty($form_state['exposed'])) {
+    if ($form_state->get('exposed')) {
       if (isset($this->options['expose']['label'])) {
         $label = $this->options['expose']['label'];
       }
diff --git a/core/modules/image/src/Form/ImageEffectFormBase.php b/core/modules/image/src/Form/ImageEffectFormBase.php
index 008e0a5..910c781 100644
--- a/core/modules/image/src/Form/ImageEffectFormBase.php
+++ b/core/modules/image/src/Form/ImageEffectFormBase.php
@@ -106,9 +106,7 @@ public function buildForm(array $form, FormStateInterface $form_state, ImageStyl
   public function validateForm(array &$form, FormStateInterface $form_state) {
     // The image effect configuration is stored in the 'data' key in the form,
     // pass that through for validation.
-    $effect_data = new FormState(array(
-      'values' => $form_state->getValue('data'),
-    ));
+    $effect_data = (new FormState())->setValues($form_state->getValue('data'));
     $this->imageEffect->validateConfigurationForm($form, $effect_data);
     // Update the original form values.
     $form_state->setValue('data', $effect_data['values']);
@@ -122,9 +120,7 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
 
     // The image effect configuration is stored in the 'data' key in the form,
     // pass that through for submission.
-    $effect_data = new FormState(array(
-      'values' => $form_state->getValue('data'),
-    ));
+    $effect_data = (new FormState())->setValues($form_state->getValue('data'));
     $this->imageEffect->submitConfigurationForm($form, $effect_data);
     // Update the original form values.
     $form_state->setValue('data', $effect_data['values']);
diff --git a/core/modules/image/src/Plugin/Field/FieldWidget/ImageWidget.php b/core/modules/image/src/Plugin/Field/FieldWidget/ImageWidget.php
index 93b5c03..b0a2914 100644
--- a/core/modules/image/src/Plugin/Field/FieldWidget/ImageWidget.php
+++ b/core/modules/image/src/Plugin/Field/FieldWidget/ImageWidget.php
@@ -231,7 +231,7 @@ public static function process($element, FormStateInterface $form_state, $form)
   public static function validateRequiredFields($element, FormStateInterface $form_state) {
     // Only do validation if the function is triggered from other places than
     // the image process form.
-    if (!in_array('file_managed_file_submit', $form_state['triggering_element']['#submit'])) {
+    if (!in_array('file_managed_file_submit', $form_state->getTriggeringElement()['#submit'])) {
       // If the image is not there, we do not check for empty values.
       $parents = $element['#parents'];
       $field = array_pop($parents);
diff --git a/core/modules/language/language.module b/core/modules/language/language.module
index e095d8d..fb7ba22 100644
--- a/core/modules/language/language.module
+++ b/core/modules/language/language.module
@@ -242,15 +242,14 @@ function language_configuration_element_process($element, FormStateInterface $fo
   // They will be used, in the submit handler, to generate the names of the
   // variables that will store the settings and are a way to uniquely identify
   // the entity.
-  if (!isset($form_state['language'])) {
-    $form_state['language'] = array();
-  }
-  $form_state['language'] += array(
-    $element['#name'] => array(
+  $language = $form_state->get('language') ?: [];
+  $language += [
+    $element['#name'] => [
       'entity_type' => $element['#entity_information']['entity_type'],
       'bundle' => $element['#entity_information']['bundle'],
-    ),
-  );
+    ],
+  ];
+  $form_state->set('language', $language);
 
   // Do not add the submit callback for the language content settings page,
   // which is handled separately.
@@ -273,8 +272,8 @@ function language_configuration_element_process($element, FormStateInterface $fo
 function language_configuration_element_submit(&$form, FormStateInterface $form_state) {
   // Iterate through all the language_configuration elements and save their
   // values.
-  if (isset($form_state['language'])) {
-    foreach ($form_state['language'] as $element_name => $values) {
+  if ($language = $form_state->get('language')) {
+    foreach ($language as $element_name => $values) {
       language_save_default_configuration($values['entity_type'], $values['bundle'],  $form_state->getValue($element_name));
     }
   }
diff --git a/core/modules/language/src/Form/NegotiationBrowserForm.php b/core/modules/language/src/Form/NegotiationBrowserForm.php
index 39d47cb..766ecb5 100644
--- a/core/modules/language/src/Form/NegotiationBrowserForm.php
+++ b/core/modules/language/src/Form/NegotiationBrowserForm.php
@@ -162,14 +162,14 @@ public function validateForm(array &$form, FormStateInterface $form_state) {
       $unique_values[$data['browser_langcode']] = $data['drupal_langcode'];
     }
 
-    $form_state['mappings'] = $unique_values;
+    $form_state->set('mappings', $unique_values);
   }
 
   /**
    * {@inheritdoc}
    */
   public function submitForm(array &$form, FormStateInterface $form_state) {
-    $mappings = $form_state['mappings'];
+    $mappings = $form_state->get('mappings');
     if (!empty($mappings)) {
       $config = $this->config('language.mappings');
       $config->setData($mappings);
diff --git a/core/modules/link/src/Plugin/Field/FieldWidget/LinkWidget.php b/core/modules/link/src/Plugin/Field/FieldWidget/LinkWidget.php
index cb95532..21e078f 100644
--- a/core/modules/link/src/Plugin/Field/FieldWidget/LinkWidget.php
+++ b/core/modules/link/src/Plugin/Field/FieldWidget/LinkWidget.php
@@ -84,7 +84,7 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen
     // Post-process the title field to make it conditionally required if URL is
     // non-empty. Omit the validation on the field edit form, since the field
     // settings cannot be saved otherwise.
-    if (empty($form_state['default_value_widget']) && $this->getFieldSetting('title') == DRUPAL_REQUIRED) {
+    if (!$form_state->get('default_value_widget') && $this->getFieldSetting('title') == DRUPAL_REQUIRED) {
       $element['#element_validate'][] = array($this, 'validateTitle');
     }
 
diff --git a/core/modules/menu_link_content/src/Form/MenuLinkContentForm.php b/core/modules/menu_link_content/src/Form/MenuLinkContentForm.php
index ae4bac0..99929e2 100644
--- a/core/modules/menu_link_content/src/Form/MenuLinkContentForm.php
+++ b/core/modules/menu_link_content/src/Form/MenuLinkContentForm.php
@@ -340,7 +340,7 @@ public function save(array $form, FormStateInterface $form_state) {
     }
     else {
       drupal_set_message($this->t('There was an error saving the menu link.'), 'error');
-      $form_state['rebuild'] = TRUE;
+      $form_state->setRebuild();
     }
   }
 
diff --git a/core/modules/menu_ui/menu_ui.module b/core/modules/menu_ui/menu_ui.module
index f2591dd..77c56ab 100644
--- a/core/modules/menu_ui/menu_ui.module
+++ b/core/modules/menu_ui/menu_ui.module
@@ -249,7 +249,7 @@ function menu_ui_node_predelete(EntityInterface $node) {
  * Implements hook_node_prepare_form().
  */
 function menu_ui_node_prepare_form(NodeInterface $node, $operation, FormStateInterface $form_state) {
-  if (empty($form_state['menu_link_definition'])) {
+  if (!$form_state->get('menu_link_definition')) {
     // Prepare the node for the edit form so that $node->menu always exists.
     $node_type_config = \Drupal::config('menu.entity.node.' . $node->getType());
     $menu_name = strtok($node_type_config->get('parent'), ':');
@@ -307,7 +307,7 @@ function menu_ui_node_prepare_form(NodeInterface $node, $operation, FormStateInt
       );
     }
     // Set default values.
-    $form_state['menu_link_definition'] = $definition;
+    $form_state->set('menu_link_definition', $definition);
   }
 }
 
@@ -322,7 +322,7 @@ function menu_ui_form_node_form_alter(&$form, FormStateInterface $form_state) {
   // Generate a list of possible parents (not including this link or descendants).
   // @todo This must be handled in a #process handler.
   $node = $form_state->getFormObject()->getEntity();
-  $definition = $form_state['menu_link_definition'];
+  $definition = $form_state->get('menu_link_definition');
   $type = $node->getType();
   /** @var \Drupal\Core\Menu\MenuParentFormSelectorInterface $menu_parent_selector */
   $menu_parent_selector = \Drupal::service('menu.parent_form_selector');
diff --git a/core/modules/menu_ui/src/MenuForm.php b/core/modules/menu_ui/src/MenuForm.php
index fde70bb..1115070 100644
--- a/core/modules/menu_ui/src/MenuForm.php
+++ b/core/modules/menu_ui/src/MenuForm.php
@@ -144,7 +144,7 @@ public function form(array $form, FormStateInterface $form_state) {
       // equally separated yet. Therefore, we use a $form_state key to point to
       // the parents of the form section.
       // @see self::submitOverviewForm()
-      $form_state['menu_overview_form_parents'] = array('links');
+      $form_state->set('menu_overview_form_parents', ['links']);
       $form['links'] = array();
       $form['links'] = $this->buildOverviewForm($form['links'], $form_state);
     }
@@ -214,7 +214,10 @@ protected function buildOverviewForm(array &$form, FormStateInterface $form_stat
     // section.
     $form['#tree'] = TRUE;
     $form['#theme'] = 'menu_overview_form';
-    $form_state->setIfNotExists('menu_overview_form_parents', array());
+
+    if (!$form_state->has('menu_overview_form_parents')) {
+      $form_state->set('menu_overview_form_parents', []);
+    }
 
     $form['#attached']['css'] = array(drupal_get_path('module', 'menu') . '/css/menu.admin.css');
 
@@ -356,7 +359,7 @@ protected function submitOverviewForm(array $complete_form, FormStateInterface $
     // within forms, but does not allow to handle the form section's submission
     // equally separated yet. Therefore, we use a $form_state key to point to
     // the parents of the form section.
-    $parents = $form_state['menu_overview_form_parents'];
+    $parents = $form_state->get('menu_overview_form_parents');
     $input = NestedArray::getValue($form_state->getUserInput(), $parents);
     $form = &NestedArray::getValue($complete_form, $parents);
 
diff --git a/core/modules/node/src/Form/NodePreviewForm.php b/core/modules/node/src/Form/NodePreviewForm.php
index cdca58c..81fd61c 100644
--- a/core/modules/node/src/Form/NodePreviewForm.php
+++ b/core/modules/node/src/Form/NodePreviewForm.php
@@ -118,8 +118,8 @@ public function buildForm(array $form, FormStateInterface $form_state, EntityInt
    */
   public function submitForm(array &$form, FormStateInterface $form_state) {
     $form_state->setRedirect('entity.node.preview', array(
-      'node_preview' => $form_state['values']['uuid'],
-      'view_mode_id' => $form_state['values']['view_mode'],
+      'node_preview' => $form_state->getValue('uuid'),
+      'view_mode_id' => $form_state->getValue('view_mode'),
     ));
   }
 
diff --git a/core/modules/node/src/NodeForm.php b/core/modules/node/src/NodeForm.php
index 7690b7c..29ba2c3 100644
--- a/core/modules/node/src/NodeForm.php
+++ b/core/modules/node/src/NodeForm.php
@@ -89,10 +89,11 @@ public function form(array $form, FormStateInterface $form_state) {
     }
 
     if ($preview = $store->get($uuid)) {
+      /** @var $preview \Drupal\Core\Form\FormStateInterface */
       $form_state = $preview;
 
       // Rebuild the form.
-      $form_state['rebuild'] = TRUE;
+      $form_state->setRebuild();
       $this->entity = $preview->getFormObject()->getEntity();
       unset($this->entity->in_preview);
     }
@@ -225,7 +226,7 @@ protected function actions(array $form, FormStateInterface $form_state) {
     $node = $this->entity;
     $preview_mode = $this->settings['preview'];
 
-    $element['submit']['#access'] = $preview_mode != DRUPAL_REQUIRED || (!$form_state->getErrors() && isset($form_state['node_preview']));
+    $element['submit']['#access'] = $preview_mode != DRUPAL_REQUIRED || (!$form_state->getErrors() && $form_state->get('node_preview'));
 
     // If saving is an option, privileged users get dedicated form submit
     // buttons to adjust the publishing status while saving in one go.
@@ -431,7 +432,7 @@ public function save(array $form, FormStateInterface $form_state) {
 
     if ($node->id()) {
       $form_state->setValue('nid', $node->id());
-      $form_state['nid'] = $node->id();
+      $form_state->set('nid', $node->id());
       if ($node->access('view')) {
         $form_state->setRedirect(
           'entity.node.canonical',
@@ -452,7 +453,7 @@ public function save(array $form, FormStateInterface $form_state) {
       // In the unlikely case something went wrong on save, the node will be
       // rebuilt and node form redisplayed the same way as in preview.
       drupal_set_message(t('The post could not be saved.'), 'error');
-      $form_state['rebuild'] = TRUE;
+      $form_state->setRebuild();
     }
   }
 
diff --git a/core/modules/node/src/NodeTypeForm.php b/core/modules/node/src/NodeTypeForm.php
index 4088154..08a4dff 100644
--- a/core/modules/node/src/NodeTypeForm.php
+++ b/core/modules/node/src/NodeTypeForm.php
@@ -220,7 +220,7 @@ public function validate(array $form, FormStateInterface $form_state) {
    */
   public function save(array $form, FormStateInterface $form_state) {
     $type = $this->entity;
-    $type->settings['node']['options']['revision'] = $form_state['values']['options']['revision'];
+    $type->settings['node']['options']['revision'] = $form_state->getValue(['options', 'revision']);
     $type->type = trim($type->id());
     $type->name = trim($type->name);
 
@@ -240,15 +240,16 @@ public function save(array $form, FormStateInterface $form_state) {
     $fields = $this->entityManager->getFieldDefinitions('node', $type->id());
     // Update title field definition.
     $title_field = $fields['title'];
-    if ($title_field->getLabel() != $form_state['values']['title_label']) {
-      $title_field->getConfig($type->id())->setLabel($form_state['values']['title_label'])->save();
+    $title_label = $form_state->getValue('title_label');
+    if ($title_field->getLabel() != $title_label) {
+      $title_field->getConfig($type->id())->setLabel($title_label)->save();
     }
     // Update workflow options.
     // @todo Make it possible to get default values without an entity.
     //   https://www.drupal.org/node/2318187
     $node = $this->entityManager->getStorage('node')->create(array('type' => $type->id()));
     foreach (array('status', 'promote', 'sticky')  as $field_name) {
-      $value = (bool) $form_state['values']['options'][$field_name];
+      $value = (bool) $form_state->getValue(['options', $field_name]);
       if ($node->$field_name->value != $value) {
         $fields[$field_name]->getConfig($type->id())->setDefaultValue($value)->save();
       }
diff --git a/core/modules/quickedit/src/Form/QuickEditFieldForm.php b/core/modules/quickedit/src/Form/QuickEditFieldForm.php
index a5c4b38..c90e75a 100644
--- a/core/modules/quickedit/src/Form/QuickEditFieldForm.php
+++ b/core/modules/quickedit/src/Form/QuickEditFieldForm.php
@@ -85,12 +85,12 @@ public function getFormId() {
    * Builds a form for a single entity field.
    */
   public function buildForm(array $form, FormStateInterface $form_state, EntityInterface $entity = NULL, $field_name = NULL) {
-    if (!isset($form_state['entity'])) {
+    if (!$form_state->has('entity')) {
       $this->init($form_state, $entity, $field_name);
     }
 
     // Add the field form.
-    $form_state['form_display']->buildForm($entity, $form, $form_state);
+    $form_state->get('form_display')->buildForm($entity, $form, $form_state);
 
     // Add a dummy changed timestamp field to attach form errors to.
     if ($entity instanceof EntityChangedInterface) {
@@ -127,8 +127,8 @@ protected function init(FormStateInterface $form_state, EntityInterface $entity,
       $entity->revision_log = NULL;
     }
 
-    $form_state['entity'] = $entity;
-    $form_state['field_name'] = $field_name;
+    $form_state->set('entity', $entity);
+    $form_state->set('field_name', $field_name);
 
     // Fetch the display used by the form. It is the display for the 'default'
     // form mode, with only the current field visible.
@@ -138,7 +138,7 @@ protected function init(FormStateInterface $form_state, EntityInterface $entity,
         $display->removeComponent($name);
       }
     }
-    $form_state['form_display'] = $display;
+    $form_state->set('form_display', $display);
   }
 
   /**
@@ -147,7 +147,7 @@ protected function init(FormStateInterface $form_state, EntityInterface $entity,
   public function validateForm(array &$form, FormStateInterface $form_state) {
     $entity = $this->buildEntity($form, $form_state);
 
-    $form_state['form_display']->validateFormValues($entity, $form, $form_state);
+    $form_state->get('form_display')->validateFormValues($entity, $form, $form_state);
 
     // Do validation on the changed field as well and assign the error to the
     // dummy form element we added for this. We don't know the name of this
@@ -166,10 +166,11 @@ public function validateForm(array &$form, FormStateInterface $form_state) {
    * Saves the entity with updated values for the edited field.
    */
   public function submitForm(array &$form, FormStateInterface $form_state) {
-    $form_state['entity'] = $this->buildEntity($form, $form_state);
+    $entity = $this->buildEntity($form, $form_state);
+    $form_state->set('entity', $entity);
 
     // Store entity in tempstore with its UUID as tempstore key.
-    $this->tempStoreFactory->get('quickedit')->set($form_state['entity']->uuid(), $form_state['entity']);
+    $this->tempStoreFactory->get('quickedit')->set($entity->uuid(), $entity);
   }
 
   /**
@@ -180,10 +181,10 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
    */
   protected function buildEntity(array $form, FormStateInterface $form_state) {
     /** @var $entity \Drupal\Core\Entity\EntityInterface */
-    $entity = clone $form_state['entity'];
-    $field_name = $form_state['field_name'];
+    $entity = clone $form_state->get('entity');
+    $field_name = $form_state->get('field_name');
 
-    $form_state['form_display']->extractFormValues($entity, $form, $form_state);
+    $form_state->get('form_display')->extractFormValues($entity, $form, $form_state);
 
     // @todo Refine automated log messages and abstract them to all entity
     //   types: http://drupal.org/node/1678002.
@@ -208,7 +209,7 @@ protected function buildEntity(array $form, FormStateInterface $form_state) {
    *   The current state of the form.
    */
   protected function simplify(array &$form, FormStateInterface $form_state) {
-    $field_name = $form_state['field_name'];
+    $field_name = $form_state->get('field_name');
     $widget_element =& $form[$field_name]['widget'];
 
     // Hide the field label from displaying within the form, because JavaScript
diff --git a/core/modules/quickedit/src/QuickEditController.php b/core/modules/quickedit/src/QuickEditController.php
index c17e4e7..0e6d497 100644
--- a/core/modules/quickedit/src/QuickEditController.php
+++ b/core/modules/quickedit/src/QuickEditController.php
@@ -178,16 +178,13 @@ public function fieldForm(EntityInterface $entity, $field_name, $langcode, $view
       $this->tempStoreFactory->get('quickedit')->set($entity->uuid(), $entity);
     }
 
-    $form_state = new FormState(array(
-      'langcode' => $langcode,
-      'no_redirect' => TRUE,
-      'build_info' => array(
-        'args' => array($entity, $field_name),
-      ),
-    ));
+    $form_state = (new FormState())
+      ->set('langcode', $langcode)
+      ->disableRedirect()
+      ->addBuildInfo('args', [$entity, $field_name]);
     $form = $this->formBuilder()->buildForm('Drupal\quickedit\Form\QuickEditFieldForm', $form_state);
 
-    if (!empty($form_state['executed'])) {
+    if ($form_state->isExecuted()) {
       // The form submission saved the entity in TempStore. Return the
       // updated view of the field from the TempStore copy.
       $entity = $this->tempStoreFactory->get('quickedit')->get($entity->uuid());
diff --git a/core/modules/search/src/Form/SearchPageForm.php b/core/modules/search/src/Form/SearchPageForm.php
index 795cbc0..eab451a 100644
--- a/core/modules/search/src/Form/SearchPageForm.php
+++ b/core/modules/search/src/Form/SearchPageForm.php
@@ -40,7 +40,7 @@ public function getFormID() {
    */
   public function form(array $form, FormStateInterface $form_state) {
     $plugin = $this->entity->getPlugin();
-    $form_state['search_page_id'] = $this->entity->id();
+    $form_state->set('search_page_id', $this->entity->id());
 
     $form['basic'] = array(
       '#type' => 'container',
@@ -90,7 +90,7 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
     // into the GET as well. If so, make sure to put 'keys' into the GET
     // parameters so that the search results generation is triggered.
     $query = $this->entity->getPlugin()->buildSearchUrlQuery($form_state);
-    $route = 'search.view_' . $form_state['search_page_id'];
+    $route = 'search.view_' . $form_state->get('search_page_id');
     $form_state->setRedirect(
       $route,
       array(),
diff --git a/core/modules/search/src/Plugin/SearchInterface.php b/core/modules/search/src/Plugin/SearchInterface.php
index 6eabf18..3c5f76d 100644
--- a/core/modules/search/src/Plugin/SearchInterface.php
+++ b/core/modules/search/src/Plugin/SearchInterface.php
@@ -95,7 +95,7 @@ public function buildResults();
    * @param \Drupal\Core\Form\FormStateInterface $form_state
    *   The current state of the form. The arguments that
    *   \Drupal::formBuilder()->getForm() was originally called with are
-   *   available in the array $form_state['build_info']['args'].
+   *   available in the array $form_state->getBuildInfo()['args'].
    *
    * @see SearchInterface::buildSearchUrlQuery()
    */
diff --git a/core/modules/search/src/Plugin/views/filter/Search.php b/core/modules/search/src/Plugin/views/filter/Search.php
index 7635125..1f8d193 100644
--- a/core/modules/search/src/Plugin/views/filter/Search.php
+++ b/core/modules/search/src/Plugin/views/filter/Search.php
@@ -92,7 +92,7 @@ protected function valueForm(&$form, FormStateInterface $form_state) {
       '#size' => 15,
       '#default_value' => $this->value,
       '#attributes' => array('title' => $this->t('Search keywords')),
-      '#title' => empty($form_state['exposed']) ? $this->t('Keywords') : '',
+      '#title' => !$form_state->get('exposed') ? $this->t('Keywords') : '',
     );
   }
 
diff --git a/core/modules/simpletest/src/Form/SimpletestResultsForm.php b/core/modules/simpletest/src/Form/SimpletestResultsForm.php
index ead1767..40e3711 100644
--- a/core/modules/simpletest/src/Form/SimpletestResultsForm.php
+++ b/core/modules/simpletest/src/Form/SimpletestResultsForm.php
@@ -271,7 +271,7 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
     }
 
     $form_execute = array();
-    $form_state_execute = new FormState(array('values' => array()));
+    $form_state_execute = new FormState();
     foreach ($classes as $class) {
       $form_state_execute['values']['tests'][$class] = $class;
     }
@@ -279,7 +279,7 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
     // Submit the simpletest test form to rerun the tests.
     // Under normal circumstances, a form object's submitForm() should never be
     // called directly, FormBuilder::submitForm() should be called instead.
-    // However, it sets $form_state['programmed'], which disables the Batch API.
+    // However, it calls $form_state->setProgrammed(), which disables the Batch API.
     $simpletest_test_form = new SimpletestTestForm();
     $simpletest_test_form->buildForm($form_execute, $form_state_execute);
     $simpletest_test_form->submitForm($form_execute, $form_state_execute);
diff --git a/core/modules/system/entity.api.php b/core/modules/system/entity.api.php
index 7b0559c..3c8c084 100644
--- a/core/modules/system/entity.api.php
+++ b/core/modules/system/entity.api.php
@@ -1564,7 +1564,7 @@ function hook_entity_display_build_alter(&$build, $context) {
 function hook_entity_prepare_form(\Drupal\Core\Entity\EntityInterface $entity, $operation, \Drupal\Core\Form\FormStateInterface $form_state) {
   if ($operation == 'edit') {
     $entity->label->value = 'Altered label';
-    $form_state['mymodule']['label_altered'] = TRUE;
+    $form_state->set('label_altered', TRUE);
   }
 }
 
@@ -1590,7 +1590,7 @@ function hook_entity_prepare_form(\Drupal\Core\Entity\EntityInterface $entity, $
 function hook_ENTITY_TYPE_prepare_form(\Drupal\Core\Entity\EntityInterface $entity, $operation, \Drupal\Core\Form\FormStateInterface $form_state) {
   if ($operation == 'edit') {
     $entity->label->value = 'Altered label';
-    $form_state['mymodule']['label_altered'] = TRUE;
+    $form_state->set('label_altered', TRUE);
   }
 }
 
diff --git a/core/modules/system/src/Controller/FormAjaxController.php b/core/modules/system/src/Controller/FormAjaxController.php
index 5285cfd..c3c678c 100644
--- a/core/modules/system/src/Controller/FormAjaxController.php
+++ b/core/modules/system/src/Controller/FormAjaxController.php
@@ -73,8 +73,9 @@ public function content(Request $request) {
     // button) that triggered the Ajax request to determine what needs to be
     // rendered.
     $callback = NULL;
-    if (!empty($form_state['triggering_element'])) {
-      $callback = $form_state['triggering_element']['#ajax']['callback'];
+    /** @var $form_state \Drupal\Core\Form\FormStateInterface */
+    if ($triggering_element = $form_state->getTriggeringElement()) {
+      $callback = $triggering_element['#ajax']['callback'];
     }
     $callback = $form_state->prepareCallback($callback);
     if (empty($callback) || !is_callable($callback)) {
@@ -118,13 +119,15 @@ protected function getForm(Request $request) {
     }
 
     // Since some of the submit handlers are run, redirects need to be disabled.
-    $form_state['no_redirect'] = TRUE;
+    $form_state->disableRedirect();
 
     // When a form is rebuilt after Ajax processing, its #build_id and #action
     // should not change.
     // @see \Drupal\Core\Form\FormBuilderInterface::rebuildForm()
-    $form_state['rebuild_info']['copy']['#build_id'] = TRUE;
-    $form_state['rebuild_info']['copy']['#action'] = TRUE;
+    $form_state->addRebuildInfo('copy', [
+      '#build_id' => TRUE,
+      '#action' => TRUE,
+    ]);
 
     // The form needs to be processed; prepare for that by setting a few internal
     // variables.
diff --git a/core/modules/system/src/Plugin/Block/SystemBrandingBlock.php b/core/modules/system/src/Plugin/Block/SystemBrandingBlock.php
index 7740c79..f50a287 100644
--- a/core/modules/system/src/Plugin/Block/SystemBrandingBlock.php
+++ b/core/modules/system/src/Plugin/Block/SystemBrandingBlock.php
@@ -85,7 +85,7 @@ public function defaultConfiguration() {
    */
   public function blockForm($form, FormStateInterface $form_state) {
     // Get the theme.
-    $theme = $form_state['block_theme'];
+    $theme = $form_state->get('block_theme');
 
     // Get permissions.
     $url_system_theme_settings = new Url('system.theme_settings');
diff --git a/core/modules/system/src/Plugin/views/field/BulkForm.php b/core/modules/system/src/Plugin/views/field/BulkForm.php
index bae235d..ce34c90 100644
--- a/core/modules/system/src/Plugin/views/field/BulkForm.php
+++ b/core/modules/system/src/Plugin/views/field/BulkForm.php
@@ -251,7 +251,7 @@ protected function getBulkOptions($filtered = TRUE) {
    *   The current state of the form.
    */
   public function viewsFormSubmit(&$form, FormStateInterface $form_state) {
-    if ($form_state['step'] == 'views_form_views_form') {
+    if ($form_state->get('step') == 'views_form_views_form') {
       // Filter only selected checkboxes.
       $selected = array_filter($form_state->getValue($this->options['id']));
       $entities = array();
diff --git a/core/modules/system/src/Tests/Entity/EntityTranslationFormTest.php b/core/modules/system/src/Tests/Entity/EntityTranslationFormTest.php
index 32a154d..f320dd8 100644
--- a/core/modules/system/src/Tests/Entity/EntityTranslationFormTest.php
+++ b/core/modules/system/src/Tests/Entity/EntityTranslationFormTest.php
@@ -73,8 +73,8 @@ function testEntityFormLanguage() {
 
     // Explicitly set form langcode.
     $langcode = $this->langcodes[0];
-    $form_state['langcode'] = $langcode;
-    \Drupal::service('entity.form_builder')->getForm($node, 'default', $form_state);
+    $form_state_additions['langcode'] = $langcode;
+    \Drupal::service('entity.form_builder')->getForm($node, 'default', $form_state_additions);
     $form_langcode = \Drupal::state()->get('entity_test.form_langcode');
     $this->assertTrue($langcode == $form_langcode, 'Form language is the same as the language parameter.');
 
diff --git a/core/modules/system/src/Tests/Entity/FieldWidgetConstraintValidatorTest.php b/core/modules/system/src/Tests/Entity/FieldWidgetConstraintValidatorTest.php
index 5fa873d..e915ec1 100644
--- a/core/modules/system/src/Tests/Entity/FieldWidgetConstraintValidatorTest.php
+++ b/core/modules/system/src/Tests/Entity/FieldWidgetConstraintValidatorTest.php
@@ -32,7 +32,7 @@ public function testValidation() {
     $display->buildForm($entity, $form, $form_state);
 
     // Pretend the form has been built.
-    $form_state['build_info']['callback_object'] = \Drupal::entityManager()->getFormObject($entity_type, 'default');
+    $form_state->setFormObject(\Drupal::entityManager()->getFormObject($entity_type, 'default'));
     \Drupal::formBuilder()->prepareForm('field_test_entity_form', $form, $form_state);
     \Drupal::formBuilder()->processForm('field_test_entity_form', $form, $form_state);
 
diff --git a/core/modules/system/src/Tests/Form/ElementsTableSelectTest.php b/core/modules/system/src/Tests/Form/ElementsTableSelectTest.php
index 0bde8d0..e9c66f3 100644
--- a/core/modules/system/src/Tests/Form/ElementsTableSelectTest.php
+++ b/core/modules/system/src/Tests/Form/ElementsTableSelectTest.php
@@ -218,7 +218,7 @@ private function formSubmitHelper($form, $edit) {
 
     $edit['form_id'] = $form_id;
     $form_state->setUserInput($edit);
-    $form_state['build_info']['callback_object'] = new StubForm($form_id, $form);
+    $form_state->setFormObject(new StubForm($form_id, $form));
 
     \Drupal::formBuilder()->prepareForm($form_id, $form, $form_state);
 
@@ -228,7 +228,7 @@ private function formSubmitHelper($form, $edit) {
 
     // Clear errors and messages.
     drupal_get_messages();
-    $form_state['errors'] = array();
+    $form_state->clearErrors();
 
     // Return the processed form together with form_state and errors
     // to allow the caller lowlevel access to the form.
diff --git a/core/modules/system/src/Tests/Form/FormDefaultHandlersTest.php b/core/modules/system/src/Tests/Form/FormDefaultHandlersTest.php
index 51c79f8..3717c6f 100644
--- a/core/modules/system/src/Tests/Form/FormDefaultHandlersTest.php
+++ b/core/modules/system/src/Tests/Form/FormDefaultHandlersTest.php
@@ -47,39 +47,47 @@ public function buildForm(array $form, FormStateInterface $form_state) {
    * {@inheritdoc}
    */
   public function customValidateForm(array &$form, FormStateInterface $form_state) {
-    $form_state['test_handlers']['validate'][] = __FUNCTION__;
+    $test_handlers = $form_state->get('test_handlers');
+    $test_handlers['validate'][] = __FUNCTION__;
+    $form_state->set('test_handlers', $test_handlers);
   }
 
   /**
    * {@inheritdoc}
    */
   public function validateForm(array &$form, FormStateInterface $form_state) {
-    $form_state['test_handlers']['validate'][] = __FUNCTION__;
+    $test_handlers = $form_state->get('test_handlers');
+    $test_handlers['validate'][] = __FUNCTION__;
+    $form_state->set('test_handlers', $test_handlers);
   }
 
   /**
    * {@inheritdoc}
    */
   public function customSubmitForm(array &$form, FormStateInterface $form_state) {
-    $form_state['test_handlers']['submit'][] = __FUNCTION__;
+    $test_handlers = $form_state->get('test_handlers');
+    $test_handlers['submit'][] = __FUNCTION__;
+    $form_state->set('test_handlers', $test_handlers);
   }
 
   /**
    * {@inheritdoc}
    */
   public function submitForm(array &$form, FormStateInterface $form_state) {
-    $form_state['test_handlers']['submit'][] = __FUNCTION__;
+    $test_handlers = $form_state->get('test_handlers');
+    $test_handlers['submit'][] = __FUNCTION__;
+    $form_state->set('test_handlers', $test_handlers);
   }
 
   /**
    * Tests that default handlers are added even if custom are specified.
    */
   function testDefaultAndCustomHandlers() {
-    $form_state = new FormState(array('values' => array()));
+    $form_state = new FormState();
     $form_builder = $this->container->get('form_builder');
     $form_builder->submitForm($this, $form_state);
 
-    $handlers = $form_state['test_handlers'];
+    $handlers = $form_state->get('test_handlers');
 
     $this->assertIdentical(count($handlers['validate']), 2);
     $this->assertIdentical($handlers['validate'][0], 'customValidateForm');
diff --git a/core/modules/system/src/Tests/Form/FormTest.php b/core/modules/system/src/Tests/Form/FormTest.php
index be8d7e0..cfcc299 100644
--- a/core/modules/system/src/Tests/Form/FormTest.php
+++ b/core/modules/system/src/Tests/Form/FormTest.php
@@ -111,8 +111,8 @@ function testRequiredFields() {
           $user_input[$element] = $empty;
           $user_input['form_id'] = $form_id;
           $form_state->setUserInput($user_input);
-          $form_state['build_info']['callback_object'] = new StubForm($form_id, $form);
-          $form_state['method'] = 'post';
+          $form_state->setFormObject(new StubForm($form_id, $form));
+          $form_state->setMethod('post');
           // The form token CSRF protection should not interfere with this test,
           // so we bypass it by setting the token to FALSE.
           $form['#token'] = FALSE;
diff --git a/core/modules/system/src/Tests/Form/ProgrammaticTest.php b/core/modules/system/src/Tests/Form/ProgrammaticTest.php
index cfe9cb2..be44907 100644
--- a/core/modules/system/src/Tests/Form/ProgrammaticTest.php
+++ b/core/modules/system/src/Tests/Form/ProgrammaticTest.php
@@ -72,7 +72,7 @@ function testSubmissionWorkflow() {
    */
   private function submitForm($values, $valid_input) {
     // Programmatically submit the given values.
-    $form_state = new FormState(array('values' => $values));
+    $form_state = (new FormState())->setValues($values);
     \Drupal::formBuilder()->submitForm('\Drupal\form_test\Form\FormTestProgrammaticForm', $form_state);
 
     // Check that the form returns an error when expected, and vice versa.
@@ -86,9 +86,7 @@ private function submitForm($values, $valid_input) {
 
     // We check submitted values only if we have a valid input.
     if ($valid_input) {
-      // By fetching the values from $form_state['storage'] we ensure that the
-      // submission handler was properly executed.
-      $stored_values = $form_state['storage']['programmatic_form_submit'];
+      $stored_values = $form_state->get('programmatic_form_submit');
       foreach ($values as $key => $value) {
         $this->assertEqual($stored_values[$key], $value, format_string('Submission handler correctly executed: %stored_key is %stored_value', array('%stored_key' => $key, '%stored_value' => print_r($value, TRUE))));
       }
@@ -99,25 +97,25 @@ private function submitForm($values, $valid_input) {
    * Test the programmed_bypass_access_check flag.
    */
   public function testProgrammaticAccessBypass() {
-    $form_state = new FormState(array('values' => array(
+    $form_state = (new FormState())->setValues([
       'textfield' => 'dummy value',
       'field_restricted' => 'dummy value'
-    )));
+    ]);
 
     // Programmatically submit the form with a value for the restricted field.
     // Since programmed_bypass_access_check is set to TRUE by default, the
     // field is accessible and can be set.
     \Drupal::formBuilder()->submitForm('\Drupal\form_test\Form\FormTestProgrammaticForm', $form_state);
-    $values = $form_state['storage']['programmatic_form_submit'];
+    $values = $form_state->get('programmatic_form_submit');
     $this->assertEqual($values['field_restricted'], 'dummy value', 'The value for the restricted field is stored correctly.');
 
     // Programmatically submit the form with a value for the restricted field
     // with programmed_bypass_access_check set to FALSE. Since access
     // restrictions apply, the restricted field is inaccessible, and the value
     // should not be stored.
-    $form_state['programmed_bypass_access_check'] = FALSE;
+    $form_state->setProgrammedBypassAccessCheck(FALSE);
     \Drupal::formBuilder()->submitForm('\Drupal\form_test\Form\FormTestProgrammaticForm', $form_state);
-    $values = $form_state['storage']['programmatic_form_submit'];
+    $values = $form_state->get('programmatic_form_submit');
     $this->assertNotEqual($values['field_restricted'], 'dummy value', 'The value for the restricted field is not stored.');
 
   }
diff --git a/core/modules/system/src/Tests/Form/StorageTest.php b/core/modules/system/src/Tests/Form/StorageTest.php
index 8b60f2c..01189d1 100644
--- a/core/modules/system/src/Tests/Form/StorageTest.php
+++ b/core/modules/system/src/Tests/Form/StorageTest.php
@@ -64,7 +64,7 @@ function testForm() {
   }
 
   /**
-   * Tests using the form with an activated $form_state['cache'] property.
+   * Tests using the form after calling $form_state->setCached().
    */
   function testFormCached() {
     $this->drupalGet('form_test/form-storage', array('query' => array('cache' => 1)));
diff --git a/core/modules/system/src/Tests/Form/TriggeringElementProgrammedUnitTest.php b/core/modules/system/src/Tests/Form/TriggeringElementProgrammedUnitTest.php
index 19d683b..65a1ae0 100644
--- a/core/modules/system/src/Tests/Form/TriggeringElementProgrammedUnitTest.php
+++ b/core/modules/system/src/Tests/Form/TriggeringElementProgrammedUnitTest.php
@@ -62,7 +62,7 @@ public function buildForm(array $form, FormStateInterface $form_state) {
    */
   public function validateForm(array &$form, FormStateInterface $form_state) {
     // Verify that the only submit button was recognized as triggering_element.
-    $this->assertEqual($form['actions']['submit']['#array_parents'], $form_state['triggering_element']['#array_parents']);
+    $this->assertEqual($form['actions']['submit']['#array_parents'], $form_state->getTriggeringElement()['#array_parents']);
   }
 
   /**
diff --git a/core/modules/system/src/Tests/Form/TriggeringElementTest.php b/core/modules/system/src/Tests/Form/TriggeringElementTest.php
index 21a2195..89bdd94 100644
--- a/core/modules/system/src/Tests/Form/TriggeringElementTest.php
+++ b/core/modules/system/src/Tests/Form/TriggeringElementTest.php
@@ -10,7 +10,7 @@
 use Drupal\simpletest\WebTestBase;
 
 /**
- * Tests that FAPI correctly determines $form_state['triggering_element'].
+ * Tests that FAPI correctly determines the triggering_element.
  *
  * @group Form
  */
@@ -24,7 +24,7 @@ class TriggeringElementTest extends WebTestBase {
   public static $modules = array('form_test');
 
   /**
-   * Test the determination of $form_state['triggering_element'] when no button
+   * Test the determination of the triggering element when no button
    * information is included in the POST data, as is sometimes the case when
    * the ENTER key is pressed in a textfield in Internet Explorer.
    */
@@ -33,17 +33,15 @@ function testNoButtonInfoInPost() {
     $edit = array();
     $form_html_id = 'form-test-clicked-button';
 
-    // Ensure submitting a form with no buttons results in no
-    // $form_state['triggering_element'] and the form submit handler not
-    // running.
+    // Ensure submitting a form with no buttons results in no triggering element
+    // and the form submit handler not running.
     $this->drupalPostForm($path, $edit, NULL, array(), array(), $form_html_id);
     $this->assertText('There is no clicked button.', '$form_state[\'triggering_element\'] set to NULL.');
     $this->assertNoText('Submit handler for form_test_clicked_button executed.', 'Form submit handler did not execute.');
 
-    // Ensure submitting a form with one or more submit buttons results in
-    // $form_state['triggering_element'] being set to the first one the user has
-    // access to. An argument with 'r' in it indicates a restricted
-    // (#access=FALSE) button.
+    // Ensure submitting a form with one or more submit buttons results in the
+    // triggering element being set to the first one the user has access to. An
+    // argument with 'r' in it indicates a restricted (#access=FALSE) button.
     $this->drupalPostForm($path . '/s', $edit, NULL, array(), array(), $form_html_id);
     $this->assertText('The clicked button is button1.', '$form_state[\'triggering_element\'] set to only button.');
     $this->assertText('Submit handler for form_test_clicked_button executed.', 'Form submit handler executed.');
@@ -56,11 +54,10 @@ function testNoButtonInfoInPost() {
     $this->assertText('The clicked button is button2.', '$form_state[\'triggering_element\'] set to first available button.');
     $this->assertText('Submit handler for form_test_clicked_button executed.', 'Form submit handler executed.');
 
-    // Ensure submitting a form with buttons of different types results in
-    // $form_state['triggering_element'] being set to the first button,
-    // regardless of type. For the FAPI 'button' type, this should result in the
-    // submit handler not executing. The types are 's'(ubmit), 'b'(utton), and
-    // 'i'(mage_button).
+    // Ensure submitting a form with buttons of different types results in the
+    // triggering element being set to the first button, regardless of type. For
+    // the FAPI 'button' type, this should result in the submit handler not
+    // executing. The types are 's'(ubmit), 'b'(utton), and 'i'(mage_button).
     $this->drupalPostForm($path . '/s/b/i', $edit, NULL, array(), array(), $form_html_id);
     $this->assertText('The clicked button is button1.', '$form_state[\'triggering_element\'] set to first button.');
     $this->assertText('Submit handler for form_test_clicked_button executed.', 'Form submit handler executed.');
@@ -75,8 +72,8 @@ function testNoButtonInfoInPost() {
   }
 
   /**
-   * Test that $form_state['triggering_element'] does not get set to a button
-   * with #access=FALSE.
+   * Test that the triggering element does not get set to a button with
+   * #access=FALSE.
    */
   function testAttemptAccessControlBypass() {
     $path = 'form-test/clicked-button';
@@ -94,11 +91,10 @@ function testAttemptAccessControlBypass() {
     $elements[0]['name'] = 'button1';
     $this->drupalPostForm(NULL, array('button1' => 'button1'), NULL, array(), array(), $form_html_id);
 
-    // Ensure that $form_state['triggering_element'] was not set to the
-    // restricted button. Do this with both a negative and positive assertion,
-    // because negative assertions alone can be brittle. See
-    // testNoButtonInfoInPost() for why the triggering element gets set to
-    // 'button2'.
+    // Ensure that the triggering element was not set to the restricted button.
+    // Do this with both a negative and positive assertion, because negative
+    // assertions alone can be brittle. See testNoButtonInfoInPost() for why the
+    //triggering element gets set to 'button2'.
     $this->assertNoText('The clicked button is button1.', '$form_state[\'triggering_element\'] not set to a restricted button.');
     $this->assertText('The clicked button is button2.', '$form_state[\'triggering_element\'] not set to a restricted button.');
   }
diff --git a/core/modules/system/src/Tests/System/SystemConfigFormTestBase.php b/core/modules/system/src/Tests/System/SystemConfigFormTestBase.php
index 388977a..a9f17c0 100644
--- a/core/modules/system/src/Tests/System/SystemConfigFormTestBase.php
+++ b/core/modules/system/src/Tests/System/SystemConfigFormTestBase.php
@@ -52,7 +52,7 @@ public function testConfigForm() {
     foreach ($this->values as $form_key => $data) {
       $values[$form_key] = $data['#value'];
     }
-    $form_state = new FormState(array('values' => $values));
+    $form_state = (new FormState())->setValues($values);
     \Drupal::formBuilder()->submitForm($this->form, $form_state);
 
     // Check that the form returns an error when expected, and vice versa.
diff --git a/core/modules/system/system.api.php b/core/modules/system/system.api.php
index 59922b7..cbdf22c 100644
--- a/core/modules/system/system.api.php
+++ b/core/modules/system/system.api.php
@@ -698,7 +698,7 @@ function hook_page_alter(&$page) {
  * @param $form_state
  *   The current state of the form. The arguments that
  *   \Drupal::formBuilder()->getForm() was originally called with are available
- *   in the array $form_state['build_info']['args'].
+ *   in the array $form_state->getBuildInfo()['args'].
  * @param $form_id
  *   String representing the name of the form itself. Typically this is the
  *   name of the function that generated the form.
@@ -737,7 +737,7 @@ function hook_form_alter(&$form, \Drupal\Core\Form\FormStateInterface $form_stat
  * @param $form_state
  *   The current state of the form. The arguments that
  *   \Drupal::formBuilder()->getForm() was originally called with are available
- *   in the array $form_state['build_info']['args'].
+ *   in the array $form_state->getBuildInfo()['args'].
  * @param $form_id
  *   String representing the name of the form itself. Typically this is the
  *   name of the function that generated the form.
@@ -774,7 +774,7 @@ function hook_form_FORM_ID_alter(&$form, \Drupal\Core\Form\FormStateInterface $f
  *
  * To identify the base form ID for a particular form (or to determine whether
  * one exists) check the $form_state. The base form ID is stored under
- * $form_state['build_info']['base_form_id'].
+ * $form_state->getBuildInfo()['base_form_id'].
  *
  * Form alter hooks are called in the following order: hook_form_alter(),
  * hook_form_BASE_FORM_ID_alter(), hook_form_FORM_ID_alter(). See
diff --git a/core/modules/system/tests/modules/ajax_forms_test/src/Form/AjaxFormsTestLazyLoadForm.php b/core/modules/system/tests/modules/ajax_forms_test/src/Form/AjaxFormsTestLazyLoadForm.php
index d413195..96f5cd5 100644
--- a/core/modules/system/tests/modules/ajax_forms_test/src/Form/AjaxFormsTestLazyLoadForm.php
+++ b/core/modules/system/tests/modules/ajax_forms_test/src/Form/AjaxFormsTestLazyLoadForm.php
@@ -75,7 +75,7 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
       drupal_render($attached);
       drupal_process_attached($attached);
     }
-    $form_state['rebuild'] = TRUE;
+    $form_state->setRebuild();
   }
 
 }
diff --git a/core/modules/system/tests/modules/batch_test/src/Controller/BatchTestController.php b/core/modules/system/tests/modules/batch_test/src/Controller/BatchTestController.php
index fcd9b01..d6829fa 100644
--- a/core/modules/system/tests/modules/batch_test/src/Controller/BatchTestController.php
+++ b/core/modules/system/tests/modules/batch_test/src/Controller/BatchTestController.php
@@ -85,9 +85,9 @@ public function testNoForm() {
    *   Render array containing markup.
    */
   function testProgrammatic($value = 1) {
-    $form_state = new FormState(array(
-      'values' => array('value' => $value)
-    ));
+    $form_state = (new FormState())->setValues([
+      'value' => $value,
+    ]);
     \Drupal::formBuilder()->submitForm('Drupal\batch_test\Form\BatchTestChainedForm', $form_state);
     return array(
       'success' => array(
diff --git a/core/modules/system/tests/modules/batch_test/src/Form/BatchTestMultiStepForm.php b/core/modules/system/tests/modules/batch_test/src/Form/BatchTestMultiStepForm.php
index e5c72e9..19bf6d3 100644
--- a/core/modules/system/tests/modules/batch_test/src/Form/BatchTestMultiStepForm.php
+++ b/core/modules/system/tests/modules/batch_test/src/Form/BatchTestMultiStepForm.php
@@ -26,12 +26,14 @@ public function getFormId() {
    * {@inheritdoc}
    */
   public function buildForm(array $form, FormStateInterface $form_state) {
-    if (empty($form_state['storage']['step'])) {
-      $form_state['storage']['step'] = 1;
+    $step = $form_state->get('step');
+    if (empty($step)) {
+      $step = 1;
+      $form_state->set('step', $step);
     }
 
     $form['step_display'] = array(
-      '#markup' => 'step ' . $form_state['storage']['step'] . '<br/>',
+      '#markup' => 'step ' . $step . '<br/>',
     );
     $form['submit'] = array(
       '#type' => 'submit',
@@ -47,7 +49,8 @@ public function buildForm(array $form, FormStateInterface $form_state) {
   public function submitForm(array &$form, FormStateInterface $form_state) {
     batch_test_stack(NULL, TRUE);
 
-    switch ($form_state['storage']['step']) {
+    $step = $form_state->get('step');
+    switch ($step) {
       case 1:
         batch_set(_batch_test_batch_1());
         break;
@@ -56,9 +59,10 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
         break;
     }
 
-    if ($form_state['storage']['step'] < 2) {
-      $form_state['storage']['step']++;
-      $form_state['rebuild'] = TRUE;
+    if ($step < 2) {
+      $step++;
+      $form_state->set('step', $step);
+      $form_state->setRebuild();
     }
 
     $form_state->setRedirect('batch_test.redirect');
diff --git a/core/modules/system/tests/modules/entity_test/src/EntityTestForm.php b/core/modules/system/tests/modules/entity_test/src/EntityTestForm.php
index d01f3b9..bb7f1a8 100644
--- a/core/modules/system/tests/modules/entity_test/src/EntityTestForm.php
+++ b/core/modules/system/tests/modules/entity_test/src/EntityTestForm.php
@@ -102,7 +102,7 @@ public function save(array $form, FormStateInterface $form_state) {
     else {
       // Error on save.
       drupal_set_message(t('The entity could not be saved.'), 'error');
-      $form_state['rebuild'] = TRUE;
+      $form_state->setRebuild();
     }
   }
 
diff --git a/core/modules/system/tests/modules/form_test/form_test.module b/core/modules/system/tests/modules/form_test/form_test.module
index ad38397..380241b 100644
--- a/core/modules/system/tests/modules/form_test/form_test.module
+++ b/core/modules/system/tests/modules/form_test/form_test.module
@@ -99,7 +99,7 @@ function form_test_form_form_test_state_persist_alter(&$form, FormStateInterface
   // Simulate a form alter implementation inserting form elements that enable
   // caching of the form, e.g. elements having #ajax.
   if (\Drupal::request()->get('cache')) {
-    $form_state['cache'] = TRUE;
+    $form_state->setCached();
   }
 }
 
@@ -119,5 +119,5 @@ function form_test_form_user_register_form_alter(&$form, FormStateInterface $for
  */
 function form_test_user_register_form_rebuild($form, FormStateInterface $form_state) {
   drupal_set_message('Form rebuilt.');
-  $form_state['rebuild'] = TRUE;
+  $form_state->setRebuild();
 }
diff --git a/core/modules/system/tests/modules/form_test/src/Callbacks.php b/core/modules/system/tests/modules/form_test/src/Callbacks.php
index ed26dd9..3edf0fa 100644
--- a/core/modules/system/tests/modules/form_test/src/Callbacks.php
+++ b/core/modules/system/tests/modules/form_test/src/Callbacks.php
@@ -28,15 +28,15 @@ public function validateName(&$element, FormStateInterface $form_state) {
       $triggered = TRUE;
     }
     if ($form_state->getValue('name') == 'element_validate_access') {
-      $form_state['storage']['form_test_name'] = $form_state->getValue('name');
+      $form_state->set('form_test_name', $form_state->getValue('name'));
       // Alter the form element.
       $element['#access'] = FALSE;
 
       $triggered = TRUE;
     }
-    elseif (!empty($form_state['storage']['form_test_name'])) {
+    elseif ($form_state->has('form_test_name')) {
       // To simplify this test, just take over the element's value into $form_state.
-      form_set_value($element, $form_state['storage']['form_test_name'], $form_state);
+      form_set_value($element, $form_state->get('form_test_name'), $form_state);
 
       $triggered = TRUE;
     }
diff --git a/core/modules/system/tests/modules/form_test/src/Form/FormTestCheckboxesZeroForm.php b/core/modules/system/tests/modules/form_test/src/Form/FormTestCheckboxesZeroForm.php
index a302fb0..ccd752f 100644
--- a/core/modules/system/tests/modules/form_test/src/Form/FormTestCheckboxesZeroForm.php
+++ b/core/modules/system/tests/modules/form_test/src/Form/FormTestCheckboxesZeroForm.php
@@ -27,7 +27,7 @@ public function getFormId() {
    * {@inheritdoc}
    */
   public function buildForm(array $form, FormStateInterface $form_state, $json = TRUE) {
-    $form_state['json'] = $json;
+    $form_state->set('json', $json);
     $form['checkbox_off'] = array(
       '#title' => t('Checkbox off'),
       '#type' => 'checkboxes',
@@ -56,11 +56,11 @@ public function buildForm(array $form, FormStateInterface $form_state, $json = T
    * {@inheritdoc}
    */
   public function submitForm(array &$form, FormStateInterface $form_state) {
-    if (!empty($form_state['json'])) {
+    if ($form_state->has('json')) {
       $form_state->setResponse(new JsonResponse($form_state->getValues()));
     }
     else {
-      $form_state['no_redirect'] = TRUE;
+      $form_state->disableRedirect();
     }
   }
 
diff --git a/core/modules/system/tests/modules/form_test/src/Form/FormTestClickedButtonForm.php b/core/modules/system/tests/modules/form_test/src/Form/FormTestClickedButtonForm.php
index b0c4957..72ac7ac 100644
--- a/core/modules/system/tests/modules/form_test/src/Form/FormTestClickedButtonForm.php
+++ b/core/modules/system/tests/modules/form_test/src/Form/FormTestClickedButtonForm.php
@@ -85,8 +85,8 @@ public function buildForm(array $form, FormStateInterface $form_state, $first =
    * {@inheritdoc}
    */
   public function validateForm(array &$form, FormStateInterface $form_state) {
-    if (isset($form_state['triggering_element'])) {
-      drupal_set_message(t('The clicked button is %name.', array('%name' => $form_state['triggering_element']['#name'])));
+    if ($triggering_element = $form_state->getTriggeringElement()) {
+      drupal_set_message(t('The clicked button is %name.', ['%name' => $triggering_element['#name']]));
     }
     else {
       drupal_set_message('There is no clicked button.');
diff --git a/core/modules/system/tests/modules/form_test/src/Form/FormTestFormStateDatabaseForm.php b/core/modules/system/tests/modules/form_test/src/Form/FormTestFormStateDatabaseForm.php
index a0dda13..87014c7 100644
--- a/core/modules/system/tests/modules/form_test/src/Form/FormTestFormStateDatabaseForm.php
+++ b/core/modules/system/tests/modules/form_test/src/Form/FormTestFormStateDatabaseForm.php
@@ -38,10 +38,10 @@ public function buildForm(array $form, FormStateInterface $form_state) {
     );
 
     $db = Database::getConnection('default');
-    $form_state['storage']['database'] = $db;
-    $form_state['storage']['database_class'] = get_class($db);
+    $form_state->set('database', $db);
+    $form_state->set('database_class', get_class($db));
 
-    if (isset($form_state['storage']['database_connection_found'])) {
+    if ($form_state->has('database_connection_found')) {
       $form['database']['#markup'] = 'Database connection found';
     }
 
@@ -52,11 +52,12 @@ public function buildForm(array $form, FormStateInterface $form_state) {
    * {@inheritdoc}
    */
   public function submitForm(array &$form, FormStateInterface $form_state) {
-    $form_state['cache'] = TRUE;
-    $form_state['rebuild'] = TRUE;
+    $form_state->setCached();
+    $form_state->setRebuild();
 
-    if ($form_state['storage']['database'] instanceof $form_state['storage']['database_class']) {
-      $form_state['storage']['database_connection_found'] = TRUE;
+    $database_class = $form_state->get('database_class');
+    if ($form_state->get('database') instanceof $database_class) {
+      $form_state->set('database_connection_found', TRUE);
     }
   }
 
diff --git a/core/modules/system/tests/modules/form_test/src/Form/FormTestProgrammaticForm.php b/core/modules/system/tests/modules/form_test/src/Form/FormTestProgrammaticForm.php
index baa146b..f5d1667 100644
--- a/core/modules/system/tests/modules/form_test/src/Form/FormTestProgrammaticForm.php
+++ b/core/modules/system/tests/modules/form_test/src/Form/FormTestProgrammaticForm.php
@@ -99,7 +99,7 @@ public function validateForm(array &$form, FormStateInterface $form_state) {
    * {@inheritdoc}
    */
   public function submitForm(array &$form, FormStateInterface $form_state) {
-    $form_state['storage']['programmatic_form_submit'] = $form_state->getValues();
+    $form_state->set('programmatic_form_submit', $form_state->getValues());
   }
 
 }
diff --git a/core/modules/system/tests/modules/form_test/src/Form/FormTestRebuildPreserveValuesForm.php b/core/modules/system/tests/modules/form_test/src/Form/FormTestRebuildPreserveValuesForm.php
index 00a9570..45d485c 100644
--- a/core/modules/system/tests/modules/form_test/src/Form/FormTestRebuildPreserveValuesForm.php
+++ b/core/modules/system/tests/modules/form_test/src/Form/FormTestRebuildPreserveValuesForm.php
@@ -49,7 +49,7 @@ public function buildForm(array $form, FormStateInterface $form_state) {
     // checkboxes and a textfield. The test is to make sure that the rebuild
     // triggered by this button preserves the user input values for the initial
     // elements and initializes the new elements with the correct default values.
-    if (empty($form_state['storage']['add_more'])) {
+    if (!$form_state->has('add_more')) {
       $form['add_more'] = array(
         '#type' => 'submit',
         '#value' => 'Add more',
@@ -88,8 +88,8 @@ public function buildForm(array $form, FormStateInterface $form_state) {
    */
   public function addMoreSubmitForm(array &$form, FormStateInterface $form_state) {
     // Rebuild, to test preservation of input values.
-    $form_state['storage']['add_more'] = TRUE;
-    $form_state['rebuild'] = TRUE;
+    $form_state->set('add_more', TRUE);
+    $form_state->setRebuild();
   }
 
   /**
diff --git a/core/modules/system/tests/modules/form_test/src/Form/FormTestRedirectForm.php b/core/modules/system/tests/modules/form_test/src/Form/FormTestRedirectForm.php
index c00dc1b..7fc352e 100644
--- a/core/modules/system/tests/modules/form_test/src/Form/FormTestRedirectForm.php
+++ b/core/modules/system/tests/modules/form_test/src/Form/FormTestRedirectForm.php
@@ -58,7 +58,7 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
       }
     }
     else {
-      $form_state['no_redirect'] = TRUE;
+      $form_state->disableRedirect();
     }
   }
 
diff --git a/core/modules/system/tests/modules/form_test/src/Form/FormTestStatePersistForm.php b/core/modules/system/tests/modules/form_test/src/Form/FormTestStatePersistForm.php
index f34f926..a4e570e 100644
--- a/core/modules/system/tests/modules/form_test/src/Form/FormTestStatePersistForm.php
+++ b/core/modules/system/tests/modules/form_test/src/Form/FormTestStatePersistForm.php
@@ -32,7 +32,7 @@ public function buildForm(array $form, FormStateInterface $form_state) {
       '#default_value' => 'DEFAULT',
       '#required' => TRUE,
     );
-    $form_state['value'] = 'State persisted.';
+    $form_state->set('value', 'State persisted.');
 
     $form['submit'] = array(
       '#type' => 'submit',
@@ -45,8 +45,8 @@ public function buildForm(array $form, FormStateInterface $form_state) {
    * {@inheritdoc}
    */
   public function submitForm(array &$form, FormStateInterface $form_state) {
-    drupal_set_message($form_state['value']);
-    $form_state['rebuild'] = TRUE;
+    drupal_set_message($form_state->get('value'));
+    $form_state->setRebuild();
   }
 
 }
diff --git a/core/modules/system/tests/modules/form_test/src/Form/FormTestStorageForm.php b/core/modules/system/tests/modules/form_test/src/Form/FormTestStorageForm.php
index fb23160..4f25aef 100644
--- a/core/modules/system/tests/modules/form_test/src/Form/FormTestStorageForm.php
+++ b/core/modules/system/tests/modules/form_test/src/Form/FormTestStorageForm.php
@@ -32,22 +32,24 @@ public function getFormId() {
    * {@inheritdoc}
    */
   public function buildForm(array $form, FormStateInterface $form_state) {
-    if ($form_state['rebuild']) {
+    if ($form_state->getRebuild()) {
       $form_state->setUserInput(array());
     }
     // Initialize
-    if (empty($form_state['storage'])) {
+    $storage = $form_state->getStorage();
+    if (empty($storage)) {
       $user_input = $form_state->getUserInput();
       if (empty($user_input)) {
         $_SESSION['constructions'] = 0;
       }
       // Put the initial thing into the storage
-      $form_state['storage'] = array(
-        'thing' => array(
+      $storage = [
+        'thing' => [
           'title' => 'none',
           'value' => '',
-        ),
-      );
+        ],
+      ];
+      $form_state->setStorage($storage);
     }
     // Count how often the form is constructed.
     $_SESSION['constructions']++;
@@ -56,13 +58,13 @@ public function buildForm(array $form, FormStateInterface $form_state) {
     $form['title'] = array(
       '#type' => 'textfield',
       '#title' => 'Title',
-      '#default_value' => $form_state['storage']['thing']['title'],
+      '#default_value' => $storage['thing']['title'],
       '#required' => TRUE,
     );
     $form['value'] = array(
       '#type' => 'textfield',
       '#title' => 'Value',
-      '#default_value' => $form_state['storage']['thing']['value'],
+      '#default_value' => $storage['thing']['value'],
       '#element_validate' => array('::elementValidateValueCached'),
     );
     $form['continue_button'] = array(
@@ -83,7 +85,7 @@ public function buildForm(array $form, FormStateInterface $form_state) {
     if (\Drupal::request()->get('cache')) {
       // Manually activate caching, so we can test that the storage keeps working
       // when it's enabled.
-      $form_state['cache'] = TRUE;
+      $form_state->setCached();
     }
 
     return $form;
@@ -100,7 +102,7 @@ public function elementValidateValueCached($element, FormStateInterface $form_st
     // elsewhere in the form. Form API should still update the cached form storage
     // though.
     if (\Drupal::request()->get('cache') && $form_state->getValue('value') == 'change_title') {
-      $form_state['storage']['thing']['changed'] = TRUE;
+      $form_state->set(['thing', 'changed'], TRUE);
     }
   }
 
@@ -108,9 +110,9 @@ public function elementValidateValueCached($element, FormStateInterface $form_st
    * {@inheritdoc}
    */
   public function continueSubmitForm(array &$form, FormStateInterface $form_state) {
-    $form_state['storage']['thing']['title'] = $form_state->getValue('title');
-    $form_state['storage']['thing']['value'] = $form_state->getValue('value');
-    $form_state['rebuild'] = TRUE;
+    $form_state->set(['thing', 'title'], $form_state->getValue('title'));
+    $form_state->set(['thing', 'value'], $form_state->getValue('value'));
+    $form_state->setRebuild();
   }
 
   /**
@@ -119,7 +121,7 @@ public function continueSubmitForm(array &$form, FormStateInterface $form_state)
   public function submitForm(array &$form, FormStateInterface $form_state) {
     drupal_set_message("Title: " . String::checkPlain($form_state->getValue('title')));
     drupal_set_message("Form constructions: " . $_SESSION['constructions']);
-    if (isset($form_state['storage']['thing']['changed'])) {
+    if ($form_state->has(['thing', 'changed'])) {
       drupal_set_message("The thing has been changed.");
     }
     $form_state->setRedirect('<front>');
diff --git a/core/modules/system/tests/modules/form_test/src/Form/FormTestValidateForm.php b/core/modules/system/tests/modules/form_test/src/Form/FormTestValidateForm.php
index d9293b4..7da8f5d 100644
--- a/core/modules/system/tests/modules/form_test/src/Form/FormTestValidateForm.php
+++ b/core/modules/system/tests/modules/form_test/src/Form/FormTestValidateForm.php
@@ -51,7 +51,7 @@ public function buildForm(array $form, FormStateInterface $form_state) {
 
     // To simplify this test, enable form caching and use form storage to
     // remember our alteration.
-    $form_state['cache'] = TRUE;
+    $form_state->setCached();
 
     return $form;
   }
diff --git a/core/modules/system/tests/modules/image_test/src/Plugin/ImageToolkit/TestToolkit.php b/core/modules/system/tests/modules/image_test/src/Plugin/ImageToolkit/TestToolkit.php
index 81688ab..f72eaaa 100644
--- a/core/modules/system/tests/modules/image_test/src/Plugin/ImageToolkit/TestToolkit.php
+++ b/core/modules/system/tests/modules/image_test/src/Plugin/ImageToolkit/TestToolkit.php
@@ -112,7 +112,7 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta
    * {@inheritdoc}
    */
   public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
-    if ($form_state['values']['test']['test_parameter'] == 0) {
+    if ($form_state->getValue(['test', 'test_parameter']) == 0) {
       $form_state->setErrorByName('test][test_parameter', $this->t('Test parameter should be different from 0.'));
     }
   }
diff --git a/core/modules/taxonomy/src/Form/OverviewTerms.php b/core/modules/taxonomy/src/Form/OverviewTerms.php
index f94d728..1d0c83d 100644
--- a/core/modules/taxonomy/src/Form/OverviewTerms.php
+++ b/core/modules/taxonomy/src/Form/OverviewTerms.php
@@ -71,7 +71,7 @@ public function buildForm(array $form, FormStateInterface $form_state, Vocabular
     // @todo Remove global variables when http://drupal.org/node/2044435 is in.
     global $pager_page_array, $pager_total, $pager_total_items;
 
-    $form_state['taxonomy']['vocabulary'] = $taxonomy_vocabulary;
+    $form_state->set(['taxonomy', 'vocabulary'], $taxonomy_vocabulary);
     $parent_fields = FALSE;
 
     $page = $this->getRequest()->query->get('page') ?: 0;
@@ -369,7 +369,7 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
     // Sort term order based on weight.
     uasort($form_state->getValue('terms'), array('Drupal\Component\Utility\SortArray', 'sortByWeightElement'));
 
-    $vocabulary = $form_state['taxonomy']['vocabulary'];
+    $vocabulary = $form_state->get(['taxonomy', 'vocabulary']);
     // Update the current hierarchy type as we go.
     $hierarchy = TAXONOMY_HIERARCHY_DISABLED;
 
@@ -452,7 +452,7 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
    */
   public function submitReset(array &$form, FormStateInterface $form_state) {
     /** @var $vocabulary \Drupal\taxonomy\VocabularyInterface */
-    $vocabulary = $form_state['taxonomy']['vocabulary'];
+    $vocabulary = $form_state->get(['taxonomy', 'vocabulary']);
     $form_state->setRedirectUrl($vocabulary->urlInfo('reset-form'));
   }
 
diff --git a/core/modules/taxonomy/src/Plugin/views/filter/TaxonomyIndexTid.php b/core/modules/taxonomy/src/Plugin/views/filter/TaxonomyIndexTid.php
index 5c3e50a..f001f43 100644
--- a/core/modules/taxonomy/src/Plugin/views/filter/TaxonomyIndexTid.php
+++ b/core/modules/taxonomy/src/Plugin/views/filter/TaxonomyIndexTid.php
@@ -161,7 +161,7 @@ protected function valueForm(&$form, FormStateInterface $form_state) {
 
       $default_value = (array) $this->value;
 
-      if (!empty($form_state['exposed'])) {
+      if ($exposed = $form_state->get('exposed')) {
         $identifier = $this->options['expose']['identifier'];
 
         if (!empty($this->options['expose']['reduce'])) {
@@ -201,13 +201,13 @@ protected function valueForm(&$form, FormStateInterface $form_state) {
       );
 
       $user_input = $form_state->getUserInput();
-      if (!empty($form_state['exposed']) && isset($identifier) && !isset($user_input[$identifier])) {
+      if ($exposed && isset($identifier) && !isset($user_input[$identifier])) {
         $user_input[$identifier] = $default_value;
         $form_state->setUserInput($user_input);
       }
     }
 
-    if (empty($form_state['exposed'])) {
+    if (!$exposed) {
       // Retain the helper option
       $this->helper->buildOptionsForm($form, $form_state);
     }
diff --git a/core/modules/taxonomy/src/TermForm.php b/core/modules/taxonomy/src/TermForm.php
index 4a82e67..2fa6f4b 100644
--- a/core/modules/taxonomy/src/TermForm.php
+++ b/core/modules/taxonomy/src/TermForm.php
@@ -25,8 +25,8 @@ public function form(array $form, FormStateInterface $form_state) {
     $vocabulary = $vocab_storage->load($term->bundle());
 
     $parent = array_keys(taxonomy_term_load_parents($term->id()));
-    $form_state['taxonomy']['parent'] = $parent;
-    $form_state['taxonomy']['vocabulary'] = $vocabulary;
+    $form_state->set(['taxonomy', 'parent'], $parent);
+    $form_state->set(['taxonomy', 'vocabulary'], $vocabulary);
 
     $language_configuration = $this->moduleHandler->moduleExists('language') ? language_get_default_configuration('taxonomy_term', $vocabulary->id()) : FALSE;
     $form['langcode'] = array(
@@ -146,7 +146,7 @@ public function save(array $form, FormStateInterface $form_state) {
     }
 
     $current_parent_count = count($form_state->getValue('parent'));
-    $previous_parent_count = count($form_state['taxonomy']['parent']);
+    $previous_parent_count = count($form_state->get(['taxonomy', 'parent']));
     // Root doesn't count if it's the only parent.
     if ($current_parent_count == 1 && $form_state->hasValue(array('parent', 0))) {
       $current_parent_count = 0;
@@ -155,18 +155,19 @@ public function save(array $form, FormStateInterface $form_state) {
 
     // If the number of parents has been reduced to one or none, do a check on the
     // parents of every term in the vocabulary value.
+    $vocabulary = $form_state->get(['taxonomy', 'vocabulary']);
     if ($current_parent_count < $previous_parent_count && $current_parent_count < 2) {
-      taxonomy_check_vocabulary_hierarchy($form_state['taxonomy']['vocabulary'], $form_state->getValues());
+      taxonomy_check_vocabulary_hierarchy($vocabulary, $form_state->getValues());
     }
     // If we've increased the number of parents and this is a single or flat
     // hierarchy, update the vocabulary immediately.
-    elseif ($current_parent_count > $previous_parent_count && $form_state['taxonomy']['vocabulary']->hierarchy != TAXONOMY_HIERARCHY_MULTIPLE) {
-      $form_state['taxonomy']['vocabulary']->hierarchy = $current_parent_count == 1 ? TAXONOMY_HIERARCHY_SINGLE : TAXONOMY_HIERARCHY_MULTIPLE;
-      $form_state['taxonomy']['vocabulary']->save();
+    elseif ($current_parent_count > $previous_parent_count && $vocabulary->hierarchy != TAXONOMY_HIERARCHY_MULTIPLE) {
+      $vocabulary->hierarchy = $current_parent_count == 1 ? TAXONOMY_HIERARCHY_SINGLE : TAXONOMY_HIERARCHY_MULTIPLE;
+      $vocabulary->save();
     }
 
     $form_state->setValue('tid', $term->id());
-    $form_state['tid'] = $term->id();
+    $form_state->set('tid', $term->id());
   }
 
 }
diff --git a/core/modules/taxonomy/src/VocabularyForm.php b/core/modules/taxonomy/src/VocabularyForm.php
index 5a70f8d..ef61676 100644
--- a/core/modules/taxonomy/src/VocabularyForm.php
+++ b/core/modules/taxonomy/src/VocabularyForm.php
@@ -91,7 +91,7 @@ public function form(array $form, FormStateInterface $form_state) {
    */
   protected function actions(array $form, FormStateInterface $form_state) {
     // If we are displaying the delete confirmation skip the regular actions.
-    if (empty($form_state['confirm_delete'])) {
+    if (!$form_state->get('confirm_delete')) {
       $actions = parent::actions($form, $form_state);
       // Add the language configuration submit handler. This is needed because
       // the submit button has custom submit handlers.
@@ -125,7 +125,7 @@ public function languageConfigurationSubmit(array &$form, FormStateInterface $fo
     // Since the machine name is not known yet, and it can be changed anytime,
     // we have to also update the bundle property for the default language
     // configuration in order to have the correct bundle value.
-    $form_state['language']['default_language']['bundle'] = $form_state->getValue('vid');
+    $form_state->set(['language', 'default_language', 'bundle'], $form_state->getValue('vid'));
   }
 
   /**
@@ -154,7 +154,7 @@ public function save(array $form, FormStateInterface $form_state) {
     }
 
     $form_state->setValue('vid', $vocabulary->id());
-    $form_state['vid'] = $vocabulary->id();
+    $form_state->set('vid', $vocabulary->id());
   }
 
 }
diff --git a/core/modules/update/src/UpdateSettingsForm.php b/core/modules/update/src/UpdateSettingsForm.php
index b1e0f32..fb872b6 100644
--- a/core/modules/update/src/UpdateSettingsForm.php
+++ b/core/modules/update/src/UpdateSettingsForm.php
@@ -72,7 +72,7 @@ public function buildForm(array $form, FormStateInterface $form_state) {
    * Implements \Drupal\Core\Form\FormInterface::validateForm().
    */
   public function validateForm(array &$form, FormStateInterface $form_state) {
-    $form_state['notify_emails'] = array();
+    $form_state->set('notify_emails', []);
     if (!$form_state->isValueEmpty('update_notify_emails')) {
       $valid = array();
       $invalid = array();
@@ -88,7 +88,7 @@ public function validateForm(array &$form, FormStateInterface $form_state) {
         }
       }
       if (empty($invalid)) {
-        $form_state['notify_emails'] = $valid;
+        $form_state->set('notify_emails', $valid);
       }
       elseif (count($invalid) == 1) {
         $form_state->setErrorByName('update_notify_emails', $this->t('%email is not a valid email address.', array('%email' => reset($invalid))));
@@ -115,7 +115,7 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
     $config
       ->set('check.disabled_extensions', $form_state->getValue('update_check_disabled'))
       ->set('check.interval_days', $form_state->getValue('update_check_frequency'))
-      ->set('notification.emails', $form_state['notify_emails'])
+      ->set('notification.emails', $form_state->get('notify_emails'))
       ->set('notification.threshold', $form_state->getValue('update_notification_threshold'))
       ->save();
 
diff --git a/core/modules/user/src/AccountForm.php b/core/modules/user/src/AccountForm.php
index 15df478..7e44651 100644
--- a/core/modules/user/src/AccountForm.php
+++ b/core/modules/user/src/AccountForm.php
@@ -156,7 +156,7 @@ public function form(array $form, FormStateInterface $form_state) {
           '#attributes' => array('autocomplete' => 'off'),
         );
 
-        $form_state['user'] = $account;
+        $form_state->set('user', $account);
         $form['#validate'][] = 'user_validate_current_pass';
       }
     }
diff --git a/core/modules/user/src/AccountSettingsForm.php b/core/modules/user/src/AccountSettingsForm.php
index d164f6e..9a85569 100644
--- a/core/modules/user/src/AccountSettingsForm.php
+++ b/core/modules/user/src/AccountSettingsForm.php
@@ -106,7 +106,7 @@ public function buildForm(array $form, FormStateInterface $form_state) {
         '#open' => TRUE,
         '#tree' => TRUE,
       );
-      $form_state['content_translation']['key'] = 'language';
+      $form_state->set(['content_translation', 'key'], 'language');
       $form['language'] += content_translation_enable_widget('user', 'user', $form, $form_state);
     }
 
diff --git a/core/modules/user/src/Form/UserLoginForm.php b/core/modules/user/src/Form/UserLoginForm.php
index 25511a8..3f702d1 100644
--- a/core/modules/user/src/Form/UserLoginForm.php
+++ b/core/modules/user/src/Form/UserLoginForm.php
@@ -116,7 +116,7 @@ public function buildForm(array $form, FormStateInterface $form_state) {
    * {@inheritdoc}
    */
   public function submitForm(array &$form, FormStateInterface $form_state) {
-    $account = $this->userStorage->load($form_state['uid']);
+    $account = $this->userStorage->load($form_state->get('uid'));
 
     // A destination was set, probably on an exception controller,
     if (!$this->getRequest()->request->has('destination')) {
@@ -145,7 +145,7 @@ public function validateName(array &$form, FormStateInterface $form_state) {
   /**
    * Checks supplied username/password against local users table.
    *
-   * If successful, $form_state['uid'] is set to the matching user ID.
+   * If successful, $form_state->get('uid') is set to the matching user ID.
    */
   public function validateAuthentication(array &$form, FormStateInterface $form_state) {
     $password = trim($form_state->getValue('pass'));
@@ -157,7 +157,7 @@ public function validateAuthentication(array &$form, FormStateInterface $form_st
       // in to many different user accounts.  We have a reasonably high limit
       // since there may be only one apparent IP for all users at an institution.
       if (!$this->flood->isAllowed('user.failed_login_ip', $flood_config->get('ip_limit'), $flood_config->get('ip_window'))) {
-        $form_state['flood_control_triggered'] = 'ip';
+        $form_state->set('flood_control_triggered', 'ip');
         return;
       }
       $accounts = $this->userStorage->loadByProperties(array('name' => $form_state->getValue('name'), 'status' => 1));
@@ -174,18 +174,18 @@ public function validateAuthentication(array &$form, FormStateInterface $form_st
           // could lock out all users with public user names.
           $identifier = $account->id() . '-' . $this->getRequest()->getClientIP();
         }
-        $form_state['flood_control_user_identifier'] = $identifier;
+        $form_state->set('flood_control_user_identifier', $identifier);
 
         // Don't allow login if the limit for this user has been reached.
         // Default is to allow 5 failed attempts every 6 hours.
         if (!$this->flood->isAllowed('user.failed_login_user', $flood_config->get('user_limit'), $flood_config->get('user_window'), $identifier)) {
-          $form_state['flood_control_triggered'] = 'user';
+          $form_state->set('flood_control_triggered', 'user');
           return;
         }
       }
       // We are not limited by flood control, so try to authenticate.
-      // Set $form_state['uid'] as a flag for self::validateFinal().
-      $form_state['uid'] = $this->userAuth->authenticate($form_state->getValue('name'), $password);
+      // Store uid in form state as a flag for self::validateFinal().
+      $form_state->set('uid', $this->userAuth->authenticate($form_state->getValue('name'), $password));
     }
   }
 
@@ -196,16 +196,16 @@ public function validateAuthentication(array &$form, FormStateInterface $form_st
    */
   public function validateFinal(array &$form, FormStateInterface $form_state) {
     $flood_config = $this->config('user.flood');
-    if (empty($form_state['uid'])) {
+    if (!$form_state->get('uid')) {
       // Always register an IP-based failed login event.
       $this->flood->register('user.failed_login_ip', $flood_config->get('ip_window'));
       // Register a per-user failed login event.
-      if (isset($form_state['flood_control_user_identifier'])) {
-        $this->flood->register('user.failed_login_user', $flood_config->get('user_window'), $form_state['flood_control_user_identifier']);
+      if ($flood_control_user_identifier = $form_state->get('flood_control_user_identifier')) {
+        $this->flood->register('user.failed_login_user', $flood_config->get('user_window'), $flood_control_user_identifier);
       }
 
-      if (isset($form_state['flood_control_triggered'])) {
-        if ($form_state['flood_control_triggered'] == 'user') {
+      if ($flood_control_triggered = $form_state->get('flood_control_triggered')) {
+        if ($flood_control_triggered == 'user') {
           $form_state->setErrorByName('name', format_plural($flood_config->get('user_limit'), 'Sorry, there has been more than one failed login attempt for this account. It is temporarily blocked. Try again later or <a href="@url">request a new password</a>.', 'Sorry, there have been more than @count failed login attempts for this account. It is temporarily blocked. Try again later or <a href="@url">request a new password</a>.', array('@url' => url('user/password'))));
         }
         else {
@@ -226,10 +226,10 @@ public function validateFinal(array &$form, FormStateInterface $form_state) {
         }
       }
     }
-    elseif (isset($form_state['flood_control_user_identifier'])) {
+    elseif ($flood_control_user_identifier = $form_state->get('flood_control_user_identifier')) {
       // Clear past failures for this user so as not to block a user who might
       // log in and out more than once in an hour.
-      $this->flood->clear('user.failed_login_user', $form_state['flood_control_user_identifier']);
+      $this->flood->clear('user.failed_login_user', $flood_control_user_identifier);
     }
   }
 
diff --git a/core/modules/user/src/Plugin/views/filter/Name.php b/core/modules/user/src/Plugin/views/filter/Name.php
index fb72e08..c32abe1 100644
--- a/core/modules/user/src/Plugin/views/filter/Name.php
+++ b/core/modules/user/src/Plugin/views/filter/Name.php
@@ -47,7 +47,7 @@ protected function valueForm(&$form, FormStateInterface $form_state) {
     );
 
     $user_input = $form_state->getUserInput();
-    if (!empty($form_state['exposed']) && !isset($user_input[$this->options['expose']['identifier']])) {
+    if ($form_state->get('exposed') && !isset($user_input[$this->options['expose']['identifier']])) {
       $user_input[$this->options['expose']['identifier']] = $default_value;
       $form_state->setUserInput($user_input);
     }
diff --git a/core/modules/user/src/RegisterForm.php b/core/modules/user/src/RegisterForm.php
index 09ddc3d..c8df76e 100644
--- a/core/modules/user/src/RegisterForm.php
+++ b/core/modules/user/src/RegisterForm.php
@@ -107,7 +107,7 @@ public function save(array $form, FormStateInterface $form_state) {
     // Assume save has gone through correctly.
     $account->save();
 
-    $form_state['user'] = $account;
+    $form_state->set('user', $account);
     $form_state->setValue('uid', $account->id());
 
     $this->logger('user')->notice('New user: %name %email.', array('%name' => $form_state->getValue('name'), '%email' => '<' . $form_state->getValue('mail') . '>', 'type' => l($this->t('Edit'), 'user/' . $account->id() . '/edit')));
diff --git a/core/modules/user/src/Tests/UserAccountFormFieldsTest.php b/core/modules/user/src/Tests/UserAccountFormFieldsTest.php
index 492218f..152a462 100644
--- a/core/modules/user/src/Tests/UserAccountFormFieldsTest.php
+++ b/core/modules/user/src/Tests/UserAccountFormFieldsTest.php
@@ -31,9 +31,8 @@ class UserAccountFormFieldsTest extends DrupalUnitTestBase {
   function testInstallConfigureForm() {
     require_once DRUPAL_ROOT . '/core/includes/install.core.inc';
     require_once DRUPAL_ROOT . '/core/includes/install.inc';
-    $install_state = install_state_defaults();
     $form_state = new FormState();
-    $form_state['build_info']['args'][] = &$install_state;
+    $form_state->addBuildInfo('args', [install_state_defaults()]);
     $form = $this->container->get('form_builder')
       ->buildForm('Drupal\Core\Installer\Form\SiteConfigureForm', $form_state);
 
diff --git a/core/modules/user/tests/modules/user_form_test/src/Form/TestCurrentPassword.php b/core/modules/user/tests/modules/user_form_test/src/Form/TestCurrentPassword.php
index b01fc79..3dd75e1 100644
--- a/core/modules/user/tests/modules/user_form_test/src/Form/TestCurrentPassword.php
+++ b/core/modules/user/tests/modules/user_form_test/src/Form/TestCurrentPassword.php
@@ -30,7 +30,7 @@ public function getFormId() {
    *   The user account.
    */
   public function buildForm(array $form, FormStateInterface $form_state, UserInterface $user = NULL) {
-    $form_state['user'] = $user ;
+    $form_state->set('user', $user);
     $form['user_form_test_field'] = array(
       '#type' => 'textfield',
       '#title' => $this->t('Test field'),
diff --git a/core/modules/user/user.module b/core/modules/user/user.module
index 75777bb..252403d 100644
--- a/core/modules/user/user.module
+++ b/core/modules/user/user.module
@@ -498,7 +498,7 @@ function _user_language_selector_langcode_value($element, $input, FormStateInter
  * @see AccountForm::form()
  */
 function user_validate_current_pass(&$form, FormStateInterface $form_state) {
-  $account = $form_state['user'];
+  $account = $form_state->get('user');
   foreach ($form_state->getValue('current_pass_required_values') as $key => $name) {
     // This validation only works for required textfields (like mail) or
     // form values like password_confirm that have their own validation
diff --git a/core/modules/views/includes/ajax.inc b/core/modules/views/includes/ajax.inc
index da57333..5347de6 100644
--- a/core/modules/views/includes/ajax.inc
+++ b/core/modules/views/includes/ajax.inc
@@ -5,6 +5,7 @@
  * Handles the server side AJAX interactions of Views.
  */
 
+use Drupal\Component\Utility\Html;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\views\Ajax\HighlightCommand;
 use Drupal\Core\Ajax\OpenModalDialogCommand;
@@ -17,23 +18,27 @@
  */
 function views_ajax_form_wrapper($form_class, FormStateInterface &$form_state) {
   // This won't override settings already in.
-  $form_state->setIfNotExists('rerender', FALSE);
-  $form_state->setIfNotExists('no_redirect', !empty($form_state['ajax']));
-  $form_state->setIfNotExists('no_cache', TRUE);
-  $form_state->setIfNotExists('build_info', array(
-    'args' => array(),
-  ));
+  if (!$form_state->has('rerender')) {
+    $form_state->set('rerender', FALSE);
+  }
+  $ajax = $form_state->get('ajax');
+  // Do not overwrite if the redirect has been disabled.
+  if (!$form_state->isRedirectDisabled()) {
+    $form_state->disableRedirect($ajax);
+  }
+  $form_state->disableCache();
 
   $form = \Drupal::formBuilder()->buildForm($form_class, $form_state);
   $output = drupal_render($form);
   drupal_process_attached($form);
 
   // These forms have the title built in, so set the title here:
-  if (empty($form_state['ajax']) && !empty($form_state['title'])) {
+  $title = $form_state->get('title') ?: '';
+  if (!$ajax && $title) {
     $form['#attached']['css'][] = drupal_get_path('module', 'views_ui') . '/css/views_ui.admin.css';
   }
 
-  if (!empty($form_state['ajax']) && (empty($form_state['executed']) || !empty($form_state['rerender']))) {
+  if ($ajax && !$form_state->isExecuted() || $form_state->get('rerender')) {
     // If the form didn't execute and we're using ajax, build up a
     // Ajax command list to execute.
     $response = new AjaxResponse();
@@ -45,7 +50,6 @@ function views_ajax_form_wrapper($form_class, FormStateInterface &$form_state) {
     }
     $display .= $output;
 
-    $title = empty($form_state['title']) ? '' : $form_state['title'];
     $options = array(
       'dialogClass' => 'views-ui-dialog',
       'width' => '50%',
@@ -53,12 +57,12 @@ function views_ajax_form_wrapper($form_class, FormStateInterface &$form_state) {
 
     $response->addCommand(new OpenModalDialogCommand($title, $display, $options));
 
-    if (!empty($form_state['#section'])) {
-      $response->addCommand(new HighlightCommand('.' . drupal_clean_css_identifier($form_state['#section'])));
+    if ($section = $form_state->get('#section')) {
+      $response->addCommand(new HighlightCommand('.' . Html::cleanCssIdentifier($section)));
     }
 
     return $response;
   }
 
-  return (!empty($form_state['title'])) ? array('#title' => $form_state['title'], '#markup' => $output) : $output;
+  return $title ? ['#title' => $title, '#markup' => $output] : $output;
 }
diff --git a/core/modules/views/src/Form/ViewsExposedForm.php b/core/modules/views/src/Form/ViewsExposedForm.php
index 618a737..26ca72b 100644
--- a/core/modules/views/src/Form/ViewsExposedForm.php
+++ b/core/modules/views/src/Form/ViewsExposedForm.php
@@ -63,15 +63,15 @@ public function buildForm(array $form, FormStateInterface $form_state) {
 
     // Make sure that we validate because this form might be submitted
     // multiple times per page.
-    $form_state['must_validate'] = TRUE;
+    $form_state->setValidationEnforced();
     /** @var \Drupal\views\ViewExecutable $view */
-    $view = $form_state['view'];
-    $display = &$form_state['display'];
+    $view = $form_state->get('view');
+    $display = &$form_state->get('display');
 
     $form_state->setUserInput($view->getExposedInput());
 
     // Let form plugins know this is for exposed widgets.
-    $form_state['exposed'] = TRUE;
+    $form_state->set('exposed', TRUE);
     // Check if the form was already created
     if ($cache = $this->exposedFormCache->getForm($view->storage->id(), $view->current_display)) {
       return $cache;
@@ -120,7 +120,7 @@ public function buildForm(array $form, FormStateInterface $form_state) {
     // $form['#attributes']['class'] = array('views-exposed-form');
 
     /** @var \Drupal\views\Plugin\views\exposed_form\ExposedFormPluginBase $exposed_form_plugin */
-    $exposed_form_plugin = $form_state['exposed_form_plugin'];
+    $exposed_form_plugin = $form_state->get('exposed_form_plugin');
     $exposed_form_plugin->exposedFormAlter($form, $form_state);
 
     // Save the form.
@@ -135,13 +135,13 @@ public function buildForm(array $form, FormStateInterface $form_state) {
   public function validateForm(array &$form, FormStateInterface $form_state) {
     foreach (array('field', 'filter') as $type) {
       /** @var \Drupal\views\Plugin\views\HandlerBase[] $handlers */
-      $handlers = &$form_state['view']->$type;
+      $handlers = &$form_state->get('view')->$type;
       foreach ($handlers as $key => $handler) {
         $handlers[$key]->validateExposed($form, $form_state);
       }
     }
     /** @var \Drupal\views\Plugin\views\exposed_form\ExposedFormPluginBase $exposed_form_plugin */
-    $exposed_form_plugin = $form_state['exposed_form_plugin'];
+    $exposed_form_plugin = $form_state->get('exposed_form_plugin');
     $exposed_form_plugin->exposedFormValidate($form, $form_state);
   }
 
@@ -151,22 +151,23 @@ public function validateForm(array &$form, FormStateInterface $form_state) {
   public function submitForm(array &$form, FormStateInterface $form_state) {
     foreach (array('field', 'filter') as $type) {
       /** @var \Drupal\views\Plugin\views\HandlerBase[] $handlers */
-      $handlers = &$form_state['view']->$type;
+      $handlers = &$form_state->get('view')->$type;
       foreach ($handlers as $key => $info) {
         $handlers[$key]->submitExposed($form, $form_state);
       }
     }
-    $form_state['view']->exposed_data = $form_state->getValues();
-    $form_state['view']->exposed_raw_input = array();
+    $view = $form_state->get('view');
+    $view->exposed_data = $form_state->getValues();
+    $view->exposed_raw_input = [];
 
     $exclude = array('submit', 'form_build_id', 'form_id', 'form_token', 'exposed_form_plugin', '', 'reset');
     /** @var \Drupal\views\Plugin\views\exposed_form\ExposedFormPluginBase $exposed_form_plugin */
-    $exposed_form_plugin = $form_state['exposed_form_plugin'];
+    $exposed_form_plugin = $form_state->get('exposed_form_plugin');
     $exposed_form_plugin->exposedFormSubmit($form, $form_state, $exclude);
 
     foreach ($form_state->getValues() as $key => $value) {
       if (!in_array($key, $exclude)) {
-        $form_state['view']->exposed_raw_input[$key] = $value;
+        $view->exposed_raw_input[$key] = $value;
       }
     }
   }
diff --git a/core/modules/views/src/Form/ViewsForm.php b/core/modules/views/src/Form/ViewsForm.php
index 2273f90..852d5fe 100644
--- a/core/modules/views/src/Form/ViewsForm.php
+++ b/core/modules/views/src/Form/ViewsForm.php
@@ -117,14 +117,17 @@ public function getFormID() {
    * {@inheritdoc}
    */
   public function buildForm(array $form, FormStateInterface $form_state, ViewExecutable $view = NULL, $output = NULL) {
-    $form_state['step'] = isset($form_state['step']) ? $form_state['step'] : 'views_form_views_form';
-    $form_state['step_controller']['views_form_views_form'] = 'Drupal\views\Form\ViewsFormMainForm';
+    if ($step = $form_state->get('step')) {
+      $step = 'views_form_views_form';
+      $form_state->set('step', $step);
+    }
+    $form_state->set(['step_controller', 'views_form_views_form'], 'Drupal\views\Form\ViewsFormMainForm');
 
     // Cache the built form to prevent it from being rebuilt prior to validation
     // and submission, which could lead to data being processed incorrectly,
     // because the views rows (and thus, the form elements as well) have changed
     // in the meantime.
-    $form_state['cache'] = TRUE;
+    $form_state->setCached();
 
     $form = array();
 
@@ -135,7 +138,7 @@ public function buildForm(array $form, FormStateInterface $form_state, ViewExecu
     // Tell the preprocessor whether it should hide the header, footer, pager...
     $form['show_view_elements'] = array(
       '#type' => 'value',
-      '#value' => ($form_state['step'] == 'views_form_views_form') ? TRUE : FALSE,
+      '#value' => ($step == 'views_form_views_form') ? TRUE : FALSE,
     );
 
     $form_object = $this->getFormObject($form_state);
@@ -171,7 +174,7 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
    */
   protected function getFormObject(FormStateInterface $form_state) {
     // If this is a class, instantiate it.
-    $form_step_class = isset($form_state['step_controller'][$form_state['step']]) ? $form_state['step_controller'][$form_state['step']] : 'Drupal\views\Form\ViewsFormMainForm';
+    $form_step_class = $form_state->get(['step_controller', $form_state->get('step')]) ?: 'Drupal\views\Form\ViewsFormMainForm';
     return $this->classResolver->getInstanceFromDefinition($form_step_class);
   }
 
diff --git a/core/modules/views/src/Form/ViewsFormMainForm.php b/core/modules/views/src/Form/ViewsFormMainForm.php
index 6c48b7e..0d21e8e 100644
--- a/core/modules/views/src/Form/ViewsFormMainForm.php
+++ b/core/modules/views/src/Form/ViewsFormMainForm.php
@@ -108,7 +108,7 @@ public function buildForm(array $form, FormStateInterface $form_state, ViewExecu
    * {@inheritdoc}
    */
   public function validateForm(array &$form, FormStateInterface $form_state) {
-    $view = $form_state['build_info']['args'][0];
+    $view = $form_state->getBuildInfo()['args'][0];
 
     // Call the validation method on every field handler that has it.
     foreach ($view->field as $field) {
@@ -131,7 +131,7 @@ public function validateForm(array &$form, FormStateInterface $form_state) {
    * {@inheritdoc}
    */
   public function submitForm(array &$form, FormStateInterface $form_state) {
-    $view = $form_state['build_info']['args'][0];
+    $view = $form_state->getBuildInfo()['args'][0];
 
     // Call the submit method on every field handler that has it.
     foreach ($view->field as $field) {
diff --git a/core/modules/views/src/Plugin/views/HandlerBase.php b/core/modules/views/src/Plugin/views/HandlerBase.php
index ce31f43..f2d234f 100644
--- a/core/modules/views/src/Plugin/views/HandlerBase.php
+++ b/core/modules/views/src/Plugin/views/HandlerBase.php
@@ -351,9 +351,9 @@ public function usesGroupBy() {
    * Provide a form for aggregation settings.
    */
   public function buildGroupByForm(&$form, FormStateInterface $form_state) {
-    $display_id = $form_state['display_id'];
-    $type = $form_state['type'];
-    $id = $form_state['id'];
+    $display_id = $form_state->get('display_id');
+    $type = $form_state->get('type');
+    $id = $form_state->get('id');
 
     $form['#section'] = $display_id . '-' . $type . '-' . $id;
 
@@ -377,7 +377,7 @@ public function buildGroupByForm(&$form, FormStateInterface $form_state) {
    * There is no need for this function to actually store the data.
    */
   public function submitGroupByForm(&$form, FormStateInterface $form_state) {
-    $form_state['handler']->options['group_type'] = $form_state->getValue(array('options', 'group_type'));
+    $form_state->get('handler')->options['group_type'] = $form_state->getValue(['options', 'group_type']);
   }
 
   /**
@@ -473,7 +473,7 @@ public function showExposeForm(&$form, FormStateInterface $form_state) {
     // have no data in POST so their defaults get wiped out. This prevents
     // these defaults from getting wiped out. This setting will only be TRUE
     // during a 2nd pass rerender.
-    if (!empty($form_state['force_expose_options'])) {
+    if ($form_state->get('force_expose_options')) {
       foreach (Element::children($form['expose']) as $id) {
         if (isset($form['expose'][$id]['#default_value']) && !isset($form['expose'][$id]['#value'])) {
           $form['expose'][$id]['#value'] = $form['expose'][$id]['#default_value'];
@@ -823,14 +823,18 @@ public function displayExposedForm($form, FormStateInterface $form_state) {
       $this->defaultExposeOptions();
     }
 
-    $form_state['view']->getExecutable()->setHandler($form_state['display_id'], $form_state['type'], $form_state['id'], $item);
+    $view = $form_state->get('view');
+    $display_id = $form_state->get('display_id');
+    $type = $form_state->get('type');
+    $id = $form_state->get('id');
+    $view->getExecutable()->setHandler($display_id, $type, $id, $item);
 
-    $form_state['view']->addFormToStack($form_state['form_key'], $form_state['display_id'], $form_state['type'], $form_state['id'], TRUE, TRUE);
+    $view->addFormToStack($form_state->get('form_key'), $display_id, $type, $id, TRUE, TRUE);
 
-    $form_state['view']->cacheSet();
-    $form_state['rerender'] = TRUE;
-    $form_state['rebuild'] = TRUE;
-    $form_state['force_expose_options'] = TRUE;
+    $view->cacheSet();
+    $form_state->set('rerender', TRUE);
+    $form_state->setRebuild();
+    $form_state->set('force_expose_options', TRUE);
   }
 
   /**
@@ -845,13 +849,14 @@ public function submitTemporaryForm($form, FormStateInterface $form_state) {
 
     // For footer/header $handler_type is area but $type is footer/header.
     // For all other handle types it's the same.
-    $handler_type = $type = $form_state['type'];
+    $handler_type = $type = $form_state->get('type');
     if (!empty($types[$type]['type'])) {
       $handler_type = $types[$type]['type'];
     }
 
     $override = NULL;
-    $executable = $form_state['view']->getExecutable();
+    $view = $form_state->get('view');
+    $executable = $view->getExecutable();
     if ($executable->display_handler->useGroupBy() && !empty($item['group_type'])) {
       if (empty($executable->query)) {
         $executable->initQuery();
@@ -876,19 +881,19 @@ public function submitTemporaryForm($form, FormStateInterface $form_state) {
     $handler->unpackOptions($handler->options, $options, NULL, FALSE);
 
     // Store the item back on the view.
-    $executable = $form_state['view']->getExecutable();
-    $executable->temporary_options[$type][$form_state['id']] = $handler->options;
+    $executable = $view->getExecutable();
+    $executable->temporary_options[$type][$form_state->get('id')] = $handler->options;
 
     // @todo Decide if \Drupal\views_ui\Form\Ajax\ViewsFormBase::getForm() is
     //   perhaps the better place to fix the issue.
     // \Drupal\views_ui\Form\Ajax\ViewsFormBase::getForm() drops the current
     // form from the stack, even if it's an #ajax. So add the item back to the top
     // of the stack.
-    $form_state['view']->addFormToStack($form_state['form_key'], $form_state['display_id'], $type, $item['id'], TRUE);
+    $view->addFormToStack($form_state->get('form_key'), $form_state->get('display_id'), $type, $item['id'], TRUE);
 
-    $form_state['rerender'] = TRUE;
-    $form_state['rebuild'] = TRUE;
+    $form_state->get('rerender', TRUE);
+    $form_state->setRebuild();
     // Write to cache
-    $form_state['view']->cacheSet();
+    $view->cacheSet();
   }
 }
diff --git a/core/modules/views/src/Plugin/views/area/AreaPluginBase.php b/core/modules/views/src/Plugin/views/area/AreaPluginBase.php
index 54a11bf..1269638 100644
--- a/core/modules/views/src/Plugin/views/area/AreaPluginBase.php
+++ b/core/modules/views/src/Plugin/views/area/AreaPluginBase.php
@@ -85,7 +85,7 @@ public function adminSummary() {
   public function buildOptionsForm(&$form, FormStateInterface $form_state) {
     parent::buildOptionsForm($form, $form_state);
 
-    if ($form_state['type'] != 'empty') {
+    if ($form_state->get('type') != 'empty') {
       $form['empty'] = array(
         '#type' => 'checkbox',
         '#title' => t('Display even if view has no result'),
diff --git a/core/modules/views/src/Plugin/views/display/Attachment.php b/core/modules/views/src/Plugin/views/display/Attachment.php
index 30870a0..663fb59 100644
--- a/core/modules/views/src/Plugin/views/display/Attachment.php
+++ b/core/modules/views/src/Plugin/views/display/Attachment.php
@@ -145,7 +145,7 @@ public function buildOptionsForm(&$form, FormStateInterface $form_state) {
     // It is very important to call the parent function here:
     parent::buildOptionsForm($form, $form_state);
 
-    switch ($form_state['section']) {
+    switch ($form_state->get('section')) {
       case 'inherit_arguments':
         $form['#title'] .= t('Inherit contextual filters');
         $form['inherit_arguments'] = array(
@@ -218,15 +218,16 @@ public function buildOptionsForm(&$form, FormStateInterface $form_state) {
   public function submitOptionsForm(&$form, FormStateInterface $form_state) {
     // It is very important to call the parent function here:
     parent::submitOptionsForm($form, $form_state);
-    switch ($form_state['section']) {
+    $section = $form_state->get('section');
+    switch ($section) {
       case 'displays':
-        $form_state->setValue($form_state['section'], array_filter($form_state->getValue($form_state['section'])));
+        $form_state->setValue($section, array_filter($form_state->getValue($section)));
       case 'inherit_arguments':
       case 'inherit_pager':
       case 'render_pager':
       case 'inherit_exposed_filters':
       case 'attachment_position':
-        $this->setOption($form_state['section'], $form_state->getValue($form_state['section']));
+        $this->setOption($section, $form_state->getValue($section));
         break;
     }
   }
diff --git a/core/modules/views/src/Plugin/views/display/Block.php b/core/modules/views/src/Plugin/views/display/Block.php
index c42a75e..544bd41 100644
--- a/core/modules/views/src/Plugin/views/display/Block.php
+++ b/core/modules/views/src/Plugin/views/display/Block.php
@@ -141,7 +141,7 @@ public function optionsSummary(&$categories, &$options) {
   public function buildOptionsForm(&$form, FormStateInterface $form_state) {
     parent::buildOptionsForm($form, $form_state);
 
-    switch ($form_state['section']) {
+    switch ($form_state->get('section')) {
       case 'block_description':
         $form['#title'] .= t('Block admin description');
         $form['block_description'] = array(
@@ -201,12 +201,13 @@ public function buildOptionsForm(&$form, FormStateInterface $form_state) {
    */
   public function submitOptionsForm(&$form, FormStateInterface $form_state) {
     parent::submitOptionsForm($form, $form_state);
-    switch ($form_state['section']) {
+    $section = $form_state->get('section');
+    switch ($section) {
       case 'block_description':
       case 'block_category':
       case 'allow':
       case 'block_hide_empty':
-        $this->setOption($form_state['section'], $form_state->getValue($form_state['section']));
+        $this->setOption($section, $form_state->getValue($section));
         break;
     }
   }
diff --git a/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php b/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php
index 7d3232f..bec2de4 100644
--- a/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php
+++ b/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php
@@ -1372,8 +1372,9 @@ public function optionsSummary(&$categories, &$options) {
    */
   public function buildOptionsForm(&$form, FormStateInterface $form_state) {
     parent::buildOptionsForm($form, $form_state);
-    if ($this->defaultableSections($form_state['section'])) {
-      views_ui_standard_display_dropdown($form, $form_state, $form_state['section']);
+    $section = $form_state->get('section');
+    if ($this->defaultableSections($section)) {
+      views_ui_standard_display_dropdown($form, $form_state, $section);
     }
     $form['#title'] = String::checkPlain($this->display['display_title']) . ': ';
 
@@ -1381,14 +1382,14 @@ public function buildOptionsForm(&$form, FormStateInterface $form_state) {
     // If it's the item we're looking at is pulling from the default display,
     // reflect that. Don't use is_defaulted since we want it to show up even
     // on the default display.
-    if (!empty($this->options['defaults'][$form_state['section']])) {
-      $form['#section'] = 'default-' . $form_state['section'];
+    if (!empty($this->options['defaults'][$section])) {
+      $form['#section'] = 'default-' . $section;
     }
     else {
-      $form['#section'] = $this->display['id'] . '-' . $form_state['section'];
+      $form['#section'] = $this->display['id'] . '-' . $section;
     }
 
-    switch ($form_state['section']) {
+    switch ($section) {
       case 'display_id':
         $form['#title'] .= t('The machine name of this display');
         $form['display_id'] = array(
@@ -1675,10 +1676,10 @@ public function buildOptionsForm(&$form, FormStateInterface $form_state) {
         }
         $plugin = $this->getPlugin(empty($style) ? 'row' : 'style', $name);
         if ($plugin) {
-          $form[$form_state['section']] = array(
+          $form[$section] = [
             '#tree' => TRUE,
-          );
-          $plugin->buildOptionsForm($form[$form_state['section']], $form_state);
+          ];
+          $plugin->buildOptionsForm($form[$section], $form_state);
         }
         break;
       case 'row':
@@ -1858,7 +1859,8 @@ public function buildOptionsForm(&$form, FormStateInterface $form_state) {
    * Validate the options form.
    */
   public function validateOptionsForm(&$form, FormStateInterface $form_state) {
-    switch ($form_state['section']) {
+    $section = $form_state->get('section');
+    switch ($section) {
       case 'display_title':
         if ($form_state->isValueEmpty('display_title')) {
           form_error($form['display_title'], $form_state, t('Display title may not be empty.'));
@@ -1892,11 +1894,11 @@ public function validateOptionsForm(&$form, FormStateInterface $form_state) {
 
     // Validate plugin options. Every section with "_options" in it, belongs to
     // a plugin type, like "style_options".
-    if (strpos($form_state['section'], '_options') !== FALSE) {
-      $plugin_type = str_replace('_options', '', $form_state['section']);
+    if (strpos($section, '_options') !== FALSE) {
+      $plugin_type = str_replace('_options', '', $section);
       // Load the plugin and let it handle the validation.
       if ($plugin = $this->getPlugin($plugin_type)) {
-        $plugin->validateOptionsForm($form[$form_state['section']], $form_state);
+        $plugin->validateOptionsForm($form[$section], $form_state);
       }
     }
 
@@ -1916,7 +1918,7 @@ public function submitOptionsForm(&$form, FormStateInterface $form_state) {
       $cache_plugin->cacheFlush();
     }
 
-    $section = $form_state['section'];
+    $section = $form_state->get('section');
     switch ($section) {
       case 'display_id':
         if ($form_state->hasValue('display_id')) {
@@ -1980,7 +1982,7 @@ public function submitOptionsForm(&$form, FormStateInterface $form_state) {
             );
             $this->setOption($plugin_type, $plugin_options);
             if ($plugin->usesOptions()) {
-              $form_state['view']->addFormToStack('display', $this->display['id'], $plugin_type . '_options');
+              $form_state->get('view')->addFormToStack('display', $this->display['id'], $plugin_type . '_options');
             }
           }
         }
@@ -1994,7 +1996,7 @@ public function submitOptionsForm(&$form, FormStateInterface $form_state) {
       case 'style_options':
         // Submit plugin options. Every section with "_options" in it, belongs to
         // a plugin type, like "style_options".
-        $plugin_type = str_replace('_options', '', $form_state['section']);
+        $plugin_type = str_replace('_options', '', $section);
         if ($plugin = $this->getPlugin($plugin_type)) {
           $plugin_options = $this->getOption($plugin_type);
           $plugin->submitOptionsForm($form[$plugin_type . '_options'], $form_state);
@@ -2013,7 +2015,7 @@ public function submitOptionsForm(&$form, FormStateInterface $form_state) {
    * If override/revert was clicked, perform the proper toggle.
    */
   public function optionsOverride($form, FormStateInterface $form_state) {
-    $this->setOverride($form_state['section']);
+    $this->setOverride($form_state->get('section'));
   }
 
   /**
diff --git a/core/modules/views/src/Plugin/views/display/Feed.php b/core/modules/views/src/Plugin/views/display/Feed.php
index bd84581..75e77c1 100644
--- a/core/modules/views/src/Plugin/views/display/Feed.php
+++ b/core/modules/views/src/Plugin/views/display/Feed.php
@@ -198,7 +198,7 @@ public function buildOptionsForm(&$form, FormStateInterface $form_state) {
     // It is very important to call the parent function here.
     parent::buildOptionsForm($form, $form_state);
 
-    switch ($form_state['section']) {
+    switch ($form_state->get('section')) {
       case 'title':
         $title = $form['title'];
         // A little juggling to move the 'title' field beyond our checkbox.
@@ -242,12 +242,13 @@ public function buildOptionsForm(&$form, FormStateInterface $form_state) {
    */
   public function submitOptionsForm(&$form, FormStateInterface $form_state) {
     parent::submitOptionsForm($form, $form_state);
-    switch ($form_state['section']) {
+    $section = $form_state->get('section');
+    switch ($section) {
       case 'title':
         $this->setOption('sitename_title', $form_state->getValue('sitename_title'));
         break;
       case 'displays':
-        $this->setOption($form_state['section'], $form_state->getValue($form_state['section']));
+        $this->setOption($section, $form_state->getValue($section));
         break;
     }
   }
diff --git a/core/modules/views/src/Plugin/views/display/Page.php b/core/modules/views/src/Plugin/views/display/Page.php
index 2b41335..8216654 100644
--- a/core/modules/views/src/Plugin/views/display/Page.php
+++ b/core/modules/views/src/Plugin/views/display/Page.php
@@ -153,7 +153,7 @@ public function optionsSummary(&$categories, &$options) {
   public function buildOptionsForm(&$form, FormStateInterface $form_state) {
     parent::buildOptionsForm($form, $form_state);
 
-    switch ($form_state['section']) {
+    switch ($form_state->get('section')) {
       case 'menu':
         $form['#title'] .= t('Menu item entry');
         $form['menu'] = array(
@@ -383,7 +383,7 @@ public function buildOptionsForm(&$form, FormStateInterface $form_state) {
   public function validateOptionsForm(&$form, FormStateInterface $form_state) {
     parent::validateOptionsForm($form, $form_state);
 
-    if ($form_state['section'] == 'menu') {
+    if ($form_state->get('section') == 'menu') {
       $path = $this->getOption('path');
       $menu_type = $form_state->getValue(array('menu', 'type'));
       if ($menu_type == 'normal' && strpos($path, '%') !== FALSE) {
@@ -410,14 +410,14 @@ public function validateOptionsForm(&$form, FormStateInterface $form_state) {
   public function submitOptionsForm(&$form, FormStateInterface $form_state) {
     parent::submitOptionsForm($form, $form_state);
 
-    switch ($form_state['section']) {
+    switch ($form_state->get('section')) {
       case 'menu':
         $menu = $form_state->getValue('menu');
         list($menu['menu_name'], $menu['parent']) = explode(':', $menu['parent'], 2);
         $this->setOption('menu', $menu);
         // send ajax form to options page if we use it.
         if ($form_state->getValue(array('menu', 'type')) == 'default tab') {
-          $form_state['view']->addFormToStack('display', $this->display['id'], 'tab_options');
+          $form_state->get('view')->addFormToStack('display', $this->display['id'], 'tab_options');
         }
         break;
       case 'tab_options':
diff --git a/core/modules/views/src/Plugin/views/display/PathPluginBase.php b/core/modules/views/src/Plugin/views/display/PathPluginBase.php
index 07a2816..4f6e220 100644
--- a/core/modules/views/src/Plugin/views/display/PathPluginBase.php
+++ b/core/modules/views/src/Plugin/views/display/PathPluginBase.php
@@ -380,7 +380,7 @@ public function optionsSummary(&$categories, &$options) {
   public function buildOptionsForm(&$form, FormStateInterface $form_state) {
     parent::buildOptionsForm($form, $form_state);
 
-    switch ($form_state['section']) {
+    switch ($form_state->get('section')) {
       case 'path':
         $form['#title'] .= t('The menu path or URL of this view');
         $form['path'] = array(
@@ -404,7 +404,7 @@ public function buildOptionsForm(&$form, FormStateInterface $form_state) {
   public function validateOptionsForm(&$form, FormStateInterface $form_state) {
     parent::validateOptionsForm($form, $form_state);
 
-    if ($form_state['section'] == 'path') {
+    if ($form_state->get('section') == 'path') {
       $errors = $this->validatePath($form_state->getValue('path'));
       foreach ($errors as $error) {
         $form_state->setError($form['path'], $error);
@@ -421,7 +421,7 @@ public function validateOptionsForm(&$form, FormStateInterface $form_state) {
   public function submitOptionsForm(&$form, FormStateInterface $form_state) {
     parent::submitOptionsForm($form, $form_state);
 
-    if ($form_state['section'] == 'path') {
+    if ($form_state->get('section') == 'path') {
       $this->setOption('path', $form_state->getValue('path'));
     }
   }
diff --git a/core/modules/views/src/Plugin/views/exposed_form/ExposedFormPluginBase.php b/core/modules/views/src/Plugin/views/exposed_form/ExposedFormPluginBase.php
index 211a388..3c30791 100644
--- a/core/modules/views/src/Plugin/views/exposed_form/ExposedFormPluginBase.php
+++ b/core/modules/views/src/Plugin/views/exposed_form/ExposedFormPluginBase.php
@@ -129,27 +129,26 @@ public function buildOptionsForm(&$form, FormStateInterface $form_state) {
    */
   public function renderExposedForm($block = FALSE) {
     // Deal with any exposed filters we may have, before building.
-    $form_state = new FormState(array(
-      'view' => &$this->view,
-      'display' => &$this->view->display_handler->display,
-      'method' => 'get',
-      'rerender' => TRUE,
-      'no_redirect' => TRUE,
-      'always_process' => TRUE,
-    ));
+    $form_state = (new FormState())
+      ->set('view', $this->view)
+      ->set('display', $this->view->display_handler->display)
+      ->set('rerender', TRUE)
+      ->setMethod('get')
+      ->setAlwaysProcess()
+      ->disableRedirect();
 
     // Some types of displays (eg. attachments) may wish to use the exposed
     // filters of their parent displays instead of showing an additional
     // exposed filter form for the attachment as well as that for the parent.
     if (!$this->view->display_handler->displaysExposed() || (!$block && $this->view->display_handler->getOption('exposed_block'))) {
-      unset($form_state['rerender']);
+      $form_state->set('rerender', NULL);
     }
 
     if (!empty($this->ajax)) {
-      $form_state['ajax'] = TRUE;
+      $form_state->set('ajax', TRUE);
     }
 
-    $form_state['exposed_form_plugin'] = $this;
+    $form_state->set('exposed_form_plugin', $this);
     $form = \Drupal::formBuilder()->buildForm('\Drupal\views\Form\ViewsExposedForm', $form_state);
 
     if (!$this->view->display_handler->displaysExposed() || (!$block && $this->view->display_handler->getOption('exposed_block'))) {
@@ -274,13 +273,13 @@ public function exposedFormAlter(&$form, FormStateInterface $form_state) {
     $pager = $this->view->display_handler->getPlugin('pager');
     if ($pager) {
       $pager->exposedFormAlter($form, $form_state);
-      $form_state['pager_plugin'] = $pager;
+      $form_state->set('pager_plugin', $pager);
     }
   }
 
   public function exposedFormValidate(&$form, FormStateInterface $form_state) {
-    if (isset($form_state['pager_plugin'])) {
-      $form_state['pager_plugin']->exposedFormValidate($form, $form_state);
+    if ($pager_plugin = $form_state->get('pager_plugin')) {
+      $pager_plugin->exposedFormValidate($form, $form_state);
     }
   }
 
@@ -299,8 +298,8 @@ public function exposedFormSubmit(&$form, FormStateInterface $form_state, &$excl
     if (!$form_state->isValueEmpty('op') && $form_state->getValue('op') == $this->options['reset_button_label']) {
       $this->resetForm($form, $form_state);
     }
-    if (isset($form_state['pager_plugin'])) {
-      $form_state['pager_plugin']->exposedFormSubmit($form, $form_state, $exclude);
+    if ($pager_plugin = $form_state->get('pager_plugin')) {
+      $pager_plugin->exposedFormSubmit($form, $form_state, $exclude);
       $exclude[] = 'pager_plugin';
     }
   }
@@ -320,14 +319,12 @@ public function resetForm(&$form, FormStateInterface $form_state) {
 
     // Set the form to allow redirect.
     if (empty($this->view->live_preview)) {
-      $form_state['no_redirect'] = FALSE;
+      $form_state->disableRedirect(FALSE);
     }
     else {
-      $form_state['rebuild'] = TRUE;
+      $form_state->setRebuild();
       $this->view->exposed_data = array();
     }
-
-    $form_state['redirect'] = current_path();
   }
 
 }
diff --git a/core/modules/views/src/Plugin/views/filter/BooleanOperator.php b/core/modules/views/src/Plugin/views/filter/BooleanOperator.php
index 7a75a11..7a87faa 100644
--- a/core/modules/views/src/Plugin/views/filter/BooleanOperator.php
+++ b/core/modules/views/src/Plugin/views/filter/BooleanOperator.php
@@ -137,7 +137,7 @@ protected function valueForm(&$form, FormStateInterface $form_state) {
       // Initialize the array of possible values for this filter.
       $this->getValueOptions();
     }
-    if (!empty($form_state['exposed'])) {
+    if ($exposed = $form_state->get('exposed')) {
       // Exposed filter: use a select box to save space.
       $filter_form_type = 'select';
     }
@@ -154,12 +154,12 @@ protected function valueForm(&$form, FormStateInterface $form_state) {
     if (!empty($this->options['exposed'])) {
       $identifier = $this->options['expose']['identifier'];
       $user_input = $form_state->getUserInput();
-      if (!empty($form_state['exposed']) && !isset($user_input[$identifier])) {
+      if ($exposed && !isset($user_input[$identifier])) {
         $user_input[$identifier] = $this->value;
         $form_state->setUserInput($user_input);
       }
       // If we're configuring an exposed filter, add an - Any - option.
-      if (empty($form_state['exposed']) || empty($this->options['expose']['required'])) {
+      if (!$exposed || empty($this->options['expose']['required'])) {
         $form['value']['#options'] = array('All' => t('- Any -')) + $form['value']['#options'];
       }
     }
diff --git a/core/modules/views/src/Plugin/views/filter/Date.php b/core/modules/views/src/Plugin/views/filter/Date.php
index c0e1b9a..aca1508 100644
--- a/core/modules/views/src/Plugin/views/filter/Date.php
+++ b/core/modules/views/src/Plugin/views/filter/Date.php
@@ -31,7 +31,7 @@ protected function defineOptions() {
    * Add a type selector to the value form
    */
   protected function valueForm(&$form, FormStateInterface $form_state) {
-    if (empty($form_state['exposed'])) {
+    if (!$form_state->get('exposed')) {
       $form['value']['type'] = array(
         '#type' => 'radios',
         '#title' => t('Value type'),
diff --git a/core/modules/views/src/Plugin/views/filter/Equality.php b/core/modules/views/src/Plugin/views/filter/Equality.php
index 7e30335..9d25e0b 100644
--- a/core/modules/views/src/Plugin/views/filter/Equality.php
+++ b/core/modules/views/src/Plugin/views/filter/Equality.php
@@ -42,7 +42,7 @@ protected function valueForm(&$form, FormStateInterface $form_state) {
       '#default_value' => $this->value,
     );
 
-    if (!empty($form_state['exposed'])) {
+    if ($exposed = $form_state->get('exposed')) {
       $identifier = $this->options['expose']['identifier'];
       $user_input = $form_state->getUserInput();
       if (!isset($user_input[$identifier])) {
diff --git a/core/modules/views/src/Plugin/views/filter/FilterPluginBase.php b/core/modules/views/src/Plugin/views/filter/FilterPluginBase.php
index c7ff6bb..2df07ed 100644
--- a/core/modules/views/src/Plugin/views/filter/FilterPluginBase.php
+++ b/core/modules/views/src/Plugin/views/filter/FilterPluginBase.php
@@ -363,7 +363,7 @@ public function showBuildGroupForm(&$form, FormStateInterface $form_state) {
     // have no data in POST so their defaults get wiped out. This prevents
     // these defaults from getting wiped out. This setting will only be TRUE
     // during a 2nd pass rerender.
-    if (!empty($form_state['force_build_group_options'])) {
+    if ($form_state->get('force_build_group_options')) {
       foreach (Element::children($form['group_info']) as $id) {
         if (isset($form['group_info'][$id]['#default_value']) && !isset($form['group_info'][$id]['#value'])) {
           $form['group_info'][$id]['#value'] = $form['group_info'][$id]['#default_value'];
@@ -437,14 +437,18 @@ public function buildGroupForm($form, FormStateInterface $form_state) {
       $this->buildGroupOptions();
     }
 
-    $form_state['view']->getExecutable()->setHandler($form_state['display_id'], $form_state['type'], $form_state['id'], $item);
+    $view = $form_state->get('view');
+    $display_id = $form_state->get('display_id');
+    $type = $form_state->get('type');
+    $id = $form_state->get('id');
+    $view->getExecutable()->setHandler($display_id, $type, $id, $item);
 
-    $form_state['view']->addFormToStack($form_state['form_key'], $form_state['display_id'], $form_state['type'], $form_state['id'], TRUE, TRUE);
+    $view->addFormToStack($form_state->get('form_key'), $display_id, $type, $id, TRUE, TRUE);
 
-    $form_state['view']->cacheSet();
-    $form_state['rerender'] = TRUE;
-    $form_state['rebuild'] = TRUE;
-    $form_state['force_build_group_options'] = TRUE;
+    $view->cacheSet();
+    $form_state->set('rerender', TRUE);
+    $form_state->setRebuild();
+    $form_state->get('force_build_group_options', TRUE);
   }
 
   /**
@@ -622,7 +626,7 @@ public function validateExposeForm($form, FormStateInterface $form_state) {
       form_error($form['expose']['identifier'], $form_state, t('This identifier is not allowed.'));
     }
 
-    if (!$this->view->display_handler->isIdentifierUnique($form_state['id'], $identifier)) {
+    if (!$this->view->display_handler->isIdentifierUnique($form_state->get('id'), $identifier)) {
       form_error($form['expose']['identifier'], $form_state, t('This identifier is used by another handler.'));
     }
   }
@@ -641,7 +645,7 @@ protected function buildGroupValidate($form, FormStateInterface $form_state) {
         form_error($form['group_info']['identifier'], $form_state, t('This identifier is not allowed.'));
       }
 
-      if (!$this->view->display_handler->isIdentifierUnique($form_state['id'], $identifier)) {
+      if (!$this->view->display_handler->isIdentifierUnique($form_state->get('id'), $identifier)) {
         form_error($form['group_info']['identifier'], $form_state, t('This identifier is used by another handler.'));
       }
     }
@@ -1090,12 +1094,14 @@ protected function buildExposedFiltersGroupForm(&$form, FormStateInterface $form
       'hidden' => TRUE,
       'limit' => 0,
     );
-    if (!empty($form_state['js settings']) && is_array($js)) {
-      $form_state['js settings'] = array_merge($form_state['js settings'], $js);
+    $js_settings = $form_state->get('js settings');
+    if ($js_settings && is_array($js)) {
+      $js_settings = array_merge($js_settings, $js);
     }
     else {
-      $form_state['js settings'] = $js;
+      $js_settings = $js;
     }
+    $form_state->set('js settings', $js_settings);
   }
 
   /**
@@ -1107,12 +1113,16 @@ public function addGroupForm($form, FormStateInterface $form_state) {
     // Add a new row.
     $item['group_info']['group_items'][] = array();
 
-    $form_state['view']->getExecutable()->setHandler($form_state['display_id'], $form_state['type'], $form_state['id'], $item);
+    $view = $form_state->get('view');
+    $display_id = $form_state->get('display_id');
+    $type = $form_state->get('type');
+    $id = $form_state->get('id');
+    $view->getExecutable()->setHandler($display_id, $type, $id, $item);
 
-    $form_state['view']->cacheSet();
-    $form_state['rerender'] = TRUE;
-    $form_state['rebuild'] = TRUE;
-    $form_state['force_build_group_options'] = TRUE;
+    $view->cacheSet();
+    $form_state->set('rerender', TRUE);
+    $form_state->setRebuild();
+    $form_state->get('force_build_group_options', TRUE);
   }
 
 
diff --git a/core/modules/views/src/Plugin/views/filter/InOperator.php b/core/modules/views/src/Plugin/views/filter/InOperator.php
index 623b44f..4d442b8 100644
--- a/core/modules/views/src/Plugin/views/filter/InOperator.php
+++ b/core/modules/views/src/Plugin/views/filter/InOperator.php
@@ -170,7 +170,8 @@ protected function valueForm(&$form, FormStateInterface $form_state) {
     $form['value'] = array();
     $options = array();
 
-    if (empty($form_state['exposed'])) {
+    $exposed = $form_state->get('exposed');
+    if (!$exposed) {
       // Add a select all option to the value form.
       $options = array('all' => t('Select all'));
     }
@@ -183,7 +184,7 @@ protected function valueForm(&$form, FormStateInterface $form_state) {
     if (!empty($form['operator'])) {
       $source = ':input[name="options[operator]"]';
     }
-    if (!empty($form_state['exposed'])) {
+    if ($exposed) {
       $identifier = $this->options['expose']['identifier'];
 
       if (empty($this->options['expose']['use_operator']) || empty($this->options['expose']['operator_id'])) {
@@ -228,13 +229,13 @@ protected function valueForm(&$form, FormStateInterface $form_state) {
         '#size' => count($options) > 8 ? 8 : count($options),
       );
       $user_input = $form_state->getUserInput();
-      if (!empty($form_state['exposed']) && !isset($user_input[$identifier])) {
+      if ($exposed && !isset($user_input[$identifier])) {
         $user_input[$identifier] = $default_value;
         $form_state->setUserInput($user_input);
       }
 
       if ($which == 'all') {
-        if (empty($form_state['exposed']) && (in_array($this->valueFormType, array('checkbox', 'checkboxes', 'radios', 'select')))) {
+        if (!$exposed && (in_array($this->valueFormType, ['checkbox', 'checkboxes', 'radios', 'select']))) {
           $form['value']['#prefix'] = '<div id="edit-options-value-wrapper">';
           $form['value']['#suffix'] = '</div>';
         }
diff --git a/core/modules/views/src/Plugin/views/filter/ManyToOne.php b/core/modules/views/src/Plugin/views/filter/ManyToOne.php
index cc8b448..7f1ca40 100644
--- a/core/modules/views/src/Plugin/views/filter/ManyToOne.php
+++ b/core/modules/views/src/Plugin/views/filter/ManyToOne.php
@@ -111,7 +111,7 @@ function operators() {
   protected function valueForm(&$form, FormStateInterface $form_state) {
     parent::valueForm($form, $form_state);
 
-    if (empty($form_state['exposed'])) {
+    if ($form_state->get('exposed')) {
       $this->helper->buildOptionsForm($form, $form_state);
     }
   }
diff --git a/core/modules/views/src/Plugin/views/filter/Numeric.php b/core/modules/views/src/Plugin/views/filter/Numeric.php
index 9837998..5579158 100644
--- a/core/modules/views/src/Plugin/views/filter/Numeric.php
+++ b/core/modules/views/src/Plugin/views/filter/Numeric.php
@@ -152,7 +152,7 @@ protected function valueForm(&$form, FormStateInterface $form_state) {
       $source = ':input[name="options[operator]"]';
     }
 
-    if (!empty($form_state['exposed'])) {
+    if ($exposed = $form_state->get('exposed')) {
       $identifier = $this->options['expose']['identifier'];
 
       if (empty($this->options['expose']['use_operator']) || empty($this->options['expose']['operator_id'])) {
@@ -168,7 +168,7 @@ protected function valueForm(&$form, FormStateInterface $form_state) {
     if ($which == 'all') {
       $form['value']['value'] = array(
         '#type' => 'textfield',
-        '#title' => empty($form_state['exposed']) ? t('Value') : '',
+        '#title' => !$exposed ? t('Value') : '',
         '#size' => 30,
         '#default_value' => $this->value['value'],
       );
@@ -178,7 +178,7 @@ protected function valueForm(&$form, FormStateInterface $form_state) {
           $source => array('value' => $operator),
         );
       }
-      if (!empty($form_state['exposed']) && !isset($user_input[$identifier]['value'])) {
+      if ($exposed && !isset($user_input[$identifier]['value'])) {
         $user_input[$identifier]['value'] = $this->value['value'];
         $form_state->setUserInput($user_input);
       }
@@ -188,11 +188,11 @@ protected function valueForm(&$form, FormStateInterface $form_state) {
       // the operator is locked.
       $form['value'] = array(
         '#type' => 'textfield',
-        '#title' => empty($form_state['exposed']) ? t('Value') : '',
+        '#title' => !$exposed ? t('Value') : '',
         '#size' => 30,
         '#default_value' => $this->value['value'],
       );
-      if (!empty($form_state['exposed']) && !isset($user_input[$identifier])) {
+      if ($exposed && !isset($user_input[$identifier])) {
         $user_input[$identifier] = $this->value['value'];
         $form_state->setUserInput($user_input);
       }
@@ -201,13 +201,13 @@ protected function valueForm(&$form, FormStateInterface $form_state) {
     if ($which == 'all' || $which == 'minmax') {
       $form['value']['min'] = array(
         '#type' => 'textfield',
-        '#title' => empty($form_state['exposed']) ? t('Min') : '',
+        '#title' => !$exposed ? t('Min') : '',
         '#size' => 30,
         '#default_value' => $this->value['min'],
       );
       $form['value']['max'] = array(
         '#type' => 'textfield',
-        '#title' => empty($form_state['exposed']) ? t('And max') : t('And'),
+        '#title' => !$exposed ? t('And max') : t('And'),
         '#size' => 30,
         '#default_value' => $this->value['max'],
       );
@@ -222,10 +222,10 @@ protected function valueForm(&$form, FormStateInterface $form_state) {
         $form['value']['min'] += $states;
         $form['value']['max'] += $states;
       }
-      if (!empty($form_state['exposed']) && !isset($user_input[$identifier]['min'])) {
+      if ($exposed && !isset($user_input[$identifier]['min'])) {
         $user_input[$identifier]['min'] = $this->value['min'];
       }
-      if (!empty($form_state['exposed']) && !isset($user_input[$identifier]['max'])) {
+      if ($exposed && !isset($user_input[$identifier]['max'])) {
         $user_input[$identifier]['max'] = $this->value['max'];
       }
 
diff --git a/core/modules/views/src/Plugin/views/filter/String.php b/core/modules/views/src/Plugin/views/filter/String.php
index 952d58d..a93c907 100644
--- a/core/modules/views/src/Plugin/views/filter/String.php
+++ b/core/modules/views/src/Plugin/views/filter/String.php
@@ -193,7 +193,7 @@ protected function valueForm(&$form, FormStateInterface $form_state) {
     if (!empty($form['operator'])) {
       $source = ':input[name="options[operator]"]';
     }
-    if (!empty($form_state['exposed'])) {
+    if ($exposed = $form_state->get('exposed')) {
       $identifier = $this->options['expose']['identifier'];
 
       if (empty($this->options['expose']['use_operator']) || empty($this->options['expose']['operator_id'])) {
@@ -213,7 +213,7 @@ protected function valueForm(&$form, FormStateInterface $form_state) {
         '#default_value' => $this->value,
       );
       $user_input = $form_state->getUserInput();
-      if (!empty($form_state['exposed']) && !isset($user_input[$identifier])) {
+      if ($exposed && !isset($user_input[$identifier])) {
         $user_input[$identifier] = $this->value;
         $form_state->setUserInput($user_input);
       }
diff --git a/core/modules/views/src/Plugin/views/row/RowPluginBase.php b/core/modules/views/src/Plugin/views/row/RowPluginBase.php
index 905f04e..459a768 100644
--- a/core/modules/views/src/Plugin/views/row/RowPluginBase.php
+++ b/core/modules/views/src/Plugin/views/row/RowPluginBase.php
@@ -82,7 +82,7 @@ protected function defineOptions() {
   public function buildOptionsForm(&$form, FormStateInterface $form_state) {
     parent::buildOptionsForm($form, $form_state);
     if (isset($this->base_table)) {
-      $executable = $form_state['view']->getExecutable();
+      $executable = $form_state->get('view')->getExecutable();
 
       // A whole bunch of code to figure out what relationships are valid for
       // this item.
diff --git a/core/modules/views/src/Plugin/views/wizard/WizardPluginBase.php b/core/modules/views/src/Plugin/views/wizard/WizardPluginBase.php
index bb63902..a989be7 100644
--- a/core/modules/views/src/Plugin/views/wizard/WizardPluginBase.php
+++ b/core/modules/views/src/Plugin/views/wizard/WizardPluginBase.php
@@ -741,7 +741,7 @@ protected function buildDisplayOptions($form, FormStateInterface $form_state) {
     }
 
     // Display: REST export.
-    if (!empty($form_state['values']['rest_export']['create'])) {
+    if (!$form_state->isValueEmpty(['rest_export', 'create'])) {
       $display_options['rest_export'] = $this->restExportDisplayOptions($form, $form_state);
     }
 
@@ -1117,7 +1117,7 @@ protected function blockDisplayOptions(array $form, FormStateInterface $form_sta
    */
   protected function restExportDisplayOptions(array $form, FormStateInterface $form_state) {
     $display_options = array();
-    $display_options['path'] = $form_state['values']['rest_export']['path'];
+    $display_options['path'] = $form_state->getValue(['rest_export', 'path']);
     $display_options['style'] = array('type' => 'serializer');
 
     return $display_options;
diff --git a/core/modules/views/src/Tests/Handler/AreaEntityTest.php b/core/modules/views/src/Tests/Handler/AreaEntityTest.php
index 9082352..af44b44 100644
--- a/core/modules/views/src/Tests/Handler/AreaEntityTest.php
+++ b/core/modules/views/src/Tests/Handler/AreaEntityTest.php
@@ -122,8 +122,8 @@ public function testEntityArea() {
 
     // Test the available view mode options.
     $form = array();
-    $form_state = new FormState();
-    $form_state['type'] = 'header';
+    $form_state = (new FormState())
+      ->set('type', 'header');
     $view->display_handler->getHandler('header', 'entity_entity_test')->buildOptionsForm($form, $form_state);
     $this->assertTrue(isset($form['view_mode']['#options']['test']), 'Ensure that the test view mode is available.');
     $this->assertTrue(isset($form['view_mode']['#options']['default']), 'Ensure that the default view mode is available.');
diff --git a/core/modules/views/src/Tests/Plugin/RowEntityTest.php b/core/modules/views/src/Tests/Plugin/RowEntityTest.php
index 649ba5f..3024b77 100644
--- a/core/modules/views/src/Tests/Plugin/RowEntityTest.php
+++ b/core/modules/views/src/Tests/Plugin/RowEntityTest.php
@@ -62,7 +62,7 @@ public function testEntityRow() {
     // Tests the available view mode options.
     $form = array();
     $form_state = new FormState();
-    $form_state['view'] = $view->storage;
+    $form_state->set('view', $view->storage);
     $view->rowPlugin->buildOptionsForm($form, $form_state);
 
     $this->assertTrue(isset($form['view_mode']['#options']['default']), 'Ensure that the default view mode is available');
diff --git a/core/modules/views/src/Tests/Wizard/WizardPluginBaseUnitTest.php b/core/modules/views/src/Tests/Wizard/WizardPluginBaseUnitTest.php
index 72a8823..07d0783 100644
--- a/core/modules/views/src/Tests/Wizard/WizardPluginBaseUnitTest.php
+++ b/core/modules/views/src/Tests/Wizard/WizardPluginBaseUnitTest.php
@@ -63,12 +63,12 @@ public function testCreateView() {
     ));
     language_save($language);
 
-    $form_state->set('values', array(
+    $form_state->setValues([
       'id' => $random_id,
       'label' => $random_label,
       'description' => $random_description,
       'base_table' => 'views_test_data',
-    ));
+    ]);
 
     $this->wizard->validateView($form, $form_state);
     $view = $this->wizard->createView($form, $form_state);
diff --git a/core/modules/views/tests/modules/views_test_data/src/Plugin/views/display/DisplayTest.php b/core/modules/views/tests/modules/views_test_data/src/Plugin/views/display/DisplayTest.php
index b14d409..b995858 100644
--- a/core/modules/views/tests/modules/views_test_data/src/Plugin/views/display/DisplayTest.php
+++ b/core/modules/views/tests/modules/views_test_data/src/Plugin/views/display/DisplayTest.php
@@ -78,7 +78,7 @@ public function optionsSummary(&$categories, &$options) {
   public function buildOptionsForm(&$form, FormStateInterface $form_state) {
     parent::buildOptionsForm($form, $form_state);
 
-    switch ($form_state['section']) {
+    switch ($form_state->get('section')) {
       case 'test_option':
         $form['#title'] .= t('Test option');
         $form['test_option'] = array(
@@ -97,7 +97,7 @@ public function buildOptionsForm(&$form, FormStateInterface $form_state) {
   public function validateOptionsForm(&$form, FormStateInterface $form_state) {
     parent::validateOptionsForm($form, $form_state);
     \Drupal::logger('views')->notice($form_state->getValue('test_option'));
-    switch ($form_state['section']) {
+    switch ($form_state->get('section')) {
       case 'test_option':
         if (!trim($form_state->getValue('test_option'))) {
           form_error($form['test_option'], $form_state, t('You cannot have an empty option.'));
@@ -111,7 +111,7 @@ public function validateOptionsForm(&$form, FormStateInterface $form_state) {
    */
   public function submitOptionsForm(&$form, FormStateInterface $form_state) {
     parent::submitOptionsForm($form, $form_state);
-    switch ($form_state['section']) {
+    switch ($form_state->get('section')) {
       case 'test_option':
         $this->setOption('test_option', $form_state->getValue('test_option'));
         break;
diff --git a/core/modules/views/tests/modules/views_test_data/src/Plugin/views/display_extender/DisplayExtenderTest.php b/core/modules/views/tests/modules/views_test_data/src/Plugin/views/display_extender/DisplayExtenderTest.php
index 8c0061d..bb35b9e 100644
--- a/core/modules/views/tests/modules/views_test_data/src/Plugin/views/display_extender/DisplayExtenderTest.php
+++ b/core/modules/views/tests/modules/views_test_data/src/Plugin/views/display_extender/DisplayExtenderTest.php
@@ -63,7 +63,7 @@ public function optionsSummary(&$categories, &$options) {
    * Overrides Drupal\views\Plugin\views\display_extender\DisplayExtenderPluginBase::buildOptionsForm().
    */
   public function buildOptionsForm(&$form, FormStateInterface $form_state) {
-    switch ($form_state['section']) {
+    switch ($form_state->get('section')) {
       case 'test_extender_test_option':
         $form['#title'] .= t('Test option');
         $form['test_extender_test_option'] = array(
@@ -80,7 +80,7 @@ public function buildOptionsForm(&$form, FormStateInterface $form_state) {
    */
   public function submitOptionsForm(&$form, FormStateInterface $form_state) {
     parent::submitOptionsForm($form, $form_state);
-    switch ($form_state['section']) {
+    switch ($form_state->get('section')) {
       case 'test_extender_test_option':
         $this->displayHandler->setOption('test_extender_test_option', $form_state->getValue('test_extender_test_option'));
         break;
diff --git a/core/modules/views_ui/admin.inc b/core/modules/views_ui/admin.inc
index 3136f64..61d2c85 100644
--- a/core/modules/views_ui/admin.inc
+++ b/core/modules/views_ui/admin.inc
@@ -138,15 +138,13 @@ function views_ui_add_limited_validation($element, FormStateInterface $form_stat
 
   // If we are in the process of a form submission and this is the button that
   // was clicked, the form API workflow in form_builder() will have already
-  // copied it to $form_state['triggering_element'] before our #process
+  // copied it to $form_state->getTriggeringElement() before our #process
   // function is run. So we need to make the same modifications in $form_state
   // as we did to the element itself, to ensure that #limit_validation_errors
   // will actually be set in the correct place.
-  if (!empty($form_state['triggering_element'])) {
-    $clicked_button = &$form_state['triggering_element'];
-    if ($clicked_button['#name'] == $element['#name'] && $clicked_button['#value'] == $element['#value']) {
-      $clicked_button['#limit_validation_errors'] = $element['#limit_validation_errors'];
-    }
+  $clicked_button = &$form_state->getTriggeringElement();
+  if ($clicked_button && $clicked_button['#name'] == $element['#name'] && $clicked_button['#value'] == $element['#value']) {
+    $clicked_button['#limit_validation_errors'] = $element['#limit_validation_errors'];
   }
 
   return $element;
@@ -197,14 +195,14 @@ function views_ui_ajax_update_form($form, FormStateInterface $form_state) {
   // The region that needs to be updated was stored in a property of the
   // triggering element by views_ui_add_ajax_trigger(), so all we have to do is
   // retrieve that here.
-  return NestedArray::getValue($form, $form_state['triggering_element']['#views_ui_ajax_data']['refresh_parents']);
+  return NestedArray::getValue($form, $form_state->getTriggeringElement()['#views_ui_ajax_data']['refresh_parents']);
 }
 
 /**
  * Non-Javascript fallback for updating the add view form.
  */
 function views_ui_nojs_submit($form, FormStateInterface $form_state) {
-  $form_state['rebuild'] = TRUE;
+  $form_state->setRebuild();
 }
 
 /**
@@ -263,8 +261,8 @@ function views_ui_taxonomy_autocomplete_validate($element, FormStateInterface $f
  * the current display.
  */
 function views_ui_standard_display_dropdown(&$form, FormStateInterface $form_state, $section) {
-  $view = $form_state['view'];
-  $display_id = $form_state['display_id'];
+  $view = $form_state->get('view');
+  $display_id = $form_state->get('display_id');
   $executable = $view->getExecutable();
   $displays = $executable->displayHandlers;
   $current_display = $executable->display_handler;
@@ -326,16 +324,16 @@ function views_ui_standard_display_dropdown(&$form, FormStateInterface $form_sta
  * information about the form.
  */
 function views_ui_build_form_path(FormStateInterface $form_state) {
-  $ajax = empty($form_state['ajax']) ? 'nojs' : 'ajax';
-  $name = $form_state['view']->id();
+  $ajax = $form_state->get('ajax') ? 'nojs' : 'ajax';
+  $name = $form_state->get('view')->id();
   $form_key = $form_state->get('form_key');
   $display_id = $form_state->get('display_id');
   $path = "admin/structure/views/$ajax/$form_key/$name/$display_id";
-  if (isset($form_state['type'])) {
-    $path .= '/' . $form_state['type'];
+  if ($type = $form_state->get('type')) {
+    $path .= '/' . $type;
   }
-  if (isset($form_state['id'])) {
-    $path .= '/' . $form_state['id'];
+  if ($id = $form_state->get('id')) {
+    $path .= '/' . $id;
   }
   return $path;
 }
@@ -357,9 +355,9 @@ function views_ui_build_form_path(FormStateInterface $form_state) {
  */
 function views_ui_form_button_was_clicked($element, FormStateInterface $form_state) {
   $user_input = $form_state->getUserInput();
-  $process_input = empty($element['#disabled']) && ($form_state['programmed'] || ($form_state['process_input'] && (!isset($element['#access']) || $element['#access'])));
-  if ($process_input && !isset($form_state['triggering_element']) && !empty($element['#is_button']) && isset($user_input[$element['#name']]) && isset($element['#values']) && in_array($user_input[$element['#name']], $element['#values'], TRUE)) {
-    $form_state['triggering_element'] = $element;
+  $process_input = empty($element['#disabled']) && ($form_state->isProgrammed() || ($form_state->isProcessingInput() && (!isset($element['#access']) || $element['#access'])));
+  if ($process_input && !$form_state->getTriggeringElement() && !empty($element['#is_button']) && isset($user_input[$element['#name']]) && isset($element['#values']) && in_array($user_input[$element['#name']], $element['#values'], TRUE)) {
+    $form_state->setTriggeringElement($element);
   }
   return $element;
 }
diff --git a/core/modules/views_ui/src/Form/Ajax/AddHandler.php b/core/modules/views_ui/src/Form/Ajax/AddHandler.php
index 4b69e93..afcfa43 100644
--- a/core/modules/views_ui/src/Form/Ajax/AddHandler.php
+++ b/core/modules/views_ui/src/Form/Ajax/AddHandler.php
@@ -50,9 +50,9 @@ public function getFormId() {
    * {@inheritdoc}
    */
   public function buildForm(array $form, FormStateInterface $form_state) {
-    $view = $form_state['view'];
-    $display_id = $form_state['display_id'];
-    $type = $form_state['type'];
+    $view = $form_state->get('view');
+    $display_id = $form_state->get('display_id');
+    $type = $form_state->get('type');
 
     $form = array(
       'options' => array(
@@ -81,7 +81,7 @@ public function buildForm(array $form, FormStateInterface $form_state) {
 
     // Figure out all the base tables allowed based upon what the relationships provide.
     $base_tables = $executable->getBaseTables();
-    $options = Views::viewsDataHelper()->fetchFields(array_keys($base_tables), $type, $display->useGroupBy(), $form_state['type']);
+    $options = Views::viewsDataHelper()->fetchFields(array_keys($base_tables), $type, $display->useGroupBy(), $form_state->get('type'));
 
     if (!empty($options)) {
       $form['override']['controls'] = array(
diff --git a/core/modules/views_ui/src/Form/Ajax/Analyze.php b/core/modules/views_ui/src/Form/Ajax/Analyze.php
index 1e0380d..95e126a 100644
--- a/core/modules/views_ui/src/Form/Ajax/Analyze.php
+++ b/core/modules/views_ui/src/Form/Ajax/Analyze.php
@@ -35,7 +35,7 @@ public function getFormId() {
    * {@inheritdoc}
    */
   public function buildForm(array $form, FormStateInterface $form_state) {
-    $view = $form_state['view'];
+    $view = $form_state->get('view');
 
     $form['#title'] = $this->t('View analysis');
     $form['#section'] = 'analyze';
@@ -50,7 +50,7 @@ public function buildForm(array $form, FormStateInterface $form_state) {
     );
 
     // Inform the standard button function that we want an OK button.
-    $form_state['ok_button'] = TRUE;
+    $form_state->set('ok_button', TRUE);
     $view->getStandardButtons($form, $form_state, 'views_ui_analyze_view_form');
     return $form;
   }
@@ -60,7 +60,7 @@ public function buildForm(array $form, FormStateInterface $form_state) {
    */
   public function submitForm(array &$form, FormStateInterface $form_state) {
     /** @var $view \Drupal\views_ui\ViewUI */
-    $view = $form_state['view'];
+    $view = $form_state->get('view');
     $form_state->setRedirectUrl($view->urlInfo('edit-form'));
   }
 
diff --git a/core/modules/views_ui/src/Form/Ajax/ConfigHandler.php b/core/modules/views_ui/src/Form/Ajax/ConfigHandler.php
index 02fb02f..90de6d9 100644
--- a/core/modules/views_ui/src/Form/Ajax/ConfigHandler.php
+++ b/core/modules/views_ui/src/Form/Ajax/ConfigHandler.php
@@ -52,10 +52,10 @@ public function getFormId() {
    * {@inheritdoc}
    */
   public function buildForm(array $form, FormStateInterface $form_state) {
-    $view = $form_state['view'];
-    $display_id = $form_state['display_id'];
-    $type = $form_state['type'];
-    $id = $form_state['id'];
+    $view = $form_state->get('view');
+    $display_id = $form_state->get('display_id');
+    $type = $form_state->get('type');
+    $id = $form_state->get('id');
 
     $form = array(
       'options' => array(
@@ -80,8 +80,9 @@ public function buildForm(array $form, FormStateInterface $form_state) {
         // If this item can come from the default display, show a dropdown
         // that lets the user choose which display the changes should apply to.
         if ($executable->display_handler->defaultableSections($types[$type]['plural'])) {
-          $form_state['section'] = $types[$type]['plural'];
-          views_ui_standard_display_dropdown($form, $form_state, $form_state['section']);
+          $section = $types[$type]['plural'];
+          $form_state->set('section', $section);
+          views_ui_standard_display_dropdown($form, $form_state, $section);
         }
 
         // A whole bunch of code to figure out what relationships are valid for
@@ -104,7 +105,7 @@ public function buildForm(array $form, FormStateInterface $form_state) {
           // If this relationship is valid for this type, add it to the list.
           $data = Views::viewsData()->get($relationship['table']);
           if (isset($data[$relationship['field']]['relationship']['base']) && $base = $data[$relationship['field']]['relationship']['base']) {
-            $base_fields = Views::viewsDataHelper()->fetchFields($base, $form_state['type'], $executable->display_handler->useGroupBy());
+            $base_fields = Views::viewsDataHelper()->fetchFields($base, $type, $executable->display_handler->useGroupBy());
             if (isset($base_fields[$item['table'] . '.' . $item['field']])) {
               $relationship_handler->init($executable, $executable->display_handler, $relationship);
               $relationship_options[$relationship['id']] = $relationship_handler->adminLabel();
@@ -115,7 +116,7 @@ public function buildForm(array $form, FormStateInterface $form_state) {
         if (!empty($relationship_options)) {
           // Make sure the existing relationship is even valid. If not, force
           // it to none.
-          $base_fields = Views::viewsDataHelper()->fetchFields($view->get('base_table'), $form_state['type'], $executable->display_handler->useGroupBy());
+          $base_fields = Views::viewsDataHelper()->fetchFields($view->get('base_table'), $type, $executable->display_handler->useGroupBy());
           if (isset($base_fields[$item['table'] . '.' . $item['field']])) {
             $relationship_options = array_merge(array('none' => $this->t('Do not use a relationship')), $relationship_options);
           }
@@ -159,13 +160,10 @@ public function buildForm(array $form, FormStateInterface $form_state) {
 
         // Get form from the handler.
         $handler->buildOptionsForm($form['options'], $form_state);
-        $form_state['handler'] = $handler;
+        $form_state->set('handler', $handler);
       }
 
-      $name = NULL;
-      if (isset($form_state['update_name'])) {
-        $name = $form_state['update_name'];
-      }
+      $name = $form_state->get('update_name');
 
       $view->getStandardButtons($form, $form_state, 'views_ui_config_item_form', $name);
       // Add a 'remove' button.
@@ -191,10 +189,10 @@ public function buildForm(array $form, FormStateInterface $form_state) {
    * {@inheritdoc}
    */
   public function validateForm(array &$form, FormStateInterface $form_state) {
-    $form_state['handler']->validateOptionsForm($form['options'], $form_state);
+    $form_state->get('handler')->validateOptionsForm($form['options'], $form_state);
 
     if ($form_state->getErrors()) {
-      $form_state['rerender'] = TRUE;
+      $form_state->set('rerender', TRUE);
     }
   }
 
@@ -202,20 +200,25 @@ public function validateForm(array &$form, FormStateInterface $form_state) {
    * {@inheritdoc}
    */
   public function submitForm(array &$form, FormStateInterface $form_state) {
+    $view = $form_state->get('view');
+    $display_id = $form_state->get('display_id');
+    $id = $form_state->get('id');
+    $handler = $form_state->get('handler');
+
     // Run it through the handler's submit function.
-    $form_state['handler']->submitOptionsForm($form['options'], $form_state);
-    $item = $form_state['handler']->options;
+    $handler->submitOptionsForm($form['options'], $form_state);
+    $item = $handler->options;
     $types = ViewExecutable::getHandlerTypes();
 
     // For footer/header $handler_type is area but $type is footer/header.
     // For all other handle types it's the same.
-    $handler_type = $type = $form_state['type'];
+    $handler_type = $type = $form_state->get('type');
     if (!empty($types[$type]['type'])) {
       $handler_type = $types[$type]['type'];
     }
 
     $override = NULL;
-    $executable = $form_state['view']->getExecutable();
+    $executable = $view->getExecutable();
     if ($executable->display_handler->useGroupBy() && !empty($item['group_type'])) {
       if (empty($executable->query)) {
         $executable->initQuery();
@@ -233,7 +236,7 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
 
     // Add the incoming options to existing options because items using
     // the extra form may not have everything in the form here.
-    $options = $form_state->getValue('options') + $form_state['handler']->options;
+    $options = $form_state->getValue('options') + $handler->options;
 
     // This unpacks only options that are in the definition, ensuring random
     // extra stuff on the form is not sent through.
@@ -248,33 +251,37 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
     $handler->options['dependencies']['module'][] = $handler->definition['provider'];
 
     // Store the item back on the view
-    $executable->setHandler($form_state['display_id'], $form_state['type'], $form_state['id'], $handler->options);
+    $executable->setHandler($display_id, $type, $id, $handler->options);
 
     // Ensure any temporary options are removed.
-    if (isset($form_state['view']->temporary_options[$type][$form_state['id']])) {
-      unset($form_state['view']->temporary_options[$type][$form_state['id']]);
+    if (isset($view->temporary_options[$type][$id])) {
+      unset($view->temporary_options[$type][$id]);
     }
 
     // Write to cache
-    $form_state['view']->cacheSet();
+    $view->cacheSet();
   }
 
   /**
    * Submit handler for removing an item from a view
    */
   public function remove(&$form, FormStateInterface $form_state) {
+    $view = $form_state->get('view');
+    $display_id = $form_state->get('display_id');
+    $type = $form_state->get('type');
+    $id = $form_state->get('id');
     // Store the item back on the view
-    list($was_defaulted, $is_defaulted) = $form_state['view']->getOverrideValues($form, $form_state);
-    $executable = $form_state['view']->getExecutable();
+    list($was_defaulted, $is_defaulted) = $view->getOverrideValues($form, $form_state);
+    $executable = $view->getExecutable();
     // If the display selection was changed toggle the override value.
     if ($was_defaulted != $is_defaulted) {
-      $display = &$executable->displayHandlers->get($form_state['display_id']);
+      $display = &$executable->displayHandlers->get($display_id);
       $display->optionsOverride($form, $form_state);
     }
-    $executable->removeHandler($form_state['display_id'], $form_state['type'], $form_state['id']);
+    $executable->removeHandler($display_id, $type, $id);
 
     // Write to cache
-    $form_state['view']->cacheSet();
+    $view->cacheSet();
   }
 
 }
diff --git a/core/modules/views_ui/src/Form/Ajax/ConfigHandlerExtra.php b/core/modules/views_ui/src/Form/Ajax/ConfigHandlerExtra.php
index 17b5011..3cdaece 100644
--- a/core/modules/views_ui/src/Form/Ajax/ConfigHandlerExtra.php
+++ b/core/modules/views_ui/src/Form/Ajax/ConfigHandlerExtra.php
@@ -51,10 +51,10 @@ public function getFormId() {
    * {@inheritdoc}
    */
   public function buildForm(array $form, FormStateInterface $form_state) {
-    $view = $form_state['view'];
-    $display_id = $form_state['display_id'];
-    $type = $form_state['type'];
-    $id = $form_state['id'];
+    $view = $form_state->get('view');
+    $display_id = $form_state->get('display_id');
+    $type = $form_state->get('type');
+    $id = $form_state->get('id');
 
     $form = array(
       'options' => array(
@@ -82,7 +82,7 @@ public function buildForm(array $form, FormStateInterface $form_state) {
 
         // Get form from the handler.
         $handler->buildExtraOptionsForm($form['options'], $form_state);
-        $form_state['handler'] = $handler;
+        $form_state->set('handler', $handler);
       }
 
       $view->getStandardButtons($form, $form_state, 'views_ui_config_item_extra_form');
@@ -94,16 +94,18 @@ public function buildForm(array $form, FormStateInterface $form_state) {
    * {@inheritdoc}
    */
   public function validateForm(array &$form, FormStateInterface $form_state) {
-    $form_state['handler']->validateExtraOptionsForm($form['options'], $form_state);
+    $form_state->get('handler')->validateExtraOptionsForm($form['options'], $form_state);
   }
 
   /**
    * {@inheritdoc}
    */
   public function submitForm(array &$form, FormStateInterface $form_state) {
+    $view = $form_state->get('view');
+    $handler = $form_state->get('handler');
     // Run it through the handler's submit function.
-    $form_state['handler']->submitExtraOptionsForm($form['options'], $form_state);
-    $item = $form_state['handler']->options;
+    $handler->submitExtraOptionsForm($form['options'], $form_state);
+    $item = $handler->options;
 
     // Store the data we're given.
     foreach ($form_state->getValue('options') as $key => $value) {
@@ -111,10 +113,10 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
     }
 
     // Store the item back on the view
-    $form_state['view']->getExecutable()->setHandler($form_state['display_id'], $form_state['type'], $form_state['id'], $item);
+    $view->getExecutable()->setHandler($form_state->get('display_id'), $form_state->get('type'), $form_state->get('id'), $item);
 
     // Write to cache
-    $form_state['view']->cacheSet();
+    $view->cacheSet();
   }
 
 }
diff --git a/core/modules/views_ui/src/Form/Ajax/ConfigHandlerGroup.php b/core/modules/views_ui/src/Form/Ajax/ConfigHandlerGroup.php
index fba13ea..3cc63f5 100644
--- a/core/modules/views_ui/src/Form/Ajax/ConfigHandlerGroup.php
+++ b/core/modules/views_ui/src/Form/Ajax/ConfigHandlerGroup.php
@@ -52,10 +52,10 @@ public function getFormId() {
    * {@inheritdoc}
    */
   public function buildForm(array $form, FormStateInterface $form_state) {
-    $view = $form_state['view'];
-    $display_id = $form_state['display_id'];
-    $type = $form_state['type'];
-    $id = $form_state['id'];
+    $view = $form_state->get('view');
+    $display_id = $form_state->get('display_id');
+    $type = $form_state->get('type');
+    $id = $form_state->get('id');
 
     $form = array(
       'options' => array(
@@ -85,7 +85,7 @@ public function buildForm(array $form, FormStateInterface $form_state) {
         $form['#title'] = $this->t('Configure aggregation settings for @type %item', array('@type' => $types[$type]['lstitle'], '%item' => $handler->adminLabel()));
 
         $handler->buildGroupByForm($form['options'], $form_state);
-        $form_state['handler'] = $handler;
+        $form_state->set('handler', $handler);
       }
 
       $view->getStandardButtons($form, $form_state, 'views_ui_config_item_group_form');
@@ -97,20 +97,21 @@ public function buildForm(array $form, FormStateInterface $form_state) {
    * {@inheritdoc}
    */
   public function submitForm(array &$form, FormStateInterface $form_state) {
-    $item = &$form_state['handler']->options;
-    $type = $form_state['type'];
+    $view = $form_state->get('view');
+    $item = &$form_state->get('handler')->options;
+    $type = $form_state->get('type');
 
     $handler = Views::handlerManager($type)->getHandler($item);
-    $executable = $form_state['view']->getExecutable();
+    $executable = $view->getExecutable();
     $handler->init($executable, $executable->display_handler, $item);
 
     $handler->submitGroupByForm($form, $form_state);
 
     // Store the item back on the view
-    $executable->setHandler($form_state['display_id'], $form_state['type'], $form_state['id'], $item);
+    $executable->setHandler($form_state->get('display_id'), $form_state->get('type'), $form_state->get('id'), $item);
 
     // Write to cache
-    $form_state['view']->cacheSet();
+    $view->cacheSet();
   }
 
 }
diff --git a/core/modules/views_ui/src/Form/Ajax/Display.php b/core/modules/views_ui/src/Form/Ajax/Display.php
index 7ac9832..307fda4 100644
--- a/core/modules/views_ui/src/Form/Ajax/Display.php
+++ b/core/modules/views_ui/src/Form/Ajax/Display.php
@@ -32,12 +32,12 @@ public function getFormKey() {
   /**
    * {@inheritdoc}
    *
-   * @todo Remove this and switch all usage of $form_state['section'] to
-   *   $form_state['type'].
+   * @todo Remove this and switch all usage of $form_state->get('section') to
+   *   $form_state->get('type').
    */
   public function getFormState(ViewStorageInterface $view, $display_id, $js) {
     $form_state = parent::getFormState($view, $display_id, $js);
-    $form_state['section'] = $this->type;
+    $form_state->set('section', $this->type);
     return $form_state;
   }
 
@@ -60,8 +60,8 @@ public function getFormId() {
    * {@inheritdoc}
    */
   public function buildForm(array $form, FormStateInterface $form_state) {
-    $view = $form_state['view'];
-    $display_id = $form_state['display_id'];
+    $view = $form_state->get('view');
+    $display_id = $form_state->get('display_id');
 
     $executable = $view->getExecutable();
     $executable->setDisplay($display_id);
@@ -84,11 +84,7 @@ public function buildForm(array $form, FormStateInterface $form_state) {
       unset($form['options']['override']);
     }
 
-    $name = NULL;
-    if (isset($form_state['update_name'])) {
-      $name = $form_state['update_name'];
-    }
-
+    $name = $form_state->get('update_name');
     $view->getStandardButtons($form, $form_state, 'views_ui_edit_display_form', $name);
     return $form;
   }
@@ -97,10 +93,12 @@ public function buildForm(array $form, FormStateInterface $form_state) {
    * {@inheritdoc}
    */
   public function validateForm(array &$form, FormStateInterface $form_state) {
-    $form_state['view']->getExecutable()->displayHandlers->get($form_state['display_id'])->validateOptionsForm($form['options'], $form_state);
+    $view = $form_state->get('view');
+    $display_id = $form_state->get('display_id');
+    $view->getExecutable()->displayHandlers->get($display_id)->validateOptionsForm($form['options'], $form_state);
 
     if ($form_state->getErrors()) {
-      $form_state['rerender'] = TRUE;
+      $form_state->set('rerender', TRUE);
     }
   }
 
@@ -108,9 +106,11 @@ public function validateForm(array &$form, FormStateInterface $form_state) {
    * {@inheritdoc}
    */
   public function submitForm(array &$form, FormStateInterface $form_state) {
-    $form_state['view']->getExecutable()->displayHandlers->get($form_state['display_id'])->submitOptionsForm($form['options'], $form_state);
+    $view = $form_state->get('view');
+    $display_id = $form_state->get('display_id');
+    $view->getExecutable()->displayHandlers->get($display_id)->submitOptionsForm($form['options'], $form_state);
 
-    $form_state['view']->cacheSet();
+    $view->cacheSet();
   }
 
 }
diff --git a/core/modules/views_ui/src/Form/Ajax/EditDetails.php b/core/modules/views_ui/src/Form/Ajax/EditDetails.php
index 2a085a0..4d373db 100644
--- a/core/modules/views_ui/src/Form/Ajax/EditDetails.php
+++ b/core/modules/views_ui/src/Form/Ajax/EditDetails.php
@@ -34,7 +34,7 @@ public function getFormId() {
    * {@inheritdoc}
    */
   public function buildForm(array $form, FormStateInterface $form_state) {
-    $view = $form_state['view'];
+    $view = $form_state->get('view');
 
     $form['#title'] = $this->t('Name and description');
     $form['#section'] = 'details';
@@ -75,7 +75,7 @@ public function buildForm(array $form, FormStateInterface $form_state) {
    * {@inheritdoc}
    */
   public function submitForm(array &$form, FormStateInterface $form_state) {
-    $view = $form_state['view'];
+    $view = $form_state->get('view');
     foreach ($form_state->getValues() as $key => $value) {
       // Only save values onto the view if they're actual view properties
       // (as opposed to 'op' or 'form_build_id').
@@ -84,11 +84,11 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
       }
     }
     $bases = Views::viewsData()->fetchBaseTables();
-    $form_state['#page_title'] = $view->label();
-
+    $page_title = $view->label();
     if (isset($bases[$view->get('base_table')])) {
-      $form_state['#page_title'] .= ' (' . $bases[$view->get('base_table')]['title'] . ')';
+      $page_title .= ' (' . $bases[$view->get('base_table')]['title'] . ')';
     }
+    $form_state->set('#page_title', $page_title);
 
     $view->cacheSet();
   }
diff --git a/core/modules/views_ui/src/Form/Ajax/Rearrange.php b/core/modules/views_ui/src/Form/Ajax/Rearrange.php
index 72b4395..25cf3f1 100644
--- a/core/modules/views_ui/src/Form/Ajax/Rearrange.php
+++ b/core/modules/views_ui/src/Form/Ajax/Rearrange.php
@@ -49,9 +49,9 @@ public function getFormId() {
    * {@inheritdoc}
    */
   public function buildForm(array $form, FormStateInterface $form_state) {
-    $view = $form_state['view'];
-    $display_id = $form_state['display_id'];
-    $type = $form_state['type'];
+    $view = $form_state->get('view');
+    $display_id = $form_state->get('display_id');
+    $type = $form_state->get('type');
 
     $types = ViewExecutable::getHandlerTypes();
     $executable = $view->getExecutable();
@@ -61,8 +61,9 @@ public function buildForm(array $form, FormStateInterface $form_state) {
     $form['#section'] = $display_id . 'rearrange-item';
 
     if ($display->defaultableSections($types[$type]['plural'])) {
-      $form_state['section'] = $types[$type]['plural'];
-      views_ui_standard_display_dropdown($form, $form_state, $form_state['section']);
+      $section = $types[$type]['plural'];
+      $form_state->set('section', $section);
+      views_ui_standard_display_dropdown($form, $form_state, $section);
     }
 
     $count = 0;
@@ -136,10 +137,14 @@ public function buildForm(array $form, FormStateInterface $form_state) {
    * {@inheritdoc}
    */
   public function submitForm(array &$form, FormStateInterface $form_state) {
-    $types = ViewExecutable::getHandlerTypes();
-    $display = &$form_state['view']->getExecutable()->displayHandlers->get($form_state['display_id']);
+    $view = $form_state->get('view');
+    $display_id = $form_state->get('display_id');
+    $type = $form_state->get('type');
 
-    $old_fields = $display->getOption($types[$form_state['type']]['plural']);
+    $types = ViewExecutable::getHandlerTypes();
+    $display = &$view->getExecutable()->displayHandlers->get($display_id);
+
+    $old_fields = $display->getOption($types[$type]['plural']);
     $new_fields = $order = array();
 
     // Make an array with the weights
@@ -158,10 +163,10 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
     foreach (array_keys($order) as $field) {
       $new_fields[$field] = $old_fields[$field];
     }
-    $display->setOption($types[$form_state['type']]['plural'], $new_fields);
+    $display->setOption($types[$type]['plural'], $new_fields);
 
     // Store in cache
-    $form_state['view']->cacheSet();
+    $view->cacheSet();
   }
 
 }
diff --git a/core/modules/views_ui/src/Form/Ajax/RearrangeFilter.php b/core/modules/views_ui/src/Form/Ajax/RearrangeFilter.php
index eb77664..58f869a 100644
--- a/core/modules/views_ui/src/Form/Ajax/RearrangeFilter.php
+++ b/core/modules/views_ui/src/Form/Ajax/RearrangeFilter.php
@@ -35,8 +35,8 @@ public function getFormId() {
    * {@inheritdoc}
    */
   public function buildForm(array $form, FormStateInterface $form_state) {
-    $view = $form_state['view'];
-    $display_id = $form_state['display_id'];
+    $view = $form_state->get('view');
+    $display_id = $form_state->get('display_id');
     $type = 'filter';
 
     $types = ViewExecutable::getHandlerTypes();
@@ -50,8 +50,9 @@ public function buildForm(array $form, FormStateInterface $form_state) {
     $form['#section'] = $display_id . 'rearrange-item';
 
     if ($display->defaultableSections($types[$type]['plural'])) {
-      $form_state['section'] = $types[$type]['plural'];
-      views_ui_standard_display_dropdown($form, $form_state, $form_state['section']);
+      $section = $types[$type]['plural'];
+      $form_state->set('section', $section);
+      views_ui_standard_display_dropdown($form, $form_state, $section);
     }
 
     if (!empty($view->form_cache)) {
@@ -216,11 +217,12 @@ public function buildForm(array $form, FormStateInterface $form_state) {
    */
   public function submitForm(array &$form, FormStateInterface $form_state) {
     $types = ViewExecutable::getHandlerTypes();
-    $display = &$form_state['view']->getExecutable()->displayHandlers->get($form_state['display_id']);
+    $view = $form_state->get('view');
+    $display = &$view->getExecutable()->displayHandlers->get($form_state->get('display_id'));
     $remember_groups = array();
 
-    if (!empty($form_state['view']->form_cache)) {
-      $old_fields = $form_state['view']->form_cache['handlers'];
+    if (!empty($view->form_cache)) {
+      $old_fields = $view->form_cache['handlers'];
     }
     else {
       $old_fields = $display->getOption($types['filter']['plural']);
@@ -256,15 +258,16 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
 
     // If the #group property is set on the clicked button, that means we are
     // either adding or removing a group, not actually updating the filters.
-    if (!empty($form_state['clicked_button']['#group'])) {
-      if ($form_state['clicked_button']['#group'] == 'add') {
+    $clicked_button = $form_state->get('clicked_button');
+    if (!empty($clicked_button['#group'])) {
+      if ($clicked_button['#group'] == 'add') {
         // Add a new group
         $groups['groups'][] = 'AND';
       }
       else {
         // Renumber groups above the removed one down.
         foreach (array_keys($groups['groups']) as $group_id) {
-          if ($group_id >= $form_state['clicked_button']['#group']) {
+          if ($group_id >= $clicked_button['#group']) {
             $old_group = $group_id + 1;
             if (isset($groups['groups'][$old_group])) {
               $groups['groups'][$group_id] = $groups['groups'][$old_group];
@@ -283,14 +286,14 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
       }
       // Update our cache with values so that cancel still works the way
       // people expect.
-      $form_state['view']->form_cache = array(
+      $view->form_cache = [
         'key' => 'rearrange-filter',
         'groups' => $groups,
         'handlers' => $new_fields,
-      );
+      ];
 
       // Return to this form except on actual Update.
-      $form_state['view']->addFormToStack('rearrange-filter', $form_state['display_id'], 'filter');
+      $view->addFormToStack('rearrange-filter', $form_state->get('display_id'), 'filter');
     }
     else {
       // The actual update button was clicked. Remove the empty groups, and
@@ -308,13 +311,13 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
       // Write the changed handler values.
       $display->setOption($types['filter']['plural'], $new_fields);
       $display->setOption('filter_groups', $groups);
-      if (isset($form_state['view']->form_cache)) {
-        unset($form_state['view']->form_cache);
+      if (isset($view->form_cache)) {
+        unset($view->form_cache);
       }
     }
 
     // Store in cache.
-    $form_state['view']->cacheSet();
+    $view->cacheSet();
   }
 
   /**
diff --git a/core/modules/views_ui/src/Form/Ajax/ReorderDisplays.php b/core/modules/views_ui/src/Form/Ajax/ReorderDisplays.php
index ba797b0..173202c 100644
--- a/core/modules/views_ui/src/Form/Ajax/ReorderDisplays.php
+++ b/core/modules/views_ui/src/Form/Ajax/ReorderDisplays.php
@@ -34,8 +34,8 @@ public function getFormId() {
    */
   public function buildForm(array $form, FormStateInterface $form_state) {
     /** @var $view \Drupal\views\ViewStorageInterface */
-    $view = $form_state['view'];
-    $display_id = $form_state['display_id'];
+    $view = $form_state->get('view');
+    $display_id = $form_state->get('display_id');
 
     $form['#title'] = $this->t('Reorder displays');
     $form['#section'] = 'reorder';
@@ -151,7 +151,7 @@ public function buildForm(array $form, FormStateInterface $form_state) {
    */
   public function submitForm(array &$form, FormStateInterface $form_state) {
     /** @var $view \Drupal\views_ui\ViewUI */
-    $view = $form_state['view'];
+    $view = $form_state->get('view');
     $order = array();
 
     $user_input = $form_state->getUserInput();
diff --git a/core/modules/views_ui/src/Form/Ajax/ViewsFormBase.php b/core/modules/views_ui/src/Form/Ajax/ViewsFormBase.php
index 15e6ee2..9ab629c 100644
--- a/core/modules/views_ui/src/Form/Ajax/ViewsFormBase.php
+++ b/core/modules/views_ui/src/Form/Ajax/ViewsFormBase.php
@@ -66,20 +66,16 @@ protected function setType($type) {
   public function getFormState(ViewStorageInterface $view, $display_id, $js) {
     // $js may already have been converted to a Boolean.
     $ajax = is_string($js) ? $js === 'ajax' : $js;
-    return new FormState(array(
-      'form_id' => $this->getFormId(),
-      'form_key' => $this->getFormKey(),
-      'ajax' => $ajax,
-      'display_id' => $display_id,
-      'view' => $view,
-      'type' => $this->type,
-      'id' => $this->id,
-      'no_redirect' => TRUE,
-      'build_info' => array(
-        'args' => array(),
-        'callback_object' => $this,
-      ),
-    ));
+    return (new FormState())
+      ->set('form_id', $this->getFormId())
+      ->set('form_key', $this->getFormKey())
+      ->set('ajax', $ajax)
+      ->set('display_id', $display_id)
+      ->set('view', $view)
+      ->set('type', $this->type)
+      ->set('id', $this->id)
+      ->disableRedirect()
+      ->addBuildInfo('callback_object', $this);
   }
 
   /**
@@ -87,8 +83,8 @@ public function getFormState(ViewStorageInterface $view, $display_id, $js) {
    */
   public function getForm(ViewStorageInterface $view, $display_id, $js) {
     $form_state = $this->getFormState($view, $display_id, $js);
-    $view = $form_state['view'];
-    $key = $form_state['form_key'];
+    $view = $form_state->get('view');
+    $key = $form_state->get('form_key');
 
     // @todo Remove the need for this.
     \Drupal::moduleHandler()->loadInclude('views_ui', 'inc', 'admin');
@@ -103,7 +99,7 @@ public function getForm(ViewStorageInterface $view, $display_id, $js) {
     // it off; if it isn't, the user clicked somewhere else and the stack is
     // now irrelevant.
     if (!empty($view->stack)) {
-      $identifier = implode('-', array_filter(array($key, $view->id(), $display_id, $form_state['type'], $form_state['id'])));
+      $identifier = implode('-', array_filter([$key, $view->id(), $display_id, $form_state->get('type'), $form_state->get('id')]));
       // Retrieve the first form from the stack without changing the integer keys,
       // as they're being used for the "2 of 3" progress indicator.
       reset($view->stack);
@@ -130,11 +126,11 @@ public function getForm(ViewStorageInterface $view, $display_id, $js) {
     // before rendering the second time.
     $drupal_add_js_original = _drupal_add_js();
     $drupal_add_js = &drupal_static('_drupal_add_js');
-    $form_class = get_class($form_state['build_info']['callback_object']);
+    $form_class = get_class($form_state->getFormObject());
     $response = views_ajax_form_wrapper($form_class, $form_state);
 
     // If the form has not been submitted, or was not set for rerendering, stop.
-    if (!$form_state['submitted'] || !empty($form_state['rerender'])) {
+    if (!$form_state->isSubmitted() || $form_state->get('rerender')) {
       return $response;
     }
 
@@ -147,18 +143,18 @@ public function getForm(ViewStorageInterface $view, $display_id, $js) {
       // Build the new form state for the next form in the stack.
       $reflection = new \ReflectionClass($view::$forms[$top[1]]);
       /** @var $form_state \Drupal\Core\Form\FormStateInterface */
-      $form_state = $reflection->newInstanceArgs(array_slice($top, 3, 2))->getFormState($view, $top[2], $form_state['ajax']);
-      $form_class = get_class($form_state['build_info']['callback_object']);
+      $form_state = $reflection->newInstanceArgs(array_slice($top, 3, 2))->getFormState($view, $top[2], $form_state->get('ajax'));
+      $form_class = get_class($form_state->getFormObject());
 
       $form_state->setUserInput(array());
       $form_path = views_ui_build_form_path($form_state);
-      if (!$form_state['ajax']) {
+      if (!$form_state->get('ajax')) {
         return new RedirectResponse(url($form_path, array('absolute' => TRUE)));
       }
-      $form_state['path'] = $form_path;
+      $form_state->set('path', $form_path);
       $response = views_ajax_form_wrapper($form_class, $form_state);
     }
-    elseif (!$form_state['ajax']) {
+    elseif (!$form_state->get('ajax')) {
       // if nothing on the stack, non-js forms just go back to the main view editor.
       $display_id = $form_state->get('display_id');
       return new RedirectResponse(url("admin/structure/views/view/{$view->id()}/edit/$display_id", array('absolute' => TRUE)));
@@ -168,8 +164,8 @@ public function getForm(ViewStorageInterface $view, $display_id, $js) {
       $response->addCommand(new CloseModalDialogCommand());
       $response->addCommand(new Ajax\ShowButtonsCommand(!empty($view->changed)));
       $response->addCommand(new Ajax\TriggerPreviewCommand());
-      if (!empty($form_state['#page_title'])) {
-        $response->addCommand(new Ajax\ReplaceTitleCommand($form_state['#page_title']));
+      if ($page_title = $form_state->get('#page_title')) {
+        $response->addCommand(new Ajax\ReplaceTitleCommand($page_title));
       }
     }
     // If this form was for view-wide changes, there's no need to regenerate
diff --git a/core/modules/views_ui/src/ViewAddForm.php b/core/modules/views_ui/src/ViewAddForm.php
index 78fb049..eedb7fd 100644
--- a/core/modules/views_ui/src/ViewAddForm.php
+++ b/core/modules/views_ui/src/ViewAddForm.php
@@ -165,9 +165,9 @@ protected function actions(array $form, FormStateInterface $form_state) {
   public function validate(array $form, FormStateInterface $form_state) {
     $wizard_type = $form_state->getValue(array('show', 'wizard_key'));
     $wizard_instance = $this->wizardManager->createInstance($wizard_type);
-    $form_state['wizard'] = $wizard_instance->getPluginDefinition();
-    $form_state['wizard_instance'] = $wizard_instance;
-    $errors = $form_state['wizard_instance']->validateView($form, $form_state);
+    $form_state->set('wizard', $wizard_instance->getPluginDefinition());
+    $form_state->set('wizard_instance', $wizard_instance);
+    $errors = $wizard_instance->validateView($form, $form_state);
 
     foreach ($errors as $display_errors) {
       foreach ($display_errors as $name => $message) {
@@ -182,7 +182,7 @@ public function validate(array $form, FormStateInterface $form_state) {
   public function submit(array $form, FormStateInterface $form_state) {
     try {
       /** @var $wizard \Drupal\views\Plugin\views\wizard\WizardInterface */
-      $wizard = $form_state['wizard_instance'];
+      $wizard = $form_state->get('wizard_instance');
       $view = $wizard->createView($form, $form_state);
     }
     // @todo Figure out whether it really makes sense to throw and catch exceptions on the wizard.
diff --git a/core/modules/views_ui/src/ViewEditForm.php b/core/modules/views_ui/src/ViewEditForm.php
index 738a087..d87b90b 100644
--- a/core/modules/views_ui/src/ViewEditForm.php
+++ b/core/modules/views_ui/src/ViewEditForm.php
@@ -82,15 +82,15 @@ public static function create(ContainerInterface $container) {
   public function form(array $form, FormStateInterface $form_state) {
     $view = $this->entity;
     $display_id = $this->displayID;
-    // Do not allow the form to be cached, because $form_state['view'] can become
+    // Do not allow the form to be cached, because $form_state->get('view') can become
     // stale between page requests.
     // See views_ui_ajax_get_form() for how this affects #ajax.
     // @todo To remove this and allow the form to be cacheable:
-    //   - Change $form_state['view'] to $form_state['temporary']['view'].
-    //   - Add a #process function to initialize $form_state['temporary']['view']
+    //   - Change $form_state->get('view') to $form_state->getTemporary()['view'].
+    //   - Add a #process function to initialize $form_state->getTemporary()['view']
     //     on cached form submissions.
     //   - Use \Drupal\Core\Form\FormStateInterface::loadInclude().
-    $form_state['no_cache'] = TRUE;
+    $form_state->disableCache();
 
     if ($display_id) {
       if (!$view->getExecutable()->setDisplay($display_id)) {
@@ -177,7 +177,7 @@ public function form(array $form, FormStateInterface $form_state) {
 
     // The rest requires a display to be selected.
     if ($display_id) {
-      $form_state['display_id'] = $display_id;
+      $form_state->set('display_id', $display_id);
 
       // The part of the page where editing will take place.
       $form['displays']['settings'] = array(
@@ -583,7 +583,7 @@ public function getDisplayDetails($view, $display) {
   public function submitDisplayUndoDelete($form, FormStateInterface $form_state) {
     $view = $this->entity;
     // Create the new display
-    $id = $form_state['display_id'];
+    $id = $form_state->get('display_id');
     $displays = $view->get('display');
     $displays[$id]['deleted'] = FALSE;
     $view->set('display', $displays);
@@ -603,7 +603,7 @@ public function submitDisplayUndoDelete($form, FormStateInterface $form_state) {
    */
   public function submitDisplayEnable($form, FormStateInterface $form_state) {
     $view = $this->entity;
-    $id = $form_state['display_id'];
+    $id = $form_state->get('display_id');
     // setOption doesn't work because this would might affect upper displays
     $view->getExecutable()->displayHandlers->get($id)->setOption('enabled', TRUE);
 
@@ -622,7 +622,7 @@ public function submitDisplayEnable($form, FormStateInterface $form_state) {
    */
   public function submitDisplayDisable($form, FormStateInterface $form_state) {
     $view = $this->entity;
-    $id = $form_state['display_id'];
+    $id = $form_state->get('display_id');
     $view->getExecutable()->displayHandlers->get($id)->setOption('enabled', FALSE);
 
     // Store in cache
@@ -640,7 +640,7 @@ public function submitDisplayDisable($form, FormStateInterface $form_state) {
    */
   public function submitDisplayDelete($form, FormStateInterface $form_state) {
     $view = $this->entity;
-    $display_id = $form_state['display_id'];
+    $display_id = $form_state->get('display_id');
 
     // Mark the display for deletion.
     $displays = $view->get('display');
@@ -774,7 +774,7 @@ public function renderDisplayTop(ViewUI $view) {
    * Submit handler for form buttons that do not complete a form workflow.
    *
    * The Edit View form is a multistep form workflow, but with state managed by
-   * the TempStore rather than $form_state['rebuild']. Without this
+   * the TempStore rather than $form_state->setRebuild(). Without this
    * submit handler, buttons that add or remove displays would redirect to the
    * destination parameter (e.g., when the Edit View form is linked to from a
    * contextual link). This handler can be added to buttons whose form submission
@@ -839,7 +839,7 @@ public function submitDisplayDuplicate($form, FormStateInterface $form_state) {
   public function submitDisplayAdd($form, FormStateInterface $form_state) {
     $view = $this->entity;
     // Create the new display.
-    $parents = $form_state['triggering_element']['#parents'];
+    $parents = $form_state->getTriggeringElement()['#parents'];
     $display_type = array_pop($parents);
     $display = $view->getExecutable()->newDisplay($display_type);
     $display_id = $display->display['id'];
@@ -863,7 +863,7 @@ public function submitDuplicateDisplayAsType($form, FormStateInterface $form_sta
     $display_id = $this->displayID;
 
     // Create the new display.
-    $parents = $form_state['triggering_element']['#parents'];
+    $parents = $form_state->getTriggeringElement()['#parents'];
     $display_type = array_pop($parents);
     $display = $view->getExecutable()->newDisplay($display_type);
     $new_display_id = $display->display['id'];
diff --git a/core/modules/views_ui/src/ViewPreviewForm.php b/core/modules/views_ui/src/ViewPreviewForm.php
index 9ba3b99..f132b64 100644
--- a/core/modules/views_ui/src/ViewPreviewForm.php
+++ b/core/modules/views_ui/src/ViewPreviewForm.php
@@ -58,7 +58,7 @@ public function form(array $form, FormStateInterface $form_state) {
     $seen_ids_init = &drupal_static('drupal_html_id:init');
     $seen_ids_init = array();
 
-    $form_state['no_cache'] = TRUE;
+    $form_state->disableCache();
 
     $form['controls']['#attributes'] = array('class' => array('clearfix'));
 
@@ -84,7 +84,7 @@ public function form(array $form, FormStateInterface $form_state) {
     }
 
     $user_input = $form_state->getUserInput();
-    if (!empty($form_state['show_preview']) || !empty($user_input['js'])) {
+    if ($form_state->get('show_preview') || !empty($user_input['js'])) {
       $form['preview'] = array(
         '#weight' => 110,
         '#theme_wrappers' => array('container'),
@@ -135,9 +135,11 @@ public function submitPreview($form, FormStateInterface $form_state) {
     if (!$new_view = $this->tempStore->get($view->id())) {
       $new_view = new ViewUI($view);
     }
-    $form_state['build_info']['args'][0] = $new_view;
-    $form_state['show_preview'] = TRUE;
-    $form_state['rebuild'] = TRUE;
+    $build_info = $form_state->getBuildInfo();
+    $build_info['args'][0] = $new_view;
+    $form_state->setBuildInfo($build_info);
+    $form_state->set('show_preview', TRUE);
+    $form_state->setRebuild();
   }
 
 }
diff --git a/core/modules/views_ui/src/ViewUI.php b/core/modules/views_ui/src/ViewUI.php
index 51e16b2..e94a3ed 100644
--- a/core/modules/views_ui/src/ViewUI.php
+++ b/core/modules/views_ui/src/ViewUI.php
@@ -250,9 +250,10 @@ public function standardSubmit($form, FormStateInterface $form_state) {
 
     // Based on the user's choice in the display dropdown, determine which display
     // these changes apply to.
+    $display_id = $form_state->get('display_id');
     if ($revert) {
       // If it's revert just change the override and return.
-      $display = &$this->executable->displayHandlers->get($form_state['display_id']);
+      $display = &$this->executable->displayHandlers->get($display_id);
       $display->optionsOverride($form, $form_state);
 
       // Don't execute the normal submit handling but still store the changed view into cache.
@@ -266,7 +267,7 @@ public function standardSubmit($form, FormStateInterface $form_state) {
     elseif ($was_defaulted && !$is_defaulted) {
       // We were using the default display's values, but we're now overriding
       // the default display and saving values specific to this display.
-      $display = &$this->executable->displayHandlers->get($form_state['display_id']);
+      $display = &$this->executable->displayHandlers->get($display_id);
       // optionsOverride toggles the override of this section.
       $display->optionsOverride($form, $form_state);
       $display->submitOptionsForm($form, $form_state);
@@ -276,22 +277,14 @@ public function standardSubmit($form, FormStateInterface $form_state) {
       // to go back to the default display.
       // Overwrite the default display with the current form values, and make
       // the current display use the new default values.
-      $display = &$this->executable->displayHandlers->get($form_state['display_id']);
+      $display = &$this->executable->displayHandlers->get($display_id);
       // optionsOverride toggles the override of this section.
       $display->optionsOverride($form, $form_state);
       $display->submitOptionsForm($form, $form_state);
     }
 
-    $submit_handler = $form['#form_id'] . '_submit';
-    if (isset($form_state['build_info']['callback_object'])) {
-      $submit_handler = array($form_state['build_info']['callback_object'], 'submitForm');
-    }
-    if (is_callable($submit_handler)) {
-      // The submit handler might be a function or a method on the
-      // callback_object. Additional note that we have to pass the parameters
-      // by reference, as php 5.4 requires us to do that.
-      call_user_func_array($submit_handler, array(&$form, &$form_state));
-    }
+    $submit_handler = [$form_state->getFormObject(), 'submitForm'];
+    call_user_func_array($submit_handler, [&$form, $form_state]);
   }
 
   /**
@@ -330,11 +323,11 @@ public function getStandardButtons(&$form, FormStateInterface $form_state, $form
     // Views provides its own custom handling of AJAX form submissions. Usually
     // this happens at the same path, but custom paths may be specified in
     // $form_state.
-    $form_path = empty($form_state['path']) ? current_path() : $form_state['path'];
+    $form_path = $form_state->get('path') ?: current_path();
 
     // Forms that are purely informational set an ok_button flag, so we know not
     // to create an "Apply" button for them.
-    if (empty($form_state['ok_button'])) {
+    if (!$form_state->get('ok_button')) {
       $form['actions']['submit'] = array(
         '#type' => 'submit',
         '#value' => $name,
@@ -364,19 +357,14 @@ public function getStandardButtons(&$form, FormStateInterface $form_state, $form
         $form['actions']['submit']['#process'] = array_merge(array('views_ui_form_button_was_clicked'), element_info_property($form['actions']['submit']['#type'], '#process', array()));
       }
       // If a validation handler exists for the form, assign it to this button.
-      if (isset($form_state['build_info']['callback_object'])) {
-        $form['actions']['submit']['#validate'][] = array($form_state['build_info']['callback_object'], 'validateForm');
-      }
-      if (function_exists($form_id . '_validate')) {
-        $form['actions']['submit']['#validate'][] = $form_id . '_validate';
-      }
+      $form['actions']['submit']['#validate'][] = [$form_state->getFormObject(), 'validateForm'];
     }
 
     // Create a "Cancel" button. For purely informational forms, label it "OK".
     $cancel_submit = function_exists($form_id . '_cancel') ? $form_id . '_cancel' : array($this, 'standardCancel');
     $form['actions']['cancel'] = array(
       '#type' => 'submit',
-      '#value' => empty($form_state['ok_button']) ? t('Cancel') : t('Ok'),
+      '#value' => $form_state->has('ok_button') ? t('Cancel') : t('Ok'),
       '#submit' => array($cancel_submit),
       '#validate' => array(),
       '#ajax' => array(
@@ -388,10 +376,10 @@ public function getStandardButtons(&$form, FormStateInterface $form_state, $form
     // Compatibility, to be removed later: // TODO: When is "later"?
     // We used to set these items on the form, but now we want them on the $form_state:
     if (isset($form['#title'])) {
-      $form_state['title'] = $form['#title'];
+      $form_state->set('title', $form['#title']);
     }
     if (isset($form['#section'])) {
-      $form_state['#section'] = $form['#section'];
+      $form_state->set('#section', $form['#section']);
     }
     // Finally, we never want these cached -- our object cache does that for us.
     $form['#no_cache'] = TRUE;
@@ -413,7 +401,7 @@ public function getOverrideValues($form, FormStateInterface $form_state) {
       if ($was_defaulted !== $is_defaulted && isset($form['#section'])) {
         // We're changing which display these values apply to.
         // Update the #section so it knows what to mark changed.
-        $form['#section'] = str_replace('default-', $form_state['display_id'] . '-', $form['#section']);
+        $form['#section'] = str_replace('default-', $form_state->get('display_id') . '-', $form['#section']);
       }
     }
     else {
@@ -477,16 +465,17 @@ public function addFormToStack($key, $display_id, $type, $id = NULL, $top = FALS
    * Submit handler for adding new item(s) to a view.
    */
   public function submitItemAdd($form, FormStateInterface $form_state) {
-    $type = $form_state['type'];
+    $type = $form_state->get('type');
     $types = ViewExecutable::getHandlerTypes();
     $section = $types[$type]['plural'];
+    $display_id = $form_state->get('display_id');
 
     // Handle the override select.
     list($was_defaulted, $is_defaulted) = $this->getOverrideValues($form, $form_state);
     if ($was_defaulted && !$is_defaulted) {
       // We were using the default display's values, but we're now overriding
       // the default display and saving values specific to this display.
-      $display = &$this->executable->displayHandlers->get($form_state['display_id']);
+      $display = &$this->executable->displayHandlers->get($display_id);
       // setOverride toggles the override of this section.
       $display->setOverride($section);
     }
@@ -495,7 +484,7 @@ public function submitItemAdd($form, FormStateInterface $form_state) {
       // to go back to the default display.
       // Overwrite the default display with the current form values, and make
       // the current display use the new default values.
-      $display = &$this->executable->displayHandlers->get($form_state['display_id']);
+      $display = &$this->executable->displayHandlers->get($display_id);
       // optionsOverride toggles the override of this section.
       $display->setOverride($section);
     }
@@ -508,7 +497,7 @@ public function submitItemAdd($form, FormStateInterface $form_state) {
         if ($cut = strpos($field, '$')) {
           $field = substr($field, 0, $cut);
         }
-        $id = $this->executable->addHandler($form_state['display_id'], $type, $table, $field);
+        $id = $this->executable->addHandler($display_id, $type, $table, $field);
 
         // check to see if we have group by settings
         $key = $type;
@@ -522,15 +511,15 @@ public function submitItemAdd($form, FormStateInterface $form_state) {
         );
         $handler = Views::handlerManager($key)->getHandler($item);
         if ($this->executable->displayHandlers->get('default')->useGroupBy() && $handler->usesGroupBy()) {
-          $this->addFormToStack('handler-group', $form_state['display_id'], $type, $id);
+          $this->addFormToStack('handler-group', $display_id, $type, $id);
         }
 
         // check to see if this type has settings, if so add the settings form first
         if ($handler && $handler->hasExtraOptions()) {
-          $this->addFormToStack('handler-extra', $form_state['display_id'], $type, $id);
+          $this->addFormToStack('handler-extra', $display_id, $type, $id);
         }
         // Then add the form to the stack
-        $this->addFormToStack('handler', $form_state['display_id'], $type, $id);
+        $this->addFormToStack('handler', $display_id, $type, $id);
       }
     }
 
diff --git a/core/tests/Drupal/Tests/Core/Form/FormBuilderTest.php b/core/tests/Drupal/Tests/Core/Form/FormBuilderTest.php
index b680217..9e36fcc 100644
--- a/core/tests/Drupal/Tests/Core/Form/FormBuilderTest.php
+++ b/core/tests/Drupal/Tests/Core/Form/FormBuilderTest.php
@@ -46,7 +46,7 @@ public function testGetFormIdWithClassName() {
     $form_id = $this->formBuilder->getFormId($form_arg, $form_state);
 
     $this->assertSame('test_form', $form_id);
-    $this->assertSame($form_arg, get_class($form_state['build_info']['callback_object']));
+    $this->assertSame($form_arg, get_class($form_state->getFormObject()));
   }
 
   /**
@@ -62,7 +62,7 @@ public function testGetFormIdWithInjectedClassName() {
     $form_id = $this->formBuilder->getFormId($form_arg, $form_state);
 
     $this->assertSame('test_form', $form_id);
-    $this->assertSame($form_arg, get_class($form_state['build_info']['callback_object']));
+    $this->assertSame($form_arg, get_class($form_state->getFormObject()));
   }
 
   /**
@@ -77,7 +77,7 @@ public function testGetFormIdWithObject() {
     $form_id = $this->formBuilder->getFormId($form_arg, $form_state);
 
     $this->assertSame($expected_form_id, $form_id);
-    $this->assertSame($form_arg, $form_state['build_info']['callback_object']);
+    $this->assertSame($form_arg, $form_state->getFormObject());
   }
 
   /**
@@ -99,8 +99,8 @@ public function testGetFormIdWithBaseForm() {
     $form_id = $this->formBuilder->getFormId($form_arg, $form_state);
 
     $this->assertSame($expected_form_id, $form_id);
-    $this->assertSame($form_arg, $form_state['build_info']['callback_object']);
-    $this->assertSame($base_form_id, $form_state['build_info']['base_form_id']);
+    $this->assertSame($form_arg, $form_state->getFormObject());
+    $this->assertSame($base_form_id, $form_state->getBuildInfo()['base_form_id']);
   }
 
   /**
@@ -123,7 +123,7 @@ public function testHandleFormStateResponse($class, $form_state_key) {
     $form_arg->expects($this->any())
       ->method('submitForm')
       ->will($this->returnCallback(function ($form, FormStateInterface $form_state) use ($response, $form_state_key) {
-        $form_state->set($form_state_key, $response);
+        $form_state->setFormState([$form_state_key => $response]);
       }));
 
     $form_state = new FormState();
@@ -136,7 +136,7 @@ public function testHandleFormStateResponse($class, $form_state_key) {
     catch (\Exception $e) {
       $this->assertSame('exit', $e->getMessage());
     }
-    $this->assertInstanceOf('Symfony\Component\HttpFoundation\Response', $form_state->get('response'));
+    $this->assertInstanceOf('Symfony\Component\HttpFoundation\Response', $form_state->getResponse());
   }
 
   /**
@@ -190,7 +190,7 @@ public function testHandleRedirectWithResponse() {
     catch (\Exception $e) {
       $this->assertSame('exit', $e->getMessage());
     }
-    $this->assertSame($response, $form_state->get('response'));
+    $this->assertSame($response, $form_state->getResponse());
   }
 
   /**
@@ -279,7 +279,7 @@ public function testBuildFormWithObject() {
     $form_state = new FormState();
     $form = $this->formBuilder->buildForm($form_arg, $form_state);
     $this->assertFormElement($expected_form, $form, 'test');
-    $this->assertSame($form_id, $form_state['build_info']['form_id']);
+    $this->assertSame($form_id, $form_state->getBuildInfo()['form_id']);
     $this->assertArrayHasKey('#id', $form);
   }
 
@@ -305,15 +305,15 @@ public function testRebuildForm() {
     $original_build_id = $form['#build_id'];
 
     // Rebuild the form, and assert that the build ID has not changed.
-    $form_state['rebuild'] = TRUE;
+    $form_state->setRebuild();
     $input['form_id'] = $form_id;
     $form_state->setUserInput($input);
-    $form_state['rebuild_info']['copy']['#build_id'] = TRUE;
+    $form_state->addRebuildInfo('copy', ['#build_id' => TRUE]);
     $this->formBuilder->processForm($form_id, $form, $form_state);
     $this->assertSame($original_build_id, $form['#build_id']);
 
     // Rebuild the form again, and assert that there is a new build ID.
-    $form_state['rebuild_info'] = array();
+    $form_state->setRebuildInfo([]);
     $form = $this->formBuilder->buildForm($form_arg, $form_state);
     $this->assertNotSame($original_build_id, $form['#build_id']);
   }
@@ -337,10 +337,9 @@ public function testGetCache() {
       ->will($this->returnValue($expected_form));
 
     // Do an initial build of the form and track the build ID.
-    $form_state = new FormState();
-    $form_state['build_info']['args'] = array();
-    $form_state['build_info']['files'] = array(array('module' => 'node', 'type' => 'pages.inc'));
-    $form_state['cache'] = TRUE;
+    $form_state = (new FormState())
+      ->addBuildInfo('files', [['module' => 'node', 'type' => 'pages.inc']])
+      ->setCached();
     $form = $this->formBuilder->buildForm($form_arg, $form_state);
 
     $cached_form = $form;
@@ -353,12 +352,12 @@ public function testGetCache() {
 
     // The final form build will not trigger any actual form building, but will
     // use the form cache.
-    $form_state['executed'] = TRUE;
+    $form_state->setExecuted();
     $input['form_id'] = $form_id;
     $input['form_build_id'] = $form['#build_id'];
     $form_state->setUserInput($input);
     $this->formBuilder->buildForm($form_arg, $form_state);
-    $this->assertEmpty($form_state['errors']);
+    $this->assertEmpty($form_state->getErrors());
   }
 
   /**
diff --git a/core/tests/Drupal/Tests/Core/Form/FormCacheTest.php b/core/tests/Drupal/Tests/Core/Form/FormCacheTest.php
index 6d668b2..d08557a 100644
--- a/core/tests/Drupal/Tests/Core/Form/FormCacheTest.php
+++ b/core/tests/Drupal/Tests/Core/Form/FormCacheTest.php
@@ -235,7 +235,7 @@ public function testLoadCachedFormState() {
       ->willReturn($cached_form_state);
 
     $this->formCache->getCache($form_build_id, $form_state);
-    $this->assertSame($cached_form_state['storage'], $form_state['storage']);
+    $this->assertSame($cached_form_state['storage'], $form_state->getStorage());
   }
 
   /**
diff --git a/core/tests/Drupal/Tests/Core/Form/FormStateTest.php b/core/tests/Drupal/Tests/Core/Form/FormStateTest.php
index 0697b2d..fe3d32e 100644
--- a/core/tests/Drupal/Tests/Core/Form/FormStateTest.php
+++ b/core/tests/Drupal/Tests/Core/Form/FormStateTest.php
@@ -29,7 +29,7 @@ class FormStateTest extends UnitTestCase {
    * @dataProvider providerTestGetRedirect
    */
   public function testGetRedirect($form_state_additions, $expected) {
-    $form_state = new FormState($form_state_additions);
+    $form_state = (new FormState())->setFormState($form_state_additions);
     $redirect = $form_state->getRedirect();
     $this->assertEquals($expected, $redirect);
   }
@@ -82,9 +82,9 @@ public function testSetError() {
    */
   public function testGetError($errors, $parents, $error = NULL) {
     $element['#parents'] = $parents;
-    $form_state = new FormState(array(
+    $form_state = (new FormState())->setFormState([
       'errors' => $errors,
-    ));
+    ]);
     $this->assertSame($error, $form_state->getError($element));
   }
 
@@ -110,9 +110,9 @@ public function providerTestGetError() {
    */
   public function testSetErrorByName($limit_validation_errors, $expected_errors, $set_message = FALSE) {
     $form_state = $this->getMockBuilder('Drupal\Core\Form\FormState')
-      ->setConstructorArgs(array(array('limit_validation_errors' => $limit_validation_errors)))
       ->setMethods(array('drupalSetMessage'))
       ->getMock();
+    $form_state->setLimitValidationErrors($limit_validation_errors);
     $form_state->clearErrors();
     $form_state->expects($set_message ? $this->once() : $this->never())
       ->method('drupalSetMessage');
@@ -122,7 +122,7 @@ public function testSetErrorByName($limit_validation_errors, $expected_errors, $
     $form_state->setErrorByName('options');
 
     $this->assertSame(!empty($expected_errors), $form_state::hasAnyErrors());
-    $this->assertSame($expected_errors, $form_state['errors']);
+    $this->assertSame($expected_errors, $form_state->getErrors());
   }
 
   public function providerTestSetErrorByName() {
@@ -147,9 +147,9 @@ public function providerTestSetErrorByName() {
    */
   public function testFormErrorsDuringSubmission() {
     $form_state = $this->getMockBuilder('Drupal\Core\Form\FormState')
-      ->setConstructorArgs(array(array('validation_complete' => TRUE)))
       ->setMethods(array('drupalSetMessage'))
       ->getMock();
+    $form_state->setValidationComplete();
     $form_state->setErrorByName('test', 'message');
   }
 
@@ -183,12 +183,12 @@ public function testSetValueForElement() {
    * @dataProvider providerTestGetValue
    */
   public function testGetValue($key, $expected, $default = NULL) {
-    $form_state = new FormState(array('values' => array(
+    $form_state = (new FormState())->setValues([
       'foo' => 'one',
       'bar' => array(
         'baz' => 'two',
       ),
-    )));
+    ]);
     $this->assertSame($expected, $form_state->getValue($key, $default));
   }
 
@@ -215,7 +215,9 @@ public function providerTestGetValue() {
    * @dataProvider providerTestSetValue
    */
   public function testSetValue($key, $value, $expected) {
-    $form_state = new FormState(array('values' => array('bar' => 'wrong')));
+    $form_state = (new FormState())->setValues([
+      'bar' => 'wrong',
+    ]);
     $form_state->setValue($key, $value);
     $this->assertSame($expected, $form_state->getValues());
   }
@@ -225,9 +227,9 @@ public function testSetValue($key, $value, $expected) {
    */
   public function testPrepareCallbackValidMethod() {
     $form_state = new FormState();
-    $form_state['build_info']['callback_object'] = new PrepareCallbackTestForm();
+    $form_state->setFormObject(new PrepareCallbackTestForm());
     $processed_callback = $form_state->prepareCallback('::buildForm');
-    $this->assertEquals(array($form_state['build_info']['callback_object'], 'buildForm'), $processed_callback);
+    $this->assertEquals([$form_state->getFormObject(), 'buildForm'], $processed_callback);
   }
 
   /**
@@ -235,7 +237,7 @@ public function testPrepareCallbackValidMethod() {
    */
   public function testPrepareCallbackInValidMethod() {
     $form_state = new FormState();
-    $form_state['build_info']['callback_object'] = new PrepareCallbackTestForm();
+    $form_state->setFormObject(new PrepareCallbackTestForm());
     $processed_callback = $form_state->prepareCallback('not_a_method');
     // The callback was not changed as no such method exists.
     $this->assertEquals('not_a_method', $processed_callback);
@@ -246,8 +248,8 @@ public function testPrepareCallbackInValidMethod() {
    */
   public function testPrepareCallbackArray() {
     $form_state = new FormState();
-    $form_state['build_info']['callback_object'] = new PrepareCallbackTestForm();
-    $callback = array($form_state['build_info']['callback_object'], 'buildForm');
+    $form_state->setFormObject(new PrepareCallbackTestForm());
+    $callback = [$form_state->getFormObject(), 'buildForm'];
     $processed_callback = $form_state->prepareCallback($callback);
     $this->assertEquals($callback, $processed_callback);
   }
@@ -272,7 +274,7 @@ public function providerTestSetValue() {
    * @dataProvider providerTestHasValue
    */
   public function testHasValue($key, $expected) {
-    $form_state = new FormState(array('values' => array(
+    $form_state = (new FormState())->setValues([
       'foo' => 'one',
       'bar' => array(
         'baz' => 'two',
@@ -280,7 +282,7 @@ public function testHasValue($key, $expected) {
       'true' => TRUE,
       'false' => FALSE,
       'null' => NULL,
-    )));
+    ]);
     $this->assertSame($expected, $form_state->hasValue($key));
   }
 
@@ -313,7 +315,7 @@ public function providerTestHasValue() {
    * @dataProvider providerTestIsValueEmpty
    */
   public function testIsValueEmpty($key, $expected) {
-    $form_state = new FormState(array('values' => array(
+    $form_state = (new FormState())->setValues([
       'foo' => 'one',
       'bar' => array(
         'baz' => 'two',
@@ -321,7 +323,7 @@ public function testIsValueEmpty($key, $expected) {
       'true' => TRUE,
       'false' => FALSE,
       'null' => NULL,
-    )));
+    ]);
     $this->assertSame($expected, $form_state->isValueEmpty($key));
   }
 
@@ -405,22 +407,73 @@ public function testLoadIncludeAlreadyLoaded() {
     $module = 'some_module';
     $name = 'some_name';
     $form_state = $this->getMockBuilder('Drupal\Core\Form\FormState')
-      ->setConstructorArgs([['build_info' => ['files' => [
-        'some_module:some_name.some_type' => [
-          'type' => $type,
-          'module' => $module,
-          'name' => $name,
-        ],
-      ]]]])
       ->setMethods(array('moduleLoadInclude'))
       ->getMock();
 
+    $form_state->addBuildInfo('files', [
+      'some_module:some_name.some_type' => [
+        'type' => $type,
+        'module' => $module,
+        'name' => $name,
+      ],
+    ]);
     $form_state->expects($this->never())
       ->method('moduleLoadInclude');
 
     $this->assertFalse($form_state->loadInclude($module, $type, $name));
   }
 
+  /**
+   * @covers ::isCached
+   *
+   * @dataProvider providerTestIsCached
+   */
+  public function testIsCached($cache_key, $no_cache_key, $expected) {
+    $form_state = (new FormState())->setFormState([
+      'cache' => $cache_key,
+      'no_cache' => $no_cache_key,
+    ]);
+    $this->assertSame($expected, $form_state->isCached());
+  }
+
+  /**
+   * Provides test data for testIsCached().
+   */
+  public function providerTestIsCached() {
+    $data = [];
+    $data[] = [
+      TRUE,
+      TRUE,
+      FALSE,
+    ];
+    $data[] = [
+      FALSE,
+      TRUE,
+      FALSE,
+    ];
+    $data[] = [
+      FALSE,
+      FALSE,
+      FALSE,
+    ];
+    $data[] = [
+      TRUE,
+      FALSE,
+      TRUE,
+    ];
+    $data[] = [
+      TRUE,
+      NULL,
+      TRUE,
+    ];
+    $data[] = [
+      FALSE,
+      NULL,
+      FALSE,
+    ];
+    return $data;
+  }
+
 }
 
 /**
diff --git a/core/tests/Drupal/Tests/Core/Form/FormSubmitterTest.php b/core/tests/Drupal/Tests/Core/Form/FormSubmitterTest.php
index e1c9462..ff2b2cd 100644
--- a/core/tests/Drupal/Tests/Core/Form/FormSubmitterTest.php
+++ b/core/tests/Drupal/Tests/Core/Form/FormSubmitterTest.php
@@ -45,7 +45,7 @@ public function testHandleFormSubmissionNotSubmitted() {
     $form_state = new FormState();
 
     $return = $form_submitter->doSubmitForm($form, $form_state);
-    $this->assertFalse($form_state['executed']);
+    $this->assertFalse($form_state->isExecuted());
     $this->assertNull($return);
   }
 
@@ -55,13 +55,12 @@ public function testHandleFormSubmissionNotSubmitted() {
   public function testHandleFormSubmissionNoRedirect() {
     $form_submitter = $this->getFormSubmitter();
     $form = array();
-    $form_state = new FormState(array(
-      'submitted' => TRUE,
-      'no_redirect' => TRUE,
-    ));
+    $form_state = (new FormState())
+      ->setSubmitted()
+      ->disableRedirect();
 
     $return = $form_submitter->doSubmitForm($form, $form_state);
-    $this->assertTrue($form_state['executed']);
+    $this->assertTrue($form_state->isExecuted());
     $this->assertNull($return);
   }
 
@@ -78,10 +77,9 @@ public function testHandleFormSubmissionWithResponses($class, $form_state_key) {
       ->method('prepare')
       ->will($this->returnValue($response));
 
-    $form_state = new FormState(array(
-      'submitted' => TRUE,
-      $form_state_key => $response,
-    ));
+    $form_state = (new FormState())
+      ->setSubmitted()
+      ->setFormState([$form_state_key => $response]);
 
     $form_submitter = $this->getFormSubmitter();
     $form = array();
@@ -202,7 +200,7 @@ public function testRedirectWithoutResult() {
    */
   public function testExecuteSubmitHandlers() {
     $form_submitter = $this->getFormSubmitter();
-    $mock = $this->getMock('stdClass', array('submit_handler', 'hash_submit', 'simple_string_submit'));
+    $mock = $this->getMockForAbstractClass('Drupal\Core\Form\FormBase', [], '', TRUE, TRUE, TRUE, ['submit_handler', 'hash_submit', 'simple_string_submit']);
     $mock->expects($this->once())
       ->method('submit_handler')
       ->with($this->isType('array'), $this->isInstanceOf('Drupal\Core\Form\FormStateInterface'));
@@ -221,13 +219,13 @@ public function testExecuteSubmitHandlers() {
     $form_submitter->executeSubmitHandlers($form, $form_state);
 
     // $form_state submit handlers will supersede $form handlers.
-    $form_state['submit_handlers'][] = array($mock, 'submit_handler');
+    $form_state->setSubmitHandlers([[$mock, 'submit_handler']]);
     $form_submitter->executeSubmitHandlers($form, $form_state);
 
     // Methods directly on the form object can be specified as a string.
-    $form_state = new FormState();
-    $form_state['build_info']['callback_object'] = $mock;
-    $form_state['submit_handlers'][] = '::simple_string_submit';
+    $form_state = (new FormState())
+      ->setFormObject($mock)
+      ->setSubmitHandlers(['::simple_string_submit']);
     $form_submitter->executeSubmitHandlers($form, $form_state);
   }
 
diff --git a/core/tests/Drupal/Tests/Core/Form/FormTestBase.php b/core/tests/Drupal/Tests/Core/Form/FormTestBase.php
index f3a9443..47b80b1 100644
--- a/core/tests/Drupal/Tests/Core/Form/FormTestBase.php
+++ b/core/tests/Drupal/Tests/Core/Form/FormTestBase.php
@@ -215,23 +215,20 @@ protected function getMockForm($form_id, $expected_form = NULL, $count = 1) {
    * @param \Drupal\Core\Form\FormStateInterface $form_state
    *   The current state of the form.
    * @param bool $programmed
-   *   Whether $form_state['programmed'] should be set to TRUE or not. If it is
-   *   not set to TRUE, you must provide additional data in $form_state for the
-   *   submission to take place.
+   *   Whether $form_state->setProgrammed() should be passed TRUE or not. If it
+   *   is not set to TRUE, you must provide additional data in $form_state for
+   *   the submission to take place.
    *
    * @return array
    *   The built form.
    */
   protected function simulateFormSubmission($form_id, FormInterface $form_arg, FormStateInterface $form_state, $programmed = TRUE) {
-    $form_state['build_info']['callback_object'] = $form_arg;
-    $form_state['build_info']['args'] = array();
-
     $input = $form_state->getUserInput();
     $input['op'] = 'Submit';
-    $form_state->setUserInput($input);
-
-    $form_state['programmed'] = $programmed;
-    $form_state['submitted'] = TRUE;
+    $form_state
+      ->setUserInput($input)
+      ->setProgrammed($programmed)
+      ->setSubmitted();
     return $this->formBuilder->buildForm($form_arg, $form_state);
   }
 
diff --git a/core/tests/Drupal/Tests/Core/Form/FormValidatorTest.php b/core/tests/Drupal/Tests/Core/Form/FormValidatorTest.php
index bc6e081..cc49c2c 100644
--- a/core/tests/Drupal/Tests/Core/Form/FormValidatorTest.php
+++ b/core/tests/Drupal/Tests/Core/Form/FormValidatorTest.php
@@ -33,9 +33,9 @@ public function testValidationComplete() {
 
     $form = array();
     $form_state = new FormState();
-    $this->assertFalse($form_state['validation_complete']);
+    $this->assertFalse($form_state->isValidationComplete());
     $form_validator->validateForm('test_form_id', $form, $form_state);
-    $this->assertTrue($form_state['validation_complete']);
+    $this->assertTrue($form_state->isValidationComplete());
   }
 
   /**
@@ -52,8 +52,8 @@ public function testPreventDuplicateValidation() {
       ->method('doValidateForm');
 
     $form = array();
-    $form_state = new FormState();
-    $form_state['validation_complete'] = TRUE;
+    $form_state = (new FormState())
+      ->setValidationComplete();
     $form_validator->validateForm('test_form_id', $form, $form_state);
     $this->assertArrayNotHasKey('#errors', $form);
   }
@@ -72,9 +72,9 @@ public function testMustValidate() {
       ->method('doValidateForm');
 
     $form = array();
-    $form_state = new FormState();
-    $form_state['validation_complete'] = TRUE;
-    $form_state['must_validate'] = TRUE;
+    $form_state = (new FormState())
+      ->setValidationComplete()
+      ->setValidationEnforced();
     $form_validator->validateForm('test_form_id', $form, $form_state);
     $this->assertArrayHasKey('#errors', $form);
   }
@@ -110,7 +110,7 @@ public function testValidateInvalidFormToken() {
       ->with('form_token', 'The form has become outdated. Copy any unsaved work in the form below and then <a href="/test/example?foo=bar">reload this page</a>.');
     $form_state->setValue('form_token', 'some_random_token');
     $form_validator->validateForm('test_form_id', $form, $form_state);
-    $this->assertTrue($form_state['validation_complete']);
+    $this->assertTrue($form_state->isValidationComplete());
   }
 
   /**
@@ -141,7 +141,7 @@ public function testValidateValidFormToken() {
       ->method('setErrorByName');
     $form_state->setValue('form_token', 'some_random_token');
     $form_validator->validateForm('test_form_id', $form, $form_state);
-    $this->assertTrue($form_state['validation_complete']);
+    $this->assertTrue($form_state->isValidationComplete());
   }
 
   /**
@@ -182,10 +182,9 @@ public function testHandleErrorsWithLimitedValidation($sections, $triggering_ele
 
     $triggering_element['#limit_validation_errors'] = $sections;
     $form = array();
-    $form_state = new FormState(array(
-      'values' => $values,
-      'triggering_element' => $triggering_element,
-    ));
+    $form_state = (new FormState())
+      ->setValues($values)
+      ->setTriggeringElement($triggering_element);
 
     $form_validator->validateForm('test_form_id', $form, $form_state);
     $this->assertSame($expected, $form_state->getValues());
@@ -292,7 +291,8 @@ public function testExecuteValidateHandlers() {
     $form_validator->executeValidateHandlers($form, $form_state);
 
     // $form_state validate handlers will supersede $form handlers.
-    $form_state['validate_handlers'][] = array($mock, 'validate_handler');
+    $validate_handlers[] = [$mock, 'validate_handler'];
+    $form_state->setValidateHandlers($validate_handlers);
     $form_validator->executeValidateHandlers($form, $form_state);
   }
 
