diff --git a/Neos.Flow/Classes/Annotations/Route.php b/Neos.Flow/Classes/Annotations/Route.php
new file mode 100644
index 0000000000..09159d72f1
--- /dev/null
+++ b/Neos.Flow/Classes/Annotations/Route.php
@@ -0,0 +1,40 @@
+routesProvider->getRoutes()[$index - 1] ?? null;
+ $route = iterator_to_array($this->routesProvider->getRoutes()->getIterator())[$index - 1] ?? null;
if ($route === null) {
$this->outputLine('Route %d was not found!', [$index]);
$this->outputLine('Run ./flow routing:list to show all registered routes');
diff --git a/Neos.Flow/Classes/Mvc/Routing/AnnotationRoutesProvider.php b/Neos.Flow/Classes/Mvc/Routing/AnnotationRoutesProvider.php
new file mode 100644
index 0000000000..bf7e07bb01
--- /dev/null
+++ b/Neos.Flow/Classes/Mvc/Routing/AnnotationRoutesProvider.php
@@ -0,0 +1,85 @@
+reflectionService->getClassesContainingMethodsAnnotatedWith(Flow\Route::class);
+ foreach ($annotatedClasses as $className) {
+ $controllerObjectName = $this->objectManager->getCaseSensitiveObjectName($className);
+ $controllerPackageKey = $this->objectManager->getPackageKeyByObjectName($controllerObjectName);
+ $controllerPackageNamespace = str_replace('.', '\\', $controllerPackageKey);
+ if (!str_ends_with($className, 'Controller')) {
+ throw new \Exception('only for controller classes');
+ }
+ if (!str_starts_with($className, $controllerPackageNamespace . '\\')) {
+ throw new \Exception('only for classes in package namespace');
+ }
+
+ $localClassName = substr($className, strlen($controllerPackageNamespace) + 1);
+
+ if (str_starts_with($localClassName, 'Controller\\')) {
+ $controllerName = substr($localClassName, 11);
+ $subPackage = null;
+ } elseif (str_contains($localClassName, '\\Controller\\')) {
+ list ($subPackage, $controllerName) = explode('\\Controller\\', $localClassName);
+ } else {
+ throw new \Exception('unknown controller pattern');
+ }
+
+ $annotatedMethods = $this->reflectionService->getMethodsAnnotatedWith($className, Flow\Route::class);
+ // @todo remove once reflectionService handles multiple annotations properly
+ $annotatedMethods = array_unique($annotatedMethods);
+ foreach ($annotatedMethods as $methodName) {
+ if (!str_ends_with($methodName, 'Action')) {
+ throw new \Exception('only for action methods');
+ }
+ $annotations = $this->reflectionService->getMethodAnnotations($className, $methodName, Flow\Route::class);
+ foreach ($annotations as $annotation) {
+ if ($annotation instanceof Flow\Route) {
+ $configuration = [
+ 'uriPattern' => $annotation->uriPattern,
+ 'defaults' => Arrays::arrayMergeRecursiveOverrule(
+ [
+ '@package' => $controllerPackageKey,
+ '@subpackage' => $subPackage,
+ '@controller' => substr($controllerName, 0, -10),
+ '@action' => substr($methodName, 0, -6),
+ '@format' => 'html'
+ ],
+ $annotation->defaults ?? []
+ )
+ ];
+ if ($annotation->name !== null) {
+ $configuration['name'] = $annotation->name;
+ }
+ if ($annotation->httpMethods !== null) {
+ $configuration['httpMethods'] = $annotation->httpMethods;
+ }
+ $routes[] = Route::fromConfiguration($configuration);
+ }
+ }
+ }
+ }
+ return Routes::create(...$routes);
+ }
+}
diff --git a/Neos.Flow/Classes/Mvc/Routing/CombinedRoutesProvider.php b/Neos.Flow/Classes/Mvc/Routing/CombinedRoutesProvider.php
new file mode 100644
index 0000000000..1aaa18f4e2
--- /dev/null
+++ b/Neos.Flow/Classes/Mvc/Routing/CombinedRoutesProvider.php
@@ -0,0 +1,17 @@
+annotationRoutesProvider->getRoutes()->merge($this->configurationRoutesProvider->getRoutes());
+ }
+}
diff --git a/Neos.Flow/Configuration/Objects.yaml b/Neos.Flow/Configuration/Objects.yaml
index 4f116f465d..53cd98eacc 100644
--- a/Neos.Flow/Configuration/Objects.yaml
+++ b/Neos.Flow/Configuration/Objects.yaml
@@ -258,7 +258,7 @@ Neos\Flow\Mvc\Routing\RouterInterface:
className: Neos\Flow\Mvc\Routing\Router
Neos\Flow\Mvc\Routing\RoutesProviderInterface:
- className: Neos\Flow\Mvc\Routing\ConfigurationRoutesProvider
+ className: Neos\Flow\Mvc\Routing\CombinedRoutesProvider
Neos\Flow\Mvc\Routing\RouterCachingService:
properties: