diff --git a/Build/Scripts/checkNamespaceIntegrity.php b/Build/Scripts/checkNamespaceIntegrity.php
new file mode 100755
index 0000000000000000000000000000000000000000..0cea25112165e585fb4fbeac144c08a886de666d
--- /dev/null
+++ b/Build/Scripts/checkNamespaceIntegrity.php
@@ -0,0 +1,287 @@
+#!/usr/bin/env php
+<?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!
+ */
+
+use PhpParser\Node;
+use PhpParser\NodeTraverser;
+use PhpParser\NodeVisitor\NameResolver;
+use PhpParser\NodeVisitorAbstract;
+use PhpParser\ParserFactory;
+use Symfony\Component\Finder\Finder;
+
+if (PHP_SAPI !== 'cli') {
+    die('Script must be called from command line.' . chr(10));
+}
+
+require __DIR__ . '/../../vendor/autoload.php';
+
+/**
+ * Class to scan for invalid namespaces.
+ */
+class CheckNamespaceIntegrity
+{
+    public function scan(): int
+    {
+        $ignoreFiles = [
+            // ignored, pure fixture file
+            'typo3/sysext/core/Tests/Unit/Configuration/TypoScript/ConditionMatching/Fixtures/ConditionMatcherUserFuncs.php',
+            // ignored, pure fixture file
+            'typo3/sysext/install/Tests/Unit/ExtensionScanner/Php/Matcher/Fixtures/PropertyExistsStaticMatcherFixture.php',
+            // ignored, pure fixture file
+            'typo3/sysext/extbase/Tests/UnitDeprecated/Object/Container/Fixtures/ContainerPropertyInjectionTestClasses.php',
+            // ignored, pure fixture file
+            'typo3/sysext/extbase/Tests/Unit/Object/Container/Fixtures/ContainerConstructorInjectionTestFixtures.php',
+        ];
+        $ignoreNamespaceParts = ['Classes'];
+        $parser = (new ParserFactory())->create(ParserFactory::ONLY_PHP7);
+        $files = $this->createFinder();
+        $invalidNamespaces = [];
+        foreach ($files as $file) {
+            /** @var $file SplFileInfo */
+            $fullFilename = $file->getRealPath();
+            preg_match('/.*typo3\/sysext\/(.*)$/', $fullFilename, $matches);
+            $relativeFilenameFromRoot = 'typo3/sysext/' . $matches[1];
+            if (in_array($relativeFilenameFromRoot, $ignoreFiles, true)) {
+                continue;
+            }
+            $parts = explode('/', $matches[1]);
+            $sysExtName = $parts[0];
+            unset($parts[0]);
+            if (in_array($parts[1], $ignoreNamespaceParts, true)) {
+                unset($parts[1]);
+            }
+
+            $relativeFilenameWithoutSystemExtensionRoot = substr($relativeFilenameFromRoot, (mb_strlen('typo3/sysext/' . $sysExtName . '/')));
+            $expectedFullQualifiedObjectNamespace = $this->determineExpectedFullQualifiedNamespace($sysExtName, $relativeFilenameWithoutSystemExtensionRoot);
+            $ast = $parser->parse($file->getContents());
+            $traverser = new NodeTraverser();
+            $visitor = new NameResolver();
+            $traverser->addVisitor($visitor);
+            $visitor = new NamespaceValidationVisitor();
+            $traverser->addVisitor($visitor);
+            $traverser->traverse($ast);
+
+            $fileObjectType = $visitor->getType();
+            $fileObjectFullQualifiedObjectNamespace = $visitor->getFullQualifiedObjectNamespace();
+            if ($fileObjectType !== ''
+                && $expectedFullQualifiedObjectNamespace !== $fileObjectFullQualifiedObjectNamespace
+            ) {
+                $invalidNamespaces[$sysExtName][] = [
+                    'file' => $relativeFilenameFromRoot,
+                    'shouldBe' => $expectedFullQualifiedObjectNamespace,
+                    'actualIs' => $fileObjectFullQualifiedObjectNamespace,
+                ];
+            }
+        }
+
+        $output = new \Symfony\Component\Console\Output\ConsoleOutput();
+        $output->writeln('');
+        if ($invalidNamespaces !== []) {
+            $output->writeln(' ❌ Namespace integrity broken.');
+            $output->writeln('');
+            $table = new \Symfony\Component\Console\Helper\Table($output);
+            $table->setHeaders([
+                'EXT',
+                'File',
+                'should be',
+                'actual is',
+            ]);
+            foreach ($invalidNamespaces as $extKey => $results) {
+                foreach ($results as $result) {
+                    $table->addRow([
+                        $extKey,
+                        $result['file'],
+                        $result['shouldBe'] ?: '❌ no proper registered PSR-4 namespace',
+                        $result['actualIs'],
+                    ]);
+                }
+            }
+            $table->render();
+            $output->writeln('');
+            $output->writeln('');
+            return 1;
+        }
+        $output->writeln(' ✅ Namespace integrity is in good shape.');
+        $output->writeln('');
+        return 0;
+    }
+
+    protected function determineExpectedFullQualifiedNamespace(
+        string $systemExtensionKey,
+        string $relativeFilename
+    ): string {
+        $namespace = '';
+        if (str_starts_with($relativeFilename, 'Classes/')) {
+            $namespace = $this->getExtensionClassesNamespace($systemExtensionKey, $relativeFilename);
+        } elseif (str_starts_with($relativeFilename, 'Tests/')) {
+            $namespace = $this->getExtensionTestsNamespaces($systemExtensionKey, $relativeFilename);
+        }
+        $ignorePartValues= ['Classes', 'Tests'];
+        if ($namespace !== '') {
+            $parts = explode('/', $relativeFilename);
+            if (in_array($parts[0], $ignorePartValues, true)) {
+                unset($parts[0]);
+            }
+            foreach ($parts as $part) {
+                if (str_ends_with($part, '.php')) {
+                    $namespace .= mb_substr($part, 0, -4);
+                    break;
+                }
+                $namespace .= $part . '\\';
+            }
+        }
+        return $namespace;
+    }
+
+    protected function getExtensionClassesNamespace(
+        string $systemExtensionKey,
+        string $relativeFilename
+    ): string {
+        return $this->getPSR4NamespaceFromComposerJson(
+            $systemExtensionKey,
+            __DIR__ . '/../../typo3/sysext/' . $systemExtensionKey . '/composer.json',
+            $relativeFilename
+        );
+    }
+
+    protected function getExtensionTestsNamespaces(
+        string $systemExtensionKey,
+        string $relativeFilename
+    ): string {
+        return $this->getPSR4NamespaceFromComposerJson(
+            $systemExtensionKey,
+            __DIR__ . '/../../composer.json',
+            $relativeFilename,
+            true
+        );
+    }
+
+    protected function getPSR4NamespaceFromComposerJson(
+        string $systemExtensionKey,
+        string $fullComposerJsonFilePath,
+        string $relativeFileName,
+        bool $autoloadDev=false
+    ): string {
+        $autoloadKey = 'autoload';
+        if ($autoloadDev) {
+            $autoloadKey .= '-dev';
+        }
+        if (file_exists($fullComposerJsonFilePath)) {
+            $composerInfo = \json_decode(
+                file_get_contents($fullComposerJsonFilePath),
+                true
+            );
+            if (is_array($composerInfo)) {
+                $autoloadPSR4 = $composerInfo[$autoloadKey]['psr-4'] ?? [];
+
+                $pathBasedAutoloadInformation = [];
+                foreach ($autoloadPSR4 as $namespace => $relativePath) {
+                    $pathBasedAutoloadInformation[trim($relativePath, '/') . '/'] = $namespace;
+                }
+                $keys = array_map('mb_strlen', array_keys($pathBasedAutoloadInformation));
+                array_multisort($keys, SORT_DESC, $pathBasedAutoloadInformation);
+
+                foreach ($pathBasedAutoloadInformation as $relativePath => $namespace) {
+                    if ($autoloadDev && str_starts_with('typo3/sysext/' . $systemExtensionKey . '/' . $relativeFileName, $relativePath)) {
+                        return $namespace;
+                    }
+                    if (str_starts_with($relativeFileName, $relativePath)) {
+                        return $namespace;
+                    }
+                }
+            }
+        }
+        return '';
+    }
+
+    protected function createFinder(): Finder
+    {
+        return (new Finder())
+            ->files()
+            ->in(
+                [
+                    __DIR__ . '/../../typo3/sysext/*/Classes',
+                    __DIR__ . '/../../typo3/sysext/*/Tests/Unit',
+                    __DIR__ . '/../../typo3/sysext/*/Tests/UnitDeprecated',
+                    __DIR__ . '/../../typo3/sysext/*/Tests/Functional',
+                    __DIR__ . '/../../typo3/sysext/*/Tests/FunctionalDeprecated',
+                    __DIR__ . '/../../typo3/sysext/core/Tests/Acceptance',
+                ]
+            )
+            ->notPath([
+                'typo3/sysext/core/Tests/Acceptance/Support/_generated',
+            ])
+            // @todo remove fixture extensions exclude and handle properly after fixture extensions has been streamlined
+            ->notPath([
+                'Fixtures/Extensions',
+                'Fixtures/Extension',
+                'Fixture/Extensions',
+                'Fixture/Extension',
+                'Core/Fixtures/test_extension',
+                'Fixtures/testclasses',
+            ])
+            ->name('*.php')
+            ->sortByName();
+    }
+}
+
+/**
+ * nikic/php-parser node visitor fo find namespace information
+ */
+class NamespaceValidationVisitor extends NodeVisitorAbstract
+{
+    private string $type = '';
+    private string $fullQualifiedObjectNamespace = '';
+
+    public function enterNode(Node $node)
+    {
+        if ($this->type === '') {
+            if ($node instanceof Node\Stmt\Class_
+                && !$node->isAnonymous()
+            ) {
+                $this->type = 'class';
+                $this->fullQualifiedObjectNamespace = (string)$node->namespacedName;
+            }
+            if ($node instanceof Node\Stmt\Interface_) {
+                $this->type = 'interface';
+                $this->fullQualifiedObjectNamespace = (string)$node->namespacedName;
+            }
+            if ($node instanceof Node\Stmt\Enum_) {
+                $this->type = 'enum';
+                $this->fullQualifiedObjectNamespace = (string)$node->namespacedName;
+            }
+            if ($node instanceof Node\Stmt\Trait_) {
+                $this->type = 'trait';
+                $this->fullQualifiedObjectNamespace = (string)$node->namespacedName;
+            }
+        }
+    }
+
+    public function getType(): string
+    {
+        return $this->type;
+    }
+
+    public function getFullQualifiedObjectNamespace(): string
+    {
+        return $this->fullQualifiedObjectNamespace;
+    }
+}
+
+// execute scan and return corresponding exit code.
+// 0: everything ok
+// 1: failed, one or more files has invalid namespace declaration
+exit((new CheckNamespaceIntegrity())->scan());
diff --git a/Build/Scripts/runTests.sh b/Build/Scripts/runTests.sh
index 3bc6f2bdae21e7b300a4ad2359482ddb36721722..cee04601af504b102c66f5a1a2155a97a6481b6d 100755
--- a/Build/Scripts/runTests.sh
+++ b/Build/Scripts/runTests.sh
@@ -152,6 +152,7 @@ Options:
             - checkFilePathLength: test core file paths do not exceed maximum length
             - checkGitSubmodule: test core git has no sub modules defined
             - checkGruntClean: Verify "grunt build" is clean. Warning: Executes git commands! Usually used in CI only.
