diff --git a/src/TreeBuilder.php b/src/TreeBuilder.php
new file mode 100644
index 0000000..3f68c63
--- /dev/null
+++ b/src/TreeBuilder.php
@@ -0,0 +1,191 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\token\TreeBuilder.
+ */
+
+namespace Drupal\token;
+
+use Drupal\Core\Cache\Cache;
+use Drupal\Core\Cache\CacheBackendInterface;
+use Drupal\Core\Language\LanguageManagerInterface;
+
+class TreeBuilder implements TreeBuilderInterface {
+
+  /**
+   * @var \Drupal\token\Token
+   */
+  protected $tokenService;
+
+  /**
+   * @var \Drupal\Core\Language\LanguageManagerInterface
+   */
+  protected $languageManager;
+
+  /**
+   * @var \Drupal\Core\Cache\CacheBackendInterface
+   */
+  protected $cacheBackend;
+
+  /**
+   * Cache already built trees.
+   *
+   * @var array
+   */
+  protected $builtTrees;
+
+  public function __construct(TokenInterface $token_service, CacheBackendInterface $cache_backend, LanguageManagerInterface $language_manager) {
+    $this->tokenService = $token_service;
+    $this->cacheBackend = $cache_backend;
+    $this->languageManager = $language_manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildTree($token_type, array $options = []) {
+    $options += [
+      'restricted' => FALSE,
+      'depth' => 4,
+      'data' => [],
+      'values' => FALSE,
+      'flat' => FALSE,
+    ];
+
+    // Do not allow past the maximum token information depth.
+    $options['depth'] = min($options['depth'], TOKEN_MAX_DEPTH);
+
+    // If $token_type is an entity, make sure we are using the actual token type.
+    if ($entity_token_type = token_get_entity_mapping('entity', $token_type)) {
+      $token_type = $entity_token_type;
+    }
+
+    $langcode = $this->languageManager->getCurrentLanguage()->getId();
+    $tree_cid = "token_tree:{$token_type}:{$langcode}:{$options['depth']}";
+
+    // If we do not have this base tree in the static cache, check the cache
+    // otherwise generate and store it in the cache.
+    if (!isset($this->builtTrees[$tree_cid])) {
+      if ($cache = $this->cacheBackend->get($tree_cid)) {
+        $this->builtTrees[$tree_cid] = $cache->data;
+      }
+      else {
+        $options['parents'] = [];
+        $this->builtTrees[$tree_cid] = $this->getTokenData($token_type, $options);
+        $this->cacheBackend->set($tree_cid, $this->builtTrees[$tree_cid], Cache::PERMANENT, [Token::TOKEN_INFO_CACHE_TAG]);
+      }
+    }
+
+    $tree = $this->builtTrees[$tree_cid];
+
+    // If the user has requested a flat tree, convert it.
+    if (!empty($options['flat'])) {
+      $tree = $this->flattenTree($tree);
+    }
+
+    // Fill in token values.
+    if (!empty($options['values'])) {
+      $token_values = [];
+      foreach ($tree as $token => $token_info) {
+        if (!empty($token_info['dynamic']) || !empty($token_info['restricted'])) {
+          continue;
+        }
+        elseif (!isset($token_info['value'])) {
+          $token_values[$token_info['token']] = $token;
+        }
+      }
+      if (!empty($token_values)) {
+        $token_values = $this->tokenService->generate($token_type, $token_values, $options['data']);
+        foreach ($token_values as $token => $replacement) {
+          $tree[$token]['value'] = $replacement;
+        }
+      }
+    }
+
+    return $tree;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function flattenTree(array $tree) {
+    $result = [];
+    foreach ($tree as $token => $token_info) {
+      $result[$token] = $token_info;
+      if (isset($token_info['children']) && is_array($token_info['children'])) {
+        $result += $this->flattenTree($token_info['children']);
+      }
+    }
+    return $result;
+  }
+
+  /**
+   * Generate a token tree.
+   *
+   * @param string $token_type
+   *   The token type.
+   * @param array $options
+   *   An associative array of additional options. See documentation for
+   *   TreeBuilderInterface::buildTree() for more information.
+   *
+   * @return array
+   *   The token data for the specified $token_type.
+   *
+   * @internal
+   */
+  protected function getTokenData($token_type, array $options) {
+    $options += [
+      'parents' => [],
+    ];
+
+    $info = $this->tokenService->getInfo();
+    if ($options['depth'] <= 0 || !isset($info['types'][$token_type]) || !isset($info['tokens'][$token_type])) {
+      return [];
+    }
+
+    $tree = [];
+    foreach ($info['tokens'][$token_type] as $token => $token_info) {
+      // Build the raw token string.
+      $token_parents = $options['parents'];
+      if (empty($token_parents)) {
+        // If the parents array is currently empty, assume the token type is its
+        // parent.
+        $token_parents[] = $token_type;
+      }
+      elseif (in_array($token, array_slice($token_parents, 1), TRUE)) {
+        // Prevent duplicate recursive tokens. For example, this will prevent
+        // the tree from generating the following tokens or deeper:
+        // [comment:parent:parent]
+        // [comment:parent:root:parent]
+        continue;
+      }
+
+      $token_parents[] = $token;
+      if (!empty($token_info['dynamic'])) {
+        $token_parents[] = '?';
+      }
+      $raw_token = '[' . implode(':', $token_parents) . ']';
+      $tree[$raw_token] = $token_info;
+      $tree[$raw_token]['raw token'] = $raw_token;
+
+      // Add the token's real name (leave out the base token type).
+      $tree[$raw_token]['token'] = implode(':', array_slice($token_parents, 1));
+
+      // Add the token's parent as its raw token value.
+      if (!empty($options['parents'])) {
+        $tree[$raw_token]['parent'] = '[' . implode(':', $options['parents']) . ']';
+      }
+
+      // Fetch the child tokens.
+      if (!empty($token_info['type'])) {
+        $child_options = $options;
+        $child_options['depth']--;
+        $child_options['parents'] = $token_parents;
+        $tree[$raw_token]['children'] = $this->getTokenData($token_info['type'], $child_options);
+      }
+    }
+
+    return $tree;
+  }
+}
diff --git a/src/TreeBuilderInterface.php b/src/TreeBuilderInterface.php
new file mode 100644
index 0000000..a321e10
--- /dev/null
+++ b/src/TreeBuilderInterface.php
@@ -0,0 +1,42 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\token\TreeBuilderInterface.
+ */
+
+namespace Drupal\token;
+
+interface TreeBuilderInterface {
+
+  /**
+   * Build a tree array of tokens used for themeing or information.
+   *
+   * @param string $token_type
+   *   The token type.
+   * @param array $options
+   *   (optional) An associative array of additional options, with the following
+   *   elements:
+   *   - 'flat' (defaults to FALSE): Set to true to generate a flat list of
+   *     token information. Otherwise, child tokens will be inside the
+   *     'children' parameter of a token.
+   *   - 'restricted' (defaults to FALSE): Set to true to how restricted tokens.
+   *   - 'depth' (defaults to 4): Maximum number of token levels to recurse.
+   *
+   * @return array
+   *   The token information constructed in a tree or flat list form depending
+   *   on $options['flat'].
+   */
+  public function buildTree($token_type, array $options = []);
+
+  /**
+   * Flatten a token tree.
+   *
+   * @param array $tree
+   *   The tree array as returned by TreeBuilderInterface::buildTree().
+   *
+   * @return array
+   *   The flattened version of the tree.
+   */
+  public function flattenTree(array $tree);
+}
diff --git a/token.module b/token.module
index 1243b4e..5c079e0 100644
--- a/token.module
+++ b/token.module
@@ -625,143 +625,11 @@ function token_form_user_admin_settings_alter(&$form, FormStateInterface $form_s
  *   An integer with the maximum number of token levels to recurse.
  * @param $parents
  *   An optional array with the current parents of the tokens.
+ *
+ * @deprecated
  */
 function token_build_tree($token_type, array $options = array()) {
-  // Static cache of already built token trees.
-  $trees = &drupal_static(__FUNCTION__, array());
-
-  $options += array(
-    'restricted' => FALSE,
-    'depth' => 4,
-    'data' => array(),
-    'values' => FALSE,
-    'flat' => FALSE,
-  );
-
-  // Do not allow past the maximum token information depth.
-  $options['depth'] = min($options['depth'], TOKEN_MAX_DEPTH);
-
-  // If $token_type is an entity, make sure we are using the actual token type.
-  if ($entity_token_type = token_get_entity_mapping('entity', $token_type)) {
-    $token_type = $entity_token_type;
-  }
-
-  $langcode = \Drupal::languageManager()->getCurrentLanguage()->getId();
-  $tree_cid = "token_tree:{$token_type}:{$langcode}:{$options['depth']}";
-
-  // If we do not have this base tree in the static cache, check the cache
-  // otherwise generate and store it in the cache.
-  if (!isset($trees[$tree_cid])) {
-    if ($cache = \Drupal::cache('data')->get($tree_cid)) {
-      $trees[$tree_cid] = $cache->data;
-    }
-    else {
-      $options['parents'] = array();
-      $trees[$tree_cid] = _token_build_tree($token_type, $options);
-      \Drupal::cache('data')->set($tree_cid, $trees[$tree_cid], Cache::PERMANENT, array(Token::TOKEN_INFO_CACHE_TAG));
-    }
-  }
-
-  $tree = $trees[$tree_cid];
-
-  // If the user has requested a flat tree, convert it.
-  if (!empty($options['flat'])) {
-    $tree = token_flatten_tree($tree);
-  }
-
-  // Fill in token values.
-  if (!empty($options['values'])) {
-    $token_values = array();
-    foreach ($tree as $token => $token_info) {
-      if (!empty($token_info['dynamic']) || !empty($token_info['restricted'])) {
-        continue;
-      }
-      elseif (!isset($token_info['value'])) {
-        $token_values[$token_info['token']] = $token;
-      }
-    }
-    if (!empty($token_values)) {
-      $token_values = \Drupal::token()->generate($token_type, $token_values, $options['data']);
-      foreach ($token_values as $token => $replacement) {
-        $tree[$token]['value'] = $replacement;
-      }
-    }
-  }
-
-  return $tree;
-}
-
-/**
- * Flatten a token tree.
- */
-function token_flatten_tree($tree) {
-  $result = array();
-  foreach ($tree as $token => $token_info) {
-    $result[$token] = $token_info;
-    if (isset($token_info['children']) && is_array($token_info['children'])) {
-      $result += token_flatten_tree($token_info['children']);
-      // unset($result[$token]['children']);
-    }
-  }
-  return $result;
-}
-
-/**
- * Generate a token tree.
- */
-function _token_build_tree($token_type, array $options) {
-  $options += array(
-    'parents' => array(),
-  );
-
-  $info = token_get_info();
-  if ($options['depth'] <= 0 || !isset($info['types'][$token_type]) || !isset($info['tokens'][$token_type])) {
-    return array();
-  }
-
-  $tree = array();
-  foreach ($info['tokens'][$token_type] as $token => $token_info) {
-    // Build the raw token string.
-    $token_parents = $options['parents'];
-    if (empty($token_parents)) {
-      // If the parents array is currently empty, assume the token type is its
-      // parent.
-      $token_parents[] = $token_type;
-    }
-    elseif (in_array($token, array_slice($token_parents, 1), TRUE)) {
-      // Prevent duplicate recursive tokens. For example, this will prevent
-      // the tree from generating the following tokens or deeper:
-      // [comment:parent:parent]
-      // [comment:parent:root:parent]
-      continue;
-    }
-
-    $token_parents[] = $token;
-    if (!empty($token_info['dynamic'])) {
-      $token_parents[] = '?';
-    }
-    $raw_token = '[' . implode(':', $token_parents) . ']';
-    $tree[$raw_token] = $token_info;
-    $tree[$raw_token]['raw token'] = $raw_token;
-
-    // Add the token's real name (leave out the base token type).
-    $tree[$raw_token]['token'] = implode(':', array_slice($token_parents, 1));
-
-    // Add the token's parent as its raw token value.
-    if (!empty($options['parents'])) {
-      $tree[$raw_token]['parent'] = '[' . implode(':', $options['parents']) . ']';
-    }
-
-    // Fetch the child tokens.
-    if (!empty($token_info['type'])) {
-      $child_options = $options;
-      $child_options['depth']--;
-      $child_options['parents'] = $token_parents;
-      $tree[$raw_token]['children'] = _token_build_tree($token_info['type'], $child_options);
-    }
-  }
-
-  return $tree;
+  return \Drupal::service('token.tree_builder')->buildTree($token_type, $options);
 }
 
 /**
diff --git a/token.services.yml b/token.services.yml
index 0baad47..a905b9b 100644
--- a/token.services.yml
+++ b/token.services.yml
@@ -1 +1,4 @@
 services:
+  token.tree_builder:
+    class: Drupal\token\TreeBuilder
+    arguments: ['@token', '@cache.data', '@language_manager']
