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..0c090af2f6305d9a23e6f116ac09b53521b1d495
--- /dev/null
+++ b/datastore_server/sourceTest/java/ch/ethz/sis/openbis/generic/dss/systemtest/api/v3/UploadServletTest.java
@@ -0,0 +1,546 @@
+/*
+ * 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.AfterMethod;
+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() throws Exception
+    {
+        JettyHttpClientFactory.getHttpClient().getCookieStore().removeAll();
+        cleanOSTempFolder();
+    }
+
+    @AfterMethod
+    private void afterMethod() throws Exception
+    {
+        assertOSTempFolderFiles();
+    }
+
+    @Test(dataProvider = FALSE_TRUE_PROVIDER)
+    public void testUploadSingleFile(boolean withSessionTokenParam) throws Exception
+    {
+        String sessionToken = as.login(USER, PASSWORD);
+
+        if (false == withSessionTokenParam)
+        {
+            initHttpSession(sessionToken);
+        }
+
+        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);
+        if (withSessionTokenParam)
+        {
+            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(dataProvider = FALSE_TRUE_PROVIDER)
+    public void testUploadMultipleFilesUnderOneSessionKey(boolean withSessionTokenParam) throws Exception
+    {
+        String sessionToken = as.login(USER, PASSWORD);
+
+        if (false == withSessionTokenParam)
+        {
+            initHttpSession(sessionToken);
+        }
+
+        cleanSessionWorkspace(sessionToken);
+
+        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 (withSessionTokenParam)
+        {
+            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, "testContent1", "testContent2");
+    }
+
+    @Test(dataProvider = FALSE_TRUE_PROVIDER)
+    public void testUploadMultipleFilesUnderMultipleSessionKeys(boolean withSessionTokenParam) throws Exception
+    {
+        String sessionToken = as.login(USER, PASSWORD);
+
+        if (false == withSessionTokenParam)
+        {
+            initHttpSession(sessionToken);
+        }
+
+        cleanSessionWorkspace(sessionToken);
+
+        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 (withSessionTokenParam)
+        {
+            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();
+
+        assertSessionWorkspaceFiles(sessionToken, "testContent1", "testContent2");
+    }
+
+    @Test
+    public void testUploadWithHttpSessionValidAndWithoutSessionTokenParam() throws Exception
+    {
+        String sessionToken = as.login(USER, PASSWORD);
+        initHttpSession(sessionToken);
+        cleanSessionWorkspace(sessionToken);
+        upload(null, "testContent");
+        assertSessionWorkspaceFiles(sessionToken, "testContent");
+    }
+
+    @Test
+    public void testUploadWithHttpSessionValidAndWithSessionTokenParamInvalid() throws Exception
+    {
+        String sessionToken1 = as.login(USER, PASSWORD);
+
+        initHttpSession(sessionToken1);
+
+        String sessionToken2 = "admin-invalidtoken";
+
+        cleanSessionWorkspace(sessionToken1);
+        cleanSessionWorkspace(sessionToken2);
+
+        ContentResponse response = upload(sessionToken2, "testContent");
+
+        assertEquals("<message type=\"error\">Session token '" + sessionToken2 + "' is invalid: user is not logged in.</message>",
+                response.getContentAsString());
+
+        assertSessionWorkspaceFiles(sessionToken1);
+        assertSessionWorkspaceFiles(sessionToken2);
+    }
+
+    @Test
+    public void testUploadWithHttpSessionValidAndWithSessionTokenParamValid() throws Exception
+    {
+        String sessionToken1 = as.login(USER, PASSWORD);
+
+        initHttpSession(sessionToken1);
+
+        String sessionToken2 = as.login(USER, PASSWORD);
+
+        cleanSessionWorkspace(sessionToken1);
+        cleanSessionWorkspace(sessionToken2);
+
+        upload(sessionToken2, "testContent");
+
+        assertSessionWorkspaceFiles(sessionToken1);
+        assertSessionWorkspaceFiles(sessionToken2, "testContent");
+    }
+
+    @Test
+    public void testUploadWithHttpSessionInvalidAndWithoutSessionTokenParam() throws Exception
+    {
+        String sessionToken = as.login(USER, PASSWORD);
+
+        initHttpSession(sessionToken);
+
+        // invalidate the session
+        as.logout(sessionToken);
+
+        cleanSessionWorkspace(sessionToken);
+
+        ContentResponse response = upload(null, "testContent");
+
+        assertEquals("<message type=\"error\">Session token '" + sessionToken + "' is invalid: user is not logged in.</message>",
+                response.getContentAsString());
+
+        assertSessionWorkspaceFiles(sessionToken);
+    }
+
+    @Test
+    public void testUploadWithHttpSessionInvalidAndWithSessionTokenParamValid() throws Exception
+    {
+        String sessionToken1 = as.login(USER, PASSWORD);
+
+        initHttpSession(sessionToken1);
+
+        // invalidate the session
+        as.logout(sessionToken1);
+
+        String sessionToken2 = as.login(USER, PASSWORD);
+
+        cleanSessionWorkspace(sessionToken1);
+        cleanSessionWorkspace(sessionToken2);
+
+        upload(sessionToken2, "testContent");
+
+        assertSessionWorkspaceFiles(sessionToken1);
+        assertSessionWorkspaceFiles(sessionToken2, "testContent");
+    }
+
+    @Test
+    public void testUploadWithHttpSessionInvalidAndWithSessionTokenParamInvalid() throws Exception
+    {
+        String sessionToken1 = as.login(USER, PASSWORD);
+
+        initHttpSession(sessionToken1);
+
+        // invalidate the session
+        as.logout(sessionToken1);
+
+        String sessionToken2 = "admin-invalidtoken";
+
+        cleanSessionWorkspace(sessionToken1);
+        cleanSessionWorkspace(sessionToken2);
+
+        ContentResponse response = upload(sessionToken2, "testContent");
+
+        assertEquals("<message type=\"error\">Session token '" + sessionToken2 + "' is invalid: user is not logged in.</message>",
+                response.getContentAsString());
+
+        assertSessionWorkspaceFiles(sessionToken1);
+        assertSessionWorkspaceFiles(sessionToken2);
+    }
+
+    @Test
+    public void testUploadWithoutHttpSessionAndWithoutSessionTokenParam() throws Exception
+    {
+        ContentResponse response = upload(null, "testContent");
+        assertEquals("<message type=\"error\">Pre-existing session required but none found</message>", response.getContentAsString());
+    }
+
+    @Test
+    public void testUploadWithoutHttpSessionAndWithSessionTokenParamValid() throws Exception
+    {
+        String sessionToken = as.login(USER, PASSWORD);
+        cleanSessionWorkspace(sessionToken);
+        upload(sessionToken, "testContent");
+        assertSessionWorkspaceFiles(sessionToken, "testContent");
+    }
+
+    @Test
+    public void testUploadWithoutHttpSessionAndWithSessionTokenParamInvalid() throws Exception
+    {
+        String sessionToken = "admin-invalidtoken";
+
+        cleanSessionWorkspace(sessionToken);
+
+        ContentResponse response = upload(sessionToken, "testContent");
+
+        assertEquals("<message type=\"error\">Session token '" + sessionToken + "' is invalid: user is not logged in.</message>",
+                response.getContentAsString());
+
+        assertSessionWorkspaceFiles(sessionToken);
+    }
+
+    @Test
+    public void testUploadWithoutSessionKeysNumberParam() 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 testUploadWithIncorrectSessionKeysNumberParamFormat() 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 testUploadWithoutSessionKeyPrefixParam() 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 testUploadWithTooFewSessionKeyPrefixParams() 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);
+
+        upload(sessionToken, "testContent");
+
+        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 ContentResponse upload(String sessionToken, String fileContent) throws Exception
+    {
+        MultiPartContentProvider multipart = new MultiPartContentProvider();
+        multipart.addFilePart("testFieldName", "testFileName", new StringContentProvider(fileContent), null);
+        multipart.close();
+
+        HttpClient client = JettyHttpClientFactory.getHttpClient();
+        Request request = client.newRequest(SERVICE_URL).method(HttpMethod.POST);
+        if (sessionToken != null)
+        {
+            request.param(PARAM_SESSION_ID, sessionToken);
+        }
+        request.param(PARAM_SESSION_KEYS_NUMBER, "1");
+        request.param(PARAM_SESSION_KEY_PREFIX + "0", "testFieldName");
+        request.content(multipart);
+
+        return 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..ec75339114fc6280248e7d3527d9e0327048ffa9 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;
@@ -45,6 +46,7 @@ import ch.systemsx.cisd.common.logging.LogFactory;
 import ch.systemsx.cisd.openbis.generic.server.ComponentNames;
 import ch.systemsx.cisd.openbis.generic.server.SessionConstants;
 import ch.systemsx.cisd.openbis.generic.shared.IOpenBisSessionManager;
+import ch.systemsx.cisd.openbis.generic.shared.ISessionWorkspaceProvider;
 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,22 +78,27 @@ public final class UploadServiceServlet extends AbstractController
     @Resource(name = ComponentNames.SESSION_MANAGER)
     protected IOpenBisSessionManager sessionManager;
 
+    @Autowired
+    private ISessionWorkspaceProvider sessionWorkspaceProvider;
+
     @Private
-    UploadServiceServlet(ISessionFilesSetter sessionFilesSetter)
+    UploadServiceServlet(ISessionFilesSetter sessionFilesSetter, IOpenBisSessionManager sessionManager,
+            ISessionWorkspaceProvider sessionWorkspaceProvider)
     {
         // super(UploadedFilesBean.class);
         setSynchronizeOnSession(true);
         setRequireSession(false); // To allow upload a file for usage from an API given a session token.
         this.sessionFilesSetter = sessionFilesSetter;
+        this.sessionManager = sessionManager;
+        this.sessionWorkspaceProvider = sessionWorkspaceProvider;
     }
 
     public UploadServiceServlet()
     {
-        this(new SessionFilesSetter());
+        this(new SessionFilesSetter(), null, null);
     }
 
-    @SuppressWarnings(
-    { "unchecked", "rawtypes" })
+    @SuppressWarnings({ "unchecked", "rawtypes" })
     private final static Iterator<String> cast(final Iterator iterator)
     {
         return iterator;
@@ -192,6 +198,11 @@ public final class UploadServiceServlet extends AbstractController
             {
                 response.setStatus(500);
                 throw new HttpSessionRequiredException("Pre-existing session required but none found");
+            } else
+            {
+                sessionToken = (String) session.getAttribute(SessionConstants.OPENBIS_SESSION_TOKEN_ATTRIBUTE_KEY);
+                // check and touch the session
+                getSession(sessionToken);
             }
 
             final MultipartHttpServletRequest multipartRequest =
@@ -208,7 +219,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(session, multipartRequest, sessionKey, sessionWorkspaceProvider);
                 atLeastOneFileUploaded = atLeastOneFileUploaded || fileExtracted;
             }
             if (atLeastOneFileUploaded == false)
@@ -247,7 +258,7 @@ 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);
+                final MultipartHttpServletRequest multipartRequest, String sessionKey, ISessionWorkspaceProvider sessionWorkspaceProvider);
     }
 
     @Private
@@ -255,19 +266,22 @@ public final class UploadServiceServlet extends AbstractController
     {
         @Override
         public boolean addFilesToSession(final HttpSession session,
-                final MultipartHttpServletRequest multipartRequest, String sessionKey)
+                final MultipartHttpServletRequest multipartRequest, String sessionKey, ISessionWorkspaceProvider sessionWorkspaceProvider)
         {
             return addFilesToSessionUsingBean(session, multipartRequest, sessionKey,
-                    new UploadedFilesBean());
+                    new UploadedFilesBean(), sessionWorkspaceProvider);
         }
 
         @Private
         boolean addFilesToSessionUsingBean(final HttpSession session,
                 final MultipartHttpServletRequest multipartRequest, String sessionKey,
-                final UploadedFilesBean uploadedFiles)
+                final UploadedFilesBean uploadedFiles, ISessionWorkspaceProvider sessionWorkspaceProvider)
         {
             assert StringUtils.isBlank(sessionKey) == false;
             boolean fileUploaded = false;
+
+            String sessionToken = (String) session.getAttribute(SessionConstants.OPENBIS_SESSION_TOKEN_ATTRIBUTE_KEY);
+
             for (final Iterator<String> iterator = cast(multipartRequest.getFileNames()); iterator
                     .hasNext(); /**/)
             {
@@ -277,7 +291,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..ff6bddbe40a5366f903772bc5e1ba65123cc0735 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
@@ -23,11 +23,15 @@ import java.util.ArrayList;
 import java.util.List;
 
 import org.apache.commons.io.FileUtils;
+import org.apache.log4j.Logger;
 import org.springframework.web.multipart.MultipartFile;
 
 import ch.systemsx.cisd.base.exceptions.IOExceptionUnchecked;
+import ch.systemsx.cisd.common.logging.LogCategory;
+import ch.systemsx.cisd.common.logging.LogFactory;
 import ch.systemsx.cisd.openbis.common.spring.IUncheckedMultipartFile;
 import ch.systemsx.cisd.openbis.common.spring.MultipartFileAdapter;
+import ch.systemsx.cisd.openbis.generic.shared.ISessionWorkspaceProvider;
 
 /**
  * A bean that contains the uploaded files.
@@ -36,24 +40,31 @@ import ch.systemsx.cisd.openbis.common.spring.MultipartFileAdapter;
  */
 public final class UploadedFilesBean
 {
+
+    private static final Logger operationLog = LogFactory.getLogger(LogCategory.OPERATION, UploadedFilesBean.class);
+
     private static final String CLASS_SIMPLE_NAME = UploadedFilesBean.class.getSimpleName();
 
     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 = sessionWorkspaceProvider.getSessionWorkspace(sessionToken);
+        final File tempFile = File.createTempFile(CLASS_SIMPLE_NAME, null, tempFolder);
         tempFile.deleteOnExit();
         return tempFile;
     }
 
-    public final void addMultipartFile(final MultipartFile multipartFile)
+    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);
+
+            operationLog.info("Uploaded file '" + multipartFile.getOriginalFilename() + "' to session workspace");
+
             final FileMultipartFileAdapter multipartFileAdapter =
                     new FileMultipartFileAdapter(multipartFile, tempFile);
             multipartFiles.add(multipartFileAdapter);
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..70bc7cac8f3539fb391f5f37b9d89eec72c122eb 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
@@ -65,6 +65,7 @@ import ch.systemsx.cisd.openbis.generic.server.plugin.SampleServerPluginRegistry
 import ch.systemsx.cisd.openbis.generic.shared.IOpenBisSessionManager;
 import ch.systemsx.cisd.openbis.generic.shared.IRemoteHostValidator;
 import ch.systemsx.cisd.openbis.generic.shared.IServer;
