diff --git a/src/main/java/org/olat/ims/qti21/QTI21Service.java b/src/main/java/org/olat/ims/qti21/QTI21Service.java
index a397deb79769aee9e9f7b79e20d90b9d48b737c4..57135c1b9ca123af5054bc2062eb4ee1fe1356b3 100644
--- a/src/main/java/org/olat/ims/qti21/QTI21Service.java
+++ b/src/main/java/org/olat/ims/qti21/QTI21Service.java
@@ -55,8 +55,12 @@ public interface QTI21Service {
 	
 	public UserTestSession createTestSession(RepositoryEntry testEntry, RepositoryEntry courseEntry, String subIdent, Identity identity);
 	
+	public UserTestSession getResumableTestSession(RepositoryEntry testEntry, RepositoryEntry courseEntry, String subIdent, Identity identity);
+	
 	public UserTestSession updateTestSession(UserTestSession session);
 	
+	public TestSessionState loadTestSessionState(UserTestSession session);
+	
 	/**
 	 * Retrieve the sessions of a user.
 	 * 
diff --git a/src/main/java/org/olat/ims/qti21/manager/EventDAO.java b/src/main/java/org/olat/ims/qti21/manager/EventDAO.java
index 45d473469e3f8be6eee11e94f851e02966176f3d..a1e251fdf6d6f803a8513b398cf15170ed231e86 100644
--- a/src/main/java/org/olat/ims/qti21/manager/EventDAO.java
+++ b/src/main/java/org/olat/ims/qti21/manager/EventDAO.java
@@ -19,15 +19,13 @@
  */
 package org.olat.ims.qti21.manager;
 
+import org.olat.ims.qti21.UserTestSession;
 import org.olat.ims.qti21.model.CandidateItemEventType;
 import org.olat.ims.qti21.model.CandidateTestEventType;
 import org.olat.ims.qti21.model.jpa.CandidateEvent;
 import org.springframework.stereotype.Service;
-import org.w3c.dom.Document;
 
-import uk.ac.ed.ph.jqtiplus.state.ItemSessionState;
 import uk.ac.ed.ph.jqtiplus.state.TestPlanNodeKey;
-import uk.ac.ed.ph.jqtiplus.state.marshalling.ItemSessionStateXmlMarshaller;
 
 /**
  * 
@@ -38,10 +36,11 @@ import uk.ac.ed.ph.jqtiplus.state.marshalling.ItemSessionStateXmlMarshaller;
 @Service
 public class EventDAO {
 	
-	public CandidateEvent create(CandidateTestEventType textEventType,
+	public CandidateEvent create(UserTestSession candidateSession, CandidateTestEventType textEventType,
 			CandidateItemEventType itemEventType, TestPlanNodeKey itemKey) {
 		
 		CandidateEvent event = new CandidateEvent();
+		event.setCandidateSession(candidateSession);
 		event.setTestEventType(textEventType);
 		event.setItemEventType(itemEventType);
 		if (itemKey != null) {
@@ -49,27 +48,16 @@ public class EventDAO {
         }
 		return event;
 	}
-	
-	
-	public CandidateEvent create(CandidateItemEventType itemEventType, ItemSessionState itemSessionState) {
-		final CandidateEvent event = new CandidateEvent();
-        //event.setCandidateSession(candidateSession);
+
+	public CandidateEvent create(UserTestSession candidateSession, CandidateItemEventType itemEventType) {
+		
+		CandidateEvent event = new CandidateEvent();
+        event.setCandidateSession(candidateSession);
         event.setItemEventType(itemEventType);
         //event.setTimestamp(requestTimestampContext.getCurrentRequestTimestamp());
 
         /* Store event */
         //candidateEventDao.persist(event);
-
-        /* Save current ItemSessionState */
-        storeItemSessionState(event, itemSessionState);
-
-
         return event;
 	}
-	
-    public void storeItemSessionState(CandidateEvent candidateEvent, ItemSessionState itemSessionState) {
-        Document stateDocument = ItemSessionStateXmlMarshaller.marshal(itemSessionState);
-        //TODO storeStateDocument(candidateEvent, stateDocument);
-    }
-
-}
+}
\ No newline at end of file
diff --git a/src/main/java/org/olat/ims/qti21/manager/QTI21ServiceImpl.java b/src/main/java/org/olat/ims/qti21/manager/QTI21ServiceImpl.java
index 8e72ff532a2e4f6b26277df3c800d1ae7fe4ba9b..49a2c08a89b54bcf7103ff38199c7f047c3b7074 100644
--- a/src/main/java/org/olat/ims/qti21/manager/QTI21ServiceImpl.java
+++ b/src/main/java/org/olat/ims/qti21/manager/QTI21ServiceImpl.java
@@ -20,6 +20,7 @@
 package org.olat.ims.qti21.manager;
 
 import java.io.File;
