diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/sample/FullSampleIdentifier.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/sample/FullSampleIdentifier.java
new file mode 100644
index 0000000000000000000000000000000000000000..bd76b7ea549a452086ea8ebee0c18c5c69124e8c
--- /dev/null
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/sample/FullSampleIdentifier.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2015 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.ethz.sis.openbis.generic.server.api.v3.executor.sample;
+
+import org.apache.commons.lang.StringUtils;
+
+import ch.ethz.sis.openbis.generic.shared.api.v3.dto.id.sample.SampleIdentifier;
+
+/**
+ * Helper class which parses a sample identifier string.
+ *
+ * @author Franz-Josef Elmer
+ */
+class FullSampleIdentifier
+{
+    private SampleIdentifierParts sampleIdentifierParts;
+    private String sampleCode;
+    
+    FullSampleIdentifier(String sampleIdentifier)
+    {
+        if (StringUtils.isBlank(sampleIdentifier))
+        {
+            throw new IllegalArgumentException("Unspecified sample identifier.");
+        }
+        if (sampleIdentifier.startsWith("/") == false)
+        {
+            throw new IllegalArgumentException("Sample identifier has to start with a '/': " + sampleIdentifier);
+        }
+        String[] parts = sampleIdentifier.split("/");
+        String spaceCode = null;
+        String projectCode = null;
+        if (parts.length > 4)
+        {
+            throw new IllegalArgumentException("Sample identifier can not contain more than three '/': " + sampleIdentifier);
+        } else if (parts.length == 3)
+        {
+            spaceCode = parts[1];
+        } else if (parts.length == 4)
+        {
+            spaceCode = parts[1];
+            projectCode = parts[2];
+        }
+        String code = parts[parts.length - 1];
+        String[] splittedCode = code.split(":");
+        String containerCode = null;
+        String plainSampleCode = code;
+        if (splittedCode.length > 2)
+        {
+            throw new IllegalArgumentException("Sample code can not contain more than one ':': " + sampleIdentifier);
+        } else if (splittedCode.length == 2)
+        {
+            containerCode = splittedCode[0];
+            plainSampleCode = splittedCode[1];
+        }
+        sampleCode = plainSampleCode;
+        sampleIdentifierParts = new SampleIdentifierParts(spaceCode, projectCode, containerCode);
+        
+    }
+
+    SampleIdentifierParts getParts()
+    {
+        return sampleIdentifierParts;
+    }
+
+    String getSampleCode()
+    {
+        return sampleCode;
+    }
+
+    @Override
+    public String toString()
+    {
+        return new SampleIdentifier(sampleIdentifierParts.getSpaceCodeOrNull(), 
+                sampleIdentifierParts.getProjectCodeOrNull(), sampleIdentifierParts.getContainerCodeOrNull(), 
+                sampleCode).toString();
+    }
+    
+}
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/sample/ListSampleTechIdByIdentifier.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/sample/ListSampleTechIdByIdentifier.java
index 730b9dc28e9568caa979adf02097afa66e7dd260..366cf9f11f03d5bd502d6870bac01be42b0fc8ff 100644
--- a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/sample/ListSampleTechIdByIdentifier.java
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/sample/ListSampleTechIdByIdentifier.java
@@ -49,11 +49,11 @@ public class ListSampleTechIdByIdentifier extends AbstractListTechIdById<SampleI
     {
         return SampleIdentifier.class;
     }