+import ch.systemsx.cisd.openbis.generic.shared.ISessionWorkspaceProvider;
 import ch.systemsx.cisd.openbis.generic.shared.ResourceNames;
 import ch.systemsx.cisd.openbis.generic.shared.authorization.IAuthorizationConfig;
 import ch.systemsx.cisd.openbis.generic.shared.basic.EntityVisitComparatorByTimeStamp;
@@ -171,6 +172,9 @@ public abstract class AbstractServer<T> extends AbstractServiceWithLogger<T> imp
     @Autowired
     private IAuthorizationConfig authorizationConfig;
 
+    @Autowired
+    private ISessionWorkspaceProvider sessionWorkspaceProvider;
+
     private IApplicationServerApi v3Api;
 
     protected String CISDHelpdeskEmail;
@@ -207,6 +211,12 @@ public abstract class AbstractServer<T> extends AbstractServiceWithLogger<T> imp
         this.displaySettingsProvider = displaySettingsProvider;
     }
 
+    // For unit tests - in production Spring will inject this object.
+    public void setSessionWorkspaceProvider(ISessionWorkspaceProvider sessionWorkspaceProvider)
+    {
+        this.sessionWorkspaceProvider = sessionWorkspaceProvider;
+    }
+
     // For unit tests - in production Spring will inject this object.
     public void setDssFactory(IDataStoreServiceFactory dssFactory)
     {
@@ -407,6 +417,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 +433,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 +941,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/CommonServerLogger.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/CommonServerLogger.java
index 3dd15b719ba57b9ed7d79ce21161b72eb05b3d34..7b7c8d83a92fa5673decdc6818fa67409be80af6 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/CommonServerLogger.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/CommonServerLogger.java
@@ -1636,7 +1636,7 @@ final class CommonServerLogger extends AbstractServerLogger implements ICommonSe
             DatastoreServiceDescription serviceDescription, Map<String, Object> parameters)
     {
         logAccess(sessionToken, "createReportFromAggregationService",
-                "SERVICE(%s), PARAMETERS(%s)", serviceDescription, parameters);
+                "SERVICE(%s), PARAMETERS(%s)", serviceDescription, parameters != null ? parameters.keySet() : "[]");
         return null;
     }
 
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/MaintenanceTaskStarter.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/MaintenanceTaskStarter.java
index 72d100cc90bcc7dff3c84acb1860ac814b1e3d90..82c2ccae73a9f6e73efc5ecac1d139005d24c693 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/MaintenanceTaskStarter.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/MaintenanceTaskStarter.java
@@ -42,6 +42,7 @@ import ch.systemsx.cisd.common.maintenance.MaintenancePlugin;
 import ch.systemsx.cisd.common.maintenance.MaintenanceTaskParameters;
 import ch.systemsx.cisd.common.maintenance.MaintenanceTaskUtils;
 import ch.systemsx.cisd.common.spring.ExposablePropertyPlaceholderConfigurer;
