diff --git a/file_replace.module b/file_replace.module
index e4ac1ae..b9cdcda 100644
--- a/file_replace.module
+++ b/file_replace.module
@@ -4,16 +4,18 @@
  * @file
  * File replace functionality.
  */
-
+use Drupal\Core\Hook\Attribute\LegacyHook;
+use Drupal\file_replace\Hook\FileReplaceHooks;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\file\FileInterface;
 
 /**
  * Implements hook_entity_type_build().
  */
-function file_replace_entity_type_build(array &$entity_types) {
-  $entity_types['file']->setFormClass('replace', 'Drupal\file_replace\Form\FileReplaceForm');
-  $entity_types['file']->setLinkTemplate('replace-form', '/admin/content/files/replace/{file}');
+#[LegacyHook]
+function file_replace_entity_type_build(array &$entity_types)
+{
+    \Drupal::service(FileReplaceHooks::class)->entityTypeBuild($entity_types);
 }
 
 /**
@@ -21,48 +23,25 @@ function file_replace_entity_type_build(array &$entity_types) {
  *
  * Views integration of file_replace.
  */
-function file_replace_views_data() {
-  $data['file_managed']['file_replace_link'] = [
-    'field' => [
-      'title' => t('Link to replace file'),
-      'help' => t('Provide a link to replace the file.'),
-      'id' => 'file_replace_link',
-    ],
-  ];
-
-  return $data;
+#[LegacyHook]
+function file_replace_views_data()
+{
+    return \Drupal::service(FileReplaceHooks::class)->viewsData();
 }
 
 /**
  * Implements hook_file_replace().
  */
+#[LegacyHook]
 function file_replace_file_replace(FileInterface $file) {
-  // Flush image cache.
-  if (\Drupal::moduleHandler()->moduleExists('image')) {
-    if (explode('/', $file->getMimeType())[0] == 'image') {
-      /** @var \Drupal\image\Entity\ImageStyle[] $image_styles */
-      $image_styles = \Drupal::entityTypeManager()->getStorage('image_style')->loadMultiple();
-      foreach ($image_styles as $image_style) {
-        $image_style->flush($file->getFileUri());
-      }
-    }
-  }
+  \Drupal::service(FileReplaceHooks::class)->fileReplace($file);
 }
 
 /**
  * Implements hook_entity_operation().
  */
-function file_replace_entity_operation(EntityInterface $entity): array {
-  $operations = [];
-  if ($entity instanceof FileInterface) {
-    $replace_url = $entity->toUrl('replace-form');
-    if ($replace_url->access()) {
-      $operations['replace'] = [
-        'title' => t('Replace'),
-        'url' => $replace_url,
-        'weight' => 100,
-      ];
-    }
-  }
-  return $operations;
+#[LegacyHook]
+function file_replace_entity_operation(EntityInterface $entity): array
+{
+    return \Drupal::service(FileReplaceHooks::class)->entityOperation($entity);
 }
diff --git a/file_replace.services.yml b/file_replace.services.yml
index e6b92c9..3bab40d 100644
--- a/file_replace.services.yml
+++ b/file_replace.services.yml
@@ -4,3 +4,7 @@ services:
     arguments: ['@file.usage']
     tags:
       - { name: access_check, applies_to: _file_replace_access }
+
+  Drupal\file_replace\Hook\FileReplaceHooks:
+    class: Drupal\file_replace\Hook\FileReplaceHooks
+    autowire: true
diff --git a/modules/file_replace_shell_exec/file_replace_shell_exec.module b/modules/file_replace_shell_exec/file_replace_shell_exec.module
index 1ae878b..2d27f3e 100644
--- a/modules/file_replace_shell_exec/file_replace_shell_exec.module
+++ b/modules/file_replace_shell_exec/file_replace_shell_exec.module
@@ -4,28 +4,15 @@
  * @file
  * Contains file_replace_shell_exec functionality.
  */
-
+use Drupal\Core\Hook\Attribute\LegacyHook;
+use Drupal\file_replace_shell_exec\Hook\FileReplaceShellExecHooks;
 use Drupal\file\FileInterface;
 
 /**
  * Implements hook_file_replace().
  */
-function file_replace_shell_exec_file_replace(FileInterface $file) {
-  $command = \Drupal::config('file_replace_shell_exec.settings')->get('command');
-  if (!$command) {
-    return;
-  }
-
-  $shell_exec = shell_exec($command);
-  if ($shell_exec) {
-    \Drupal::logger('file_replace_shell_exec')->info('The command %command was executed with message:' . PHP_EOL . '<pre>%message</pre>', [
-      '%command' => $command,
-      '%message' => $shell_exec,
-    ]);
-  }
-  else {
-    \Drupal::logger('file_replace_shell_exec')->error('The command %command could not be executed.', [
-      '%command' => $command,
-    ]);
-  }
+#[LegacyHook]
+function file_replace_shell_exec_file_replace(FileInterface $file)
+{
+    \Drupal::service(FileReplaceShellExecHooks::class)->fileReplace($file);
 }
diff --git a/modules/file_replace_shell_exec/file_replace_shell_exec.services.yml b/modules/file_replace_shell_exec/file_replace_shell_exec.services.yml
new file mode 100644
index 0000000..db51ff8
--- /dev/null
+++ b/modules/file_replace_shell_exec/file_replace_shell_exec.services.yml
@@ -0,0 +1,5 @@
+
+services:
+  Drupal\file_replace_shell_exec\Hook\FileReplaceShellExecHooks:
+    class: Drupal\file_replace_shell_exec\Hook\FileReplaceShellExecHooks
+    autowire: true
diff --git a/modules/file_replace_shell_exec/src/Hook/FileReplaceShellExecHooks.php b/modules/file_replace_shell_exec/src/Hook/FileReplaceShellExecHooks.php
new file mode 100644
index 0000000..7f67e67
--- /dev/null
+++ b/modules/file_replace_shell_exec/src/Hook/FileReplaceShellExecHooks.php
@@ -0,0 +1,34 @@
+<?php
+
+namespace Drupal\file_replace_shell_exec\Hook;
+
+use Drupal\file\FileInterface;
+use Drupal\Core\Hook\Attribute\Hook;
+/**
+ * Hook implementations for file_replace_shell_exec.
+ */
+class FileReplaceShellExecHooks
+{
+    /**
+     * Implements hook_file_replace().
+     */
+    #[Hook('file_replace')]
+    public function fileReplace(\Drupal\file\FileInterface $file)
+    {
+        $command = \Drupal::config('file_replace_shell_exec.settings')->get('command');
+        if (!$command) {
+            return;
+        }
+        $shell_exec = shell_exec($command);
+        if ($shell_exec) {
+            \Drupal::logger('file_replace_shell_exec')->info('The command %command was executed with message:' . PHP_EOL . '<pre>%message</pre>', [
+                '%command' => $command,
+                '%message' => $shell_exec,
+            ]);
+        } else {
+            \Drupal::logger('file_replace_shell_exec')->error('The command %command could not be executed.', [
+                '%command' => $command,
+            ]);
+        }
+    }
+}
diff --git a/src/Hook/FileReplaceHooks.php b/src/Hook/FileReplaceHooks.php
new file mode 100644
index 0000000..3540467
--- /dev/null
+++ b/src/Hook/FileReplaceHooks.php
@@ -0,0 +1,78 @@
+<?php
+
+namespace Drupal\file_replace\Hook;
+
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\file\FileInterface;
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+/**
+ * Hook implementations for file_replace.
+ */
+class FileReplaceHooks
+{
+    use StringTranslationTrait;
+    /**
+     * Implements hook_entity_type_build().
+     */
+    #[Hook('entity_type_build')]
+    public function entityTypeBuild(array &$entity_types)
+    {
+        $entity_types['file']->setFormClass('replace', 'Drupal\file_replace\Form\FileReplaceForm');
+        $entity_types['file']->setLinkTemplate('replace-form', '/admin/content/files/replace/{file}');
+    }
+    /**
+     * Implements hook_views_data().
+     *
+     * Views integration of file_replace.
+     */
+    #[Hook('views_data')]
+    public function viewsData()
+    {
+        $data['file_managed']['file_replace_link'] = [
+            'field' => [
+                'title' => $this->t('Link to replace file'),
+                'help' => $this->t('Provide a link to replace the file.'),
+                'id' => 'file_replace_link',
+            ],
+        ];
+        return $data;
+    }
+    /**
+     * Implements hook_file_replace().
+     */
+    #[Hook('file_replace')]
+    public function fileReplace(\Drupal\file\FileInterface $file)
+    {
+        // Flush image cache.
+        if (\Drupal::moduleHandler()->moduleExists('image')) {
+            if (explode('/', $file->getMimeType())[0] == 'image') {
+                /** @var \Drupal\image\Entity\ImageStyle[] $image_styles */
+                $image_styles = \Drupal::entityTypeManager()->getStorage('image_style')->loadMultiple();
+                foreach ($image_styles as $image_style) {
+                    $image_style->flush($file->getFileUri());
+                }
+            }
+        }
+    }
+    /**
+     * Implements hook_entity_operation().
+     */
+    #[Hook('entity_operation')]
+    public function entityOperation(\Drupal\Core\Entity\EntityInterface $entity): array
+    {
+        $operations = [
+        ];
+        if ($entity instanceof \Drupal\file\FileInterface) {
+            $replace_url = $entity->toUrl('replace-form');
+            if ($replace_url->access()) {
+                $operations['replace'] = [
+                    'title' => $this->t('Replace'),
+                    'url' => $replace_url,
+                    'weight' => 100,
+                ];
+            }
+        }
+        return $operations;
+    }
+}