+            - checkNamespaceIntegrity: Verify namespace integrity in class and test code files are in good shape.
             - checkPermissions: test some core files for correct executable bits
             - checkRst: test .rst files for integrity
             - checkTestMethodsPrefix: check tests methods do not start with "test"
@@ -650,6 +651,12 @@ case ${TEST_SUITE} in
         SUITE_EXIT_CODE=$?
         docker-compose down
         ;;
+    checkNamespaceIntegrity)
+        setUpDockerComposeDotEnv
+        docker-compose run check_namespace_integrity
+        SUITE_EXIT_CODE=$?
+        docker-compose down
+        ;;
     checkPermissions)
         setUpDockerComposeDotEnv
         docker-compose run check_permissions
diff --git a/Build/gitlab-ci/nightly/integrity.yml b/Build/gitlab-ci/nightly/integrity.yml
index a0ffdeddabd52ab400b7640c8f00d2f426e80651..8f9ac0294e9ca7411629677564260b431190c937 100644
--- a/Build/gitlab-ci/nightly/integrity.yml
+++ b/Build/gitlab-ci/nightly/integrity.yml
@@ -52,6 +52,7 @@ integration various:
     - Build/Scripts/runTests.sh -s checkBom -p 7.4
     - Build/Scripts/runTests.sh -s checkComposer -p 7.4
     - Build/Scripts/runTests.sh -s checkTestMethodsPrefix -p 7.4
