diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/Tests/Views/TaxonomyIndexTidUiTest.php b/core/modules/taxonomy/lib/Drupal/taxonomy/Tests/Views/TaxonomyIndexTidUiTest.php
index 1075d7b..27f664b 100644
--- a/core/modules/taxonomy/lib/Drupal/taxonomy/Tests/Views/TaxonomyIndexTidUiTest.php
+++ b/core/modules/taxonomy/lib/Drupal/taxonomy/Tests/Views/TaxonomyIndexTidUiTest.php
@@ -93,6 +93,16 @@ public function testFilterUI() {
         $this->assertEqual($terms[$i][$j]->id(), $tid);
       }
     }
+
+    // Ensure the autocomplete input element appears when using the 'textfield'
+    // type.
+    $view = entity_load('view', 'test_filter_taxonomy_index_tid');
+    $display =& $view->getDisplay('default');
+    $display['display_options']['filters']['tid']['type'] = 'textfield';
+    $view->save();
+    $this->drupalGet('admin/structure/views/nojs/config-item/test_filter_taxonomy_index_tid/default/filter/tid');
+    $result = $this->xpath('//input[@id = "edit-options-value-autocomplete"]');
+    $this->assertEqual((string) $result[0]['value'], url('admin/views/ajax/autocomplete/taxonomy/tags'));
   }
 
 }
diff --git a/core/modules/views/includes/ajax.inc b/core/modules/views/includes/ajax.inc
index a662cc8..c3b493e 100644
--- a/core/modules/views/includes/ajax.inc
+++ b/core/modules/views/includes/ajax.inc
@@ -80,53 +80,5 @@ function views_ajax_form_wrapper($form_id, &$form_state) {
 }
 
 /**
- * Page callback for views taxonomy autocomplete.
- *
- * @param $vid
- *   The vocabulary id of the tags which should be returned.
- *
- * @see taxonomy_autocomplete()
- */
-function views_ajax_autocomplete_taxonomy($vid) {
-  // The user enters a comma-separated list of tags. We only autocomplete the
-  // last tag.
-  $tags_typed = Tags::explode(Drupal::request()->query->get('q'));
-  $tag_last = Unicode::strtolower(array_pop($tags_typed));
-  $term_matches = array();
-
-  if ($tag_last != '') {
-    $query = db_select('taxonomy_term_data', 't');
-    $query->addTag('term_access');
-
-    // Do not select already entered terms.
-    if (!empty($tags_typed)) {
-      $query->condition('t.name', $tags_typed, 'NOT IN');
-    }
-    // Select rows that match by term name.
-    $tags_return = $query
-      ->fields('t', array('tid', 'name'))
-      ->condition('t.vid', $vid)
-      ->condition('t.name', '%' . db_like($tag_last) . '%', 'LIKE')
-      ->range(0, 10)
-      ->execute()
-      ->fetchAllKeyed();
-
-    $prefix = count($tags_typed) ? Tags::implode($tags_typed) . ', ' : '';
-
-    foreach ($tags_return as $tid => $name) {
-      $n = $name;
-      // Term names containing commas or quotes must be wrapped in quotes.
-      if (strpos($name, ',') !== FALSE || strpos($name, '"') !== FALSE) {
-        $n = '"' . str_replace('"', '""', $name) . '"';
-      }
-      // Add term name to list of matches.
-      $term_matches[$prefix . $n] = String::checkPlain($name);
-    }
-  }
-
-  return new JsonResponse($term_matches);
-}
-
-/**
  * @}
  */