-
+    
     @Override
     protected Map<Long, SampleIdentifier> createIdsByTechIdsMap(List<SampleIdentifier> ids)
     {
-        Map<Key, Map<String, SampleIdentifier>> groupedIdentifiers = new HashMap<>();
+        Map<SampleIdentifierParts, Map<String, SampleIdentifier>> groupedIdentifiers = new HashMap<>();
         for (SampleIdentifier sampleIdentifier : ids)
         {
             ch.systemsx.cisd.openbis.generic.shared.dto.identifier.SampleIdentifier sampleIdentifier2 
@@ -75,7 +75,7 @@ public class ListSampleTechIdByIdentifier extends AbstractListTechIdById<SampleI
             }
             String sampleSubCode = CodeConverter.tryToDatabase(sampleIdentifier2.getSampleSubCode());
             String containerCode = CodeConverter.tryToDatabase(sampleIdentifier2.tryGetContainerCode());
-            Key key = new Key(spaceCode, containerCode);
+            SampleIdentifierParts key = new SampleIdentifierParts(spaceCode, null, containerCode);
             Map<String, SampleIdentifier> identifiersByCode = groupedIdentifiers.get(key);
             if (identifiersByCode == null)
             {
@@ -87,9 +87,9 @@ public class ListSampleTechIdByIdentifier extends AbstractListTechIdById<SampleI
         
         Map<Long, SampleIdentifier> result = new HashMap<>();
         SampleQuery query = QueryTool.getManagedQuery(SampleQuery.class);
-        for (Entry<Key, Map<String, SampleIdentifier>> entry : groupedIdentifiers.entrySet())
+        for (Entry<SampleIdentifierParts, Map<String, SampleIdentifier>> entry : groupedIdentifiers.entrySet())
         {
-            Key key = entry.getKey();
+            SampleIdentifierParts key = entry.getKey();
             Map<String, SampleIdentifier> identifiersByCode = entry.getValue();
             List<TechIdStringIdentifierRecord> records = list(query, key, identifiersByCode.keySet());
             for (TechIdStringIdentifierRecord record : records)
@@ -101,11 +101,11 @@ public class ListSampleTechIdByIdentifier extends AbstractListTechIdById<SampleI
         return result;
     }
     
-    private List<TechIdStringIdentifierRecord> list(SampleQuery query, Key key, Collection<String> codes)
+    private List<TechIdStringIdentifierRecord> list(SampleQuery query, SampleIdentifierParts key, Collection<String> codes)
     {
         String[] codesArray = codes.toArray(new String[codes.size()]);
-        String spaceCode = key.spaceCodeOrNull;
-        String containerCode = key.containerCodeOrNull;
+        String spaceCode = key.getSpaceCodeOrNull();
+        String containerCode = key.getContainerCodeOrNull();
         if (spaceCode == null)
         {
             if (containerCode == null)
@@ -121,48 +121,4 @@ public class ListSampleTechIdByIdentifier extends AbstractListTechIdById<SampleI
         return query.listSpaceSampleTechIdsByContainerCodeAndCodes(spaceCode, containerCode, codesArray);
     }
 
-    private static final class Key
-    {
-        private String spaceCodeOrNull;
-        private String containerCodeOrNull;
-
-        Key(String spaceCodeOrNull, String containerCodeOrNull)
-        {
-            this.spaceCodeOrNull = spaceCodeOrNull;
-            this.containerCodeOrNull = containerCodeOrNull;
-        }
-
-        @Override
-        public boolean equals(Object obj)
-        {
-            if (obj == this)
-            {
-                return true;
-            }
-            if (obj instanceof Key == false)
-            {
-                return false;
-            }
-            Key key = (Key) obj;
-            return isEqual(spaceCodeOrNull, key.spaceCodeOrNull) 
-                    && isEqual(containerCodeOrNull, key.containerCodeOrNull);
-        }
-        
-        private boolean isEqual(String str1, String str2)
-        {
-            return str1 == null ? str1 == str2 : str1.equals(str2);
-        }
-        
-        @Override
-        public int hashCode()
-        {
-            return 37 * calcHashCode(spaceCodeOrNull) + calcHashCode(containerCodeOrNull);
-        }
-        
-        private int calcHashCode(String str)
-        {
-            return str == null ? 0 : str.hashCode();
-        }
-    }
-
 }
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/sample/SampleIdentifierParts.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/sample/SampleIdentifierParts.java
new file mode 100644
index 0000000000000000000000000000000000000000..61302519b06e6ba661900518a0ddad2ac413fd82
--- /dev/null
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/sample/SampleIdentifierParts.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2015 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.ethz.sis.openbis.generic.server.api.v3.executor.sample;
+
+final class SampleIdentifierParts
+{
+    private String spaceCodeOrNull;
+    private String projectCodeOrNull;
+    private String containerCodeOrNull;
+
+    SampleIdentifierParts(String spaceCodeOrNull, String projectCodeOrNull, String containerCodeOrNull)
+    {
+        this.spaceCodeOrNull = spaceCodeOrNull;
+        this.projectCodeOrNull = projectCodeOrNull;
+        this.containerCodeOrNull = containerCodeOrNull;
+    }
+
+    public String getSpaceCodeOrNull()
+    {
+        return spaceCodeOrNull;
+    }
+
+    public String getProjectCodeOrNull()
+    {
+        return projectCodeOrNull;
+    }
+
+    public String getContainerCodeOrNull()
+    {
+        return containerCodeOrNull;
+    }
+
+    @Override
+    public boolean equals(Object obj)
+    {
+        if (obj == this)
+        {
+            return true;
+        }
+        if (obj instanceof SampleIdentifierParts == false)
+        {
+            return false;
+        }
+        SampleIdentifierParts key = (SampleIdentifierParts) obj;
+        return isEqual(spaceCodeOrNull, key.spaceCodeOrNull) 
+                && isEqual(projectCodeOrNull, key.projectCodeOrNull)
+                && isEqual(containerCodeOrNull, key.containerCodeOrNull);
+    }
+    
+    private boolean isEqual(String str1, String str2)
+    {
+        return str1 == null ? str1 == str2 : str1.equals(str2);
+    }
+    
+    @Override
+    public int hashCode()
+    {
+        return 37 * (37 * calcHashCode(spaceCodeOrNull) + calcHashCode(projectCodeOrNull)) 
+                + calcHashCode(containerCodeOrNull);
+    }
+    
+    @Override
+    public String toString()
+    {
+        // TODO Auto-generated method stub
+        return super.toString();
+    }
+
+    private int calcHashCode(String str)
+    {
+        return str == null ? 0 : str.hashCode();
+    }
+}
\ No newline at end of file
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/sample/SampleBaseRecord.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/sample/SampleBaseRecord.java
index 2760bddbb3ca3522b74f06e34a458fae5e9bc4e8..49fa50b50122fbaecf03dc021c3481e5ce3a0c8f 100644
--- a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/sample/SampleBaseRecord.java
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/sample/SampleBaseRecord.java
@@ -30,6 +30,8 @@ public class SampleBaseRecord extends ObjectBaseRecord
 
     public String spaceCode;
 
+    public String projectCode;
+    
     public String containerCode;
 
     public String permId;
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/sample/SampleQuery.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/sample/SampleQuery.java
index 798f6198f3260e2cd2d0f109a2a760d2acc16b5b..bd87d642c458c6664b5f8985b98d7e837c5176ef 100644
--- a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/sample/SampleQuery.java
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/sample/SampleQuery.java
@@ -41,8 +41,11 @@ public interface SampleQuery extends ObjectQuery
             + "where s.id = any(?{1})", parameterBindings = { LongSetMapper.class }, fetchSize = FETCH_SIZE)
     public List<SampleAuthorizationRecord> getAuthorizations(LongSet sampleIds);
 
-    @Select(sql = "select s.id, s.code, s.perm_id as permId, sp.code as spaceCode, sc.code as containerCode, s.registration_timestamp as registrationDate, s.modification_timestamp as modificationDate "
+    @Select(sql = "select s.id, s.code, s.perm_id as permId, sp.code as spaceCode, p.code as projectCode, "
+            + "sc.code as containerCode, s.registration_timestamp as registrationDate, "
+            + "s.modification_timestamp as modificationDate "
             + "from samples s left join spaces sp on s.space_id = sp.id "
+            + "left join projects p on s.proj_id = p.id "
             + "left join samples sc on s.samp_id_part_of = sc.id "
             + "where s.id = any(?{1})", parameterBindings = { LongSetMapper.class }, fetchSize = FETCH_SIZE)
     public List<SampleBaseRecord> getSamples(LongSet sampleIds);
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/sample/SampleTranslator.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/sample/SampleTranslator.java
index 888af5f9247fbddd9a9d550813d190785e831518..33e0ea3ae6df187795f8928bf624aade9662100b 100644
--- a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/sample/SampleTranslator.java
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/sample/SampleTranslator.java
@@ -208,7 +208,8 @@ public class SampleTranslator extends AbstractCachingTranslator<Long, Sample, Sa
 
         result.setPermId(new SamplePermId(baseRecord.permId));
         result.setCode(baseRecord.code);
-        result.setIdentifier(new SampleIdentifier(baseRecord.spaceCode, baseRecord.containerCode, baseRecord.code));
+        result.setIdentifier(new SampleIdentifier(baseRecord.spaceCode, baseRecord.projectCode, 
+                baseRecord.containerCode, baseRecord.code));
         result.setModificationDate(baseRecord.modificationDate);
         result.setRegistrationDate(baseRecord.registrationDate);
 
diff --git a/openbis/sourceTest/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/sample/FullSampleIdentifierTest.java b/openbis/sourceTest/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/sample/FullSampleIdentifierTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..4feb0e8aefa4aed8fabc72bb9b8482b40f70daa5
--- /dev/null
+++ b/openbis/sourceTest/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/sample/FullSampleIdentifierTest.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2015 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.ethz.sis.openbis.generic.server.api.v3.executor.sample;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.fail;
+
+import org.testng.annotations.Test;
+
+/**
+ * 
+ *
+ * @author Franz-Josef Elmer
+ */
+public class FullSampleIdentifierTest
+{
+
+    @Test
+    public void testHappyCases()
+    {
+        assertSampId("/S1");
+        assertSampId("/SPACE1/S2");
+        assertSampId("/SPACE1/S2:A02");
+        assertSampId("/SPACE1/PROJECT1/S1");
+        assertSampId("/SPACE1/PROJECT1/S1:A02");
+    }
+    
+    @Test
+    public void testFailingCases()
+    {
+        assertInvalidSampId("Unspecified sample identifier.", null);
+        assertInvalidSampId("Unspecified sample identifier.", "");
+        assertInvalidSampId("Sample identifier has to start with a '/': A/BC", "A/BC");
+        assertInvalidSampId("Sample identifier can not contain more than three '/': /A/B/C/D", "/A/B/C/D");
+        assertInvalidSampId("Sample code can not contain more than one ':': /A/B:C:D", "/A/B:C:D");
+    }
+    
+    private void assertInvalidSampId(String expectedErrorMsg, String identifier)
+    {
+        try
+        {
+            new FullSampleIdentifier(identifier);
+            fail("IllegalArgumentException expected");
+        } catch (IllegalArgumentException ex)
+        {
+            assertEquals(expectedErrorMsg, ex.getMessage());
+        }
+    }
+    
+    private void assertSampId(String identifier)
+    {
+        assertEquals(new FullSampleIdentifier(identifier).toString(), identifier);
+    }
+
+}
diff --git a/openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/api/v3/ProjectSampleTest.java b/openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/api/v3/ProjectSampleTest.java
index a4e75b18e9ff0a8e2bc9192ae25d726f79cfacf1..0a8f05a6754429136cff918ade6cca0f97792fdd 100644
--- a/openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/api/v3/ProjectSampleTest.java
+++ b/openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/api/v3/ProjectSampleTest.java
@@ -367,7 +367,7 @@ public class ProjectSampleTest extends AbstractTest
         
         SearchResult<Sample> result = v3api.searchSamples(systemSessionToken, searchCriteria, fetchOptions);
         
-        assertEquals(result.getObjects().get(0).getIdentifier().getIdentifier(), "/CISD/3VCP5");
+        assertEquals(result.getObjects().get(0).getIdentifier().getIdentifier(), "/CISD/NEMO/3VCP5");
         assertEquals(result.getObjects().get(0).getProject().getIdentifier().getIdentifier(), "/CISD/NEMO");
         assertEquals(result.getTotalCount(), 1);
     }
@@ -383,7 +383,7 @@ public class ProjectSampleTest extends AbstractTest
         
         SearchResult<Sample> result = v3api.searchSamples(systemSessionToken, searchCriteria, fetchOptions);
         
-        assertEquals(result.getObjects().get(0).getIdentifier().getIdentifier(), "/CISD/3VCP5");
+        assertEquals(result.getObjects().get(0).getIdentifier().getIdentifier(), "/CISD/NEMO/3VCP5");
         assertEquals(result.getObjects().get(0).getProject().getIdentifier().getIdentifier(), "/CISD/NEMO");
         assertEquals(result.getTotalCount(), 1);
     }
