diff --git a/core/lib/Drupal/Component/Plugin/DefaultPluginBag.php b/core/lib/Drupal/Component/Plugin/DefaultPluginBag.php
index 9f9d566..1e54c45 100644
--- a/core/lib/Drupal/Component/Plugin/DefaultPluginBag.php
+++ b/core/lib/Drupal/Component/Plugin/DefaultPluginBag.php
@@ -133,4 +133,12 @@ public function setConfiguration($instance_id, array $configuration) {
     }
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function removeInstanceID($instance_id) {
+    parent::removeInstanceID($instance_id);
+    unset($this->configurations[$instance_id]);
+  }
+
 }
diff --git a/core/lib/Drupal/Component/Plugin/PluginBag.php b/core/lib/Drupal/Component/Plugin/PluginBag.php
index e37b367..bb516ac 100644
--- a/core/lib/Drupal/Component/Plugin/PluginBag.php
+++ b/core/lib/Drupal/Component/Plugin/PluginBag.php
@@ -101,7 +101,9 @@ public function remove($instance_id) {
    *   The ID of the plugin instance to add.
    */
   public function addInstanceID($id) {
-    $this->instanceIDs[$id] = $id;
+    if (!isset($this->instanceIDs[$id])) {
+      $this->instanceIDs[$id] = $id;
+    }
   }
 
   /**
@@ -125,6 +127,17 @@ public function setInstanceIDs(array $instance_ids) {
   }
 
   /**
+   * Removes an instance ID.
+   *
+   * @param string $instance_id
+   *   An image effect instance IDs.
+   */
+  public function removeInstanceID($instance_id) {
+    unset($this->instanceIDs[$instance_id]);
+    $this->remove($instance_id);
+  }
+
+  /**
    * Implements \Iterator::current().
    */
   public function current() {
diff --git a/core/modules/filter/lib/Drupal/filter/FilterBag.php b/core/modules/filter/lib/Drupal/filter/FilterBag.php
index 599625c..0d521c6 100644
--- a/core/modules/filter/lib/Drupal/filter/FilterBag.php
+++ b/core/modules/filter/lib/Drupal/filter/FilterBag.php
@@ -7,10 +7,8 @@
 
 namespace Drupal\filter;
 
-use Drupal\Component\Plugin\Exception\PluginException;
 use Drupal\Component\Utility\NestedArray;
 use Drupal\Component\Plugin\DefaultPluginBag;
-use Drupal\Component\Utility\String;
 
 /**
  * A collection of filters.
@@ -48,6 +46,8 @@ public function getAll() {
     // Retrieve all available filter plugin definitions.
     if (!$this->definitions) {
       $this->definitions = $this->manager->getDefinitions();
+      // Do not allow the null filter to be used directly, only as a fallback.
+      unset($this->definitions['filter_null']);
     }
 
     // Ensure that there is an instance of all available filters.
@@ -68,25 +68,17 @@ public function getAll() {
   protected function initializePlugin($instance_id) {
     // Filters have a 1:1 relationship to text formats and can be added and
     // instantiated at any time.
-    $definition = $this->manager->getDefinition($instance_id);
-
-    if (isset($definition)) {
-      // $configuration is the whole filter plugin instance configuration, as
-      // contained in the text format configuration. The default configuration
-      // is the filter plugin definition.
-      // @todo Configuration should not be contained in definitions. Move into a
-      //   FilterBase::init() method.
-      $configuration = $definition;
-      // Merge the actual configuration into the default configuration.
-      if (isset($this->configurations[$instance_id])) {
-        $configuration = NestedArray::mergeDeep($configuration, $this->configurations[$instance_id]);
-      }
-      $this->configurations[$instance_id] = $configuration;
-      parent::initializePlugin($instance_id);
-    }
-    else {
-      throw new PluginException(String::format("Unknown filter plugin ID '@filter'.", array('@filter' => $instance_id)));
+    // @todo $configuration is the whole filter plugin instance configuration,
+    //   as contained in the text format configuration. The default
+    //   configuration is the filter plugin definition. Configuration should not
+    //   be contained in definitions. Move into a FilterBase::init() method.
+    $configuration = $this->manager->getDefinition($instance_id);
+    // Merge the actual configuration into the default configuration.
+    if (isset($this->configurations[$instance_id])) {
+      $configuration = NestedArray::mergeDeep($configuration, $this->configurations[$instance_id]);
     }
+    $this->configurations[$instance_id] = $configuration;
+    parent::initializePlugin($instance_id);
   }
 
   /**
diff --git a/core/modules/filter/lib/Drupal/filter/FilterFormatFormControllerBase.php b/core/modules/filter/lib/Drupal/filter/FilterFormatFormControllerBase.php
index 98495d5..4b49743 100644
--- a/core/modules/filter/lib/Drupal/filter/FilterFormatFormControllerBase.php
+++ b/core/modules/filter/lib/Drupal/filter/FilterFormatFormControllerBase.php
@@ -12,6 +12,7 @@
 use Drupal\Core\Entity\EntityFormController;
 use Drupal\Core\Entity\Query\QueryFactory;
 use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\filter\Plugin\Filter\FilterNull;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
@@ -115,6 +116,14 @@ public function form(array $form, array &$form_state) {
     // Create filter plugin instances for all available filters, including both
     // enabled/configured ones as well as new and not yet unconfigured ones.
     $filters = $format->filters()->sort();
+    foreach ($filters as $filter_id => $filter) {
+      // If a filter is missing, it will have been replaced by the null filter.
+      // Remove it here, so that saving the form will remove the missing filter.
+      if ($filter instanceof FilterNull) {
+        drupal_set_message(t('The %filter filter is missing, and will be removed once this format is saved.', array('%filter' => $filter_id)), 'warning');
+        $filters->removeInstanceID($filter_id);
+      }
+    }
 
     // Filter status.
     $form['filters']['status'] = array(
diff --git a/core/modules/filter/lib/Drupal/filter/FilterPluginManager.php b/core/modules/filter/lib/Drupal/filter/FilterPluginManager.php
index 3d9b649..0189bc5 100644
--- a/core/modules/filter/lib/Drupal/filter/FilterPluginManager.php
+++ b/core/modules/filter/lib/Drupal/filter/FilterPluginManager.php
@@ -40,4 +40,13 @@ public function __construct(\Traversable $namespaces) {
     $this->factory = new ContainerFactory($this);
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function getDefinition($plugin_id) {
+    $plugins = $this->getDefinitions();
+    // If the requested filter is missing, use the null filter.
+    return isset($plugins[$plugin_id]) ? $plugins[$plugin_id] : $plugins['filter_null'];
+  }
+
 }
diff --git a/core/modules/filter/lib/Drupal/filter/Plugin/Filter/FilterNull.php b/core/modules/filter/lib/Drupal/filter/Plugin/Filter/FilterNull.php
new file mode 100644
index 0000000..8c2e791
--- /dev/null
+++ b/core/modules/filter/lib/Drupal/filter/Plugin/Filter/FilterNull.php
@@ -0,0 +1,67 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\filter\Plugin\Filter\FilterNull.
+ */
+
+namespace Drupal\filter\Plugin\Filter;
+
+use Drupal\filter\Annotation\Filter;
+use Drupal\Core\Annotation\Translation;
+use Drupal\filter\Plugin\FilterBase;
+
+/**
+ * Provides a filter to display no text.
+ *
+ * @Filter(
+ *   id = "filter_null",
+ *   module = "filter",
+ *   title = @Translation("Returns the empty string"),
+ *   type = FILTER_TYPE_HTML_RESTRICTOR,
+ *   weight = -10
+ * )
+ */
+class FilterNull extends FilterBase {
+
+  /**
+   * Tracks if an alert about this filter has been logged.
+   *
+   * @var bool
+   */
+  protected $logged = FALSE;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function __construct(array $configuration, $plugin_id, array $plugin_definition) {
+    // Once per filter, log that a filter plugin was missing.
+    if (!$this->logged) {
+      watchdog('filter', 'Missing filter plugin: %filter.', array('%filter' => $plugin_id), WATCHDOG_ALERT);
+    }
+    parent::__construct($configuration, $plugin_id, $plugin_definition);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function process($text, $langcode, $cache, $cache_id) {
+    return '';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getHTMLRestrictions() {
+    // Nothing is allowed.
+    return array('allowed' => array());
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function tips($long = FALSE) {
+    return t('No HTML tags allowed.');
+  }
+
+}
diff --git a/core/modules/image/lib/Drupal/image/ImageEffectBag.php b/core/modules/image/lib/Drupal/image/ImageEffectBag.php
index 7d0c8eb..0624183 100644
--- a/core/modules/image/lib/Drupal/image/ImageEffectBag.php
+++ b/core/modules/image/lib/Drupal/image/ImageEffectBag.php
@@ -26,17 +26,6 @@ public function &get($instance_id) {
   }
 
   /**
-   * Removes an instance ID.
-   *
-   * @param string $instance_id
-   *   An image effect instance IDs.
-   */
-  public function removeInstanceID($instance_id) {
-    unset($this->instanceIDs[$instance_id], $this->configurations[$instance_id]);
-    $this->remove($instance_id);
-  }
-
-  /**
    * Updates the configuration for an image effect instance.
    *
    * If there is no plugin instance yet, a new will be instantiated. Otherwise,
diff --git a/core/modules/system/lib/Drupal/system/Tests/Upgrade/FilterFormatUpgradePathTest.php b/core/modules/system/lib/Drupal/system/Tests/Upgrade/FilterFormatUpgradePathTest.php
index 1a42c7f..c48cc72 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Upgrade/FilterFormatUpgradePathTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Upgrade/FilterFormatUpgradePathTest.php
@@ -73,6 +73,23 @@ public function testFilterFormatUpgrade() {
     // Check that role 5 cannot access to the defined text formats
     $this->assertNoFieldChecked('edit-5-use-text-format-format-one', 'Use text format format_one permission for role is set correctly.');
     $this->assertNoFieldChecked('edit-5-use-text-format-format-two', 'Use text format format_two permission for role is set correctly.');
+
+    // Check that the format has the missing filter.
+    $two = filter_format_load('format_two');
+    $this->assertTrue($two->filters()->has('missing_filter'));
+
+    // Try to use a filter format with a missing filter, this should not throw
+    // an exception.
+    $empty_markup = check_markup($this->randomName(), 'format_two');
+    $this->assertIdentical($empty_markup, '', 'The filtered text is empty while a filter is missing.');
+
+    // Editing and saving the format should drop the missing filter.
+    $this->drupalGet('admin/config/content/formats/manage/format_two');
+    $this->assertRaw(t('The %filter filter is missing, and will be removed once this format is saved.', array('%filter' => 'missing_filter')));
+    $this->drupalPost(NULL, array(), t('Save configuration'));
+    filter_formats_reset();
+    $two = filter_format_load('format_two');
+    $this->assertFalse($two->filters()->has('missing_filter'));
   }
 
 }
diff --git a/core/modules/system/tests/upgrade/drupal-7.filter_formats.database.php b/core/modules/system/tests/upgrade/drupal-7.filter_formats.database.php
index 402670c..dc64b28 100644
--- a/core/modules/system/tests/upgrade/drupal-7.filter_formats.database.php
+++ b/core/modules/system/tests/upgrade/drupal-7.filter_formats.database.php
@@ -132,6 +132,14 @@
   'status' => '1',
   'settings' => 'a:1:{s:17:"filter_url_length";s:2:"72";}',
 ))
+->values(array(
+  'format' => 'format_two',
+  'module' => 'missing_module',
+  'name' => 'missing_filter',
+  'weight' => '0',
+  'status' => '1',
+  'settings' => 'a:0:{}',
+))
 ->execute();
 
 // Define which roles can use the text formats.
