diff --git a/datastore_server/sourceTest/java/ch/ethz/sis/openbis/generic/dss/systemtest/api/v3/CreateExperimentsImportTest.java b/datastore_server/sourceTest/java/ch/ethz/sis/openbis/generic/dss/systemtest/api/v3/CreateExperimentsImportTest.java
index 8c5d208d9315b38a73e7ae4070d706f722305d35..546641eb42349436365226e481cd0c853a134c02 100644
--- a/datastore_server/sourceTest/java/ch/ethz/sis/openbis/generic/dss/systemtest/api/v3/CreateExperimentsImportTest.java
+++ b/datastore_server/sourceTest/java/ch/ethz/sis/openbis/generic/dss/systemtest/api/v3/CreateExperimentsImportTest.java
@@ -46,6 +46,7 @@ public class CreateExperimentsImportTest extends ObjectsImportTest
             ImportFile file = new ImportFile("identifier", "DESCRIPTION");
             file.addLine(experimentIdentifier.getIdentifier(), "imported description");
             uploadFiles(sessionToken, TEST_UPLOAD_KEY, file.toString());
+            assertUploadedFiles(sessionToken, file.toString());
 
             Experiment experiment = getObject(sessionToken, experimentIdentifier);
             assertNull(experiment);
@@ -75,6 +76,9 @@ public class CreateExperimentsImportTest extends ObjectsImportTest
                 assertEquals("1 experiment(s) found and registered.", message);
                 assertNoEmails(timestamp);
             }
+
+            assertUploadedFiles(sessionToken);
+
         } finally
         {
             ExperimentDeletionOptions options = new ExperimentDeletionOptions();
diff --git a/datastore_server/sourceTest/java/ch/ethz/sis/openbis/generic/dss/systemtest/api/v3/CreateMaterialsImportTest.java b/datastore_server/sourceTest/java/ch/ethz/sis/openbis/generic/dss/systemtest/api/v3/CreateMaterialsImportTest.java
index c507327c7ba2d5ee66cb616ce2d6995a4adb54e0..4c842d79324e13a949f1b8506aa00ff306a4ae72 100644
--- a/datastore_server/sourceTest/java/ch/ethz/sis/openbis/generic/dss/systemtest/api/v3/CreateMaterialsImportTest.java
+++ b/datastore_server/sourceTest/java/ch/ethz/sis/openbis/generic/dss/systemtest/api/v3/CreateMaterialsImportTest.java
@@ -45,6 +45,7 @@ public class CreateMaterialsImportTest extends ObjectsImportTest
             ImportFile file = new ImportFile("code", "DESCRIPTION");
             file.addLine(materialPermId.getCode(), "imported description");
             uploadFiles(sessionToken, TEST_UPLOAD_KEY, file.toString());
+            assertUploadedFiles(sessionToken, file.toString());
 
             Material material = getObject(sessionToken, materialPermId);
             assertNull(material);
@@ -75,6 +76,9 @@ public class CreateMaterialsImportTest extends ObjectsImportTest
                 assertEquals("Registration/update of 1 material(s) is complete.", message);
                 assertNoEmails(timestamp);
             }
+
+            assertUploadedFiles(sessionToken);
+
         } finally
         {
             MaterialDeletionOptions options = new MaterialDeletionOptions();
diff --git a/datastore_server/sourceTest/java/ch/ethz/sis/openbis/generic/dss/systemtest/api/v3/CreateSamplesImportTest.java b/datastore_server/sourceTest/java/ch/ethz/sis/openbis/generic/dss/systemtest/api/v3/CreateSamplesImportTest.java
index 83f7a31f810f3ed06dbb1bc185b63938514e760b..c4f23a444ec89d79d23d61c2f577d5ad52ef0696 100644
--- a/datastore_server/sourceTest/java/ch/ethz/sis/openbis/generic/dss/systemtest/api/v3/CreateSamplesImportTest.java
+++ b/datastore_server/sourceTest/java/ch/ethz/sis/openbis/generic/dss/systemtest/api/v3/CreateSamplesImportTest.java
@@ -46,6 +46,7 @@ public class CreateSamplesImportTest extends ObjectsImportTest
             ImportFile file = new ImportFile("identifier", "COMMENT");
             file.addLine(sampleIdentifier.getIdentifier(), "imported comment");
             uploadFiles(sessionToken, TEST_UPLOAD_KEY, file.toString());
+            assertUploadedFiles(sessionToken, file.toString());
 
             Sample sample = getObject(sessionToken, sampleIdentifier);
             assertNull(sample);
@@ -76,6 +77,9 @@ public class CreateSamplesImportTest extends ObjectsImportTest
                 assertEquals("Registration of 1 sample(s) is complete.", message);
                 assertNoEmails(timestamp);
             }
+
+            assertUploadedFiles(sessionToken);
+
         } finally
         {
             SampleDeletionOptions options = new SampleDeletionOptions();
diff --git a/datastore_server/sourceTest/java/ch/ethz/sis/openbis/generic/dss/systemtest/api/v3/CustomImportTest.java b/datastore_server/sourceTest/java/ch/ethz/sis/openbis/generic/dss/systemtest/api/v3/CustomImportTest.java
index ce545c8b938569d0ae08b3810deda520d9bea72e..165a7af966d64fde122f84897ddb8d97302b8206 100644
--- a/datastore_server/sourceTest/java/ch/ethz/sis/openbis/generic/dss/systemtest/api/v3/CustomImportTest.java
+++ b/datastore_server/sourceTest/java/ch/ethz/sis/openbis/generic/dss/systemtest/api/v3/CustomImportTest.java
@@ -51,6 +51,7 @@ public class CustomImportTest extends ObjectsImportTest
             multiPart.close();
 
             uploadFiles(sessionToken, TEST_UPLOAD_KEY, multiPart);
+            assertUploadedFiles(sessionToken, "test-file-content");
 
             DataSet dataSet = getObject(sessionToken, dataSetPermId);
             assertNull(dataSet);
@@ -80,6 +81,9 @@ public class CustomImportTest extends ObjectsImportTest
                 assertEquals("Import successfully completed.", message);
                 assertNoEmails(timestamp);
             }
