diff --git a/src/main/java/org/olat/core/commons/services/_spring/servicesCorecontext.xml b/src/main/java/org/olat/core/commons/services/_spring/servicesCorecontext.xml
index ab7ab2d7628be12e241b7c58665f3d4d15ef08e1..603257c3149e46053e7edf9e5706a87e3779b653 100644
--- a/src/main/java/org/olat/core/commons/services/_spring/servicesCorecontext.xml
+++ b/src/main/java/org/olat/core/commons/services/_spring/servicesCorecontext.xml
@@ -10,7 +10,7 @@
   
 	<context:component-scan base-package="org.olat.core.commons.services" />
 	
-  	<import resource="classpath:/org/olat/core/commons/services/jmx/_spring/jmxContext.xml"/>
+	<import resource="classpath:/org/olat/core/commons/services/jmx/_spring/jmxContext.xml"/>
 	<import resource="classpath:/org/olat/core/commons/services/scheduler/_spring/schedulerContext.xml"/>
 	<import resource="classpath:/org/olat/core/commons/services/taskexecutor/_spring/taskExecutorCorecontext.xml"/>
 	<import resource="classpath:/org/olat/core/commons/services/notifications/_spring/notificationsContext.xml"/>
@@ -47,13 +47,13 @@
 			</bean>
 		</property>
 		<property name="navigationKey" value="analytics" />
-		<property name="parentTreeNodeIdentifier" value="externalToolsParent" /> 
+		<property name="parentTreeNodeIdentifier" value="externalToolsParent" />
 		<property name="i18nActionKey" value="admin.menu.title"/>
 		<property name="i18nDescriptionKey" value="admin.menu.title.alt"/>
 		<property name="translationPackage" value="org.olat.core.commons.services.analytics.ui"/>
 		<property name="extensionPoints">
 			<list>	
-				<value>org.olat.admin.SystemAdminMainController</value>		
+				<value>org.olat.admin.SystemAdminMainController</value>
 			</list>
 		</property>
 	</bean>
@@ -67,13 +67,13 @@
 			</bean>
 		</property>
 		<property name="navigationKey" value="license" />
-		<property name="parentTreeNodeIdentifier" value="sysconfigParent" /> 
+		<property name="parentTreeNodeIdentifier" value="sysconfigParent" />
 		<property name="i18nActionKey" value="admin.menu.title"/>
 		<property name="i18nDescriptionKey" value="admin.menu.title.alt"/>
 		<property name="translationPackage" value="org.olat.core.commons.services.license.ui"/>
 		<property name="extensionPoints">
 			<list>	
-				<value>org.olat.admin.SystemAdminMainController</value>		
+				<value>org.olat.admin.SystemAdminMainController</value>
 			</list>
 		</property>
 	</bean>
@@ -90,10 +90,10 @@
 		<property name="translationPackage" value="org.olat.admin" />
 		<property name="i18nActionKey" value="menu.versions"/>
 		<property name="i18nDescriptionKey" value="menu.versions.alt"/>
-		<property name="parentTreeNodeIdentifier" value="sysconfigParent" /> 
+		<property name="parentTreeNodeIdentifier" value="sysconfigParent" />
 		<property name="extensionPoints">
 			<list>	
-				<value>org.olat.admin.SystemAdminMainController</value>		
+				<value>org.olat.admin.SystemAdminMainController</value>
 			</list>
 		</property>
 	</bean>
@@ -107,18 +107,18 @@
 			</bean>
 		</property>
 		<property name="navigationKey" value="pdfservice" />
-		<property name="parentTreeNodeIdentifier" value="externalToolsParent" /> 
+		<property name="parentTreeNodeIdentifier" value="externalToolsParent" />
 		<property name="i18nActionKey" value="admin.menu.title"/>
 		<property name="i18nDescriptionKey" value="admin.menu.title.alt"/>
 		<property name="translationPackage" value="org.olat.core.commons.services.pdf.ui"/>
 		<property name="extensionPoints">
 			<list>	
