diff --git a/entity_ui.module b/entity_ui.module
index d5e4ef3..9f445a9 100644
--- a/entity_ui.module
+++ b/entity_ui.module
@@ -4,7 +4,8 @@
  * @file
  * Primary hook implementations for the Entity UI Builder module.
  */
-
+use Drupal\Core\Hook\Attribute\LegacyHook;
+use Drupal\entity_ui\Hook\EntityUiHooks;
 use Drupal\entity_ui\EntityHandler\BundleEntityCollection;
 use Drupal\entity_ui\EntityHandler\FieldUIWithoutBundleEntityProxy;
 use Drupal\entity_ui\Routing\TabRouteProvider;
@@ -12,98 +13,17 @@ use Drupal\entity_ui\Routing\TabRouteProvider;
 /**
  * Implements hook_entity_type_build().
  */
-function entity_ui_entity_type_build(array &$entity_types) {
-  $target_entity_types = \Drupal::service('entity_ui.target_entity_types')->filterTargetEntityTypes($entity_types);
-  foreach ($target_entity_types as $entity_type_id => $entity_type) {
-    // Add our route provider for the tabs we add to entities.
-    // @todo Core forgot to add a direct way to manipulate route_provider, so
-    // we have to do it the sloppy way for now.
-    $providers = $entity_type->getRouteProviderClasses() ?: [];
-    if (empty($providers['entity_ui'])) {
-      $providers['entity_ui'] = TabRouteProvider::class;
-      $entity_type->setHandlerClass('route_provider', $providers);
-    }
-
-    // Add our admin UI handler.
-    // Which handler class we add depends on what sort of admin UI the entity
-    // type has.
-    // @see \Drupal\entity_ui\EntityHandler\EntityUIAdminInterface
-    // @todo Consider moving this logic to static methods on the actual handler
-    // classes.
-    // Don't set a handler if the entity type has one set already. This allows
-    // entity type modules to define their own handlers if their UIs require it.
-    if ($entity_type->hasHandlerClass('entity_ui_admin')) {
-      continue;
-    }
-
-    // Skip bundles which are defined by plugins (using entity module).
-    // @todo Implement this.
-    // @see https://www.drupal.org/project/entity_ui/issues/3301484
-    if ($entity_type->get('bundle_plugin_type')) {
-      continue;
-    }
-
-    // Case 1: The entity type has a bundle entity type, and there is a link
-    // template for the collection of bundle entities.
-    if ($bundle_entity_type_id = $entity_type->getBundleEntityType()) {
-      $bundle_entity_type = $entity_types[$bundle_entity_type_id];
-      if ($bundle_collection_link_template = $bundle_entity_type->getLinkTemplate('collection')) {
-        // We ideally should check that there is a route actually defined for
-        // the link template, as if the entity's module has forgotten to do this
-        // our handler can't do anything, but this causes problems in tests and
-        // if we are enabled at the same time as entity type modules.
-        // @see https://www.drupal.org/node/2896669
-        $entity_type->setHandlerClass('entity_ui_admin', BundleEntityCollection::class);
-
-        // Done with this entity type.
-        continue;
-      }
-      else {
-        // If there is no link template for the collection of bundle entities,
-        // we don't know what to do.
-        continue;
-      }
-    }
-
-    // If there is no bundle entity, but there is a field UI base route, then
-    // there are these two cases, but we cannot distinguish between them at this
-    // point:
-    // - PlainBundlesEntityUIAdmin: an entity type that allows multiple
-    //   bundles, but where these are not defined by a config entity.
-    // - BasicFieldUI: an entity type that only has a single bundle.
-    // This is because we would either need to check the path of the field admin
-    // UI route for a '{bundle}' path component, and we cannot load routes
-    // during entity type build, or get the number of bundles from the
-    // entity_type.bundle.info service, and we can't call that during entity
-    // type build.
-    // Instead, we use a proxy handler, which will instantiate the real handlers
-    // during the router rebuild.
-    $field_ui_base_route_name = $entity_type->get('field_ui_base_route');
-
-    if ($field_ui_base_route_name) {
-      $entity_type->setHandlerClass('entity_ui_admin', FieldUIWithoutBundleEntityProxy::class);
-
-      // Done with this entity type.
-      continue;
-    }
-
-    // Other cases are not yet handled.
-  }
+#[LegacyHook]
+function entity_ui_entity_type_build(array &$entity_types)
+{
+    \Drupal::service(EntityUiHooks::class)->entityTypeBuild($entity_types);
 }
 
 /**
  * Implements hook_local_tasks_alter().
  */
-function entity_ui_local_tasks_alter(&$local_tasks) {
-  // Allow Entity UI admin handlers to alter local tasks for an entity type's
-  // admin UI.
-  $entity_type_manager = \Drupal::service('entity_type.manager');
-  $entity_types = $entity_type_manager->getDefinitions();
-  foreach ($entity_types as $entity_type_id => $entity_type) {
-    if ($entity_type_manager->hasHandler($entity_type_id, 'entity_ui_admin')) {
-      $entity_ui_admin_handler = $entity_type_manager->getHandler($entity_type_id, 'entity_ui_admin');
-
-      $entity_ui_admin_handler->localTasksAlter($local_tasks);
-    }
-  }
+#[LegacyHook]
+function entity_ui_local_tasks_alter(&$local_tasks)
+{
+    \Drupal::service(EntityUiHooks::class)->localTasksAlter($local_tasks);
 }
diff --git a/entity_ui.services.yml b/entity_ui.services.yml
index 184e0e6..e186e74 100644
--- a/entity_ui.services.yml
+++ b/entity_ui.services.yml
@@ -20,3 +20,11 @@ services:
     parent: system.breadcrumb.default
     tags:
      - { name: breadcrumb_builder, priority: 100 }
+
+  Drupal\entity_ui\Hook\EntityUiHooks:
+    class: Drupal\entity_ui\Hook\EntityUiHooks
+    autowire: true
+
+  Drupal\entity_ui\Hook\EntityUiTokensHooks:
+    class: Drupal\entity_ui\Hook\EntityUiTokensHooks
+    autowire: true
diff --git a/entity_ui.tokens.inc b/entity_ui.tokens.inc
index 04f0c4b..8e34de8 100644
--- a/entity_ui.tokens.inc
+++ b/entity_ui.tokens.inc
@@ -4,44 +4,24 @@
  * @file
  * Contains token hooks.
  */
-
+use Drupal\Core\Hook\Attribute\LegacyHook;
+use Drupal\entity_ui\Hook\EntityUiTokensHooks;
 use Drupal\Core\Render\BubbleableMetadata;
 
 /**
  * Implements hook_token_info().
  */
-function entity_ui_token_info() {
-  $type = [
-    'name' => t('Entity UI tab'),
-    'description' => t('Entity UI tab.'),
-    // @todo Also needs entity_ui_target_entity, but we can only specify one!
-    'needs-data' => 'entity_ui_tab',
-  ];
-
-  $node['target_entity'] = [
-    'name' => t("Target entity"),
-    'description' => t("The entity that the entity tab is displayed on."),
-  ];
-
-  return [
-    'types' => ['entity_ui_tab' => $type],
-    'tokens' => ['entity_ui_tab' => $node],
-  ];
+#[LegacyHook]
+function entity_ui_token_info()
+{
+    return \Drupal::service(EntityUiTokensHooks::class)->tokenInfo();
 }
 
 /**
  * Implements hook_tokens().
  */
-function entity_ui_tokens($type, $tokens, array $data, array $options, BubbleableMetadata $bubbleable_metadata) {
-  $token_service = \Drupal::token();
-
-  $replacements = [];
-
-  if ($target_entity_tokens = $token_service->findWithPrefix($tokens, 'target_entity')) {
-    $target_entity_type_id = $data['entity_ui_target_entity']->getEntityTypeId();
-
-    $replacements += $token_service->generate($target_entity_type_id, $target_entity_tokens, [$target_entity_type_id => $data['entity_ui_target_entity']], $options, $bubbleable_metadata);
-  }
-
-  return $replacements;
+#[LegacyHook]
+function entity_ui_tokens($type, $tokens, array $data, array $options, BubbleableMetadata $bubbleable_metadata)
+{
+    return \Drupal::service(EntityUiTokensHooks::class)->tokens($type, $tokens, $data, $options, $bubbleable_metadata);
 }
diff --git a/src/Hook/EntityUiHooks.php b/src/Hook/EntityUiHooks.php
new file mode 100644
index 0000000..562e40d
--- /dev/null
+++ b/src/Hook/EntityUiHooks.php
@@ -0,0 +1,106 @@
+<?php
+
+namespace Drupal\entity_ui\Hook;
+
+use Drupal\entity_ui\EntityHandler\BundleEntityCollection;
+use Drupal\entity_ui\EntityHandler\FieldUIWithoutBundleEntityProxy;
+use Drupal\entity_ui\Routing\TabRouteProvider;
+use Drupal\Core\Hook\Attribute\Hook;
+/**
+ * Hook implementations for entity_ui.
+ */
+class EntityUiHooks
+{
+    /**
+     * Implements hook_entity_type_build().
+     */
+    #[Hook('entity_type_build')]
+    public static function entityTypeBuild(array &$entity_types)
+    {
+        $target_entity_types = \Drupal::service('entity_ui.target_entity_types')->filterTargetEntityTypes($entity_types);
+        foreach ($target_entity_types as $entity_type_id => $entity_type) {
+            // Add our route provider for the tabs we add to entities.
+            // @todo Core forgot to add a direct way to manipulate route_provider, so
+            // we have to do it the sloppy way for now.
+            $providers = $entity_type->getRouteProviderClasses() ?: [
+            ];
+            if (empty($providers['entity_ui'])) {
+                $providers['entity_ui'] = \Drupal\entity_ui\Routing\TabRouteProvider::class;
+                $entity_type->setHandlerClass('route_provider', $providers);
+            }
+            // Add our admin UI handler.
+            // Which handler class we add depends on what sort of admin UI the entity
+            // type has.
+            // @see \Drupal\entity_ui\EntityHandler\EntityUIAdminInterface
+            // @todo Consider moving this logic to static methods on the actual handler
+            // classes.
+            // Don't set a handler if the entity type has one set already. This allows
+            // entity type modules to define their own handlers if their UIs require it.
+            if ($entity_type->hasHandlerClass('entity_ui_admin')) {
+                continue;
+            }
+            // Skip bundles which are defined by plugins (using entity module).
+            // @todo Implement this.
+            // @see https://www.drupal.org/project/entity_ui/issues/3301484
+            if ($entity_type->get('bundle_plugin_type')) {
+                continue;
+            }
+            // Case 1: The entity type has a bundle entity type, and there is a link
+            // template for the collection of bundle entities.
+            if ($bundle_entity_type_id = $entity_type->getBundleEntityType()) {
+                $bundle_entity_type = $entity_types[$bundle_entity_type_id];
+                if ($bundle_collection_link_template = $bundle_entity_type->getLinkTemplate('collection')) {
+                    // We ideally should check that there is a route actually defined for
+                    // the link template, as if the entity's module has forgotten to do this
+                    // our handler can't do anything, but this causes problems in tests and
+                    // if we are enabled at the same time as entity type modules.
+                    // @see https://www.drupal.org/node/2896669
+                    $entity_type->setHandlerClass('entity_ui_admin', \Drupal\entity_ui\EntityHandler\BundleEntityCollection::class);
+                    // Done with this entity type.
+                    continue;
+                } else {
+                    // If there is no link template for the collection of bundle entities,
+                    // we don't know what to do.
+                    continue;
+                }
+            }
+            // If there is no bundle entity, but there is a field UI base route, then
+            // there are these two cases, but we cannot distinguish between them at this
+            // point:
+            // - PlainBundlesEntityUIAdmin: an entity type that allows multiple
+            //   bundles, but where these are not defined by a config entity.
+            // - BasicFieldUI: an entity type that only has a single bundle.
+            // This is because we would either need to check the path of the field admin
+            // UI route for a '{bundle}' path component, and we cannot load routes
+            // during entity type build, or get the number of bundles from the
+            // entity_type.bundle.info service, and we can't call that during entity
+            // type build.
+            // Instead, we use a proxy handler, which will instantiate the real handlers
+            // during the router rebuild.
+            $field_ui_base_route_name = $entity_type->get('field_ui_base_route');
+            if ($field_ui_base_route_name) {
+                $entity_type->setHandlerClass('entity_ui_admin', \Drupal\entity_ui\EntityHandler\FieldUIWithoutBundleEntityProxy::class);
+                // Done with this entity type.
+                continue;
+            }
+            // Other cases are not yet handled.
+        }
+    }
+    /**
+     * Implements hook_local_tasks_alter().
+     */
+    #[Hook('local_tasks_alter')]
+    public static function localTasksAlter(&$local_tasks)
+    {
+        // Allow Entity UI admin handlers to alter local tasks for an entity type's
+        // admin UI.
+        $entity_type_manager = \Drupal::service('entity_type.manager');
+        $entity_types = $entity_type_manager->getDefinitions();
+        foreach ($entity_types as $entity_type_id => $entity_type) {
+            if ($entity_type_manager->hasHandler($entity_type_id, 'entity_ui_admin')) {
+                $entity_ui_admin_handler = $entity_type_manager->getHandler($entity_type_id, 'entity_ui_admin');
+                $entity_ui_admin_handler->localTasksAlter($local_tasks);
+            }
+        }
+    }
+}
diff --git a/src/Hook/EntityUiTokensHooks.php b/src/Hook/EntityUiTokensHooks.php
new file mode 100644
index 0000000..b410bd1
--- /dev/null
+++ b/src/Hook/EntityUiTokensHooks.php
@@ -0,0 +1,56 @@
+<?php
+
+namespace Drupal\entity_ui\Hook;
+
+use Drupal\Core\Render\BubbleableMetadata;
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+/**
+ * Hook implementations for entity_ui.
+ */
+class EntityUiTokensHooks
+{
+    use StringTranslationTrait;
+    /**
+     * Implements hook_token_info().
+     */
+    #[Hook('token_info')]
+    public function tokenInfo()
+    {
+        $type = [
+            'name' => $this->t('Entity UI tab'),
+            'description' => $this->t('Entity UI tab.'),
+            // @todo Also needs entity_ui_target_entity, but we can only specify one!
+            'needs-data' => 'entity_ui_tab',
+        ];
+        $node['target_entity'] = [
+            'name' => $this->t("Target entity"),
+            'description' => $this->t("The entity that the entity tab is displayed on."),
+        ];
+        return [
+            'types' => [
+                'entity_ui_tab' => $type,
+            ],
+            'tokens' => [
+                'entity_ui_tab' => $node,
+            ],
+        ];
+    }
+    /**
+     * Implements hook_tokens().
+     */
+    #[Hook('tokens')]
+    public static function tokens($type, $tokens, array $data, array $options, \Drupal\Core\Render\BubbleableMetadata $bubbleable_metadata)
+    {
+        $token_service = \Drupal::token();
+        $replacements = [
+        ];
+        if ($target_entity_tokens = $token_service->findWithPrefix($tokens, 'target_entity')) {
+            $target_entity_type_id = $data['entity_ui_target_entity']->getEntityTypeId();
+            $replacements += $token_service->generate($target_entity_type_id, $target_entity_tokens, [
+                $target_entity_type_id => $data['entity_ui_target_entity'],
+            ], $options, $bubbleable_metadata);
+        }
+        return $replacements;
+    }
+}
diff --git a/tests/src/Kernel/AdminUIHandlerTest.php b/tests/src/Kernel/AdminUIHandlerTest.php
index 1dd772a..1bf5bb9 100644
--- a/tests/src/Kernel/AdminUIHandlerTest.php
+++ b/tests/src/Kernel/AdminUIHandlerTest.php
@@ -85,7 +85,6 @@ class AdminUIHandlerTest extends KernelTestBase {
     // Hack into the proxy handler to get the real one.
     $reflection = new \ReflectionClass($user_handler);
     $property = $reflection->getProperty('realHandler');
-    $property->setAccessible(TRUE);
     $real_user_handler = $property->getValue($user_handler);
 
     $this->assertEquals(BasicFieldUI::class, get_class($real_user_handler));