+    - Build/Scripts/runTests.sh -s checkNamespaceIntegrity -p 7.4
 
 lint php 7.4:
   stage: integrity
diff --git a/Build/gitlab-ci/pre-merge/integrity.yml b/Build/gitlab-ci/pre-merge/integrity.yml
index a05aef2e51b7effb3acec07bfe63c8563a6bd0b4..98809b72d4b22844ab235122af39005695b04a23 100644
--- a/Build/gitlab-ci/pre-merge/integrity.yml
+++ b/Build/gitlab-ci/pre-merge/integrity.yml
@@ -56,6 +56,7 @@ integration various pre-merge:
     - Build/Scripts/runTests.sh -s checkBom -p 7.4
     - Build/Scripts/runTests.sh -s checkComposer -p 7.4
     - Build/Scripts/runTests.sh -s checkTestMethodsPrefix -p 7.4
+    - Build/Scripts/runTests.sh -s checkNamespaceIntegrity -p 7.4
 
 lint scss ts html pre-merge:
   stage: main
diff --git a/Build/testing-docker/local/docker-compose.yml b/Build/testing-docker/local/docker-compose.yml
index 252a5c83b700adf23ae250aa04bb9571db1aeb06..06db4b186b9953f5f1610cc3b3910c3ab223047f 100644
--- a/Build/testing-docker/local/docker-compose.yml
+++ b/Build/testing-docker/local/docker-compose.yml
@@ -611,6 +611,20 @@ services:
         git status | grep -q \"nothing to commit, working tree clean\"
       "
 