+import ch.systemsx.cisd.openbis.generic.server.task.SessionWorkspaceCleanUpMaintenanceTask;
 
 /**
  * Configures and starts maintenance tasks.
@@ -101,6 +102,15 @@ public class MaintenanceTaskStarter implements ApplicationContextAware, Initiali
                     operationExecutionConfig.getMarkTimedOutOrDeletedTaskInterval());
         }
 
+        if (false == isTaskConfigured(tasks, SessionWorkspaceCleanUpMaintenanceTask.class))
+        {
+            tasks = addTask(tasks,
+                    SessionWorkspaceCleanUpMaintenanceTask.class,
+                    SessionWorkspaceCleanUpMaintenanceTask.DEFAULT_MAINTENANCE_TASK_NAME,
+                    false,
+                    SessionWorkspaceCleanUpMaintenanceTask.DEFAULT_MAINTENANCE_TASK_INTERVAL);
+        }
+
         plugins = MaintenanceTaskUtils.startupMaintenancePlugins(tasks);
     }
 
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..ecf7550f724b28afd3655fa249c41608eea07c30 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
@@ -27,6 +27,7 @@ import ch.systemsx.cisd.common.shared.basic.string.StringUtils;
 import ch.systemsx.cisd.openbis.generic.server.business.IDataStoreServiceFactory;
 import ch.systemsx.cisd.openbis.generic.server.dataaccess.IDAOFactory;
 import ch.systemsx.cisd.openbis.generic.server.dataaccess.IDataStoreDAO;
+import ch.systemsx.cisd.openbis.generic.shared.ISessionWorkspaceProvider;
 import ch.systemsx.cisd.openbis.generic.shared.dto.DataStorePE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.Session;
 import ch.systemsx.cisd.openbis.generic.shared.dto.Session.ISessionCleaner;
@@ -45,15 +46,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 +79,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/task/SessionWorkspaceCleanUpMaintenanceTask.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/task/SessionWorkspaceCleanUpMaintenanceTask.java
new file mode 100644
index 0000000000000000000000000000000000000000..61f78fbc6a39675a2524b340ea2f1cd6f3571cc7
--- /dev/null
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/task/SessionWorkspaceCleanUpMaintenanceTask.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2018 ETH Zuerich, SIS
+ *
+ * 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.task;
+
+import java.io.File;
+import java.util.Map;
+import java.util.Properties;
+
+import org.apache.log4j.Logger;
+
+import ch.ethz.sis.openbis.generic.asapi.v3.IApplicationServerApi;
+import ch.systemsx.cisd.common.logging.LogCategory;
+import ch.systemsx.cisd.common.logging.LogFactory;
+import ch.systemsx.cisd.common.maintenance.IMaintenanceTask;
+import ch.systemsx.cisd.openbis.generic.server.CommonServiceProvider;
+import ch.systemsx.cisd.openbis.generic.shared.ISessionWorkspaceProvider;
+import ch.systemsx.cisd.openbis.generic.shared.SessionWorkspaceProvider;
+
+/**
+ * @author pkupczyk
+ */
+public class SessionWorkspaceCleanUpMaintenanceTask implements IMaintenanceTask
+{
+
+    public static final String DEFAULT_MAINTENANCE_TASK_NAME = "session-workspace-clean-up-task";
+
+    public static final int DEFAULT_MAINTENANCE_TASK_INTERVAL = 3600;
+
+    private static final Logger operationLog = LogFactory.getLogger(LogCategory.OPERATION,
+            SessionWorkspaceCleanUpMaintenanceTask.class);
+
+    private IApplicationServerApi applicationServerApi;
+
+    private ISessionWorkspaceProvider sessionWorkspaceProvider;
+
+    public SessionWorkspaceCleanUpMaintenanceTask()
+    {
+        this(CommonServiceProvider.getApplicationServerApi(),
+                (ISessionWorkspaceProvider) CommonServiceProvider.tryToGetBean(SessionWorkspaceProvider.INTERNAL_SERVICE_NAME));
+    }
+
+    SessionWorkspaceCleanUpMaintenanceTask(IApplicationServerApi applicationServerApi, ISessionWorkspaceProvider sessionWorkspaceProvider)
+    {
+        this.applicationServerApi = applicationServerApi;
+        this.sessionWorkspaceProvider = sessionWorkspaceProvider;
+    }
+
+    @Override
+    public void setUp(String pluginName, Properties properties)
+    {
+        operationLog.info("Setup plugin " + pluginName);
+    }
+
+    @Override
+    public void execute()
+    {
+        Map<String, File> sessionWorkspaces = sessionWorkspaceProvider.getSessionWorkspaces();
+        int count = 0;
+
+        for (String sessionToken : sessionWorkspaces.keySet())
+        {
+            if (false == applicationServerApi.isSessionActive(sessionToken))
+            {
+                operationLog.info("Session '" + sessionToken + "' is no longer active. Its session workspace will be removed.");
+                sessionWorkspaceProvider.deleteSessionWorkspace(sessionToken);
+                count++;
+            }
+        }
+
+        operationLog.info("Session workspace clean up finished. Removed " + count + " workspace(s) of inactive session(s).");
+    }
+
+}
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/ISessionWorkspaceProvider.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/ISessionWorkspaceProvider.java
new file mode 100644
index 0000000000000000000000000000000000000000..f56cb5851b5827ce9e9b9887e96c8a4bb0f13ccb
--- /dev/null
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/ISessionWorkspaceProvider.java
@@ -0,0 +1,34 @@
+/*
+ * 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.shared;
+
+import java.io.File;
+import java.util.Map;
+
+/**
+ * @author pkupczyk
+ */
+public interface ISessionWorkspaceProvider
+{
+
+    Map<String, File> getSessionWorkspaces();
+
+    File getSessionWorkspace(String sessionToken);
+
+    void deleteSessionWorkspace(String sessionToken);
+
+}
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/SessionWorkspaceProvider.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/SessionWorkspaceProvider.java
new file mode 100644
index 0000000000000000000000000000000000000000..6c87f705aa22dfb68bc613035d07b55e616f9984
--- /dev/null
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/SessionWorkspaceProvider.java
@@ -0,0 +1,149 @@
+/*
+ * 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.shared;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.util.Map;
+import java.util.Properties;
+import java.util.TreeMap;
+
+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 = SessionWorkspaceProvider.INTERNAL_SERVICE_NAME)
+public class SessionWorkspaceProvider implements ISessionWorkspaceProvider
+{
+
+    public static final String INTERNAL_SERVICE_NAME = "session-workspace-provider";
+
+    public static final String SESSION_WORKSPACE_ROOT_DIR_KEY = "session-workspace-root-dir";
+
+    public static final String SESSION_WORKSPACE_ROOT_DIR_DEFAULT = "sessionWorkspace";
+
+    public static final String SESSION_WORKSPACE_SHREDDER_QUEUE_FILE = ".shredder";
+
+    private static final Logger operationLog = LogFactory.getLogger(LogCategory.OPERATION, SessionWorkspaceProvider.class);
+
+    private Properties serviceProperties;
+
+    private File sessionWorkspaceRootDir;
+
+    public SessionWorkspaceProvider()
+    {
+    }
+
+    SessionWorkspaceProvider(Properties serviceProperties)
+    {
+        this.serviceProperties = serviceProperties;
+    }
+
+    @PostConstruct
+    void init() throws Exception
+    {
+        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 Map<String, File> getSessionWorkspaces()
+    {
+        File[] sessionWorkspaces = sessionWorkspaceRootDir.listFiles(new FileFilter()
+            {
+                @Override
+                public boolean accept(File file)
+                {
+                    return false == file.isHidden();
+                }
+            });
+
+        Map<String, File> map = new TreeMap<String, File>();
+
+        if (sessionWorkspaces != null)
+        {
+            for (File sessionWorkspace : sessionWorkspaces)
+            {
+                map.put(sessionWorkspace.getName(), sessionWorkspace);
+            }
+        }
+
+        return map;
+    }
+
+    @Override
+    public File getSessionWorkspace(String sessionToken)
+    {
+        File sessionWorkspace = new File(sessionWorkspaceRootDir, sessionToken);
+
+        if (false == sessionWorkspace.exists())
+        {
+            sessionWorkspace.mkdirs();
+            operationLog.info("Session workspace 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 added to shredder queue");
+            }
+        } catch (Exception e)
+        {
+            operationLog.warn("Session workspace could not be shredded", e);
+        }
+    }
+
+    @Resource(name = ExposablePropertyPlaceholderConfigurer.PROPERTY_CONFIGURER_BEAN_NAME)
+    private void setServicePropertiesPlaceholder(ExposablePropertyPlaceholderConfigurer servicePropertiesPlaceholder)
+    {
+        serviceProperties = servicePropertiesPlaceholder.getResolvedProps();
+    }
+
+}
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..9cc20a3e906aff4497c65c8519feaed561b93c4b 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
@@ -33,14 +33,15 @@ import ch.systemsx.cisd.common.exceptions.UserFailureException;
 import ch.systemsx.cisd.common.servlet.IRequestContextProvider;
 import ch.systemsx.cisd.openbis.generic.client.web.server.UploadServiceServlet.ISessionFilesSetter;
 import ch.systemsx.cisd.openbis.generic.client.web.server.UploadServiceServlet.SessionFilesSetter;
+import ch.systemsx.cisd.openbis.generic.shared.IOpenBisSessionManager;
+import ch.systemsx.cisd.openbis.generic.shared.ISessionWorkspaceProvider;
 
 /**
  * Tests for {@link UploadServiceServlet}.
  * 
  * @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
 {
 
@@ -50,6 +51,8 @@ public final class UploadServiceServletTest extends AssertJUnit
 
     private static final String SESSION_KEYS_NUMBER = "sessionKeysNumber";
 
+    private static final String SESSION_TOKEN_KEY = "openbis-session-token";
+
     private static final String SESSION_TOKEN = "sessionID";
 
     protected Mockery context;
@@ -62,8 +65,12 @@ public final class UploadServiceServletTest extends AssertJUnit
 
     protected HttpSession httpSession;
 
+    protected IOpenBisSessionManager sessionManager;
+
     protected ISessionFilesSetter sessionFilesSetter;
 
+    protected ISessionWorkspaceProvider sessionWorkspaceProvider;
+
     @BeforeMethod
     public void setUp()
     {
@@ -72,7 +79,9 @@ public final class UploadServiceServletTest extends AssertJUnit
         multipartHttpServletRequest = context.mock(MultipartHttpServletRequest.class);
         servletResponse = context.mock(HttpServletResponse.class);
         httpSession = context.mock(HttpSession.class);
+        sessionManager = context.mock(IOpenBisSessionManager.class);
         sessionFilesSetter = context.mock(ISessionFilesSetter.class);
+        sessionWorkspaceProvider = context.mock(ISessionWorkspaceProvider.class);
     }
 
     @AfterMethod
@@ -85,7 +94,7 @@ public final class UploadServiceServletTest extends AssertJUnit
 
     private UploadServiceServlet createServlet()
     {
-        return new UploadServiceServlet(sessionFilesSetter);
+        return new UploadServiceServlet(sessionFilesSetter, sessionManager, sessionWorkspaceProvider);
     }
 
     private void expectSendResponse(Expectations exp)
@@ -98,6 +107,9 @@ public final class UploadServiceServletTest extends AssertJUnit
     {
         exp.one(multipartHttpServletRequest).getSession(false);
         exp.will(Expectations.returnValue(httpSession));
+        exp.one(httpSession).getAttribute(SESSION_TOKEN_KEY);
+        exp.will(Expectations.returnValue(SESSION_TOKEN));
+        exp.one(sessionManager).getSession(SESSION_TOKEN);
     }
 
     @Test
@@ -231,8 +243,7 @@ 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, sessionWorkspaceProvider);
                         will(returnValue(false));
                     }
 
@@ -269,8 +280,7 @@ 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, sessionWorkspaceProvider);
                         will(returnValue(true));
                     }
                     expectSendResponse(this);
@@ -299,8 +309,7 @@ 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, sessionWorkspaceProvider);
                         will(returnValue(i != numberOfSessionKeys - 1));
                     }
                     expectSendResponse(this);
diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/CommonServerTest.java b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/CommonServerTest.java
index 743befe3899775a2d2ea1063c23ceec7fa2dfbb1..0b29a6d131cc16cfafedcc6206b5b4f4aedaa638 100644
--- a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/CommonServerTest.java
+++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/CommonServerTest.java
@@ -52,6 +52,7 @@ import ch.systemsx.cisd.openbis.generic.server.plugin.ISampleTypeSlaveServerPlug
 import ch.systemsx.cisd.openbis.generic.shared.AbstractServerTestCase;
 import ch.systemsx.cisd.openbis.generic.shared.CommonTestUtils;
 import ch.systemsx.cisd.openbis.generic.shared.IDataStoreService;
+import ch.systemsx.cisd.openbis.generic.shared.ISessionWorkspaceProvider;
 import ch.systemsx.cisd.openbis.generic.shared.basic.TechId;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.AbstractExternalData;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.BatchOperationKind;
@@ -165,6 +166,8 @@ public final class CommonServerTest extends AbstractServerTestCase
 
     private org.hibernate.Session hibernateSession;
 
+    private ISessionWorkspaceProvider sessionWorkspaceProvider;
+
     private final CommonServer createServer()
     {
         CommonServer server =
@@ -189,6 +192,7 @@ public final class CommonServerTest extends AbstractServerTestCase
         server.setDataSetTypeSlaveServerPlugin(dataSetTypeSlaveServerPlugin);
         server.setBaseIndexURL(SESSION_TOKEN, BASE_INDEX_URL);
         server.setDisplaySettingsProvider(new DisplaySettingsProvider());
+        server.setSessionWorkspaceProvider(sessionWorkspaceProvider);
         return server;
     }
 
@@ -215,6 +219,7 @@ public final class CommonServerTest extends AbstractServerTestCase
         hotDeploymentController = context.mock(IHotDeploymentController.class);
         hibernateSessionFactory = context.mock(SessionFactory.class);
         hibernateSession = context.mock(org.hibernate.Session.class);
+        sessionWorkspaceProvider = context.mock(ISessionWorkspaceProvider.class);
     }
 
     @Test
@@ -275,6 +280,8 @@ public final class CommonServerTest extends AbstractServerTestCase
                     will(returnValue(dataStoreService));
 
                     one(dataStoreService).cleanupSession(SESSION_TOKEN);
+
+                    one(sessionWorkspaceProvider).deleteSessionWorkspace(SESSION_TOKEN);
                 }
             });
 
diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/task/SessionWorkspaceCleanUpMaintenanceTaskTest.java b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/task/SessionWorkspaceCleanUpMaintenanceTaskTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..8b4ec62971e0ea1029a2a9bdb9e8ba2a41fa68e1
--- /dev/null
+++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/task/SessionWorkspaceCleanUpMaintenanceTaskTest.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2018 ETH Zuerich, SIS
+ *
+ * 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.task;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.log4j.Level;
+import org.jmock.Expectations;
+import org.jmock.Mockery;
+import org.testng.AssertJUnit;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import ch.ethz.sis.openbis.generic.asapi.v3.IApplicationServerApi;
+import ch.systemsx.cisd.common.logging.BufferedAppender;
+import ch.systemsx.cisd.common.test.AssertionUtil;
+import ch.systemsx.cisd.openbis.generic.shared.ISessionWorkspaceProvider;
+import ch.systemsx.cisd.openbis.util.LogRecordingUtils;
+
+/**
+ * @author pkupczyk
+ */
+public class SessionWorkspaceCleanUpMaintenanceTaskTest extends AssertJUnit
+{
+
+    private BufferedAppender logRecorder;
+
+    private Mockery context;
+
+    private IApplicationServerApi applicationServerApi;
+
+    private ISessionWorkspaceProvider sessionWorkspaceProvider;
+
+    @BeforeMethod
+    public void setUp()
+    {
+        logRecorder = LogRecordingUtils.createRecorder("%-5p %c - %m%n", Level.INFO);
+
+        context = new Mockery();
+        applicationServerApi = context.mock(IApplicationServerApi.class);
+        sessionWorkspaceProvider = context.mock(ISessionWorkspaceProvider.class);
+    }
+
+    @AfterMethod
+    public void tearDown()
+    {
+        context.assertIsSatisfied();
+    }
+
+    @Test
+    public void testExecuteWithNoWorkspaces()
+    {
+        Map<String, File> sessionWorkspaces = new HashMap<String, File>();
+
+        context.checking(new Expectations()
+            {
+                {
+                    one(sessionWorkspaceProvider).getSessionWorkspaces();
+                    will(returnValue(sessionWorkspaces));
+                }
+            });
+
+        SessionWorkspaceCleanUpMaintenanceTask task = new SessionWorkspaceCleanUpMaintenanceTask(applicationServerApi, sessionWorkspaceProvider);
+        task.execute();
+
+        AssertionUtil.assertContainsLines(
+                "INFO  OPERATION.SessionWorkspaceCleanUpMaintenanceTask - Session workspace clean up finished. Removed 0 workspace(s) of inactive session(s).",
+                logRecorder.getLogContent());
+    }
+
+    @Test
+    public void testExecuteWithMultipleWorkspaces()
+    {
+        Map<String, File> sessionWorkspaces = new HashMap<String, File>();
+        sessionWorkspaces.put("token1", new File("workspace1"));
+        sessionWorkspaces.put("token2", new File("workspace2"));
+
+        context.checking(new Expectations()
+            {
+                {
+                    one(sessionWorkspaceProvider).getSessionWorkspaces();
+                    will(returnValue(sessionWorkspaces));
+
+                    one(applicationServerApi).isSessionActive("token1");
+                    will(returnValue(true));
+
+                    one(applicationServerApi).isSessionActive("token2");
+                    will(returnValue(false));
+
+                    one(sessionWorkspaceProvider).deleteSessionWorkspace("token2");
+                }
+            });
+
+        SessionWorkspaceCleanUpMaintenanceTask task = new SessionWorkspaceCleanUpMaintenanceTask(applicationServerApi, sessionWorkspaceProvider);
+        task.execute();
+
+        AssertionUtil.assertContainsLines(
+                "INFO  OPERATION.SessionWorkspaceCleanUpMaintenanceTask - Session 'token2' is no longer active. Its session workspace will be removed.",
+                logRecorder.getLogContent());
+
+        AssertionUtil.assertContainsLines(
+                "INFO  OPERATION.SessionWorkspaceCleanUpMaintenanceTask - Session workspace clean up finished. Removed 1 workspace(s) of inactive session(s).",
+                logRecorder.getLogContent());
+    }
+
+}
diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/SessionWorkspaceProviderTest.java b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/SessionWorkspaceProviderTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..fa093842df0e0735b460425e32b119238fe22926
--- /dev/null
+++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/SessionWorkspaceProviderTest.java
@@ -0,0 +1,96 @@
+package ch.systemsx.cisd.openbis.generic.shared;
+
+import java.io.File;
+import java.util.Map;
+import java.util.Properties;
+
+import org.apache.commons.io.FileUtils;
+import org.testng.annotations.Test;
+
+import ch.systemsx.cisd.base.tests.AbstractFileSystemTestCase;
+import ch.systemsx.cisd.openbis.generic.shared.SessionWorkspaceProvider;
+
+public class SessionWorkspaceProviderTest extends AbstractFileSystemTestCase
+{
+
+    @Test
+    public void testGetSessionWorkspace() throws Exception
+    {
+        Properties properties = new Properties();
+        properties.setProperty(SessionWorkspaceProvider.SESSION_WORKSPACE_ROOT_DIR_KEY, workingDirectory.getPath());
+
+        SessionWorkspaceProvider provider = new SessionWorkspaceProvider(properties);
+        provider.init();
+
+        Map<String, File> sessionWorkspaces = provider.getSessionWorkspaces();
+        assertEquals("[]", sessionWorkspaces.keySet().toString());
+
+        File sessionWorkspace = provider.getSessionWorkspace("token");
+
+        sessionWorkspaces = provider.getSessionWorkspaces();
+        assertEquals("[token]", sessionWorkspaces.keySet().toString());
+
+        assertEquals(true, sessionWorkspace.exists());
+        assertEquals(workingDirectory, sessionWorkspace.getParentFile());
+    }
+
+    @Test
+    public void testGetSessionWorkspaces() throws Exception
+    {
+        Properties properties = new Properties();
+        properties.setProperty(SessionWorkspaceProvider.SESSION_WORKSPACE_ROOT_DIR_KEY, workingDirectory.getPath());
+
+        SessionWorkspaceProvider provider = new SessionWorkspaceProvider(properties);
+        provider.init();
+
+        Map<String, File> sessionWorkspaces = provider.getSessionWorkspaces();
+        assertEquals("[]", sessionWorkspaces.keySet().toString());
+
+        provider.getSessionWorkspace("token1");
+        provider.getSessionWorkspace("token2");
+
+        sessionWorkspaces = provider.getSessionWorkspaces();
+        assertEquals("[token1, token2]", sessionWorkspaces.keySet().toString());
+
+        provider.deleteSessionWorkspace("token1");
+
+        sessionWorkspaces = provider.getSessionWorkspaces();
+        assertEquals("[token2]", sessionWorkspaces.keySet().toString());
+    }
+
+    @Test
+    public void testDeleteSessionWorkspace() throws Exception
+    {
+        Properties properties = new Properties();
+        properties.setProperty(SessionWorkspaceProvider.SESSION_WORKSPACE_ROOT_DIR_KEY, workingDirectory.getPath());
+
+        SessionWorkspaceProvider provider = new SessionWorkspaceProvider(properties);
+        provider.init();
+
+        File workspace1 = provider.getSessionWorkspace("token1");
+        File workspace2 = provider.getSessionWorkspace("token2");
+
+        FileUtils.writeStringToFile(new File(workspace1, "file1A"), "1A");
+        FileUtils.writeStringToFile(new File(workspace1, "file1B"), "1B");
+        FileUtils.writeStringToFile(new File(workspace2, "file2"), "2");
+
+        Map<String, File> sessionWorkspaces = provider.getSessionWorkspaces();
+        assertEquals("[token1, token2]", sessionWorkspaces.keySet().toString());
+
+        provider.deleteSessionWorkspace("token1");
+
+        sessionWorkspaces = provider.getSessionWorkspaces();
+        assertEquals("[token2]", sessionWorkspaces.keySet().toString());
+
+        provider.deleteSessionWorkspace("token2");
+
+        sessionWorkspaces = provider.getSessionWorkspaces();
+        assertEquals("[]", sessionWorkspaces.keySet().toString());
+
+        provider.deleteSessionWorkspace("token3");
+
+        sessionWorkspaces = provider.getSessionWorkspaces();
+        assertEquals("[]", sessionWorkspaces.keySet().toString());
+    }
+
+}
diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/plugin/generic/client/web/server/GenericClientServiceTest.java b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/plugin/generic/client/web/server/GenericClientServiceTest.java
index 353a2e618a25c4121c5833e61815ecf7c4fdcd40..2edd89dc413ba15bbf179fe38766bf15f5967e3c 100644
--- a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/plugin/generic/client/web/server/GenericClientServiceTest.java
+++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/plugin/generic/client/web/server/GenericClientServiceTest.java
@@ -41,6 +41,7 @@ import ch.systemsx.cisd.common.filesystem.FileUtilities;
 import ch.systemsx.cisd.openbis.generic.client.web.server.AbstractClientServiceTest;
 import ch.systemsx.cisd.openbis.generic.client.web.server.UploadedFilesBean;
 import ch.systemsx.cisd.openbis.generic.shared.CommonTestUtils;
