diff --git a/representative_image.api.php b/representative_image.api.php
index f7f319f..6ec8cf7 100644
--- a/representative_image.api.php
+++ b/representative_image.api.php
@@ -1,5 +1,13 @@
 <?php
 
+/**
+ * @file
+ */
+
+use Drupal\Core\Field\FieldItemListInterface;
+use Drupal\Core\Field\FieldDefinitionInterface;
+use Drupal\Core\Entity\FieldableEntityInterface;
+
 /**
  * @file
  * API integration for the representative_image module.
@@ -15,7 +23,7 @@
  * @param \Drupal\Core\Entity\FieldableEntityInterface $entity
  *   The source entity.
  */
-function hook_representative_image_alter(\Drupal\Core\Field\FieldItemListInterface $items, \Drupal\Core\Field\FieldDefinitionInterface $representative_image_field, \Drupal\Core\Entity\FieldableEntityInterface $entity): void {
+function hook_representative_image_alter(FieldItemListInterface $items, FieldDefinitionInterface $representative_image_field, FieldableEntityInterface $entity): void {
   // If the image has empty alt text, load in the field_alt_text value.
   if ($entity->hasField('field_alt_text')) {
     if (!empty($items[0]->alt) && !$entity->get('field_alt_text')->isEmpty()) {
diff --git a/representative_image.module b/representative_image.module
index ca212a3..bcd630b 100644
--- a/representative_image.module
+++ b/representative_image.module
@@ -5,64 +5,21 @@
  * Primary module hooks for Representative Image module.
  */
 
-use Drupal\Component\Render\FormattableMarkup;
-use Drupal\Core\StringTranslation\TranslatableMarkup;
+use Drupal\Core\Hook\Attribute\LegacyHook;
+use Drupal\representative_image\Hook\RepresentativeImageHooks;
 
 /**
  * Implements hook_token_info().
  */
+#[LegacyHook]
 function representative_image_token_info() {
-  $tokens = [];
-
-  $entity_definitions = \Drupal::entityTypeManager()->getDefinitions();
-  foreach ($entity_definitions as $entity_type_id => $entity_type_definition) {
-    $info['representative_image'] = [
-      'name' => new TranslatableMarkup('Representative image'),
-      'description' => new FormattableMarkup('Replaced by an image selected by the configuration of a Representative Image field.', []),
-    ];
-    $tokens[$entity_type_id] = $info;
-  }
-
-  return [
-    'tokens' => $tokens,
-  ];
+  return \Drupal::service(RepresentativeImageHooks::class)->tokenInfo();
 }
 
 /**
  * Implements hook_tokens().
  */
+#[LegacyHook]
 function representative_image_tokens($type, $tokens, array $data = [], array $options = []) {
-  $replacements = [];
-
-  $entity_definitions = \Drupal::entityTypeManager()->getDefinitions();
-  $representative_image_picker = \Drupal::service('representative_image.picker');
-  foreach ($entity_definitions as $entity_type_id => $entity_type_definition) {
-    if (($type == $entity_type_id) && !empty($data[$entity_type_id])) {
-      $entity = $data[$entity_type_id];
-
-      foreach ($tokens as $name => $original) {
-        switch ($name) {
-          case 'representative_image':
-            if (!$representative_image_picker->hasRepresentativeImageField($entity)) {
-              break;
-            }
-
-            $representative_image_field = $representative_image_picker->getRepresentativeImageField($entity);
-
-            // Extract the image URL from the field formatter.
-            $view_builder = \Drupal::entityTypeManager()->getViewBuilder($entity_type_id);
-            $field_output = $view_builder->viewField($representative_image_field, 'default');
-            if (empty($field_output[0]['#item'])) {
-              break;
-            }
-            /** @var \Drupal\file\Entity\File $file_entity */
-            $file_entity = $field_output[0]['#item']->entity;
-            $replacements[$original] = $file_entity->createFileUrl(FALSE);
-            break;
-        }
-      }
-    }
-  }
-
-  return $replacements;
+  return \Drupal::service(RepresentativeImageHooks::class)->tokens($type, $tokens, $data, $options);
 }
diff --git a/representative_image.services.yml b/representative_image.services.yml
index 6caed26..2f330fe 100644
--- a/representative_image.services.yml
+++ b/representative_image.services.yml
@@ -2,3 +2,7 @@ services:
   representative_image.picker:
     class: Drupal\representative_image\RepresentativeImagePicker
     arguments: ['@entity_field.manager', '@entity_type.manager', '@entity.repository', '@module_handler']
+
+  Drupal\representative_image\Hook\RepresentativeImageHooks:
+    class: Drupal\representative_image\Hook\RepresentativeImageHooks
+    autowire: true
diff --git a/src/Hook/RepresentativeImageHooks.php b/src/Hook/RepresentativeImageHooks.php
new file mode 100644
index 0000000..24dea16
--- /dev/null
+++ b/src/Hook/RepresentativeImageHooks.php
@@ -0,0 +1,73 @@
+<?php
+
+namespace Drupal\representative_image\Hook;
+
+use Drupal\Component\Render\FormattableMarkup;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
+use Drupal\Core\Hook\Attribute\Hook;
+
+/**
+ * Hook implementations for representative_image.
+ */
+class RepresentativeImageHooks {
+
+  /**
+   * Implements hook_token_info().
+   */
+  #[Hook('token_info')]
+  public static function tokenInfo() {
+    $tokens = [];
+    $entity_definitions = \Drupal::entityTypeManager()->getDefinitions();
+    foreach ($entity_definitions as $entity_type_id => $entity_type_definition) {
+      $info['representative_image'] = [
+        'name' => new TranslatableMarkup('Representative image'),
+        'description' => new FormattableMarkup('Replaced by an image selected by the configuration of a Representative Image field.', []),
+      ];
+      $tokens[$entity_type_id] = $info;
+    }
+    return [
+      'tokens' => $tokens,
+    ];
+  }
+
+  /**
+   * Implements hook_tokens().
+   */
+  #[Hook('tokens')]
+  public static function tokens(
+    $type,
+    $tokens,
+    array $data = [],
+    array $options = [],
+  ) {
+    $replacements = [];
+    $entity_definitions = \Drupal::entityTypeManager()->getDefinitions();
+    $representative_image_picker = \Drupal::service('representative_image.picker');
+    foreach ($entity_definitions as $entity_type_id => $entity_type_definition) {
+      if ($type == $entity_type_id && !empty($data[$entity_type_id])) {
+        $entity = $data[$entity_type_id];
+        foreach ($tokens as $name => $original) {
+          switch ($name) {
+            case 'representative_image':
+              if (!$representative_image_picker->hasRepresentativeImageField($entity)) {
+                break;
+              }
+              $representative_image_field = $representative_image_picker->getRepresentativeImageField($entity);
+              // Extract the image URL from the field formatter.
+              $view_builder = \Drupal::entityTypeManager()->getViewBuilder($entity_type_id);
+              $field_output = $view_builder->viewField($representative_image_field, 'default');
+              if (empty($field_output[0]['#item'])) {
+                break;
+              }
+              /** @var \Drupal\file\Entity\File $file_entity */
+              $file_entity = $field_output[0]['#item']->entity;
+              $replacements[$original] = $file_entity->createFileUrl(FALSE);
+              break;
+          }
+        }
+      }
+    }
+    return $replacements;
+  }
+
+}
diff --git a/tests/src/FunctionalJavascript/RepresentativeImageEntitiesTest.php b/tests/src/FunctionalJavascript/RepresentativeImageEntitiesTest.php
index e39374d..a240baf 100644
--- a/tests/src/FunctionalJavascript/RepresentativeImageEntitiesTest.php
+++ b/tests/src/FunctionalJavascript/RepresentativeImageEntitiesTest.php
@@ -2,11 +2,16 @@
 
 namespace Drupal\Tests\representative_image\FunctionalJavascript;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
+
 /**
  * Test that entities can have associated representative image fields.
  *
  * @group representative_image
  */
+#[Group('representative_image')]
+#[RunTestsInSeparateProcesses]
 class RepresentativeImageEntitiesTest extends RepresentativeImageTestBase {
 
   /**
diff --git a/tests/src/FunctionalJavascript/RepresentativeImageTokenTest.php b/tests/src/FunctionalJavascript/RepresentativeImageTokenTest.php
index 0d8c415..392aeb0 100644
--- a/tests/src/FunctionalJavascript/RepresentativeImageTokenTest.php
+++ b/tests/src/FunctionalJavascript/RepresentativeImageTokenTest.php
@@ -2,11 +2,16 @@
 
 namespace Drupal\Tests\representative_image\FunctionalJavascript;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
+
 /**
  * Test the token integration of representative images.
  *
  * @group representative_image
  */
+#[Group('representative_image')]
+#[RunTestsInSeparateProcesses]
 class RepresentativeImageTokenTest extends RepresentativeImageTestBase {
 
   /**
