diff --git a/apps/accessibility/lib/AppInfo/Application.php b/apps/accessibility/lib/AppInfo/Application.php
index 0cac878d34e6ff803a4c93e86f6d4b4529bda2e7..e691b8353d99c5e8d442d5f7f65b24a9ebb0b78f 100644
--- a/apps/accessibility/lib/AppInfo/Application.php
+++ b/apps/accessibility/lib/AppInfo/Application.php
@@ -1,4 +1,7 @@
 <?php
+
+declare(strict_types=1);
+
 /**
  * @copyright Copyright (c) 2018 John Molakvoæ <skjnldsv@protonmail.com>
  *
@@ -27,14 +30,21 @@
 namespace OCA\Accessibility\AppInfo;
 
 use OCP\AppFramework\App;
+use OCP\AppFramework\Bootstrap\IBootContext;
+use OCP\AppFramework\Bootstrap\IBootstrap;
+use OCP\AppFramework\Bootstrap\IRegistrationContext;
 use OCP\IConfig;
+use OCP\IServerContainer;
 use OCP\IURLGenerator;
 use OCP\IUserSession;
+use function count;
+use function implode;
+use function md5;
 
-class Application extends App {
+class Application extends App implements IBootstrap {
 
 	/** @var string */
-	public const APP_NAME = 'accessibility';
+	public const APP_ID = 'accessibility';
 
 	/** @var IConfig */
 	private $config;