+import ch.systemsx.cisd.openbis.generic.shared.ISessionWorkspaceProvider;
 import ch.systemsx.cisd.openbis.generic.shared.basic.TechId;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.AbstractExternalData;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.BatchRegistrationResult;
@@ -66,7 +67,6 @@ import ch.systemsx.cisd.openbis.generic.shared.basic.dto.UpdatedBasicExperiment;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.UpdatedExperimentsWithType;
 import ch.systemsx.cisd.openbis.generic.shared.dto.MaterialTypePE;
 import ch.systemsx.cisd.openbis.generic.shared.translator.MaterialTypeTranslator;
-import ch.systemsx.cisd.openbis.plugin.generic.shared.IGenericServer;
 import ch.systemsx.cisd.openbis.plugin.generic.shared.IGenericServerInternal;
 
 /**
@@ -86,6 +86,8 @@ public final class GenericClientServiceTest extends AbstractClientServiceTest
 
     private GenericClientService genericClientService;
 
+    private ISessionWorkspaceProvider sessionWorkspaceProvider;
+
     private final static NewSample createNewSample(final String sampleIdentifier,
             final String sampleTypeCode, final IEntityProperty[] properties)
     {
@@ -133,6 +135,7 @@ public final class GenericClientServiceTest extends AbstractClientServiceTest
         super.setUp();
         genericServer = context.mock(IGenericServerInternal.class);
         multipartFile = context.mock(MultipartFile.class);
+        sessionWorkspaceProvider = context.mock(ISessionWorkspaceProvider.class);
         genericClientService = new GenericClientService(genericServer, requestContextProvider);
     }
 
@@ -272,8 +275,7 @@ public final class GenericClientServiceTest extends AbstractClientServiceTest
         newSample.setIdentifier("MP1");
         newSample.setContainerIdentifier("MP2");
         newSample.setParentIdentifier("MP3");
-        newSample.setProperties(new IEntityProperty[]
-        { createSampleProperty("prop1", "RED"), createSampleProperty("prop2", "1") });
+        newSample.setProperties(new IEntityProperty[] { createSampleProperty("prop1", "RED"), createSampleProperty("prop2", "1") });
         final SampleType sampleType = createSampleType("MASTER_PLATE");
         final String fileName = "originalFileName.txt";
 
@@ -287,12 +289,15 @@ public final class GenericClientServiceTest extends AbstractClientServiceTest
                     prepareGetHttpSession(this);
                     prepareGetSessionToken(this);
 
+                    allowing(sessionWorkspaceProvider).getSessionWorkspace(SESSION_TOKEN);
+                    will(returnValue(getSessionWorkspaceDir()));
+
                     allowing(httpSession).getAttribute(sessionKey);
                     will(returnValue(uploadedFilesBean));
 
                     allowing(httpSession).removeAttribute(sessionKey);
 
-                    exactly(1).of(multipartFile).getOriginalFilename();
+                    allowing(multipartFile).getOriginalFilename();
                     will(returnValue(fileName));
 
                     one(multipartFile).transferTo(with(any(File.class)));
@@ -315,8 +320,7 @@ public final class GenericClientServiceTest extends AbstractClientServiceTest
                         {
 
                             @Override
-                            @SuppressWarnings(
-                            { "unchecked" })
+                            @SuppressWarnings({ "unchecked" })
                             public Object invoke(Invocation invocation) throws Throwable
                             {
                                 final List<NewSamplesWithTypes> samplesSecions =
@@ -342,7 +346,7 @@ public final class GenericClientServiceTest extends AbstractClientServiceTest
                         });
                 }
             });
-        uploadedFilesBean.addMultipartFile(multipartFile);
+        uploadedFilesBean.addMultipartFile(SESSION_TOKEN, multipartFile, sessionWorkspaceProvider);
         final List<BatchRegistrationResult> result =
                 genericClientService.registerSamples(sampleType, sessionKey, false, null, null, false);
         assertEquals(1, result.size());
@@ -360,8 +364,7 @@ public final class GenericClientServiceTest extends AbstractClientServiceTest
         final String sessionKey = "uploaded-files";
         final NewSample newSample = new NewSample();
         newSample.setIdentifier("MP");
-        newSample.setParentsOrNull(new String[]
-        { "MP_1", "MP_2" });
+        newSample.setParentsOrNull(new String[] { "MP_1", "MP_2" });
         newSample.setProperties(new IEntityProperty[0]);
         final SampleType sampleType = createSampleType("MASTER_PLATE");
         final String fileName = "originalFileName.txt";
@@ -375,13 +378,16 @@ public final class GenericClientServiceTest extends AbstractClientServiceTest
                 {
                     prepareGetHttpSession(this);
                     prepareGetSessionToken(this);
+                    
+                    allowing(sessionWorkspaceProvider).getSessionWorkspace(SESSION_TOKEN);
+                    will(returnValue(getSessionWorkspaceDir()));
 
                     allowing(httpSession).getAttribute(sessionKey);
                     will(returnValue(uploadedFilesBean));
 
                     allowing(httpSession).removeAttribute(sessionKey);
 
-                    exactly(1).of(multipartFile).getOriginalFilename();
+                    allowing(multipartFile).getOriginalFilename();
                     will(returnValue(fileName));
 
                     one(multipartFile).transferTo(with(any(File.class)));
@@ -403,8 +409,7 @@ public final class GenericClientServiceTest extends AbstractClientServiceTest
                         {
 
                             @Override
-                            @SuppressWarnings(
-                            { "unchecked" })
+                            @SuppressWarnings({ "unchecked" })
                             public Object invoke(Invocation invocation) throws Throwable
                             {
                                 final List<NewSamplesWithTypes> samplesSecions =
@@ -427,7 +432,7 @@ public final class GenericClientServiceTest extends AbstractClientServiceTest
                         });
                 }
             });
-        uploadedFilesBean.addMultipartFile(multipartFile);
+        uploadedFilesBean.addMultipartFile(SESSION_TOKEN, multipartFile, sessionWorkspaceProvider);
         final List<BatchRegistrationResult> result =
                 genericClientService.registerSamples(sampleType, sessionKey, false, null, null, false);
         assertEquals(1, result.size());
@@ -453,13 +458,16 @@ public final class GenericClientServiceTest extends AbstractClientServiceTest
                 {
                     prepareGetHttpSession(this);
                     prepareGetSessionToken(this);
+                    
+                    allowing(sessionWorkspaceProvider).getSessionWorkspace(SESSION_TOKEN);
+                    will(returnValue(getSessionWorkspaceDir()));
 
                     allowing(httpSession).getAttribute(sessionKey);
                     will(returnValue(uploadedFilesBean));
 
                     allowing(httpSession).removeAttribute(sessionKey);
 
-                    exactly(1).of(multipartFile).getOriginalFilename();
+                    allowing(multipartFile).getOriginalFilename();
                     will(returnValue(fileName));
 
                     one(multipartFile).transferTo(with(any(File.class)));
@@ -483,8 +491,7 @@ public final class GenericClientServiceTest extends AbstractClientServiceTest
                         {
 
                             @Override
-                            @SuppressWarnings(
-                            { "unchecked", "deprecation" })
+                            @SuppressWarnings({ "unchecked", "deprecation" })
                             public Object invoke(Invocation invocation) throws Throwable
                             {
                                 final List<NewSamplesWithTypes> samplesSecions =
@@ -517,7 +524,7 @@ public final class GenericClientServiceTest extends AbstractClientServiceTest
                         });
                 }
             });
-        uploadedFilesBean.addMultipartFile(multipartFile);
+        uploadedFilesBean.addMultipartFile(SESSION_TOKEN, multipartFile, sessionWorkspaceProvider);
         final List<BatchRegistrationResult> result =
                 genericClientService.updateSamples(sampleType, sessionKey, false, null, defaultGroupIdentifier);
         assertEquals(1, result.size());
@@ -542,13 +549,16 @@ public final class GenericClientServiceTest extends AbstractClientServiceTest
                 {
                     prepareGetHttpSession(this);
                     prepareGetSessionToken(this);
+                    
+                    allowing(sessionWorkspaceProvider).getSessionWorkspace(SESSION_TOKEN);
+                    will(returnValue(getSessionWorkspaceDir()));
 
                     allowing(httpSession).getAttribute(sessionKey);
                     will(returnValue(uploadedFilesBean));
 
                     allowing(httpSession).removeAttribute(sessionKey);
 
-                    exactly(1).of(multipartFile).getOriginalFilename();
+                    allowing(multipartFile).getOriginalFilename();
                     will(returnValue(fileName));
 
                     one(multipartFile).transferTo(with(any(File.class)));
@@ -596,7 +606,7 @@ public final class GenericClientServiceTest extends AbstractClientServiceTest
                         });
                 }
             });
-        uploadedFilesBean.addMultipartFile(multipartFile);
+        uploadedFilesBean.addMultipartFile(SESSION_TOKEN, multipartFile, sessionWorkspaceProvider);
         final List<BatchRegistrationResult> result =
                 genericClientService.updateSamples(sampleType, sessionKey, false, null, defaultGroupIdentifier);
         assertEquals(1, result.size());
@@ -672,11 +682,14 @@ public final class GenericClientServiceTest extends AbstractClientServiceTest
             {
                 {
                     prepareGetSessionToken(this);
+                    
+                    allowing(sessionWorkspaceProvider).getSessionWorkspace(SESSION_TOKEN);
+                    will(returnValue(getSessionWorkspaceDir()));
 
                     allowing(httpSession).getAttribute(sessionKey);
                     will(returnValue(uploadedFilesBean));
 
-                    one(multipartFile).getOriginalFilename();
+                    allowing(multipartFile).getOriginalFilename();
                     will(returnValue("file name"));
 
                     one(multipartFile).transferTo(with(any(File.class)));
@@ -699,7 +712,7 @@ public final class GenericClientServiceTest extends AbstractClientServiceTest
                     will(returnValue(updateCount));
                 }
             });
-        uploadedFilesBean.addMultipartFile(multipartFile);
+        uploadedFilesBean.addMultipartFile(SESSION_TOKEN, multipartFile, sessionWorkspaceProvider);
     }
 
     @Test
@@ -768,13 +781,16 @@ public final class GenericClientServiceTest extends AbstractClientServiceTest
                 {
                     prepareGetHttpSession(this);
                     prepareGetSessionToken(this);
+                    
+                    allowing(sessionWorkspaceProvider).getSessionWorkspace(SESSION_TOKEN);
+                    will(returnValue(getSessionWorkspaceDir()));
 
                     allowing(httpSession).getAttribute(sessionKey);
                     will(returnValue(uploadedFilesBean));
 
                     allowing(httpSession).removeAttribute(sessionKey);
 
-                    exactly(1).of(multipartFile).getOriginalFilename();
+                    allowing(multipartFile).getOriginalFilename();
                     will(returnValue(fileName));
 
                     one(multipartFile).transferTo(with(any(File.class)));
@@ -825,7 +841,7 @@ public final class GenericClientServiceTest extends AbstractClientServiceTest
                         });
                 }
             });
-        uploadedFilesBean.addMultipartFile(multipartFile);
+        uploadedFilesBean.addMultipartFile(SESSION_TOKEN, multipartFile, sessionWorkspaceProvider);
         final List<BatchRegistrationResult> result =
                 genericClientService.updateExperiments(experimentType, sessionKey, false, null);
         assertEquals(1, result.size());
@@ -849,13 +865,16 @@ public final class GenericClientServiceTest extends AbstractClientServiceTest
                 {
                     prepareGetHttpSession(this);
                     prepareGetSessionToken(this);
+                    
+                    allowing(sessionWorkspaceProvider).getSessionWorkspace(SESSION_TOKEN);
+                    will(returnValue(getSessionWorkspaceDir()));
 
                     allowing(httpSession).getAttribute(sessionKey);
                     will(returnValue(uploadedFilesBean));
 
                     allowing(httpSession).removeAttribute(sessionKey);
 
-                    exactly(1).of(multipartFile).getOriginalFilename();
+                    allowing(multipartFile).getOriginalFilename();
                     will(returnValue(fileName));
 
                     one(multipartFile).transferTo(with(any(File.class)));
@@ -911,7 +930,7 @@ public final class GenericClientServiceTest extends AbstractClientServiceTest
                         });
                 }
             });
-        uploadedFilesBean.addMultipartFile(multipartFile);
+        uploadedFilesBean.addMultipartFile(SESSION_TOKEN, multipartFile, sessionWorkspaceProvider);
         final List<BatchRegistrationResult> result =
                 genericClientService.updateExperiments(experimentType, sessionKey, false, null);
         assertEquals(1, result.size());
@@ -1033,4 +1052,14 @@ public final class GenericClientServiceTest extends AbstractClientServiceTest
             return true;
         }
     }
+
+    private File getSessionWorkspaceDir()
+    {
+        File dir = new File("targets/sessionWorkspace");
+        if (false == dir.exists())
+        {
+            dir.mkdirs();
+        }
+        return dir;
+    }
 }
diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/systemtest/AttachmentUploadTest.java b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/systemtest/AttachmentUploadTest.java
index a6520506bad7a202ec2954b545791b72b7f4e054..14494385d0ce07a21b0fbe68abba4e069596840e 100644
--- a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/systemtest/AttachmentUploadTest.java
+++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/systemtest/AttachmentUploadTest.java
@@ -54,7 +54,7 @@ public class AttachmentUploadTest extends SystemTestCase
         Project project = projects.getResultSet().getList().extractOriginalObjects().get(0).getObjectOrNull();
         TechId projectID = TechId.create(project);
 
-        uploadFile(FILE_NAME, FILE_CONTENT);
+        uploadFile(sessionContext.getSessionID(), FILE_NAME, FILE_CONTENT);
         commonClientService.addAttachment(projectID, SESSION_KEY, AttachmentHolderKind.PROJECT,
                 new NewAttachment(FILE_NAME, "my file", "example file"));
 
@@ -70,7 +70,7 @@ public class AttachmentUploadTest extends SystemTestCase
         SessionContext sessionContext = logIntoCommonClientService();
         TechId experimentID = new TechId(2);
 
-        uploadFile(FILE_NAME, FILE_CONTENT);
+        uploadFile(sessionContext.getSessionID(), FILE_NAME, FILE_CONTENT);
         commonClientService.addAttachment(experimentID, SESSION_KEY, AttachmentHolderKind.EXPERIMENT,
                 new NewAttachment(FILE_NAME, "my file", "example file"));
 
@@ -86,7 +86,7 @@ public class AttachmentUploadTest extends SystemTestCase
         SessionContext sessionContext = logIntoCommonClientService();
         TechId sampleID = new TechId(1);
 
-        uploadFile(FILE_NAME, FILE_CONTENT);
+        uploadFile(sessionContext.getSessionID(), FILE_NAME, FILE_CONTENT);
         commonClientService.addAttachment(sampleID, SESSION_KEY, AttachmentHolderKind.SAMPLE,
                 new NewAttachment(FILE_NAME, "my file", "example file"));
 
diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/systemtest/BatchMaterialRegistrationAndUpdateTest.java b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/systemtest/BatchMaterialRegistrationAndUpdateTest.java
index ebe26ec25ff0c10683e47ad4aa0cd512a5cc6c7b..aef439063117011566a41d734ee26ff208fb3b5a 100644
--- a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/systemtest/BatchMaterialRegistrationAndUpdateTest.java
+++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/systemtest/BatchMaterialRegistrationAndUpdateTest.java
@@ -36,6 +36,7 @@ import ch.systemsx.cisd.openbis.generic.client.web.client.application.util.lang.
 import ch.systemsx.cisd.openbis.generic.client.web.client.dto.DisplayedOrSelectedIdHolderCriteria;
 import ch.systemsx.cisd.openbis.generic.client.web.client.dto.GridRowModels;
 import ch.systemsx.cisd.openbis.generic.client.web.client.dto.ListMaterialDisplayCriteria;
+import ch.systemsx.cisd.openbis.generic.client.web.client.dto.SessionContext;
 import ch.systemsx.cisd.openbis.generic.client.web.client.dto.TypedTableResultSet;
 import ch.systemsx.cisd.openbis.generic.client.web.client.exception.UserFailureException;
 import ch.systemsx.cisd.openbis.generic.shared.basic.GridRowModel;
@@ -73,7 +74,7 @@ public class BatchMaterialRegistrationAndUpdateTest extends SystemTestCase
     @Test(groups = "slow")
     public void testBatchRegistrationWithManagedProperty()
     {
-        logIntoCommonClientService().getSessionID();
+        SessionContext session = logIntoCommonClientService();
         deleteTestMaterials();
         Script script = new Script();
         script.setScriptType(ScriptType.MANAGED_PROPERTY);
@@ -96,7 +97,7 @@ public class BatchMaterialRegistrationAndUpdateTest extends SystemTestCase
                 "code\tdescription\tsize\tcomment:a\tcomment:b\n" + "c1\tcompound 1\t42\tx\ty\n"
                         + "c2\tcompound 2\t43\ta\tb";
 
-        List<BatchRegistrationResult> result = registerMaterials(materialBatchData, MATERIAL_TYPE);
+        List<BatchRegistrationResult> result = registerMaterials(session.getSessionID(), materialBatchData, MATERIAL_TYPE);
 
         assertEquals("Registration/update of 2 material(s) is complete.", result.get(0)
                 .getMessage());
@@ -109,7 +110,7 @@ public class BatchMaterialRegistrationAndUpdateTest extends SystemTestCase
     @Test(groups = "slow")
     public void testUpdateOfPropertiesOfVariousTypes()
     {
-        logIntoCommonClientService();
+        SessionContext session = logIntoCommonClientService();
         deleteTestMaterials();
 
         NewETPTAssignment assignment = new NewETPTAssignment();
@@ -124,12 +125,12 @@ public class BatchMaterialRegistrationAndUpdateTest extends SystemTestCase
                 "code\tdescription\tsize\tgender\tbacterium\n"
                         + "c1\tcompound 1\t42\tfemale\tbacterium1\n"
                         + "c2\tcompound 2\t43\tmale\tbacterium-x";
-        registerMaterials(materialBatchData, MATERIAL_TYPE);
+        registerMaterials(session.getSessionID(), materialBatchData, MATERIAL_TYPE);
 
         long timeBeforeUpdate = System.currentTimeMillis();
 
         List<BatchRegistrationResult> result =
-                updateMaterials("code\tdescription\tgender\tbacterium\n"
+                updateMaterials(session.getSessionID(), "code\tdescription\tgender\tbacterium\n"
                         + "c1\tnew description\tmale\tbacterium2\n" + "c2\t\tmale\tbacterium-y",
                         MATERIAL_TYPE, false);
 
@@ -157,7 +158,7 @@ public class BatchMaterialRegistrationAndUpdateTest extends SystemTestCase
         assertEquals("[BACTERIUM: material:BACTERIUM-X [BACTERIUM]<a:2>]",
                 getMaterialPropertiesHistory(getMaterialOrNull("C2").getId()).toString());
 
-        updateMaterials("code\tdescription\tgender\tbacterium\n"
+        updateMaterials(session.getSessionID(), "code\tdescription\tgender\tbacterium\n"
                 + "c2\t--DELETE--\tfemale\tbacterium2\n", MATERIAL_TYPE, false);
 
         assertEquals(
@@ -169,14 +170,14 @@ public class BatchMaterialRegistrationAndUpdateTest extends SystemTestCase
     @Test
     public void testUpdateIgnoreUnregistered()
     {
-        logIntoCommonClientService();
+        SessionContext session = logIntoCommonClientService();
         deleteTestMaterials();
         String materialBatchData =
                 "code\tdescription\tsize\n" + "c1\tcompound 1\t42\n" + "c2\tcompound 2\t43";
-        registerMaterials(materialBatchData, MATERIAL_TYPE);
+        registerMaterials(session.getSessionID(), materialBatchData, MATERIAL_TYPE);
 
         List<BatchRegistrationResult> result =
-                updateMaterials("code\tdescription\tsize\n" + "c1\tcompound one\t\n"
+                updateMaterials(session.getSessionID(), "code\tdescription\tsize\n" + "c1\tcompound one\t\n"
                         + "c2\tcompound two\t4711\n" + "c3\t3\t\n", MATERIAL_TYPE, true);
 
         assertEquals("2 material(s) updated, 1 ignored.", result.get(0).getMessage());
@@ -259,20 +260,20 @@ public class BatchMaterialRegistrationAndUpdateTest extends SystemTestCase
         }
     }
 
-    private List<BatchRegistrationResult> registerMaterials(String materialBatchData,
+    private List<BatchRegistrationResult> registerMaterials(String sessionToken, String materialBatchData,
             String materialTypeCode)
     {
-        uploadFile("my-file", materialBatchData);
+        uploadFile(sessionToken, "my-file", materialBatchData);
         MaterialType materialType = new MaterialType();
         materialType.setCode(materialTypeCode);
         return genericClientService
                 .registerMaterials(materialType, false, SESSION_KEY, false, null);
     }
 
-    private List<BatchRegistrationResult> updateMaterials(String materialBatchData,
+    private List<BatchRegistrationResult> updateMaterials(String sessionToken, String materialBatchData,
             String materialTypeCode, boolean ignoreUnregistered)
     {
-        uploadFile("my-file", materialBatchData);
+        uploadFile(sessionToken, "my-file", materialBatchData);
         MaterialType materialType = new MaterialType();
         materialType.setCode(materialTypeCode);
         return genericClientService.updateMaterials(materialType, SESSION_KEY, ignoreUnregistered,
diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/systemtest/CodeGenerationTest.java b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/systemtest/CodeGenerationTest.java
index 95fb668d1f06cf59f34e42e6cf24986fc6aacb00..3bafe97a7f7b21c1b77f3495c53e1e8b845692d0 100644
--- a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/systemtest/CodeGenerationTest.java
+++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/systemtest/CodeGenerationTest.java
@@ -155,7 +155,7 @@ public class CodeGenerationTest extends SystemTestCase
         setSampleCodeSequence(10);
 
         String sessionID = logIntoCommonClientService().getSessionID();
-        uploadFile("testAutomaticCreationOfSampleCodesInBatchSampleRegistration.txt",
+        uploadFile(sessionID, "testAutomaticCreationOfSampleCodesInBatchSampleRegistration.txt",
                 "experiment\tCOMMENT\n" + "/CISD/NEMO/EXP1\tA\n" + "/CISD/NEMO/EXP1\tB\n");
         SampleType sampleType = new SampleType();
         sampleType.setGeneratedCodePrefix("A");
diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/systemtest/PersistentSystemTestCase.java b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/systemtest/PersistentSystemTestCase.java
index 0601e65dc652c56efbc3d56a0a2cc716ab517e54..5b4a5fa56010cfc21b822c7dfb5a907f1e2ea016 100644
--- a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/systemtest/PersistentSystemTestCase.java
+++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/systemtest/PersistentSystemTestCase.java
@@ -16,11 +16,8 @@
 
 package ch.systemsx.cisd.openbis.systemtest;
 
-import javax.servlet.http.HttpSession;
-
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.mock.web.MockHttpServletRequest;
-import org.springframework.mock.web.MockMultipartFile;
 import org.springframework.test.context.ContextConfiguration;
 import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
 import org.springframework.transaction.annotation.Transactional;
@@ -31,7 +28,6 @@ import org.testng.annotations.BeforeSuite;
 import ch.systemsx.cisd.common.servlet.SpringRequestContextProvider;
 import ch.systemsx.cisd.openbis.generic.client.web.client.ICommonClientService;
 import ch.systemsx.cisd.openbis.generic.client.web.client.dto.SessionContext;
-import ch.systemsx.cisd.openbis.generic.client.web.server.UploadedFilesBean;
 import ch.systemsx.cisd.openbis.generic.server.ICommonServerForInternalUse;
 import ch.systemsx.cisd.openbis.generic.server.util.TestInitializer;
 import ch.systemsx.cisd.openbis.generic.shared.IServiceForDataStoreServer;
@@ -201,13 +197,4 @@ public abstract class PersistentSystemTestCase extends AbstractTestNGSpringConte
         return commonServer.tryAuthenticate(user, "password").getSessionToken();
     }
 
-    protected void uploadFile(String fileName, String fileContent)
-    {
-        UploadedFilesBean bean = new UploadedFilesBean();
-        bean.addMultipartFile(new MockMultipartFile(fileName, fileName, null, fileContent
-                .getBytes()));
-        HttpSession session = request.getSession();
-        session.setAttribute(SESSION_KEY, bean);
-    }
-
 }
diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/systemtest/SystemTestCase.java b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/systemtest/SystemTestCase.java
index 135f56f85ab26cf9175d166c1d42c79c83ea000a..1f4800368191a8af689941b4cea0910d700a1b67 100644
--- a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/systemtest/SystemTestCase.java
+++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/systemtest/SystemTestCase.java
@@ -50,6 +50,7 @@ import ch.systemsx.cisd.openbis.generic.server.dataaccess.IDAOFactory;
 import ch.systemsx.cisd.openbis.generic.server.util.TestInitializer;
 import ch.systemsx.cisd.openbis.generic.shared.Constants;
 import ch.systemsx.cisd.openbis.generic.shared.IServiceForDataStoreServer;
+import ch.systemsx.cisd.openbis.generic.shared.ISessionWorkspaceProvider;
 import ch.systemsx.cisd.openbis.generic.shared.basic.GridRowModel;
 import ch.systemsx.cisd.openbis.generic.shared.basic.IIdentifierHolder;
 import ch.systemsx.cisd.openbis.generic.shared.basic.TechId;
@@ -135,6 +136,9 @@ public abstract class SystemTestCase extends AbstractTransactionalTestNGSpringCo
 
     protected String systemSessionToken;
 
+    @Autowired
+    protected ISessionWorkspaceProvider sessionWorkspaceProvider;
+
     @BeforeSuite
     public void beforeSuite()
     {
@@ -371,11 +375,11 @@ public abstract class SystemTestCase extends AbstractTransactionalTestNGSpringCo
         return new NewSampleBuilder(identifier);
     }
 
-    protected void uploadFile(String fileName, String fileContent)
+    protected void uploadFile(String sessionToken, String fileName, String fileContent)
     {
         UploadedFilesBean bean = new UploadedFilesBean();
-        bean.addMultipartFile(new MockMultipartFile(fileName, fileName, null, fileContent
-                .getBytes()));
+        bean.addMultipartFile(sessionToken, new MockMultipartFile(fileName, fileName, null, fileContent
+                .getBytes()), sessionWorkspaceProvider);
         HttpSession session = request.getSession();
         session.setAttribute(SESSION_KEY, bean);
     }
diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/systemtest/api/v1/GeneralInformationChangingServiceTest.java b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/systemtest/api/v1/GeneralInformationChangingServiceTest.java
index d06610e91943c90bba173a62cab38b53c0b8eb88..246e6529c892a2118d59aecef8a73986b2b979fd 100644
--- a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/systemtest/api/v1/GeneralInformationChangingServiceTest.java
+++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/systemtest/api/v1/GeneralInformationChangingServiceTest.java
@@ -834,7 +834,7 @@ public class GeneralInformationChangingServiceTest extends SystemTestCase
 
         String sampleType = "CELL_PLATE";
 
-        uploadFile("testRegisterSamplesWithProjectAuthorization.txt",
+        uploadFile(session.getSessionID(), "testRegisterSamplesWithProjectAuthorization.txt",
                 "identifier\texperiment\tCOMMENT\n"
                         + "/TEST-SPACE/PA_UPLOAD\t/TEST-SPACE/TEST-PROJECT/EXP-SPACE-TEST\ttest comment\n");
 
@@ -871,7 +871,7 @@ public class GeneralInformationChangingServiceTest extends SystemTestCase
 
         String sampleType = "CELL_PLATE";
 
-        uploadFile("testUpdateSamplesWithProjectAuthorization.txt",
+        uploadFile(session.getSessionID(), "testUpdateSamplesWithProjectAuthorization.txt",
                 "identifier\tCOMMENT\n"
                         + "/TEST-SPACE/FV-TEST\tupdated comment\n");
 
@@ -908,7 +908,7 @@ public class GeneralInformationChangingServiceTest extends SystemTestCase
 
         String sampleType = "CELL_PLATE";
 
-        uploadFile("testUploadedSamplesInfoWithProjectAuthorization.txt",
+        uploadFile(session.getSessionID(), "testUploadedSamplesInfoWithProjectAuthorization.txt",
                 "identifier\texperiment\tCOMMENT\n"
                         + "/TEST-SPACE/PA_UPLOAD\t/TEST-SPACE/TEST-PROJECT/EXP-SPACE-TEST\ttest comment\n");
 
diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/systemtest/plugin/generic/ExperimentRegistrationTest.java b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/systemtest/plugin/generic/ExperimentRegistrationTest.java
index 2ef60a6778b5962d4c9ef38a741d11193885182a..ddedfc5255e04a669e319b939d5975eb5989cf10 100644
--- a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/systemtest/plugin/generic/ExperimentRegistrationTest.java
+++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/systemtest/plugin/generic/ExperimentRegistrationTest.java
@@ -32,6 +32,7 @@ import org.testng.annotations.Test;
 import ch.systemsx.cisd.common.string.UnicodeUtils;
 import ch.systemsx.cisd.openbis.generic.client.web.client.dto.ListSampleDisplayCriteria;
 import ch.systemsx.cisd.openbis.generic.client.web.client.dto.ResultSetWithEntityTypes;
+import ch.systemsx.cisd.openbis.generic.client.web.client.dto.SessionContext;
 import ch.systemsx.cisd.openbis.generic.client.web.client.exception.UserFailureException;
 import ch.systemsx.cisd.openbis.generic.shared.basic.GridRowModel;
 import ch.systemsx.cisd.openbis.generic.shared.basic.TechId;
@@ -89,8 +90,7 @@ public class ExperimentRegistrationTest extends GenericSystemTestCase
         String experimentCode = commonClientService.generateCode("EXP", EntityKind.EXPERIMENT);
         String experimentIdentifier = "/cisd/default/" + experimentCode;
         NewExperiment newExperiment = new NewExperiment(experimentIdentifier, "SIRNA_HCS");
-        newExperiment.setProperties(new IEntityProperty[]
-        { property("DESCRIPTION", "my éxpériment") });
+        newExperiment.setProperties(new IEntityProperty[] { property("DESCRIPTION", "my éxpériment") });
         genericClientService.registerExperiment(ATTACHMENTS_SESSION_KEY, SAMPLES_SESSION_KEY,
                 newExperiment);
 
@@ -113,10 +113,8 @@ public class ExperimentRegistrationTest extends GenericSystemTestCase
         String experimentCode = commonClientService.generateCode("EXP", EntityKind.EXPERIMENT);
         String experimentIdentifier = "/cisd/default/" + experimentCode;
         NewExperiment newExperiment = new NewExperiment(experimentIdentifier, "SIRNA_HCS");
-        newExperiment.setProperties(new IEntityProperty[]
-        { property("DESCRIPTION", "my experiment") });
-        newExperiment.setSamples(new String[]
-        { "3vcp8" });
+        newExperiment.setProperties(new IEntityProperty[] { property("DESCRIPTION", "my experiment") });
+        newExperiment.setSamples(new String[] { "3vcp8" });
         genericClientService.registerExperiment(ATTACHMENTS_SESSION_KEY, SAMPLES_SESSION_KEY,
                 newExperiment);
 
@@ -136,15 +134,14 @@ public class ExperimentRegistrationTest extends GenericSystemTestCase
     @Test
     public void testRegisterExperimentAndSamples()
     {
-        logIntoCommonClientService();
+        SessionContext session = logIntoCommonClientService();
 
         String batchSamplesFileContent = "identifier\torganism\n" + "S1001\tfly\n" + "S1002\tdog\n";
-        addMultiPartFile(SAMPLES_SESSION_KEY, "samples.txt", batchSamplesFileContent.getBytes());
+        addMultiPartFile(session.getSessionID(), SAMPLES_SESSION_KEY, "samples.txt", batchSamplesFileContent.getBytes());
         String experimentCode = commonClientService.generateCode("EXP", EntityKind.EXPERIMENT);
         String experimentIdentifier = "/cisd/default/" + experimentCode;
         NewExperiment newExperiment = new NewExperiment(experimentIdentifier, "SIRNA_HCS");
-        newExperiment.setProperties(new IEntityProperty[]
-        { property("DESCRIPTION", "my experiment") });
+        newExperiment.setProperties(new IEntityProperty[] { property("DESCRIPTION", "my experiment") });
         newExperiment.setRegisterSamples(true);
         SampleType sampleType = new SampleType();
         sampleType.setCode("CELL_PLATE");
@@ -186,12 +183,11 @@ public class ExperimentRegistrationTest extends GenericSystemTestCase
     {
         String sessionToken = logIntoCommonClientService().getSessionID();
 
-        addMultiPartFile(ATTACHMENTS_SESSION_KEY, "hello.txt", "hello world".getBytes());
+        addMultiPartFile(sessionToken, ATTACHMENTS_SESSION_KEY, "hello.txt", "hello world".getBytes());
         String experimentCode = commonClientService.generateCode("EXP", EntityKind.EXPERIMENT);
         String experimentIdentifier = "/cisd/default/" + experimentCode;
         NewExperiment newExperiment = new NewExperiment(experimentIdentifier, "SIRNA_HCS");
-        newExperiment.setProperties(new IEntityProperty[]
-        { property("DESCRIPTION", "my experiment") });
+        newExperiment.setProperties(new IEntityProperty[] { property("DESCRIPTION", "my experiment") });
         newExperiment.setAttachments(Arrays.asList(new NewAttachment("hello.txt", "hello",
                 "test attachment")));
         genericClientService.registerExperiment(ATTACHMENTS_SESSION_KEY, SAMPLES_SESSION_KEY,
@@ -220,19 +216,17 @@ public class ExperimentRegistrationTest extends GenericSystemTestCase
     @Test(groups = "slow")
     public void testBulkUpdateExperiments() throws UnsupportedEncodingException
     {
-        logIntoCommonClientService();
+        SessionContext session = logIntoCommonClientService();
 
         int expCount = 10;
         // Create some experiments to update
         ArrayList<String> expIds = registerNewExperiments(expCount);
-        String[] codes = new String[]
-        { "DESCRIPTION" };
-        String[] values = new String[]
-        { "New déscription" };
+        String[] codes = new String[] { "DESCRIPTION" };
+        String[] values = new String[] { "New déscription" };
         String bulkUpdateString = createBulkUpdateString(expIds, codes, values);
 
         // Update the experiments
-        addMultiPartFile(EXPERIMENTS_SESSION_KEY, "experiments.txt",
+        addMultiPartFile(session.getSessionID(), EXPERIMENTS_SESSION_KEY, "experiments.txt",
                 bulkUpdateString.getBytes(UnicodeUtils.DEFAULT_UNICODE_CHARSET));
         ExperimentType experimentType = new ExperimentType();
         experimentType.setCode("SIRNA_HCS");
@@ -251,19 +245,17 @@ public class ExperimentRegistrationTest extends GenericSystemTestCase
     @Test
     public void testBulkUpdateExperimentsWithProjectChanges() throws UnsupportedEncodingException
     {
-        logIntoCommonClientService();
+        SessionContext session = logIntoCommonClientService();
 
         int expCount = 10;
         // Create some experiments to update
         ArrayList<String> expIds = registerNewExperiments(expCount);
-        String[] codes = new String[]
-        { "DESCRIPTION" };
-        String[] values = new String[]
-        { "New déscription" };
+        String[] codes = new String[] { "DESCRIPTION" };
+        String[] values = new String[] { "New déscription" };
         String bulkUpdateString = createBulkUpdateString(expIds, "/cisd/nemo", codes, values);
 
         // Update the experiments
-        addMultiPartFile(EXPERIMENTS_SESSION_KEY, "experiments.txt",
+        addMultiPartFile(session.getSessionID(), EXPERIMENTS_SESSION_KEY, "experiments.txt",
                 bulkUpdateString.getBytes(UnicodeUtils.DEFAULT_UNICODE_CHARSET));
         ExperimentType experimentType = new ExperimentType();
         experimentType.setCode("SIRNA_HCS");
@@ -287,18 +279,17 @@ public class ExperimentRegistrationTest extends GenericSystemTestCase
     @Test
     public void testBulkUpdateExperimentWithSamplesWithProjectChanges()
     {
-        logIntoCommonClientService();
+        SessionContext session = logIntoCommonClientService();
 
         // Create an experiment with samples
         String batchSamplesFileContent = "identifier\torganism\n" + "S2001\tfly\n" + "S2002\tdog\n";
-        addMultiPartFile(SAMPLES_SESSION_KEY, "samples.txt", batchSamplesFileContent.getBytes());
+        addMultiPartFile(session.getSessionID(), SAMPLES_SESSION_KEY, "samples.txt", batchSamplesFileContent.getBytes());
         String experimentCode =
                 commonClientService.generateCode("EXP-WITH-PROJ", EntityKind.EXPERIMENT);
         String experimentIdentifier = "/cisd/default/" + experimentCode;
         List<String> expIds = Collections.singletonList(experimentIdentifier);
         NewExperiment newExperiment = new NewExperiment(experimentIdentifier, "SIRNA_HCS");
-        newExperiment.setProperties(new IEntityProperty[]
-        { property("DESCRIPTION", "my experiment") });
+        newExperiment.setProperties(new IEntityProperty[] { property("DESCRIPTION", "my experiment") });
         newExperiment.setRegisterSamples(true);
         SampleType sampleType = new SampleType();
         sampleType.setCode("CELL_PLATE");
@@ -313,7 +304,7 @@ public class ExperimentRegistrationTest extends GenericSystemTestCase
                 createBulkUpdateString(expIds, "/testgroup/testproj", codes, values);
 
         // Update the experiments
-        addMultiPartFile(EXPERIMENTS_SESSION_KEY, "experiments.txt", bulkUpdateString.getBytes());
+        addMultiPartFile(session.getSessionID(), EXPERIMENTS_SESSION_KEY, "experiments.txt", bulkUpdateString.getBytes());
         ExperimentType experimentType = new ExperimentType();
         experimentType.setCode("SIRNA_HCS");
         List<BatchRegistrationResult> results =
@@ -355,17 +346,15 @@ public class ExperimentRegistrationTest extends GenericSystemTestCase
     @Test
     public void testBulkUpdateExperimentsDeletingMandatoryProperty()
     {
-        logIntoCommonClientService();
+        SessionContext session = logIntoCommonClientService();
 
         int expCount = 10;
         // Create some experiments to update
         ArrayList<String> expIds = registerNewExperiments(expCount);
-        String bulkUpdateString = createBulkUpdateString(expIds, new String[]
-        { "DESCRIPTION" }, new String[]
-        { "--DELETE--" });
+        String bulkUpdateString = createBulkUpdateString(expIds, new String[] { "DESCRIPTION" }, new String[] { "--DELETE--" });
 
         // Update the experiments
-        addMultiPartFile(EXPERIMENTS_SESSION_KEY, "experiments.txt", bulkUpdateString.getBytes());
+        addMultiPartFile(session.getSessionID(), EXPERIMENTS_SESSION_KEY, "experiments.txt", bulkUpdateString.getBytes());
         ExperimentType experimentType = new ExperimentType();
         experimentType.setCode("SIRNA_HCS");
         try
@@ -383,19 +372,17 @@ public class ExperimentRegistrationTest extends GenericSystemTestCase
     public void testBulkUpdateExperimentsDeletingNonMandatoryProperty()
             throws UnsupportedEncodingException
     {
-        logIntoCommonClientService();
+        SessionContext session = logIntoCommonClientService();
 
         int expCount = 10;
         // Create some experiments to update
         ArrayList<String> expIds = registerNewExperiments(expCount);
-        String[] codes = new String[]
-        { "DESCRIPTION", "GENDER" };
-        String[] values = new String[]
-        { "New déscription", "MALE" };
+        String[] codes = new String[] { "DESCRIPTION", "GENDER" };
+        String[] values = new String[] { "New déscription", "MALE" };
         String bulkUpdateString = createBulkUpdateString(expIds, codes, values);
 
         // Add/Modify some properties
-        addMultiPartFile(EXPERIMENTS_SESSION_KEY, "experiments.txt",
+        addMultiPartFile(session.getSessionID(), EXPERIMENTS_SESSION_KEY, "experiments.txt",
                 bulkUpdateString.getBytes(UnicodeUtils.DEFAULT_UNICODE_CHARSET));
         ExperimentType experimentType = new ExperimentType();
         experimentType.setCode("SIRNA_HCS");
@@ -404,19 +391,15 @@ public class ExperimentRegistrationTest extends GenericSystemTestCase
         verifyBulkUpdate(expIds, codes, values);
 
         // Delete some properties
-        codes = new String[]
-        { "GENDER" };
-        values = new String[]
-        { "__DELETE__" };
+        codes = new String[] { "GENDER" };
+        values = new String[] { "__DELETE__" };
         bulkUpdateString = createBulkUpdateString(expIds, codes, values);
 
-        addMultiPartFile(EXPERIMENTS_SESSION_KEY, "experiments.txt",
+        addMultiPartFile(session.getSessionID(), EXPERIMENTS_SESSION_KEY, "experiments.txt",
                 bulkUpdateString.getBytes(UnicodeUtils.DEFAULT_UNICODE_CHARSET));
         genericClientService.updateExperiments(experimentType, EXPERIMENTS_SESSION_KEY, false, null);
 
-        verifyBulkUpdate(expIds, new String[]
-        { "DESCRIPTION" }, new String[]
-        { "New déscription" });
+        verifyBulkUpdate(expIds, new String[] { "DESCRIPTION" }, new String[] { "New déscription" });
     }
 
     /**
@@ -431,8 +414,7 @@ public class ExperimentRegistrationTest extends GenericSystemTestCase
                     commonClientService.generateCode("BULK-EXP", EntityKind.EXPERIMENT);
             String experimentIdentifier = "/cisd/default/" + experimentCode;
             NewExperiment newExperiment = new NewExperiment(experimentIdentifier, "SIRNA_HCS");
-            newExperiment.setProperties(new IEntityProperty[]
-            { property("DESCRIPTION", "my éxpériment") });
+            newExperiment.setProperties(new IEntityProperty[] { property("DESCRIPTION", "my éxpériment") });
             genericClientService.registerExperiment(ATTACHMENTS_SESSION_KEY, SAMPLES_SESSION_KEY,
                     newExperiment);
             expIds.add(experimentIdentifier);
diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/systemtest/plugin/generic/GenericSystemTestCase.java b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/systemtest/plugin/generic/GenericSystemTestCase.java
index eef627556d4dfdf082db7145059839204af7b123..3b1aa94e4d86061d08231b3ce475bb0c27889ca8 100644
--- a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/systemtest/plugin/generic/GenericSystemTestCase.java
+++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/systemtest/plugin/generic/GenericSystemTestCase.java
@@ -22,6 +22,7 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.mock.web.MockMultipartFile;
 
 import ch.systemsx.cisd.openbis.generic.client.web.server.UploadedFilesBean;
+import ch.systemsx.cisd.openbis.generic.shared.ISessionWorkspaceProvider;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.EntityProperty;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.IEntityProperty;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.PropertyType;
@@ -42,7 +43,10 @@ public class GenericSystemTestCase extends SystemTestCase
     @Autowired
     protected IGenericServer genericServer;
 
-    protected void addMultiPartFile(String sessionAttributeKey, String fileName, byte[] data)
+    @Autowired
+    protected ISessionWorkspaceProvider sessionWorkspaceProvider;
+
+    protected void addMultiPartFile(String sessionToken, String sessionAttributeKey, String fileName, byte[] data)
     {
         HttpSession session = request.getSession();
         UploadedFilesBean uploadedFilesBean =
@@ -53,7 +57,7 @@ public class GenericSystemTestCase extends SystemTestCase
             session.setAttribute(sessionAttributeKey, uploadedFilesBean);
         }
         MockMultipartFile multipartFile = new MockMultipartFile(fileName, fileName, "", data);
-        uploadedFilesBean.addMultipartFile(multipartFile);
+        uploadedFilesBean.addMultipartFile(sessionToken, multipartFile, sessionWorkspaceProvider);
     }
 
     protected IEntityProperty property(String type, String value)
diff --git a/openbis_api/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/query/execute/QueryExecutionOptions.java b/openbis_api/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/query/execute/QueryExecutionOptions.java
index ffc081b0b72ef2681222fb2e4cfc8d58f64a81a6..167e5d46825f86243b99b3c6893e4c92a0a5d3d5 100644
--- a/openbis_api/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/query/execute/QueryExecutionOptions.java
+++ b/openbis_api/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/query/execute/QueryExecutionOptions.java
@@ -52,7 +52,7 @@ public class QueryExecutionOptions implements Serializable
     @Override
     public String toString()
     {
-        return getClass().getSimpleName() + ": parameters=" + parameters;
+        return getClass().getSimpleName() + ": parameterKeys=" + (parameters != null ? parameters.keySet() : "[]");
     }
 
 }
diff --git a/openbis_api/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/query/execute/SqlExecutionOptions.java b/openbis_api/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/query/execute/SqlExecutionOptions.java
index 194b346df97a7d5d69632a0eaa16e5a13523e7bb..670e6047231d4bfcdb06517655224556558a23e9 100644
--- a/openbis_api/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/query/execute/SqlExecutionOptions.java
+++ b/openbis_api/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/query/execute/SqlExecutionOptions.java
@@ -66,7 +66,7 @@ public class SqlExecutionOptions implements Serializable
     @Override
     public String toString()
     {
-        return getClass().getSimpleName() + ": databaseId=" + databaseId + ", parameters=" + parameters;
+        return getClass().getSimpleName() + ": databaseId=" + databaseId + ", parameterKeys=" + (parameters != null ? parameters.keySet() : "[]");
     }
 
 }
diff --git a/openbis_api/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/service/execute/AbstractExecutionOptionsWithParameters.java b/openbis_api/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/service/execute/AbstractExecutionOptionsWithParameters.java
index a3b8efc0c5bb6088f3d3502feb23356afa5a9110..3251822892c7b3c98b272e76564ff533d3bb7490 100644
--- a/openbis_api/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/service/execute/AbstractExecutionOptionsWithParameters.java
+++ b/openbis_api/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/service/execute/AbstractExecutionOptionsWithParameters.java
@@ -47,7 +47,7 @@ public abstract class AbstractExecutionOptionsWithParameters<EO extends Abstract
     @Override
     public String toString()
     {
-        return getClass().getSimpleName() + ": parameterKeys=" + parameters.keySet();
+        return getClass().getSimpleName() + ": parameterKeys=" + (parameters != null ? parameters.keySet() : "[]");
     }
 
 }
diff --git a/openbis_standard_technologies/build.gradle b/openbis_standard_technologies/build.gradle
index 259f97a04f4770f66d1795c6320598f98d43b0de..02faf2b53280bda1eb1e69fe3f2711b5ff007aaf 100644
--- a/openbis_standard_technologies/build.gradle
+++ b/openbis_standard_technologies/build.gradle
@@ -272,9 +272,9 @@ def downloadFile(url, filename) {
 
 task zipCorePlugins(type: Zip) {
   archiveName 'core-plugins.zip'
-  downloadFile('https://github.com/aarpon/obit_flow_core_technology/archive/master.zip', 'flow.zip')
-  downloadFile('https://github.com/aarpon/obit_microscopy_core_technology/archive/master.zip', 'microscopy.zip')
-  downloadFile('https://github.com/aarpon/obit_shared_core_technology/archive/master.zip', 'shared.zip')
+  downloadFile('https://github.com/aarpon/obit_flow_core_technology/archive/release/18.x.zip', 'flow.zip')
+  downloadFile('https://github.com/aarpon/obit_microscopy_core_technology/archive/release/18.x.zip', 'microscopy.zip')
+  downloadFile('https://github.com/aarpon/obit_shared_core_technology/archive/release/18.x.zip', 'shared.zip')
   from project(':rtd_phosphonetx').fileTree(dir: 'source/core-plugins', includes:['proteomics/**', 'proteomics-optional/**'], excludes:['**/dss/**', '**/package-to-dist'])
   from project(':screening').fileTree(dir: 'source/core-plugins', includes:['screening/**', 'screening-optional/**'], excludes:['**/dss/**', '**/package-to-dist'])
   from project(':deep_sequencing_unit').fileTree(dir: 'source/core-plugins', includes:['illumina-ngs/**'], excludes:['**/dss/**', '**/package-to-dist'])