@@ -398,7 +398,7 @@ public class ProjectSampleTest extends AbstractTest
         
         SearchResult<Sample> result = v3api.searchSamples(systemSessionToken, searchCriteria, fetchOptions);
         
-        assertEquals(result.getObjects().get(0).getIdentifier().getIdentifier(), "/CISD/3VCP5");
+        assertEquals(result.getObjects().get(0).getIdentifier().getIdentifier(), "/CISD/NEMO/3VCP5");
         assertEquals(result.getObjects().get(0).getProject().getIdentifier().getIdentifier(), "/CISD/NEMO");
         assertEquals(result.getTotalCount(), 1);
     }
@@ -413,7 +413,7 @@ public class ProjectSampleTest extends AbstractTest
         
         SearchResult<Sample> result = v3api.searchSamples(systemSessionToken, searchCriteria, fetchOptions);
         
-        assertEquals(result.getObjects().get(0).getIdentifier().getIdentifier(), "/CISD/3VCP5");
+        assertEquals(result.getObjects().get(0).getIdentifier().getIdentifier(), "/CISD/NEMO/3VCP5");
         assertEquals(result.getObjects().get(0).getProject().getIdentifier().getIdentifier(), "/CISD/NEMO");
         assertEquals(result.getTotalCount(), 1);
     }