+import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.OutputStream;
 import java.net.URI;
@@ -29,6 +30,12 @@ import java.util.ArrayList;
 import java.util.Date;
 import java.util.List;
 
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+
 import org.olat.basesecurity.IdentityRef;
 import org.olat.core.commons.persistence.DB;
 import org.olat.core.gui.components.form.flexible.impl.MultipartFileInfos;
@@ -46,11 +53,13 @@ import org.olat.ims.qti21.UserTestSession;
 import org.olat.ims.qti21.model.CandidateItemEventType;
 import org.olat.ims.qti21.model.CandidateTestEventType;
 import org.olat.ims.qti21.model.jpa.CandidateEvent;
+import org.olat.ims.qti21.ui.rendering.XmlUtilities;
 import org.olat.repository.RepositoryEntry;
 import org.olat.repository.RepositoryEntryRef;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Bean;
 import org.springframework.stereotype.Service;
+import org.w3c.dom.Document;
 
 import uk.ac.ed.ph.jqtiplus.JqtiExtensionManager;
 import uk.ac.ed.ph.jqtiplus.JqtiExtensionPackage;
@@ -69,10 +78,14 @@ import uk.ac.ed.ph.jqtiplus.serialization.QtiSerializer;
 import uk.ac.ed.ph.jqtiplus.state.ItemSessionState;
 import uk.ac.ed.ph.jqtiplus.state.TestPlanNodeKey;
 import uk.ac.ed.ph.jqtiplus.state.TestSessionState;
+import uk.ac.ed.ph.jqtiplus.state.marshalling.ItemSessionStateXmlMarshaller;
+import uk.ac.ed.ph.jqtiplus.state.marshalling.TestSessionStateXmlMarshaller;
 import uk.ac.ed.ph.jqtiplus.value.RecordValue;
 import uk.ac.ed.ph.jqtiplus.value.SingleValue;
 import uk.ac.ed.ph.jqtiplus.value.Value;
 import uk.ac.ed.ph.jqtiplus.xmlutils.locators.ResourceLocator;
+import uk.ac.ed.ph.jqtiplus.xmlutils.xslt.XsltSerializationOptions;
+import uk.ac.ed.ph.jqtiplus.xmlutils.xslt.XsltStylesheetManager;
 import uk.ac.ed.ph.qtiworks.mathassess.GlueValueBinder;
 import uk.ac.ed.ph.qtiworks.mathassess.MathAssessConstants;
 import uk.ac.ed.ph.qtiworks.mathassess.MathAssessExtensionPackage;
@@ -165,6 +178,39 @@ public class QTI21ServiceImpl implements QTI21Service {
 		return testSessionDao.createTestSession(testEntry, courseEntry, courseSubIdent, identity);
 	}
 