+  check_namespace_integrity:
+    image: typo3/core-testing-${DOCKER_PHP_IMAGE}:latest
+    user: "${HOST_UID}"
+    volumes:
+      - ${CORE_ROOT}:${CORE_ROOT}
+    working_dir: ${CORE_ROOT}
+    command: >
+      /bin/sh -c "
+        if [ ${SCRIPT_VERBOSE} -eq 1 ]; then
+          set -x
+        fi
+        php -dxdebug.mode=off Build/Scripts/checkNamespaceIntegrity.php;
+      "
+
   check_permissions:
     image: typo3/core-testing-${DOCKER_PHP_IMAGE}:latest
     user: "${HOST_UID}"
diff --git a/typo3/sysext/core/Tests/UnitDeprecated/Database/SoftReferenceIndexTest.php b/typo3/sysext/core/Tests/UnitDeprecated/Database/SoftReferenceIndexTest.php
index d7a041769958a3927255529fd0b414661291c01c..4882676d13e0cc10c102828950d00d6343f692a2 100644
--- a/typo3/sysext/core/Tests/UnitDeprecated/Database/SoftReferenceIndexTest.php
+++ b/typo3/sysext/core/Tests/UnitDeprecated/Database/SoftReferenceIndexTest.php
@@ -15,7 +15,7 @@ declare(strict_types=1);
  * The TYPO3 project - inspiring people to share!
  */
 
-namespace TYPO3\CMS\Core\Tests\Unit\Database;
+namespace TYPO3\CMS\Core\Tests\UnitDeprecated\Database;
 
 use Prophecy\Argument;
 use Prophecy\PhpUnit\ProphecyTrait;