+
+            assertUploadedFiles(sessionToken);
+
         } finally
         {
             DataSetDeletionOptions options = new DataSetDeletionOptions();
diff --git a/datastore_server/sourceTest/java/ch/ethz/sis/openbis/generic/dss/systemtest/api/v3/GeneralImportTest.java b/datastore_server/sourceTest/java/ch/ethz/sis/openbis/generic/dss/systemtest/api/v3/GeneralImportTest.java
index 8fa63d6701d77a658909327040888fdbc5ac23e0..ac838263e91c24f98e2329e7db079634bc02a69f 100644
--- a/datastore_server/sourceTest/java/ch/ethz/sis/openbis/generic/dss/systemtest/api/v3/GeneralImportTest.java
+++ b/datastore_server/sourceTest/java/ch/ethz/sis/openbis/generic/dss/systemtest/api/v3/GeneralImportTest.java
@@ -66,6 +66,7 @@ public class GeneralImportTest extends ObjectsImportTest
             multiPart.close();
 
             uploadFiles(sessionToken, TEST_UPLOAD_KEY, multiPart);
+            assertUploadedFiles(sessionToken, FileUtils.readFileToString(materialsFile));
 
             Map<String, Object> parameters = new HashMap<String, Object>();
             parameters.put(PARAM_UPLOAD_KEY, TEST_UPLOAD_KEY);
@@ -95,6 +96,9 @@ public class GeneralImportTest extends ObjectsImportTest
                 assertEquals("Registration/update of 2 material(s) is complete.\nRegistration of 0 sample(s) is complete.", message);
                 assertNoEmails(timestamp);
             }
+
+            assertUploadedFiles(sessionToken);
+
         } finally
         {
             deleteMaterials(sessionToken, materialPermId1, materialPermId2);
diff --git a/datastore_server/sourceTest/java/ch/ethz/sis/openbis/generic/dss/systemtest/api/v3/ObjectsImportTest.java b/datastore_server/sourceTest/java/ch/ethz/sis/openbis/generic/dss/systemtest/api/v3/ObjectsImportTest.java
index b41d7e9b67760a962e23299bd4b5fa35e374b9dd..97808fd9202e1384d5b135864bd8ab5a2968892e 100644
--- a/datastore_server/sourceTest/java/ch/ethz/sis/openbis/generic/dss/systemtest/api/v3/ObjectsImportTest.java
+++ b/datastore_server/sourceTest/java/ch/ethz/sis/openbis/generic/dss/systemtest/api/v3/ObjectsImportTest.java
@@ -16,6 +16,7 @@
 
 package ch.ethz.sis.openbis.generic.dss.systemtest.api.v3;
 
+import java.io.File;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -23,6 +24,7 @@ import java.util.Map;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeoutException;
 
+import org.apache.commons.io.FileUtils;
 import org.eclipse.jetty.client.HttpClient;
 import org.eclipse.jetty.client.api.ContentProvider;
 import org.eclipse.jetty.client.api.ContentResponse;
@@ -85,7 +87,7 @@ public class ObjectsImportTest extends AbstractFileTest
     protected static final String PARAM_UPDATE_EXISTING = "updateExisting";
 
     protected static final String PARAM_IGNORE_UNREGISTERED = "ignoreUnregistered";
-    
+
     protected static final String PARAM_CUSTOM_IMPORT_CODE = "customImportCode";
 
     protected IApplicationServerApi as;
@@ -129,6 +131,29 @@ public class ObjectsImportTest extends AbstractFileTest
         return uploadFiles(sessionToken, uploadSessionKey, multiPart);
     }
 
+    protected void assertUploadedFiles(String sessionToken, String... fileContents) throws Exception
+    {
+        File sessionWorkspaceRootDir = new File("targets/sessionWorkspace");
+        File sessionWorkspace = new File(sessionWorkspaceRootDir, sessionToken);
+        File[] files = sessionWorkspace.listFiles();
+
+        List<String> expectedContents = new ArrayList<String>(Arrays.asList(fileContents));
+        List<String> actualContents = new ArrayList<String>();
+
+        if (files != null)
+        {
+            for (File file : files)
+            {
+                actualContents.add(FileUtils.readFileToString(file));
+            }
+        }
+
+        expectedContents.sort(String.CASE_INSENSITIVE_ORDER);
+        actualContents.sort(String.CASE_INSENSITIVE_ORDER);
+
+        assertEquals(expectedContents, actualContents);
+    }
+
     protected String executeImport(String sessionToken, String operation, Map<String, Object> parameters)
     {
         CustomASServiceCode serviceId = new CustomASServiceCode("import-test");
diff --git a/datastore_server/sourceTest/java/ch/ethz/sis/openbis/generic/dss/systemtest/api/v3/UpdateDataSetsImportTest.java b/datastore_server/sourceTest/java/ch/ethz/sis/openbis/generic/dss/systemtest/api/v3/UpdateDataSetsImportTest.java
index a18bb9e0d9a33481bfe57cdab1ec9b3b05c2f3e2..5c86b06f0f6478a81e9b1143e7fc46a37b3da2c2 100644
--- a/datastore_server/sourceTest/java/ch/ethz/sis/openbis/generic/dss/systemtest/api/v3/UpdateDataSetsImportTest.java
+++ b/datastore_server/sourceTest/java/ch/ethz/sis/openbis/generic/dss/systemtest/api/v3/UpdateDataSetsImportTest.java
@@ -78,6 +78,7 @@ public class UpdateDataSetsImportTest extends ObjectsImportTest
             ImportFile file = new ImportFile("code", "COMMENT");
             file.addLine(dataSetPermId.getPermId(), "imported comment");
             uploadFiles(sessionToken, TEST_UPLOAD_KEY, file.toString());
+            assertUploadedFiles(sessionToken, file.toString());
 
             Map<String, Object> parameters = new HashMap<String, Object>();
             parameters.put(PARAM_UPLOAD_KEY, TEST_UPLOAD_KEY);
@@ -104,6 +105,9 @@ public class UpdateDataSetsImportTest extends ObjectsImportTest
                 assertEquals("1 data set(s) found and registered.", message);
                 assertNoEmails(timestamp);
             }
+
+            assertUploadedFiles(sessionToken);
+
         } finally
         {
             DataSetDeletionOptions options = new DataSetDeletionOptions();
diff --git a/datastore_server/sourceTest/java/ch/ethz/sis/openbis/generic/dss/systemtest/api/v3/UpdateExperimentsImportTest.java b/datastore_server/sourceTest/java/ch/ethz/sis/openbis/generic/dss/systemtest/api/v3/UpdateExperimentsImportTest.java
index 094fb142295cb166a518c6c46e84675cc41e9b2f..c907b907b6c980fc6a73b6c076a0a48883705b0e 100644
--- a/datastore_server/sourceTest/java/ch/ethz/sis/openbis/generic/dss/systemtest/api/v3/UpdateExperimentsImportTest.java
+++ b/datastore_server/sourceTest/java/ch/ethz/sis/openbis/generic/dss/systemtest/api/v3/UpdateExperimentsImportTest.java
@@ -63,6 +63,7 @@ public class UpdateExperimentsImportTest extends ObjectsImportTest
             ImportFile file = new ImportFile("identifier", "DESCRIPTION");
             file.addLine(experimentIdentifier.getIdentifier(), "imported description");
             uploadFiles(sessionToken, TEST_UPLOAD_KEY, file.toString());
+            assertUploadedFiles(sessionToken, file.toString());
 
             Map<String, Object> parameters = new HashMap<String, Object>();
             parameters.put(PARAM_UPLOAD_KEY, TEST_UPLOAD_KEY);
@@ -89,6 +90,9 @@ public class UpdateExperimentsImportTest extends ObjectsImportTest
                 assertEquals("Update of 1 experiment(s) is complete.", message);
                 assertNoEmails(timestamp);
             }