+	@Override
+	public UserTestSession getResumableTestSession(RepositoryEntry testEntry, RepositoryEntry courseEntry, String subIdent, Identity identity) {
+		UserTestSession session = testSessionDao.getLastTestSession(testEntry, courseEntry, subIdent, identity);
+		if(session == null || session.isExploded() || session.getTerminationTime() != null) {
+			session = null;
+		} else {
+			File sessionFile = getTestSessionStateFile(session);
+			if(sessionFile == null || !sessionFile.exists()) {
+				session = null;
+			}
+		}
+		return session;
+	}
+
+	@Override
+	public TestSessionState loadTestSessionState(UserTestSession candidateSession) {
+        Document document = loadStateDocument(candidateSession);
+        return document == null ? null: TestSessionStateXmlMarshaller.unmarshal(document.getDocumentElement());
+    }
+	
+    private Document loadStateDocument(UserTestSession candidateSession) {
+        File sessionFile = getTestSessionStateFile(candidateSession);
+        if(sessionFile.exists()) {
+	        DocumentBuilder documentBuilder = XmlUtilities.createNsAwareDocumentBuilder();
+	        try {
+	            return documentBuilder.parse(sessionFile);
+	        } catch (final Exception e) {
+	            throw new OLATRuntimeException("Could not parse serailized state XML. This is an internal error as we currently don't expose this data to clients", e);
+	        }
+        }
+        return null;
+    }
+
 	@Override
 	public UserTestSession updateTestSession(UserTestSession session) {
 		return testSessionDao.update(session);
@@ -250,8 +296,8 @@ public class QTI21ServiceImpl implements QTI21Service {
 	@Override
 	public CandidateEvent recordCandidateTestEvent(UserTestSession candidateSession, CandidateTestEventType textEventType,
 			CandidateItemEventType itemEventType, TestSessionState testSessionState, NotificationRecorder notificationRecorder) {
-		
 		CandidateEvent event = new CandidateEvent();
+		event.setCandidateSession(candidateSession);
 		event.setTestEventType(textEventType);
 		return recordCandidateTestEvent(candidateSession, textEventType, itemEventType, null, testSessionState, notificationRecorder);
 	}
@@ -259,22 +305,65 @@ public class QTI21ServiceImpl implements QTI21Service {
 	@Override
 	public CandidateEvent recordCandidateTestEvent(UserTestSession candidateSession, CandidateTestEventType textEventType,
 			CandidateItemEventType itemEventType, TestPlanNodeKey itemKey, TestSessionState testSessionState, NotificationRecorder notificationRecorder) {
-		return eventDao.create(textEventType, itemEventType, itemKey);
+		CandidateEvent event = eventDao.create(candidateSession, textEventType, itemEventType, itemKey);
+		storeTestSessionState(event, testSessionState);
+		return event;
+	}
+	
+	private void storeTestSessionState(CandidateEvent candidateEvent, TestSessionState testSessionState) {
+		Document stateDocument = TestSessionStateXmlMarshaller.marshal(testSessionState);
+		File sessionFile = getTestSessionStateFile(candidateEvent);
+		storeStateDocument(stateDocument, sessionFile);
+		System.out.println("Store state: " + sessionFile);
 	}
+
+    private File getTestSessionStateFile(CandidateEvent candidateEvent) {
+    	UserTestSession candidateSession = candidateEvent.getCandidateSession();
+    	return getTestSessionStateFile(candidateSession);
+    }
+    
+    private File getTestSessionStateFile(UserTestSession candidateSession) {
+    	File myStore = storage.getDirectory(candidateSession.getStorage());
+        return new File(myStore, "testSessionState.xml");
+    }
 	
+    @Override
+	public CandidateEvent recordCandidateItemEvent(UserTestSession candidateSession, CandidateItemEventType itemEventType,
+			ItemSessionState itemSessionState) {
+		return recordCandidateItemEvent(candidateSession, itemEventType, itemSessionState, null);
+	}
+		
 	@Override
     public CandidateEvent recordCandidateItemEvent(UserTestSession candidateSession, CandidateItemEventType itemEventType,
     		ItemSessionState itemSessionState, NotificationRecorder notificationRecorder) {
-    	return eventDao.create(itemEventType, itemSessionState);
+    	return eventDao.create(candidateSession, itemEventType);
     }
 	
-	
+    public void storeItemSessionState(CandidateEvent candidateEvent, ItemSessionState itemSessionState) {
+        Document stateDocument = ItemSessionStateXmlMarshaller.marshal(itemSessionState);
+        File sessionFile = getItemSessionStateFile(candidateEvent);
+        storeStateDocument(stateDocument, sessionFile);
+    }
+    
+    private File getItemSessionStateFile(CandidateEvent candidateEvent) {
+    	UserTestSession candidateSession = candidateEvent.getCandidateSession();
+    	File myStore = storage.getDirectory(candidateSession.getStorage());
+        return new File(myStore, "itemSessionState.xml");
+    }
+    
+	private void storeStateDocument(Document stateXml, File sessionFile) {
+        XsltSerializationOptions xsltSerializationOptions = new XsltSerializationOptions();
+        xsltSerializationOptions.setIndenting(true);
+        xsltSerializationOptions.setIncludingXMLDeclaration(false);
+        
+        Transformer serializer = XsltStylesheetManager.createSerializer(xsltSerializationOptions);
+        try(OutputStream resultStream = new FileOutputStream(sessionFile)) {
+            serializer.transform(new DOMSource(stateXml), new StreamResult(resultStream));
+        } catch (TransformerException | IOException e) {
+            throw new OLATRuntimeException("Unexpected Exception serializing state DOM", e);
+        }
+    }
 
-	@Override
-	public CandidateEvent recordCandidateItemEvent( UserTestSession candidateSession, CandidateItemEventType itemEventType,
-			ItemSessionState itemSessionState) {
-		return recordCandidateItemEvent(candidateSession, itemEventType, itemSessionState, null);
-	}
 
 	@Override
 	public UserTestSession finishItemSession(UserTestSession candidateSession, AssessmentResult assessmentResult, Date timestamp) {
diff --git a/src/main/java/org/olat/ims/qti21/manager/SessionDAO.java b/src/main/java/org/olat/ims/qti21/manager/SessionDAO.java
index 1de61e3171f63828d7b994e50c1175cf01c75708..13560f89ebb71043c62c1822ba76466d61ae52aa 100644
--- a/src/main/java/org/olat/ims/qti21/manager/SessionDAO.java
+++ b/src/main/java/org/olat/ims/qti21/manager/SessionDAO.java
@@ -22,6 +22,8 @@ package org.olat.ims.qti21.manager;
 import java.util.Date;
 import java.util.List;
 
+import javax.persistence.TypedQuery;
+
 import org.olat.basesecurity.IdentityRef;
 import org.olat.core.commons.persistence.DB;
 import org.olat.core.id.Identity;
@@ -65,6 +67,40 @@ public class SessionDAO {
 		return testSession;
 	}
 	
+	public UserTestSession getLastTestSession(RepositoryEntryRef testEntry,
+			RepositoryEntryRef courseEntry, String courseSubIdent, IdentityRef identity) {
+		
+		StringBuilder sb = new StringBuilder();
+		sb.append("select session from qtitestsession session ")
+		  .append("where session.testEntry.key=:testEntryKey and session.identity.key=:identityKey");
+		if(courseEntry != null) {
+			sb.append(" and session.courseEntry.key=:courseEntryKey");
+		} else {
+			sb.append(" and session.courseEntry.key is null");
+		}
+		
+		if(courseSubIdent != null) {
+			sb.append(" and session.courseSubIdent=:courseSubIdent");
+		} else {
+			sb.append(" and session.courseSubIdent is null");
+		}
+		sb.append(" order by session.creationDate desc");
+		
+		TypedQuery<UserTestSession> query = dbInstance.getCurrentEntityManager()
+				.createQuery(sb.toString(), UserTestSession.class)
+				.setParameter("testEntryKey", testEntry.getKey())
+				.setParameter("identityKey", identity.getKey());
+		if(courseEntry != null) {
+			query.setParameter("courseEntryKey", courseEntry.getKey());
+		}
+		if(courseSubIdent != null) {
+			query.setParameter("courseSubIdent", courseSubIdent);
+		}
+		
+		List<UserTestSession> lastSessions = query.setMaxResults(1).getResultList();
+		return lastSessions == null || lastSessions.isEmpty() ? null : lastSessions.get(0);
+	}
+	
 	public UserTestSession update(UserTestSession testSession) {
 		((UserTestSessionImpl)testSession).setLastModified(new Date());
 		return dbInstance.getCurrentEntityManager().merge(testSession);
diff --git a/src/main/java/org/olat/ims/qti21/model/jpa/CandidateEvent.java b/src/main/java/org/olat/ims/qti21/model/jpa/CandidateEvent.java
index 4420515701aa7ed9da8c5e51b603b0cd6ed3be70..f985f33f93f9e602e61ce7ede8439012784bf97b 100644
--- a/src/main/java/org/olat/ims/qti21/model/jpa/CandidateEvent.java
+++ b/src/main/java/org/olat/ims/qti21/model/jpa/CandidateEvent.java
@@ -21,6 +21,7 @@ package org.olat.ims.qti21.model.jpa;
 
 import java.util.Date;
 
+import org.olat.ims.qti21.UserTestSession;
 import org.olat.ims.qti21.model.CandidateItemEventType;
 import org.olat.ims.qti21.model.CandidateTestEventType;
 
@@ -32,16 +33,17 @@ import org.olat.ims.qti21.model.CandidateTestEventType;
  */
 public class CandidateEvent {
 	
-	
+	private Date timestamp;
+    private String testItemKey;
+
+	private UserTestSession candidateSession;
 	
 	private CandidateTestEventType testEventType;
-	
 	private CandidateItemEventType itemEventType;
-	
-    private String testItemKey;
-    
-    private Date timestamp;
- 
+
+    public CandidateEvent() {
+    	//
+    }
 
 	public Date getTimestamp() {
 		return timestamp;
@@ -74,9 +76,13 @@ public class CandidateEvent {
 	public void setTestItemKey(String testItemKey) {
 		this.testItemKey = testItemKey;
 	}
-	
-	
-	
-	
 
+	public UserTestSession getCandidateSession() {
+		return candidateSession;
+	}
+
+	public void setCandidateSession(UserTestSession candidateSession) {
+		this.candidateSession = candidateSession;
+	}
+	
 }
diff --git a/src/main/java/org/olat/ims/qti21/ui/AssessmentTestDisplayController.java b/src/main/java/org/olat/ims/qti21/ui/AssessmentTestDisplayController.java
index 81e12b7faf63baa76317c8379cbf1fe5f1e86ab6..43ecd8e5235743a0d4e4257c694bc1237a1be898 100644
--- a/src/main/java/org/olat/ims/qti21/ui/AssessmentTestDisplayController.java
+++ b/src/main/java/org/olat/ims/qti21/ui/AssessmentTestDisplayController.java
@@ -128,13 +128,22 @@ public class AssessmentTestDisplayController extends BasicController implements
 		
 		FileResourceManager frm = FileResourceManager.getInstance();
 		fUnzippedDirRoot = frm.unzipFileResource(entry.getOlatResource());
+		mapperUri = registerCacheableMapper(null, "QTI21Resources::" + entry.getKey(), new ResourcesMapper());
 		
 		currentRequestTimestamp = ureq.getRequestTimestamp();
 		
-		candidateSession = qtiService.createTestSession(entry, courseRe, courseSubIdent, getIdentity());
-		mapperUri = registerCacheableMapper(null, "QTI21Resources::" + entry.getKey(), new ResourcesMapper());
-		
-		testSessionController = enterSession(ureq);
+		UserTestSession lastSession = qtiService.getResumableTestSession(entry, courseRe, courseSubIdent, getIdentity());
+		if(lastSession == null) {
+			candidateSession = qtiService.createTestSession(entry, courseRe, courseSubIdent, getIdentity());
+			testSessionController = enterSession(ureq);
+		} else {
+			candidateSession = lastSession;
+			lastEvent = new CandidateEvent();
+			lastEvent.setCandidateSession(candidateSession);
+			lastEvent.setTestEventType(CandidateTestEventType.ITEM_EVENT);
+			
+			testSessionController = resumeSession();
+		}
 
 		/* Handle immediate end of test session */
         if (testSessionController.getTestSessionState().isEnded()) {
@@ -673,6 +682,37 @@ public class AssessmentTestDisplayController extends BasicController implements
 		return result;
 	}
 	
+	private TestSessionController resumeSession() {
+        final NotificationRecorder notificationRecorder = new NotificationRecorder(NotificationLevel.INFO);
+		return createTestSessionController(notificationRecorder);
+	}
+	
+	private TestSessionController createTestSessionController(NotificationRecorder notificationRecorder) {
+        final TestSessionState testSessionState = qtiService.loadTestSessionState(candidateSession);
+        return createTestSessionController(testSessionState, notificationRecorder);
+    }
+	
+    public TestSessionController createTestSessionController(TestSessionState testSessionState,  NotificationRecorder notificationRecorder) {
+        /* Try to resolve the underlying JQTI+ object */
+        final TestProcessingMap testProcessingMap = getTestProcessingMap();
+        if (testProcessingMap == null) {
+            return null;
+        }
+
+        /* Create config for TestSessionController */
+        final TestSessionControllerSettings testSessionControllerSettings = new TestSessionControllerSettings();
+        testSessionControllerSettings.setTemplateProcessingLimit(computeTemplateProcessingLimit());
+
+        /* Create controller and wire up notification recorder (if passed) */
+        final TestSessionController result = new TestSessionController(jqtiExtensionManager,
+                testSessionControllerSettings, testProcessingMap, testSessionState);
+        if (notificationRecorder!=null) {
+            result.addNotificationListener(notificationRecorder);
+        }
+
+        return result;
+    }
+	
 	private AssessmentResult computeAndRecordTestAssessmentResult(UserTestSession candidateSession,
 			TestSessionController testSessionController, boolean submit) {
 		AssessmentResult assessmentResult = computeTestAssessmentResult(candidateSession, testSessionController);