diff --git a/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/id/sample/SampleIdentifier.java b/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/id/sample/SampleIdentifier.java
index 90fe47d68de6c23c272fc5effcbbdc05cc0ec283..e029e57e7f8832695e1f378b4f8a1dcacbde01db 100644
--- a/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/id/sample/SampleIdentifier.java
+++ b/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/id/sample/SampleIdentifier.java
@@ -40,14 +40,31 @@ public class SampleIdentifier extends ObjectIdentifier implements ISampleId
 
     public SampleIdentifier(String spaceCodeOrNull, String containerCodeOrNull, String sampleCode)
     {
-        this(createIdentifier(spaceCodeOrNull, containerCodeOrNull, sampleCode));
+        this(spaceCodeOrNull, null, containerCodeOrNull, sampleCode);
     }
 
-    private static String createIdentifier(String spaceCodeOrNull, String containerCodeOrNull, String sampleCode)
+    public SampleIdentifier(String spaceCodeOrNull, String projectCodeOrNull, String containerCodeOrNull, String sampleCode)
     {
-        String spaceCode = spaceCodeOrNull != null ? "/" + spaceCodeOrNull : "";
-        String containerCode = containerCodeOrNull != null ? containerCodeOrNull + ":" : "";
-        return spaceCode + "/" + containerCode + sampleCode;
+        this(createIdentifier(spaceCodeOrNull, projectCodeOrNull, containerCodeOrNull, sampleCode));
+    }
+    
+    private static String createIdentifier(String spaceCodeOrNull, String projectCodeOrNull, 
+            String containerCodeOrNull, String sampleCode)
+    {
+        StringBuilder builder = new StringBuilder("/");
+        if (spaceCodeOrNull != null)
+        {
+            builder.append(spaceCodeOrNull).append("/");
+        }
+        if (projectCodeOrNull != null)
+        {
+            builder.append(projectCodeOrNull).append("/");
+        }
+        if (containerCodeOrNull != null)
+        {
+            builder.append(containerCodeOrNull).append(":");
+        }
+        return builder.append(sampleCode).toString();
     }
 
     //