+
+            assertUploadedFiles(sessionToken);
+
         } finally
         {
             ExperimentDeletionOptions options = new ExperimentDeletionOptions();
diff --git a/datastore_server/sourceTest/java/ch/ethz/sis/openbis/generic/dss/systemtest/api/v3/UpdateMaterialsImportTest.java b/datastore_server/sourceTest/java/ch/ethz/sis/openbis/generic/dss/systemtest/api/v3/UpdateMaterialsImportTest.java
index d3a8e817b86e00a179ef5b34b786fd0f4906ae87..07f9e790eab3a082cc926c487c954cc433513928 100644
--- a/datastore_server/sourceTest/java/ch/ethz/sis/openbis/generic/dss/systemtest/api/v3/UpdateMaterialsImportTest.java
+++ b/datastore_server/sourceTest/java/ch/ethz/sis/openbis/generic/dss/systemtest/api/v3/UpdateMaterialsImportTest.java
@@ -60,6 +60,7 @@ public class UpdateMaterialsImportTest extends ObjectsImportTest
             ImportFile file = new ImportFile("code", "DESCRIPTION");
             file.addLine(materialPermId.getCode(), "imported description");
             uploadFiles(sessionToken, TEST_UPLOAD_KEY, file.toString());
+            assertUploadedFiles(sessionToken, file.toString());
 
             Map<String, Object> parameters = new HashMap<String, Object>();
             parameters.put(PARAM_UPLOAD_KEY, TEST_UPLOAD_KEY);
@@ -87,6 +88,9 @@ public class UpdateMaterialsImportTest extends ObjectsImportTest
                 assertEquals("1 material(s) updated.", message);
                 assertNoEmails(timestamp);
             }
+
+            assertUploadedFiles(sessionToken);
+
         } finally
         {
             MaterialDeletionOptions options = new MaterialDeletionOptions();
diff --git a/datastore_server/sourceTest/java/ch/ethz/sis/openbis/generic/dss/systemtest/api/v3/UpdateSamplesImportTest.java b/datastore_server/sourceTest/java/ch/ethz/sis/openbis/generic/dss/systemtest/api/v3/UpdateSamplesImportTest.java
index 01effa654c52b8463219e950b899f07bffea1540..1c873ede7468a7a63e20b14b4c1506507a2aa7d3 100644
--- a/datastore_server/sourceTest/java/ch/ethz/sis/openbis/generic/dss/systemtest/api/v3/UpdateSamplesImportTest.java
+++ b/datastore_server/sourceTest/java/ch/ethz/sis/openbis/generic/dss/systemtest/api/v3/UpdateSamplesImportTest.java
@@ -63,6 +63,7 @@ public class UpdateSamplesImportTest extends ObjectsImportTest
             ImportFile file = new ImportFile("identifier", "COMMENT");
             file.addLine(sampleIdentifier.getIdentifier(), "imported comment");
             uploadFiles(sessionToken, TEST_UPLOAD_KEY, file.toString());
+            assertUploadedFiles(sessionToken, file.toString());
 
             Map<String, Object> parameters = new HashMap<String, Object>();
             parameters.put(PARAM_UPLOAD_KEY, TEST_UPLOAD_KEY);
@@ -90,6 +91,9 @@ public class UpdateSamplesImportTest extends ObjectsImportTest
                 assertEquals("Update of 1 sample(s) is complete.", message);
                 assertNoEmails(timestamp);
             }
+
+            assertUploadedFiles(sessionToken);
+
         } finally
         {
             SampleDeletionOptions options = new SampleDeletionOptions();
diff --git a/datastore_server/sourceTest/java/ch/ethz/sis/openbis/generic/dss/systemtest/api/v3/UploadServletTest.java b/datastore_server/sourceTest/java/ch/ethz/sis/openbis/generic/dss/systemtest/api/v3/UploadServletTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..d81b844f0183ee8645d7bf67ce2a3b040b989464
--- /dev/null
+++ b/datastore_server/sourceTest/java/ch/ethz/sis/openbis/generic/dss/systemtest/api/v3/UploadServletTest.java
@@ -0,0 +1,475 @@
+/*
+ * Copyright 2018 ETH Zuerich, CISD
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.ethz.sis.openbis.generic.dss.systemtest.api.v3;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.apache.commons.io.FileUtils;
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.util.MultiPartContentProvider;
+import org.eclipse.jetty.client.util.StringContentProvider;
+import org.eclipse.jetty.http.HttpMethod;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import ch.ethz.sis.openbis.generic.asapi.v3.IApplicationServerApi;
+import ch.systemsx.cisd.common.http.JettyHttpClientFactory;
+import ch.systemsx.cisd.openbis.datastoreserver.systemtests.SystemTestCase;
+import ch.systemsx.cisd.openbis.generic.client.web.server.UploadedFilesBean;
+import ch.systemsx.cisd.openbis.generic.shared.util.TestInstanceHostUtils;
+
+/**
+ * @author pkupczyk
+ */
+public class UploadServletTest extends SystemTestCase
+{
+
+    private static final String SERVICE_URL = TestInstanceHostUtils.getOpenBISUrl() + "/openbis/upload";
+
+    private static final String PARAM_SESSION_ID = "sessionID";
+
+    private static final String PARAM_SESSION_KEY_PREFIX = "sessionKey_";
+
+    private static final String PARAM_SESSION_KEYS_NUMBER = "sessionKeysNumber";
+
+    private static final String USER = "test";
+
+    private static final String PASSWORD = "password";
+
+    private static final String FALSE_TRUE_PROVIDER = "false-true-provider";
+
+    private IApplicationServerApi as;
+
+    @BeforeClass
+    private void beforeClass() throws Exception
+    {
+        as = applicationContext.getBean(IApplicationServerApi.class);
+    }
+
+    @BeforeMethod
+    private void beforeMethod()
+    {
+        JettyHttpClientFactory.getHttpClient().getCookieStore().removeAll();
+    }
+
+    @Test(dataProvider = FALSE_TRUE_PROVIDER)
+    public void testUploadSingleFile(boolean withSessionToken) throws Exception
+    {
+        String sessionToken = as.login(USER, PASSWORD);
+
+        if (withSessionToken)
+        {
+            cleanSessionWorkspace(sessionToken);
+        } else
+        {
+            initHttpSession(sessionToken);
+            cleanOSTempFolder();
+        }
+
+        MultiPartContentProvider multipart = new MultiPartContentProvider();
+        multipart.addFilePart("testFieldName", "testFileName", new StringContentProvider("testContent"), null);
+        multipart.close();
+
+        HttpClient client = JettyHttpClientFactory.getHttpClient();
+        Request request = client.newRequest(SERVICE_URL).method(HttpMethod.POST);
+        if (withSessionToken)
+        {
+            request.param(PARAM_SESSION_ID, sessionToken);
+        }
+        request.param(PARAM_SESSION_KEYS_NUMBER, "1");
+        request.param(PARAM_SESSION_KEY_PREFIX + "0", "testFieldName");
+        request.content(multipart);
+
+        request.send();
+
+        if (withSessionToken)
+        {
+            assertSessionWorkspaceFiles(sessionToken, "testContent");
+        } else
+        {
+            assertOSTempFolderFiles("testContent");
+        }
+    }
+
+    @Test(dataProvider = FALSE_TRUE_PROVIDER)
+    public void testUploadMultipleFilesUnderOneSessionKey(boolean withSessionToken) throws Exception
+    {
+        String sessionToken = as.login(USER, PASSWORD);
+
+        if (withSessionToken)
+        {
+            cleanSessionWorkspace(sessionToken);
+        } else
+        {
+            initHttpSession(sessionToken);
+            cleanOSTempFolder();
+        }
+
+        MultiPartContentProvider multipart = new MultiPartContentProvider();
+        multipart.addFilePart("testFieldName1", "testFileName1", new StringContentProvider("testContent1"), null);
+        multipart.addFilePart("testFieldName2", "testFileName2", new StringContentProvider("testContent2"), null);
+        multipart.close();
+
+        HttpClient client = JettyHttpClientFactory.getHttpClient();
+        Request request = client.newRequest(SERVICE_URL).method(HttpMethod.POST);
+        if (withSessionToken)
+        {
+            request.param(PARAM_SESSION_ID, sessionToken);
+        }
+        request.param(PARAM_SESSION_KEYS_NUMBER, "1");
+        request.param(PARAM_SESSION_KEY_PREFIX + "0", "testFieldName");
+        request.content(multipart);
+
+        request.send();
+
+        if (withSessionToken)
+        {
+            assertSessionWorkspaceFiles(sessionToken, "testContent1", "testContent2");
+        } else
+        {
+            assertOSTempFolderFiles("testContent1", "testContent2");
+        }
+    }
+
+    @Test(dataProvider = FALSE_TRUE_PROVIDER)
+    public void testUploadMultipleFilesUnderMultipleSessionKeys(boolean withSessionToken) throws Exception
+    {
+        String sessionToken = as.login(USER, PASSWORD);
+
+        if (withSessionToken)
+        {
+            cleanSessionWorkspace(sessionToken);
+        } else
+        {
+            initHttpSession(sessionToken);
+            cleanOSTempFolder();
+        }
+
+        MultiPartContentProvider multipart = new MultiPartContentProvider();
+        multipart.addFilePart("testFieldName1", "testFileName1", new StringContentProvider("testContent1"), null);
+        multipart.addFilePart("testFieldName2", "testFileName2", new StringContentProvider("testContent2"), null);
+        multipart.close();
+
+        HttpClient client = JettyHttpClientFactory.getHttpClient();
+        Request request = client.newRequest(SERVICE_URL).method(HttpMethod.POST);
+        if (withSessionToken)
+        {
+            request.param(PARAM_SESSION_ID, sessionToken);
+        }
+        request.param(PARAM_SESSION_KEYS_NUMBER, "2");
+        request.param(PARAM_SESSION_KEY_PREFIX + "0", "testFieldName1");
+        request.param(PARAM_SESSION_KEY_PREFIX + "1", "testFieldName2");
+        request.content(multipart);
+
+        request.send();
+
+        if (withSessionToken)
+        {
+            assertSessionWorkspaceFiles(sessionToken, "testContent1", "testContent2");
+        } else
+        {
+            assertOSTempFolderFiles("testContent1", "testContent2");
+        }
+    }
+
+    @Test
+    public void testUploadWithHttpSessionAndWithoutSessionToken() throws Exception
+    {
+        String sessionToken = as.login(USER, PASSWORD);
+
+        initHttpSession(sessionToken);
+
+        cleanSessionWorkspace(sessionToken);
+        cleanOSTempFolder();
+
+        MultiPartContentProvider multipart = new MultiPartContentProvider();
+        multipart.addFilePart("testFieldName", "testFileName", new StringContentProvider("testContent"), null);
+        multipart.close();
+
+        HttpClient client = JettyHttpClientFactory.getHttpClient();
+        Request request = client.newRequest(SERVICE_URL).method(HttpMethod.POST);
+        request.param(PARAM_SESSION_KEYS_NUMBER, "1");
+        request.param(PARAM_SESSION_KEY_PREFIX + "0", "testFieldName");
+        request.content(multipart);
+
+        request.send();
+
+        assertSessionWorkspaceFiles(sessionToken);
+        assertOSTempFolderFiles("testContent");
+    }
+
+    @Test
+    public void testUploadWithoutHttpSessionAndWithoutSessionToken() throws Exception
+    {
+        MultiPartContentProvider multipart = new MultiPartContentProvider();
+        multipart.addFilePart("testFieldName", "testFileName", new StringContentProvider("testContent"), null);
+        multipart.close();
+
+        HttpClient client = JettyHttpClientFactory.getHttpClient();
+        Request request = client.newRequest(SERVICE_URL).method(HttpMethod.POST);
+        request.param(PARAM_SESSION_KEYS_NUMBER, "1");
+        request.param(PARAM_SESSION_KEY_PREFIX + "0", "testFieldName");
+        request.content(multipart);
+
+        ContentResponse response = request.send();
+        assertEquals("<message type=\"error\">Pre-existing session required but none found</message>", response.getContentAsString());
+    }
+
+    @Test
+    public void testUploadWithoutHttpSessionAndWithSessionToken() throws Exception
+    {
+        String sessionToken = as.login(USER, PASSWORD);
+
+        cleanSessionWorkspace(sessionToken);
+
+        MultiPartContentProvider multipart = new MultiPartContentProvider();
+        multipart.addFilePart("testFieldName", "testFileName", new StringContentProvider("testContent"), null);
+        multipart.close();
+
+        HttpClient client = JettyHttpClientFactory.getHttpClient();
+        Request request = client.newRequest(SERVICE_URL).method(HttpMethod.POST);
+        request.param(PARAM_SESSION_ID, sessionToken);
+        request.param(PARAM_SESSION_KEYS_NUMBER, "1");
+        request.param(PARAM_SESSION_KEY_PREFIX + "0", "testFieldName");
+        request.content(multipart);
+
+        request.send();
+
+        assertSessionWorkspaceFiles(sessionToken, "testContent");
+    }
+
+    @Test
+    public void testUploadWithoutSessionKeysNumber() throws Exception
+    {
+        String sessionToken = as.login(USER, PASSWORD);
+
+        MultiPartContentProvider multipart = new MultiPartContentProvider();
+        multipart.addFilePart("testFieldName", "testFileName", new StringContentProvider("testContent"), null);
+        multipart.close();
+
+        HttpClient client = JettyHttpClientFactory.getHttpClient();
+        Request request = client.newRequest(SERVICE_URL).method(HttpMethod.POST);
+        request.param(PARAM_SESSION_ID, sessionToken);
+        request.param(PARAM_SESSION_KEY_PREFIX + "0", "testFieldName");
+        request.content(multipart);
+
+        ContentResponse response = request.send();
+        assertEquals("<message type=\"error\">No form field 'sessionKeysNumber' could be found in the transmitted form.</message>",
+                response.getContentAsString());
+    }
+
+    @Test
+    public void testUploadWithIncorrectSessionKeysNumberFormat() throws Exception
+    {
+        String sessionToken = as.login(USER, PASSWORD);
+
+        MultiPartContentProvider multipart = new MultiPartContentProvider();
+        multipart.addFilePart("testFieldName", "testFileName", new StringContentProvider("testContent"), null);
+        multipart.close();
+
+        HttpClient client = JettyHttpClientFactory.getHttpClient();
+        Request request = client.newRequest(SERVICE_URL).method(HttpMethod.POST);
+        request.param(PARAM_SESSION_ID, sessionToken);
+        request.param(PARAM_SESSION_KEYS_NUMBER, "thisShouldBeANumber");
+        request.param(PARAM_SESSION_KEY_PREFIX + "0", "testFieldName");
+        request.content(multipart);
+
+        ContentResponse response = request.send();
+        assertEquals("<message type=\"error\">For input string: \"thisShouldBeANumber\"</message>",
+                response.getContentAsString());
+    }
+
+    @Test
+    public void testUploadWithoutSessionKeyPrefix() throws Exception
+    {
+        String sessionToken = as.login(USER, PASSWORD);
+
+        MultiPartContentProvider multipart = new MultiPartContentProvider();
+        multipart.addFilePart("testFieldName", "testFileName", new StringContentProvider("testContent"), null);
+        multipart.close();
+
+        HttpClient client = JettyHttpClientFactory.getHttpClient();
+        Request request = client.newRequest(SERVICE_URL).method(HttpMethod.POST);
+        request.param(PARAM_SESSION_ID, sessionToken);
+        request.param(PARAM_SESSION_KEYS_NUMBER, "1");
+        request.content(multipart);
+
+        ContentResponse response = request.send();
+
+        assertEquals("<message type=\"error\">No field '" + PARAM_SESSION_KEY_PREFIX + "0' could be found in the transmitted form.</message>",
+                response.getContentAsString());
+    }
+
+    @Test
+    public void testUploadWithTooFewSessionKeyPrefixes() throws Exception
+    {
+        String sessionToken = as.login(USER, PASSWORD);
+
+        MultiPartContentProvider multipart = new MultiPartContentProvider();
+        multipart.addFilePart("testFieldName", "testFileName", new StringContentProvider("testContent"), null);
+        multipart.close();
+
+        HttpClient client = JettyHttpClientFactory.getHttpClient();
+        Request request = client.newRequest(SERVICE_URL).method(HttpMethod.POST);
+        request.param(PARAM_SESSION_ID, sessionToken);
+        request.param(PARAM_SESSION_KEYS_NUMBER, "2");
+        request.param(PARAM_SESSION_KEY_PREFIX + "0", "testFieldName");
+        request.content(multipart);
+
+        ContentResponse response = request.send();
+
+        assertEquals("<message type=\"error\">No field '" + PARAM_SESSION_KEY_PREFIX + "1' could be found in the transmitted form.</message>",
+                response.getContentAsString());
+    }
+
+    @Test
+    public void testUploadWithLogout() throws Exception
+    {
+        String sessionToken = as.login(USER, PASSWORD);
+
+        MultiPartContentProvider multipart = new MultiPartContentProvider();
+        multipart.addFilePart("testFieldName", "testFileName", new StringContentProvider("testContent"), null);
+        multipart.close();
+
+        HttpClient client = JettyHttpClientFactory.getHttpClient();
+        Request request = client.newRequest(SERVICE_URL).method(HttpMethod.POST);
+        request.param(PARAM_SESSION_ID, sessionToken);
+        request.param(PARAM_SESSION_KEYS_NUMBER, "1");
+        request.param(PARAM_SESSION_KEY_PREFIX + "0", "testFieldName");
+        request.content(multipart);
+
+        request.send();
+
+        assertSessionWorkspaceFiles(sessionToken, "testContent");
+
+        as.logout(sessionToken);
+
+        assertSessionWorkspaceFiles(sessionToken);
+    }
+
+    private File getSessionWorkspace(String sessionToken) throws Exception
+    {
+        File sessionWorkspaceRootDir = new File("targets/sessionWorkspace");
+        return new File(sessionWorkspaceRootDir, sessionToken);
+    }
+
+    private void cleanSessionWorkspace(String sessionToken) throws Exception
+    {
+        File sessionWorkspace = getSessionWorkspace(sessionToken);
+
+        if (sessionWorkspace.exists())
+        {
+            FileUtils.deleteQuietly(sessionWorkspace);
+        }
+
+        assertSessionWorkspaceFiles(sessionToken);
+    }
+
+    private void assertSessionWorkspaceFiles(String sessionToken, String... fileContents) throws Exception
+    {
+        assertFiles(getSessionWorkspace(sessionToken).listFiles(), fileContents);
+    }
+
+    private void initHttpSession(String sessionToken) throws Exception
+    {
+        // upload a dummy file to initialize HTTP session
+        MultiPartContentProvider multipart = new MultiPartContentProvider();
+        multipart.addFilePart("initHttpSession", "initHttpSession", new StringContentProvider("initHttpSession"), null);
+        multipart.close();
+
+        HttpClient client = JettyHttpClientFactory.getHttpClient();
+        Request request = client.newRequest(SERVICE_URL).method(HttpMethod.POST);
+        request.param(PARAM_SESSION_ID, sessionToken);
+        request.param(PARAM_SESSION_KEYS_NUMBER, "1");
+        request.param(PARAM_SESSION_KEY_PREFIX + "0", "initHttpSession");
+        request.content(multipart);
+
+        request.send();
+    }
+
+    private File getOSTempFolder() throws Exception
+    {
+        return new File(System.getProperty("java.io.tmpdir"));
+    }
+
+    private void cleanOSTempFolder() throws Exception
+    {
+        File tempFolder = getOSTempFolder();
+
+        if (tempFolder.exists())
+        {
+            File[] files = tempFolder.listFiles(new OSTempFolderFileFilter());
+
+            for (File file : files)
+            {
+                FileUtils.deleteQuietly(file);
+            }
+        }
+
+        assertOSTempFolderFiles();
+    }
+
+    private void assertOSTempFolderFiles(String... fileContents) throws Exception
+    {
+        assertFiles(getOSTempFolder().listFiles(new OSTempFolderFileFilter()), fileContents);
+    }
+
+    private void assertFiles(File[] files, String... fileContents) throws Exception
+    {
+        List<String> expectedContents = new ArrayList<String>(Arrays.asList(fileContents));
+        List<String> actualContents = new ArrayList<String>();
+
+        if (files != null)
+        {
+            for (File file : files)
+            {
+                actualContents.add(FileUtils.readFileToString(file));
+            }
+        }
+
+        expectedContents.sort(String.CASE_INSENSITIVE_ORDER);
+        actualContents.sort(String.CASE_INSENSITIVE_ORDER);
+
+        assertEquals(expectedContents, actualContents);
+    }
+
+    private static class OSTempFolderFileFilter implements FileFilter
+    {
+
+        @Override
+        public boolean accept(File file)
+        {
+            return file.getName().startsWith(UploadedFilesBean.class.getSimpleName());
+        }
+
+    }
+
+    @DataProvider(name = FALSE_TRUE_PROVIDER)
+    public static Object[][] provideFalseTrue()
+    {
+        return new Object[][] { { false }, { true } };
+    }
+
+}
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/server/UploadServiceServlet.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/server/UploadServiceServlet.java
index 2653065254d88b053deb7b3d32fb5970588fb8b7..660b9952ab3f374608a0bc0da0b6abb63292221b 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/server/UploadServiceServlet.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/server/UploadServiceServlet.java
@@ -29,6 +29,7 @@ import javax.servlet.http.HttpSession;
 
 import org.apache.commons.lang.StringUtils;
 import org.apache.log4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Controller;
 import org.springframework.web.HttpSessionRequiredException;
 import org.springframework.web.bind.annotation.RequestMapping;
@@ -43,6 +44,7 @@ import ch.systemsx.cisd.common.exceptions.UserFailureException;
 import ch.systemsx.cisd.common.logging.LogCategory;
 import ch.systemsx.cisd.common.logging.LogFactory;
 import ch.systemsx.cisd.openbis.generic.server.ComponentNames;
+import ch.systemsx.cisd.openbis.generic.server.ISessionWorkspaceProvider;
 import ch.systemsx.cisd.openbis.generic.server.SessionConstants;
 import ch.systemsx.cisd.openbis.generic.shared.IOpenBisSessionManager;
 import ch.systemsx.cisd.openbis.generic.shared.dto.Session;
@@ -65,8 +67,7 @@ import ch.systemsx.cisd.openbis.generic.shared.dto.Session;
  * @author Christian Ribeaud
  */
 @Controller
-@RequestMapping(
-{ "/upload", "/openbis/upload" })
+@RequestMapping({ "/upload", "/openbis/upload" })
 public final class UploadServiceServlet extends AbstractController
 {
     private static final Logger operationLog = LogFactory.getLogger(LogCategory.OPERATION,
@@ -77,6 +78,9 @@ public final class UploadServiceServlet extends AbstractController
     @Resource(name = ComponentNames.SESSION_MANAGER)
     protected IOpenBisSessionManager sessionManager;
 
+    @Autowired
+    private ISessionWorkspaceProvider sessionWorkspaceProvider;
+
     @Private
     UploadServiceServlet(ISessionFilesSetter sessionFilesSetter)
     {
@@ -91,8 +95,7 @@ public final class UploadServiceServlet extends AbstractController
         this(new SessionFilesSetter());
     }
 
-    @SuppressWarnings(
-    { "unchecked", "rawtypes" })
+    @SuppressWarnings({ "unchecked", "rawtypes" })
     private final static Iterator<String> cast(final Iterator iterator)
     {
         return iterator;
@@ -208,7 +211,7 @@ public final class UploadServiceServlet extends AbstractController
             {
                 // Note: addFilesToSession has a side effect - adds extracted files to the session
                 boolean fileExtracted =
-                        sessionFilesSetter.addFilesToSession(session, multipartRequest, sessionKey);
+                        sessionFilesSetter.addFilesToSession(sessionToken, session, multipartRequest, sessionKey, sessionWorkspaceProvider);
                 atLeastOneFileUploaded = atLeastOneFileUploaded || fileExtracted;
             }
             if (atLeastOneFileUploaded == false)
@@ -246,25 +249,25 @@ public final class UploadServiceServlet extends AbstractController
          * 
          * @return <code>true</code> if at least one file has been found and added
          */
-        public boolean addFilesToSession(final HttpSession session,
-                final MultipartHttpServletRequest multipartRequest, String sessionKey);
+        public boolean addFilesToSession(String sessionToken, final HttpSession session,
+                final MultipartHttpServletRequest multipartRequest, String sessionKey, ISessionWorkspaceProvider sessionWorkspaceProvider);
     }
 
     @Private
     static class SessionFilesSetter implements ISessionFilesSetter
     {
         @Override
-        public boolean addFilesToSession(final HttpSession session,
-                final MultipartHttpServletRequest multipartRequest, String sessionKey)
+        public boolean addFilesToSession(String sessionToken, final HttpSession session,
+                final MultipartHttpServletRequest multipartRequest, String sessionKey, ISessionWorkspaceProvider sessionWorkspaceProvider)
         {
-            return addFilesToSessionUsingBean(session, multipartRequest, sessionKey,
-                    new UploadedFilesBean());
+            return addFilesToSessionUsingBean(sessionToken, session, multipartRequest, sessionKey,
+                    new UploadedFilesBean(), sessionWorkspaceProvider);
         }
 
         @Private
-        boolean addFilesToSessionUsingBean(final HttpSession session,
+        boolean addFilesToSessionUsingBean(String sessionToken, final HttpSession session,
                 final MultipartHttpServletRequest multipartRequest, String sessionKey,
-                final UploadedFilesBean uploadedFiles)
+                final UploadedFilesBean uploadedFiles, ISessionWorkspaceProvider sessionWorkspaceProvider)
         {
             assert StringUtils.isBlank(sessionKey) == false;
             boolean fileUploaded = false;
@@ -277,7 +280,7 @@ public final class UploadServiceServlet extends AbstractController
                     final MultipartFile multipartFile = multipartRequest.getFile(fileName);
                     if (multipartFile.isEmpty() == false)
                     {
-                        uploadedFiles.addMultipartFile(multipartFile);
+                        uploadedFiles.addMultipartFile(sessionToken, multipartFile, sessionWorkspaceProvider);
                         fileUploaded = true;
                     }
                 }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/server/UploadedFilesBean.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/server/UploadedFilesBean.java
index 37d717f34299111e7e1b91a92463df1ac1e51e20..408d55ee7cb5f674a717675e95a58679d1fc911e 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/server/UploadedFilesBean.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/server/UploadedFilesBean.java
@@ -26,8 +26,10 @@ import org.apache.commons.io.FileUtils;
 import org.springframework.web.multipart.MultipartFile;
 
 import ch.systemsx.cisd.base.exceptions.IOExceptionUnchecked;
+import ch.systemsx.cisd.common.shared.basic.string.StringUtils;
 import ch.systemsx.cisd.openbis.common.spring.IUncheckedMultipartFile;
 import ch.systemsx.cisd.openbis.common.spring.MultipartFileAdapter;
+import ch.systemsx.cisd.openbis.generic.server.ISessionWorkspaceProvider;
 
 /**
  * A bean that contains the uploaded files.
@@ -40,19 +42,31 @@ public final class UploadedFilesBean
 
     private List<IUncheckedMultipartFile> multipartFiles = new ArrayList<IUncheckedMultipartFile>();
 
-    private final File createTempFile() throws IOException
+    private final File createTempFile(String sessionToken, ISessionWorkspaceProvider sessionWorkspaceProvider) throws IOException
     {
-        final File tempFile = File.createTempFile(CLASS_SIMPLE_NAME, null);
+        File tempFolder = null;
+
+        if (false == StringUtils.isBlank(sessionToken) && sessionWorkspaceProvider != null)
+        {
+            tempFolder = sessionWorkspaceProvider.getSessionWorkspace(sessionToken);
+        }
+
+        final File tempFile = File.createTempFile(CLASS_SIMPLE_NAME, null, tempFolder);
         tempFile.deleteOnExit();
         return tempFile;
     }
 
     public final void addMultipartFile(final MultipartFile multipartFile)
+    {
+        addMultipartFile(null, multipartFile, null);
+    }
+
+    public final void addMultipartFile(String sessionToken, final MultipartFile multipartFile, ISessionWorkspaceProvider sessionWorkspaceProvider)
     {
         assert multipartFile != null : "Unspecified multipart file.";
         try
         {
-            final File tempFile = createTempFile();
+            final File tempFile = createTempFile(sessionToken, sessionWorkspaceProvider);
             multipartFile.transferTo(tempFile);
             final FileMultipartFileAdapter multipartFileAdapter =
                     new FileMultipartFileAdapter(multipartFile, tempFile);
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/AbstractServer.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/AbstractServer.java
index 8a889f811abcf6e6680d812240c5043e607707a6..c3fb261e0c74bfcd5e469b4cedac6837b7aef20f 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/AbstractServer.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/AbstractServer.java
@@ -171,6 +171,9 @@ public abstract class AbstractServer<T> extends AbstractServiceWithLogger<T> imp
     @Autowired
     private IAuthorizationConfig authorizationConfig;
 
+    @Autowired
+    private ISessionWorkspaceProvider sessionWorkspaceProvider;
+
     private IApplicationServerApi v3Api;
 
     protected String CISDHelpdeskEmail;
@@ -407,6 +410,7 @@ public abstract class AbstractServer<T> extends AbstractServiceWithLogger<T> imp
         try
         {
             sessionManager.closeSession(sessionToken);
+            sessionWorkspaceProvider.deleteSessionWorkspace(sessionToken);
             SessionFactory.cleanUpSessionOnDataStoreServers(sessionToken,
                     daoFactory.getDataStoreDAO(), dssFactory);
         } catch (InvalidSessionException e)
@@ -422,6 +426,7 @@ public abstract class AbstractServer<T> extends AbstractServiceWithLogger<T> imp
         try
         {
             sessionManager.expireSession(sessionToken);
+            sessionWorkspaceProvider.deleteSessionWorkspace(sessionToken);
             SessionFactory.cleanUpSessionOnDataStoreServers(sessionToken,
                     daoFactory.getDataStoreDAO(), dssFactory);
         } catch (InvalidSessionException e)
@@ -929,8 +934,9 @@ public abstract class AbstractServer<T> extends AbstractServiceWithLogger<T> imp
         if (person != null)
         {
             SpacePE homeGroup =
-                    groupIdOrNull == null ? null : getDAOFactory().getSpaceDAO().getByTechId(
-                            groupIdOrNull);
+                    groupIdOrNull == null ? null
+                            : getDAOFactory().getSpaceDAO().getByTechId(
+                                    groupIdOrNull);
             person.setHomeSpace(homeGroup);
             getDAOFactory().getPersonDAO().updatePerson(person);
         }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/ISessionWorkspaceProvider.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/ISessionWorkspaceProvider.java
new file mode 100644
index 0000000000000000000000000000000000000000..5f127a53e3723e7299193227569305a0f7f78a48
--- /dev/null
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/ISessionWorkspaceProvider.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2018 ETH Zuerich, CISD
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.systemsx.cisd.openbis.generic.server;
+
+import java.io.File;
+
+/**
+ * @author pkupczyk
+ */
+public interface ISessionWorkspaceProvider
+{
+
+    File getSessionWorkspace(String sessionToken);
+
+    void deleteSessionWorkspace(String sessionToken);
+
+}
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/SessionFactory.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/SessionFactory.java
index 501d80455368bc449752077e05016f23a8869b95..d16c72b71c5141f8bd3eca505fb4b3cfdf949fef 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/SessionFactory.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/SessionFactory.java
@@ -45,15 +45,18 @@ public final class SessionFactory implements ISessionFactory<Session>
 
     private final IDataStoreServiceFactory dssFactory;
 
+    private final ISessionWorkspaceProvider sessionWorkspaceProvider;
+
     public SessionFactory()
     {
-        this(null, null);
+        this(null, null, null);
     }
 
-    public SessionFactory(IDAOFactory daoFactory, IDataStoreServiceFactory dssFactory)
+    public SessionFactory(IDAOFactory daoFactory, IDataStoreServiceFactory dssFactory, ISessionWorkspaceProvider sessionWorkspaceProvider)
     {
         this.datastoreDAO = (daoFactory != null) ? daoFactory.getDataStoreDAO() : null;
         this.dssFactory = dssFactory;
+        this.sessionWorkspaceProvider = sessionWorkspaceProvider;
     }
 
     //
@@ -75,6 +78,7 @@ public final class SessionFactory implements ISessionFactory<Session>
                     @Override
                     public void cleanup()
                     {
+                        sessionWorkspaceProvider.deleteSessionWorkspace(sessionToken);
                         cleanUpSessionOnDataStoreServers(sessionToken, datastoreDAO, dssFactory);
                     }
                 });
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/SessionWorkspaceProvider.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/SessionWorkspaceProvider.java
new file mode 100644
index 0000000000000000000000000000000000000000..3d447f134c09106795815196ca7d31f8d4f45dfd
--- /dev/null
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/SessionWorkspaceProvider.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2018 ETH Zuerich, CISD
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.systemsx.cisd.openbis.generic.server;
+
+import java.io.File;
+import java.util.Properties;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.Resource;
+
+import org.apache.log4j.Logger;
+import org.springframework.stereotype.Component;
+
+import ch.systemsx.cisd.common.filesystem.QueueingPathRemoverService;
+import ch.systemsx.cisd.common.logging.LogCategory;
+import ch.systemsx.cisd.common.logging.LogFactory;
+import ch.systemsx.cisd.common.properties.PropertyUtils;
+import ch.systemsx.cisd.common.spring.ExposablePropertyPlaceholderConfigurer;
+
+/**
+ * @author pkupczyk
+ */
+@Component(value = "session-workspace-provider")
+public class SessionWorkspaceProvider implements ISessionWorkspaceProvider
+{
+
+    private static final String SESSION_WORKSPACE_ROOT_DIR_KEY = "session-workspace-root-dir";
+
+    private static final String SESSION_WORKSPACE_ROOT_DIR_DEFAULT = "sessionWorkspace";
+
+    private static final String SESSION_WORKSPACE_SHREDDER_QUEUE_FILE = ".shredder";
+
+    private static final Logger operationLog = LogFactory.getLogger(LogCategory.OPERATION, SessionWorkspaceProvider.class);
+
+    @Resource(name = ExposablePropertyPlaceholderConfigurer.PROPERTY_CONFIGURER_BEAN_NAME)
+    private ExposablePropertyPlaceholderConfigurer servicePropertiesPlaceholder;
+
+    private File sessionWorkspaceRootDir;
+
+    @PostConstruct
+    private void init() throws Exception
+    {
+        Properties serviceProperties = servicePropertiesPlaceholder.getResolvedProps();
+
+        String sessionWorkspaceRootDirString =
+                PropertyUtils.getProperty(serviceProperties, SESSION_WORKSPACE_ROOT_DIR_KEY, SESSION_WORKSPACE_ROOT_DIR_DEFAULT);
+        sessionWorkspaceRootDir = new File(sessionWorkspaceRootDirString);
+
+        operationLog.info("Session workspace root dir '" + sessionWorkspaceRootDir.getCanonicalPath() + "'");
+
+        if (false == sessionWorkspaceRootDir.exists())
+        {
+            sessionWorkspaceRootDir.mkdirs();
+        }
+
+        QueueingPathRemoverService.start(sessionWorkspaceRootDir, new File(SESSION_WORKSPACE_SHREDDER_QUEUE_FILE));
+
+        operationLog.info("Session workspace shredder service started");
+    }
+
+    @Override
+    public File getSessionWorkspace(String sessionToken)
+    {
+        File sessionWorkspace = new File(sessionWorkspaceRootDir, sessionToken);
+
+        if (false == sessionWorkspace.exists())
+        {
+            sessionWorkspace.mkdirs();
+            operationLog.info("Session workspace '" + sessionToken + "' created");
+        }
+
+        return sessionWorkspace;
+    }
+
+    @Override
+    public void deleteSessionWorkspace(String sessionToken)
+    {
+        try
+        {
+            File sessionWorkspace = new File(sessionWorkspaceRootDir, sessionToken);
+
+            if (sessionWorkspace.exists())
+            {
+                QueueingPathRemoverService.removeRecursively(sessionWorkspace);
+                operationLog.info("Session workspace '" + sessionToken + "' added to shredder queue");
+            }
+        } catch (Exception e)
+        {
+            operationLog.warn("Session workspace '" + sessionToken + "' could not be shredded", e);
+        }
+    }
+
+}
diff --git a/openbis/source/java/genericApplicationContext.xml b/openbis/source/java/genericApplicationContext.xml
index 362ea3aeb071cef72f782074b3846b749201c9cb..331b13663100be01b1589ba339f0d0e9f69c4e11 100644
--- a/openbis/source/java/genericApplicationContext.xml
+++ b/openbis/source/java/genericApplicationContext.xml
@@ -130,6 +130,7 @@
 			<bean class="ch.systemsx.cisd.openbis.generic.server.SessionFactory">
 				<constructor-arg ref="dao-factory" />
 				<constructor-arg ref="dss-factory" />
+				<constructor-arg ref="session-workspace-provider" />
 			</bean>
 		</constructor-arg>
 		<constructor-arg>
diff --git a/openbis/source/java/service.properties b/openbis/source/java/service.properties
index 568792007cb475125e82621cc4091c25f46bd520..3aa53d026290fa8be24873ec2ed1efa38c7c405e 100644
--- a/openbis/source/java/service.properties
+++ b/openbis/source/java/service.properties
@@ -20,6 +20,8 @@ create-continuous-sample-codes = false
 
 data-set-types-with-no-experiment-needed = (?!REQUIRES\\_EXPERIMENT).*
 
+session-workspace-root-dir = targets/sessionWorkspace
+
 # Supported: currently only 'postgresql' is supported
 database.engine = postgresql
 database.create-from-scratch = false
diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/client/web/server/UploadServiceServletTest.java b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/client/web/server/UploadServiceServletTest.java
index 24d96d6c23b126b885e10d5fa7412a2551bf0ede..18884125e7bfc42766449768613bd04a3e3b2de2 100644
--- a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/client/web/server/UploadServiceServletTest.java
+++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/client/web/server/UploadServiceServletTest.java
@@ -39,8 +39,7 @@ import ch.systemsx.cisd.openbis.generic.client.web.server.UploadServiceServlet.S
  * 
  * @author Izabela Adamczyk
  */
-@Friend(toClasses =
-{ UploadServiceServlet.class, ISessionFilesSetter.class, SessionFilesSetter.class })
+@Friend(toClasses = { UploadServiceServlet.class, ISessionFilesSetter.class, SessionFilesSetter.class })
 public final class UploadServiceServletTest extends AssertJUnit
 {
 
@@ -231,8 +230,8 @@ public final class UploadServiceServletTest extends AssertJUnit
                         one(multipartHttpServletRequest).getParameter(SESSION_KEY_PREFIX + i);
                         will(returnValue(sessionKey));
 
-                        one(sessionFilesSetter).addFilesToSession(httpSession,
-                                multipartHttpServletRequest, sessionKey);
+                        one(sessionFilesSetter).addFilesToSession("", httpSession,
+                                multipartHttpServletRequest, sessionKey, null);
                         will(returnValue(false));
                     }
 
@@ -269,8 +268,8 @@ public final class UploadServiceServletTest extends AssertJUnit
                         one(multipartHttpServletRequest).getParameter(SESSION_KEY_PREFIX + i);
                         will(returnValue(sessionKey));
 
-                        one(sessionFilesSetter).addFilesToSession(httpSession,
-                                multipartHttpServletRequest, sessionKey);
+                        one(sessionFilesSetter).addFilesToSession("", httpSession,
+                                multipartHttpServletRequest, sessionKey, null);
                         will(returnValue(true));
                     }
                     expectSendResponse(this);
@@ -299,8 +298,8 @@ public final class UploadServiceServletTest extends AssertJUnit
                         one(multipartHttpServletRequest).getParameter(SESSION_KEY_PREFIX + i);
                         will(returnValue(sessionKey));
 
-                        one(sessionFilesSetter).addFilesToSession(httpSession,
-                                multipartHttpServletRequest, sessionKey);
+                        one(sessionFilesSetter).addFilesToSession("", httpSession,
+                                multipartHttpServletRequest, sessionKey, null);
                         will(returnValue(i != numberOfSessionKeys - 1));
                     }
                     expectSendResponse(this);