@@ -46,31 +56,48 @@ class Application extends App {
 	private $urlGenerator;
 
 	public function __construct() {
-		parent::__construct(self::APP_NAME);
-		$this->config       = \OC::$server->getConfig();
-		$this->userSession  = \OC::$server->getUserSession();
-		$this->urlGenerator = \OC::$server->getURLGenerator();
+		parent::__construct(self::APP_ID);
+	}
+
+	public function register(IRegistrationContext $context): void {
+	}
+
+	public function boot(IBootContext $context): void {
+		$this->injectCss(
+			$context->getAppContainer()->query(IUserSession::class),
+			$context->getAppContainer()->query(IConfig::class),
+			$context->getAppContainer()->query(IURLGenerator::class)
+		);
+		$this->injectJavascript(
+			$context->getAppContainer()->query(IURLGenerator::class),
+			$context->getAppContainer()->query(IConfig::class),
+			$context->getServerContainer()
+		);
 	}
 
-	public function injectCss() {
+	private function injectCss(IUserSession $userSession,
+							   IConfig $config,
+							   IURLGenerator $urlGenerator) {
 		// Inject the fake css on all pages if enabled and user is logged
-		$loggedUser = $this->userSession->getUser();
-		if (!is_null($loggedUser)) {
-			$userValues = $this->config->getUserKeys($loggedUser->getUID(), self::APP_NAME);
+		$loggedUser = $userSession->getUser();
+		if ($loggedUser !== null) {
+			$userValues = $config->getUserKeys($loggedUser->getUID(), self::APP_ID);
 			// we want to check if any theme or font is enabled.
 			if (count($userValues) > 0) {
-				$hash = $this->config->getUserValue($loggedUser->getUID(), self::APP_NAME, 'icons-css', md5(implode('-', $userValues)));
-				$linkToCSS = $this->urlGenerator->linkToRoute(self::APP_NAME . '.accessibility.getCss', ['md5' => $hash]);
+				$hash = $config->getUserValue($loggedUser->getUID(), self::APP_ID, 'icons-css', md5(implode('-', $userValues)));
+				$linkToCSS = $urlGenerator->linkToRoute(self::APP_ID . '.accessibility.getCss', ['md5' => $hash]);
 				\OCP\Util::addHeader('link', ['rel' => 'stylesheet', 'href' => $linkToCSS]);
 			}
 		}
 	}
 
-	public function injectJavascript() {
-		$linkToJs = $this->urlGenerator->linkToRoute(
-			self::APP_NAME . '.accessibility.getJavascript',
+	private function injectJavascript(IURLGenerator $urlGenerator,
+									  IConfig $config,
+									  IServerContainer $serverContainer) {
+		$linkToJs = $urlGenerator->linkToRoute(
+			self::APP_ID . '.accessibility.getJavascript',
 			[
-				'v' => \OC::$server->getConfig()->getAppValue('accessibility', 'cachebuster', '0'),
+				'v' => $config->getAppValue(self::APP_ID, 'cachebuster', '0'),
 			]
 		);
 
@@ -78,7 +105,7 @@ class Application extends App {
 			'script',
 			[
 				'src' => $linkToJs,
-				'nonce' => \OC::$server->getContentSecurityPolicyNonceManager()->getNonce()
+				'nonce' => $serverContainer->getContentSecurityPolicyNonceManager()->getNonce()
 			],
 			''
 		);
diff --git a/apps/accessibility/lib/Migration/RepairUserConfig.php b/apps/accessibility/lib/Migration/RepairUserConfig.php
index 4b946f6a78512c221920376be3499a0767300cd0..2b4bbc1ed19c8345841f923756acea3aafb29f70 100644
--- a/apps/accessibility/lib/Migration/RepairUserConfig.php
+++ b/apps/accessibility/lib/Migration/RepairUserConfig.php
@@ -75,13 +75,13 @@ class RepairUserConfig implements IRepairStep {
 	public function run(IOutput $output) {
 		$output->startProgress();
 		$this->userManager->callForSeenUsers(function (IUser $user) use ($output) {
-			$theme = $this->config->getUserValue($user->getUID(), Application::APP_NAME, 'theme', false);
+			$theme = $this->config->getUserValue($user->getUID(), Application::APP_ID, 'theme', false);
 			if ($theme === 'themedark') {
-				$this->config->setUserValue($user->getUID(), Application::APP_NAME, 'theme', 'dark');
+				$this->config->setUserValue($user->getUID(), Application::APP_ID, 'theme', 'dark');
 			}
 			if ($theme === 'themehighcontrast') {
-				$this->config->setUserValue($user->getUID(), Application::APP_NAME, 'highcontrast', 'highcontrast');
-				$this->config->deleteUserValue($user->getUID(), Application::APP_NAME, 'theme');
+				$this->config->setUserValue($user->getUID(), Application::APP_ID, 'highcontrast', 'highcontrast');
+				$this->config->deleteUserValue($user->getUID(), Application::APP_ID, 'theme');
 			}
 			$output->advance();
 		});
diff --git a/apps/admin_audit/lib/AppInfo/Application.php b/apps/admin_audit/lib/AppInfo/Application.php
index 83c392e92eaf0384b0267da27b6c6e30a1c42809..dde0605103cc9f0fb7bb681c2c7b8801b399a26c 100644
--- a/apps/admin_audit/lib/AppInfo/Application.php
+++ b/apps/admin_audit/lib/AppInfo/Application.php
@@ -49,90 +49,110 @@ use OCA\AdminAudit\Actions\UserManagement;
 use OCA\AdminAudit\Actions\Versions;
 use OCP\App\ManagerEvent;
 use OCP\AppFramework\App;
+use OCP\AppFramework\Bootstrap\IBootContext;
+use OCP\AppFramework\Bootstrap\IBootstrap;
+use OCP\AppFramework\Bootstrap\IRegistrationContext;
 use OCP\Authentication\TwoFactorAuth\IProvider;
 use OCP\Console\ConsoleEvent;
+use OCP\IConfig;
 use OCP\IGroupManager;
 use OCP\ILogger;
 use OCP\IPreview;
+use OCP\IServerContainer;
 use OCP\IUserSession;
+use OCP\Log\ILogFactory;
 use OCP\Share;
 use OCP\Util;
 use Symfony\Component\EventDispatcher\GenericEvent;
 
-class Application extends App {
+class Application extends App implements IBootstrap {
 
 	/** @var ILogger */
 	protected $logger;
 
 	public function __construct() {
 		parent::__construct('admin_audit');
-		$this->initLogger();
 	}
 
-	public function initLogger() {
-		$c = $this->getContainer()->getServer();
-		$config = $c->getConfig();
+	public function register(IRegistrationContext $context): void {
+	}
+
+	public function boot(IBootContext $context): void {
+		$logger = $this->getLogger(
+			$context->getAppContainer()->query(IConfig::class),
+			$context->getAppContainer()->query(ILogger::class),
+			$context->getAppContainer()->query(ILogFactory::class)
+		);
+
+		/*
+		 * TODO: once the hooks are migrated to lazy events, this should be done
+		 *       in \OCA\AdminAudit\AppInfo\Application::register
+		 */
+		$this->registerHooks($logger, $context->getServerContainer());
+	}
 
+	private function getLogger(IConfig $config,
+							   ILogger $logger,
+							   ILogFactory $logFactory): ILogger {
 		$default = $config->getSystemValue('datadirectory', \OC::$SERVERROOT . '/data') . '/audit.log';
 		$logFile = $config->getAppValue('admin_audit', 'logfile', $default);
+
 		if ($logFile === null) {
-			$this->logger = $c->getLogger();
-			return;
+			return $logger;
 		}
-		$this->logger = $c->getLogFactory()->getCustomLogger($logFile);
-	}
-
-	public function register() {
-		$this->registerHooks();
+		return $logFactory->getCustomLogger($logFile);
 	}
 
 	/**
 	 * Register hooks in order to log them
 	 */
-	protected function registerHooks() {
-		$this->userManagementHooks();
-		$this->groupHooks();
-		$this->authHooks();
+	private function registerHooks(ILogger $logger,
+									 IServerContainer $serverContainer) {
+		$this->userManagementHooks($logger, $serverContainer);
+		$this->groupHooks($logger, $serverContainer);
+		$this->authHooks($logger);
 
-		$this->consoleHooks();
-		$this->appHooks();
+		$this->consoleHooks($logger, $serverContainer);
+		$this->appHooks($logger, $serverContainer);
 
-		$this->sharingHooks();
+		$this->sharingHooks($logger);
 
-		$this->fileHooks();
-		$this->trashbinHooks();
-		$this->versionsHooks();
+		$this->fileHooks($logger, $serverContainer);
+		$this->trashbinHooks($logger);
+		$this->versionsHooks($logger);
 
-		$this->securityHooks();
+		$this->securityHooks($logger, $serverContainer);
 	}
 
-	protected function userManagementHooks() {
-		$userActions = new UserManagement($this->logger);
+	private function userManagementHooks(ILogger $logger,
+										   IServerContainer $serverContainer) {
+		$userActions = new UserManagement($logger);
 
-		Util::connectHook('OC_User', 'post_createUser',	$userActions, 'create');
-		Util::connectHook('OC_User', 'post_deleteUser',	$userActions, 'delete');
-		Util::connectHook('OC_User', 'changeUser',	$userActions, 'change');
+		Util::connectHook('OC_User', 'post_createUser', $userActions, 'create');
+		Util::connectHook('OC_User', 'post_deleteUser', $userActions, 'delete');
+		Util::connectHook('OC_User', 'changeUser', $userActions, 'change');
 
 		/** @var IUserSession|Session $userSession */
-		$userSession = $this->getContainer()->getServer()->getUserSession();
+		$userSession = $serverContainer->getUserSession();
 		$userSession->listen('\OC\User', 'postSetPassword', [$userActions, 'setPassword']);
 		$userSession->listen('\OC\User', 'assignedUserId', [$userActions, 'assign']);
 		$userSession->listen('\OC\User', 'postUnassignedUserId', [$userActions, 'unassign']);
 	}
 
-	protected function groupHooks() {
-		$groupActions = new GroupManagement($this->logger);
+	private function groupHooks(ILogger $logger,
+								  IServerContainer $serverContainer) {
+		$groupActions = new GroupManagement($logger);
 
 		/** @var IGroupManager|Manager $groupManager */
-		$groupManager = $this->getContainer()->getServer()->getGroupManager();
-		$groupManager->listen('\OC\Group', 'postRemoveUser',  [$groupActions, 'removeUser']);
-		$groupManager->listen('\OC\Group', 'postAddUser',  [$groupActions, 'addUser']);
-		$groupManager->listen('\OC\Group', 'postDelete',  [$groupActions, 'deleteGroup']);
-		$groupManager->listen('\OC\Group', 'postCreate',  [$groupActions, 'createGroup']);
+		$groupManager = $serverContainer->getGroupManager();
+		$groupManager->listen('\OC\Group', 'postRemoveUser', [$groupActions, 'removeUser']);
+		$groupManager->listen('\OC\Group', 'postAddUser', [$groupActions, 'addUser']);
+		$groupManager->listen('\OC\Group', 'postDelete', [$groupActions, 'deleteGroup']);
+		$groupManager->listen('\OC\Group', 'postCreate', [$groupActions, 'createGroup']);
 	}
 
-	protected function sharingHooks() {
-		$shareActions = new Sharing($this->logger);
+	private function sharingHooks(ILogger $logger) {
+		$shareActions = new Sharing($logger);
 
 		Util::connectHook(Share::class, 'post_shared', $shareActions, 'shared');
 		Util::connectHook(Share::class, 'post_unshare', $shareActions, 'unshare');
@@ -143,41 +163,44 @@ class Application extends App {
 		Util::connectHook(Share::class, 'share_link_access', $shareActions, 'shareAccessed');
 	}
 
-	protected function authHooks() {
-		$authActions = new Auth($this->logger);
+	private function authHooks(ILogger $logger) {
+		$authActions = new Auth($logger);
 
 		Util::connectHook('OC_User', 'pre_login', $authActions, 'loginAttempt');
 		Util::connectHook('OC_User', 'post_login', $authActions, 'loginSuccessful');
 		Util::connectHook('OC_User', 'logout', $authActions, 'logout');
 	}
 
-	protected function appHooks() {
-		$eventDispatcher = $this->getContainer()->getServer()->getEventDispatcher();
-		$eventDispatcher->addListener(ManagerEvent::EVENT_APP_ENABLE, function (ManagerEvent $event) {
-			$appActions = new AppManagement($this->logger);
+	private function appHooks(ILogger $logger,
+								IServerContainer $serverContainer) {
+		$eventDispatcher = $serverContainer->getEventDispatcher();
+		$eventDispatcher->addListener(ManagerEvent::EVENT_APP_ENABLE, function (ManagerEvent $event) use ($logger) {
+			$appActions = new AppManagement($logger);
 			$appActions->enableApp($event->getAppID());
 		});
-		$eventDispatcher->addListener(ManagerEvent::EVENT_APP_ENABLE_FOR_GROUPS, function (ManagerEvent $event) {
-			$appActions = new AppManagement($this->logger);
+		$eventDispatcher->addListener(ManagerEvent::EVENT_APP_ENABLE_FOR_GROUPS, function (ManagerEvent $event) use ($logger) {
+			$appActions = new AppManagement($logger);
 			$appActions->enableAppForGroups($event->getAppID(), $event->getGroups());
 		});
-		$eventDispatcher->addListener(ManagerEvent::EVENT_APP_DISABLE, function (ManagerEvent $event) {
-			$appActions = new AppManagement($this->logger);
+		$eventDispatcher->addListener(ManagerEvent::EVENT_APP_DISABLE, function (ManagerEvent $event) use ($logger) {
+			$appActions = new AppManagement($logger);
 			$appActions->disableApp($event->getAppID());
 		});
 	}
 
-	protected function consoleHooks() {
-		$eventDispatcher = $this->getContainer()->getServer()->getEventDispatcher();
-		$eventDispatcher->addListener(ConsoleEvent::EVENT_RUN, function (ConsoleEvent $event) {
-			$appActions = new Console($this->logger);
+	private function consoleHooks(ILogger $logger,
+									IServerContainer $serverContainer) {
+		$eventDispatcher = $serverContainer->getEventDispatcher();
+		$eventDispatcher->addListener(ConsoleEvent::EVENT_RUN, function (ConsoleEvent $event) use ($logger) {
+			$appActions = new Console($logger);
 			$appActions->runCommand($event->getArguments());
 		});
 	}
 
-	protected function fileHooks() {
-		$fileActions = new Files($this->logger);
-		$eventDispatcher = $this->getContainer()->getServer()->getEventDispatcher();
+	private function fileHooks(ILogger $logger,
+								 IServerContainer $serverContainer) {
+		$fileActions = new Files($logger);
+		$eventDispatcher = $serverContainer->getEventDispatcher();
 		$eventDispatcher->addListener(
 			IPreview::EVENT,
 			function (GenericEvent $event) use ($fileActions) {
@@ -188,7 +211,7 @@ class Application extends App {
 					'width' => $event->getArguments()['width'],
 					'height' => $event->getArguments()['height'],
 					'crop' => $event->getArguments()['crop'],
-					'mode'  => $event->getArguments()['mode']
+					'mode' => $event->getArguments()['mode']
 				]);
 			}
 		);
@@ -237,26 +260,27 @@ class Application extends App {
 		);
 	}
 
-	protected function versionsHooks() {
-		$versionsActions = new Versions($this->logger);
+	private function versionsHooks(ILogger $logger) {
+		$versionsActions = new Versions($logger);
 		Util::connectHook('\OCP\Versions', 'rollback', $versionsActions, 'rollback');
-		Util::connectHook('\OCP\Versions', 'delete',$versionsActions, 'delete');
+		Util::connectHook('\OCP\Versions', 'delete', $versionsActions, 'delete');
 	}
 
-	protected function trashbinHooks() {
-		$trashActions = new Trashbin($this->logger);
+	private function trashbinHooks(ILogger $logger) {
+		$trashActions = new Trashbin($logger);
 		Util::connectHook('\OCP\Trashbin', 'preDelete', $trashActions, 'delete');
 		Util::connectHook('\OCA\Files_Trashbin\Trashbin', 'post_restore', $trashActions, 'restore');
 	}
 
-	protected function securityHooks() {
-		$eventDispatcher = $this->getContainer()->getServer()->getEventDispatcher();
-		$eventDispatcher->addListener(IProvider::EVENT_SUCCESS, function (GenericEvent $event) {
-			$security = new Security($this->logger);
+	private function securityHooks(ILogger $logger,
+									 IServerContainer $serverContainer) {
+		$eventDispatcher = $serverContainer->getEventDispatcher();
+		$eventDispatcher->addListener(IProvider::EVENT_SUCCESS, function (GenericEvent $event) use ($logger) {
+			$security = new Security($logger);
 			$security->twofactorSuccess($event->getSubject(), $event->getArguments());
 		});
-		$eventDispatcher->addListener(IProvider::EVENT_FAILED, function (GenericEvent $event) {
-			$security = new Security($this->logger);
+		$eventDispatcher->addListener(IProvider::EVENT_FAILED, function (GenericEvent $event) use ($logger) {
+			$security = new Security($logger);
 			$security->twofactorFailed($event->getSubject(), $event->getArguments());
 		});
 	}
diff --git a/apps/comments/composer/composer/autoload_classmap.php b/apps/comments/composer/composer/autoload_classmap.php
index c1cdce7d146a6a721e7b98fde0df89630e170c8c..d5d51c7a12c5a9776d72c78da21893a2aa0fa6a4 100644
--- a/apps/comments/composer/composer/autoload_classmap.php
+++ b/apps/comments/composer/composer/autoload_classmap.php
@@ -16,6 +16,7 @@ return array(
     'OCA\\Comments\\Controller\\Notifications' => $baseDir . '/../lib/Controller/Notifications.php',
     'OCA\\Comments\\EventHandler' => $baseDir . '/../lib/EventHandler.php',
     'OCA\\Comments\\JSSettingsHelper' => $baseDir . '/../lib/JSSettingsHelper.php',
+    'OCA\\Comments\\Listener\\CommentsEntityEventListener' => $baseDir . '/../lib/Listener/CommentsEntityEventListener.php',
     'OCA\\Comments\\Listener\\LoadAdditionalScripts' => $baseDir . '/../lib/Listener/LoadAdditionalScripts.php',
     'OCA\\Comments\\Listener\\LoadSidebarScripts' => $baseDir . '/../lib/Listener/LoadSidebarScripts.php',
     'OCA\\Comments\\Notification\\Listener' => $baseDir . '/../lib/Notification/Listener.php',
diff --git a/apps/comments/composer/composer/autoload_static.php b/apps/comments/composer/composer/autoload_static.php
index b38ae5841a018f87c885bc94dac2356144b650d5..1292415290ba84cc15fef8943f738783f1962441 100644
--- a/apps/comments/composer/composer/autoload_static.php
+++ b/apps/comments/composer/composer/autoload_static.php
@@ -31,6 +31,7 @@ class ComposerStaticInitComments
         'OCA\\Comments\\Controller\\Notifications' => __DIR__ . '/..' . '/../lib/Controller/Notifications.php',
         'OCA\\Comments\\EventHandler' => __DIR__ . '/..' . '/../lib/EventHandler.php',
         'OCA\\Comments\\JSSettingsHelper' => __DIR__ . '/..' . '/../lib/JSSettingsHelper.php',
+        'OCA\\Comments\\Listener\\CommentsEntityEventListener' => __DIR__ . '/..' . '/../lib/Listener/CommentsEntityEventListener.php',
         'OCA\\Comments\\Listener\\LoadAdditionalScripts' => __DIR__ . '/..' . '/../lib/Listener/LoadAdditionalScripts.php',
         'OCA\\Comments\\Listener\\LoadSidebarScripts' => __DIR__ . '/..' . '/../lib/Listener/LoadSidebarScripts.php',
         'OCA\\Comments\\Notification\\Listener' => __DIR__ . '/..' . '/../lib/Notification/Listener.php',
diff --git a/apps/comments/lib/AppInfo/Application.php b/apps/comments/lib/AppInfo/Application.php
index 7f3235eb62d6e2cb153c16539346eb10ea5b02c4..8bcf17b2afe433182a0813a28695127b5df973a6 100644
--- a/apps/comments/lib/AppInfo/Application.php
+++ b/apps/comments/lib/AppInfo/Application.php
@@ -31,6 +31,7 @@ use OCA\Comments\Capabilities;
 use OCA\Comments\Controller\Notifications;
 use OCA\Comments\EventHandler;
 use OCA\Comments\JSSettingsHelper;
+use OCA\Comments\Listener\CommentsEntityEventListener;
 use OCA\Comments\Listener\LoadAdditionalScripts;
 use OCA\Comments\Listener\LoadSidebarScripts;
 use OCA\Comments\Notification\Notifier;
@@ -38,60 +39,55 @@ use OCA\Comments\Search\Provider;
 use OCA\Files\Event\LoadAdditionalScriptsEvent;
 use OCA\Files\Event\LoadSidebar;
 use OCP\AppFramework\App;
+use OCP\AppFramework\Bootstrap\IBootContext;
+use OCP\AppFramework\Bootstrap\IBootstrap;
+use OCP\AppFramework\Bootstrap\IRegistrationContext;
 use OCP\Comments\CommentsEntityEvent;
-use OCP\EventDispatcher\IEventDispatcher;
+use OCP\IServerContainer;
 use OCP\Util;
 
-class Application extends App {
+class Application extends App implements IBootstrap {
 	public const APP_ID = 'comments';
 
 	public function __construct(array $urlParams = []) {
 		parent::__construct(self::APP_ID, $urlParams);
-		$container = $this->getContainer();
-
-		$container->registerAlias('NotificationsController', Notifications::class);
-
-		$jsSettingsHelper = new JSSettingsHelper($container->getServer());
-		Util::connectHook('\OCP\Config', 'js', $jsSettingsHelper, 'extend');
-
-		$this->register();
 	}
 
-	private function register() {
-		$server = $this->getContainer()->getServer();
-
-		/** @var IEventDispatcher $newDispatcher */
-		$dispatcher = $server->query(IEventDispatcher::class);
+	public function register(IRegistrationContext $context): void {
+		$context->registerCapability(Capabilities::class);
 
-		$this->registerEventsScripts($dispatcher);
-		$this->registerDavEntity($dispatcher);
-		$this->registerNotifier();
-		$this->registerCommentsEventHandler();
+		$context->registerServiceAlias('NotificationsController', Notifications::class);
 
-		$this->getContainer()->registerCapability(Capabilities::class);
-		$server->getSearch()->registerProvider(Provider::class, ['apps' => ['files']]);
+		$context->registerEventListener(
+			LoadAdditionalScriptsEvent::class,
+			LoadAdditionalScripts::class
+		);
+		$context->registerEventListener(
+			LoadSidebar::class,
+			LoadSidebarScripts::class
+		);
+		$context->registerEventListener(
+			CommentsEntityEvent::EVENT_ENTITY,
+			CommentsEntityEventListener::class
+		);
 	}
 
-	protected function registerEventsScripts(IEventDispatcher $dispatcher) {
-		$dispatcher->addServiceListener(LoadAdditionalScriptsEvent::class, LoadAdditionalScripts::class);
-		$dispatcher->addServiceListener(LoadSidebar::class, LoadSidebarScripts::class);
-	}
+	public function boot(IBootContext $context): void {
+		$this->registerNotifier($context->getServerContainer());
+		$this->registerCommentsEventHandler($context->getServerContainer());
 
-	protected function registerDavEntity(IEventDispatcher $dispatcher) {
-		$dispatcher->addListener(CommentsEntityEvent::EVENT_ENTITY, function (CommentsEntityEvent $event) {
-			$event->addEntityCollection('files', function ($name) {
-				$nodes = \OC::$server->getUserFolder()->getById((int)$name);
-				return !empty($nodes);
-			});
-		});
+		$jsSettingsHelper = new JSSettingsHelper($context->getServerContainer());
+		Util::connectHook('\OCP\Config', 'js', $jsSettingsHelper, 'extend');
+
+		$context->getServerContainer()->getSearch()->registerProvider(Provider::class, ['apps' => ['files']]);
 	}
 
-	protected function registerNotifier() {
-		$this->getContainer()->getServer()->getNotificationManager()->registerNotifierService(Notifier::class);
+	protected function registerNotifier(IServerContainer $container) {
+		$container->getNotificationManager()->registerNotifierService(Notifier::class);
 	}
 
-	protected function registerCommentsEventHandler() {
-		$this->getContainer()->getServer()->getCommentsManager()->registerEventHandler(function () {
+	protected function registerCommentsEventHandler(IServerContainer $container) {
+		$container->getCommentsManager()->registerEventHandler(function () {
 			return $this->getContainer()->query(EventHandler::class);
 		});
 	}
diff --git a/apps/accessibility/appinfo/app.php b/apps/comments/lib/Listener/CommentsEntityEventListener.php
similarity index 50%
rename from apps/accessibility/appinfo/app.php
rename to apps/comments/lib/Listener/CommentsEntityEventListener.php
index b37e726b6ce19111cc848cdac1273f5ff79b16a6..c08d992405f380718a15b4e656165501b7b7bb0f 100644
--- a/apps/accessibility/appinfo/app.php
+++ b/apps/comments/lib/Listener/CommentsEntityEventListener.php
@@ -3,11 +3,9 @@
 declare(strict_types=1);
 
 /**
- * @copyright Copyright (c) 2018 John Molakvoæ <skjnldsv@protonmail.com>
+ * @copyright 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
  *
- * @author Alexey Pyltsyn <lex61rus@gmail.com>
- * @author John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
- * @author Robin Appelman <robin@icewind.nl>
+ * @author 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
  *
  * @license GNU AGPL version 3 or any later version
  *
@@ -22,16 +20,25 @@ declare(strict_types=1);
  * GNU Affero General Public License for more details.
  *
  * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-use OCA\Accessibility\AppInfo\Application;
+namespace OCA\Comments\Listener;
+
+use OCP\Comments\CommentsEntityEvent;
+use OCP\EventDispatcher\Event;
+use OCP\EventDispatcher\IEventListener;
 
-$app = \OC::$server->query(Application::class);
+class CommentsEntityEventListener implements IEventListener {
+	public function handle(Event $event): void {
+		if (!($event instanceof CommentsEntityEvent)) {
+			// Unrelated
+			return;
+		}
 
-// Separate from the constructor since the route are not initialized before that
-// 1. create the app
-// 2. generate css route and inject
-$app->injectCss();
-$app->injectJavascript();
+		$event->addEntityCollection('files', function ($name) {
+			$nodes = \OC::$server->getUserFolder()->getById((int)$name);
+			return !empty($nodes);
+		});
+	}
+}
diff --git a/apps/settings/lib/AppInfo/Application.php b/apps/settings/lib/AppInfo/Application.php
index 8f70ca426ad8b782a068caa3b2539cb8d83ffa43..27f51a32f106114325362c04dfa4eafbc084bc56 100644
--- a/apps/settings/lib/AppInfo/Application.php
+++ b/apps/settings/lib/AppInfo/Application.php
@@ -1,4 +1,7 @@
 <?php
+
+declare(strict_types=1);
+
 /**
  * @copyright Copyright (c) 2016, ownCloud, Inc.
  *
@@ -44,6 +47,9 @@ use OCA\Settings\Mailer\NewUserMailHelper;
 use OCA\Settings\Middleware\SubadminMiddleware;
 use OCP\Activity\IManager as IActivityManager;
 use OCP\AppFramework\App;
+use OCP\AppFramework\Bootstrap\IBootContext;
+use OCP\AppFramework\Bootstrap\IBootstrap;
+use OCP\AppFramework\Bootstrap\IRegistrationContext;
 use OCP\Defaults;
 use OCP\IContainer;
 use OCP\IGroup;
@@ -54,7 +60,7 @@ use OCP\Util;
 use Symfony\Component\EventDispatcher\EventDispatcherInterface;
 use Symfony\Component\EventDispatcher\GenericEvent;
 
-class Application extends App {
+class Application extends App implements IBootstrap {
 	public const APP_ID = 'settings';
 
 	/**
@@ -62,22 +68,22 @@ class Application extends App {
 	 */
 	public function __construct(array $urlParams=[]) {
 		parent::__construct(self::APP_ID, $urlParams);
+	}
 
-		$container = $this->getContainer();
-
+	public function register(IRegistrationContext $context): void {
 		// Register Middleware
-		$container->registerAlias('SubadminMiddleware', SubadminMiddleware::class);
-		$container->registerMiddleWare('SubadminMiddleware');
+		$context->registerServiceAlias('SubadminMiddleware', SubadminMiddleware::class);
+		$context->registerMiddleware(SubadminMiddleware::class);
 
 		/**
 		 * Core class wrappers
 		 */
 		/** FIXME: Remove once OC_User is non-static and mockable */
-		$container->registerService('isAdmin', function () {
+		$context->registerService('isAdmin', function () {
 			return \OC_User::isAdminUser(\OC_User::getUser());
 		});
 		/** FIXME: Remove once OC_SubAdmin is non-static and mockable */
-		$container->registerService('isSubAdmin', function (IContainer $c) {
+		$context->registerService('isSubAdmin', function (IContainer $c) {
 			$userObject = \OC::$server->getUserSession()->getUser();
 			$isSubAdmin = false;
 			if ($userObject !== null) {
@@ -85,20 +91,20 @@ class Application extends App {
 			}
 			return $isSubAdmin;
 		});
-		$container->registerService('userCertificateManager', function (IContainer $c) {
+		$context->registerService('userCertificateManager', function (IContainer $c) {
 			return $c->query('ServerContainer')->getCertificateManager();
 		}, false);
-		$container->registerService('systemCertificateManager', function (IContainer $c) {
+		$context->registerService('systemCertificateManager', function (IContainer $c) {
 			return $c->query('ServerContainer')->getCertificateManager(null);
 		}, false);
-		$container->registerService(IProvider::class, function (IContainer $c) {
+		$context->registerService(IProvider::class, function (IContainer $c) {
 			return $c->query('ServerContainer')->query(IProvider::class);
 		});
-		$container->registerService(IManager::class, function (IContainer $c) {
+		$context->registerService(IManager::class, function (IContainer $c) {
 			return $c->query('ServerContainer')->getSettingsManager();
 		});
 
-		$container->registerService(NewUserMailHelper::class, function (IContainer $c) {
+		$context->registerService(NewUserMailHelper::class, function (IContainer $c) {
 			/** @var Server $server */
 			$server = $c->query('ServerContainer');
 			/** @var Defaults $defaults */
@@ -116,9 +122,12 @@ class Application extends App {
 				Util::getDefaultEmailAddress('no-reply')
 			);
 		});
+	}
 
+	public function boot(IBootContext $context): void {
 		/** @var EventDispatcherInterface $eventDispatcher */
-		$eventDispatcher = $container->getServer()->getEventDispatcher();
+		$eventDispatcher = $context->getServerContainer()->getEventDispatcher();
+		$container = $context->getAppContainer();
 		$eventDispatcher->addListener('app_password_created', function (GenericEvent $event) use ($container) {
 			if (($token = $event->getSubject()) instanceof IToken) {
 				/** @var IActivityManager $activityManager */
@@ -141,13 +150,11 @@ class Application extends App {
 				}
 			}
 		});
-	}
 
-	public function register() {
 		Util::connectHook('OC_User', 'post_setPassword', $this, 'onChangePassword');
 		Util::connectHook('OC_User', 'changeUser', $this, 'onChangeInfo');
 
-		$groupManager = $this->getContainer()->getServer()->getGroupManager();
+		$groupManager = $context->getServerContainer()->getGroupManager();
 		$groupManager->listen('\OC\Group', 'postRemoveUser',  [$this, 'removeUserFromGroup']);
 		$groupManager->listen('\OC\Group', 'postAddUser',  [$this, 'addUserToGroup']);
 
diff --git a/apps/twofactor_backupcodes/appinfo/app.php b/apps/twofactor_backupcodes/appinfo/app.php
deleted file mode 100644
index 2ca0108443d4d2b83b8bf9809fc7fd78e941446a..0000000000000000000000000000000000000000
--- a/apps/twofactor_backupcodes/appinfo/app.php
+++ /dev/null
@@ -1,29 +0,0 @@
-<?php
-/**
- * @copyright Copyright (c) 2017 Joas Schilling <coding@schilljs.com>
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Joas Schilling <coding@schilljs.com>
- * @author Robin Appelman <robin@icewind.nl>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
- */
-
-use OCA\TwoFactorBackupCodes\AppInfo\Application;
-
-$app = \OC::$server->query(Application::class);
-$app->register();
diff --git a/apps/twofactor_backupcodes/lib/AppInfo/Application.php b/apps/twofactor_backupcodes/lib/AppInfo/Application.php
index 4c5d852f9564849b682ee9421ee38e4e212b392a..1941cb545adb9c31ec53391704f2dbf767472472 100644
--- a/apps/twofactor_backupcodes/lib/AppInfo/Application.php
+++ b/apps/twofactor_backupcodes/lib/AppInfo/Application.php
@@ -1,4 +1,7 @@
 <?php
+
+declare(strict_types=1);
+
 /**
  * @copyright Copyright (c) 2017 Joas Schilling <coding@schilljs.com>
  *
@@ -34,45 +37,44 @@ use OCA\TwoFactorBackupCodes\Listener\ProviderEnabled;
 use OCA\TwoFactorBackupCodes\Listener\RegistryUpdater;
 use OCA\TwoFactorBackupCodes\Notifications\Notifier;
 use OCP\AppFramework\App;
+use OCP\AppFramework\Bootstrap\IBootContext;
+use OCP\AppFramework\Bootstrap\IBootstrap;
+use OCP\AppFramework\Bootstrap\IRegistrationContext;
 use OCP\Authentication\TwoFactorAuth\IRegistry;
-use OCP\EventDispatcher\IEventDispatcher;
 use OCP\Notification\IManager;
 use OCP\Util;
 
-class Application extends App {
+class Application extends App implements IBootstrap {
+	public const APP_ID = 'twofactor_backupcodes';
+
 	public function __construct() {
-		parent::__construct('twofactor_backupcodes');
+		parent::__construct(self::APP_ID);
 	}
 
-	/**
-	 * Register the different app parts
-	 */
-	public function register() {
-		$this->registerHooksAndEvents();
-		$this->registerNotification();
+	public function register(IRegistrationContext $context): void {
+		$this->registerHooksAndEvents($context);
 	}
 
-	/**
-	 * Register the hooks and events
-	 */
-	public function registerHooksAndEvents() {
+	public function boot(IBootContext $context): void {
 		Util::connectHook('OC_User', 'post_deleteUser', $this, 'deleteUser');
 
-		$container = $this->getContainer();
+		$this->registerNotification(
+			$context->getAppContainer()->query(IManager::class)
+		);
+	}
 
-		/** @var IEventDispatcher $eventDispatcher */
-		$eventDispatcher = $container->query(IEventDispatcher::class);
-		$eventDispatcher->addServiceListener(CodesGenerated::class, ActivityPublisher::class);
-		$eventDispatcher->addServiceListener(CodesGenerated::class, RegistryUpdater::class);
-		$eventDispatcher->addServiceListener(CodesGenerated::class, ClearNotifications::class);
-		$eventDispatcher->addServiceListener(IRegistry::EVENT_PROVIDER_ENABLED, ProviderEnabled::class);
-		$eventDispatcher->addServiceListener(IRegistry::EVENT_PROVIDER_DISABLED, ProviderDisabled::class);
+	/**
+	 * Register the hooks and events
+	 */
+	public function registerHooksAndEvents(IRegistrationContext $context) {
+		$context->registerEventListener(CodesGenerated::class, ActivityPublisher::class);
+		$context->registerEventListener(CodesGenerated::class, RegistryUpdater::class);
+		$context->registerEventListener(CodesGenerated::class, ClearNotifications::class);
+		$context->registerEventListener(IRegistry::EVENT_PROVIDER_ENABLED, ProviderEnabled::class);
+		$context->registerEventListener(IRegistry::EVENT_PROVIDER_DISABLED, ProviderDisabled::class);
 	}
 
-	public function registerNotification() {
-		$container = $this->getContainer();
-		/** @var IManager $manager */
-		$manager = $container->query(IManager::class);
+	private function registerNotification(IManager $manager) {
 		$manager->registerNotifierService(Notifier::class);
 	}
 
diff --git a/lib/base.php b/lib/base.php
index 3ac6d978e4e0c91f352c939468fd7c999e999c74..8109e47cdc07cd769159e6bf582c7c550639eb99 100644
--- a/lib/base.php
+++ b/lib/base.php
@@ -644,6 +644,10 @@ class OC {
 			OC\Log\ErrorHandler::register($debug);
 		}
 
+		/** @var \OC\AppFramework\Bootstrap\Coordinator $bootstrapCoordinator */
+		$bootstrapCoordinator = \OC::$server->query(\OC\AppFramework\Bootstrap\Coordinator::class);
+		$bootstrapCoordinator->runRegistration();
+
 		\OC::$server->getEventLogger()->start('init_session', 'Initialize session');
 		OC_App::loadApps(['session']);
 		if (!self::$CLI) {
@@ -735,8 +739,6 @@ class OC {
 		// Make sure that the application class is not loaded before the database is setup
 		if ($systemConfig->getValue("installed", false)) {
 			OC_App::loadApp('settings');
-			$settings = \OC::$server->query(\OCA\Settings\AppInfo\Application::class);
-			$settings->register();
 		}
 
 		//make sure temporary files are cleaned up
diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php
index 930a14df5d5c9aa105f615fa266f6ec08f52c944..09defdc5ae4e185b71976a1b02794dad9b87c32b 100644
--- a/lib/composer/composer/autoload_classmap.php
+++ b/lib/composer/composer/autoload_classmap.php
@@ -23,6 +23,9 @@ return array(
     'OCP\\AppFramework\\ApiController' => $baseDir . '/lib/public/AppFramework/ApiController.php',
     'OCP\\AppFramework\\App' => $baseDir . '/lib/public/AppFramework/App.php',
     'OCP\\AppFramework\\AuthPublicShareController' => $baseDir . '/lib/public/AppFramework/AuthPublicShareController.php',
+    'OCP\\AppFramework\\Bootstrap\\IBootContext' => $baseDir . '/lib/public/AppFramework/Bootstrap/IBootContext.php',
+    'OCP\\AppFramework\\Bootstrap\\IBootstrap' => $baseDir . '/lib/public/AppFramework/Bootstrap/IBootstrap.php',
+    'OCP\\AppFramework\\Bootstrap\\IRegistrationContext' => $baseDir . '/lib/public/AppFramework/Bootstrap/IRegistrationContext.php',
     'OCP\\AppFramework\\Controller' => $baseDir . '/lib/public/AppFramework/Controller.php',
     'OCP\\AppFramework\\Db\\DoesNotExistException' => $baseDir . '/lib/public/AppFramework/Db/DoesNotExistException.php',
     'OCP\\AppFramework\\Db\\Entity' => $baseDir . '/lib/public/AppFramework/Db/Entity.php',
@@ -538,6 +541,9 @@ return array(
     'OC\\AllConfig' => $baseDir . '/lib/private/AllConfig.php',
     'OC\\AppConfig' => $baseDir . '/lib/private/AppConfig.php',
     'OC\\AppFramework\\App' => $baseDir . '/lib/private/AppFramework/App.php',
+    'OC\\AppFramework\\Bootstrap\\BootContext' => $baseDir . '/lib/private/AppFramework/Bootstrap/BootContext.php',
+    'OC\\AppFramework\\Bootstrap\\Coordinator' => $baseDir . '/lib/private/AppFramework/Bootstrap/Coordinator.php',
+    'OC\\AppFramework\\Bootstrap\\RegistrationContext' => $baseDir . '/lib/private/AppFramework/Bootstrap/RegistrationContext.php',
     'OC\\AppFramework\\DependencyInjection\\DIContainer' => $baseDir . '/lib/private/AppFramework/DependencyInjection/DIContainer.php',
     'OC\\AppFramework\\Http' => $baseDir . '/lib/private/AppFramework/Http.php',
     'OC\\AppFramework\\Http\\Dispatcher' => $baseDir . '/lib/private/AppFramework/Http/Dispatcher.php',
diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php
index 9539dbc1a4bc088a25c0098ab9fc74e0b853f16e..ef34b902cf4800ed3962150e2f251309261b94fe 100644
--- a/lib/composer/composer/autoload_static.php
+++ b/lib/composer/composer/autoload_static.php
@@ -52,6 +52,9 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
         'OCP\\AppFramework\\ApiController' => __DIR__ . '/../../..' . '/lib/public/AppFramework/ApiController.php',
         'OCP\\AppFramework\\App' => __DIR__ . '/../../..' . '/lib/public/AppFramework/App.php',
         'OCP\\AppFramework\\AuthPublicShareController' => __DIR__ . '/../../..' . '/lib/public/AppFramework/AuthPublicShareController.php',
+        'OCP\\AppFramework\\Bootstrap\\IBootContext' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Bootstrap/IBootContext.php',
+        'OCP\\AppFramework\\Bootstrap\\IBootstrap' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Bootstrap/IBootstrap.php',
+        'OCP\\AppFramework\\Bootstrap\\IRegistrationContext' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Bootstrap/IRegistrationContext.php',
         'OCP\\AppFramework\\Controller' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Controller.php',
         'OCP\\AppFramework\\Db\\DoesNotExistException' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Db/DoesNotExistException.php',
         'OCP\\AppFramework\\Db\\Entity' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Db/Entity.php',
@@ -567,6 +570,9 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
         'OC\\AllConfig' => __DIR__ . '/../../..' . '/lib/private/AllConfig.php',
         'OC\\AppConfig' => __DIR__ . '/../../..' . '/lib/private/AppConfig.php',
         'OC\\AppFramework\\App' => __DIR__ . '/../../..' . '/lib/private/AppFramework/App.php',
+        'OC\\AppFramework\\Bootstrap\\BootContext' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Bootstrap/BootContext.php',
+        'OC\\AppFramework\\Bootstrap\\Coordinator' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Bootstrap/Coordinator.php',
+        'OC\\AppFramework\\Bootstrap\\RegistrationContext' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Bootstrap/RegistrationContext.php',
         'OC\\AppFramework\\DependencyInjection\\DIContainer' => __DIR__ . '/../../..' . '/lib/private/AppFramework/DependencyInjection/DIContainer.php',
         'OC\\AppFramework\\Http' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Http.php',
         'OC\\AppFramework\\Http\\Dispatcher' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Http/Dispatcher.php',
diff --git a/lib/private/AppFramework/Bootstrap/BootContext.php b/lib/private/AppFramework/Bootstrap/BootContext.php
new file mode 100644
index 0000000000000000000000000000000000000000..3831fe4e396e9a82e9b8621f158aa2103981f064
--- /dev/null
+++ b/lib/private/AppFramework/Bootstrap/BootContext.php
@@ -0,0 +1,48 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @author 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace OC\AppFramework\Bootstrap;
+
+use OCP\AppFramework\Bootstrap\IBootContext;
+use OCP\AppFramework\IAppContainer;
+use OCP\IServerContainer;
+
+class BootContext implements IBootContext {
+
+	/** @var IAppContainer */
+	private $appContainer;
+
+	public function __construct(IAppContainer $appContainer) {
+		$this->appContainer = $appContainer;
+	}
+
+	public function getAppContainer(): IAppContainer {
+		return $this->appContainer;
+	}
+
+	public function getServerContainer(): IServerContainer {
+		return $this->appContainer->getServer();
+	}
+}
diff --git a/lib/private/AppFramework/Bootstrap/Coordinator.php b/lib/private/AppFramework/Bootstrap/Coordinator.php
new file mode 100644
index 0000000000000000000000000000000000000000..e713ad7ce91c9c6458d012461100662ee9973c15
--- /dev/null
+++ b/lib/private/AppFramework/Bootstrap/Coordinator.php
@@ -0,0 +1,130 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @author 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace OC\AppFramework\Bootstrap;
+
+use OC_App;
+use OCP\AppFramework\App;
+use OCP\AppFramework\Bootstrap\IBootstrap;
+use OCP\AppFramework\QueryException;
+use OCP\EventDispatcher\IEventDispatcher;
+use OCP\ILogger;
+use OCP\IServerContainer;
+use function class_exists;
+use function class_implements;
+use function in_array;
+
+class Coordinator {
+
+	/** @var IServerContainer */
+	private $serverContainer;
+
+	/** @var IEventDispatcher */
+	private $eventDispatcher;
+
+	/** @var ILogger */
+	private $logger;
+
+	public function __construct(IServerContainer $container,
+								IEventDispatcher $eventListener,
+								ILogger $logger) {
+		$this->serverContainer = $container;
+		$this->eventDispatcher = $eventListener;
+		$this->logger = $logger;
+	}
+
+	public function runRegistration(): void {
+		$context = new RegistrationContext($this->logger);
+		$apps = [];
+		foreach (OC_App::getEnabledApps() as $appId) {
+			/*
+			 * First, we have to enable the app's autoloader
+			 *
+			 * @todo use $this->appManager->getAppPath($appId) here
+			 */
+			$path = OC_App::getAppPath($appId);
+			if ($path === false) {
+				// Ignore
+				continue;
+			}
+			OC_App::registerAutoloading($appId, $path);
+
+			/*
+			 * Next we check if there is an application class and it implements
+			 * the \OCP\AppFramework\Bootstrap\IBootstrap interface
+			 */
+			$appNameSpace = App::buildAppNamespace($appId);
+			$applicationClassName = $appNameSpace . '\\AppInfo\\Application';
+			if (class_exists($applicationClassName) && in_array(IBootstrap::class, class_implements($applicationClassName), true)) {
+				try {
+					/** @var IBootstrap|App $application */
+					$apps[$appId] = $application = $this->serverContainer->query($applicationClassName);
+					$application->register($context->for($appId));
+				} catch (QueryException $e) {
+					// Weird, but ok
+				}
+			}
+		}
+
+		/**
+		 * Now that all register methods have been called, we can delegate the registrations
+		 * to the actual services
+		 */
+		$context->delegateCapabilityRegistrations($apps);
+		$context->delegateEventListenerRegistrations($this->eventDispatcher);
+		$context->delegateContainerRegistrations($apps);
+		$context->delegateMiddlewareRegistrations($apps);
+	}
+
+	public function bootApp(string $appId): void {
+		$appNameSpace = App::buildAppNamespace($appId);
+		$applicationClassName = $appNameSpace . '\\AppInfo\\Application';
+		if (!class_exists($applicationClassName)) {
+			// Nothing to boot
+			return;
+		}
+
+		/*
+		 * Now it is time to fetch an instance of the App class. For classes
+		 * that implement \OCP\AppFramework\Bootstrap\IBootstrap this means
+		 * the instance was already created for register, but any other
+		 * (legacy) code will now do their magic via the constructor.
+		 */
+		try {
+			/** @var App $application */
+			$application = $this->serverContainer->query($applicationClassName);
+			if ($application instanceof IBootstrap) {
+				/** @var BootContext $context */
+				$context = new BootContext($application->getContainer());
+				$application->boot($context);
+			}
+		} catch (QueryException $e) {
+			$this->logger->logException($e, [
+				'message' => "Could not boot $appId" . $e->getMessage(),
+			]);
+			return;
+		}
+	}
+}
diff --git a/lib/private/AppFramework/Bootstrap/RegistrationContext.php b/lib/private/AppFramework/Bootstrap/RegistrationContext.php
new file mode 100644
index 0000000000000000000000000000000000000000..c7b2290e6b89e94425ec12111fafa314fde71cdd
--- /dev/null
+++ b/lib/private/AppFramework/Bootstrap/RegistrationContext.php
@@ -0,0 +1,295 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @author 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace OC\AppFramework\Bootstrap;
+
+use Closure;
+use OCP\AppFramework\App;
+use OCP\AppFramework\Bootstrap\IRegistrationContext;
+use OCP\EventDispatcher\IEventDispatcher;
+use OCP\ILogger;
+use Throwable;
+
+class RegistrationContext {
+
+	/** @var array[] */
+	private $capabilities = [];
+
+	/** @var array[] */
+	private $services = [];
+
+	/** @var array[] */
+	private $aliases = [];
+
+	/** @var array[] */
+	private $parameters = [];
+
+	/** @var array[] */
+	private $eventListeners = [];
+
+	/** @var array[] */
+	private $middlewares = [];
+
+	/** @var ILogger */
+	private $logger;
+
+	public function __construct(ILogger $logger) {
+		$this->logger = $logger;
+	}
+
+	public function for(string $appId): IRegistrationContext {
+		return new class($appId, $this) implements IRegistrationContext {
+			/** @var string */
+			private $appId;
+
+			/** @var RegistrationContext */
+			private $context;
+
+			public function __construct(string $appId, RegistrationContext $context) {
+				$this->appId = $appId;
+				$this->context = $context;
+			}
+
+			public function registerCapability(string $capability): void {
+				$this->context->registerCapability(
+					$this->appId,
+					$capability
+				);
+			}
+
+			public function registerService(string $name, callable $factory, bool $shared = true): void {
+				$this->context->registerService(
+					$this->appId,
+					$name,
+					$factory,
+					$shared
+				);
+			}
+
+			public function registerServiceAlias(string $alias, string $target): void {
+				$this->context->registerServiceAlias(
+					$this->appId,
+					$alias,
+					$target
+				);
+			}
+
+			public function registerParameter(string $name, $value): void {
+				$this->context->registerParameter(
+					$this->appId,
+					$name,
+					$value
+				);
+			}
+
+			public function registerEventListener(string $event, string $listener, int $priority = 0): void {
+				$this->context->registerEventListener(
+					$this->appId,
+					$event,
+					$listener,
+					$priority
+				);
+			}
+
+			public function registerMiddleware(string $class): void {
+				$this->context->registerMiddleware(
+					$this->appId,
+					$class
+				);
+			}
+		};
+	}
+
+	public function registerCapability(string $appId, string $capability): void {
+		$this->capabilities[] = [
+			'appId' => $appId,
+			'capability' => $capability
+		];
+	}
+
+	public function registerService(string $appId, string $name, callable $factory, bool $shared = true): void {
+		$this->services[] = [
+			"appId" => $appId,
+			"name" => $name,
+			"factory" => $factory,
+			"sharred" => $shared,
+		];
+	}
+
+	public function registerServiceAlias(string $appId, string $alias, string $target): void {
+		$this->aliases[] = [
+			"appId" => $appId,
+			"alias" => $alias,
+			"target" => $target,
+		];
+	}
+
+	public function registerParameter(string $appId, string $name, $value): void {
+		$this->parameters[] = [
+			"appId" => $appId,
+			"name" => $name,
+			"value" => $value,
+		];
+	}
+
+	public function registerEventListener(string $appId, string $event, string $listener, int $priority = 0): void {
+		$this->eventListeners[] = [
+			"appId" => $appId,
+			"event" => $event,
+			"listener" => $listener,
+			"priority" => $priority,
+		];
+	}
+
+	public function registerMiddleware(string $appId, string $class): void {
+		$this->middlewares[] = [
+			"appId" => $appId,
+			"class" => $class,
+		];
+	}
+
+	/**
+	 * @param App[] $apps
+	 */
+	public function delegateCapabilityRegistrations(array $apps): void {
+		foreach ($this->capabilities as $registration) {
+			try {
+				$apps[$registration['appId']]
+					->getContainer()
+					->registerCapability($registration['capability']);
+			} catch (Throwable $e) {
+				$appId = $registration['appId'];
+				$this->logger->logException($e, [
+					'message' => "Error during capability registration of $appId: " . $e->getMessage(),
+					'level' => ILogger::ERROR,
+				]);
+			}
+		}
+	}
+
+	public function delegateEventListenerRegistrations(IEventDispatcher $eventDispatcher): void {
+		foreach ($this->eventListeners as $registration) {
+			try {
+				if (isset($registration['priority'])) {
+					$eventDispatcher->addServiceListener(
+						$registration['event'],
+						$registration['listener'],
+						$registration['priority']
+					);
+				} else {
+					$eventDispatcher->addListener(
+						$registration['event'],
+						$registration['listener']
+					);
+				}
+			} catch (Throwable $e) {
+				$appId = $registration['appId'];
+				$this->logger->logException($e, [
+					'message' => "Error during event listener registration of $appId: " . $e->getMessage(),
+					'level' => ILogger::ERROR,
+				]);
+			}
+		}
+	}
+
+	/**
+	 * @param App[] $apps
+	 */
+	public function delegateContainerRegistrations(array $apps): void {
+		foreach ($this->services as $registration) {
+			try {
+				/**
+				 * Register the service and convert the callable into a \Closure if necessary
+				 */
+				$apps[$registration['appId']]
+					->getContainer()
+					->registerService(
+						$registration['name'],
+						Closure::fromCallable($registration['factory']),
+						$registration['shared'] ?? true
+					);
+			} catch (Throwable $e) {
+				$appId = $registration['appId'];
+				$this->logger->logException($e, [
+					'message' => "Error during service registration of $appId: " . $e->getMessage(),
+					'level' => ILogger::ERROR,
+				]);
+			}
+		}
+
+		foreach ($this->aliases as $registration) {
+			try {
+				$apps[$registration['appId']]
+					->getContainer()
+					->registerAlias(
+						$registration['alias'],
+						$registration['target']
+					);
+			} catch (Throwable $e) {
+				$appId = $registration['appId'];
+				$this->logger->logException($e, [
+					'message' => "Error during service alias registration of $appId: " . $e->getMessage(),
+					'level' => ILogger::ERROR,
+				]);
+			}
+		}
+
+		foreach ($this->parameters as $registration) {
+			try {
+				$apps[$registration['appId']]
+					->getContainer()
+					->registerParameter(
+						$registration['name'],
+						$registration['value']
+					);
+			} catch (Throwable $e) {
+				$appId = $registration['appId'];
+				$this->logger->logException($e, [
+					'message' => "Error during service alias registration of $appId: " . $e->getMessage(),
+					'level' => ILogger::ERROR,
+				]);
+			}
+		}
+	}
+
+	/**
+	 * @param App[] $apps
+	 */
+	public function delegateMiddlewareRegistrations(array $apps): void {
+		foreach ($this->middlewares as $middleware) {
+			try {
+				$apps[$middleware['appId']]
+					->getContainer()
+					->registerMiddleWare($middleware['class']);
+			} catch (Throwable $e) {
+				$appId = $middleware['appId'];
+				$this->logger->logException($e, [
+					'message' => "Error during capability registration of $appId: " . $e->getMessage(),
+					'level' => ILogger::ERROR,
+				]);
+			}
+		}
+	}
+}
diff --git a/lib/private/legacy/OC_App.php b/lib/private/legacy/OC_App.php
index dfeac41497fe569b18cb472dbdaf518cee7c82a3..a3c429c9ce4803ae2b9d824cedb71db838b70d1d 100644
--- a/lib/private/legacy/OC_App.php
+++ b/lib/private/legacy/OC_App.php
@@ -149,6 +149,9 @@ class OC_App {
 		// in case someone calls loadApp() directly
 		self::registerAutoloading($app, $appPath);
 
+		/** @var \OC\AppFramework\Bootstrap\Coordinator $coordinator */
+		$coordinator = \OC::$server->query(\OC\AppFramework\Bootstrap\Coordinator::class);
+		$coordinator->bootApp($app);
 		if (is_file($appPath . '/appinfo/app.php')) {
 			\OC::$server->getEventLogger()->start('load_app_' . $app, 'Load app: ' . $app);
 			try {
diff --git a/lib/public/AppFramework/Bootstrap/IBootContext.php b/lib/public/AppFramework/Bootstrap/IBootContext.php
new file mode 100644
index 0000000000000000000000000000000000000000..6ceec5d2fcbf14f250afa945f46ff63004211efa
--- /dev/null
+++ b/lib/public/AppFramework/Bootstrap/IBootContext.php
@@ -0,0 +1,55 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @author 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace OCP\AppFramework\Bootstrap;
+
+use OCP\AppFramework\IAppContainer;
+use OCP\IServerContainer;
+
+/**
+ * @since 20.0.0
+ */
+interface IBootContext {
+
+	/**
+	 * Get hold of the app's container
+	 *
+	 * Useful to register and query app-specific services
+	 *
+	 * @return IAppContainer
+	 * @since 20.0.0
+	 */
+	public function getAppContainer(): IAppContainer;
+
+	/**
+	 * Get hold of the server DI container
+	 *
+	 * Useful to register and query system-wide services
+	 *
+	 * @return IServerContainer
+	 * @since 20.0.0
+	 */
+	public function getServerContainer(): IServerContainer;
+}
diff --git a/apps/admin_audit/appinfo/app.php b/lib/public/AppFramework/Bootstrap/IBootstrap.php
similarity index 56%
rename from apps/admin_audit/appinfo/app.php
rename to lib/public/AppFramework/Bootstrap/IBootstrap.php
index aa44f6931d4f1196b9b9d03c90977b25ec10e7f3..581c7d6636ad63c821eef4e15096a860b19e7b3e 100644
--- a/apps/admin_audit/appinfo/app.php
+++ b/lib/public/AppFramework/Bootstrap/IBootstrap.php
@@ -3,12 +3,9 @@
 declare(strict_types=1);
 
 /**
- * @copyright Copyright (c) 2016 Bjoern Schiessle <bjoern@schiessle.org>
- * @copyright Copyright (c) 2017 Lukas Reschke <lukas@statuscode.ch>
+ * @copyright 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
  *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Joas Schilling <coding@schilljs.com>
- * @author Lukas Reschke <lukas@statuscode.ch>
+ * @author 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
  *
  * @license GNU AGPL version 3 or any later version
  *
@@ -23,9 +20,27 @@ declare(strict_types=1);
  * GNU Affero General Public License for more details.
  *
  * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace OCP\AppFramework\Bootstrap;
+
+/**
+ * @since 20.0.0
  */
+interface IBootstrap {
+
+	/**
+	 * @param IRegistrationContext $context
+	 *
+	 * @since 20.0.0
+	 */
+	public function register(IRegistrationContext $context): void;
 
-$app = \OC::$server->query(\OCA\AdminAudit\AppInfo\Application::class);
-$app->register();
+	/**
+	 * @param IBootContext $context
+	 *
+	 * @since 20.0.0
+	 */
+	public function boot(IBootContext $context): void;
+}
diff --git a/lib/public/AppFramework/Bootstrap/IRegistrationContext.php b/lib/public/AppFramework/Bootstrap/IRegistrationContext.php
new file mode 100644
index 0000000000000000000000000000000000000000..589b5def5a86c4a89945c172ca293ea97924cea4
--- /dev/null
+++ b/lib/public/AppFramework/Bootstrap/IRegistrationContext.php
@@ -0,0 +1,108 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @author 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace OCP\AppFramework\Bootstrap;
+
+use OCP\AppFramework\IAppContainer;
+use OCP\EventDispatcher\IEventDispatcher;
+use OCP\IContainer;
+
+/**
+ * The context object passed to IBootstrap::register
+ *
+ * @since 20.0.0
+ * @see IBootstrap::register()
+ */
+interface IRegistrationContext {
+
+	/**
+	 * @param string $capability
+	 * @see IAppContainer::registerCapability
+	 *
+	 * @since 20.0.0
+	 */
+	public function registerCapability(string $capability): void;
+
+	/**
+	 * Register a service
+	 *
+	 * @param string $name
+	 * @param callable $factory
+	 * @param bool $shared
+	 *
+	 * @return void
+	 * @see IContainer::registerService()
+	 *
+	 * @since 20.0.0
+	 */
+	public function registerService(string $name, callable $factory, bool $shared = true): void;
+
+	/**
+	 * @param string $alias
+	 * @param string $target
+	 *
+	 * @return void
+	 * @see IContainer::registerAlias()
+	 *
+	 * @since 20.0.0
+	 */
+	public function registerServiceAlias(string $alias, string $target): void;
+
+	/**
+	 * @param string $name
+	 * @param mixed $value
+	 *
+	 * @return void
+	 * @see IContainer::registerParameter()
+	 *
+	 * @since 20.0.0
+	 */
+	public function registerParameter(string $name, $value): void;
+
+	/**
+	 * Register a service listener
+	 *
+	 * This is equivalent to calling IEventDispatcher::addServiceListener
+	 *
+	 * @param string $event preferably the fully-qualified class name of the Event sub class to listen for
+	 * @param string $listener fully qualified class name (or ::class notation) of a \OCP\EventDispatcher\IEventListener that can be built by the DI container
+	 * @param int $priority
+	 *
+	 * @see IEventDispatcher::addServiceListener()
+	 *
+	 * @since 20.0.0
+	 */
+	public function registerEventListener(string $event, string $listener, int $priority = 0): void;
+
+	/**
+	 * @param string $class
+	 *
+	 * @return void
+	 * @see IAppContainer::registerMiddleWare()
+	 *
+	 * @since 20.0.0
+	 */
+	public function registerMiddleware(string $class): void;
+}
diff --git a/tests/lib/AppFramework/Bootstrap/BootContextTest.php b/tests/lib/AppFramework/Bootstrap/BootContextTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..219c6d7e7824bf9f6f5da7cd51ea91554ebac7c0
--- /dev/null
+++ b/tests/lib/AppFramework/Bootstrap/BootContextTest.php
@@ -0,0 +1,67 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @author 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace lib\AppFramework\Bootstrap;
+
+use OC\AppFramework\Bootstrap\BootContext;
+use OCP\AppFramework\IAppContainer;
+use OCP\IServerContainer;
+use PHPUnit\Framework\MockObject\MockObject;
+use Test\TestCase;
+
+class BootContextTest extends TestCase {
+
+	/** @var IAppContainer|MockObject */
+	private $appContainer;
+
+	/** @var BootContext */
+	private $context;
+
+	protected function setUp(): void {
+		parent::setUp();
+
+		$this->appContainer = $this->createMock(IAppContainer::class);
+
+		$this->context = new BootContext(
+			$this->appContainer
+		);
+	}
+
+	public function testGetAppContainer(): void {
+		$container = $this->context->getAppContainer();
+
+		$this->assertSame($this->appContainer, $container);
+	}
+
+	public function testGetServerContainer(): void {
+		$serverContainer = $this->createMock(IServerContainer::class);
+		$this->appContainer->method('getServer')
+			->willReturn($serverContainer);
+
+		$container = $this->context->getServerContainer();
+
+		$this->assertSame($serverContainer, $container);
+	}
+}
diff --git a/tests/lib/AppFramework/Bootstrap/CoordinatorTest.php b/tests/lib/AppFramework/Bootstrap/CoordinatorTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..114872ed54da8f28c271841ef910fa5dbd0ec61c
--- /dev/null
+++ b/tests/lib/AppFramework/Bootstrap/CoordinatorTest.php
@@ -0,0 +1,116 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @author 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace lib\AppFramework\Bootstrap;
+
+use OC\AppFramework\Bootstrap\Coordinator;
+use OCP\App\IAppManager;
+use OCP\AppFramework\App;
+use OCP\AppFramework\Bootstrap\IBootContext;
+use OCP\AppFramework\Bootstrap\IBootstrap;
+use OCP\AppFramework\Bootstrap\IRegistrationContext;
+use OCP\AppFramework\QueryException;
+use OCP\EventDispatcher\IEventDispatcher;
+use OCP\ILogger;
+use OCP\IServerContainer;
+use PHPUnit\Framework\MockObject\MockObject;
+use Test\TestCase;
+
+class CoordinatorTest extends TestCase {
+
+	/** @var IAppManager|MockObject */
+	private $appManager;
+
+	/** @var IServerContainer|MockObject */
+	private $serverContainer;
+
+	/** @var IEventDispatcher|MockObject */
+	private $eventDispatcher;
+
+	/** @var ILogger|MockObject */
+	private $logger;
+
+	/** @var Coordinator */
+	private $coordinator;
+
+	protected function setUp(): void {
+		parent::setUp();
+
+		$this->appManager = $this->createMock(IAppManager::class);
+		$this->serverContainer = $this->createMock(IServerContainer::class);
+		$this->eventDispatcher = $this->createMock(IEventDispatcher::class);
+		$this->logger = $this->createMock(ILogger::class);
+
+		$this->coordinator = new Coordinator(
+			$this->serverContainer,
+			$this->eventDispatcher,
+			$this->logger
+		);
+	}
+
+	public function testBootAppNotLoadable(): void {
+		$appId = 'settings';
+		$this->serverContainer->expects($this->once())
+			->method('query')
+			->with(\OCA\Settings\AppInfo\Application::class)
+			->willThrowException(new QueryException(""));
+		$this->logger->expects($this->once())
+			->method('logException');
+
+		$this->coordinator->bootApp($appId);
+	}
+
+	public function testBootAppNotBootable(): void {
+		$appId = 'settings';
+		$mockApp = $this->createMock(\OCA\Settings\AppInfo\Application::class);
+		$this->serverContainer->expects($this->once())
+			->method('query')
+			->with(\OCA\Settings\AppInfo\Application::class)
+			->willReturn($mockApp);
+
+		$this->coordinator->bootApp($appId);
+	}
+
+	public function testBootApp(): void {
+		$appId = 'settings';
+		$mockApp = new class extends App implements IBootstrap {
+			public function __construct() {
+				parent::__construct('test', []);
+			}
+
+			public function register(IRegistrationContext $context): void {
+			}
+
+			public function boot(IBootContext $context): void {
+			}
+		};
+		$this->serverContainer->expects($this->once())
+			->method('query')
+			->with(\OCA\Settings\AppInfo\Application::class)
+			->willReturn($mockApp);
+
+		$this->coordinator->bootApp($appId);
+	}
+}
diff --git a/tests/lib/AppFramework/Bootstrap/RegistrationContextTest.php b/tests/lib/AppFramework/Bootstrap/RegistrationContextTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..8441b6e983d8aa8a66d7b47647ab99608ffba6d6
--- /dev/null
+++ b/tests/lib/AppFramework/Bootstrap/RegistrationContextTest.php
@@ -0,0 +1,162 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @author 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace lib\AppFramework\Bootstrap;
+
+use OC\AppFramework\Bootstrap\RegistrationContext;
+use OCP\AppFramework\App;
+use OCP\AppFramework\IAppContainer;
+use OCP\EventDispatcher\IEventDispatcher;
+use OCP\ILogger;
+use PHPUnit\Framework\MockObject\MockObject;
+use Test\TestCase;
+
+class RegistrationContextTest extends TestCase {
+
+	/** @var ILogger|MockObject */
+	private $logger;
+
+	/** @var RegistrationContext */
+	private $context;
+
+	protected function setUp(): void {
+		parent::setUp();
+
+		$this->logger = $this->createMock(ILogger::class);
+
+		$this->context = new RegistrationContext(
+			$this->logger
+		);
+	}
+
+	public function testRegisterCapability(): void {
+		$app = $this->createMock(App::class);
+		$name = 'abc';
+		$container = $this->createMock(IAppContainer::class);
+		$app->method('getContainer')
+			->willReturn($container);
+		$container->expects($this->once())
+			->method('registerCapability')
+			->with($name);
+		$this->logger->expects($this->never())
+			->method('logException');
+
+		$this->context->for('myapp')->registerCapability($name);
+		$this->context->delegateCapabilityRegistrations([
+			'myapp' => $app,
+		]);
+	}
+
+	public function testRegisterEventListener(): void {
+		$event = 'abc';
+		$service = 'def';
+		$dispatcher = $this->createMock(IEventDispatcher::class);
+		$dispatcher->expects($this->once())
+			->method('addServiceListener')
+			->with($event, $service, 0);
+		$this->logger->expects($this->never())
+			->method('logException');
+
+		$this->context->for('myapp')->registerEventListener($event, $service);
+		$this->context->delegateEventListenerRegistrations($dispatcher);
+	}
+
+	public function testRegisterService(): void {
+		$app = $this->createMock(App::class);
+		$service = 'abc';
+		$factory = function () {
+			return 'def';
+		};
+		$container = $this->createMock(IAppContainer::class);
+		$app->method('getContainer')
+			->willReturn($container);
+		$container->expects($this->once())
+			->method('registerService')
+			->with($service, $factory, true);
+		$this->logger->expects($this->never())
+			->method('logException');
+
+		$this->context->for('myapp')->registerService($service, $factory);
+		$this->context->delegateContainerRegistrations([
+			'myapp' => $app,
+		]);
+	}
+
+	public function testRegisterServiceAlias(): void {
+		$app = $this->createMock(App::class);
+		$alias = 'abc';
+		$target = 'def';
+		$container = $this->createMock(IAppContainer::class);
+		$app->method('getContainer')
+			->willReturn($container);
+		$container->expects($this->once())
+			->method('registerAlias')
+			->with($alias, $target);
+		$this->logger->expects($this->never())
+			->method('logException');
+
+		$this->context->for('myapp')->registerServiceAlias($alias, $target);
+		$this->context->delegateContainerRegistrations([
+			'myapp' => $app,
+		]);
+	}
+
+	public function testRegisterParameter(): void {
+		$app = $this->createMock(App::class);
+		$name = 'abc';
+		$value = 'def';
+		$container = $this->createMock(IAppContainer::class);
+		$app->method('getContainer')
+			->willReturn($container);
+		$container->expects($this->once())
+			->method('registerParameter')
+			->with($name, $value);
+		$this->logger->expects($this->never())
+			->method('logException');
+
+		$this->context->for('myapp')->registerParameter($name, $value);
+		$this->context->delegateContainerRegistrations([
+			'myapp' => $app,
+		]);
+	}
+
+	public function testRegisterMiddleware(): void {
+		$app = $this->createMock(App::class);
+		$name = 'abc';
+		$container = $this->createMock(IAppContainer::class);
+		$app->method('getContainer')
+			->willReturn($container);
+		$container->expects($this->once())
+			->method('registerMiddleware')
+			->with($name);
+		$this->logger->expects($this->never())
+			->method('logException');
+
+		$this->context->for('myapp')->registerMiddleware($name);
+		$this->context->delegateMiddlewareRegistrations([
+			'myapp' => $app,
+		]);
+	}
+}