diff --git a/typo3/sysext/core/Classes/Category/CategoryRegistry.php b/typo3/sysext/core/Classes/Category/CategoryRegistry.php
deleted file mode 100644
index 0dce0a03139c49b8cdd0fbd8a3a3718b808064d3..0000000000000000000000000000000000000000
--- a/typo3/sysext/core/Classes/Category/CategoryRegistry.php
+++ /dev/null
@@ -1,480 +0,0 @@
-<?php
-
-/*
- * This file is part of the TYPO3 CMS project.
- *
- * It is free software; you can redistribute it and/or modify it under
- * the terms of the GNU General Public License, either version 2
- * of the License, or any later version.
- *
- * For the full copyright and license information, please read the
- * LICENSE.txt file that was distributed with this source code.
- *
- * The TYPO3 project - inspiring people to share!
- */
-
-namespace TYPO3\CMS\Core\Category;
-
-use TYPO3\CMS\Core\Database\Event\AlterTableDefinitionStatementsEvent;
-use TYPO3\CMS\Core\Localization\LanguageService;
-use TYPO3\CMS\Core\SingletonInterface;
-use TYPO3\CMS\Core\Utility\ArrayUtility;
-use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
-use TYPO3\CMS\Core\Utility\GeneralUtility;
-
-/**
- * Class to register category configurations.
- * @deprecated since v11, will be removed in v12
- */
-class CategoryRegistry implements SingletonInterface
-{
-    /**
-     * @var array
-     */
-    protected $registry = [];
-
-    /**
-     * @var array
-     */
-    protected $extensions = [];
-
-    /**
-     * @var array
-     */
-    protected $addedCategoryTabs = [];
-
-    /**
-     * @var string
-     */
-    protected $template = '';
-
-    /**
-     * Returns a class instance
-     *
-     * @return CategoryRegistry
-     * @internal
-     */
-    public static function getInstance()
-    {
-        return GeneralUtility::makeInstance(__CLASS__);
-    }
-
-    /**
-     * Creates this object.
-     */
-    public function __construct()
-    {
-        $this->template = str_repeat(PHP_EOL, 3) . 'CREATE TABLE %s (' . PHP_EOL
-            . '  %s int(11) DEFAULT \'0\' NOT NULL' . PHP_EOL . ');' . str_repeat(PHP_EOL, 3);
-    }
-
-    /**
-     * Adds a new category configuration to this registry.
-     * TCA changes are directly applied
-     *
-     * @param string $extensionKey Extension key to be used
-     * @param string $tableName Name of the table to be registered
-     * @param string $fieldName Name of the field to be registered
-     * @param array $options Additional configuration options
-     *              + fieldList: field configuration to be added to showitems
-     *              + typesList: list of types that shall visualize the categories field
-     *              + position: insert position of the categories field
-     *              + label: backend label of the categories field
-     *              + fieldConfiguration: TCA field config array to override defaults
-     * @param bool $override If TRUE, any category configuration for the same table / field is removed before the new configuration is added
-     * @return bool
-     * @throws \InvalidArgumentException
-     * @throws \RuntimeException
-     * @internal
-     */
-    public function add($extensionKey, $tableName, $fieldName = 'categories', array $options = [], $override = false)
-    {
-        $didRegister = false;
-        if (empty($tableName) || !is_string($tableName)) {
-            throw new \InvalidArgumentException('No or invalid table name "' . $tableName . '" given.', 1369122038);
-        }
-        if (empty($extensionKey) || !is_string($extensionKey)) {
-            throw new \InvalidArgumentException('No or invalid extension key "' . $extensionKey . '" given.', 1397836158);
-        }
-
-        if ($override) {
-            $this->remove($tableName, $fieldName);
-        }
-
-        if (!$this->isRegistered($tableName, $fieldName)) {
-            $this->registry[$tableName][$fieldName] = $options;
-            $this->extensions[$extensionKey][$tableName][$fieldName] = $fieldName;
-
-            if (isset($GLOBALS['TCA'][$tableName]['columns'])) {
-                $this->applyTcaForTableAndField($tableName, $fieldName);
-                $didRegister = true;
-            }
-        }
-
-        return $didRegister;
-    }
-
-    /**
-     * Gets all extension keys that registered a category configuration.
-     *
-     * @return array
-     * @internal
-     */
-    public function getExtensionKeys()
-    {
-        return array_keys($this->extensions);
-    }
-
-    /**
-     * Gets all categorized tables
-     *
-     * @return array
-     * @internal
-     */
-    public function getCategorizedTables()
-    {
-        return array_keys($this->registry);
-    }
-
-    /**
-     * Returns a list of category fields for a given table for populating selector "category_field"
-     * in tt_content table (called as itemsProcFunc).
-     *
-     * @param array $configuration Current field configuration
-     * @throws \UnexpectedValueException
-     * @internal
-     */
-    public function getCategoryFieldsForTable(array &$configuration)
-    {
-        $table = $configuration['config']['itemsProcConfig']['table'] ?? '';
-        // Return early if no table is defined
-        if (empty($table)) {
-            throw new \UnexpectedValueException('No table is given.', 1381823570);
-        }
-        // Loop on all registries and find entries for the correct table
-        foreach ($this->registry as $tableName => $fields) {
-            if ($tableName === $table) {
-                foreach ($fields as $fieldName => $options) {
-                    $fieldLabel = $this->getLanguageService()->sL($GLOBALS['TCA'][$tableName]['columns'][$fieldName]['label']);
-                    $configuration['items'][] = [$fieldLabel, $fieldName];
-                }
-            }
-        }
-    }
-
-    /**
-     * Tells whether a table has a category configuration in the registry.
-     *
-     * @param string $tableName Name of the table to be looked up
-     * @param string $fieldName Name of the field to be looked up
-     * @return bool
-     * @internal
-     */
-    public function isRegistered($tableName, $fieldName = 'categories')
-    {
-        return isset($this->registry[$tableName][$fieldName]);
-    }
-
-    /**
-     * Generates tables definitions for all registered tables.
-     *
-     * @return string
-     * @internal
-     */
-    public function getDatabaseTableDefinitions()
-    {
-        $sql = '';
-        foreach ($this->getExtensionKeys() as $extensionKey) {
-            $sql .= $this->getDatabaseTableDefinition($extensionKey);
-        }
-        return $sql;
-    }
-
-    /**
-     * Generates table definitions for registered tables by an extension.
-     *
-     * @param string $extensionKey Extension key to have the database definitions created for
-     * @return string
-     * @internal
-     */
-    public function getDatabaseTableDefinition($extensionKey)
-    {
-        if (!isset($this->extensions[$extensionKey]) || !is_array($this->extensions[$extensionKey])) {
-            return '';
-        }
-        $sql = '';
-
-        foreach ($this->extensions[$extensionKey] as $tableName => $fields) {
-            foreach ($fields as $fieldName) {
-                $sql .= sprintf($this->template, $tableName, $fieldName);
-            }
-        }
-        return $sql;
-    }
-
-    /**
-     * Apply TCA to all registered tables
-     *
-     * @internal
-     */
-    public function applyTcaForPreRegisteredTables()
-    {
-        $this->registerDefaultCategorizedTables();
-        foreach ($this->registry as $tableName => $fields) {
-            foreach ($fields as $fieldName => $_) {
-                $this->applyTcaForTableAndField($tableName, $fieldName);
-            }
-        }
-    }
-
-    /**
-     * Applies the additions directly to the TCA
-     *
-     * @param string $tableName
-     * @param string $fieldName
-     */
-    protected function applyTcaForTableAndField($tableName, $fieldName)
-    {
-        $this->addTcaColumn($tableName, $fieldName, $this->registry[$tableName][$fieldName]);
-        $this->addToAllTCAtypes($tableName, $fieldName, $this->registry[$tableName][$fieldName]);
-    }
-
-    /**
-     * Add default categorized tables to the registry
-     */
-    protected function registerDefaultCategorizedTables()
-    {
-        $defaultCategorizedTables = GeneralUtility::trimExplode(
-            ',',
-            $GLOBALS['TYPO3_CONF_VARS']['SYS']['defaultCategorizedTables'],
-            true
-        );
-        if ($defaultCategorizedTables !== []) {
-            trigger_error(
-                '$GLOBALS[\'TYPO3_CONF_VARS\'][\'SYS\'][\'defaultCategorizedTables\'] is deprecated and will be removed in v12. Register category fields with the TCA type "category" instead.',
-                E_USER_DEPRECATED
-            );
-        }
-        foreach ($defaultCategorizedTables as $defaultCategorizedTable) {
-            if (!$this->isRegistered($defaultCategorizedTable)) {
-                $this->add('core', $defaultCategorizedTable, 'categories');
-            }
-        }
-    }
-
-    /**
-     * Add a new field into the TCA types -> showitem
-     *
-     * @param string $tableName Name of the table to be categorized
-     * @param string $fieldName Name of the field to be used to store categories
-     * @param array $options Additional configuration options
-     *              + fieldList: field configuration to be added to showitems
-     *              + typesList: list of types that shall visualize the categories field
-     *              + position: insert position of the categories field
-     */
-    protected function addToAllTCAtypes($tableName, $fieldName, array $options)
-    {
-
-        // Makes sure to add more TCA to an existing structure
-        if (isset($GLOBALS['TCA'][$tableName]['columns'])) {
-            if (empty($options['fieldList'])) {
-                $fieldList = $this->addCategoryTab($tableName, $fieldName);
-            } else {
-                $fieldList = $options['fieldList'];
-            }
-
-            $typesList = '';
-            if (isset($options['typesList']) && $options['typesList'] !== '') {
-                $typesList = $options['typesList'];
-            }
-
-            $position = '';
-            if (!empty($options['position'])) {
-                $position = $options['position'];
-            }
-
-            // Makes the new "categories" field to be visible in TSFE.
-            ExtensionManagementUtility::addToAllTCAtypes($tableName, $fieldList, $typesList, $position);
-        }
-    }
-
-    /**
-     * Creates the 'fieldList' string for $fieldName which includes a categories tab.
-     * But only one categories tab is added per table.
-     *
-     * @param string $tableName
-     * @param string $fieldName
-     * @return string
-     */
-    protected function addCategoryTab($tableName, $fieldName)
-    {
-        $fieldList = '';
-        if (!isset($this->addedCategoryTabs[$tableName])) {
-            $fieldList .= '--div--;LLL:EXT:core/Resources/Private/Language/locallang_tca.xlf:sys_category.tabs.category, ';
-            $this->addedCategoryTabs[$tableName] = $tableName;
-        }
-        $fieldList .= $fieldName;
-        return $fieldList;
-    }
-
-    /**
-     * Add a new TCA Column
-     *
-     * @param string $tableName Name of the table to be categorized
-     * @param string $fieldName Name of the field to be used to store categories
-     * @param array $options Additional configuration options
-     *              + fieldConfiguration: TCA field config array to override defaults
-     *              + label: backend label of the categories field
-     *              + interface: boolean if the category should be included in the "interface" section of the TCA table
-     *              + l10n_mode
-     *              + l10n_display
-     */
-    protected function addTcaColumn($tableName, $fieldName, array $options)
-    {
-        // Makes sure to add more TCA to an existing structure
-        if (isset($GLOBALS['TCA'][$tableName]['columns'])) {
-            // Take specific label into account
-            $label = 'LLL:EXT:core/Resources/Private/Language/locallang_tca.xlf:sys_category.categories';
-            if (!empty($options['label'])) {
-                $label = $options['label'];
-            }
-
-            // Take specific value of exclude flag into account
-            $exclude = true;
-            if (isset($options['exclude'])) {
-                $exclude = (bool)$options['exclude'];
-            }
-
-            $fieldConfiguration = empty($options['fieldConfiguration']) ? [] : $options['fieldConfiguration'];
-
-            $columns = [
-                $fieldName => [
-                    'exclude' => $exclude,
-                    'label' => $label,
-                    'config' =>  static::getTcaFieldConfiguration($tableName, $fieldName, $fieldConfiguration),
-                ],
-            ];
-
-            if (isset($options['l10n_mode'])) {
-                $columns[$fieldName]['l10n_mode'] = $options['l10n_mode'];
-            }
-            if (isset($options['l10n_display'])) {
-                $columns[$fieldName]['l10n_display'] = $options['l10n_display'];
-            }
-            if (isset($options['displayCond'])) {
-                $columns[$fieldName]['displayCond'] = $options['displayCond'];
-            }
-            if (isset($options['onChange'])) {
-                $columns[$fieldName]['onChange'] = $options['onChange'];
-            }
-
-            // Register opposite references for the foreign side of a relation
-            if (empty($GLOBALS['TCA']['sys_category']['columns']['items']['config']['MM_oppositeUsage'][$tableName])) {
-                $GLOBALS['TCA']['sys_category']['columns']['items']['config']['MM_oppositeUsage'][$tableName] = [];
-            }
-            if (!in_array($fieldName, $GLOBALS['TCA']['sys_category']['columns']['items']['config']['MM_oppositeUsage'][$tableName])) {
-                $GLOBALS['TCA']['sys_category']['columns']['items']['config']['MM_oppositeUsage'][$tableName][] = $fieldName;
-            }
-
-            // Adding fields to an existing table definition
-            ExtensionManagementUtility::addTCAcolumns($tableName, $columns);
-        }
-    }
-
-    /**
-     * Get the config array for given table and field.
-     * This method does NOT take care of adding sql fields, adding the field to TCA types
-     * nor does it set the MM_oppositeUsage in the sys_category TCA. This has to be taken care of manually!
-     *
-     * @param string $tableName The table name
-     * @param string $fieldName The field name (default categories)
-     * @param array $fieldConfigurationOverride Changes to the default configuration
-     * @return array
-     */
-    public static function getTcaFieldConfiguration($tableName, $fieldName = 'categories', array $fieldConfigurationOverride = [])
-    {
-        // Forges a new field, default name is "categories"
-        $fieldConfiguration = [
-            'type' => 'select',
-            'renderType' => 'selectTree',
-            'foreign_table' => 'sys_category',
-            'foreign_table_where' => ' AND {#sys_category}.{#sys_language_uid} IN (-1, 0)',
-            'MM' => 'sys_category_record_mm',
-            'MM_opposite_field' => 'items',
-            'MM_match_fields' => [
-                'tablenames' => $tableName,
-                'fieldname' => $fieldName,
-            ],
-            'size' => 20,
-            'maxitems' => 9999,
-            'treeConfig' => [
-                'parentField' => 'parent',
-                'appearance' => [
-                    'expandAll' => true,
-                    'showHeader' => true,
-                    'maxLevels' => 99,
-                ],
-            ],
-        ];
-
-        // Merge changes to TCA configuration
-        if (!empty($fieldConfigurationOverride)) {
-            ArrayUtility::mergeRecursiveWithOverrule(
-                $fieldConfiguration,
-                $fieldConfigurationOverride
-            );
-        }
-
-        return $fieldConfiguration;
-    }
-
-    /**
-     * A event listener to inject the required category database fields to the
-     * tables definition string
-     *
-     * @param AlterTableDefinitionStatementsEvent $event
-     * @internal
-     */
-    public function addCategoryDatabaseSchema(AlterTableDefinitionStatementsEvent $event): void
-    {
-        $this->registerDefaultCategorizedTables();
-        $event->addSqlData($this->getDatabaseTableDefinitions());
-    }
-
-    /**
-     * @return LanguageService
-     */
-    protected function getLanguageService()
-    {
-        return $GLOBALS['LANG'];
-    }
-
-    /**
-     * Removes the given field in the given table from the registry if it is found.
-     *
-     * @param string $tableName The name of the table for which the registration should be removed.
-     * @param string $fieldName The name of the field for which the registration should be removed.
-     */
-    protected function remove($tableName, $fieldName)
-    {
-        if (!$this->isRegistered($tableName, $fieldName)) {
-            return;
-        }
-
-        unset($this->registry[$tableName][$fieldName]);
-
-        foreach ($this->extensions as $extensionKey => $tableFieldConfig) {
-            foreach ($tableFieldConfig as $extTableName => $fieldNameArray) {
-                if ($extTableName === $tableName && isset($fieldNameArray[$fieldName])) {
-                    unset($this->extensions[$extensionKey][$tableName][$fieldName]);
-                    break;
-                }
-            }
-        }
-
-        // If no more fields are configured we unregister the categories tab.
-        if (empty($this->registry[$tableName]) && isset($this->addedCategoryTabs[$tableName])) {
-            unset($this->addedCategoryTabs[$tableName]);
-        }
-    }
-}
diff --git a/typo3/sysext/core/Classes/Hooks/TcaItemsProcessorFunctions.php b/typo3/sysext/core/Classes/Hooks/TcaItemsProcessorFunctions.php
index d59ae5a1c09e1fe990704b18d3b18e58d3ee2f8d..aa9b6932e79a21ee1113de2fd4eea84cddc8fae1 100644
--- a/typo3/sysext/core/Classes/Hooks/TcaItemsProcessorFunctions.php
+++ b/typo3/sysext/core/Classes/Hooks/TcaItemsProcessorFunctions.php
@@ -18,7 +18,6 @@ declare(strict_types=1);
 namespace TYPO3\CMS\Core\Hooks;
 
 use TYPO3\CMS\Backend\Module\ModuleLoader;
-use TYPO3\CMS\Core\Category\CategoryRegistry;
 use TYPO3\CMS\Core\Configuration\FlexForm\Exception\InvalidIdentifierException;
 use TYPO3\CMS\Core\Configuration\FlexForm\FlexFormTools;
 use TYPO3\CMS\Core\Imaging\IconFactory;
@@ -208,18 +207,13 @@ class TcaItemsProcessorFunctions
             throw new \RuntimeException('Given table ' . $table . ' does not define any columns to search for category fields.', 1627565459);
         }
 
-        // For backwards compatibility, see CategoryRegistry->getCategoryFieldsForTable,
-        // only category fields with the "manyToMany" relationship are allowed by default.
+        // Only category fields with the "manyToMany" relationship are allowed by default.
         // This can however be changed using the "allowedRelationships" itemsProcConfig.
         $allowedRelationships = $fieldDefinition['config']['itemsProcConfig']['allowedRelationships'] ?? false;
         if (!is_array($allowedRelationships) || $allowedRelationships === []) {
             $allowedRelationships = ['manyToMany'];
         }
 
-        // @deprecated Only for backwards compatibility, in case extensions still add categories
-        // through the registry (Not having type "category" set). Can be removed in v12.
-        CategoryRegistry::getInstance()->getCategoryFieldsForTable($fieldDefinition);
-
         // Loop on all table columns to find category fields
         foreach ($columns as $fieldName => $fieldConfig) {
             if (($fieldConfig['config']['type'] ?? '') !== 'category'
diff --git a/typo3/sysext/core/Classes/Preparations/TcaPreparation.php b/typo3/sysext/core/Classes/Preparations/TcaPreparation.php
index d4aef1c006345ed93d0982ae41213010d8243ad5..5481208de25a2306ee9096a42c8c77598ea59a3e 100644
--- a/typo3/sysext/core/Classes/Preparations/TcaPreparation.php
+++ b/typo3/sysext/core/Classes/Preparations/TcaPreparation.php
@@ -125,7 +125,6 @@ class TcaPreparation
                     || $fieldConfig['config']['relationship'] === 'manyToMany'
                 ) {
                     // In case maxitems is not set or set to 0, set the default value "99999"
-                    // - backwards compatibility, see: CategoryRegistry->addTcaColumn()
                     if (!($fieldConfig['config']['maxitems'] ?? false)) {
                         $fieldConfig['config']['maxitems'] = 99999;
                     } elseif ((int)($fieldConfig['config']['maxitems'] ?? 0) === 1) {
@@ -168,8 +167,7 @@ class TcaPreparation
                         $tca['sys_category']['columns']['items']['config']['MM_oppositeUsage'][$table][] = $fieldName;
                     }
 
-                    // Take specific value of exclude flag into account -
-                    // backwards compatibility, see: CategoryRegistry->addTcaColumn()
+                    // Take specific value of exclude flag into account
                     if (!isset($fieldConfig['exclude'])) {
                         $fieldConfig['exclude'] = true;
                     }
diff --git a/typo3/sysext/core/Classes/Utility/ExtensionManagementUtility.php b/typo3/sysext/core/Classes/Utility/ExtensionManagementUtility.php
index 954160d413cf7190379806d8ed5f17d58b0cca50..f2fd99fc64867830da17b5200e77e296ddfb87a7 100644
--- a/typo3/sysext/core/Classes/Utility/ExtensionManagementUtility.php
+++ b/typo3/sysext/core/Classes/Utility/ExtensionManagementUtility.php
@@ -21,11 +21,9 @@ use TYPO3\CMS\Backend\Routing\Route;
 use TYPO3\CMS\Backend\Routing\Router;
 use TYPO3\CMS\Core\Cache\CacheManager;
 use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface;
-use TYPO3\CMS\Core\Category\CategoryRegistry;
 use TYPO3\CMS\Core\Configuration\Event\AfterTcaCompilationEvent;
 use TYPO3\CMS\Core\Core\Environment;
 use TYPO3\CMS\Core\Imaging\IconRegistry;
-use TYPO3\CMS\Core\Log\LogManager;
 use TYPO3\CMS\Core\Migrations\TcaMigration;
 use TYPO3\CMS\Core\Package\Cache\PackageDependentCacheIdentifier;
 use TYPO3\CMS\Core\Package\Exception as PackageException;
@@ -1608,14 +1606,6 @@ tt_content.' . $key . $suffix . ' {
             $cacheData = $codeCache->require($cacheIdentifier);
             if ($cacheData) {
                 $GLOBALS['TCA'] = $cacheData['tca'];
-                // @deprecated remove categoryRegistry in v12
-                GeneralUtility::setSingletonInstance(
-                    CategoryRegistry::class,
-                    unserialize(
-                        $cacheData['categoryRegistry'],
-                        ['allowed_classes' => [CategoryRegistry::class]]
-                    )
-                );
             } else {
                 static::buildBaseTcaFromSingleFiles();
                 static::createBaseTcaCacheFile($codeCache);
@@ -1656,10 +1646,6 @@ tt_content.' . $key . $suffix . ' {
             }
         }
 
-        // Apply category stuff
-        // @deprecated since v11, can be removed in v12
-        CategoryRegistry::getInstance()->applyTcaForPreRegisteredTables();
-
         // Execute override files from Configuration/TCA/Overrides
         foreach ($activePackages as $package) {
             try {
@@ -1711,11 +1697,10 @@ tt_content.' . $key . $suffix . ' {
      */
     public static function createBaseTcaCacheFile(FrontendInterface $codeCache)
     {
-        // @deprecated Remove 'categoryRegistry' in v12
         $codeCache->set(
             static::getBaseTcaCacheIdentifier(),
             'return '
-                . var_export(['tca' => $GLOBALS['TCA'], 'categoryRegistry' => serialize(CategoryRegistry::getInstance())], true)
+                . var_export(['tca' => $GLOBALS['TCA']], true)
                 . ';'
         );
     }
@@ -1864,35 +1849,4 @@ tt_content.' . $key . $suffix . ' {
         }
         static::$packageManager->deactivatePackage($extensionKey);
     }
-
-    /**
-     * Makes a table categorizable by adding value into the category registry.
-     * FOR USE IN ext_localconf.php FILES or files in Configuration/TCA/Overrides/*.php Use the latter to benefit from TCA caching!
-     *
-     * @param string $extensionKey Extension key to be used
-     * @param string $tableName Name of the table to be categorized
-     * @param string $fieldName Name of the field to be used to store categories
-     * @param array $options Additional configuration options
-     * @param bool $override If TRUE, any category configuration for the same table / field is removed before the new configuration is added
-     * @see addTCAcolumns
-     * @see addToAllTCAtypes
-     */
-    public static function makeCategorizable($extensionKey, $tableName, $fieldName = 'categories', array $options = [], $override = false)
-    {
-        trigger_error(
-            __CLASS__ . '::makeCategorizable() is deprecated and will be removed in v12. Use the TCA type "category" instead.',
-            E_USER_DEPRECATED
-        );
-
-        // Update the category registry
-        $result = CategoryRegistry::getInstance()->add($extensionKey, $tableName, $fieldName, $options, $override);
-        if ($result === false) {
-            GeneralUtility::makeInstance(LogManager::class)
-                ->getLogger(__CLASS__)
-                ->warning(sprintf(
-                    CategoryRegistry::class . ': no category registered for table "%s". Key was already registered.',
-                    $tableName
-                ));
-        }
-    }
 }
diff --git a/typo3/sysext/core/Configuration/Services.yaml b/typo3/sysext/core/Configuration/Services.yaml
index cb0b5c29c85172affceca68a9e6d590829cbb387..36a046499bd78a626194d7a648e8971f0f337508 100644
--- a/typo3/sysext/core/Configuration/Services.yaml
+++ b/typo3/sysext/core/Configuration/Services.yaml
@@ -134,12 +134,6 @@ services:
         identifier: 'caching-framework'
         method: 'addCachingFrameworkDatabaseSchema'
 
-  TYPO3\CMS\Core\Category\CategoryRegistry:
-    tags:
-      - name: event.listener
-        identifier: 'category-registry'
-        method: 'addCategoryDatabaseSchema'
-
   # Soft Reference Parsers
   TYPO3\CMS\Core\DataHandling\SoftReference\SubstituteSoftReferenceParser:
     tags:
diff --git a/typo3/sysext/core/Documentation/Changelog/12.0/Breaking-96107-DeprecatedFunctionalityRemoved.rst b/typo3/sysext/core/Documentation/Changelog/12.0/Breaking-96107-DeprecatedFunctionalityRemoved.rst
index 6d4dbecc0b985f3faa33087aa455ea04ecdccbc7..80ef8b2e8277b6f3d04aa73fbc79427a55d2f6af 100644
--- a/typo3/sysext/core/Documentation/Changelog/12.0/Breaking-96107-DeprecatedFunctionalityRemoved.rst
+++ b/typo3/sysext/core/Documentation/Changelog/12.0/Breaking-96107-DeprecatedFunctionalityRemoved.rst
@@ -13,6 +13,7 @@ The following PHP classes that have previously been marked as deprecated for v11
 
 - :php:`\TYPO3\CMS\Core\Cache\Backend\PdoBackend`
 - :php:`\TYPO3\CMS\Core\Cache\Backend\WincacheBackend`
+- :php:`\TYPO3\CMS\Core\Category\CategoryRegistry`
 - :php:`\TYPO3\CMS\Core\Database\QueryGenerator`
 - :php:`\TYPO3\CMS\Core\Database\QueryView`
 - :php:`\TYPO3\CMS\Core\Database\SoftReferenceIndex`
@@ -44,10 +45,11 @@ The following PHP class methods that have previously been marked as deprecated f
 
 The following PHP static class methods that have previously been marked as deprecated for v11 and were now removed:
 
+- :php:`\TYPO3\CMS\Backend\Utility\BackendUtility::explodeSoftRefParserList()`
 - :php:`\TYPO3\CMS\Backend\Utility\BackendUtility::fixVersioningPid()`
 - :php:`\TYPO3\CMS\Backend\Utility\BackendUtility::softRefParserObj()`
-- :php:`\TYPO3\CMS\Backend\Utility\BackendUtility::explodeSoftRefParserList()`
 - :php:`\TYPO3\CMS\Backend\Utility\BackendUtility::viewOnClick`
+- :php:`\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::makeCategorizable()`
 - :php:`\TYPO3\CMS\Core\Utility\GeneralUtility::rmFromList()`
 - :php:`\TYPO3\CMS\Core\Utility\GeneralUtility::stdAuthCode()`
 - :php:`\TYPO3\CMS\Core\Utility\HttpUtility::redirect()`
@@ -106,7 +108,7 @@ The following class constants have been dropped:
 
 The following global option handling have been dropped and are ignored:
 
-- :php:`$GLOBALS['TYPO3_CONF_VARS']['KEY']['subKey']`
+- :php:`$GLOBALS['TYPO3_CONF_VARS']['SYS']['defaultCategorizedTables']`
 
 The following global variables have been removed:
 
diff --git a/typo3/sysext/core/Tests/Unit/Utility/ExtensionManagementUtilityTest.php b/typo3/sysext/core/Tests/Unit/Utility/ExtensionManagementUtilityTest.php
index 24afc4b3c4d4b3bce443f217328bf10175117652..8c94c46832ece913ded3879255a874cc32fab017 100644
--- a/typo3/sysext/core/Tests/Unit/Utility/ExtensionManagementUtilityTest.php
+++ b/typo3/sysext/core/Tests/Unit/Utility/ExtensionManagementUtilityTest.php
@@ -22,7 +22,6 @@ use Prophecy\PhpUnit\ProphecyTrait;
 use Psr\EventDispatcher\EventDispatcherInterface;
 use TYPO3\CMS\Core\Cache\CacheManager;
 use TYPO3\CMS\Core\Cache\Frontend\PhpFrontend;
-use TYPO3\CMS\Core\Category\CategoryRegistry;
 use TYPO3\CMS\Core\Core\Environment;
 use TYPO3\CMS\Core\Package\Cache\PackageStatesPackageCache;
 use TYPO3\CMS\Core\Package\MetaData;
@@ -1455,7 +1454,7 @@ class ExtensionManagementUtilityTest extends UnitTestCase
             ->disableOriginalConstructor()
             ->getMock();
 
-        $mockCache->expects(self::once())->method('require')->willReturn(['tca' => [], 'categoryRegistry' => \serialize(CategoryRegistry::getInstance())]);
+        $mockCache->expects(self::once())->method('require')->willReturn(['tca' => []]);
         ExtensionManagementUtilityAccessibleProxy::loadBaseTca(true, $mockCache);
     }
 
diff --git a/typo3/sysext/core/Tests/UnitDeprecated/Category/CategoryRegistryTest.php b/typo3/sysext/core/Tests/UnitDeprecated/Category/CategoryRegistryTest.php
deleted file mode 100644
index cd31c27ae899f7d5dfcd782832dfc9686b8391d2..0000000000000000000000000000000000000000
--- a/typo3/sysext/core/Tests/UnitDeprecated/Category/CategoryRegistryTest.php
+++ /dev/null
@@ -1,328 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-/*
- * This file is part of the TYPO3 CMS project.
- *
- * It is free software; you can redistribute it and/or modify it under
- * the terms of the GNU General Public License, either version 2
- * of the License, or any later version.
- *
- * For the full copyright and license information, please read the
- * LICENSE.txt file that was distributed with this source code.
- *
- * The TYPO3 project - inspiring people to share!
- */
-
-namespace TYPO3\CMS\Core\Tests\UnitDeprecated\Category;
-
-use TYPO3\CMS\Core\Category\CategoryRegistry;
-use TYPO3\CMS\Core\Utility\StringUtility;
-use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
-
-/**
- * Testcase for CategoryRegistry
- */
-class CategoryRegistryTest extends UnitTestCase
-{
-    protected CategoryRegistry $subject;
-
-    protected array $tables;
-
-    /**
-     * Sets up this test suite.
-     */
-    protected function setUp(): void
-    {
-        parent::setUp();
-        $GLOBALS['TYPO3_CONF_VARS']['SYS']['defaultCategorizedTables'] = 'pages';
-        $GLOBALS['TCA']['pages']['columns'] = [];
-        $this->subject = new CategoryRegistry();
-        $this->tables = [
-            'first' => StringUtility::getUniqueId('first'),
-            'second' => StringUtility::getUniqueId('second'),
-        ];
-        foreach ($this->tables as $tableName) {
-            $GLOBALS['TCA'][$tableName] = [
-                'ctrl' => [],
-                'columns' => [],
-                'types' => [
-                    '0' => [
-                        'showitem' => '',
-                    ],
-                    '1' => [
-                        'showitem' => '',
-                    ],
-                ],
-            ];
-        }
-    }
-
-    /**
-     * @test
-     */
-    public function doesAddReturnTrueOnDefinedTable(): void
-    {
-        self::assertTrue($this->subject->add('test_extension_a', $this->tables['first'], 'categories'));
-    }
-
-    /**
-     * @test
-     */
-    public function doesAddReturnTrueOnDefinedTableTheFirstTimeAndFalseTheSecondTime(): void
-    {
-        self::assertTrue($this->subject->add('test_extension_a', $this->tables['first'], 'categories'));
-        self::assertFalse($this->subject->add('test_extension_a', $this->tables['first'], 'categories'));
-    }
-
-    /**
-     * @test
-     */
-    public function doesAddThrowExceptionOnEmptyTablename(): void
-    {
-        $this->expectException(\InvalidArgumentException::class);
-        $this->expectExceptionCode(1369122038);
-
-        $this->subject->add('test_extension_a', '', 'categories');
-    }
-
-    /**
-     * @test
-     */
-    public function doesAddThrowExceptionOnEmptyExtensionKey(): void
-    {
-        $this->expectException(\InvalidArgumentException::class);
-        $this->expectExceptionCode(1397836158);
-
-        $this->subject->add('', 'foo', 'categories');
-    }
-
-    /**
-     * @test
-     */
-    public function doesAddThrowExceptionOnInvalidTablename(): void
-    {
-        $this->expectException(\InvalidArgumentException::class);
-        $this->expectExceptionCode(1369122038);
-
-        $this->subject->add('test_extension_a', '', 'categories');
-    }
-
-    /**
-     * @test
-     */
-    public function doesAddThrowExceptionOnInvalidExtensionKey(): void
-    {
-        $this->expectException(\InvalidArgumentException::class);
-        $this->expectExceptionCode(1397836158);
-
-        $this->subject->add('', 'foo', 'categories');
-    }
-
-    /**
-     * @test
-     */
-    public function areMultipleElementsOfSameExtensionRegistered(): void
-    {
-        $this->subject->add('test_extension_a', $this->tables['first'], 'categories');
-        $this->subject->add('test_extension_a', $this->tables['second'], 'categories');
-        $this->subject->applyTcaForPreRegisteredTables();
-
-        self::assertArrayHasKey('categories', $GLOBALS['TCA'][$this->tables['first']]['columns']);
-        self::assertArrayHasKey('categories', $GLOBALS['TCA'][$this->tables['second']]['columns']);
-    }
-
-    /**
-     * @test
-     */
-    public function areElementsOfDifferentExtensionsRegistered(): void
-    {
-        $this->subject->add('test_extension_a', $this->tables['first'], 'categories');
-        $this->subject->add('test_extension_b', $this->tables['second'], 'categories');
-        $this->subject->applyTcaForPreRegisteredTables();
-
-        self::assertArrayHasKey('categories', $GLOBALS['TCA'][$this->tables['first']]['columns']);
-        self::assertArrayHasKey('categories', $GLOBALS['TCA'][$this->tables['second']]['columns']);
-    }
-
-    /**
-     * @test
-     */
-    public function areElementsOfDifferentExtensionsOnSameTableRegistered(): void
-    {
-        $this->subject->add('test_extension_a', $this->tables['first'], 'categories1');
-        $this->subject->add('test_extension_b', $this->tables['first'], 'categories2');
-        $this->subject->applyTcaForPreRegisteredTables();
-
-        self::assertArrayHasKey('categories1', $GLOBALS['TCA'][$this->tables['first']]['columns']);
-        self::assertArrayHasKey('categories2', $GLOBALS['TCA'][$this->tables['first']]['columns']);
-    }
-
-    /**
-     * @test
-     */
-    public function areElementsOfSameExtensionOnSameTableRegistered(): void
-    {
-        $this->subject->add('test_extension_a', $this->tables['first'], 'categories1');
-        $this->subject->add('test_extension_a', $this->tables['first'], 'categories2');
-        $this->subject->applyTcaForPreRegisteredTables();
-
-        self::assertArrayHasKey('categories1', $GLOBALS['TCA'][$this->tables['first']]['columns']);
-        self::assertArrayHasKey('categories2', $GLOBALS['TCA'][$this->tables['first']]['columns']);
-    }
-
-    /**
-     * @test
-     */
-    public function areDatabaseDefinitionsOfAllElementsAvailable(): void
-    {
-        $this->subject->add('test_extension_a', $this->tables['first'], 'categories');
-        $this->subject->add('test_extension_b', $this->tables['second'], 'categories');
-        $this->subject->add('test_extension_c', $this->tables['first'], 'categories');
-        $definitions = $this->subject->getDatabaseTableDefinitions();
-        $matches = [];
-        preg_match_all('#CREATE TABLE\\s*([^ (]+)\\s*\\(\\s*([^ )]+)\\s+int\\(11\\)[^)]+\\);#mis', $definitions, $matches);
-        self::assertCount(2, $matches[0]);
-        self::assertEquals($matches[1][0], $this->tables['first']);
-        self::assertEquals('categories', $matches[2][0]);
-        self::assertEquals($matches[1][1], $this->tables['second']);
-        self::assertEquals('categories', $matches[2][1]);
-    }
-
-    /**
-     * @test
-     */
-    public function areDatabaseDefinitionsOfParticularExtensionAvailable(): void
-    {
-        $this->subject->add('test_extension_a', $this->tables['first'], 'categories');
-        $this->subject->add('test_extension_b', $this->tables['second'], 'categories');
-        $definitions = $this->subject->getDatabaseTableDefinition('test_extension_a');
-        $matches = [];
-        preg_match_all('#CREATE TABLE\\s*([^ (]+)\\s*\\(\\s*([^ )]+)\\s+int\\(11\\)[^)]+\\);#mis', $definitions, $matches);
-        self::assertCount(1, $matches[0]);
-        self::assertEquals($matches[1][0], $this->tables['first']);
-        self::assertEquals('categories', $matches[2][0]);
-    }
-
-    /**
-     * @test
-     */
-    public function areDefaultCategorizedTablesLoaded(): void
-    {
-        $GLOBALS['TYPO3_CONF_VARS']['SYS']['defaultCategorizedTables'] = $this->tables['first'] . ',' . $this->tables['second'];
-        $this->subject->applyTcaForPreRegisteredTables();
-
-        self::assertArrayHasKey('categories', $GLOBALS['TCA'][$this->tables['first']]['columns']);
-        self::assertArrayHasKey('categories', $GLOBALS['TCA'][$this->tables['second']]['columns']);
-    }
-
-    /**
-     * @test
-     */
-    public function canApplyTca(): void
-    {
-        $this->subject->add('test_extension_a', $this->tables['first'], 'categories');
-        $this->subject->add('test_extension_b', $this->tables['second'], 'categories');
-        $this->subject->applyTcaForPreRegisteredTables();
-
-        self::assertNotEmpty($GLOBALS['TCA'][$this->tables['first']]['columns']['categories']);
-        self::assertNotEmpty($GLOBALS['TCA'][$this->tables['second']]['columns']['categories']);
-    }
-
-    /**
-     * @test
-     */
-    public function isRegisteredReturnsTrueIfElementIsAlreadyRegistered(): void
-    {
-        $this->subject->add('test_extension_a', $this->tables['first'], 'categories');
-        self::assertTrue($this->subject->isRegistered($this->tables['first'], 'categories'));
-    }
-
-    /**
-     * @test
-     */
-    public function isRegisteredReturnsFalseIfElementIsNotRegistered(): void
-    {
-        $this->subject->add('test_extension_a', $this->tables['first'], 'categories');
-        self::assertFalse($this->subject->isRegistered($this->tables['first'], '_not_registered'));
-        self::assertFalse($this->subject->isRegistered($this->tables['second'], 'categories'));
-    }
-
-    /**
-     * @test
-     */
-    public function tabIsAddedForElement(): void
-    {
-        $this->subject->add('text_extension_a', $this->tables['first']);
-        $this->subject->applyTcaForPreRegisteredTables();
-
-        foreach ($GLOBALS['TCA'][$this->tables['first']]['types'] as $typeConfig) {
-            self::assertStringContainsString('--div--;LLL:EXT:core/Resources/Private/Language/locallang_tca.xlf:sys_category.tabs.category', $typeConfig['showitem']);
-        }
-    }
-
-    /**
-     * @test
-     */
-    public function tabIsNotAddedForElementIfFieldListIsSpecified(): void
-    {
-        $this->subject->add('text_extension_a', $this->tables['first'], 'categories', ['fieldList' => 'categories']);
-        $this->subject->applyTcaForPreRegisteredTables();
-
-        foreach ($GLOBALS['TCA'][$this->tables['first']]['types'] as $typeConfig) {
-            self::assertStringNotContainsString('--div--;LLL:EXT:core/Resources/Private/Language/locallang_tca.xlf:sys_category.tabs.category', $typeConfig['showitem']);
-        }
-    }
-
-    /**
-     * @test
-     */
-    public function tabIsOnlyAddedForTypesThatAreSpecifiedInTypesList(): void
-    {
-        $this->subject->add('text_extension_a', $this->tables['first'], 'categories', ['typesList' => '0']);
-        $this->subject->applyTcaForPreRegisteredTables();
-        self::assertSame('', $GLOBALS['TCA'][$this->tables['first']]['types'][1]['showitem']);
-    }
-
-    /**
-     * @test
-     */
-    public function tabIsAddedOnlyOncePerTable(): void
-    {
-        $this->subject->add('text_extension_a', $this->tables['first'], 'categories1');
-        $this->subject->add('text_extension_a', $this->tables['first'], 'categories2');
-        $this->subject->applyTcaForPreRegisteredTables();
-
-        foreach ($GLOBALS['TCA'][$this->tables['first']]['types'] as $typeConfig) {
-            self::assertSame(
-                1,
-                substr_count($typeConfig['showitem'], '--div--;LLL:EXT:core/Resources/Private/Language/locallang_tca.xlf:sys_category.tabs.category')
-            );
-        }
-    }
-
-    /**
-     * @test
-     */
-    public function addAllowsSettingOfTheSameTableFieldTwice(): void
-    {
-        $this->subject->add('text_extension_a', $this->tables['first'], 'categories1');
-        $result = $this->subject->add('text_extension_a', $this->tables['first'], 'categories1', [], true);
-        self::assertTrue($result);
-    }
-
-    /**
-     * @test
-     */
-    public function addInitializesMissingTypes(): void
-    {
-        $this->subject->add('text_extension_a', $this->tables['first'], 'categories1');
-        $GLOBALS['TCA'][$this->tables['first']]['types']['newtypeafterfirstadd'] = ['showitem' => ''];
-        $this->subject->add('text_extension_a', $this->tables['first'], 'categories1', [], true);
-        self::assertSame(
-            1,
-            substr_count($GLOBALS['TCA'][$this->tables['first']]['types']['newtypeafterfirstadd']['showitem'], '--div--;LLL:EXT:core/Resources/Private/Language/locallang_tca.xlf:sys_category.tabs.category')
-        );
-    }
-}
diff --git a/typo3/sysext/core/Tests/UnitDeprecated/Utility/ExtensionManagementUtilityTest.php b/typo3/sysext/core/Tests/UnitDeprecated/Utility/ExtensionManagementUtilityTest.php
deleted file mode 100644
index 00cc61af3abcbaff5322d72f3bd4d72397324767..0000000000000000000000000000000000000000
--- a/typo3/sysext/core/Tests/UnitDeprecated/Utility/ExtensionManagementUtilityTest.php
+++ /dev/null
@@ -1,61 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-/*
- * This file is part of the TYPO3 CMS project.
- *
- * It is free software; you can redistribute it and/or modify it under
- * the terms of the GNU General Public License, either version 2
- * of the License, or any later version.
- *
- * For the full copyright and license information, please read the
- * LICENSE.txt file that was distributed with this source code.
- *
- * The TYPO3 project - inspiring people to share!
- */
-
-namespace TYPO3\CMS\Core\Tests\UnitDeprecated\Utility;
-
-use TYPO3\CMS\Core\Category\CategoryRegistry;
-use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
-use TYPO3\CMS\Core\Utility\GeneralUtility;
-use TYPO3\CMS\Core\Utility\StringUtility;
-use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
-
-class ExtensionManagementUtilityTest extends UnitTestCase
-{
-    /**
-     * @var bool Reset singletons created by subject
-     */
-    protected bool $resetSingletonInstances = true;
-
-    /**
-     * @test
-     */
-    public function doesMakeCategorizableCallsTheCategoryRegistryWithDefaultFieldName(): void
-    {
-        $extensionKey = StringUtility::getUniqueId('extension');
-        $tableName = StringUtility::getUniqueId('table');
-
-        $registryMock = $this->getMockBuilder(CategoryRegistry::class)->getMock();
-        $registryMock->expects(self::once())->method('add')->with($extensionKey, $tableName, 'categories', []);
-        GeneralUtility::setSingletonInstance(CategoryRegistry::class, $registryMock);
-        ExtensionManagementUtility::makeCategorizable($extensionKey, $tableName);
-    }
-
-    /**
-     * @test
-     */
-    public function doesMakeCategorizableCallsTheCategoryRegistryWithFieldName(): void
-    {
-        $extensionKey = StringUtility::getUniqueId('extension');
-        $tableName = StringUtility::getUniqueId('table');
-        $fieldName = StringUtility::getUniqueId('field');
-
-        $registryMock = $this->getMockBuilder(CategoryRegistry::class)->getMock();
-        $registryMock->expects(self::once())->method('add')->with($extensionKey, $tableName, $fieldName, []);
-        GeneralUtility::setSingletonInstance(CategoryRegistry::class, $registryMock);
-        ExtensionManagementUtility::makeCategorizable($extensionKey, $tableName, $fieldName);
-    }
-}
diff --git a/typo3/sysext/install/Classes/Service/LoadTcaService.php b/typo3/sysext/install/Classes/Service/LoadTcaService.php
index 40bd05f1e8244ecc61e62022d95e8f0563bacf08..8bcebc41007007699a58e817033ff21b628c7f26 100644
--- a/typo3/sysext/install/Classes/Service/LoadTcaService.php
+++ b/typo3/sysext/install/Classes/Service/LoadTcaService.php
@@ -15,7 +15,6 @@
 
 namespace TYPO3\CMS\Install\Service;
 
-use TYPO3\CMS\Core\Category\CategoryRegistry;
 use TYPO3\CMS\Core\Package\Exception\UnknownPackageException;
 use TYPO3\CMS\Core\Package\PackageManager;
 
@@ -75,10 +74,6 @@ class LoadTcaService
             }
         }
 
-        // Apply category stuff
-        // @deprecated since v11, can be removed in v12
-        CategoryRegistry::getInstance()->applyTcaForPreRegisteredTables();
-
         // Execute override files from Configuration/TCA/Overrides
         foreach ($activePackages as $package) {
             $tcaOverridesPathForPackage = $package->getPackagePath() . 'Configuration/TCA/Overrides';
diff --git a/typo3/sysext/install/Configuration/ExtensionScanner/Php/ArrayDimensionMatcher.php b/typo3/sysext/install/Configuration/ExtensionScanner/Php/ArrayDimensionMatcher.php
index d4795d3c88ca9178c5e2b589c02966871574bf63..009f119922af0f8b7a0528d0db1591fe8755144b 100644
--- a/typo3/sysext/install/Configuration/ExtensionScanner/Php/ArrayDimensionMatcher.php
+++ b/typo3/sysext/install/Configuration/ExtensionScanner/Php/ArrayDimensionMatcher.php
@@ -490,6 +490,7 @@ return [
     '$GLOBALS[\'TYPO3_CONF_VARS\'][\'SYS\'][\'defaultCategorizedTables\']' => [
         'restFiles' => [
             'Deprecation-85613-CategoryRegistry.rst',
+            'Breaking-96107-DeprecatedFunctionalityRemoved.rst',
         ],
     ],
     '$GLOBALS[\'TYPO3_CONF_VARS\'][\'SC_OPTIONS\'][\'typo3/classes/class.frontendedit.php\']' => [
diff --git a/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodCallStaticMatcher.php b/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodCallStaticMatcher.php
index 5b9016332673fa1dfd8935852a5d426364042132..8bad6edc0832ec10924651e17adfc40ab5527c7b 100644
--- a/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodCallStaticMatcher.php
+++ b/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodCallStaticMatcher.php
@@ -1154,6 +1154,7 @@ return [
         'maximumNumberOfArguments' => 5,
         'restFiles' => [
             'Deprecation-85613-CategoryRegistry.rst',
+            'Breaking-96107-DeprecatedFunctionalityRemoved.rst',
         ],
     ],
     'TYPO3\CMS\Backend\Utility\BackendUtility::softRefParserObj' => [