diff --git a/core/modules/views/lib/Drupal/views/Controller/TagsAutocompleteController.php b/core/modules/views/lib/Drupal/views/Controller/TagsAutocompleteController.php
new file mode 100644
index 0000000..139fe4e
--- /dev/null
+++ b/core/modules/views/lib/Drupal/views/Controller/TagsAutocompleteController.php
@@ -0,0 +1,125 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\views\Controller\TagsAutocompleteController.
+ */
+
+namespace Drupal\views\Controller;
+
+use Drupal\Component\Utility\String;
+use Drupal\Component\Utility\Tags;
+use Drupal\Component\Utility\Unicode;
+use Drupal\Core\Controller\ControllerInterface;
+use Drupal\Core\Entity\EntityManager;
+use Drupal\Core\Entity\Query\QueryFactory;
+use Drupal\taxonomy\VocabularyInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\HttpFoundation\JsonResponse;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * Returns autocomplete responses for taxonomy terms by vocabulary.
+ */
+class TagsAutocompleteController implements ControllerInterface {
+
+  /**
+   * The entity query factory.
+   *
+   * @var \Drupal\Core\Entity\Query\QueryFactory
+   */
+  protected $queryFactory;
+
+  /**
+   * The term storage controller.
+   *
+   * @var \Drupal\taxonomy\TermStorageControllerInterface
+   */
+  protected $termStorageController;
+
+  /**
+   * Constructs a TagsAutocompleteController object.
+   *
+   * @param \Drupal\Core\Entity\Query\QueryFactory $query_factory
+   *   The entity query factory.
+   * @param \Drupal\Core\Entity\EntityManager $entity_manager
+   *   The entity manager.
+   */
+  public function __construct(QueryFactory $query_factory, EntityManager $entity_manager) {
+    $this->queryFactory = $query_factory;
+    $this->termStorageController = $entity_manager->getStorageController('taxonomy_term');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static($container->get('entity.query'), $container->get('plugin.manager.entity'));
+  }
+
+  /**
+   * Retrieves suggestions for taxonomy term autocompletion.
+   *
+   * This function outputs term name suggestions in response to Ajax requests
+   * made by views tags filter. The output is a JSON object of plain-text term
+   * suggestions, keyed by the user-entered value with the completed term name
+   * appended. Term names containing commas are wrapped in quotes.
+   *
+   * For example, suppose the user has entered the string 'red fish, blue' in
+   * the filter, and there are two taxonomy terms, 'blue fish' and 'blue moon'.
+   * The JSON output would have the following structure:
+   * @code
+   *   {
+   *     "red fish, blue fish": "blue fish",
+   *     "red fish, blue moon": "blue moon",
+   *   };
+   * @endcode
+   *
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   *   The request object.
+   * @param \Drupal\taxonomy\VocabularyInterface $taxonomy_vocabulary
+   *   The vocabulary to filter by.
+   *
+   * @return \Symfony\Component\HttpFoundation\JsonResponse
+   *   When the vocabulary is specified, a JSON response containing the
+   *   autocomplete suggestions for taxonomy terms.
+   */
+  public function autocomplete(Request $request, VocabularyInterface $taxonomy_vocabulary) {
+    // The user enters a comma-separated list of tags. We only autocomplete the
+    // last tag.
+    $tags_typed = Tags::explode($request->query->get('q'));
+    $tag_last = Unicode::strtolower(array_pop($tags_typed));
+    $term_matches = array();
+
+    if ($tag_last != '') {
+      // Select rows that match by term name.
+      $query = $this->queryFactory->get('taxonomy_term');
+      $query->addTag('term_access');
+      $query->condition('vid', $taxonomy_vocabulary->id())
+        ->condition('name', $tag_last, 'STARTS_WITH');
+
+      // Do not select already entered terms.
+      if (!empty($tags_typed)) {
+        $query->condition('t.name', $tags_typed, 'NOT IN');
+      }
+
+      $query->range(0, 10);
+      $tags = $this->termStorageController->loadMultiple($query->execute());
+
+      $prefix = count($tags_typed) ? Tags::implode($tags_typed) . ', ' : '';
+
+      foreach ($tags as $term) {
+        $key = $name = $term->name->value;
+        // Term names containing commas or quotes must be wrapped in quotes.
+        if (strpos($name, ',') !== FALSE || strpos($name, '"') !== FALSE) {
+          $key = '"' . str_replace('"', '""', $name) . '"';
+        }
+        // Add term name to list of matches.
+        $term_matches[$prefix . $key] = String::checkPlain($name);
+      }
+    }
+
+    return new JsonResponse($term_matches);
+  }
+
+}
diff --git a/core/modules/views/views.module b/core/modules/views/views.module
index 26a9dbb..a45d29f 100644
--- a/core/modules/views/views.module
+++ b/core/modules/views/views.module
@@ -322,17 +322,6 @@ function views_menu() {
     'route_name' => 'views_ajax',
     'type' => MENU_CALLBACK,
   );
-  // Define another taxonomy autocomplete because the default one of drupal
-  // does not support a vid a argument anymore
-  $items['admin/views/ajax/autocomplete/taxonomy/%'] = array(
-    'page callback' => 'views_ajax_autocomplete_taxonomy',
-    'page arguments' => array(5),
-    'theme callback' => 'ajax_base_page_theme',
-    'access callback' => 'user_access',
-    'access arguments' => array('access content'),
-    'type' => MENU_CALLBACK,
-    'file' => 'includes/ajax.inc',
-  );
   return $items;
 }
 
diff --git a/core/modules/views/views.routing.yml b/core/modules/views/views.routing.yml
index 016b2a5..7648308 100644
--- a/core/modules/views/views.routing.yml
+++ b/core/modules/views/views.routing.yml
@@ -4,3 +4,9 @@ views_ajax:
     _controller: '\Drupal\views\Controller\ViewAjaxController::ajaxView'
   requirements:
     _access: 'TRUE'
+views_taxonomy_autocomplete:
+  pattern: 'admin/views/ajax/autocomplete/taxonomy/{taxonomy_vocabulary}'
+  defaults:
+    _controller: '\Drupal\views\Controller\TagsAutocompleteController::autocomplete'
+  requirements:
+    _permission: 'access content'
