diff --git a/cache_control_override.info.yml b/cache_control_override.info.yml
index ed1f622a10fce365caa5a758cf2291ec21bc8d08..5014530f9791e2c1178f0fdc4aac473e89db4611 100644
--- a/cache_control_override.info.yml
+++ b/cache_control_override.info.yml
@@ -1,6 +1,6 @@
 name: Cache Control Override
 type: module
 description: Override page Cache-Control header based on bubbled cacheability metadata.
-core: 8.x
-core_version_requirement: ^8 || ^9
+core_version_requirement: '>=10'
+php: 8.1
 package: Performance and scalability
diff --git a/composer.json b/composer.json
new file mode 100644
index 0000000000000000000000000000000000000000..3b2a7881e399cb1005e80b032143ee3864aead25
--- /dev/null
+++ b/composer.json
@@ -0,0 +1,9 @@
+{
+    "name": "drupal/cache_control_override",
+    "description": "Override page Cache-Control header based on bubbled cacheability metadata.",
+    "type": "drupal-module",
+    "require": {
+        "php": ">=8.1",
+        "drupal/core": "^10"
+    }
+}
diff --git a/src/EventSubscriber/CacheControlOverrideSubscriber.php b/src/EventSubscriber/CacheControlOverrideSubscriber.php
index 6ce37d8f06d83dea87b0dee4ddc01d741d2b108f..3bd61fd98389c10cff3456a7f1b99ab9655ce4b5 100644
--- a/src/EventSubscriber/CacheControlOverrideSubscriber.php
+++ b/src/EventSubscriber/CacheControlOverrideSubscriber.php
@@ -1,26 +1,35 @@
 <?php
 
+declare(strict_types=1);
+
 namespace Drupal\cache_control_override\EventSubscriber;
 
-use Drupal\Core\Cache\Cache;
 use Drupal\Core\Cache\CacheableResponseInterface;
-use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
-use Symfony\Component\HttpKernel\KernelEvents;
+use Drupal\Core\Cache\CacheBackendInterface;
 use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+use Symfony\Component\HttpKernel\Event\ResponseEvent;
+use Symfony\Component\HttpKernel\KernelEvents;
 
 /**
  * Cache Control Override.
+ *
+ * @internal
+ *   There is no extensibility promise for this class. To override this
+ *   functionality, you may subscribe to events at a higher priority, then
+ *   set $event->stopPropagation(). The service may be decorated. Or you may
+ *   remove, or replace this class entirely in service registration by
+ *   implementing a ServiceProvider.
  */