-				<value>org.olat.admin.SystemAdminMainController</value>		
+				<value>org.olat.admin.SystemAdminMainController</value>
 			</list>
 		</property>
 	</bean>
 	
-	<!-- Collabora admin. panel -->
+	<!-- Document editor admin. panel -->
 	<bean class="org.olat.core.extensions.action.GenericActionExtension" init-method="initExtensionPoints">
 		<property name="order" value="8256" />
 		<property name="actionController">	
@@ -133,20 +133,31 @@
 		<property name="parentTreeNodeIdentifier" value="externalToolsParent" /> 
 		<property name="extensionPoints">
 			<list>	
-				<value>org.olat.admin.SystemAdminMainController</value>		
+				<value>org.olat.admin.SystemAdminMainController</value>
 			</list>
 		</property>
 	</bean>
 	
+		<!-- Cleaning job for WOPI access -->
+	<bean id="wopiDeleteExpiredAccessJob" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
+		<property name="jobDetail">
+		<bean class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
+					<property name="jobClass" value="org.olat.core.commons.services.doceditor.wopi.manager.WopiDeleteExpiredAccessJob" />
+				</bean>
+			</property>
+			<property name="cronExpression" value="0 23 4 * * ?"/>
+			<property name="startDelay" value="45000" />
+	</bean>
+	
 	<!-- Cleaning job for CSP reports -->
 	<bean id="cspCleanupJob" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
 		<property name="jobDetail">
-	    		<bean class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
+				<bean class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
 					<property name="jobClass" value="org.olat.core.commons.services.csp.manager.CSPLogCleanup" />
 				</bean>
-	    	</property>
-	    	<property name="cronExpression" value="0 0 4 * * ?"/><!-- 2am, daily -->
-	    	<property name="startDelay" value="45000" />
+			</property>
+			<property name="cronExpression" value="0 0 4 * * ?"/><!-- 2am, daily -->
+			<property name="startDelay" value="45000" />
 	</bean>
 
 </beans>
\ No newline at end of file
diff --git a/src/main/java/org/olat/core/commons/services/doceditor/wopi/manager/AccessDAO.java b/src/main/java/org/olat/core/commons/services/doceditor/wopi/manager/AccessDAO.java
index 2e182747dd5cd4cc1534e978dd279d34c3a4e5c1..3dc9e7ba5863869ad2cd53c08fe6ed8b1cb87a72 100644
--- a/src/main/java/org/olat/core/commons/services/doceditor/wopi/manager/AccessDAO.java
+++ b/src/main/java/org/olat/core/commons/services/doceditor/wopi/manager/AccessDAO.java
@@ -129,6 +129,19 @@ class AccessDAO {
 				.setParameter("token", token)
 				.executeUpdate();
 	}
