diff --git a/search_api.api.php b/search_api.api.php
index 0a37e32..8f6a413 100644
--- a/search_api.api.php
+++ b/search_api.api.php
@@ -203,7 +203,7 @@ function hook_search_api_views_field_handler_mapping_alter(array &$mapping) {
  */
 function hook_search_api_index_items_alter(\Drupal\search_api\IndexInterface $index, array &$items) {
   foreach ($items as $item_id => $item) {
-    list(, $raw_id) = \Drupal\search_api\Utility::splitCombinedId($item->getId());
+    list(, $raw_id) = \Drupal\search_api\Utility\Utility::splitCombinedId($item->getId());
     if ($raw_id % 5 == 0) {
       unset($items[$item_id]);
     }
diff --git a/search_api.module b/search_api.module
index 2fd2dfc..e06845e 100644
--- a/search_api.module
+++ b/search_api.module
@@ -141,7 +141,7 @@ function search_api_config_import_steps_alter(&$sync_steps, ConfigImporter $conf
   $prefix_length = strlen($prefix);
   foreach ($new_or_changed as $config_id) {
     if (substr($config_id, 0, $prefix_length) === $prefix) {
-      $sync_steps[] = array('Drupal\search_api\Utility', 'processIndexTasks');
+      $sync_steps[] = array('Drupal\search_api\Task\IndexTaskManager', 'processIndexTasks');
     }
   }
 }
diff --git a/search_api.services.yml b/search_api.services.yml
index bb956e7..279b0b0 100644
--- a/search_api.services.yml
+++ b/search_api.services.yml
@@ -50,15 +50,23 @@ services:
     tags:
       - { name: event_subscriber }
 
+  search_api.data_type_helper:
+    class: \Drupal\search_api\Utility\DataTypeHelper
+    arguments: ['@module_handler', '@plugin.manager.search_api.data_type']
+
+  search_api.fields_helper:
+    class: \Drupal\search_api\Utility\FieldsHelper
+    arguments: ['@entity_field.manager', '@entity_type.bundle.info', '@search_api.data_type_helper']
+
   search_api.index_task_manager:
     class: Drupal\search_api\Task\IndexTaskManager
     arguments: ['@search_api.task_manager', '@entity_type.manager']
     tags:
       - { name: event_subscriber }
 
-  search_api.results_static_cache:
-    class: Drupal\search_api\Query\ResultsCache
-    arguments: ['@request_stack']
+  search_api.query_helper:
+    class: Drupal\search_api\Utility\QueryHelper
+    arguments: ['@request_stack', '@module_handler', '@plugin.manager.search_api.parse_mode']
 
   search_api.server_task_manager:
     class: Drupal\search_api\Task\ServerTaskManager
diff --git a/search_api.theme.inc b/search_api.theme.inc
index 01d9892..fcc7886 100644
--- a/search_api.theme.inc
+++ b/search_api.theme.inc
@@ -10,7 +10,7 @@
 use Drupal\Core\Url;
 use Drupal\search_api\Query\QueryInterface;
 use Drupal\search_api\SearchApiException;
-use Drupal\search_api\Utility;
+use Drupal\search_api\Utility\Utility;
 
 /**
  * Returns HTML for a fields form table.
diff --git a/search_api.views.inc b/search_api.views.inc
index 90ba698..c7dc863 100644
--- a/search_api.views.inc
+++ b/search_api.views.inc
@@ -13,7 +13,7 @@
 use Drupal\search_api\Entity\Index;
 use Drupal\search_api\Item\FieldInterface;
 use Drupal\search_api\SearchApiException;
-use Drupal\search_api\Utility;
+use Drupal\search_api\Utility\Utility;
 
 /**
  * Implements hook_views_data().
diff --git a/search_api_db/src/Plugin/search_api/backend/Database.php b/search_api_db/src/Plugin/search_api/backend/Database.php
index 457593c..d167241 100644
--- a/search_api_db/src/Plugin/search_api/backend/Database.php
+++ b/search_api_db/src/Plugin/search_api/backend/Database.php
@@ -28,7 +28,7 @@
 use Drupal\search_api\Query\ConditionGroupInterface;
 use Drupal\search_api\Query\QueryInterface;
 use Drupal\search_api\SearchApiException;
-use Drupal\search_api\Utility;
+use Drupal\search_api\Utility\Utility;
 use Drupal\search_api_db\DatabaseCompatibility\DatabaseCompatibilityHandlerInterface;
 use Drupal\search_api_db\DatabaseCompatibility\GenericDatabase;
 use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -778,7 +778,7 @@ protected function createFieldTable(FieldInterface $field = NULL, $db, $type = '
    *
    * @param string $type
    *   An indexed field's search type. One of the keys from
-   *   \Drupal\search_api\Utility::getDefaultDataTypes().
+   *   \Drupal\search_api\Utility\Utility::getDefaultDataTypes().
    *
    * @return array
    *   Column configurations to use for the field's database column.
diff --git a/src/Backend/BackendPluginBase.php b/src/Backend/BackendPluginBase.php
index 24c01c1..8674d8a 100644
--- a/src/Backend/BackendPluginBase.php
+++ b/src/Backend/BackendPluginBase.php
@@ -9,7 +9,7 @@
 use Drupal\search_api\IndexInterface;
 use Drupal\search_api\Plugin\ConfigurablePluginBase;
 use Drupal\search_api\ServerInterface;
-use Drupal\search_api\Utility;
+use Drupal\search_api\Utility\Utility;
 
 /**
  * Defines a base class for backend plugins.
diff --git a/src/Entity/Index.php b/src/Entity/Index.php
index c9c31d5..2f136a4 100644
--- a/src/Entity/Index.php
+++ b/src/Entity/Index.php
@@ -17,7 +17,7 @@
 use Drupal\search_api\SearchApiException;
 use Drupal\search_api\ServerInterface;
 use Drupal\search_api\Tracker\TrackerInterface;
-use Drupal\search_api\Utility;
+use Drupal\search_api\Utility\Utility;
 use Drupal\user\TempStoreException;
 use Drupal\views\Views;
 use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
diff --git a/src/Form/IndexAddFieldsForm.php b/src/Form/IndexAddFieldsForm.php
index 6c2d37d..a8167dc 100644
--- a/src/Form/IndexAddFieldsForm.php
+++ b/src/Form/IndexAddFieldsForm.php
@@ -16,7 +16,7 @@
 use Drupal\search_api\DataType\DataTypePluginManager;
 use Drupal\search_api\Processor\ConfigurablePropertyInterface;
 use Drupal\search_api\UnsavedConfigurationInterface;
-use Drupal\search_api\Utility;
+use Drupal\search_api\Utility\Utility;
 use Drupal\user\SharedTempStoreFactory;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
diff --git a/src/Form/IndexFieldsForm.php b/src/Form/IndexFieldsForm.php
index 81c91ba..95399d8 100644
--- a/src/Form/IndexFieldsForm.php
+++ b/src/Form/IndexFieldsForm.php
@@ -12,7 +12,7 @@
 use Drupal\search_api\DataType\DataTypePluginManager;
 use Drupal\search_api\Processor\ConfigurablePropertyInterface;
 use Drupal\search_api\UnsavedConfigurationInterface;
-use Drupal\search_api\Utility;
+use Drupal\search_api\Utility\Utility;
 use Drupal\user\SharedTempStoreFactory;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
diff --git a/src/IndexInterface.php b/src/IndexInterface.php
index fed87d6..b949d67 100644
--- a/src/IndexInterface.php
+++ b/src/IndexInterface.php
@@ -72,7 +72,7 @@ public function getOption($name, $default = NULL);
    *   identifiers, the values are arrays for specifying the field settings. The
    *   structure of those arrays looks like this:
    *   - type: The type set for this field. One of the types returned by
-   *     \Drupal\search_api\Utility::getDefaultDataTypes().
+   *     \Drupal\search_api\Utility\Utility::getDefaultDataTypes().
    *   - boost: (optional) A boost value for terms found in this field during
    *     searches. Usually only relevant for fulltext fields. Defaults to 1.0.
    * - processors: An array of all processors available for the index. The keys
diff --git a/src/Item/Field.php b/src/Item/Field.php
index c755d8b..8420b7b 100644
--- a/src/Item/Field.php
+++ b/src/Item/Field.php
@@ -7,7 +7,7 @@
 use Drupal\search_api\IndexInterface;
 use Drupal\search_api\Processor\ConfigurablePropertyInterface;
 use Drupal\search_api\SearchApiException;
-use Drupal\search_api\Utility;
+use Drupal\search_api\Utility\Utility;
 
 /**
  * Represents a field on a search item that can be indexed.
diff --git a/src/Item/Item.php b/src/Item/Item.php
index 719f7e2..e288698 100644
--- a/src/Item/Item.php
+++ b/src/Item/Item.php
@@ -9,7 +9,7 @@
 use Drupal\search_api\Processor\ProcessorPropertyInterface;
 use Drupal\search_api\SearchApiException;
 use Drupal\search_api\IndexInterface;
-use Drupal\search_api\Utility;
+use Drupal\search_api\Utility\Utility;
 
 /**
  * Provides a default implementation for a search item.
diff --git a/src/Plugin/search_api/datasource/ContentEntity.php b/src/Plugin/search_api/datasource/ContentEntity.php
index 59f8490..fbb9108 100644
--- a/src/Plugin/search_api/datasource/ContentEntity.php
+++ b/src/Plugin/search_api/datasource/ContentEntity.php
@@ -26,7 +26,7 @@
 use Drupal\search_api\Plugin\PluginFormTrait;
 use Drupal\search_api\SearchApiException;
 use Drupal\search_api\IndexInterface;
-use Drupal\search_api\Utility;
+use Drupal\search_api\Utility\Utility;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
diff --git a/src/Plugin/search_api/processor/AggregatedFields.php b/src/Plugin/search_api/processor/AggregatedFields.php
index 6e6554e..05c2938 100644
--- a/src/Plugin/search_api/processor/AggregatedFields.php
+++ b/src/Plugin/search_api/processor/AggregatedFields.php
@@ -6,7 +6,7 @@
 use Drupal\search_api\Item\ItemInterface;
 use Drupal\search_api\Plugin\search_api\processor\Property\AggregatedFieldProperty;
 use Drupal\search_api\Processor\ProcessorPluginBase;
-use Drupal\search_api\Utility;
+use Drupal\search_api\Utility\Utility;
 
 /**
  * Adds customized aggregations of existing fields to the index.
diff --git a/src/Plugin/search_api/processor/Highlight.php b/src/Plugin/search_api/processor/Highlight.php
index 6405ee7..54e0da9 100644
--- a/src/Plugin/search_api/processor/Highlight.php
+++ b/src/Plugin/search_api/processor/Highlight.php
@@ -11,7 +11,7 @@
 use Drupal\search_api\Processor\ProcessorPluginBase;
 use Drupal\search_api\Query\QueryInterface;
 use Drupal\search_api\Query\ResultSetInterface;
-use Drupal\search_api\Utility;
+use Drupal\search_api\Utility\Utility;
 
 /**
  * Adds a highlighted excerpt to results and highlights returned fields.
diff --git a/src/Plugin/search_api/processor/HtmlFilter.php b/src/Plugin/search_api/processor/HtmlFilter.php
index 6d109c8..8a936ec 100644
--- a/src/Plugin/search_api/processor/HtmlFilter.php
+++ b/src/Plugin/search_api/processor/HtmlFilter.php
@@ -9,7 +9,7 @@
 use Drupal\search_api\Item\FieldInterface;
 use Drupal\search_api\Plugin\search_api\data_type\value\TextValueInterface;
 use Drupal\search_api\Processor\FieldsProcessorPluginBase;
-use Drupal\search_api\Utility;
+use Drupal\search_api\Utility\Utility;
 use Symfony\Component\Yaml\Dumper;
 use Symfony\Component\Yaml\Exception\ParseException;
 use Symfony\Component\Yaml\Parser;
diff --git a/src/Plugin/search_api/processor/Property/AggregatedFieldProperty.php b/src/Plugin/search_api/processor/Property/AggregatedFieldProperty.php
index 1612537..a5366c5 100644
--- a/src/Plugin/search_api/processor/Property/AggregatedFieldProperty.php
+++ b/src/Plugin/search_api/processor/Property/AggregatedFieldProperty.php
@@ -8,7 +8,7 @@
 use Drupal\search_api\Item\FieldInterface;
 use Drupal\search_api\Processor\ConfigurablePropertyBase;
 use Drupal\search_api\Processor\ConfigurablePropertyInterface;
-use Drupal\search_api\Utility;
+use Drupal\search_api\Utility\Utility;
 
 /**
  * Defines an "aggregated field" property.
diff --git a/src/Plugin/search_api/processor/Stopwords.php b/src/Plugin/search_api/processor/Stopwords.php
index fa05cfd..fb235dd 100644
--- a/src/Plugin/search_api/processor/Stopwords.php
+++ b/src/Plugin/search_api/processor/Stopwords.php
@@ -5,7 +5,7 @@
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\search_api\Processor\FieldsProcessorPluginBase;
 use Drupal\search_api\Query\QueryInterface;
-use Drupal\search_api\Utility;
+use Drupal\search_api\Utility\Utility;
 
 /**
  * Allows you to define stopwords which will be ignored in searches.
diff --git a/src/Plugin/search_api/processor/Tokenizer.php b/src/Plugin/search_api/processor/Tokenizer.php
index 2be40cd..89a3dd1 100644
--- a/src/Plugin/search_api/processor/Tokenizer.php
+++ b/src/Plugin/search_api/processor/Tokenizer.php
@@ -8,7 +8,7 @@
 use Drupal\search_api\Item\FieldInterface;
 use Drupal\search_api\Plugin\search_api\data_type\value\TextValueInterface;
 use Drupal\search_api\Processor\FieldsProcessorPluginBase;
-use Drupal\search_api\Utility;
+use Drupal\search_api\Utility\Utility;
 
 /**
  * Splits text into individual words for searching.
diff --git a/src/Plugin/search_api/tracker/Basic.php b/src/Plugin/search_api/tracker/Basic.php
index 0a74ca1..16e9f20 100644
--- a/src/Plugin/search_api/tracker/Basic.php
+++ b/src/Plugin/search_api/tracker/Basic.php
@@ -4,7 +4,7 @@
 
 use Drupal\Core\Database\Connection;
 use Drupal\search_api\Tracker\TrackerPluginBase;
-use Drupal\search_api\Utility;
+use Drupal\search_api\Utility\Utility;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
diff --git a/src/Plugin/views/cache/SearchApiCache.php b/src/Plugin/views/cache/SearchApiCache.php
index 18c1580..6867bca 100644
--- a/src/Plugin/views/cache/SearchApiCache.php
+++ b/src/Plugin/views/cache/SearchApiCache.php
@@ -66,9 +66,9 @@ public function cacheGet($type) {
         // et al. work.
         /** @var \Drupal\search_api\Query\ResultSetInterface $results */
         $results = $cache->data['search_api results'];
-        /** @var \Drupal\search_api\Query\ResultsCacheInterface $static_results_cache */
-        $static_results_cache = \Drupal::service('search_api.results_static_cache');
-        $static_results_cache->addResults($results);
+        \Drupal::getContainer()
+          ->get('search_api.query_helper')
+          ->addResults($results);
 
         try {
           $this->getQuery()->setSearchApiResults($results);
diff --git a/src/Plugin/views/field/SearchApiEntityField.php b/src/Plugin/views/field/SearchApiEntityField.php
index fa8b790..46a13ef 100644
--- a/src/Plugin/views/field/SearchApiEntityField.php
+++ b/src/Plugin/views/field/SearchApiEntityField.php
@@ -5,7 +5,7 @@
 use Drupal\Core\Form\FormHelper;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\search_api\Plugin\views\EntityFieldRenderer;
-use Drupal\search_api\Utility;
+use Drupal\search_api\Utility\Utility;
 use Drupal\views\Plugin\views\display\DisplayPluginBase;
 use Drupal\views\Plugin\views\field\Field;
 use Drupal\views\Plugin\views\field\MultiItemsFieldHandlerInterface;
diff --git a/src/Plugin/views/field/SearchApiFieldTrait.php b/src/Plugin/views/field/SearchApiFieldTrait.php
index 3397a9e..146a005 100644
--- a/src/Plugin/views/field/SearchApiFieldTrait.php
+++ b/src/Plugin/views/field/SearchApiFieldTrait.php
@@ -8,7 +8,7 @@
 use Drupal\Core\TypedData\DataReferenceInterface;
 use Drupal\Core\TypedData\ListInterface;
 use Drupal\search_api\Plugin\views\SearchApiHandlerTrait;
-use Drupal\search_api\Utility;
+use Drupal\search_api\Utility\Utility;
 use Drupal\views\Plugin\views\field\MultiItemsFieldHandlerInterface;
 use Drupal\views\ResultRow;
 
diff --git a/src/Plugin/views/query/SearchApiQuery.php b/src/Plugin/views/query/SearchApiQuery.php
index 4efe4ed..bebd38f 100644
--- a/src/Plugin/views/query/SearchApiQuery.php
+++ b/src/Plugin/views/query/SearchApiQuery.php
@@ -14,7 +14,7 @@
 use Drupal\search_api\Query\QueryInterface;
 use Drupal\search_api\Query\ResultSetInterface;
 use Drupal\search_api\UncacheableDependencyTrait;
-use Drupal\search_api\Utility;
+use Drupal\search_api\Utility\Utility;
 use Drupal\user\Entity\User;
 use Drupal\views\Plugin\views\display\DisplayPluginBase;
 use Drupal\views\Plugin\views\query\QueryPluginBase;
diff --git a/src/Processor/FieldsProcessorPluginBase.php b/src/Processor/FieldsProcessorPluginBase.php
index 1f31c1e..bbe6c82 100644
--- a/src/Processor/FieldsProcessorPluginBase.php
+++ b/src/Processor/FieldsProcessorPluginBase.php
@@ -7,12 +7,13 @@
 use Drupal\Core\Plugin\PluginFormInterface;
 use Drupal\Core\Render\Element;
 use Drupal\search_api\Item\FieldInterface;
+use Drupal\search_api\Utility\DataTypeHelperInterface;
 use Drupal\search_api\Plugin\PluginFormTrait;
 use Drupal\search_api\Plugin\search_api\data_type\value\TextValueInterface;
 use Drupal\search_api\Query\ConditionGroupInterface;
 use Drupal\search_api\Query\ConditionInterface;
 use Drupal\search_api\Query\QueryInterface;
-use Drupal\search_api\Utility;
+use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
  * Provides a base class for processors that work on individual fields.
@@ -38,12 +39,54 @@
 
   use PluginFormTrait;
 
+  /**
+   * The data type helper.
+   *
+   * @var \Drupal\search_api\Utility\DataTypeHelperInterface|null
+   */
+  protected $dataTypeHelper;
+
   // @todo Add defaultConfiguration() implementation and find a cleaner solution
   //   for all the isset($this->configuration['fields']) checks.
 
   /**
    * {@inheritdoc}
    */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+    /** @var static $processor */
+    $processor = parent::create($container, $configuration, $plugin_id, $plugin_definition);
+
+    $processor->setDataTypeHelper($container->get('search_api.data_type_helper'));
+
+    return $processor;
+  }
+
+  /**
+   * Retrieves the data type helper.
+   *
+   * @return \Drupal\search_api\Utility\DataTypeHelperInterface
+   *   The data type helper.
+   */
+  public function getDataTypeHelper() {
+    return $this->dataTypeHelper ?: \Drupal::service('search_api.data_type_helper');
+  }
+
+  /**
+   * Sets the data type helper.
+   *
+   * @param \Drupal\search_api\Utility\DataTypeHelperInterface $data_type_helper
+   *   The new data type helper.
+   *
+   * @return $this
+   */
+  public function setDataTypeHelper(DataTypeHelperInterface $data_type_helper) {
+    $this->dataTypeHelper = $data_type_helper;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
   public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
     $fields = $this->index->getFields();
     $field_options = array();
@@ -287,7 +330,7 @@ protected function testField($name, FieldInterface $field) {
    *   TRUE if fields of that type should be processed, FALSE otherwise.
    */
   protected function testType($type) {
-    return Utility::isTextType($type, array('text', 'string'));
+    return $this->getDataTypeHelper()->isTextType($type, array('text', 'string'));
   }
 
   /**
diff --git a/src/Processor/ProcessorPluginBase.php b/src/Processor/ProcessorPluginBase.php
index b680255..8f00c62 100644
--- a/src/Processor/ProcessorPluginBase.php
+++ b/src/Processor/ProcessorPluginBase.php
@@ -9,7 +9,7 @@
 use Drupal\search_api\Query\QueryInterface;
 use Drupal\search_api\Query\ResultSetInterface;
 use Drupal\search_api\SearchApiException;
-use Drupal\search_api\Utility;
+use Drupal\search_api\Utility\Utility;
 
 /**
  * Defines a base class from which other processors may extend.
diff --git a/src/Query/Query.php b/src/Query/Query.php
index f6f34d4..6c409bc 100644
--- a/src/Query/Query.php
+++ b/src/Query/Query.php
@@ -3,10 +3,13 @@
 namespace Drupal\search_api\Query;
 
 use Drupal\Core\DependencyInjection\DependencySerializationTrait;
+use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\StringTranslation\StringTranslationTrait;
 use Drupal\search_api\IndexInterface;
 use Drupal\search_api\ParseMode\ParseModeInterface;
+use Drupal\search_api\ParseMode\ParseModePluginManager;
 use Drupal\search_api\SearchApiException;
+use Drupal\search_api\Utility\QueryHelperInterface;
 
 /**
  * Provides a standard implementation for a Search API query.
@@ -43,13 +46,6 @@ class Query implements QueryInterface {
   protected $results;
 
   /**
-   * The result cache service.
-   *
-   * @var \Drupal\search_api\Query\ResultsCacheInterface
-   */
-  protected $resultsCache;
-
-  /**
    * The parse mode to use for fulltext search keys.
    *
    * @var \Drupal\search_api\ParseMode\ParseModeInterface|null
@@ -145,12 +141,31 @@ class Query implements QueryInterface {
   protected $executed = FALSE;
 
   /**
+   * The module handler.
+   *
+   * @var \Drupal\Core\Extension\ModuleHandlerInterface|null
+   */
+  protected $moduleHandler;
+
+  /**
+   * The parse mode manager.
+   *
+   * @var \Drupal\search_api\ParseMode\ParseModePluginManager|null
+   */
+  protected $parseModeManager;
+
+  /**
+   * The result cache service.
+   *
+   * @var \Drupal\search_api\Utility\QueryHelperInterface
+   */
+  protected $queryHelper;
+
+  /**
    * Constructs a Query object.
    *
    * @param \Drupal\search_api\IndexInterface $index
    *   The index the query should be executed on.
-   * @param \Drupal\search_api\Query\ResultsCacheInterface $results_cache
-   *   The results cache that should be used for this query.
    * @param array $options
    *   (optional) Associative array of options configuring this query. See
    *   \Drupal\search_api\Query\QueryInterface::setOption() for a list of
@@ -160,14 +175,13 @@ class Query implements QueryInterface {
    *   Thrown if a search on that index (or with those options) won't be
    *   possible.
    */
-  public function __construct(IndexInterface $index, ResultsCacheInterface $results_cache, array $options = array()) {
+  public function __construct(IndexInterface $index, array $options = array()) {
     if (!$index->status()) {
       $index_label = $index->label();
       throw new SearchApiException("Can't search on index '$index_label' which is disabled.");
     }
     $this->index = $index;
     $this->results = new ResultSet($this);
-    $this->resultsCache = $results_cache;
     $this->options = $options + array(
       'conjunction' => 'AND',
       'search id' => __CLASS__,
@@ -178,8 +192,77 @@ public function __construct(IndexInterface $index, ResultsCacheInterface $result
   /**
    * {@inheritdoc}
    */
-  public static function create(IndexInterface $index, ResultsCacheInterface $results_cache, array $options = array()) {
-    return new static($index, $results_cache, $options);
+  public static function create(IndexInterface $index, array $options = array()) {
+    return new static($index, $options);
+  }
+
+  /**
+   * Retrieves the module handler.
+   *
+   * @return \Drupal\Core\Extension\ModuleHandlerInterface
+   *   The module handler.
+   */
+  public function getModuleHandler() {
+    return $this->moduleHandler ?: \Drupal::moduleHandler();
+  }
+
+  /**
+   * Sets the module handler.
+   *
+   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
+   *   The new module handler.
+   *
+   * @return $this
+   */
+  public function setModuleHandler(ModuleHandlerInterface $module_handler) {
+    $this->moduleHandler = $module_handler;
+    return $this;
+  }
+
+  /**
+   * Retrieves the parse mode manager.
+   *
+   * @return \Drupal\search_api\ParseMode\ParseModePluginManager
+   *   The parse mode manager.
+   */
+  public function getParseModeManager() {
+    return $this->parseModeManager ?: \Drupal::service('plugin.manager.search_api.parse_mode');
+  }
+
+  /**
+   * Sets the parse mode manager.
+   *
+   * @param \Drupal\search_api\ParseMode\ParseModePluginManager $parse_mode_manager
+   *   The new parse mode manager.
+   *
+   * @return $this
+   */
+  public function setParseModeManager(ParseModePluginManager $parse_mode_manager) {
+    $this->parseModeManager = $parse_mode_manager;
+    return $this;
+  }
+
+  /**
+   * Retrieves the query helper.
+   *
+   * @return \Drupal\search_api\Utility\QueryHelperInterface
+   *   The query helper.
+   */
+  public function getQueryHelper() {
+    return $this->queryHelper ?: \Drupal::service('search_api.query_helper');
+  }
+
+  /**
+   * Sets the query helper.
+   *
+   * @param \Drupal\search_api\Utility\QueryHelperInterface $query_helper
+   *   The new query helper.
+   *
+   * @return $this
+   */
+  public function setQueryHelper(QueryHelperInterface $query_helper) {
+    $this->queryHelper = $query_helper;
+    return $this;
   }
 
   /**
@@ -187,10 +270,8 @@ public static function create(IndexInterface $index, ResultsCacheInterface $resu
    */
   public function getParseMode() {
     if (!$this->parseMode) {
-      $this->parseMode = \Drupal::getContainer()
-        ->get('plugin.manager.search_api.parse_mode')
-        ->createInstance('terms')
-        ->setConjunction($this->options['conjunction']);
+      $this->parseMode = $this->getParseModeManager()->createInstance('terms');
+      $this->parseMode->setConjunction($this->options['conjunction']);
     }
     return $this->parseMode;
   }
@@ -384,7 +465,7 @@ public function preExecute() {
       foreach ($this->tags as $tag) {
         $hooks[] = "search_api_query_$tag";
       }
-      \Drupal::moduleHandler()->alter($hooks, $this);
+      $this->getModuleHandler()->alter($hooks, $this);
     }
   }
 
@@ -404,10 +485,10 @@ public function postExecute() {
     foreach ($this->tags as $tag) {
       $hooks[] = "search_api_results_$tag";
     }
-    \Drupal::moduleHandler()->alter($hooks, $this->results);
+    $this->getModuleHandler()->alter($hooks, $this->results);
 
     // Store the results in the static cache.
-    $this->resultsCache->addResults($this->results);
+    $this->getQueryHelper()->addResults($this->results);
   }
 
   /**
diff --git a/src/Query/QueryInterface.php b/src/Query/QueryInterface.php
index fc0db7b..fb8778e 100644
--- a/src/Query/QueryInterface.php
+++ b/src/Query/QueryInterface.php
@@ -2,8 +2,10 @@
 
 namespace Drupal\search_api\Query;
 
+use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\search_api\IndexInterface;
 use Drupal\search_api\ParseMode\ParseModeInterface;
+use Drupal\search_api\ParseMode\ParseModePluginManager;
 
 /**
  * Represents a search query on a Search API index.
@@ -55,8 +57,6 @@
    *
    * @param \Drupal\search_api\IndexInterface $index
    *   The index for which the query should be created.
-   * @param \Drupal\search_api\Query\ResultsCacheInterface $results_cache
-   *   The results cache that should be used for this query.
    * @param array $options
    *   (optional) The options to set for the query.
    *
@@ -67,7 +67,7 @@
    *   Thrown if a search on that index (or with those options) won't be
    *   possible.
    */
-  public static function create(IndexInterface $index, ResultsCacheInterface $results_cache, array $options = array());
+  public static function create(IndexInterface $index, array $options = array());
 
   /**
    * Retrieves the parse mode.
diff --git a/src/Task/IndexTaskManager.php b/src/Task/IndexTaskManager.php
index 3af2d43..1d488c6 100644
--- a/src/Task/IndexTaskManager.php
+++ b/src/Task/IndexTaskManager.php
@@ -3,8 +3,10 @@
 namespace Drupal\search_api\Task;
 
 use Drupal\Component\Render\FormattableMarkup;
+use Drupal\Core\Config\ConfigImporter;
 use Drupal\Core\Entity\EntityTypeManagerInterface;
 use Drupal\Core\StringTranslation\StringTranslationTrait;
+use Drupal\search_api\Entity\Index;
 use Drupal\search_api\IndexInterface;
 use Drupal\search_api\SearchApiException;
 use Symfony\Component\EventDispatcher\EventSubscriberInterface;
@@ -58,6 +60,71 @@ public static function getSubscribedEvents() {
   }
 
   /**
+   * Processes all pending index tasks inside a batch run.
+   *
+   * @param array $context
+   *   The current batch context.
+   * @param \Drupal\Core\Config\ConfigImporter $config_importer
+   *   The config importer.
+   *
+   * @throws \Drupal\search_api\SearchApiException
+   *   Thrown if any error occurred while tracking items.
+   */
+  public static function processIndexTasks(array &$context, ConfigImporter $config_importer) {
+    $index_task_manager = \Drupal::getContainer()
+      ->get('search_api.index_task_manager');
+
+    if (!isset($context['sandbox']['indexes'])) {
+      $context['sandbox']['indexes'] = array();
+
+      $indexes = \Drupal::entityTypeManager()
+        ->getStorage('search_api_index')
+        ->loadByProperties(array(
+          'status' => TRUE,
+        ));
+      $deleted = $config_importer->getUnprocessedConfiguration('delete');
+
+      /** @var \Drupal\search_api\IndexInterface $index */
+      foreach ($indexes as $index_id => $index) {
+        if (!$index_task_manager->isTrackingComplete($index) && !in_array($index->getConfigDependencyName(), $deleted)) {
+          $context['sandbox']['indexes'][] = $index_id;
+        }
+      }
+      $context['sandbox']['total'] = count($context['sandbox']['indexes']);
+      if (!$context['sandbox']['total']) {
+        $context['finished'] = 1;
+        return;
+      }
+    }
+
+    $index_id = array_shift($context['sandbox']['indexes']);
+    $index = Index::load($index_id);
+    try {
+      if (!($index_task_manager->addItemsOnce($index))) {
+        array_unshift($context['sandbox']['indexes'], $index_id);
+      }
+    }
+    catch (SearchApiException $e) {
+      watchdog_exception('search_api', $e);
+    }
+
+    if (empty($context['sandbox']['indexes'])) {
+      $context['finished'] = 1;
+    }
+    else {
+      $finished = $context['sandbox']['total'] - count($context['sandbox']['indexes']);
+      $context['finished'] = $finished / $context['sandbox']['total'];
+      $args = array(
+        '%index' => $index->label(),
+        '@num' => $finished + 1,
+        '@total' => $context['sandbox']['total'],
+      );
+      $context['message'] = \Drupal::translation()
+        ->translate('Tracking items for search index %index (@num of @total)', $args);
+    }
+  }
+
+  /**
    * Tracks items according to the given event.
    *
    * @param \Drupal\search_api\Task\TaskEvent $event
diff --git a/src/Tests/ExampleContentTrait.php b/src/Tests/ExampleContentTrait.php
index 817c8a4..f20988b 100644
--- a/src/Tests/ExampleContentTrait.php
+++ b/src/Tests/ExampleContentTrait.php
@@ -3,7 +3,7 @@
 namespace Drupal\search_api\Tests;
 
 use Drupal\search_api\Entity\Index;
-use Drupal\search_api\Utility;
+use Drupal\search_api\Utility\Utility;
 
 /**
  * Contains helpers to create data that can be used by tests.
diff --git a/src/Tests/IntegrationTest.php b/src/Tests/IntegrationTest.php
index 37b9ff1..8e1a64c 100644
--- a/src/Tests/IntegrationTest.php
+++ b/src/Tests/IntegrationTest.php
@@ -11,7 +11,7 @@
 use Drupal\search_api\Entity\Server;
 use Drupal\search_api\Plugin\search_api\tracker\Basic;
 use Drupal\search_api\SearchApiException;
-use Drupal\search_api\Utility;
+use Drupal\search_api\Utility\Utility;
 use Drupal\search_api_test\Plugin\search_api\tracker\TestTracker;
 use Drupal\search_api_test\PluginTestTrait;
 
diff --git a/src/Tests/ViewsTest.php b/src/Tests/ViewsTest.php
index be2ebe8..af60abf 100644
--- a/src/Tests/ViewsTest.php
+++ b/src/Tests/ViewsTest.php
@@ -6,9 +6,9 @@
 use Drupal\Core\StringTranslation\StringTranslationTrait;
 use Drupal\Core\Url;
 use Drupal\search_api\Entity\Index;
-use Drupal\search_api\Utility;
 use Drupal\simpletest\WebTestBase as SimpletestWebTestBase;
 use Symfony\Component\DependencyInjection\ContainerInterface;
+use Drupal\search_api\Utility\Utility;
 
 /**
  * Tests the Views integration of the Search API.
diff --git a/src/Utility/DataTypeHelper.php b/src/Utility/DataTypeHelper.php
new file mode 100644
index 0000000..0eee4ed
--- /dev/null
+++ b/src/Utility/DataTypeHelper.php
@@ -0,0 +1,165 @@
+<?php
+
+namespace Drupal\search_api\Utility;
+
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\search_api\DataType\DataTypePluginManager;
+use Drupal\search_api\IndexInterface;
+use Drupal\search_api\SearchApiException;
+
+/**
+ * Provides helper methods for dealing with Search API data types.
+ */
+class DataTypeHelper implements DataTypeHelperInterface {
+
+  /**
+   * The module handler.
+   *
+   * @var \Drupal\Core\Extension\ModuleHandlerInterface
+   */
+  protected $moduleHandler;
+
+  /**
+   * The data type plugin manager.
+   *
+   * @var \Drupal\search_api\DataType\DataTypePluginManager
+   */
+  protected $dataTypeManager;
+
+  /**
+   * Cache for the field type mapping.
+   *
+   * @var array|null
+   *
+   * @see getFieldTypeMapping()
+   */
+  protected $fieldTypeMapping;
+
+  /**
+   * Cache for the fallback data type mapping per index.
+   *
+   * @var array
+   *
+   * @see getDataTypeFallbackMapping()
+   */
+  protected $dataTypeFallbackMapping = array();
+
+  /**
+   * Constructs a DataTypeHelper object.
+   *
+   * @param \Drupal\Core\Extension\ModuleHandlerInterface $moduleHandler
+   *   The module handler.
+   * @param \Drupal\search_api\DataType\DataTypePluginManager $dataTypeManager
+   *   The data type plugin manager.
+   */
+  public function __construct(ModuleHandlerInterface $moduleHandler, DataTypePluginManager $dataTypeManager) {
+    $this->moduleHandler = $moduleHandler;
+    $this->dataTypeManager = $dataTypeManager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isTextType($type, array $textTypes = array('text')) {
+    if (in_array($type, $textTypes)) {
+      return TRUE;
+    }
+    $dataType = $this->dataTypeManager->createInstance($type);
+    if ($dataType && !$dataType->isDefault()) {
+      return in_array($dataType->getFallbackType(), $textTypes);
+    }
+    return FALSE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFieldTypeMapping() {
+    // Check the cache first.
+    if (!isset($this->fieldTypeMapping)) {
+      // It's easier to write and understand this array in the form of
+      // $searchApiFieldType => array($dataTypes) and flip it below.
+      $defaultMapping = array(
+        'text' => array(
+          'field_item:string_long.string',
+          'field_item:text_long.string',
+          'field_item:text_with_summary.string',
+          'text',
+        ),
+        'string' => array(
+          'string',
+          'email',
+          'uri',
+          'filter_format',
+          'duration_iso8601',
+          'field_item:path',
+        ),
+        'integer' => array(
+          'integer',
+          'timespan',
+        ),
+        'decimal' => array(
+          'decimal',
+          'float',
+        ),
+        'date' => array(
+          'date',
+          'datetime_iso8601',
+          'timestamp',
+        ),
+        'boolean' => array(
+          'boolean',
+        ),
+        // Types we know about but want/have to ignore.
+        NULL => array(
+          'language',
+        ),
+      );
+
+      foreach ($defaultMapping as $searchApiType => $dataTypes) {
+        foreach ($dataTypes as $dataType) {
+          $mapping[$dataType] = $searchApiType;
+        }
+      }
+
+      // Allow other modules to intercept and define what default type they want
+      // to use for their data type.
+      $this->moduleHandler->alter('search_api_field_type_mapping', $mapping);
+
+      $this->fieldTypeMapping = $mapping;
+    }
+
+    return $this->fieldTypeMapping;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDataTypeFallbackMapping(IndexInterface $index) {
+    // Check the cache first.
+    $indexId = $index->id();
+    if (empty($this->dataTypeFallbackMapping[$indexId])) {
+      $server = NULL;
+      try {
+        $server = $index->getServerInstance();
+      }
+      catch (SearchApiException $e) {
+        // If the server isn't available, just ignore it here and return all
+        // custom types.
+      }
+      $this->dataTypeFallbackMapping[$indexId] = array();
+      $dataTypes = $this->dataTypeManager->getInstances();
+      foreach ($dataTypes as $typeId => $dataType) {
+        // We know for sure that we do not need to fall back for the default
+        // data types as they are always present and are required to be
+        // supported by all backends.
+        if (!$dataType->isDefault() && (!$server || !$server->supportsDataType($typeId))) {
+          $this->dataTypeFallbackMapping[$indexId][$typeId] = $dataType->getFallbackType();
+        }
+      }
+    }
+
+    return $this->dataTypeFallbackMapping[$indexId];
+  }
+
+}
diff --git a/src/Utility/DataTypeHelperInterface.php b/src/Utility/DataTypeHelperInterface.php
new file mode 100644
index 0000000..2cd1d28
--- /dev/null
+++ b/src/Utility/DataTypeHelperInterface.php
@@ -0,0 +1,49 @@
+<?php
+
+namespace Drupal\search_api\Utility;
+
+use Drupal\search_api\IndexInterface;
+
+/**
+ * Provides an interface for implementations of the data type helper service.
+ */
+interface DataTypeHelperInterface {
+
+  /**
+   * Determines whether fields of the given type contain fulltext data.
+   *
+   * @param string $type
+   *   The type to check.
+   * @param string[] $textTypes
+   *   (optional) An array of types to be considered as text.
+   *
+   * @return bool
+   *   TRUE if $type is one of the specified types, FALSE otherwise.
+   */
+  public function isTextType($type, array $textTypes = array('text'));
+
+  /**
+   * Retrieves the mapping for known data types to Search API's internal types.
+   *
+   * @return string[]
+   *   An array mapping all known (and supported) Drupal data types to their
+   *   corresponding Search API data types. Empty values mean that fields of
+   *   that type should be ignored by the Search API.
+   *
+   * @see hook_search_api_field_type_mapping_alter()
+   */
+  public function getFieldTypeMapping();
+
+  /**
+   * Retrieves the necessary type fallbacks for an index.
+   *
+   * @param \Drupal\search_api\IndexInterface $index
+   *   The index for which to return the type fallbacks.
+   *
+   * @return string[]
+   *   An array containing the IDs of all custom data types that are not
+   *   supported by the index's current server, mapped to their fallback types.
+   */
+  public function getDataTypeFallbackMapping(IndexInterface $index);
+
+}
diff --git a/src/Utility/FieldsHelper.php b/src/Utility/FieldsHelper.php
new file mode 100644
index 0000000..383bde7
--- /dev/null
+++ b/src/Utility/FieldsHelper.php
@@ -0,0 +1,329 @@
+<?php
+
+namespace Drupal\search_api\Utility;
+
+use Drupal\Core\Entity\EntityFieldManagerInterface;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
+use Drupal\Core\Entity\TypedData\EntityDataDefinitionInterface;
+use Drupal\Core\TypedData\ComplexDataDefinitionInterface;
+use Drupal\Core\TypedData\ComplexDataInterface;
+use Drupal\Core\TypedData\DataDefinitionInterface;
+use Drupal\Core\TypedData\DataReferenceDefinitionInterface;
+use Drupal\Core\TypedData\DataReferenceInterface;
+use Drupal\Core\TypedData\ListDataDefinitionInterface;
+use Drupal\Core\TypedData\ListInterface;
+use Drupal\Core\TypedData\TypedDataInterface;
+use Drupal\search_api\Datasource\DatasourceInterface;
+use Drupal\search_api\IndexInterface;
+use Drupal\search_api\Item\Field;
+use Drupal\search_api\Item\FieldInterface;
+use Drupal\search_api\Item\Item;
+use Drupal\search_api\Processor\ConfigurablePropertyInterface;
+use Drupal\search_api\SearchApiException;
+use Drupal\search_api\Utility\Utility;
+use Symfony\Component\DependencyInjection\Container;
+
+/**
+ * Provides helper methods for dealing with Search API fields and properties.
+ */
+class FieldsHelper implements FieldsHelperInterface {
+
+  /**
+   * The entity field manager.
+   *
+   * @var \Drupal\Core\Entity\EntityFieldManagerInterface
+   */
+  protected $entityFieldManager;
+
+  /**
+   * The entity type bundle info service.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface
+   */
+  protected $entityBundleInfo;
+
+  /**
+   * The data type plugin manager.
+   *
+   * @var \Drupal\search_api\Utility\DataTypeHelperInterface
+   */
+  protected $dataTypeHelper;
+
+  /**
+   * Cache for the field type mapping.
+   *
+   * @var array|null
+   *
+   * @see getFieldTypeMapping()
+   */
+  protected $fieldTypeMapping;
+
+  /**
+   * Cache for the fallback data type mapping per index.
+   *
+   * @var array
+   *
+   * @see getDataTypeFallbackMapping()
+   */
+  protected $dataTypeFallbackMapping = array();
+
+  /**
+   * Constructs a FieldsHelper object.
+   *
+   * @param \Drupal\Core\Entity\EntityFieldManagerInterface $entityFieldManager
+   *   The entity field manager.
+   * @param EntityTypeBundleInfoInterface $entityBundleInfo
+   *   The entity type bundle info service.
+   * @param \Drupal\search_api\Utility\DataTypeHelperInterface $dataTypeHelper
+   *   The data type helper service.
+   */
+  public function __construct(EntityFieldManagerInterface $entityFieldManager, EntityTypeBundleInfoInterface $entityBundleInfo, DataTypeHelperInterface $dataTypeHelper) {
+    $this->entityFieldManager = $entityFieldManager;
+    $this->entityBundleInfo = $entityBundleInfo;
+    $this->dataTypeHelper = $dataTypeHelper;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function extractFields(ComplexDataInterface $item, array $fields) {
+    // Figure out which fields are directly on the item and which need to be
+    // extracted from nested items.
+    $directFields = array();
+    $nestedFields = array();
+    foreach (array_keys($fields) as $key) {
+      if (strpos($key, ':') !== FALSE) {
+        list($direct, $nested) = explode(':', $key, 2);
+        $nestedFields[$direct][$nested] = $fields[$key];
+      }
+      else {
+        $directFields[] = $key;
+      }
+    }
+    // Extract the direct fields.
+    $properties = $item->getProperties(TRUE);
+    foreach ($directFields as $key) {
+      if (empty($properties[$key])) {
+        continue;
+      }
+      $data = $item->get($key);
+      foreach ($fields[$key] as $field) {
+        $this->extractField($data, $field);
+      }
+    }
+    // Recurse for all nested fields.
+    foreach ($nestedFields as $direct => $fieldsNested) {
+      if (empty($properties[$direct])) {
+        continue;
+      }
+      $itemNested = $item->get($direct);
+      if ($itemNested instanceof DataReferenceInterface) {
+        $itemNested = $itemNested->getTarget();
+      }
+      if ($itemNested instanceof EntityInterface) {
+        $itemNested = $itemNested->getTypedData();
+      }
+      if ($itemNested instanceof ComplexDataInterface && !$itemNested->isEmpty()) {
+        $this->extractFields($itemNested, $fieldsNested);
+      }
+      elseif ($itemNested instanceof ListInterface && !$itemNested->isEmpty()) {
+        foreach ($itemNested as $listItem) {
+          if ($listItem instanceof ComplexDataInterface && !$listItem->isEmpty()) {
+            $this->extractFields($listItem, $fieldsNested);
+          }
+        }
+      }
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function extractField(TypedDataInterface $data, FieldInterface $field) {
+    $values = $this->extractFieldValues($data);
+
+    foreach ($values as $i => $value) {
+      $field->addValue($value);
+    }
+    $field->setOriginalType($data->getDataDefinition()->getDataType());
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function extractFieldValues(TypedDataInterface $data) {
+    if ($data->getDataDefinition()->isList()) {
+      $values = array();
+      foreach ($data as $piece) {
+        $values[] = $this->extractFieldValues($piece);
+      }
+      return $values ? call_user_func_array('array_merge', $values) : array();
+    }
+
+    $value = $data->getValue();
+    $definition = $data->getDataDefinition();
+    if ($definition instanceof ComplexDataDefinitionInterface) {
+      $property = $definition->getMainPropertyName();
+      if (isset($value[$property])) {
+        return array($value[$property]);
+      }
+    }
+    elseif (is_array($value)) {
+      return array_values($value);
+    }
+    return array($value);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getNestedProperties(ComplexDataDefinitionInterface $property) {
+    $nestedProperties = $property->getPropertyDefinitions();
+    if ($property instanceof EntityDataDefinitionInterface) {
+      $bundles = $this->entityBundleInfo
+        ->getBundleInfo($property->getEntityTypeId());
+      foreach ($bundles as $bundle => $bundleLabel) {
+        $bundleProperties = $this->entityFieldManager
+          ->getFieldDefinitions($property->getEntityTypeId(), $bundle);
+        $nestedProperties += $bundleProperties;
+      }
+    }
+    return $nestedProperties;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function retrieveNestedProperty(array $properties, $propertyPath) {
+    list($key, $nestedPath) = Utility::splitPropertyPath($propertyPath, FALSE);
+    if (!isset($properties[$key])) {
+      return NULL;
+    }
+
+    $property = $this->getInnerProperty($properties[$key]);
+    if (!isset($nestedPath)) {
+      return $property;
+    }
+
+    if (!$property instanceof ComplexDataDefinitionInterface) {
+      return NULL;
+    }
+
+    return $this->retrieveNestedProperty($this->getNestedProperties($property), $nestedPath);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getInnerProperty(DataDefinitionInterface $property) {
+    while ($property instanceof ListDataDefinitionInterface) {
+      $property = $property->getItemDefinition();
+    }
+    while ($property instanceof DataReferenceDefinitionInterface) {
+      $property = $property->getTargetDefinition();
+    }
+    return $property;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isFieldIdReserved($fieldId) {
+    return substr($fieldId, 0, 11) == 'search_api_';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function createItem(IndexInterface $index, $id, DatasourceInterface $datasource = NULL) {
+    return new Item($index, $id, $datasource);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function createItemFromObject(IndexInterface $index, ComplexDataInterface $originalObject, $id = NULL, DatasourceInterface $datasource = NULL) {
+    if (!isset($id)) {
+      if (!isset($datasource)) {
+        throw new \InvalidArgumentException('Need either an item ID or the datasource to create a search item from an object.');
+      }
+      $id = Utility::createCombinedId($datasource->getPluginId(), $datasource->getItemId($originalObject));
+    }
+    $item = $this->createItem($index, $id, $datasource);
+    $item->setOriginalObject($originalObject);
+    return $item;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function createField(IndexInterface $index, $fieldIdentifier, $fieldInfo = array()) {
+    $field = new Field($index, $fieldIdentifier);
+
+    foreach ($fieldInfo as $key => $value) {
+      $method = 'set' . Container::camelize($key);
+      if (method_exists($field, $method)) {
+        $field->$method($value);
+      }
+    }
+
+    return $field;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function createFieldFromProperty(IndexInterface $index, DataDefinitionInterface $property, $datasourceId, $propertyPath, $fieldId = NULL, $type = NULL) {
+    if (!isset($fieldId)) {
+      $fieldId = $this->getNewFieldId($index, $propertyPath);
+    }
+
+    if (!isset($type)) {
+      $typeMapping = $this->dataTypeHelper->getFieldTypeMapping();
+      $propertyType = $property->getDataType();
+      if (isset($typeMapping[$propertyType])) {
+        $type = $typeMapping[$propertyType];
+      }
+      else {
+        $propertyName = $property->getLabel();
+        throw new SearchApiException("No default data type mapping could be found for property '$propertyName' ($propertyPath) of type '$propertyType'.");
+      }
+    }
+
+    $fieldInfo = array(
+      'label' => $property->getLabel(),
+      'datasource_id' => $datasourceId,
+      'property_path' => $propertyPath,
+      'type' => $type,
+    );
+    if ($property instanceof ConfigurablePropertyInterface) {
+      $fieldInfo['configuration'] = $property->defaultConfiguration();
+    }
+    return $this->createField($index, $fieldId, $fieldInfo);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getNewFieldId(IndexInterface $index, $propertyPath) {
+    list(, $suggestedId) = Utility::splitPropertyPath($propertyPath);
+
+    // Avoid clashes with reserved IDs by removing the reserved "search_api_"
+    // from our suggested ID.
+    $suggestedId = str_replace('search_api_', '', $suggestedId);
+
+    $fieldId = $suggestedId;
+    $i = 0;
+    while ($index->getField($fieldId)) {
+      $fieldId = $suggestedId . '_' . ++$i;
+    }
+
+    while ($this->isFieldIdReserved($fieldId)) {
+      $fieldId = '_' . $fieldId;
+    }
+
+    return $fieldId;
+  }
+
+}
diff --git a/src/Utility/FieldsHelperInterface.php b/src/Utility/FieldsHelperInterface.php
new file mode 100644
index 0000000..9354691
--- /dev/null
+++ b/src/Utility/FieldsHelperInterface.php
@@ -0,0 +1,206 @@
+<?php
+
+namespace Drupal\search_api\Utility;
+
+use Drupal\Core\TypedData\ComplexDataDefinitionInterface;
+use Drupal\Core\TypedData\ComplexDataInterface;
+use Drupal\Core\TypedData\DataDefinitionInterface;
+use Drupal\Core\TypedData\TypedDataInterface;
+use Drupal\search_api\Datasource\DatasourceInterface;
+use Drupal\search_api\IndexInterface;
+use Drupal\search_api\Item\FieldInterface;
+
+/**
+ * Provides an interface for implementations of the fields helper service.
+ */
+interface FieldsHelperInterface {
+
+  /**
+   * Extracts specific field values from a complex data object.
+   *
+   * The values will be set directly on the given field objects, nothing is
+   * returned.
+   *
+   * @param \Drupal\Core\TypedData\ComplexDataInterface $item
+   *   The item from which fields should be extracted.
+   * @param \Drupal\search_api\Item\FieldInterface[][] $fields
+   *   An associative array, keyed by property paths, mapped to field objects
+   *   with that property path.
+   */
+  public function extractFields(ComplexDataInterface $item, array $fields);
+
+  /**
+   * Extracts value and original type from a single piece of data.
+   *
+   * @param \Drupal\Core\TypedData\TypedDataInterface $data
+   *   The piece of data from which to extract information.
+   * @param \Drupal\search_api\Item\FieldInterface $field
+   *   The field into which to put the extracted data.
+   */
+  public function extractField(TypedDataInterface $data, FieldInterface $field);
+
+  /**
+   * Extracts field values from a typed data object.
+   *
+   * @param \Drupal\Core\TypedData\TypedDataInterface $data
+   *   The typed data object.
+   *
+   * @return array
+   *   An array of values.
+   */
+  public function extractFieldValues(TypedDataInterface $data);
+
+  /**
+   * Retrieves a list of nested properties from a complex property.
+   *
+   * Takes care of including bundle-specific properties for entity reference
+   * properties.
+   *
+   * @param \Drupal\Core\TypedData\ComplexDataDefinitionInterface $property
+   *   The base definition.
+   *
+   * @return \Drupal\Core\TypedData\DataDefinitionInterface[]
+   *   The nested properties, keyed by property name.
+   */
+  public function getNestedProperties(ComplexDataDefinitionInterface $property);
+
+  /**
+   * Retrieves a nested property from a list of properties.
+   *
+   * @param \Drupal\Core\TypedData\DataDefinitionInterface[] $properties
+   *   The base properties, keyed by property name.
+   * @param string $propertyPath
+   *   The property path of the property to retrieve.
+   *
+   * @return \Drupal\Core\TypedData\DataDefinitionInterface|null
+   *   The requested property, or NULL if it couldn't be found.
+   */
+  public function retrieveNestedProperty(array $properties, $propertyPath);
+
+  /**
+   * Retrieves the inner property definition of a compound property definition.
+   *
+   * This will retrieve the list item type from a list data definition or the
+   * definition of the referenced data from a reference data definition.
+   *
+   * @param \Drupal\Core\TypedData\DataDefinitionInterface $property
+   *   The original property definition.
+   *
+   * @return \Drupal\Core\TypedData\DataDefinitionInterface
+   *   The inner property definition.
+   */
+  public function getInnerProperty(DataDefinitionInterface $property);
+
+  /**
+   * Determines whether a field ID is reserved for special use.
+   *
+   * We define all field IDs starting with "search_api_" as reserved, to be safe
+   * for future additions (and from clashing with backend-defined fields).
+   *
+   * @param string $fieldId
+   *   The field ID.
+   *
+   * @return bool
+   *   TRUE if the field ID is reserved, FALSE if it can be used normally.
+   */
+  public function isFieldIdReserved($fieldId);
+
+  /**
+   * Creates a search item object.
+   *
+   * @param \Drupal\search_api\IndexInterface $index
+   *   The item's search index.
+   * @param string $id
+   *   The item's (combined) ID.
+   * @param \Drupal\search_api\Datasource\DatasourceInterface|null $datasource
+   *   (optional) The datasource of the item. If not set, it will be determined
+   *   from the ID and loaded from the index if needed.
+   *
+   * @return \Drupal\search_api\Item\ItemInterface
+   *   A search item with the given values.
+   */
+  public function createItem(IndexInterface $index, $id, DatasourceInterface $datasource = NULL);
+
+  /**
+   * Creates a search item object by wrapping an existing complex data object.
+   *
+   * @param \Drupal\search_api\IndexInterface $index
+   *   The item's search index.
+   * @param \Drupal\Core\TypedData\ComplexDataInterface $originalObject
+   *   The original object to wrap.
+   * @param string $id
+   *   (optional) The item's (combined) ID. If not set, it will be determined
+   *   with the \Drupal\search_api\Datasource\DatasourceInterface::getItemId()
+   *   method of $datasource. In this case, $datasource must not be NULL.
+   * @param \Drupal\search_api\Datasource\DatasourceInterface|null $datasource
+   *   (optional) The datasource of the item. If not set, it will be determined
+   *   from the ID and loaded from the index if needed.
+   *
+   * @return \Drupal\search_api\Item\ItemInterface
+   *   A search item with the given values.
+   *
+   * @throws \InvalidArgumentException
+   *   Thrown if both $datasource and $id are NULL.
+   */
+  public function createItemFromObject(IndexInterface $index, ComplexDataInterface $originalObject, $id = NULL, DatasourceInterface $datasource = NULL);
+
+  /**
+   * Creates a new field object wrapping a field of the given index.
+   *
+   * @param \Drupal\search_api\IndexInterface $index
+   *   The index to which this field should be attached.
+   * @param string $fieldIdentifier
+   *   The field identifier.
+   * @param array $fieldInfo
+   *   (optional) An array with further configuration for the field.
+   *
+   * @return \Drupal\search_api\Item\FieldInterface
+   *   A new field object.
+   */
+  public function createField(IndexInterface $index, $fieldIdentifier, $fieldInfo = array());
+
+  /**
+   * Creates a new field on an index based on a property.
+   *
+   * Will find and set a new unique field identifier for the field on the index.
+   *
+   * @param \Drupal\search_api\IndexInterface $index
+   *   The search index.
+   * @param \Drupal\Core\TypedData\DataDefinitionInterface $property
+   *   The data definition of the property.
+   * @param string|null $datasourceId
+   *   The ID of the index's datasource this property belongs to, or NULL if it
+   *   is a datasource-independent property.
+   * @param string $propertyPath
+   *   The property's property path within the property structure of the
+   *   datasource.
+   * @param string|null $fieldId
+   *   (optional) The identifier to use for the field. If not set, a new unique
+   *   field identifier on the index will be chosen automatically.
+   * @param string|null $type
+   *   (optional) The type to set for the field, or NULL to determine a default
+   *   type automatically.
+   *
+   * @return \Drupal\search_api\Item\FieldInterface
+   *   A new field object for the index, based on the given property.
+   *
+   * @throws \Drupal\search_api\SearchApiException
+   *   Thrown if no type was given and no default could be determined.
+   */
+  public function createFieldFromProperty(IndexInterface $index, DataDefinitionInterface $property, $datasourceId, $propertyPath, $fieldId = NULL, $type = NULL);
+
+  /**
+   * Finds a new unique field identifier on the given index.
+   *
+   * @param \Drupal\search_api\IndexInterface $index
+   *   The search index.
+   * @param string $propertyPath
+   *   The property path on which the field identifier should be based. Only the
+   *   last component of the property path will be considered.
+   *
+   * @return string
+   *   A new unique field identifier on the given index.
+   */
+  public function getNewFieldId(IndexInterface $index, $propertyPath);
+
+}
diff --git a/src/Query/ResultsCache.php b/src/Utility/QueryHelper.php
similarity index 60%
rename from src/Query/ResultsCache.php
rename to src/Utility/QueryHelper.php
index 6ef369d..c4d7d17 100644
--- a/src/Query/ResultsCache.php
+++ b/src/Utility/QueryHelper.php
@@ -1,13 +1,18 @@
 <?php
 
-namespace Drupal\search_api\Query;
+namespace Drupal\search_api\Utility;
 
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\search_api\IndexInterface;
+use Drupal\search_api\ParseMode\ParseModePluginManager;
+use Drupal\search_api\Query\Query;
+use Drupal\search_api\Query\ResultSetInterface;
 use Symfony\Component\HttpFoundation\RequestStack;
 
 /**
- * Implements a simple request stack-aware cache for the search results.
+ * Provides methods for creating search queries and statically caching results.
  */
-class ResultsCache implements ResultsCacheInterface {
+class QueryHelper implements QueryHelperInterface {
 
   /**
    * The request stack.
@@ -17,6 +22,20 @@ class ResultsCache implements ResultsCacheInterface {
   protected $requestStack;
 
   /**
+   * The module handler.
+   *
+   * @var \Drupal\Core\Extension\ModuleHandlerInterface
+   */
+  protected $moduleHandler;
+
+  /**
+   * The parse mode manager.
+   *
+   * @var \Drupal\search_api\ParseMode\ParseModePluginManager
+   */
+  protected $parseModeManager;
+
+  /**
    * Storage for the results, keyed by request and search ID.
    *
    * @var \SplObjectStorage
@@ -31,13 +50,19 @@ class ResultsCache implements ResultsCacheInterface {
   protected $null;
 
   /**
-   * Constructs a ResultsCache object.
+   * Constructs a QueryHelper object.
    *
-   * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
+   * @param \Symfony\Component\HttpFoundation\RequestStack $requestStack
    *   The request stack.
+   * @param \Drupal\Core\Extension\ModuleHandlerInterface $moduleHandler
+   *   The module handler.
+   * @param \Drupal\search_api\ParseMode\ParseModePluginManager $parseModeManager
+   *   The parse mode manager.
    */
-  public function __construct(RequestStack $request_stack) {
-    $this->requestStack = $request_stack;
+  public function __construct(RequestStack $requestStack, ModuleHandlerInterface $moduleHandler, ParseModePluginManager $parseModeManager) {
+    $this->requestStack = $requestStack;
+    $this->moduleHandler = $moduleHandler;
+    $this->parseModeManager = $parseModeManager;
     $this->results = new \SplObjectStorage();
     $this->null = (object) array();
   }
@@ -45,8 +70,21 @@ public function __construct(RequestStack $request_stack) {
   /**
    * {@inheritdoc}
    */
+  public function createQuery(IndexInterface $index, array $options = array()) {
+    $query = Query::create($index, $options);
+
+    $query->setModuleHandler($this->moduleHandler);
+    $query->setParseModeManager($this->parseModeManager);
+    $query->setQueryHelper($this);
+
+    return $query;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
   public function addResults(ResultSetInterface $results) {
-    // @todo Create getter and setter methods for the search ID.
+    // @todo Create getter and setter methods for the search ID. See #2772829.
     $search_id = $results->getQuery()->getOption('search id', '');
     $request = $this->getCurrentRequest();
     if (!isset($this->results[$request])) {
@@ -101,7 +139,7 @@ public function removeResults($search_id) {
    *   NULL key.
    */
   protected function getCurrentRequest() {
-    return $this->requestStack->getCurrentRequest();
+    return $this->requestStack->getCurrentRequest() ?: $this->null;
   }
 
 }
diff --git a/src/Query/ResultsCacheInterface.php b/src/Utility/QueryHelperInterface.php
similarity index 50%
rename from src/Query/ResultsCacheInterface.php
rename to src/Utility/QueryHelperInterface.php
index 76c72e1..8268f5d 100644
--- a/src/Query/ResultsCacheInterface.php
+++ b/src/Utility/QueryHelperInterface.php
@@ -1,11 +1,29 @@
 <?php
 
-namespace Drupal\search_api\Query;
+namespace Drupal\search_api\Utility;
+
+use Drupal\search_api\IndexInterface;
+use Drupal\search_api\Query\ResultSetInterface;
 
 /**
- * Represents a search results cache.
+ * Provides an interface for query helper services.
  */
-interface ResultsCacheInterface {
+interface QueryHelperInterface {
+
+  /**
+   * Creates a new search query object.
+   *
+   * @param \Drupal\search_api\IndexInterface $index
+   *   The index on which to search.
+   * @param array $options
+   *   (optional) The options to set for the query. See
+   *   \Drupal\search_api\Query\QueryInterface::setOption() for a list of
+   *   options that are recognized by default.
+   *
+   * @return \Drupal\search_api\Query\QueryInterface
+   *   A search query object to use.
+   */
+  public function createQuery(IndexInterface $index, array $options = array());
 
   /**
    * Adds a result set to the cache.
diff --git a/src/Utility.php b/src/Utility/Utility.php
similarity index 53%
rename from src/Utility.php
rename to src/Utility/Utility.php
index ecbf26d..eeac087 100644
--- a/src/Utility.php
+++ b/src/Utility/Utility.php
@@ -1,55 +1,22 @@
 <?php
 
-namespace Drupal\search_api;
+namespace Drupal\search_api\Utility;
 
-use Drupal\Core\Config\ConfigImporter;
-use Drupal\Core\Entity\EntityInterface;
-use Drupal\Core\Entity\TypedData\EntityDataDefinitionInterface;
 use Drupal\Core\TypedData\ComplexDataDefinitionInterface;
 use Drupal\Core\TypedData\ComplexDataInterface;
 use Drupal\Core\TypedData\DataDefinitionInterface;
-use Drupal\Core\TypedData\DataReferenceDefinitionInterface;
-use Drupal\Core\TypedData\DataReferenceInterface;
-use Drupal\Core\TypedData\ListDataDefinitionInterface;
-use Drupal\Core\TypedData\ListInterface;
 use Drupal\Core\TypedData\TypedDataInterface;
 use Drupal\search_api\Datasource\DatasourceInterface;
-use Drupal\search_api\Entity\Index;
-use Drupal\search_api\Item\Field;
+use Drupal\search_api\IndexInterface;
 use Drupal\search_api\Item\FieldInterface;
-use Drupal\search_api\Item\Item;
 use Drupal\search_api\Plugin\search_api\data_type\value\TextToken;
-use Drupal\search_api\Processor\ConfigurablePropertyInterface;
-use Drupal\search_api\Query\Query;
-use Symfony\Component\DependencyInjection\Container;
 
 /**
  * Contains utility methods for the Search API.
- *
- * @todo Maybe move some of these methods to other classes (and/or split this
- *   class into several utility classes).
  */
 class Utility {
 
   /**
-   * Static cache for the field type mapping.
-   *
-   * @var array
-   *
-   * @see getFieldTypeMapping()
-   */
-  protected static $fieldTypeMapping = array();
-
-  /**
-   * Static cache for the fallback data type mapping per index.
-   *
-   * @var array
-   *
-   * @see getDataTypeFallbackMapping()
-   */
-  protected static $dataTypeFallbackMapping = array();
-
-  /**
    * Determines whether fields of the given type contain fulltext data.
    *
    * @param string $type
@@ -59,18 +26,13 @@ class Utility {
    *
    * @return bool
    *   TRUE if $type is one of the specified types, FALSE otherwise.
+   *
+   * @deprecated Will be removed during Beta phase.
    */
   public static function isTextType($type, array $text_types = array('text')) {
-    if (in_array($type, $text_types)) {
-      return TRUE;
-    }
-    $data_type = \Drupal::getContainer()
-      ->get('plugin.manager.search_api.data_type')
-      ->createInstance($type);
-    if ($data_type && !$data_type->isDefault()) {
-      return in_array($data_type->getFallbackType(), $text_types);
-    }
-    return FALSE;
+    return \Drupal::getContainer()
+      ->get('search_api.data_type_helper')
+      ->isTextType($type, $text_types);
   }
 
   /**
@@ -81,64 +43,14 @@ public static function isTextType($type, array $text_types = array('text')) {
    *   corresponding Search API data types. Empty values mean that fields of
    *   that type should be ignored by the Search API.
    *
+   * @deprecated Will be removed during Beta phase.
+   *
    * @see hook_search_api_field_type_mapping_alter()
    */
   public static function getFieldTypeMapping() {
-    // Check the static cache first.
-    if (empty(static::$fieldTypeMapping)) {
-      // It's easier to write and understand this array in the form of
-      // $search_api_field_type => array($data_types) and flip it below.
-      $default_mapping = array(
-        'text' => array(
-          'field_item:string_long.string',
-          'field_item:text_long.string',
-          'field_item:text_with_summary.string',
-          'text',
-        ),
-        'string' => array(
-          'string',
-          'email',
-          'uri',
-          'filter_format',
-          'duration_iso8601',
-          'field_item:path',
-        ),
-        'integer' => array(
-          'integer',
-          'timespan',
-        ),
-        'decimal' => array(
-          'decimal',
-          'float',
-        ),
-        'date' => array(
-          'date',
-          'datetime_iso8601',
-          'timestamp',
-        ),
-        'boolean' => array(
-          'boolean',
-        ),
-        // Types we know about but want/have to ignore.
-        NULL => array(
-          'language',
-        ),
-      );
-
-      foreach ($default_mapping as $search_api_type => $data_types) {
-        foreach ($data_types as $data_type) {
-          $mapping[$data_type] = $search_api_type;
-        }
-      }
-
-      // Allow other modules to intercept and define what default type they want
-      // to use for their data type.
-      \Drupal::moduleHandler()->alter('search_api_field_type_mapping', $mapping);
-
-      static::$fieldTypeMapping = $mapping;
-    }
-
-    return static::$fieldTypeMapping;
+    return \Drupal::getContainer()
+      ->get('search_api.data_type_helper')
+      ->getFieldTypeMapping();
   }
 
   /**
@@ -150,34 +62,13 @@ public static function getFieldTypeMapping() {
    * @return string[]
    *   An array containing the IDs of all custom data types that are not
    *   supported by the index's current server, mapped to their fallback types.
+   *
+   * @deprecated Will be removed during Beta phase.
    */
   public static function getDataTypeFallbackMapping(IndexInterface $index) {
-    // Check the static cache first.
-    $index_id = $index->id();
-    if (empty(static::$dataTypeFallbackMapping[$index_id])) {
-      $server = NULL;
-      try {
-        $server = $index->getServerInstance();
-      }
-      catch (SearchApiException $e) {
-        // If the server isn't available, just ignore it here and return all
-        // custom types.
-      }
-      static::$dataTypeFallbackMapping[$index_id] = array();
-      $data_types = \Drupal::getContainer()
-        ->get('plugin.manager.search_api.data_type')
-        ->getInstances();
-      foreach ($data_types as $type_id => $data_type) {
-        // We know for sure that we do not need to fall back for the default
-        // data types as they are always present and are required to be
-        // supported by all backends.
-        if (!$data_type->isDefault() && (!$server || !$server->supportsDataType($type_id))) {
-          static::$dataTypeFallbackMapping[$index_id][$type_id] = $data_type->getFallbackType();
-        }
-      }
-    }
-
-    return static::$dataTypeFallbackMapping[$index_id];
+    return \Drupal::getContainer()
+      ->get('search_api.data_type_helper')
+      ->getDataTypeFallbackMapping($index);
   }
 
   /**
@@ -191,60 +82,13 @@ public static function getDataTypeFallbackMapping(IndexInterface $index) {
    * @param \Drupal\search_api\Item\FieldInterface[][] $fields
    *   An associative array, keyed by property paths, mapped to field objects
    *   with that property path.
+   *
+   * @deprecated Will be removed during Beta phase.
    */
   public static function extractFields(ComplexDataInterface $item, array $fields) {
-    // Figure out which fields are directly on the item and which need to be
-    // extracted from nested items.
-    $direct_fields = array();
-    $nested_fields = array();
-    foreach (array_keys($fields) as $key) {
-      if (strpos($key, ':') !== FALSE) {
-        list($direct, $nested) = explode(':', $key, 2);
-        $nested_fields[$direct][$nested] = $fields[$key];
-      }
-      else {
-        $direct_fields[] = $key;
-      }
-    }
-    // Extract the direct fields.
-    foreach ($direct_fields as $key) {
-      try {
-        $data = $item->get($key);
-        foreach ($fields[$key] as $field) {
-          self::extractField($data, $field);
-        }
-      }
-      catch (\InvalidArgumentException $e) {
-        // This can happen with properties added by processors.
-        // @todo Find a cleaner solution for this.
-      }
-    }
-    // Recurse for all nested fields.
-    foreach ($nested_fields as $direct => $fields_nested) {
-      try {
-        $item_nested = $item->get($direct);
-        if ($item_nested instanceof DataReferenceInterface) {
-          $item_nested = $item_nested->getTarget();
-        }
-        if ($item_nested instanceof EntityInterface) {
-          $item_nested = $item_nested->getTypedData();
-        }
-        if ($item_nested instanceof ComplexDataInterface && !$item_nested->isEmpty()) {
-          self::extractFields($item_nested, $fields_nested);
-        }
-        elseif ($item_nested instanceof ListInterface && !$item_nested->isEmpty()) {
-          foreach ($item_nested as $list_item) {
-            if ($list_item instanceof ComplexDataInterface && !$list_item->isEmpty()) {
-              self::extractFields($list_item, $fields_nested);
-            }
-          }
-        }
-      }
-      catch (\InvalidArgumentException $e) {
-        // This can happen with properties added by processors.
-        // @todo Find a cleaner solution for this.
-      }
-    }
+    \Drupal::getContainer()
+      ->get('search_api.fields_helper')
+      ->extractFields($item, $fields);
   }
 
   /**
@@ -254,14 +98,13 @@ public static function extractFields(ComplexDataInterface $item, array $fields)
    *   The piece of data from which to extract information.
    * @param \Drupal\search_api\Item\FieldInterface $field
    *   The field into which to put the extracted data.
+   *
+   * @deprecated Will be removed during Beta phase.
    */
   public static function extractField(TypedDataInterface $data, FieldInterface $field) {
-    $values = static::extractFieldValues($data);
-
-    foreach ($values as $i => $value) {
-      $field->addValue($value);
-    }
-    $field->setOriginalType($data->getDataDefinition()->getDataType());
+    \Drupal::getContainer()
+      ->get('search_api.fields_helper')
+      ->extractField($data, $field);
   }
 
   /**
@@ -272,28 +115,13 @@ public static function extractField(TypedDataInterface $data, FieldInterface $fi
    *
    * @return array
    *   An array of values.
+   *
+   * @deprecated Will be removed during Beta phase.
    */
   public static function extractFieldValues(TypedDataInterface $data) {
-    if ($data->getDataDefinition()->isList()) {
-      $values = array();
-      foreach ($data as $piece) {
-        $values[] = self::extractFieldValues($piece);
-      }
-      return $values ? call_user_func_array('array_merge', $values) : array();
-    }
-
-    $value = $data->getValue();
-    $definition = $data->getDataDefinition();
-    if ($definition instanceof ComplexDataDefinitionInterface) {
-      $property = $definition->getMainPropertyName();
-      if (isset($value[$property])) {
-        return array($value[$property]);
-      }
-    }
-    elseif (is_array($value)) {
-      return array_values($value);
-    }
-    return array($value);
+    return \Drupal::getContainer()
+      ->get('search_api.fields_helper')
+      ->extractFieldValues($data);
   }
 
   /**
@@ -307,20 +135,13 @@ public static function extractFieldValues(TypedDataInterface $data) {
    *
    * @return \Drupal\Core\TypedData\DataDefinitionInterface[]
    *   The nested properties, keyed by property name.
+   *
+   * @deprecated Will be removed during Beta phase.
    */
   public static function getNestedProperties(ComplexDataDefinitionInterface $property) {
-    $nested_properties = $property->getPropertyDefinitions();
-    if ($property instanceof EntityDataDefinitionInterface) {
-      $container = \Drupal::getContainer();
-      $bundles = $container->get('entity_type.bundle.info')
-        ->getBundleInfo($property->getEntityTypeId());
-      $field_manager = $container->get('entity_field.manager');
-      foreach ($bundles as $bundle => $bundle_label) {
-        $bundle_properties = $field_manager->getFieldDefinitions($property->getEntityTypeId(), $bundle);
-        $nested_properties += $bundle_properties;
-      }
-    }
-    return $nested_properties;
+    return \Drupal::getContainer()
+      ->get('search_api.fields_helper')
+      ->getNestedProperties($property);
   }
 
   /**
@@ -333,23 +154,13 @@ public static function getNestedProperties(ComplexDataDefinitionInterface $prope
    *
    * @return \Drupal\Core\TypedData\DataDefinitionInterface|null
    *   The requested property, or NULL if it couldn't be found.
+   *
+   * @deprecated Will be removed during Beta phase.
    */
   public static function retrieveNestedProperty(array $properties, $property_path) {
-    list($key, $nested_path) = static::splitPropertyPath($property_path, FALSE);
-    if (!isset($properties[$key])) {
-      return NULL;
-    }
-
-    $property = static::getInnerProperty($properties[$key]);
-    if (!isset($nested_path)) {
-      return $property;
-    }
-
-    if (!$property instanceof ComplexDataDefinitionInterface) {
-      return NULL;
-    }
-
-    return static::retrieveNestedProperty(static::getNestedProperties($property), $nested_path);
+    return \Drupal::getContainer()
+      ->get('search_api.fields_helper')
+      ->retrieveNestedProperty($properties, $property_path);
   }
 
   /**
@@ -363,50 +174,13 @@ public static function retrieveNestedProperty(array $properties, $property_path)
    *
    * @return \Drupal\Core\TypedData\DataDefinitionInterface
    *   The inner property definition.
-   */
-  public static function getInnerProperty(DataDefinitionInterface $property) {
-    while ($property instanceof ListDataDefinitionInterface) {
-      $property = $property->getItemDefinition();
-    }
-    while ($property instanceof DataReferenceDefinitionInterface) {
-      $property = $property->getTargetDefinition();
-    }
-    return $property;
-  }
-
-  /**
-   * Splits a property path into two parts along a path separator (:).
-   *
-   * The path is split into one part with a single property name, and one part
-   * with the complete rest of the property path (which might be empty).
-   * Depending on $separate_last the returned single property key will be the
-   * first (FALSE) or last (TRUE) property of the path.
    *
-   * @param string $property_path
-   *   The property path to split.
-   * @param bool $separate_last
-   *   (optional) If FALSE, separate the first property of the path. By default,
-   *   the last property is separated from the rest.
-   * @param string $separator
-   *   (optional) The separator to use.
-   *
-   * @return string[]
-   *   An array with indexes 0 and 1, 0 containing the first part of the
-   *   property path and 1 the second. If $separate_last is FALSE, index 0 will
-   *   always contain a single property name (without any colons) and index 1
-   *   might be NULL. If $separate_last is TRUE it's the exact other way round.
+   * @deprecated Will be removed during Beta phase.
    */
-  public static function splitPropertyPath($property_path, $separate_last = TRUE, $separator = ':') {
-    $function = $separate_last ? 'strrpos' : 'strpos';
-    $pos = $function($property_path, $separator);
-    if ($pos !== FALSE) {
-      return array(
-        substr($property_path, 0, $pos),
-        substr($property_path, $pos + 1),
-      );
-    }
-
-    return $separate_last ? array(NULL, $property_path) : array($property_path, NULL);
+  public static function getInnerProperty(DataDefinitionInterface $property) {
+    return \Drupal::getContainer()
+      ->get('search_api.fields_helper')
+      ->getInnerProperty($property);
   }
 
   /**
@@ -420,72 +194,13 @@ public static function splitPropertyPath($property_path, $separate_last = TRUE,
    *
    * @return bool
    *   TRUE if the field ID is reserved, FALSE if it can be used normally.
-   */
-  public static function isFieldIdReserved($field_id) {
-    return substr($field_id, 0, 11) == 'search_api_';
-  }
-
-  /**
-   * Processes all pending index tasks inside a batch run.
-   *
-   * @param array $context
-   *   The current batch context.
-   * @param \Drupal\Core\Config\ConfigImporter $config_importer
-   *   The config importer.
    *
-   * @throws \Drupal\search_api\SearchApiException
-   *   Thrown if any error occurred while tracking items.
+   * @deprecated Will be removed during Beta phase.
    */
-  public static function processIndexTasks(array &$context, ConfigImporter $config_importer) {
-    $index_task_manager = \Drupal::getContainer()->get('search_api.index_task_manager');
-
-    if (!isset($context['sandbox']['indexes'])) {
-      $context['sandbox']['indexes'] = array();
-
-      $indexes = \Drupal::entityTypeManager()
-        ->getStorage('search_api_index')
-        ->loadByProperties(array(
-          'status' => TRUE,
-        ));
-      $deleted = $config_importer->getUnprocessedConfiguration('delete');
-
-      /** @var \Drupal\search_api\IndexInterface $index */
-      foreach ($indexes as $index_id => $index) {
-        if (!$index_task_manager->isTrackingComplete($index) && !in_array($index->getConfigDependencyName(), $deleted)) {
-          $context['sandbox']['indexes'][] = $index_id;
-        }
-      }
-      $context['sandbox']['total'] = count($context['sandbox']['indexes']);
-      if (!$context['sandbox']['total']) {
-        $context['finished'] = 1;
-        return;
-      }
-    }
-
-    try {
-      $index_id = array_shift($context['sandbox']['indexes']);
-      $index = Index::load($index_id);
-      if (!($index_task_manager->addItemsOnce($index))) {
-        array_unshift($context['sandbox']['indexes'], $index_id);
-      }
-    }
-    catch (SearchApiException $e) {
-      watchdog_exception('search_api', $e);
-    }
-
-    if (empty($context['sandbox']['indexes'])) {
-      $context['finished'] = 1;
-    }
-    else {
-      $finished = $context['sandbox']['total'] - count($context['sandbox']['indexes']);
-      $context['finished'] = $finished / $context['sandbox']['total'];
-      $args = array(
-        '%index' => $index->label(),
-        '@num' => $finished + 1,
-        '@total' => $context['sandbox']['total'],
-      );
-      $context['message'] = \Drupal::translation()->translate('Tracking items for search index %index (@num of @total)', $args);
-    }
+  public static function isFieldIdReserved($field_id) {
+    return \Drupal::getContainer()
+      ->get('search_api.fields_helper')
+      ->isFieldIdReserved($field_id);
   }
 
   /**
@@ -501,11 +216,14 @@ public static function processIndexTasks(array &$context, ConfigImporter $config
    * @return \Drupal\search_api\Query\QueryInterface
    *   A search query object to use.
    *
+   * @deprecated Will be removed during Beta phase.
+   *
    * @see \Drupal\search_api\Query\QueryInterface::create()
    */
   public static function createQuery(IndexInterface $index, array $options = array()) {
-    $search_results_cache = \Drupal::service('search_api.results_static_cache');
-    return Query::create($index, $search_results_cache, $options);
+    return \Drupal::getContainer()
+      ->get('search_api.query_helper')
+      ->createQuery($index, $options);
   }
 
   /**
@@ -521,9 +239,13 @@ public static function createQuery(IndexInterface $index, array $options = array
    *
    * @return \Drupal\search_api\Item\ItemInterface
    *   A search item with the given values.
+   *
+   * @deprecated Will be removed during Beta phase.
    */
   public static function createItem(IndexInterface $index, $id, DatasourceInterface $datasource = NULL) {
-    return new Item($index, $id, $datasource);
+    return \Drupal::getContainer()
+      ->get('search_api.fields_helper')
+      ->createItem($index, $id, $datasource);
   }
 
   /**
@@ -546,17 +268,13 @@ public static function createItem(IndexInterface $index, $id, DatasourceInterfac
    *
    * @throws \InvalidArgumentException
    *   Thrown if both $datasource and $id are NULL.
+   *
+   * @deprecated Will be removed during Beta phase.
    */
   public static function createItemFromObject(IndexInterface $index, ComplexDataInterface $original_object, $id = NULL, DatasourceInterface $datasource = NULL) {
-    if (!isset($id)) {
-      if (!isset($datasource)) {
-        throw new \InvalidArgumentException('Need either an item ID or the datasource to create a search item from an object.');
-      }
-      $id = self::createCombinedId($datasource->getPluginId(), $datasource->getItemId($original_object));
-    }
-    $item = static::createItem($index, $id, $datasource);
-    $item->setOriginalObject($original_object);
-    return $item;
+    return \Drupal::getContainer()
+      ->get('search_api.fields_helper')
+      ->createItemFromObject($index, $original_object, $id, $datasource);
   }
 
   /**
@@ -571,18 +289,13 @@ public static function createItemFromObject(IndexInterface $index, ComplexDataIn
    *
    * @return \Drupal\search_api\Item\FieldInterface
    *   A new field object.
+   *
+   * @deprecated Will be removed during Beta phase.
    */
   public static function createField(IndexInterface $index, $field_identifier, $field_info = array()) {
-    $field = new Field($index, $field_identifier);
-
-    foreach ($field_info as $key => $value) {
-      $method = 'set' . Container::camelize($key);
-      if (method_exists($field, $method)) {
-        $field->$method($value);
-      }
-    }
-
-    return $field;
+    return \Drupal::getContainer()
+      ->get('search_api.fields_helper')
+      ->createField($index, $field_identifier, $field_info);
   }
 
   /**
@@ -612,34 +325,13 @@ public static function createField(IndexInterface $index, $field_identifier, $fi
    *
    * @throws \Drupal\search_api\SearchApiException
    *   Thrown if no type was given and no default could be determined.
+   *
+   * @deprecated Will be removed during Beta phase.
    */
   public static function createFieldFromProperty(IndexInterface $index, DataDefinitionInterface $property, $datasource_id, $property_path, $field_id = NULL, $type = NULL) {
-    if (!isset($field_id)) {
-      $field_id = static::getNewFieldId($index, $property_path);
-    }
-
-    if (!isset($type)) {
-      $type_mapping = static::getFieldTypeMapping();
-      $property_type = $property->getDataType();
-      if (isset($type_mapping[$property_type])) {
-        $type = $type_mapping[$property_type];
-      }
-      else {
-        $property_name = $property->getLabel();
-        throw new SearchApiException("No default data type mapping could be found for property '$property_name' ($property_path) of type '$property_type'.");
-      }
-    }
-
-    $field_info = array(
-      'label' => $property->getLabel(),
-      'datasource_id' => $datasource_id,
-      'property_path' => $property_path,
-      'type' => $type,
-    );
-    if ($property instanceof ConfigurablePropertyInterface) {
-      $field_info['configuration'] = $property->defaultConfiguration();
-    }
-    return self::createField($index, $field_id, $field_info);
+    return \Drupal::getContainer()
+      ->get('search_api.fields_helper')
+      ->createFieldFromProperty($index, $property, $datasource_id, $property_path, $field_id, $type);
   }
 
   /**
@@ -653,25 +345,13 @@ public static function createFieldFromProperty(IndexInterface $index, DataDefini
    *
    * @return string
    *   A new unique field identifier on the given index.
+   *
+   * @deprecated Will be removed during Beta phase.
    */
   public static function getNewFieldId(IndexInterface $index, $property_path) {
-    list(, $suggested_id) = static::splitPropertyPath($property_path);
-
-    // Avoid clashes with reserved IDs by removing the reserved "search_api_"
-    // from our suggested ID.
-    $suggested_id = str_replace('search_api_', '', $suggested_id);
-
-    $field_id = $suggested_id;
-    $i = 0;
-    while ($index->getField($field_id)) {
-      $field_id = $suggested_id . '_' . ++$i;
-    }
-
-    while (static::isFieldIdReserved($field_id)) {
-      $field_id = '_' . $field_id;
-    }
-
-    return $field_id;
+    return \Drupal::getContainer()
+      ->get('search_api.fields_helper')
+      ->getNewFieldId($index, $property_path);
   }
 
   /**
@@ -772,4 +452,42 @@ public static function splitCombinedId($combined_id) {
     return array(NULL, $combined_id);
   }
 
+  /**
+   * Splits a property path into two parts along a path separator (:).
+   *
+   * The path is split into one part with a single property name, and one part
+   * with the complete rest of the property path (which might be empty).
+   * Depending on $separate_last the returned single property key will be the
+   * first (FALSE) or last (TRUE) property of the path.
+   *
+   * @param string $property_path
+   *   The property path to split.
+   * @param bool $separate_last
+   *   (optional) If FALSE, separate the first property of the path. By default,
+   *   the last property is separated from the rest.
+   * @param string $separator
+   *   (optional) The separator to use.
+   *
+   * @return string[]
+   *   An array with indexes 0 and 1, 0 containing the first part of the
+   *   property path and 1 the second. If $separate_last is FALSE, index 0 will
+   *   always contain a single property name (without any colons) and index 1
+   *   might be NULL. If $separate_last is TRUE it's the exact other way round.
+   */
+  public static function splitPropertyPath($property_path, $separate_last = TRUE, $separator = ':') {
+    $function = $separate_last ? 'strrpos' : 'strpos';
+    $pos = $function($property_path, $separator);
+    if ($pos !== FALSE) {
+      return array(
+        substr($property_path, 0, $pos),
+        substr($property_path, $pos + 1),
+      );
+    }
+
+    if ($separate_last) {
+      return array(NULL, $property_path);
+    }
+    return array($property_path, NULL);
+  }
+
 }
diff --git a/tests/search_api_test/src/Plugin/search_api/backend/TestBackend.php b/tests/search_api_test/src/Plugin/search_api/backend/TestBackend.php
index 887746f..e0db91a 100644
--- a/tests/search_api_test/src/Plugin/search_api/backend/TestBackend.php
+++ b/tests/search_api_test/src/Plugin/search_api/backend/TestBackend.php
@@ -8,7 +8,7 @@
 use Drupal\search_api\IndexInterface;
 use Drupal\search_api\Plugin\PluginFormTrait;
 use Drupal\search_api\Query\QueryInterface;
-use Drupal\search_api\Utility;
+use Drupal\search_api\Utility\Utility;
 use Drupal\search_api_test\TestPluginTrait;
 
 /**
diff --git a/tests/search_api_test_hooks/search_api_test_hooks.module b/tests/search_api_test_hooks/search_api_test_hooks.module
index 0535016..a46d467 100644
--- a/tests/search_api_test_hooks/search_api_test_hooks.module
+++ b/tests/search_api_test_hooks/search_api_test_hooks.module
@@ -9,7 +9,7 @@
 use Drupal\search_api\Query\QueryInterface;
 use Drupal\search_api\Query\ResultSetInterface;
 use Drupal\search_api\ServerInterface;
-use Drupal\search_api\Utility;
+use Drupal\search_api\Utility\Utility;
 
 /**
  * Implements hook_search_api_backend_info_alter.
diff --git a/tests/src/Kernel/BackendTestBase.php b/tests/src/Kernel/BackendTestBase.php
index 59fdb99..4b11af4 100644
--- a/tests/src/Kernel/BackendTestBase.php
+++ b/tests/src/Kernel/BackendTestBase.php
@@ -13,7 +13,7 @@
 use Drupal\search_api\Query\QueryInterface;
 use Drupal\search_api\Query\ResultSetInterface;
 use Drupal\search_api\Tests\ExampleContentTrait;
-use Drupal\search_api\Utility;
+use Drupal\search_api\Utility\Utility;
 
 /**
  * Provides a base class for backend tests.
diff --git a/tests/src/Kernel/CustomDataTypesTest.php b/tests/src/Kernel/CustomDataTypesTest.php
index e0b5882..0eabe35 100644
--- a/tests/src/Kernel/CustomDataTypesTest.php
+++ b/tests/src/Kernel/CustomDataTypesTest.php
@@ -6,7 +6,7 @@
 use Drupal\KernelTests\KernelTestBase;
 use Drupal\search_api\Entity\Index;
 use Drupal\search_api\Entity\Server;
-use Drupal\search_api\Utility;
+use Drupal\search_api\Utility\Utility;
 
 /**
  * Tests custom data types integration.
diff --git a/tests/src/Kernel/DatasourceTaskTest.php b/tests/src/Kernel/DatasourceTaskTest.php
index b8886b3..8bb83a0 100644
--- a/tests/src/Kernel/DatasourceTaskTest.php
+++ b/tests/src/Kernel/DatasourceTaskTest.php
@@ -7,7 +7,7 @@
 use Drupal\language\Entity\ConfigurableLanguage;
 use Drupal\search_api\Entity\Index;
 use Drupal\search_api\Entity\Server;
-use Drupal\search_api\Utility;
+use Drupal\search_api\Utility\Utility;
 
 /**
  * Tests task integration of the content entity datasource.
diff --git a/tests/src/Kernel/DependencyRemovalTest.php b/tests/src/Kernel/DependencyRemovalTest.php
index ce52b52..1b7003a 100644
--- a/tests/src/Kernel/DependencyRemovalTest.php
+++ b/tests/src/Kernel/DependencyRemovalTest.php
@@ -7,7 +7,7 @@
 use Drupal\field\Entity\FieldStorageConfig;
 use Drupal\search_api\Entity\Index;
 use Drupal\search_api\Entity\Server;
-use Drupal\search_api\Utility;
+use Drupal\search_api\Utility\Utility;
 use Drupal\search_api_test\PluginTestTrait;
 
 /**
diff --git a/tests/src/Kernel/FieldValuesExtractionTest.php b/tests/src/Kernel/FieldValuesExtractionTest.php
index b8e5f2b..110977f 100644
--- a/tests/src/Kernel/FieldValuesExtractionTest.php
+++ b/tests/src/Kernel/FieldValuesExtractionTest.php
@@ -3,7 +3,7 @@
 namespace Drupal\Tests\search_api\Kernel;
 
 use Drupal\KernelTests\KernelTestBase;
-use Drupal\search_api\Utility;
+use Drupal\search_api\Utility\Utility;
 
 /**
  * Tests extraction of field values, as used during indexing.
diff --git a/tests/src/Kernel/IndexChangesTest.php b/tests/src/Kernel/IndexChangesTest.php
index 3d872f7..1090453 100644
--- a/tests/src/Kernel/IndexChangesTest.php
+++ b/tests/src/Kernel/IndexChangesTest.php
@@ -8,7 +8,7 @@
 use Drupal\KernelTests\KernelTestBase;
 use Drupal\search_api\Entity\Index;
 use Drupal\search_api\Entity\Server;
-use Drupal\search_api\Utility;
+use Drupal\search_api\Utility\Utility;
 use Drupal\search_api_test\PluginTestTrait;
 use Drupal\user\Entity\User;
 
diff --git a/tests/src/Kernel/LanguageKernelTest.php b/tests/src/Kernel/LanguageKernelTest.php
index bb1f438..17c42f0 100644
--- a/tests/src/Kernel/LanguageKernelTest.php
+++ b/tests/src/Kernel/LanguageKernelTest.php
@@ -8,7 +8,7 @@
 use Drupal\language\Entity\ConfigurableLanguage;
 use Drupal\search_api\Entity\Index;
 use Drupal\search_api\Entity\Server;
-use Drupal\search_api\Utility;
+use Drupal\search_api\Utility\Utility;
 
 /**
  * Tests translation handling of the content entity datasource.
diff --git a/tests/src/Kernel/Processor/ContentAccessTest.php b/tests/src/Kernel/Processor/ContentAccessTest.php
index 52eaa87..e9f2f14 100644
--- a/tests/src/Kernel/Processor/ContentAccessTest.php
+++ b/tests/src/Kernel/Processor/ContentAccessTest.php
@@ -9,7 +9,7 @@
 use Drupal\Core\TypedData\DataDefinitionInterface;
 use Drupal\node\Entity\Node;
 use Drupal\node\Entity\NodeType;
-use Drupal\search_api\Utility;
+use Drupal\search_api\Utility\Utility;
 use Drupal\Tests\search_api\Kernel\ResultsTrait;
 use Drupal\user\Entity\Role;
 use Drupal\user\Entity\User;
diff --git a/tests/src/Kernel/Processor/ProcessorTestBase.php b/tests/src/Kernel/Processor/ProcessorTestBase.php
index 3e8712f..b28304b 100644
--- a/tests/src/Kernel/Processor/ProcessorTestBase.php
+++ b/tests/src/Kernel/Processor/ProcessorTestBase.php
@@ -6,7 +6,7 @@
 use Drupal\search_api\Entity\Index;
 use Drupal\search_api\Entity\Server;
 use Drupal\search_api\Item\Field;
-use Drupal\search_api\Utility;
+use Drupal\search_api\Utility\Utility;
 use Drupal\system\Entity\Action;
 
 /**
diff --git a/tests/src/Kernel/Processor/RenderedItemTest.php b/tests/src/Kernel/Processor/RenderedItemTest.php
index 67df37f..e2fed7f 100644
--- a/tests/src/Kernel/Processor/RenderedItemTest.php
+++ b/tests/src/Kernel/Processor/RenderedItemTest.php
@@ -7,7 +7,7 @@
 use Drupal\node\Entity\Node;
 use Drupal\node\Entity\NodeType;
 use Drupal\search_api\Entity\Index;
-use Drupal\search_api\Utility;
+use Drupal\search_api\Utility\Utility;
 use Drupal\user\Entity\Role;
 use Drupal\user\Entity\User;
 
diff --git a/tests/src/Kernel/QueryTest.php b/tests/src/Kernel/QueryTest.php
index 035665c..f94399b 100644
--- a/tests/src/Kernel/QueryTest.php
+++ b/tests/src/Kernel/QueryTest.php
@@ -172,8 +172,7 @@ public function testQueryCloning() {
    * Tests that serialization of queries works correctly.
    */
   public function testQuerySerialization() {
-    $results_cache = $this->container->get('search_api.results_static_cache');
-    $query = Query::create($this->index, $results_cache);
+    $query = Query::create($this->index);
     $tags = array('tag1', 'tag2');
     $query->keys('foo bar')
       ->addCondition('field1', 'value', '<')
diff --git a/tests/src/Kernel/ResultsTrait.php b/tests/src/Kernel/ResultsTrait.php
index 7129613..ffa6573 100644
--- a/tests/src/Kernel/ResultsTrait.php
+++ b/tests/src/Kernel/ResultsTrait.php
@@ -3,7 +3,7 @@
 namespace Drupal\Tests\search_api\Kernel;
 
 use Drupal\search_api\Query\ResultSetInterface;
-use Drupal\search_api\Utility;
+use Drupal\search_api\Utility\Utility;
 
 /**
  * Defines a trait for testing results.
diff --git a/tests/src/Unit/Plugin/Processor/AddURLTest.php b/tests/src/Unit/Plugin/Processor/AddURLTest.php
index f8a37d5..5db77ec 100644
--- a/tests/src/Unit/Plugin/Processor/AddURLTest.php
+++ b/tests/src/Unit/Plugin/Processor/AddURLTest.php
@@ -38,7 +38,7 @@ class AddURLTest extends UnitTestCase {
   protected function setUp() {
     parent::setUp();
 
-    $this->setUpDataTypePlugin();
+    $this->setUpMockContainer();
 
     // Create a mock for the URL to be returned.
     $url = $this->getMockBuilder('Drupal\Core\Url')
diff --git a/tests/src/Unit/Plugin/Processor/AggregatedFieldsTest.php b/tests/src/Unit/Plugin/Processor/AggregatedFieldsTest.php
index 2313d5f..9b0b52e 100644
--- a/tests/src/Unit/Plugin/Processor/AggregatedFieldsTest.php
+++ b/tests/src/Unit/Plugin/Processor/AggregatedFieldsTest.php
@@ -8,7 +8,7 @@
 use Drupal\search_api\Plugin\search_api\processor\AggregatedFields;
 use Drupal\search_api\Processor\ProcessorInterface;
 use Drupal\search_api\Processor\ProcessorProperty;
-use Drupal\search_api\Utility;
+use Drupal\search_api\Utility\Utility;
 use Drupal\Tests\UnitTestCase;
 
 /**
@@ -83,7 +83,7 @@ protected function setUp() {
     ), 'search_api_index');
     $this->processor = new AggregatedFields(array('index' => $this->index), 'aggregated_field', array());
     $this->index->addProcessor($this->processor);
-    $this->setUpDataTypePlugin();
+    $this->setUpMockContainer();
   }
 
   /**
@@ -259,6 +259,10 @@ public function testFieldExtraction() {
       ->willReturnMap(array(
         array('foo', $bar_foo_property),
       ));
+    $bar_property->method('getProperties')
+      ->willReturn(array(
+        'foo' => TRUE,
+      ));
     $foobar_property = $this->getMock('Drupal\Core\TypedData\TypedDataInterface');
     $foobar_property->method('getValue')
       ->willReturn('wrong_value2');
@@ -269,6 +273,11 @@ public function testFieldExtraction() {
         array('bar', $bar_property),
         array('foobar', $foobar_property),
       ));
+    $object->method('getProperties')
+      ->willReturn(array(
+        'bar' => TRUE,
+        'foobar' => TRUE,
+      ));
 
     /** @var \Drupal\search_api\IndexInterface|\PHPUnit_Framework_MockObject_MockObject $index */
     $index = $this->getMock('Drupal\search_api\IndexInterface');
diff --git a/tests/src/Unit/Plugin/Processor/FieldsProcessorPluginBaseTest.php b/tests/src/Unit/Plugin/Processor/FieldsProcessorPluginBaseTest.php
index 9929cb9..f63b359 100644
--- a/tests/src/Unit/Plugin/Processor/FieldsProcessorPluginBaseTest.php
+++ b/tests/src/Unit/Plugin/Processor/FieldsProcessorPluginBaseTest.php
@@ -5,7 +5,7 @@
 use Drupal\search_api\Plugin\search_api\data_type\value\TextToken;
 use Drupal\search_api\Plugin\search_api\data_type\value\TextValue;
 use Drupal\search_api\Query\Condition;
-use Drupal\search_api\Utility;
+use Drupal\search_api\Utility\Utility;
 use Drupal\Tests\UnitTestCase;
 
 /**
@@ -39,7 +39,7 @@ class FieldsProcessorPluginBaseTest extends UnitTestCase {
   public function setUp() {
     parent::setUp();
 
-    $this->setUpDataTypePlugin();
+    $this->setUpMockContainer();
     $this->index = $this->getMock('Drupal\search_api\IndexInterface');
     $this->index->expects($this->any())
       ->method('status')
diff --git a/tests/src/Unit/Plugin/Processor/HighlightTest.php b/tests/src/Unit/Plugin/Processor/HighlightTest.php
index 6c43393..922529a 100644
--- a/tests/src/Unit/Plugin/Processor/HighlightTest.php
+++ b/tests/src/Unit/Plugin/Processor/HighlightTest.php
@@ -2,7 +2,6 @@
 
 namespace Drupal\Tests\search_api\Unit\Plugin\Processor;
 
-use Drupal\Core\DependencyInjection\ContainerBuilder;
 use Drupal\Core\TypedData\DataDefinition;
 use Drupal\search_api\Item\Field;
 use Drupal\search_api\Item\ItemInterface;
@@ -11,7 +10,7 @@
 use Drupal\search_api\Processor\ProcessorProperty;
 use Drupal\search_api\Query\QueryInterface;
 use Drupal\search_api\Query\ResultSet;
-use Drupal\search_api\Utility;
+use Drupal\search_api\Utility\Utility;
 use Drupal\Tests\UnitTestCase;
 
 /**
@@ -45,6 +44,8 @@ class HighlightTest extends UnitTestCase {
   protected function setUp() {
     parent::setUp();
 
+    $this->setUpMockContainer();
+
     $this->processor = new Highlight(array(), 'highlight', array());
 
     $this->index = $this->getMock('Drupal\search_api\IndexInterface');
@@ -154,7 +155,7 @@ public function testPostprocessSearchResultsWithResults() {
       ->will($this->returnValue('foo'));
     /** @var \Drupal\search_api\Query\QueryInterface $query */
 
-    $field = $this->createField('body', 'entity:node/body');
+    $field = $this->createTestField('body', 'entity:node/body');
 
     $this->index->expects($this->atLeastOnce())
       ->method('getFields')
@@ -200,7 +201,7 @@ public function testPostprocessSearchResultsWithChangedPrefixSuffix() {
       ->will($this->returnValue(array('#conjunction' => 'AND', 'foo')));
     /** @var \Drupal\search_api\Query\QueryInterface $query */
 
-    $field = $this->createField('body', 'entity:node/body');
+    $field = $this->createTestField('body', 'entity:node/body');
 
     $this->index->expects($this->atLeastOnce())
       ->method('getFields')
@@ -243,7 +244,7 @@ public function testPostprocessSearchResultsWithoutHighlight() {
       ->will($this->returnValue(array('#conjunction' => 'AND', 'foo')));
     /** @var \Drupal\search_api\Query\QueryInterface $query */
 
-    $field = $this->createField('body', 'entity:node/body');
+    $field = $this->createTestField('body', 'entity:node/body');
 
     $this->index->expects($this->atLeastOnce())
       ->method('getFields')
@@ -284,7 +285,7 @@ public function testPostprocessSearchResultsWithPreviousHighlighting() {
       ->will($this->returnValue(array('#conjunction' => 'AND', 'foo')));
     /** @var \Drupal\search_api\Query\QueryInterface $query */
 
-    $field = $this->createField('body', 'entity:node/body');
+    $field = $this->createTestField('body', 'entity:node/body');
 
     $this->index->expects($this->atLeastOnce())
       ->method('getFields')
@@ -330,7 +331,7 @@ public function testPostprocessSearchResultsExcerpt() {
       ->will($this->returnValue(array('#conjunction' => 'AND', 'congue')));
     /** @var \Drupal\search_api\Query\QueryInterface $query */
 
-    $field = $this->createField('body', 'entity:node/body');
+    $field = $this->createTestField('body', 'entity:node/body');
 
     $this->index->expects($this->atLeastOnce())
       ->method('getFields')
@@ -373,7 +374,7 @@ public function testPostprocessSearchResultsExerptMatchNearEnd() {
       ->will($this->returnValue(array('#conjunction' => 'AND', 'diam')));
     /** @var \Drupal\search_api\Query\QueryInterface $query */
 
-    $field = $this->createField('body', 'entity:node/body');
+    $field = $this->createTestField('body', 'entity:node/body');
 
     $this->index->expects($this->atLeastOnce())
       ->method('getFields')
@@ -418,7 +419,7 @@ public function testPostprocessSearchResultsWithChangedExcerptLength() {
       ->will($this->returnValue('congue'));
     /** @var \Drupal\search_api\Query\QueryInterface $query */
 
-    $field = $this->createField('body', 'entity:node/body');
+    $field = $this->createTestField('body', 'entity:node/body');
 
     $this->index->expects($this->atLeastOnce())
       ->method('getFields')
@@ -463,7 +464,7 @@ public function testPostprocessSearchResultsWithoutExcerpt() {
       ->will($this->returnValue(array('#conjunction' => 'AND', 'congue')));
     /** @var \Drupal\search_api\Query\QueryInterface $query */
 
-    $field = $this->createField('body', 'entity:node/body');
+    $field = $this->createTestField('body', 'entity:node/body');
 
     $this->index->expects($this->atLeastOnce())
       ->method('getFields')
@@ -520,7 +521,7 @@ public function testPostprocessSearchResultsWithComplexKeys() {
       ->will($this->returnValue($keys));
     /** @var \Drupal\search_api\Query\QueryInterface $query */
 
-    $body_field = $this->createField('body', 'entity:node/body');
+    $body_field = $this->createTestField('body', 'entity:node/body');
 
     $this->index->expects($this->atLeastOnce())
       ->method('getFields')
@@ -565,8 +566,8 @@ public function testPostprocessSearchResultsWithTwoFields() {
       ->will($this->returnValue(array('#conjunction' => 'AND', 'foo')));
     /** @var \Drupal\search_api\Query\QueryInterface $query */
 
-    $body_field = $this->createField('body', 'entity:node/body');
-    $title_field = $this->createField('title', 'title');
+    $body_field = $this->createTestField('body', 'entity:node/body');
+    $title_field = $this->createTestField('title', 'title');
 
     $this->index->expects($this->atLeastOnce())
       ->method('getFields')
@@ -623,7 +624,7 @@ public function testPostprocessSearchResultsWithTwoItems() {
       ->will($this->returnValue(array('#conjunction' => 'OR', 'foo')));
     /** @var \Drupal\search_api\Query\QueryInterface $query */
 
-    $body_field = $this->createField('body', 'entity:node/body');
+    $body_field = $this->createTestField('body', 'entity:node/body');
 
     $this->index->expects($this->atLeastOnce())
       ->method('getFields')
@@ -674,8 +675,8 @@ public function testExcerptExcludeFields() {
       ->will($this->returnValue(array('#conjunction' => 'AND', 'foo')));
     /** @var \Drupal\search_api\Query\QueryInterface $query */
 
-    $body_field = $this->createField('body', 'entity:node/body');
-    $title_field = $this->createField('title', 'title');
+    $body_field = $this->createTestField('body', 'entity:node/body');
+    $title_field = $this->createTestField('title', 'title');
 
     $this->index->expects($this->atLeastOnce())
       ->method('getFields')
@@ -736,6 +737,10 @@ public function testFieldExtraction() {
       ->willReturnMap(array(
         array('foo', $bar_foo_property),
       ));
+    $bar_property->method('getProperties')
+      ->willReturn(array(
+        'foo' => TRUE,
+      ));
     $foobar_property = $this->getMock('Drupal\Core\TypedData\TypedDataInterface');
     $foobar_property->method('getValue')
       ->willReturn('wrong_value2 foo');
@@ -746,14 +751,19 @@ public function testFieldExtraction() {
         array('bar', $bar_property),
         array('foobar', $foobar_property),
       ));
+    $object->method('getProperties')
+      ->willReturn(array(
+        'bar' => TRUE,
+        'foobar' => TRUE,
+      ));
 
     $this->index->method('getFields')
       ->willReturn(array(
-        'field1' => $this->createField('field1', 'entity:test1/bar:foo'),
-        'field2' => $this->createField('field2', 'entity:test2/foobar'),
-        'field3' => $this->createField('field3', 'foo'),
-        'field4' => $this->createField('field4', 'baz', FALSE),
-        'field5' => $this->createField('field5', 'entity:test1/foobar'),
+        'field1' => $this->createTestField('field1', 'entity:test1/bar:foo'),
+        'field2' => $this->createTestField('field2', 'entity:test2/foobar'),
+        'field3' => $this->createTestField('field3', 'foo'),
+        'field4' => $this->createTestField('field4', 'baz', FALSE),
+        'field5' => $this->createTestField('field5', 'entity:test1/foobar'),
       ));
     $this->index->method('getPropertyDefinitions')
       ->willReturnMap(array(
@@ -794,13 +804,6 @@ public function testFieldExtraction() {
       ));
     $this->processor->setIndex($this->index);
 
-    $container = new ContainerBuilder();
-    $data_type_manager = $this->getMockBuilder('Drupal\search_api\DataType\DataTypePluginManager')
-      ->disableOriginalConstructor()
-      ->getMock();
-    $container->set('plugin.manager.search_api.data_type', $data_type_manager);
-    \Drupal::setContainer($container);
-
     /** @var \Drupal\search_api\Datasource\DatasourceInterface|\PHPUnit_Framework_MockObject_MockObject $datasource */
     $datasource = $this->getMock('Drupal\search_api\Datasource\DatasourceInterface');
     $datasource->method('getPluginId')
@@ -808,10 +811,10 @@ public function testFieldExtraction() {
 
     $item = Utility::createItem($this->index, 'id', $datasource);
     $item->setOriginalObject($object);
-    $field = $this->createField('field4', 'baz')
+    $field = $this->createTestField('field4', 'baz')
       ->addValue('wrong_value1 foo');
     $item->setField('field4', $field);
-    $field = $this->createField('field5', 'entity:test1/foobar')
+    $field = $this->createTestField('field5', 'entity:test1/foobar')
       ->addValue('value1 foo')
       ->addValue('value2 foo');
     $item->setField('field5', $field);
@@ -860,7 +863,7 @@ public function testFieldExtraction() {
    * @return \Drupal\search_api\Item\FieldInterface
    *   A field object.
    */
-  protected function createField($id, $combined_property_path, $text = TRUE) {
+  protected function createTestField($id, $combined_property_path, $text = TRUE) {
     $field = new Field($this->index, $id);
     list ($datasource_id, $property_path) = Utility::splitCombinedId($combined_property_path);
     $field->setDatasourceId($datasource_id);
diff --git a/tests/src/Unit/Plugin/Processor/HtmlFilterTest.php b/tests/src/Unit/Plugin/Processor/HtmlFilterTest.php
index d32a629..b289edb 100644
--- a/tests/src/Unit/Plugin/Processor/HtmlFilterTest.php
+++ b/tests/src/Unit/Plugin/Processor/HtmlFilterTest.php
@@ -2,9 +2,8 @@
 
 namespace Drupal\Tests\search_api\Unit\Plugin\Processor;
 
-use Drupal\Core\DependencyInjection\ContainerBuilder;
 use Drupal\search_api\Plugin\search_api\processor\HtmlFilter;
-use Drupal\search_api\Utility;
+use Drupal\search_api\Utility\Utility;
 use Drupal\Tests\UnitTestCase;
 
 /**
@@ -17,12 +16,16 @@
 class HtmlFilterTest extends UnitTestCase {
 
   use ProcessorTestTrait;
+  use TestItemsTrait;
 
   /**
    * Creates a new processor object for use in the tests.
    */
   public function setUp() {
     parent::setUp();
+
+    $this->setUpMockContainer();
+
     $this->processor = new HtmlFilter(array(), 'html_filter', array());
   }
 
@@ -229,13 +232,6 @@ public function tagConfigurationDataProvider() {
    * @dataProvider stringProcessingDataProvider
    */
   public function testStringProcessing(array $config) {
-    $data_type_manager = $this->getMockBuilder('Drupal\search_api\DataType\DataTypePluginManager')
-      ->disableOriginalConstructor()
-      ->getMock();
-    $container = new ContainerBuilder();
-    $container->set('plugin.manager.search_api.data_type', $data_type_manager);
-    \Drupal::setContainer($container);
-
     $this->processor->setConfiguration($config);
 
     $passed_value = '<h2>Foo Bar <em>Baz</em></h2>
diff --git a/tests/src/Unit/Plugin/Processor/NodeStatusTest.php b/tests/src/Unit/Plugin/Processor/NodeStatusTest.php
index ca30bfd..a1abf22 100644
--- a/tests/src/Unit/Plugin/Processor/NodeStatusTest.php
+++ b/tests/src/Unit/Plugin/Processor/NodeStatusTest.php
@@ -4,7 +4,7 @@
 
 use Drupal\Core\Entity\Plugin\DataType\EntityAdapter;
 use Drupal\search_api\Plugin\search_api\processor\NodeStatus;
-use Drupal\search_api\Utility;
+use Drupal\search_api\Utility\Utility;
 use Drupal\Tests\UnitTestCase;
 
 /**
@@ -16,6 +16,8 @@
  */
 class NodeStatusTest extends UnitTestCase {
 
+  use TestItemsTrait;
+
   /**
    * The processor to be tested.
    *
@@ -36,6 +38,8 @@ class NodeStatusTest extends UnitTestCase {
   protected function setUp() {
     parent::setUp();
 
+    $this->setUpMockContainer();
+
     $this->processor = new NodeStatus(array(), 'node_status', array());
 
     $datasource = $this->getMock('Drupal\search_api\Datasource\DatasourceInterface');
diff --git a/tests/src/Unit/Plugin/Processor/RoleFilterTest.php b/tests/src/Unit/Plugin/Processor/RoleFilterTest.php
index 47b69c5..1f0d1a4 100644
--- a/tests/src/Unit/Plugin/Processor/RoleFilterTest.php
+++ b/tests/src/Unit/Plugin/Processor/RoleFilterTest.php
@@ -4,7 +4,7 @@
 
 use Drupal\Core\Entity\Plugin\DataType\EntityAdapter;
 use Drupal\search_api\Plugin\search_api\processor\RoleFilter;
-use Drupal\search_api\Utility;
+use Drupal\search_api\Utility\Utility;
 use Drupal\Tests\UnitTestCase;
 
 /**
@@ -16,6 +16,8 @@
  */
 class RoleFilterTest extends UnitTestCase {
 
+  use TestItemsTrait;
+
   /**
    * The processor to be tested.
    *
@@ -36,6 +38,8 @@ class RoleFilterTest extends UnitTestCase {
   protected function setUp() {
     parent::setUp();
 
+    $this->setUpMockContainer();
+
     $this->processor = new RoleFilter(array(), 'role_filter', array());
 
     /** @var \Drupal\search_api\IndexInterface $index */
diff --git a/tests/src/Unit/Plugin/Processor/StopwordsTest.php b/tests/src/Unit/Plugin/Processor/StopwordsTest.php
index 9d93786..c0eeea2 100644
--- a/tests/src/Unit/Plugin/Processor/StopwordsTest.php
+++ b/tests/src/Unit/Plugin/Processor/StopwordsTest.php
@@ -3,7 +3,7 @@
 namespace Drupal\Tests\search_api\Unit\Plugin\Processor;
 
 use Drupal\search_api\Plugin\search_api\processor\Stopwords;
-use Drupal\search_api\Utility;
+use Drupal\search_api\Utility\Utility;
 use Drupal\Tests\UnitTestCase;
 
 /**
@@ -22,7 +22,7 @@ class StopwordsTest extends UnitTestCase {
    */
   protected function setUp() {
     parent::setUp();
-    $this->setUpDataTypePlugin();
+    $this->setUpMockContainer();
     $this->processor = new Stopwords(array(), 'stopwords', array());;
   }
 
diff --git a/tests/src/Unit/Plugin/Processor/TestFieldsProcessorPlugin.php b/tests/src/Unit/Plugin/Processor/TestFieldsProcessorPlugin.php
index 922fc69..e9dbd0b 100644
--- a/tests/src/Unit/Plugin/Processor/TestFieldsProcessorPlugin.php
+++ b/tests/src/Unit/Plugin/Processor/TestFieldsProcessorPlugin.php
@@ -5,7 +5,7 @@
 use Drupal\search_api\Item\FieldInterface;
 use Drupal\search_api\Plugin\search_api\data_type\value\TextValue;
 use Drupal\search_api\Processor\FieldsProcessorPluginBase;
-use Drupal\search_api\Utility;
+use Drupal\search_api\Utility\Utility;
 
 /**
  * Mimics a processor working on individual fields of items.
diff --git a/tests/src/Unit/Plugin/Processor/TestItemsTrait.php b/tests/src/Unit/Plugin/Processor/TestItemsTrait.php
index e93c9f1..46a9df3 100644
--- a/tests/src/Unit/Plugin/Processor/TestItemsTrait.php
+++ b/tests/src/Unit/Plugin/Processor/TestItemsTrait.php
@@ -5,8 +5,13 @@
 use Drupal\Core\DependencyInjection\ContainerBuilder;
 use Drupal\Core\TypedData\ComplexDataInterface;
 use Drupal\search_api\IndexInterface;
+use Drupal\search_api\Item\Field;
 use Drupal\search_api\Item\FieldInterface;
-use Drupal\search_api\Utility;
+use Drupal\search_api\Utility\DataTypeHelper;
+use Drupal\search_api\Utility\FieldsHelper;
+use Drupal\search_api\Item\Item;
+use Drupal\search_api\Query\Query;
+use Drupal\search_api\Utility\Utility;
 
 /**
  * Provides common methods for test cases that need to create search items.
@@ -32,29 +37,29 @@
    *
    * @param \Drupal\search_api\IndexInterface $index
    *   The index that should be used for the item.
-   * @param string $field_type
+   * @param string $fieldType
    *   The field type to set for the field.
-   * @param mixed $field_value
+   * @param mixed $fieldValue
    *   A field value to add to the field.
    * @param \Drupal\search_api\Item\FieldInterface $field
    *   (optional) A variable, passed by reference, into which the created field
    *   will be saved.
-   * @param string $field_id
+   * @param string $fieldId
    *   (optional) The field ID to set for the field.
    *
    * @return \Drupal\search_api\Item\ItemInterface[]
    *   An array containing a single item with the specified field.
    */
-  public function createSingleFieldItem(IndexInterface $index, $field_type, $field_value, FieldInterface &$field = NULL, $field_id = 'field_test') {
-    $this->itemIds[0] = $item_id = Utility::createCombinedId('entity:node', '1:en');
-    $item = Utility::createItem($index, $item_id);
-    $field = Utility::createField($index, $field_id);
-    $field->setType($field_type);
-    $field->addValue($field_value);
-    $item->setField($field_id, $field);
+  public function createSingleFieldItem(IndexInterface $index, $fieldType, $fieldValue, FieldInterface &$field = NULL, $fieldId = 'field_test') {
+    $this->itemIds[0] = $itemId = Utility::createCombinedId('entity:node', '1:en');
+    $item = new Item($index, $itemId);
+    $field = new Field($index, $fieldId);
+    $field->setType($fieldType);
+    $field->addValue($fieldValue);
+    $item->setField($fieldId, $field);
     $item->setFieldsExtracted(TRUE);
 
-    return array($item_id => $item);
+    return array($itemId => $item);
   }
 
   /**
@@ -82,7 +87,7 @@ public function createItems(IndexInterface $index, $count, array $fields, Comple
     for ($i = 0; $i < $count; ++$i) {
       $datasource_id = $datasource_ids[$i % $datasource_count];
       $this->itemIds[$i] = $item_id = Utility::createCombinedId($datasource_id, ($i + 1) . ':en');
-      $item = Utility::createItem($index, $item_id);
+      $item = new Item($index, $item_id);
       if (isset($object)) {
         $item->setOriginalObject($object);
       }
@@ -92,7 +97,9 @@ public function createItems(IndexInterface $index, $count, array $fields, Comple
         if (isset($field_info['datasource_id']) && $field_info['datasource_id'] != $datasource_id) {
           continue;
         }
-        $field_id = Utility::getNewFieldId($index, $field_info['property_path']);
+        $field_id = \Drupal::getContainer()
+          ->get('search_api.fields_helper')
+          ->getNewFieldId($index, $field_info['property_path']);
         $field = Utility::createField($index, $field_id, $field_info);
         $item->setField($field_id, $field);
       }
@@ -103,27 +110,42 @@ public function createItems(IndexInterface $index, $count, array $fields, Comple
   }
 
   /**
-   * Adds mock data type plugin manager and results cache services to \Drupal.
+   * Adds a container with several mock services commonly needed by our tests.
    */
-  protected function setUpDataTypePlugin() {
+  protected function setUpMockContainer() {
     /** @var \Drupal\Tests\UnitTestCase|\Drupal\Tests\search_api\Unit\Plugin\Processor\TestItemsTrait $this */
-    $data_type_plugin = $this->getMockBuilder('Drupal\search_api\DataType\DataTypePluginManager')
+    $dataTypeManager = $this->getMockBuilder('Drupal\search_api\DataType\DataTypePluginManager')
       ->disableOriginalConstructor()
       ->getMock();
-    $data_type_plugin->expects($this->any())
-      ->method('getInstances')
+    $dataTypeManager->method('getInstances')
       ->will($this->returnValue(array()));
 
-    $results_static_cache = $this->getMockBuilder('Drupal\search_api\Query\ResultsCache')
+    $moduleHandler = $this->getMockBuilder('Drupal\Core\Extension\ModuleHandlerInterface')
       ->disableOriginalConstructor()
       ->getMock();
-    $results_static_cache->expects($this->any())
-      ->method('getResults')
+    $dataTypeHelper = new DataTypeHelper($moduleHandler, $dataTypeManager);
+
+    $entityFieldManager = $this->getMockBuilder('Drupal\Core\Entity\EntityFieldManagerInterface')
+      ->disableOriginalConstructor()
+      ->getMock();
+    $entityBundleInfo = $this->getMockBuilder('Drupal\Core\Entity\EntityTypeBundleInfoInterface')
+      ->disableOriginalConstructor()
+      ->getMock();
+    $fieldsHelper = new FieldsHelper($entityFieldManager, $entityBundleInfo, $dataTypeHelper);
+
+    $queryHelper = $this->getMock('Drupal\search_api\Utility\QueryHelperInterface');
+    $queryHelper->method('createQuery')
+      ->willReturnCallback(function (IndexInterface $index, array $options = array()) {
+        return Query::create($index, $options);
+      });
+    $queryHelper->method('getResults')
       ->will($this->returnValue(array()));
 
     $this->container = new ContainerBuilder();
-    $this->container->set('plugin.manager.search_api.data_type', $data_type_plugin);
-    $this->container->set('search_api.results_static_cache', $results_static_cache);
+    $this->container->set('plugin.manager.search_api.data_type', $dataTypeManager);
+    $this->container->set('search_api.data_type_helper', $dataTypeHelper);
+    $this->container->set('search_api.fields_helper', $fieldsHelper);
+    $this->container->set('search_api.query_helper', $queryHelper);
     \Drupal::setContainer($this->container);
   }
 
diff --git a/tests/src/Unit/Plugin/Processor/TokenizerTest.php b/tests/src/Unit/Plugin/Processor/TokenizerTest.php
index 2b46854..f6981ea 100644
--- a/tests/src/Unit/Plugin/Processor/TokenizerTest.php
+++ b/tests/src/Unit/Plugin/Processor/TokenizerTest.php
@@ -4,7 +4,7 @@
 
 use Drupal\Component\Utility\Unicode;
 use Drupal\search_api\Plugin\search_api\processor\Tokenizer;
-use Drupal\search_api\Utility;
+use Drupal\search_api\Utility\Utility;
 use Drupal\Tests\UnitTestCase;
 
 /**
diff --git a/tests/src/Unit/Plugin/Processor/TransliterationTest.php b/tests/src/Unit/Plugin/Processor/TransliterationTest.php
index bb233dc..555988a 100644
--- a/tests/src/Unit/Plugin/Processor/TransliterationTest.php
+++ b/tests/src/Unit/Plugin/Processor/TransliterationTest.php
@@ -31,7 +31,7 @@ public function setUp() {
 
     $this->index = $this->getMock('Drupal\search_api\IndexInterface');
 
-    $this->setUpDataTypePlugin();
+    $this->setUpMockContainer();
     $this->processor = new Transliteration(array(), 'transliteration', array());
     $this->processor->setLangcode('en');
 