-class CacheControlOverrideSubscriber implements EventSubscriberInterface {
+final class CacheControlOverrideSubscriber implements EventSubscriberInterface {
 
   /**
    * Overrides cache control header if any of override methods are enabled.
    *
-   * @param \Symfony\Component\HttpKernel\Event\FilterResponseEvent $event
+   * @param \Symfony\Component\HttpKernel\Event\ResponseEvent $event
    *   The event to process.
    */
-  public function onRespond(FilterResponseEvent $event) {
-    if (!$event->isMasterRequest()) {
+  public function onRespond(ResponseEvent $event): void {
+    if (FALSE === $event->isMainRequest()) {
       return;
     }
 
@@ -34,25 +43,32 @@ class CacheControlOverrideSubscriber implements EventSubscriberInterface {
 
     // If FinishResponseSubscriber didn't set the response as cacheable, then
     // don't override anything.
-    if (!$response->headers->hasCacheControlDirective('max-age') || !$response->headers->hasCacheControlDirective('public')) {
+    if (FALSE === $response->headers->hasCacheControlDirective('max-age')) {
+      return;
+    }
+
+    if (FALSE === $response->headers->hasCacheControlDirective('public')) {
       return;
     }
 
-    $max_age = $response->getCacheableMetadata()->getCacheMaxAge();
+    $maxAge = $response->getCacheableMetadata()->getCacheMaxAge();
 
     // We treat permanent cache max-age as default therefore we don't override
     // the max-age.
-    if ($max_age != Cache::PERMANENT) {
-      $response->headers->set('Cache-Control', 'public, max-age=' . $max_age);
+    if ($maxAge !== CacheBackendInterface::CACHE_PERMANENT) {
+      $response->headers->set('Cache-Control', 'public, max-age=' . $maxAge);
     }
   }
 
   /**
    * {@inheritdoc}
    */
-  public static function getSubscribedEvents() {
-    $events[KernelEvents::RESPONSE][] = ['onRespond'];
-    return $events;
+  public static function getSubscribedEvents(): array {
+    return [
+      KernelEvents::RESPONSE => [
+        ['onRespond'],
+      ],
+    ];
   }
 
 }
diff --git a/src/PageCache/DenyOnCacheControlOverride.php b/src/PageCache/DenyOnCacheControlOverride.php
index 9f8f21291824f53dd0c1af159bd4cf3f5ae9accc..7f68fdfe948155180d85eeb68fe26fd37623c955 100644
--- a/src/PageCache/DenyOnCacheControlOverride.php
+++ b/src/PageCache/DenyOnCacheControlOverride.php
@@ -1,5 +1,7 @@
 <?php
 
+declare(strict_types=1);
+
 namespace Drupal\cache_control_override\PageCache;
 
 use Drupal\Core\Cache\CacheableResponseInterface;
@@ -9,13 +11,19 @@ use Symfony\Component\HttpFoundation\Response;
 
 /**
  * Cache policy for responses that have a bubbled max-age=0.
+ *
+ * @internal
+ *   There is no extensibility promise for this class. To override this
+ *   functionality, the service may be decorated. Or you may
+ *   remove, or replace this class entirely in service registration by
+ *   implementing a ServiceProvider.
  */
-class DenyOnCacheControlOverride implements ResponsePolicyInterface {
+final class DenyOnCacheControlOverride implements ResponsePolicyInterface {
 
   /**
    * {@inheritdoc}
    */
-  public function check(Response $response, Request $request) {
+  public function check(Response $response, Request $request): ?string {
     if (!$response instanceof CacheableResponseInterface) {
       return NULL;
     }
@@ -24,6 +32,8 @@ class DenyOnCacheControlOverride implements ResponsePolicyInterface {
       // @TODO: This will affect users using Internal Page Cache as well, find a way to document that.
       return static::DENY;
     }
+
+    return NULL;
   }
 
 }
diff --git a/tests/modules/cache_control_override_test/cache_control_override_test.info.yml b/tests/modules/cache_control_override_test/cache_control_override_test.info.yml
index ab13f5d738e84b698fede65543beaeea301e8aab..4fafa99adfa3d10f8205d8889db65e28d1280ddb 100644
--- a/tests/modules/cache_control_override_test/cache_control_override_test.info.yml
+++ b/tests/modules/cache_control_override_test/cache_control_override_test.info.yml
@@ -1,7 +1,6 @@
 name: Cache Control Override test
 type: module
 description: 'Provides functionality for testing Cache Control Override.'
-hidden: true
-core: 8.x
+package: Testing
 dependencies:
-  - cache_control_override
+  - cache_control_override:cache_control_override
diff --git a/tests/modules/cache_control_override_test/cache_control_override_test.routing.yml b/tests/modules/cache_control_override_test/cache_control_override_test.routing.yml
index 84596937af315632056384d467a14ec5c6811cde..e557c9442a147509c3c11b82bf3a26bdd133b3de 100644
--- a/tests/modules/cache_control_override_test/cache_control_override_test.routing.yml
+++ b/tests/modules/cache_control_override_test/cache_control_override_test.routing.yml
@@ -1,13 +1,12 @@
-cache_control_override_test.permanent:
-  path: '/cco'
-  defaults:
-    _controller: '\Drupal\cache_control_override_test\Controller\CacheControl::maxAge'
-  requirements:
-    _access: 'TRUE'
-
 cache_control_override_test.max_age:
   path: '/cco/{max_age}'
   defaults:
-    _controller: '\Drupal\cache_control_override_test\Controller\CacheControl::maxAge'
+    _controller: '\Drupal\cache_control_override_test\Controller\CacheControl'
+    max_age: -1
   requirements:
     _access: 'TRUE'
+    max_age: \d+
+  options:
+    parameters:
+      max_age:
+        type: int
diff --git a/tests/modules/cache_control_override_test/src/Controller/CacheControl.php b/tests/modules/cache_control_override_test/src/Controller/CacheControl.php
index 28525d86c6c4c71ebb53bf8f947cd496c1b3b1e8..f88307cddfa811e8cf564f16cb5de468ca9eb338 100644
--- a/tests/modules/cache_control_override_test/src/Controller/CacheControl.php
+++ b/tests/modules/cache_control_override_test/src/Controller/CacheControl.php
@@ -1,27 +1,32 @@
 <?php
 
+declare(strict_types=1);
+
 namespace Drupal\cache_control_override_test\Controller;
 
-use Drupal\Core\Cache\Cache;
+use Drupal\Core\Cache\CacheBackendInterface;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
+use Symfony\Component\HttpFoundation\Response;
 
 /**
- * Controllers for testing the cache control override.
+ * Test caching with a max age provided from the URL.
  */
-class CacheControl {
+final class CacheControl {
 
   /**
-   * Controller callback: Test content with a specified max age.
+   * Test content with a specified max age.
    *
-   * @param int $max_age
+   * @param string $max_age
    *   Max age value to be used in the response.
    *
-   * @return array
-   *   Render array of page output.
+   * @return array|\Symfony\Component\HttpFoundation\Response
+   *   The response.
    */
-  public function maxAge($max_age = Cache::PERMANENT) {
-
+  public function __invoke(mixed $max_age = CacheBackendInterface::CACHE_PERMANENT): array|Response {
     return [
-      '#markup' => 'Max age test content',
+      '#markup' => new TranslatableMarkup('Max age test content: @max_age', [
+        '@max_age' => $max_age,
+      ]),
       '#cache' => ['max-age' => $max_age],
     ];
   }
diff --git a/tests/src/Functional/CacheControlOverrideMaxAgeTest.php b/tests/src/Functional/CacheControlOverrideMaxAgeTest.php
index 30893660f9226177021cee71a34ee0cb03d0bd27..7263454cd59562c5f2b805327471a4b036945750 100644
--- a/tests/src/Functional/CacheControlOverrideMaxAgeTest.php
+++ b/tests/src/Functional/CacheControlOverrideMaxAgeTest.php
@@ -1,7 +1,10 @@
 <?php
 
+declare(strict_types=1);
+
 namespace Drupal\Tests\cache_control_override\Functional;
 
+use Drupal\Core\Url;
 use Drupal\Tests\BrowserTestBase;
 
 /**
@@ -9,7 +12,12 @@ use Drupal\Tests\BrowserTestBase;
  *
  * @group cache_control_override
  */
-class CacheControlOverrideMaxAgeTest extends BrowserTestBase {
+final class CacheControlOverrideMaxAgeTest extends BrowserTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $defaultTheme = 'stark';
 
   /**
    * {@inheritdoc}
@@ -22,27 +30,29 @@ class CacheControlOverrideMaxAgeTest extends BrowserTestBase {
   /**
    * {@inheritdoc}
    */
-  public function setUp() {
+  public function setUp(): void {
     parent::setUp();
-
-    $config = $this->config('system.performance');
-    $config->set('cache.page.max_age', 3600);
-    $config->save();
+    $this->config('system.performance')
+      ->set('cache.page.max_age', 3600)
+      ->save();
   }
 
   /**
    * Test the cache properties in response header data.
    */
-  public function testMaxAge() {
-
-    $this->drupalGet('cco');
+  public function testMaxAge(): void {
+    $this->drupalGet(Url::fromRoute('cache_control_override_test.max_age'));
     $this->assertSession()->responseContains('Max age test content');
     $this->assertSession()->responseHeaderContains('Cache-Control', 'max-age=3600, public');
 
-    $this->drupalGet('cco/333');
+    $this->drupalGet(Url::fromRoute('cache_control_override_test.max_age', route_parameters: [
+      'max_age' => '333',
+    ]));
     $this->assertSession()->responseHeaderContains('Cache-Control', 'max-age=333, public');
 
-    $this->drupalGet('cco/0');
+    $this->drupalGet(Url::fromRoute('cache_control_override_test.max_age', route_parameters: [
+      'max_age' => '0',
+    ]));
     $this->assertSession()->responseHeaderContains('Cache-Control', 'max-age=0, public');
   }