+	
+	void deleteExpired(Date before) {
+		if (before == null) return;
+
+		QueryBuilder sb = new QueryBuilder();
+		sb.append("delete from wopiaccess access");
+		sb.and().append("access.expiresAt < :before");
+		
+		dbInstance.getCurrentEntityManager()
+				.createQuery(sb.toString())
+				.setParameter("before", before)
+				.executeUpdate();
+	}
 
 	void deleteAll() {
 		dbInstance.getCurrentEntityManager()
diff --git a/src/main/java/org/olat/core/commons/services/doceditor/wopi/manager/WopiDeleteExpiredAccessJob.java b/src/main/java/org/olat/core/commons/services/doceditor/wopi/manager/WopiDeleteExpiredAccessJob.java
new file mode 100644
index 0000000000000000000000000000000000000000..3442efcab1004243f4b94c064110b9715b0e899a
--- /dev/null
+++ b/src/main/java/org/olat/core/commons/services/doceditor/wopi/manager/WopiDeleteExpiredAccessJob.java
@@ -0,0 +1,44 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.core.commons.services.doceditor.wopi.manager;
+
+import java.util.Date;
+
+import org.olat.core.CoreSpringFactory;
+import org.olat.core.commons.services.scheduler.JobWithDB;
+import org.quartz.JobExecutionContext;
+import org.quartz.JobExecutionException;
+
+/**
+ * 
+ * Initial date: 6 May 2019<br>
+ * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com
+ *
+ */
+public class WopiDeleteExpiredAccessJob extends JobWithDB {
+
+	@Override
+	public void executeWithDB(JobExecutionContext arg0) throws JobExecutionException {
+		AccessDAO accessDAO = CoreSpringFactory.getImpl(AccessDAO.class);
+		Date now = new Date();
+		accessDAO.deleteExpired(now);
+	}
+	
+}
diff --git a/src/test/java/org/olat/core/commons/services/doceditor/wopi/manager/AccessDAOTest.java b/src/test/java/org/olat/core/commons/services/doceditor/wopi/manager/AccessDAOTest.java
index 5f9ed5b77d2970765fde5f334021c2ead03af51b..be60bd20a13f16aaab1640cc8bb1c0e8adccf1a7 100644
--- a/src/test/java/org/olat/core/commons/services/doceditor/wopi/manager/AccessDAOTest.java
+++ b/src/test/java/org/olat/core/commons/services/doceditor/wopi/manager/AccessDAOTest.java
@@ -108,7 +108,7 @@ public class AccessDAOTest extends OlatTestCase {
 		Access createdAccess = createRandomAccess();
 		dbInstance.commitAndCloseSession();
 		
-		Access reloadedAccess = sut.loadAccess(createdAccess.getToken());
+		Access reloadedAccess = reload(createdAccess);
 		
 		assertThat(reloadedAccess).isEqualTo(createdAccess);
 	}
@@ -142,10 +142,39 @@ public class AccessDAOTest extends OlatTestCase {
 		sut.deleteAccess(createdAccess.getToken());
 		dbInstance.commitAndCloseSession();
 		
-		Access reloadedAccess = sut.loadAccess(createdAccess.getToken());
+		Access reloadedAccess = reload(createdAccess);
 		assertThat(reloadedAccess).isNull();
 	}
 	
+	@Test
+	public void shouldDeleteExpired() {
+		Identity identity = JunitTestHelper.createAndPersistIdentityAsRndUser("wopi");
+		VFSMetadata vfsMetadata = vfsMetadataDAO.createMetadata(random(), "relPath", "file.name", new Date(), 1000l, false, "", "file", null);
+		Date twoDaysAgo = Date.from(Instant.now().minus(Duration.ofDays(2)));;
+		Access expiredTwoDaysAgo = sut.createAccess(vfsMetadata, identity, random(), random(), true, true, true, twoDaysAgo);
+		Date oneDayAgo = Date.from(Instant.now().minus(Duration.ofDays(1)));;
+		Access expiredOneDayAgo = sut.createAccess(vfsMetadata, identity, random(), random(), true, true, true, oneDayAgo);
+		Date inOneDay = Date.from(Instant.now().plus(Duration.ofDays(1)));;
+		Access expiresInOneDay = sut.createAccess(vfsMetadata, identity, random(), random(), true, true, true, inOneDay);
+		Access noExpiration = sut.createAccess(vfsMetadata, identity, random(), random(), true, true, true, null);
+		dbInstance.commitAndCloseSession();
+		
+		Date now = new Date();
+		sut.deleteExpired(now);
+		dbInstance.commitAndCloseSession();
+		
+		SoftAssertions softly = new SoftAssertions();
+		softly.assertThat(reload(expiredOneDayAgo)).isNull();
+		softly.assertThat(reload(expiredTwoDaysAgo)).isNull();
+		softly.assertThat(reload(expiresInOneDay)).isNotNull();
+		softly.assertThat(reload(noExpiration)).isNotNull();
+		softly.assertAll();
+	}
+
+	private Access reload(Access access) {
+		return sut.loadAccess(access.getToken());
+	}
+	
 	private Access createRandomAccess() {
 		Identity identity = JunitTestHelper.createAndPersistIdentityAsRndUser("wopi");
 		VFSMetadata vfsMetadata = vfsMetadataDAO.createMetadata(random(), "relPath", "file.name", new Date(), 1000l, false, "", "file", null);