diff --git a/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/common/interfaces/IPropertiesHolder.java b/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/common/interfaces/IPropertiesHolder.java
index 3e6dcb3aa609b73a9c10d280f3c9543e52b3d1c6..7a5346813db382a1c786d6d085486400d355ca0f 100644
--- a/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/common/interfaces/IPropertiesHolder.java
+++ b/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/common/interfaces/IPropertiesHolder.java
@@ -33,9 +33,9 @@ public interface IPropertiesHolder
 
     void setProperties(Map<String, Serializable> properties);
 
-    String getProperty(String propertyName);
+    Serializable getProperty(String propertyName);
 
-    void setProperty(String propertyName, String propertyValue);
+    void setProperty(String propertyName, Serializable propertyValue);
 
     Long getIntegerProperty(String propertyName);
 
@@ -61,13 +61,13 @@ public interface IPropertiesHolder
 
     void setBooleanProperty(String propertyName, Boolean propertyValue);
 
-    String getControlledVocabularyProperty(String propertyName);
+    String[] getControlledVocabularyProperty(String propertyName);
 
-    void setControlledVocabularyProperty(String propertyName, String propertyValue);
+    void setControlledVocabularyProperty(String propertyName, String[] propertyValue);
 
-    SamplePermId getSampleProperty(String propertyName);
+    SamplePermId[] getSampleProperty(String propertyName);
 
-    void setSampleProperty(String propertyName, SamplePermId propertyValue);
+    void setSampleProperty(String propertyName, SamplePermId[] propertyValue);
 
     String getHyperlinkProperty(String propertyName);
 
diff --git a/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/dataset/DataSet.java b/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/dataset/DataSet.java
index 5596ba677d94d593c8f17e0af960c46c8711cdcf..426d7c7fad55a01c14a73f88f3b6c707c0070d0c 100644
--- a/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/dataset/DataSet.java
+++ b/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/dataset/DataSet.java
@@ -15,6 +15,7 @@
  */
 package ch.ethz.sis.openbis.generic.asapi.v3.dto.dataset;
 
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.id.ObjectPermId;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.interfaces.ICodeHolder;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.interfaces.IEntityTypeHolder;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.interfaces.IExperimentHolder;
@@ -55,6 +56,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
+import java.util.stream.Stream;
 
 /*
  * Class automatically generated with DtoGenerator
@@ -123,7 +125,7 @@ public class DataSet implements Serializable, ICodeHolder, IEntityTypeHolder, IE
     private Map<String, Material> materialProperties;
 
     @JsonProperty
-    private Map<String, Sample> sampleProperties;
+    private Map<String, Sample[]> sampleProperties;
 
     @JsonProperty
     private List<DataSet> parents;
@@ -508,7 +510,7 @@ public class DataSet implements Serializable, ICodeHolder, IEntityTypeHolder, IE
 
     // Method automatically generated with DtoGenerator
     @JsonIgnore
-    public Map<String, Sample> getSampleProperties()
+    public Map<String, Sample[]> getSampleProperties()
     {
         if (getFetchOptions() != null && getFetchOptions().hasSampleProperties())
         {
@@ -521,7 +523,7 @@ public class DataSet implements Serializable, ICodeHolder, IEntityTypeHolder, IE
     }
 
     // Method automatically generated with DtoGenerator
-    public void setSampleProperties(Map<String, Sample> sampleProperties)
+    public void setSampleProperties(Map<String, Sample[]> sampleProperties)
     {
         this.sampleProperties = sampleProperties;
     }
@@ -945,7 +947,7 @@ public class DataSet implements Serializable, ICodeHolder, IEntityTypeHolder, IE
     }
 
     @Override
-    public void setProperty(String propertyName, String propertyValue)
+    public void setProperty(String propertyName, Serializable propertyValue)
     {
         if (properties == null)
         {
@@ -1072,28 +1074,49 @@ public class DataSet implements Serializable, ICodeHolder, IEntityTypeHolder, IE
     }
 
     @Override
-    public String getControlledVocabularyProperty(String propertyName)
+    public String[] getControlledVocabularyProperty(String propertyName)
     {
-        return getProperty(propertyName);
+        if(getProperties() == null || getProperties().get(propertyName) == null) {
+            return null;
+        }
+        Serializable value = getProperties().get(propertyName);
+        if(value.getClass().isArray()) {
+            Serializable[] values = (Serializable[]) value;
+            return Arrays.stream(values).map(x -> (String)x).toArray(String[]::new);
+        } else {
+            String propertyValue = (String) value;
+            return new String[]{ propertyValue };
+        }
     }
 
     @Override
-    public void setControlledVocabularyProperty(String propertyName, String propertyValue)
+    public void setControlledVocabularyProperty(String propertyName, String[] propertyValue)
     {
         setProperty(propertyName, propertyValue);
     }
 
     @Override
-    public SamplePermId getSampleProperty(String propertyName)
+    public SamplePermId[] getSampleProperty(String propertyName)
     {
-        String propertyValue = getProperty(propertyName);
-        return (propertyValue == null || propertyValue.trim().isEmpty()) ? null : new SamplePermId(propertyValue);
+        if(getProperties() == null || getProperties().get(propertyName) == null) {
+            return null;
+        }
+        Serializable value = getProperties().get(propertyName);
+        if(value.getClass().isArray()) {
+            Serializable[] values = (Serializable[]) value;
+            return Arrays.stream(values).map(x -> new SamplePermId((String)x)).toArray(SamplePermId[]::new);
+        } else {
+            String propertyValue = (String) value;
+            return new SamplePermId[]{new SamplePermId(propertyValue)};
+        }
     }
 
     @Override
-    public void setSampleProperty(String propertyName, SamplePermId propertyValue)
+    public void setSampleProperty(String propertyName, SamplePermId[] propertyValue)
     {
-        setProperty(propertyName, propertyValue == null ? null : propertyValue.getPermId());
+        setProperty(propertyName, propertyValue == null ? null : Arrays.stream(propertyValue)
+                .map(ObjectPermId::getPermId)
+                .toArray(String[]::new));
     }
 
     @Override
diff --git a/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/dataset/create/DataSetCreation.java b/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/dataset/create/DataSetCreation.java
index b28ed7571f3c814c2ed7f1c74c6efd5b2c488d7b..1c46341b431e3cbcd2bbaa2a3c5f3c43c986d79a 100644
--- a/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/dataset/create/DataSetCreation.java
+++ b/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/dataset/create/DataSetCreation.java
@@ -24,6 +24,7 @@ import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.ObjectToString;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.create.ICreation;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.create.IObjectCreation;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.id.CreationId;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.id.ObjectPermId;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.interfaces.ICreationIdHolder;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.interfaces.IPropertiesHolder;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.property.PropertiesDeserializer;
@@ -258,7 +259,7 @@ public class DataSetCreation implements ICreation, ICreationIdHolder, IObjectCre
     }
 
     @Override
-    public void setProperty(String propertyName, String propertyValue)
+    public void setProperty(String propertyName, Serializable propertyValue)
     {
         this.properties.put(propertyName, propertyValue);
     }
@@ -404,28 +405,49 @@ public class DataSetCreation implements ICreation, ICreationIdHolder, IObjectCre
     }
 
     @Override
-    public String getControlledVocabularyProperty(String propertyName)
+    public String[] getControlledVocabularyProperty(String propertyName)
     {
-        return getProperty(propertyName);
+        if(getProperties() == null || getProperties().get(propertyName) == null) {
+            return null;
+        }
+        Serializable value = getProperties().get(propertyName);
+        if(value.getClass().isArray()) {
+            Serializable[] values = (Serializable[]) value;
+            return Arrays.stream(values).map(x -> (String)x).toArray(String[]::new);
+        } else {
+            String propertyValue = (String) value;
+            return new String[]{ propertyValue };
+        }
     }
 
     @Override
-    public void setControlledVocabularyProperty(String propertyName, String propertyValue)
+    public void setControlledVocabularyProperty(String propertyName, String[] propertyValue)
     {
         setProperty(propertyName, propertyValue);
     }
 
     @Override
-    public SamplePermId getSampleProperty(String propertyName)
+    public SamplePermId[] getSampleProperty(String propertyName)
     {
-        String propertyValue = getProperty(propertyName);
-        return (propertyValue == null || propertyValue.trim().isEmpty()) ? null : new SamplePermId(propertyValue);
+        if(getProperties() == null || getProperties().get(propertyName) == null) {
+            return null;
+        }
+        Serializable value = getProperties().get(propertyName);
+        if(value.getClass().isArray()) {
+            Serializable[] values = (Serializable[]) value;
+            return Arrays.stream(values).map(x -> new SamplePermId((String)x)).toArray(SamplePermId[]::new);
+        } else {
+            String propertyValue = (String) value;
+            return new SamplePermId[]{new SamplePermId(propertyValue)};
+        }
     }
 
     @Override
-    public void setSampleProperty(String propertyName, SamplePermId propertyValue)
+    public void setSampleProperty(String propertyName, SamplePermId[] propertyValue)
     {
-        setProperty(propertyName, propertyValue == null ? null : propertyValue.getPermId());
+        setProperty(propertyName, propertyValue == null ? null : Arrays.stream(propertyValue)
+                .map(ObjectPermId::getPermId)
+                .toArray(String[]::new));
     }
 
     @Override
diff --git a/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/dataset/update/DataSetUpdate.java b/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/dataset/update/DataSetUpdate.java
index a3ace513f77dd5ab591dc848d027d5f047788049..5894c766a93bce7302b80717d0663921546a751b 100644
--- a/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/dataset/update/DataSetUpdate.java
+++ b/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/dataset/update/DataSetUpdate.java
@@ -20,6 +20,7 @@ import java.time.ZonedDateTime;
 import java.time.format.DateTimeFormatter;
 import java.util.*;
 
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.id.ObjectPermId;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.interfaces.IMetaDataUpdateHolder;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.property.PropertiesDeserializer;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.update.*;
@@ -224,7 +225,7 @@ public class DataSetUpdate implements IUpdate, IObjectUpdate<IDataSetId>, IPrope
 
     @Override
     @JsonIgnore
-    public void setProperty(String propertyName, String propertyValue)
+    public void setProperty(String propertyName, Serializable propertyValue)
     {
         properties.put(propertyName, propertyValue);
     }
@@ -412,28 +413,49 @@ public class DataSetUpdate implements IUpdate, IObjectUpdate<IDataSetId>, IPrope
     }
 
     @Override
-    public String getControlledVocabularyProperty(String propertyName)
+    public String[] getControlledVocabularyProperty(String propertyName)
     {
-        return getProperty(propertyName);
+        if(getProperties() == null || getProperties().get(propertyName) == null) {
+            return null;
+        }
+        Serializable value = getProperties().get(propertyName);
+        if(value.getClass().isArray()) {
+            Serializable[] values = (Serializable[]) value;
+            return Arrays.stream(values).map(x -> (String)x).toArray(String[]::new);
+        } else {
+            String propertyValue = (String) value;
+            return new String[]{ propertyValue };
+        }
     }
 
     @Override
-    public void setControlledVocabularyProperty(String propertyName, String propertyValue)
+    public void setControlledVocabularyProperty(String propertyName, String[] propertyValue)
     {
         setProperty(propertyName, propertyValue);
     }
 
     @Override
-    public SamplePermId getSampleProperty(String propertyName)
+    public SamplePermId[] getSampleProperty(String propertyName)
     {
-        String propertyValue = getProperty(propertyName);
-        return (propertyValue == null || propertyValue.trim().isEmpty()) ? null : new SamplePermId(propertyValue);
+        if(getProperties() == null || getProperties().get(propertyName) == null) {
+            return null;
+        }
+        Serializable value = getProperties().get(propertyName);
+        if(value.getClass().isArray()) {
+            Serializable[] values = (Serializable[]) value;
+            return Arrays.stream(values).map(x -> new SamplePermId((String)x)).toArray(SamplePermId[]::new);
+        } else {
+            String propertyValue = (String) value;
+            return new SamplePermId[]{new SamplePermId(propertyValue)};
+        }
     }
 
     @Override
-    public void setSampleProperty(String propertyName, SamplePermId propertyValue)
+    public void setSampleProperty(String propertyName, SamplePermId[] propertyValue)
     {
-        setProperty(propertyName, propertyValue == null ? null : propertyValue.getPermId());
+        setProperty(propertyName, propertyValue == null ? null : Arrays.stream(propertyValue)
+                .map(ObjectPermId::getPermId)
+                .toArray(String[]::new));
     }
 
     @Override
diff --git a/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/experiment/Experiment.java b/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/experiment/Experiment.java
index fb39da239ecccedca5c2d0a09b185dd223d81fec..b69adb9a37ea8a81ffbc239adad25effd521e6d2 100644
--- a/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/experiment/Experiment.java
+++ b/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/experiment/Experiment.java
@@ -16,6 +16,7 @@
 package ch.ethz.sis.openbis.generic.asapi.v3.dto.experiment;
 
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.attachment.Attachment;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.id.ObjectPermId;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.interfaces.IAttachmentsHolder;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.interfaces.ICodeHolder;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.interfaces.IDataSetsHolder;
@@ -136,7 +137,7 @@ public class Experiment implements Serializable, IAttachmentsHolder, ICodeHolder
     private Map<String, Material> materialProperties;
 
     @JsonProperty
-    private Map<String, Sample> sampleProperties;
+    private Map<String, Sample[]> sampleProperties;
 
     @JsonProperty
     private Set<Tag> tags;
@@ -526,7 +527,7 @@ public class Experiment implements Serializable, IAttachmentsHolder, ICodeHolder
 
     // Method automatically generated with DtoGenerator
     @JsonIgnore
-    public Map<String, Sample> getSampleProperties()
+    public Map<String, Sample[]> getSampleProperties()
     {
         if (getFetchOptions() != null && getFetchOptions().hasSampleProperties())
         {
@@ -539,7 +540,7 @@ public class Experiment implements Serializable, IAttachmentsHolder, ICodeHolder
     }
 
     // Method automatically generated with DtoGenerator
-    public void setSampleProperties(Map<String, Sample> sampleProperties)
+    public void setSampleProperties(Map<String, Sample[]> sampleProperties)
     {
         this.sampleProperties = sampleProperties;
     }
@@ -635,7 +636,7 @@ public class Experiment implements Serializable, IAttachmentsHolder, ICodeHolder
     }
 
     @Override
-    public void setProperty(String propertyName, String propertyValue)
+    public void setProperty(String propertyName, Serializable propertyValue)
     {
         if (properties == null)
         {
@@ -762,28 +763,49 @@ public class Experiment implements Serializable, IAttachmentsHolder, ICodeHolder
     }
 
     @Override
-    public String getControlledVocabularyProperty(String propertyName)
+    public String[] getControlledVocabularyProperty(String propertyName)
     {
-        return getProperty(propertyName);
+        if(getProperties() == null || getProperties().get(propertyName) == null) {
+            return null;
+        }
+        Serializable value = getProperties().get(propertyName);
+        if(value.getClass().isArray()) {
+            Serializable[] values = (Serializable[]) value;
+            return Arrays.stream(values).map(x -> (String)x).toArray(String[]::new);
+        } else {
+            String propertyValue = (String) value;
+            return new String[]{ propertyValue };
+        }
     }
 
     @Override
-    public void setControlledVocabularyProperty(String propertyName, String propertyValue)
+    public void setControlledVocabularyProperty(String propertyName, String[] propertyValue)
     {
         setProperty(propertyName, propertyValue);
     }
 
     @Override
-    public SamplePermId getSampleProperty(String propertyName)
+    public SamplePermId[] getSampleProperty(String propertyName)
     {
-        String propertyValue = getProperty(propertyName);
-        return (propertyValue == null || propertyValue.trim().isEmpty()) ? null : new SamplePermId(propertyValue);
+        if(getProperties() == null || getProperties().get(propertyName) == null) {
+            return null;
+        }
+        Serializable value = getProperties().get(propertyName);
+        if(value.getClass().isArray()) {
+            Serializable[] values = (Serializable[]) value;
+            return Arrays.stream(values).map(x -> new SamplePermId((String)x)).toArray(SamplePermId[]::new);
+        } else {
+            String propertyValue = (String) value;
+            return new SamplePermId[]{new SamplePermId(propertyValue)};
+        }
     }
 
     @Override
-    public void setSampleProperty(String propertyName, SamplePermId propertyValue)
+    public void setSampleProperty(String propertyName, SamplePermId[] propertyValue)
     {
-        setProperty(propertyName, propertyValue == null ? null : propertyValue.getPermId());
+        setProperty(propertyName, propertyValue == null ? null : Arrays.stream(propertyValue)
+                .map(ObjectPermId::getPermId)
+                .toArray(String[]::new));
     }
 
     @Override
diff --git a/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/experiment/create/ExperimentCreation.java b/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/experiment/create/ExperimentCreation.java
index e08d0062834de4631fa540d08be08601dc5e34c2..a02ed0f159d2a174898940e765d4a338f5bc3c54 100644
--- a/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/experiment/create/ExperimentCreation.java
+++ b/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/experiment/create/ExperimentCreation.java
@@ -25,6 +25,7 @@ import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.ObjectToString;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.create.ICreation;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.create.IObjectCreation;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.id.CreationId;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.id.ObjectPermId;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.interfaces.ICreationIdHolder;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.interfaces.IPropertiesHolder;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.property.PropertiesDeserializer;
@@ -111,7 +112,7 @@ public class ExperimentCreation implements ICreation, IObjectCreation, ICreation
     }
 
     @Override
-    public void setProperty(String propertyName, String propertyValue)
+    public void setProperty(String propertyName, Serializable propertyValue)
     {
         this.properties.put(propertyName, propertyValue);
     }
@@ -257,28 +258,49 @@ public class ExperimentCreation implements ICreation, IObjectCreation, ICreation
     }
 
     @Override
-    public String getControlledVocabularyProperty(String propertyName)
+    public String[] getControlledVocabularyProperty(String propertyName)
     {
-        return getProperty(propertyName);
+        if(getProperties() == null || getProperties().get(propertyName) == null) {
+            return null;
+        }
+        Serializable value = getProperties().get(propertyName);
+        if(value.getClass().isArray()) {
+            Serializable[] values = (Serializable[]) value;
+            return Arrays.stream(values).map(x -> (String)x).toArray(String[]::new);
+        } else {
+            String propertyValue = (String) value;
+            return new String[]{ propertyValue };
+        }
     }
 
     @Override
-    public void setControlledVocabularyProperty(String propertyName, String propertyValue)
+    public void setControlledVocabularyProperty(String propertyName, String[] propertyValue)
     {
         setProperty(propertyName, propertyValue);
     }
 
     @Override
-    public SamplePermId getSampleProperty(String propertyName)
+    public SamplePermId[] getSampleProperty(String propertyName)
     {
-        String propertyValue = getProperty(propertyName);
-        return (propertyValue == null || propertyValue.trim().isEmpty()) ? null : new SamplePermId(propertyValue);
+        if(getProperties() == null || getProperties().get(propertyName) == null) {
+            return null;
+        }
+        Serializable value = getProperties().get(propertyName);
+        if(value.getClass().isArray()) {
+            Serializable[] values = (Serializable[]) value;
+            return Arrays.stream(values).map(x -> new SamplePermId((String)x)).toArray(SamplePermId[]::new);
+        } else {
+            String propertyValue = (String) value;
+            return new SamplePermId[]{new SamplePermId(propertyValue)};
+        }
     }
 
     @Override
-    public void setSampleProperty(String propertyName, SamplePermId propertyValue)
+    public void setSampleProperty(String propertyName, SamplePermId[] propertyValue)
     {
-        setProperty(propertyName, propertyValue == null ? null : propertyValue.getPermId());
+        setProperty(propertyName, propertyValue == null ? null : Arrays.stream(propertyValue)
+                .map(ObjectPermId::getPermId)
+                .toArray(String[]::new));
     }
 
     @Override
diff --git a/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/experiment/update/ExperimentUpdate.java b/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/experiment/update/ExperimentUpdate.java
index 45bf57e3cff5753ec4e1fa5eac6906ccbca32af5..6933803e3cb97b946493c47b2789a843c0234c21 100644
--- a/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/experiment/update/ExperimentUpdate.java
+++ b/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/experiment/update/ExperimentUpdate.java
@@ -20,6 +20,7 @@ import java.time.ZonedDateTime;
 import java.time.format.DateTimeFormatter;
 import java.util.*;
 
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.id.ObjectPermId;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.property.PropertiesDeserializer;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.interfaces.IMetaDataUpdateHolder;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.update.*;
@@ -131,7 +132,7 @@ public class ExperimentUpdate implements IUpdate, IObjectUpdate<IExperimentId>,
 
     @Override
     @JsonIgnore
-    public void setProperty(String propertyName, String propertyValue)
+    public void setProperty(String propertyName, Serializable propertyValue)
     {
         properties.put(propertyName, propertyValue);
     }
@@ -289,28 +290,49 @@ public class ExperimentUpdate implements IUpdate, IObjectUpdate<IExperimentId>,
     }
 
     @Override
-    public String getControlledVocabularyProperty(String propertyName)
+    public String[] getControlledVocabularyProperty(String propertyName)
     {
-        return getProperty(propertyName);
+        if(getProperties() == null || getProperties().get(propertyName) == null) {
+            return null;
+        }
+        Serializable value = getProperties().get(propertyName);
+        if(value.getClass().isArray()) {
+            Serializable[] values = (Serializable[]) value;
+            return Arrays.stream(values).map(x -> (String)x).toArray(String[]::new);
+        } else {
+            String propertyValue = (String) value;
+            return new String[]{ propertyValue };
+        }
     }
 
     @Override
-    public void setControlledVocabularyProperty(String propertyName, String propertyValue)
+    public void setControlledVocabularyProperty(String propertyName, String[] propertyValue)
     {
         setProperty(propertyName, propertyValue);
     }
 
     @Override
-    public SamplePermId getSampleProperty(String propertyName)
+    public SamplePermId[] getSampleProperty(String propertyName)
     {
-        String propertyValue = getProperty(propertyName);
-        return (propertyValue == null || propertyValue.trim().isEmpty()) ? null : new SamplePermId(propertyValue);
+        if(getProperties() == null || getProperties().get(propertyName) == null) {
+            return null;
+        }
+        Serializable value = getProperties().get(propertyName);
+        if(value.getClass().isArray()) {
+            Serializable[] values = (Serializable[]) value;
+            return Arrays.stream(values).map(x -> new SamplePermId((String)x)).toArray(SamplePermId[]::new);
+        } else {
+            String propertyValue = (String) value;
+            return new SamplePermId[]{new SamplePermId(propertyValue)};
+        }
     }
 
     @Override
-    public void setSampleProperty(String propertyName, SamplePermId propertyValue)
+    public void setSampleProperty(String propertyName, SamplePermId[] propertyValue)
     {
-        setProperty(propertyName, propertyValue == null ? null : propertyValue.getPermId());
+        setProperty(propertyName, propertyValue == null ? null : Arrays.stream(propertyValue)
+                .map(ObjectPermId::getPermId)
+                .toArray(String[]::new));
     }
 
     @Override
diff --git a/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/material/Material.java b/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/material/Material.java
index aec4c5169ebfdf4b5df82457ee62239ccf887ec7..08d79b540a75b88ecffa37af2d4b6d14c449d915 100644
--- a/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/material/Material.java
+++ b/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/material/Material.java
@@ -15,6 +15,7 @@
  */
 package ch.ethz.sis.openbis.generic.asapi.v3.dto.material;
 
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.id.ObjectPermId;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.interfaces.ICodeHolder;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.interfaces.IEntityTypeHolder;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.interfaces.IMaterialPropertiesHolder;
@@ -293,7 +294,7 @@ public class Material implements Serializable, ICodeHolder, IEntityTypeHolder, I
     }
 
     @Override
-    public void setProperty(String propertyName, String propertyValue)
+    public void setProperty(String propertyName, Serializable propertyValue)
     {
         if (properties == null)
         {
@@ -420,28 +421,49 @@ public class Material implements Serializable, ICodeHolder, IEntityTypeHolder, I
     }
 
     @Override
-    public String getControlledVocabularyProperty(String propertyName)
+    public String[] getControlledVocabularyProperty(String propertyName)
     {
-        return getProperty(propertyName);
+        if(getProperties() == null || getProperties().get(propertyName) == null) {
+            return null;
+        }
+        Serializable value = getProperties().get(propertyName);
+        if(value.getClass().isArray()) {
+            Serializable[] values = (Serializable[]) value;
+            return Arrays.stream(values).map(x->(String)x).toArray(String[]::new);
+        } else {
+            String propertyValue = (String) value;
+            return new String[]{ propertyValue };
+        }
     }
 
     @Override
-    public void setControlledVocabularyProperty(String propertyName, String propertyValue)
+    public void setControlledVocabularyProperty(String propertyName, String[] propertyValue)
     {
         setProperty(propertyName, propertyValue);
     }
 
     @Override
-    public SamplePermId getSampleProperty(String propertyName)
+    public SamplePermId[] getSampleProperty(String propertyName)
     {
-        String propertyValue = getProperty(propertyName);
-        return (propertyValue == null || propertyValue.trim().isEmpty()) ? null : new SamplePermId(propertyValue);
+        if(getProperties() == null || getProperties().get(propertyName) == null) {
+            return null;
+        }
+        Serializable value = getProperties().get(propertyName);
+        if(value.getClass().isArray()) {
+            Serializable[] values = (Serializable[]) value;
+            return Arrays.stream(values).map(x -> new SamplePermId((String)x)).toArray(SamplePermId[]::new);
+        } else {
+            String propertyValue = (String) value;
+            return new SamplePermId[]{new SamplePermId(propertyValue)};
+        }
     }
 
     @Override
-    public void setSampleProperty(String propertyName, SamplePermId propertyValue)
+    public void setSampleProperty(String propertyName, SamplePermId[] propertyValue)
     {
-        setProperty(propertyName, propertyValue == null ? null : propertyValue.getPermId());
+        setProperty(propertyName, propertyValue == null ? null : Arrays.stream(propertyValue)
+                .map(ObjectPermId::getPermId)
+                .toArray(String[]::new));
     }
 
     @Override
diff --git a/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/material/create/MaterialCreation.java b/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/material/create/MaterialCreation.java
index 9a71e049081625ffa1f4c7e55449f3bb73e49212..ae64d25568b8bfd63b49735d4b05264c7d2404aa 100644
--- a/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/material/create/MaterialCreation.java
+++ b/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/material/create/MaterialCreation.java
@@ -24,6 +24,7 @@ import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.ObjectToString;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.create.ICreation;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.create.IObjectCreation;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.id.CreationId;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.id.ObjectPermId;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.interfaces.ICreationIdHolder;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.interfaces.IPropertiesHolder;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.property.PropertiesDeserializer;
@@ -85,7 +86,7 @@ public class MaterialCreation implements ICreation, IObjectCreation, ICreationId
     }
 
     @Override
-    public void setProperty(String propertyName, String propertyValue)
+    public void setProperty(String propertyName, Serializable propertyValue)
     {
         this.properties.put(propertyName, propertyValue);
     }
@@ -231,28 +232,49 @@ public class MaterialCreation implements ICreation, IObjectCreation, ICreationId
     }
 
     @Override
-    public String getControlledVocabularyProperty(String propertyName)
+    public String[] getControlledVocabularyProperty(String propertyName)
     {
-        return getProperty(propertyName);
+        if(getProperties() == null || getProperties().get(propertyName) == null) {
+            return null;
+        }
+        Serializable value = getProperties().get(propertyName);
+        if(value.getClass().isArray()) {
+            Serializable[] values = (Serializable[]) value;
+            return Arrays.stream(values).map(x->(String)x).toArray(String[]::new);
+        } else {
+            String propertyValue = (String) value;
+            return new String[]{ propertyValue };
+        }
     }
 
     @Override
-    public void setControlledVocabularyProperty(String propertyName, String propertyValue)
+    public void setControlledVocabularyProperty(String propertyName, String[] propertyValue)
     {
         setProperty(propertyName, propertyValue);
     }
 
     @Override
-    public SamplePermId getSampleProperty(String propertyName)
+    public SamplePermId[] getSampleProperty(String propertyName)
     {
-        String propertyValue = getProperty(propertyName);
-        return (propertyValue == null || propertyValue.trim().isEmpty()) ? null : new SamplePermId(propertyValue);
+        if(getProperties() == null || getProperties().get(propertyName) == null) {
+            return null;
+        }
+        Serializable value = getProperties().get(propertyName);
+        if(value.getClass().isArray()) {
+            Serializable[] values = (Serializable[]) value;
+            return Arrays.stream(values).map(x -> new SamplePermId((String)x)).toArray(SamplePermId[]::new);
+        } else {
+            String propertyValue = (String) value;
+            return new SamplePermId[]{new SamplePermId(propertyValue)};
+        }
     }
 
     @Override
-    public void setSampleProperty(String propertyName, SamplePermId propertyValue)
+    public void setSampleProperty(String propertyName, SamplePermId[] propertyValue)
     {
-        setProperty(propertyName, propertyValue == null ? null : propertyValue.getPermId());
+        setProperty(propertyName, propertyValue == null ? null : Arrays.stream(propertyValue)
+                .map(ObjectPermId::getPermId)
+                .toArray(String[]::new));
     }
 
     @Override
diff --git a/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/material/update/MaterialUpdate.java b/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/material/update/MaterialUpdate.java
index 711d6e131897ed1aa55fec68453f1f34ec39754a..77d7c055dfafb22d87272f5862422f70be1f2b23 100644
--- a/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/material/update/MaterialUpdate.java
+++ b/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/material/update/MaterialUpdate.java
@@ -20,6 +20,7 @@ import java.time.ZonedDateTime;
 import java.time.format.DateTimeFormatter;
 import java.util.*;
 
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.id.ObjectPermId;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.property.PropertiesDeserializer;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.sample.id.SamplePermId;
 import com.fasterxml.jackson.annotation.JsonIgnore;
@@ -75,7 +76,7 @@ public class MaterialUpdate implements IUpdate, IObjectUpdate<IMaterialId>, IPro
 
     @Override
     @JsonIgnore
-    public void setProperty(String propertyName, String propertyValue)
+    public void setProperty(String propertyName, Serializable propertyValue)
     {
         properties.put(propertyName, propertyValue);
     }
@@ -215,28 +216,49 @@ public class MaterialUpdate implements IUpdate, IObjectUpdate<IMaterialId>, IPro
     }
 
     @Override
-    public String getControlledVocabularyProperty(String propertyName)
+    public String[] getControlledVocabularyProperty(String propertyName)
     {
-        return getProperty(propertyName);
+        if(getProperties() == null || getProperties().get(propertyName) == null) {
+            return null;
+        }
+        Serializable value = getProperties().get(propertyName);
+        if(value.getClass().isArray()) {
+            Serializable[] values = (Serializable[]) value;
+            return Arrays.stream(values).map(x->(String)x).toArray(String[]::new);
+        } else {
+            String propertyValue = (String) value;
+            return new String[]{ propertyValue };
+        }
     }
 
     @Override
-    public void setControlledVocabularyProperty(String propertyName, String propertyValue)
+    public void setControlledVocabularyProperty(String propertyName, String[] propertyValue)
     {
         setProperty(propertyName, propertyValue);
     }
 
     @Override
-    public SamplePermId getSampleProperty(String propertyName)
+    public SamplePermId[] getSampleProperty(String propertyName)
     {
-        String propertyValue = getProperty(propertyName);
-        return (propertyValue == null || propertyValue.trim().isEmpty()) ? null : new SamplePermId(propertyValue);
+        if(getProperties() == null || getProperties().get(propertyName) == null) {
+            return null;
+        }
+        Serializable value = getProperties().get(propertyName);
+        if(value.getClass().isArray()) {
+            Serializable[] values = (Serializable[]) value;
+            return Arrays.stream(values).map(x -> new SamplePermId((String)x)).toArray(SamplePermId[]::new);
+        } else {
+            String propertyValue = (String) value;
+            return new SamplePermId[]{new SamplePermId(propertyValue)};
+        }
     }
 
     @Override
-    public void setSampleProperty(String propertyName, SamplePermId propertyValue)
+    public void setSampleProperty(String propertyName, SamplePermId[] propertyValue)
     {
-        setProperty(propertyName, propertyValue == null ? null : propertyValue.getPermId());
+        setProperty(propertyName, propertyValue == null ? null : Arrays.stream(propertyValue)
+                .map(ObjectPermId::getPermId)
+                .toArray(String[]::new));
     }
 
     @Override
diff --git a/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/sample/Sample.java b/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/sample/Sample.java
index f0ff0702ed71c31c19bbde738516799bb4d15e4b..3cb82e616df8d7062a8e26605a768cd96a49303a 100644
--- a/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/sample/Sample.java
+++ b/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/sample/Sample.java
@@ -17,6 +17,7 @@ package ch.ethz.sis.openbis.generic.asapi.v3.dto.sample;
 
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.attachment.Attachment;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.Relationship;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.id.ObjectPermId;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.interfaces.IAttachmentsHolder;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.interfaces.ICodeHolder;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.interfaces.IDataSetsHolder;
@@ -125,7 +126,7 @@ public class Sample implements Serializable, IAttachmentsHolder, ICodeHolder, ID
     private Map<String, Material> materialProperties;
 
     @JsonProperty
-    private Map<String, Sample> sampleProperties;
+    private Map<String, Sample[]> sampleProperties;
 
     @JsonProperty
     private List<Sample> parents;
@@ -474,7 +475,7 @@ public class Sample implements Serializable, IAttachmentsHolder, ICodeHolder, ID
 
     // Method automatically generated with DtoGenerator
     @JsonIgnore
-    public Map<String, Sample> getSampleProperties()
+    public Map<String, Sample[]> getSampleProperties()
     {
         if (getFetchOptions() != null && getFetchOptions().hasSampleProperties())
         {
@@ -487,7 +488,7 @@ public class Sample implements Serializable, IAttachmentsHolder, ICodeHolder, ID
     }
 
     // Method automatically generated with DtoGenerator
-    public void setSampleProperties(Map<String, Sample> sampleProperties)
+    public void setSampleProperties(Map<String, Sample[]> sampleProperties)
     {
         this.sampleProperties = sampleProperties;
     }
@@ -946,7 +947,7 @@ public class Sample implements Serializable, IAttachmentsHolder, ICodeHolder, ID
     }
 
     @Override
-    public void setProperty(String propertyName, String propertyValue)
+    public void setProperty(String propertyName, Serializable propertyValue)
     {
         if (properties == null)
         {
@@ -1073,28 +1074,49 @@ public class Sample implements Serializable, IAttachmentsHolder, ICodeHolder, ID
     }
 
     @Override
-    public String getControlledVocabularyProperty(String propertyName)
+    public String[] getControlledVocabularyProperty(String propertyName)
     {
-        return getProperty(propertyName);
+        if(getProperties() == null || getProperties().get(propertyName) == null) {
+            return null;
+        }
+        Serializable value = getProperties().get(propertyName);
+        if(value.getClass().isArray()) {
+            Serializable[] values = (Serializable[]) value;
+            return Arrays.stream(values).map(x->(String)x).toArray(String[]::new);
+        } else {
+            String propertyValue = (String) value;
+            return new String[]{ propertyValue };
+        }
     }
 
     @Override
-    public void setControlledVocabularyProperty(String propertyName, String propertyValue)
+    public void setControlledVocabularyProperty(String propertyName, String[] propertyValue)
     {
         setProperty(propertyName, propertyValue);
     }
 
     @Override
-    public SamplePermId getSampleProperty(String propertyName)
+    public SamplePermId[] getSampleProperty(String propertyName)
     {
-        String propertyValue = getProperty(propertyName);
-        return (propertyValue == null || propertyValue.trim().isEmpty()) ? null : new SamplePermId(propertyValue);
+        if(getProperties() == null || getProperties().get(propertyName) == null) {
+            return null;
+        }
+        Serializable value = getProperties().get(propertyName);
+        if(value.getClass().isArray()) {
+            Serializable[] values = (Serializable[]) value;
+            return Arrays.stream(values).map(x -> new SamplePermId((String)x)).toArray(SamplePermId[]::new);
+        } else {
+            String propertyValue = (String) value;
+            return new SamplePermId[]{new SamplePermId(propertyValue)};
+        }
     }
 
     @Override
-    public void setSampleProperty(String propertyName, SamplePermId propertyValue)
+    public void setSampleProperty(String propertyName, SamplePermId[] propertyValue)
     {
-        setProperty(propertyName, propertyValue == null ? null : propertyValue.getPermId());
+        setProperty(propertyName, propertyValue == null ? null : Arrays.stream(propertyValue)
+                .map(ObjectPermId::getPermId)
+                .toArray(String[]::new));
     }
 
     @Override
diff --git a/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/sample/create/SampleCreation.java b/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/sample/create/SampleCreation.java
index 8d22b21c7e6440b9d491c7f762a5a37462aba568..08e37b95bba451eb559344af76c1c4a77c4c685a 100644
--- a/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/sample/create/SampleCreation.java
+++ b/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/sample/create/SampleCreation.java
@@ -20,6 +20,7 @@ import java.time.ZonedDateTime;
 import java.time.format.DateTimeFormatter;
 import java.util.*;
 
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.id.ObjectPermId;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.property.PropertiesDeserializer;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.sample.id.SamplePermId;
 import com.fasterxml.jackson.annotation.JsonIgnore;
@@ -238,7 +239,7 @@ public class SampleCreation implements ICreation, ICreationIdHolder, IProperties
     }
 
     @Override
-    public void setProperty(String propertyName, String propertyValue)
+    public void setProperty(String propertyName, Serializable propertyValue)
     {
         this.properties.put(propertyName, propertyValue);
     }
@@ -374,28 +375,49 @@ public class SampleCreation implements ICreation, ICreationIdHolder, IProperties
     }
 
     @Override
-    public String getControlledVocabularyProperty(String propertyName)
+    public String[] getControlledVocabularyProperty(String propertyName)
     {
-        return getProperty(propertyName);
+        if(getProperties() == null || getProperties().get(propertyName) == null) {
+            return null;
+        }
+        Serializable value = getProperties().get(propertyName);
+        if(value.getClass().isArray()) {
+            Serializable[] values = (Serializable[]) value;
+            return Arrays.stream(values).map(x->(String)x).toArray(String[]::new);
+        } else {
+            String propertyValue = (String) value;
+            return new String[]{ propertyValue };
+        }
     }
 
     @Override
-    public void setControlledVocabularyProperty(String propertyName, String propertyValue)
+    public void setControlledVocabularyProperty(String propertyName, String[] propertyValue)
     {
         setProperty(propertyName, propertyValue);
     }
 
     @Override
-    public SamplePermId getSampleProperty(String propertyName)
+    public SamplePermId[] getSampleProperty(String propertyName)
     {
-        String propertyValue = getProperty(propertyName);
-        return (propertyValue == null || propertyValue.trim().isEmpty()) ? null : new SamplePermId(propertyValue);
+        if(getProperties() == null || getProperties().get(propertyName) == null) {
+            return null;
+        }
+        Serializable value = getProperties().get(propertyName);
+        if(value.getClass().isArray()) {
+            Serializable[] values = (Serializable[]) value;
+            return Arrays.stream(values).map(x -> new SamplePermId((String)x)).toArray(SamplePermId[]::new);
+        } else {
+            String propertyValue = (String) value;
+            return new SamplePermId[]{new SamplePermId(propertyValue)};
+        }
     }
 
     @Override
-    public void setSampleProperty(String propertyName, SamplePermId propertyValue)
+    public void setSampleProperty(String propertyName, SamplePermId[] propertyValue)
     {
-        setProperty(propertyName, propertyValue == null ? null : propertyValue.getPermId());
+        setProperty(propertyName, propertyValue == null ? null : Arrays.stream(propertyValue)
+                .map(ObjectPermId::getPermId)
+                .toArray(String[]::new));
     }
 
     @Override
diff --git a/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/sample/update/SampleUpdate.java b/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/sample/update/SampleUpdate.java
index 60b6b33e984586e474a152e8a3ed45ae918001a9..bb3e514e275745b1f49954ef51fdefe0ea260655 100644
--- a/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/sample/update/SampleUpdate.java
+++ b/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/sample/update/SampleUpdate.java
@@ -20,6 +20,7 @@ import java.time.ZonedDateTime;
 import java.time.format.DateTimeFormatter;
 import java.util.*;
 
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.id.ObjectPermId;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.interfaces.IMetaDataUpdateHolder;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.property.PropertiesDeserializer;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.update.*;
@@ -232,7 +233,7 @@ public class SampleUpdate implements IUpdate, IPropertiesHolder, IObjectUpdate<I
 
     @Override
     @JsonIgnore
-    public void setProperty(String propertyName, String propertyValue)
+    public void setProperty(String propertyName, Serializable propertyValue)
     {
         properties.put(propertyName, propertyValue);
     }
@@ -452,30 +453,49 @@ public class SampleUpdate implements IUpdate, IPropertiesHolder, IObjectUpdate<I
     }
 
     @Override
-    public String getControlledVocabularyProperty(String propertyName)
+    public String[] getControlledVocabularyProperty(String propertyName)
     {
-        return getProperty(propertyName);
+        if(getProperties() == null || getProperties().get(propertyName) == null) {
+            return null;
+        }
+        Serializable value = getProperties().get(propertyName);
+        if(value.getClass().isArray()) {
+            Serializable[] values = (Serializable[]) value;
+            return Arrays.stream(values).map(x->(String)x).toArray(String[]::new);
+        } else {
+            String propertyValue = (String) value;
+            return new String[]{ propertyValue };
+        }
     }
 
     @Override
-    public void setControlledVocabularyProperty(String propertyName, String propertyValue)
+    public void setControlledVocabularyProperty(String propertyName, String[] propertyValue)
     {
         setProperty(propertyName, propertyValue);
     }
 
     @Override
-    public SamplePermId getSampleProperty(String propertyName)
+    public SamplePermId[] getSampleProperty(String propertyName)
     {
-        String propertyValue = getProperty(propertyName);
-        return (propertyValue == null || propertyValue.trim().isEmpty()) ?
-                null :
-                new SamplePermId(propertyValue);
+        if(getProperties() == null || getProperties().get(propertyName) == null) {
+            return null;
+        }
+        Serializable value = getProperties().get(propertyName);
+        if(value.getClass().isArray()) {
+            Serializable[] values = (Serializable[]) value;
+            return Arrays.stream(values).map(x -> new SamplePermId((String)x)).toArray(SamplePermId[]::new);
+        } else {
+            String propertyValue = (String) value;
+            return new SamplePermId[]{new SamplePermId(propertyValue)};
+        }
     }
 
     @Override
-    public void setSampleProperty(String propertyName, SamplePermId propertyValue)
+    public void setSampleProperty(String propertyName, SamplePermId[] propertyValue)
     {
-        setProperty(propertyName, propertyValue == null ? null : propertyValue.getPermId());
+        setProperty(propertyName, propertyValue == null ? null : Arrays.stream(propertyValue)
+                .map(ObjectPermId::getPermId)
+                .toArray(String[]::new));
     }
 
     @Override
diff --git a/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/helper/sort/PropertyComparator.java b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/helper/sort/PropertyComparator.java
index da6408bc6453b24871431bce926d0e30665f6ea5..62de73bffdad2b9aff4941a0d3ea0c922a4592b2 100644
--- a/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/helper/sort/PropertyComparator.java
+++ b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/helper/sort/PropertyComparator.java
@@ -17,6 +17,8 @@ package ch.ethz.sis.openbis.generic.server.asapi.v3.helper.sort;
 
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.interfaces.IPropertiesHolder;
 
+import java.io.Serializable;
+
 /**
  * @author pkupczyk
  */
@@ -33,6 +35,27 @@ public class PropertyComparator<OBJECT extends IPropertiesHolder> extends Abstra
     @Override
     protected String getValue(OBJECT o)
     {
-        return o.getProperty(propertyName);
+        return getAsString(o.getProperty(propertyName));
+    }
+
+    protected String getAsString(Serializable value) {
+        if(value == null) {
+            return null;
+        }
+        if(value.getClass().isArray()) {
+            Serializable[] values = (Serializable[]) value;
+            StringBuilder buffer = new StringBuilder("[");
+            for(Serializable serializable : values) {
+                if(buffer.length() > 1) {
+                    buffer.append(",");
+                }
+                buffer.append((String) serializable);
+            }
+            buffer.append("]");
+            return buffer.toString();
+        }
+        return (String) value;
     }
+
+
 }
diff --git a/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/helper/sort/SortAndPage.java b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/helper/sort/SortAndPage.java
index b1b73bb666607d83047d738d1fa411bd0989bf5a..031290814db7c91ae0459cf4caaa26cceae0a917 100644
--- a/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/helper/sort/SortAndPage.java
+++ b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/helper/sort/SortAndPage.java
@@ -33,7 +33,8 @@ import java.util.*;
  * @author pkupczyk
  */
 @SuppressWarnings({ "rawtypes", "unchecked", "cast" })
-public class SortAndPage {
+public class SortAndPage
+{
 
     private Set processed = new HashSet();
 
@@ -81,7 +82,11 @@ public class SortAndPage {
             } else if (objects instanceof Collection)
             {
                 sorted = new ArrayList(objects);
-                Collections.sort((List) sorted, comparator);
+                if (sorted.isEmpty() == false && ((ArrayList<?>) sorted).get(0).getClass()
+                        .isArray() == false)
+                {
+                    Collections.sort((List) sorted, comparator);
+                }
             }
 
             return sorted;
@@ -174,14 +179,17 @@ public class SortAndPage {
                                 sortAndPage(((Map) value).values(), c, subFo);
                             } else
                             {
-                                if (subFo.getSortBy() != null && subFo.getSortBy().getSortings() != null &&
+                                if (subFo.getSortBy() != null && subFo.getSortBy()
+                                        .getSortings() != null &&
                                         !subFo.getSortBy().getSortings().isEmpty())
                                 {
-                                    throw new IllegalArgumentException("Nested sort options can be used only "
-                                            + "for sorting nested collection or map types.");
+                                    throw new IllegalArgumentException(
+                                            "Nested sort options can be used only "
+                                                    + "for sorting nested collection or map types.");
                                 }
 
-                                Collection newValue = sortAndPage(Collections.singleton(value), c, subFo);
+                                Collection newValue =
+                                        sortAndPage(Collections.singleton(value), c, subFo);
                                 if (setMethod != null)
                                 {
                                     setMethod.invoke(object, newValue.iterator().next());
@@ -191,7 +199,9 @@ public class SortAndPage {
                     }
                 } catch (Exception e)
                 {
-                    throw new RuntimeException("Sorting and paging failed for object: + " + object + " and fieldName: " + fieldName, e);
+                    throw new RuntimeException(
+                            "Sorting and paging failed for object: + " + object + " and fieldName: " + fieldName,
+                            e);
                 }
             }
         }
@@ -230,18 +240,22 @@ public class SortAndPage {
                 {
                     if (sorting.getField() != null)
                     {
-                        ComparatorFactory comparatorFactory = ComparatorFactory.getInstance(sortByClass);
+                        ComparatorFactory comparatorFactory =
+                                ComparatorFactory.getInstance(sortByClass);
 
                         if (comparatorFactory == null)
                         {
-                            throw new IllegalArgumentException("Comparator factory for sort by " + sortByClass + " not found");
+                            throw new IllegalArgumentException(
+                                    "Comparator factory for sort by " + sortByClass + " not found");
                         }
 
-                        Comparator aComparator = comparatorFactory.getComparator(sorting.getField(), sorting.getParameters(), criteria);
+                        Comparator aComparator = comparatorFactory.getComparator(sorting.getField(),
+                                sorting.getParameters(), criteria);
 
                         if (aComparator == null)
                         {
-                            throw new IllegalArgumentException("Comparator for field " + sorting.getField() + " not found");
+                            throw new IllegalArgumentException(
+                                    "Comparator for field " + sorting.getField() + " not found");
                         }
 
                         comparators[index] = aComparator;
@@ -251,24 +265,24 @@ public class SortAndPage {
                 }
 
                 return new Comparator()
+                {
+                    @Override
+                    public int compare(Object o1, Object o2)
                     {
-                        @Override
-                        public int compare(Object o1, Object o2)
+                        for (int i = 0; i < sortings.size(); i++)
                         {
-                            for (int i = 0; i < sortings.size(); i++)
-                            {
-                                Comparator c = comparators[i];
-                                int d = directions[i];
+                            Comparator c = comparators[i];
+                            int d = directions[i];
 
-                                int result = d * c.compare(o1, o2);
-                                if (result != 0)
-                                {
-                                    return result;
-                                }
+                            int result = d * c.compare(o1, o2);
+                            if (result != 0)
+                            {
+                                return result;
                             }
-                            return 0;
                         }
-                    };
+                        return 0;
+                    }
+                };
             }
         }
 
@@ -338,7 +352,8 @@ public class SortAndPage {
                         continue;
                     }
 
-                    if (method.getName().startsWith("has") && false == method.getName().equals("hashCode"))
+                    if (method.getName().startsWith("has") && false == method.getName()
+                            .equals("hashCode"))
                     {
                         String fieldName = method.getName().substring(3);
 
@@ -350,7 +365,8 @@ public class SortAndPage {
                         fieldNames.add(fieldName);
                     } else if (method.getName().equals("sortBy"))
                     {
-                        if (sortByClass == null || sortByClass.isAssignableFrom(method.getReturnType()))
+                        if (sortByClass == null || sortByClass.isAssignableFrom(
+                                method.getReturnType()))
                         {
                             sortByClass = method.getReturnType();
                         }
@@ -358,7 +374,9 @@ public class SortAndPage {
                 }
             } catch (Exception e)
             {
-                throw new RuntimeException("Finding methods for fetch options class: " + clazz.getName() + " failed.", e);
+                throw new RuntimeException(
+                        "Finding methods for fetch options class: " + clazz.getName() + " failed.",
+                        e);
             }
         }
 
@@ -397,7 +415,9 @@ public class SortAndPage {
 
             for (PropertyDescriptor descriptor : descriptors)
             {
-                String fieldName = descriptor.getName().substring(0, 1).toUpperCase() + descriptor.getName().substring(1);
+                String fieldName =
+                        descriptor.getName().substring(0, 1).toUpperCase() + descriptor.getName()
+                                .substring(1);
                 getMethods.put(fieldName, descriptor.getReadMethod());
                 setMethods.put(fieldName, descriptor.getWriteMethod());
             }
diff --git a/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/translator/property/ISamplePropertyTranslator.java b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/translator/property/ISamplePropertyTranslator.java
index 96692184c5c3a3051ac384754990fd0220222de4..710b6987443e53ce077617025f5611225474a1d8 100644
--- a/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/translator/property/ISamplePropertyTranslator.java
+++ b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/translator/property/ISamplePropertyTranslator.java
@@ -22,7 +22,7 @@ import ch.ethz.sis.openbis.generic.asapi.v3.dto.sample.fetchoptions.SampleFetchO
 import ch.ethz.sis.openbis.generic.server.asapi.v3.translator.ITranslator;
 import ch.ethz.sis.openbis.generic.server.asapi.v3.translator.common.ObjectHolder;
 
-public interface ISamplePropertyTranslator extends ITranslator<Long, ObjectHolder<Map<String, Sample>>, SampleFetchOptions>
+public interface ISamplePropertyTranslator extends ITranslator<Long, ObjectHolder<Map<String, Sample[]>>, SampleFetchOptions>
 {
 
 }
diff --git a/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/translator/property/SamplePropertyTranslator.java b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/translator/property/SamplePropertyTranslator.java
index 83a9dffceb9686cf40bbd710ac258dd01835a394..158cda52d2e21f007aff5d095bc9eaf821e3be11 100644
--- a/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/translator/property/SamplePropertyTranslator.java
+++ b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/translator/property/SamplePropertyTranslator.java
@@ -15,11 +15,7 @@
  */
 package ch.ethz.sis.openbis.generic.server.asapi.v3.translator.property;
 
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
 
 import org.springframework.beans.factory.annotation.Autowired;
 
@@ -31,16 +27,16 @@ import ch.ethz.sis.openbis.generic.server.asapi.v3.translator.common.ObjectHolde
 import ch.ethz.sis.openbis.generic.server.asapi.v3.translator.sample.ISampleTranslator;
 
 public abstract class SamplePropertyTranslator extends
-        AbstractCachingTranslator<Long, ObjectHolder<Map<String, Sample>>, SampleFetchOptions> implements ISamplePropertyTranslator
+        AbstractCachingTranslator<Long, ObjectHolder<Map<String, Sample[]>>, SampleFetchOptions> implements ISamplePropertyTranslator
 {
 
     @Autowired
     private ISampleTranslator sampleTranslator;
 
     @Override
-    protected ObjectHolder<Map<String, Sample>> createObject(TranslationContext context, Long objectId, SampleFetchOptions fetchOptions)
+    protected ObjectHolder<Map<String, Sample[]>> createObject(TranslationContext context, Long objectId, SampleFetchOptions fetchOptions)
     {
-        return new ObjectHolder<Map<String, Sample>>();
+        return new ObjectHolder<Map<String, Sample[]>>();
     }
 
     @Override
@@ -56,20 +52,22 @@ public abstract class SamplePropertyTranslator extends
         }
 
         Map<Long, Sample> samples = sampleTranslator.translate(context, propertyValues, fetchOptions);
-        Map<Long, Map<String, Sample>> sampleProperties = new HashMap<Long, Map<String, Sample>>();
+        Map<Long, Map<String, Sample[]>> sampleProperties = new HashMap<Long, Map<String, Sample[]>>();
+        Map<Long, Map<String, List<Sample>>> samplePropertiesTmp = new HashMap<Long, Map<String, List<Sample>>>();
 
-        for (SamplePropertyRecord record : records)
-        {
-            Map<String, Sample> properties = sampleProperties.get(record.objectId);
-            if (properties == null)
-            {
-                properties = new HashMap<String, Sample>();
-                sampleProperties.put(record.objectId, properties);
-            }
+        for (SamplePropertyRecord record : records) {
+            Map<String, List<Sample>> properties =
+                    samplePropertiesTmp.computeIfAbsent(record.objectId, k -> new HashMap<>());
             Sample sample = samples.get(record.propertyValue);
-            if (sample != null)
-            {
-                properties.put(record.propertyCode, sample);
+            properties.computeIfAbsent(record.propertyCode, x -> new ArrayList<>());
+            properties.get(record.propertyCode).add(sample);
+        }
+
+        for(Map.Entry<Long, Map<String, List<Sample>>> entry : samplePropertiesTmp.entrySet()) {
+            sampleProperties.computeIfAbsent(entry.getKey(), x -> new HashMap<>());
+            Map<String, Sample[]> properties = sampleProperties.get(entry.getKey());
+            for(Map.Entry<String, List<Sample>> property : entry.getValue().entrySet()) {
+                properties.put(property.getKey(), property.getValue().toArray(new Sample[0]));
             }
         }
 
@@ -78,15 +76,15 @@ public abstract class SamplePropertyTranslator extends
 
     @SuppressWarnings("unchecked")
     @Override
-    protected void updateObject(TranslationContext context, Long objectId, ObjectHolder<Map<String, Sample>> result, Object relations,
+    protected void updateObject(TranslationContext context, Long objectId, ObjectHolder<Map<String, Sample[]>> result, Object relations,
             SampleFetchOptions fetchOptions)
     {
-        Map<Long, Map<String, Sample>> sampleProperties = (Map<Long, Map<String, Sample>>) relations;
-        Map<String, Sample> objectProperties = sampleProperties.get(objectId);
+        Map<Long, Map<String, Sample[]>> sampleProperties = (Map<Long, Map<String, Sample[]>>) relations;
+        Map<String, Sample[]> objectProperties = sampleProperties.get(objectId);
 
         if (objectProperties == null)
         {
-            objectProperties = new HashMap<String, Sample>();
+            objectProperties = new HashMap<String, Sample[]>();
         }
 
         result.setObject(objectProperties);
diff --git a/server-application-server/sourceTest/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/property/UpdateEntityPropertyExecutorTest.java b/server-application-server/sourceTest/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/property/UpdateEntityPropertyExecutorTest.java
index 3689273f929ceb444f1f21aa86e209800044149f..77a6ce3585cf0606b9860f97e35c2cc82bbbc03c 100644
--- a/server-application-server/sourceTest/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/property/UpdateEntityPropertyExecutorTest.java
+++ b/server-application-server/sourceTest/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/property/UpdateEntityPropertyExecutorTest.java
@@ -134,7 +134,7 @@ public class UpdateEntityPropertyExecutorTest extends AbstractEntityPropertyExec
         IPropertiesHolder holder = new IPropertiesHolder()
             {
                 @Override
-                public void setProperty(String propertyName, String propertyValue)
+                public void setProperty(String propertyName, Serializable propertyValue)
                 {
                     throw new UnsupportedOperationException();
                 }
@@ -170,26 +170,26 @@ public class UpdateEntityPropertyExecutorTest extends AbstractEntityPropertyExec
                 }
 
                 @Override
-                public String getControlledVocabularyProperty(String propertyName)
+                public String[] getControlledVocabularyProperty(String propertyName)
                 {
                     throw new UnsupportedOperationException();
                 }
 
                 @Override
                 public void setControlledVocabularyProperty(String propertyName,
-                        String propertyValue)
+                        String[] propertyValue)
                 {
                     throw new UnsupportedOperationException();
                 }
 
                 @Override
-                public SamplePermId getSampleProperty(String propertyName)
+                public SamplePermId[] getSampleProperty(String propertyName)
                 {
                     throw new UnsupportedOperationException();
                 }
 
                 @Override
-                public void setSampleProperty(String propertyName, SamplePermId propertyValue)
+                public void setSampleProperty(String propertyName, SamplePermId[] propertyValue)
                 {
                     throw new UnsupportedOperationException();
                 }
diff --git a/server-application-server/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/CreateDataSetTest.java b/server-application-server/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/CreateDataSetTest.java
index d31f15e18428741babf375913fa7499015080ff1..076afb23dff115d21abf07f553e8713ec47ff72e 100644
--- a/server-application-server/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/CreateDataSetTest.java
+++ b/server-application-server/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/CreateDataSetTest.java
@@ -18,6 +18,7 @@ package ch.ethz.sis.openbis.systemtest.asapi.v3;
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertNull;
 
+import java.io.Serializable;
 import java.time.ZonedDateTime;
 import java.util.Arrays;
 import java.util.Collections;
@@ -26,6 +27,9 @@ import java.util.List;
 import java.util.Map;
 import java.util.UUID;
 
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.property.create.PropertyTypeCreation;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.sample.fetchoptions.SampleFetchOptions;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.vocabulary.id.VocabularyPermId;
 import org.testng.annotations.Test;
 
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.id.CreationId;
@@ -2042,7 +2046,7 @@ public class CreateDataSetTest extends AbstractDataSetTest
         fetchOptions.withProperties();
         fetchOptions.withSampleProperties();
         DataSet dataSet = v3api.getDataSets(sessionToken, dataSetIds, fetchOptions).get(dataSetIds.get(0));
-        Sample sampleProperty = dataSet.getSampleProperties().get(propertyType.getPermId());
+        Sample sampleProperty = dataSet.getSampleProperties().get(propertyType.getPermId())[0];
         assertEquals(sampleProperty.getIdentifier().getIdentifier(), "/CISD/CL1");
         assertEquals(dataSet.getSampleProperties().size(), 1);
         assertEquals(dataSet.getProperties().get(PLATE_GEOMETRY.getPermId()), "384_WELLS_16X24");
@@ -2241,6 +2245,176 @@ public class CreateDataSetTest extends AbstractDataSetTest
         assertEquals(dataSet.getProperties().size(), 2);
     }
 
+
+    @Test
+    public void testCreateWithMultiValuePropertyVocabulary() {
+        // Given
+        String sessionToken = v3api.login(TEST_USER, PASSWORD);
+
+        final PropertyTypeCreation propertyTypeCreation = new PropertyTypeCreation();
+        propertyTypeCreation.setCode("TYPE-" + System.currentTimeMillis());
+        propertyTypeCreation.setDataType(DataType.CONTROLLEDVOCABULARY);
+        propertyTypeCreation.setLabel("label");
+        propertyTypeCreation.setDescription("description");
+        propertyTypeCreation.setMultiValue(true);
+        propertyTypeCreation.setVocabularyId(new VocabularyPermId("ORGANISM"));
+        PropertyTypePermId propertyType = v3api.createPropertyTypes(sessionToken, Collections.singletonList(propertyTypeCreation)).get(0);
+
+        EntityTypePermId dataSetType = createADataSetType(sessionToken, true, propertyType, PLATE_GEOMETRY);
+
+        DataSetCreation creation = physicalDataSetCreation();
+        creation.setTypeId(dataSetType);
+        creation.setProperty(PLATE_GEOMETRY.getPermId(), "384_WELLS_16X24");
+        creation.setControlledVocabularyProperty(propertyType.getPermId(), new String[] {"DOG", "HUMAN"});
+
+        // When
+        List<DataSetPermId> dataSetIds = v3api.createDataSets(sessionToken, Arrays.asList(creation));
+
+        // Then
+        assertEquals(dataSetIds.size(), 1);
+        DataSetFetchOptions fetchOptions = new DataSetFetchOptions();
+        fetchOptions.withProperties();
+        fetchOptions.withSampleProperties();
+        DataSet dataSet = v3api.getDataSets(sessionToken, dataSetIds, fetchOptions).get(dataSetIds.get(0));
+        assertEquals(dataSet.getProperties().get(PLATE_GEOMETRY.getPermId()), "384_WELLS_16X24");
+        String[] vocabProperties = dataSet.getControlledVocabularyProperty(propertyType.getPermId());
+        Arrays.sort(vocabProperties);
+        assertEquals(vocabProperties, new String[] {"DOG", "HUMAN"});
+        assertEquals(dataSet.getProperties().size(), 2);
+    }
+
+    @Test
+    public void testCreateWithMultiValuePropertySample() {
+        // Given
+        String sessionToken = v3api.login(TEST_USER, PASSWORD);
+
+        //Create sample
+        PropertyTypePermId propertyType1 = createASamplePropertyType(sessionToken, null);
+        EntityTypePermId sampleType = createASampleType(sessionToken, true, propertyType1, PLATE_GEOMETRY);
+
+        SampleCreation sample = new SampleCreation();
+        sample.setCode("SAMPLE_WITH_SAMPLE_PROPERTY");
+        sample.setTypeId(sampleType);
+        sample.setSpaceId(new SpacePermId("CISD"));
+        sample.setProperty(PLATE_GEOMETRY.getPermId(), "384_WELLS_16X24");
+        sample.setProperty(propertyType1.getPermId(), "200811050919915-8");
+
+        // When
+        List<SamplePermId> sampleIds = v3api.createSamples(sessionToken, Arrays.asList(sample));
+
+        // Then
+        assertEquals(sampleIds.size(), 1);
+
+        SampleFetchOptions sampleFetchOptions = new SampleFetchOptions();
+        sampleFetchOptions.withProperties();
+        sampleFetchOptions.withSampleProperties();
+        Sample sample2 = v3api.getSamples(sessionToken, sampleIds, sampleFetchOptions).get(sampleIds.get(0));
+
+        final PropertyTypeCreation propertyTypeCreation = new PropertyTypeCreation();
+        propertyTypeCreation.setCode("TYPE-" + System.currentTimeMillis());
+        propertyTypeCreation.setDataType(DataType.SAMPLE);
+        propertyTypeCreation.setLabel("label");
+        propertyTypeCreation.setDescription("description");
+        propertyTypeCreation.setMultiValue(true);
+        PropertyTypePermId propertyType = v3api.createPropertyTypes(sessionToken, Collections.singletonList(propertyTypeCreation)).get(0);
+
+        EntityTypePermId dataSetType = createADataSetType(sessionToken, true, propertyType, PLATE_GEOMETRY);
+
+
+        DataSetCreation creation = physicalDataSetCreation();
+        creation.setTypeId(dataSetType);
+        creation.setProperty(PLATE_GEOMETRY.getPermId(), "384_WELLS_16X24");
+        creation.setProperty(propertyType.getPermId(), new String[] {"/CISD/CL1", sampleIds.get(0).getPermId()});
+
+        // When
+        List<DataSetPermId> dataSetIds = v3api.createDataSets(sessionToken, Arrays.asList(creation));
+
+        // Then
+        assertEquals(dataSetIds.size(), 1);
+        DataSetFetchOptions fetchOptions = new DataSetFetchOptions();
+        fetchOptions.withProperties();
+        fetchOptions.withSampleProperties();
+        DataSet dataSet = v3api.getDataSets(sessionToken, dataSetIds, fetchOptions).get(dataSetIds.get(0));
+        assertEquals(dataSet.getProperties().get(PLATE_GEOMETRY.getPermId()), "384_WELLS_16X24");
+
+        Map<String, Sample[]> sampleProperties = dataSet.getSampleProperties();
+
+        Sample[] samples = sampleProperties.get(propertyType.getPermId());
+        Serializable[] sampleProps = Arrays.stream(samples).map(x -> x.getPermId().getPermId()).sorted().toArray(String[]::new);
+        assertEquals(sampleProps, new Serializable[]{"200811050919915-8", sample2.getPermId().getPermId()});
+
+        sampleProps = (Serializable[]) dataSet.getProperties().get(propertyType.getPermId());
+        Arrays.sort(sampleProps);
+        assertEquals(sampleProps, new Serializable[]{"200811050919915-8", sample2.getPermId().getPermId()});
+        assertEquals(dataSet.getProperties().size(), 2);
+    }
+
+    @Test
+    public void testCreateWithMultiValuePropertySample2() {
+        // Given
+        String sessionToken = v3api.login(TEST_USER, PASSWORD);
+
+        //Create sample
+        PropertyTypePermId propertyType1 = createASamplePropertyType(sessionToken, null);
+        EntityTypePermId sampleType = createASampleType(sessionToken, true, propertyType1, PLATE_GEOMETRY);
+
+        SampleCreation sample = new SampleCreation();
+        sample.setCode("SAMPLE_WITH_SAMPLE_PROPERTY");
+        sample.setTypeId(sampleType);
+        sample.setSpaceId(new SpacePermId("CISD"));
+        sample.setProperty(PLATE_GEOMETRY.getPermId(), "384_WELLS_16X24");
+        sample.setProperty(propertyType1.getPermId(), "200811050919915-8");
+
+        // When
+        List<SamplePermId> sampleIds = v3api.createSamples(sessionToken, Arrays.asList(sample));
+
+        // Then
+        assertEquals(sampleIds.size(), 1);
+
+        SampleFetchOptions sampleFetchOptions = new SampleFetchOptions();
+        sampleFetchOptions.withProperties();
+        sampleFetchOptions.withSampleProperties();
+        Sample sample2 = v3api.getSamples(sessionToken, sampleIds, sampleFetchOptions).get(sampleIds.get(0));
+
+        final PropertyTypeCreation propertyTypeCreation = new PropertyTypeCreation();
+        propertyTypeCreation.setCode("TYPE-" + System.currentTimeMillis());
+        propertyTypeCreation.setDataType(DataType.SAMPLE);
+        propertyTypeCreation.setLabel("label");
+        propertyTypeCreation.setDescription("description");
+        propertyTypeCreation.setMultiValue(true);
+        PropertyTypePermId propertyType = v3api.createPropertyTypes(sessionToken, Collections.singletonList(propertyTypeCreation)).get(0);
+
+        EntityTypePermId dataSetType = createADataSetType(sessionToken, true, propertyType, PLATE_GEOMETRY);
+
+
+        DataSetCreation creation = physicalDataSetCreation();
+        creation.setTypeId(dataSetType);
+        creation.setProperty(PLATE_GEOMETRY.getPermId(), "384_WELLS_16X24");
+        creation.setSampleProperty(propertyType.getPermId(), new SamplePermId[]{ new SamplePermId("/CISD/CL1"), sampleIds.get(0)});
+
+        // When
+        List<DataSetPermId> dataSetIds = v3api.createDataSets(sessionToken, Arrays.asList(creation));
+
+        // Then
+        assertEquals(dataSetIds.size(), 1);
+        DataSetFetchOptions fetchOptions = new DataSetFetchOptions();
+        fetchOptions.withProperties();
+        fetchOptions.withSampleProperties();
+        DataSet dataSet = v3api.getDataSets(sessionToken, dataSetIds, fetchOptions).get(dataSetIds.get(0));
+        assertEquals(dataSet.getProperties().get(PLATE_GEOMETRY.getPermId()), "384_WELLS_16X24");
+
+        Map<String, Sample[]> sampleProperties = dataSet.getSampleProperties();
+
+        Sample[] samples = sampleProperties.get(propertyType.getPermId());
+        Serializable[] sampleProps = Arrays.stream(samples).map(x -> x.getPermId().getPermId()).sorted().toArray(String[]::new);
+        assertEquals(sampleProps, new Serializable[]{"200811050919915-8", sample2.getPermId().getPermId()});
+
+        sampleProps = (Serializable[]) dataSet.getProperties().get(propertyType.getPermId());
+        Arrays.sort(sampleProps);
+        assertEquals(sampleProps, new Serializable[]{"200811050919915-8", sample2.getPermId().getPermId()});
+        assertEquals(dataSet.getProperties().size(), 2);
+    }
+
     @Test
     public void testCreateWithMetaData()
     {
diff --git a/server-application-server/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/CreateExperimentTest.java b/server-application-server/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/CreateExperimentTest.java
index af439024ef27298f2d9a6b26f6b7754f16949c68..26f4652b764a05ac8545aa60c5104a38d4058515 100644
--- a/server-application-server/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/CreateExperimentTest.java
+++ b/server-application-server/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/CreateExperimentTest.java
@@ -19,6 +19,7 @@ import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertNotNull;
 import static org.testng.Assert.assertTrue;
 
+import java.io.Serializable;
 import java.time.ZonedDateTime;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -29,6 +30,11 @@ import java.util.List;
 import java.util.Map;
 import java.util.UUID;
 
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.property.create.PropertyTypeCreation;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.sample.fetchoptions.SampleFetchOptions;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.sample.id.SamplePermId;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.space.id.SpacePermId;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.vocabulary.id.VocabularyPermId;
 import org.testng.annotations.Test;
 
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.attachment.Attachment;
@@ -798,7 +804,7 @@ public class CreateExperimentTest extends AbstractExperimentTest
         fetchOptions.withProperties();
         fetchOptions.withSampleProperties();
         Experiment experiment2 = v3api.getExperiments(sessionToken, experimentIds, fetchOptions).get(experimentIds.get(0));
-        Sample sampleProperty = experiment2.getSampleProperties().get(propertyType.getPermId());
+        Sample sampleProperty = experiment2.getSampleProperties().get(propertyType.getPermId())[0];
         assertEquals(sampleProperty.getIdentifier().getIdentifier(), "/CISD/CL1");
         assertEquals(experiment2.getSampleProperties().size(), 1);
         assertEquals(experiment2.getProperties().get(PLATE_GEOMETRY.getPermId()), "384_WELLS_16X24");
@@ -1011,6 +1017,180 @@ public class CreateExperimentTest extends AbstractExperimentTest
         assertEquals(experiment2.getProperties().size(), 2);
     }
 
+    @Test
+    public void testCreateWithMultiValuePropertyVocabulary()
+    {
+        // Given
+        String sessionToken = v3api.login(TEST_USER, PASSWORD);
+
+        final PropertyTypeCreation propertyTypeCreation = new PropertyTypeCreation();
+        propertyTypeCreation.setCode("TYPE-" + System.currentTimeMillis());
+        propertyTypeCreation.setDataType(DataType.CONTROLLEDVOCABULARY);
+        propertyTypeCreation.setLabel("label");
+        propertyTypeCreation.setDescription("description");
+        propertyTypeCreation.setMultiValue(true);
+        propertyTypeCreation.setVocabularyId(new VocabularyPermId("ORGANISM"));
+        PropertyTypePermId propertyType = v3api.createPropertyTypes(sessionToken, Collections.singletonList(propertyTypeCreation)).get(0);
+
+        EntityTypePermId experimentType = createAnExperimentType(sessionToken, true, propertyType, PLATE_GEOMETRY);
+
+        ExperimentCreation creation = new ExperimentCreation();
+        creation.setCode("EXPERIMENT_WITH_SAMPLE_PROPERTY");
+        creation.setTypeId(experimentType);
+        creation.setProjectId(new ProjectIdentifier("/CISD/NEMO"));
+        creation.setProperty(PLATE_GEOMETRY.getPermId(), "384_WELLS_16X24");
+        creation.setControlledVocabularyProperty(propertyType.getPermId(), new String[] {"DOG", "HUMAN"});
+
+        // When
+        List<ExperimentPermId> experimentIds = v3api.createExperiments(sessionToken, Arrays.asList(creation));
+
+        // Then
+        assertEquals(experimentIds.size(), 1);
+        ExperimentFetchOptions fetchOptions = new ExperimentFetchOptions();
+        fetchOptions.withProperties();
+        fetchOptions.withSampleProperties();
+        Experiment experiment2 = v3api.getExperiments(sessionToken, experimentIds, fetchOptions).get(experimentIds.get(0));
+        assertEquals(experiment2.getProperties().get(PLATE_GEOMETRY.getPermId()), "384_WELLS_16X24");
+        String[] vocabProperties = experiment2.getControlledVocabularyProperty(propertyType.getPermId());
+        Arrays.sort(vocabProperties);
+        assertEquals(vocabProperties, new String[] {"DOG", "HUMAN"});
+        assertEquals(experiment2.getProperties().size(), 2);
+    }
+
+    @Test
+    public void testCreateWithMultiValuePropertySample()
+    {
+        // Given
+        String sessionToken = v3api.login(TEST_USER, PASSWORD);
+
+        //Create sample
+        PropertyTypePermId propertyType1 = createASamplePropertyType(sessionToken, null);
+        EntityTypePermId sampleType = createASampleType(sessionToken, true, propertyType1, PLATE_GEOMETRY);
+
+        SampleCreation sample = new SampleCreation();
+        sample.setCode("SAMPLE_WITH_SAMPLE_PROPERTY");
+        sample.setTypeId(sampleType);
+        sample.setSpaceId(new SpacePermId("CISD"));
+        sample.setProperty(PLATE_GEOMETRY.getPermId(), "384_WELLS_16X24");
+        sample.setProperty(propertyType1.getPermId(), "200811050919915-8");
+
+        // When
+        List<SamplePermId> sampleIds = v3api.createSamples(sessionToken, Arrays.asList(sample));
+
+        // Then
+        assertEquals(sampleIds.size(), 1);
+
+        SampleFetchOptions sampleFetchOptions = new SampleFetchOptions();
+        sampleFetchOptions.withProperties();
+        sampleFetchOptions.withSampleProperties();
+        Sample sample2 = v3api.getSamples(sessionToken, sampleIds, sampleFetchOptions).get(sampleIds.get(0));
+
+        final PropertyTypeCreation propertyTypeCreation = new PropertyTypeCreation();
+        propertyTypeCreation.setCode("TYPE-" + System.currentTimeMillis());
+        propertyTypeCreation.setDataType(DataType.SAMPLE);
+        propertyTypeCreation.setLabel("label");
+        propertyTypeCreation.setDescription("description");
+        propertyTypeCreation.setMultiValue(true);
+        PropertyTypePermId propertyType = v3api.createPropertyTypes(sessionToken, Collections.singletonList(propertyTypeCreation)).get(0);
+
+        EntityTypePermId experimentType = createAnExperimentType(sessionToken, true, propertyType, PLATE_GEOMETRY);
+
+        ExperimentCreation creation = new ExperimentCreation();
+        creation.setCode("EXPERIMENT_WITH_SAMPLE_PROPERTY");
+        creation.setTypeId(experimentType);
+        creation.setProjectId(new ProjectIdentifier("/CISD/NEMO"));
+        creation.setProperty(PLATE_GEOMETRY.getPermId(), "384_WELLS_16X24");
+        creation.setProperty(propertyType.getPermId(), new String[] {"/CISD/CL1", sampleIds.get(0).getPermId()});
+
+        // When
+        List<ExperimentPermId> experimentIds = v3api.createExperiments(sessionToken, Arrays.asList(creation));
+
+        // Then
+        assertEquals(experimentIds.size(), 1);
+        ExperimentFetchOptions fetchOptions = new ExperimentFetchOptions();
+        fetchOptions.withProperties();
+        fetchOptions.withSampleProperties();
+        Experiment experiment2 = v3api.getExperiments(sessionToken, experimentIds, fetchOptions).get(experimentIds.get(0));
+        assertEquals(experiment2.getProperties().get(PLATE_GEOMETRY.getPermId()), "384_WELLS_16X24");
+        Map<String, Sample[]> sampleProperties = experiment2.getSampleProperties();
+
+        Sample[] samples = sampleProperties.get(propertyType.getPermId());
+        Serializable[] sampleProps = Arrays.stream(samples).map(x -> x.getPermId().getPermId()).sorted().toArray(String[]::new);
+        assertEquals(sampleProps, new Serializable[]{"200811050919915-8", sample2.getPermId().getPermId()});
+
+        sampleProps = (Serializable[]) experiment2.getProperties().get(propertyType.getPermId());
+        Arrays.sort(sampleProps);
+        assertEquals(sampleProps, new Serializable[]{"200811050919915-8", sample2.getPermId().getPermId()});
+        assertEquals(experiment2.getProperties().size(), 2);
+    }
+
+    @Test
+    public void testCreateWithMultiValuePropertySample2()
+    {
+        // Given
+        String sessionToken = v3api.login(TEST_USER, PASSWORD);
+
+        //Create sample
+        PropertyTypePermId propertyType1 = createASamplePropertyType(sessionToken, null);
+        EntityTypePermId sampleType = createASampleType(sessionToken, true, propertyType1, PLATE_GEOMETRY);
+
+        SampleCreation sample = new SampleCreation();
+        sample.setCode("SAMPLE_WITH_SAMPLE_PROPERTY");
+        sample.setTypeId(sampleType);
+        sample.setSpaceId(new SpacePermId("CISD"));
+        sample.setProperty(PLATE_GEOMETRY.getPermId(), "384_WELLS_16X24");
+        sample.setProperty(propertyType1.getPermId(), "200811050919915-8");
+
+        // When
+        List<SamplePermId> sampleIds = v3api.createSamples(sessionToken, Arrays.asList(sample));
+
+        // Then
+        assertEquals(sampleIds.size(), 1);
+
+        SampleFetchOptions sampleFetchOptions = new SampleFetchOptions();
+        sampleFetchOptions.withProperties();
+        sampleFetchOptions.withSampleProperties();
+        Sample sample2 = v3api.getSamples(sessionToken, sampleIds, sampleFetchOptions).get(sampleIds.get(0));
+
+        final PropertyTypeCreation propertyTypeCreation = new PropertyTypeCreation();
+        propertyTypeCreation.setCode("TYPE-" + System.currentTimeMillis());
+        propertyTypeCreation.setDataType(DataType.SAMPLE);
+        propertyTypeCreation.setLabel("label");
+        propertyTypeCreation.setDescription("description");
+        propertyTypeCreation.setMultiValue(true);
+        PropertyTypePermId propertyType = v3api.createPropertyTypes(sessionToken, Collections.singletonList(propertyTypeCreation)).get(0);
+
+        EntityTypePermId experimentType = createAnExperimentType(sessionToken, true, propertyType, PLATE_GEOMETRY);
+
+        ExperimentCreation creation = new ExperimentCreation();
+        creation.setCode("EXPERIMENT_WITH_SAMPLE_PROPERTY");
+        creation.setTypeId(experimentType);
+        creation.setProjectId(new ProjectIdentifier("/CISD/NEMO"));
+        creation.setProperty(PLATE_GEOMETRY.getPermId(), "384_WELLS_16X24");
+        creation.setSampleProperty(propertyType.getPermId(), new SamplePermId[]{ new SamplePermId("/CISD/CL1"), sampleIds.get(0)});
+
+        // When
+        List<ExperimentPermId> experimentIds = v3api.createExperiments(sessionToken, Arrays.asList(creation));
+
+        // Then
+        assertEquals(experimentIds.size(), 1);
+        ExperimentFetchOptions fetchOptions = new ExperimentFetchOptions();
+        fetchOptions.withProperties();
+        fetchOptions.withSampleProperties();
+        Experiment experiment2 = v3api.getExperiments(sessionToken, experimentIds, fetchOptions).get(experimentIds.get(0));
+        assertEquals(experiment2.getProperties().get(PLATE_GEOMETRY.getPermId()), "384_WELLS_16X24");
+        Map<String, Sample[]> sampleProperties = experiment2.getSampleProperties();
+
+        Sample[] samples = sampleProperties.get(propertyType.getPermId());
+        Serializable[] sampleProps = Arrays.stream(samples).map(x -> x.getPermId().getPermId()).sorted().toArray(String[]::new);
+        assertEquals(sampleProps, new Serializable[]{"200811050919915-8", sample2.getPermId().getPermId()});
+
+        sampleProps = (Serializable[]) experiment2.getProperties().get(propertyType.getPermId());
+        Arrays.sort(sampleProps);
+        assertEquals(sampleProps, new Serializable[]{"200811050919915-8", sample2.getPermId().getPermId()});
+        assertEquals(experiment2.getProperties().size(), 2);
+    }
+
     @Test
     public void testCreateWithMetaData()
     {
diff --git a/server-application-server/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/CreateSampleTest.java b/server-application-server/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/CreateSampleTest.java
index 180d7bd3b6de7edb3938e57c7af3062f1290649e..631c85eb6de0d2aedafafdeb3324643b6babd4b6 100644
--- a/server-application-server/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/CreateSampleTest.java
+++ b/server-application-server/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/CreateSampleTest.java
@@ -18,6 +18,7 @@ package ch.ethz.sis.openbis.systemtest.asapi.v3;
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertNull;
 
+import java.io.Serializable;
 import java.time.ZonedDateTime;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -28,6 +29,8 @@ import java.util.List;
 import java.util.Map;
 import java.util.UUID;
 
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.property.create.PropertyTypeCreation;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.vocabulary.id.VocabularyPermId;
 import org.testng.annotations.DataProvider;
 import org.testng.annotations.Test;
 
@@ -1400,7 +1403,7 @@ public class CreateSampleTest extends AbstractSampleTest
         fetchOptions.withProperties();
         fetchOptions.withSampleProperties();
         Sample sample2 = v3api.getSamples(sessionToken, sampleIds, fetchOptions).get(sampleIds.get(0));
-        Sample sampleProperty = sample2.getSampleProperties().get(propertyType.getPermId());
+        Sample sampleProperty = sample2.getSampleProperties().get(propertyType.getPermId())[0];
         assertEquals(sampleProperty.getPermId().getPermId(), "200811050947161-652");
         assertEquals(sampleProperty.getIdentifier().getIdentifier(), "/MP");
         assertEquals(sample2.getSampleProperties().size(), 1);
@@ -1433,7 +1436,7 @@ public class CreateSampleTest extends AbstractSampleTest
         fetchOptions.withProperties();
         fetchOptions.withSampleProperties();
         Sample sample2 = v3api.getSamples(sessionToken, sampleIds, fetchOptions).get(sampleIds.get(0));
-        Sample sampleProperty = sample2.getSampleProperties().get(propertyType.getPermId());
+        Sample sampleProperty = sample2.getSampleProperties().get(propertyType.getPermId())[0];
         assertEquals(sampleProperty.getPermId().getPermId(), "200811050919915-8");
         assertEquals(sampleProperty.getIdentifier().getIdentifier(), "/CISD/CL1");
         assertEquals(sample2.getSampleProperties().size(), 1);
@@ -1668,6 +1671,169 @@ public class CreateSampleTest extends AbstractSampleTest
         assertEquals(sample2.getProperties().size(), 2);
     }
 
+    @Test
+    public void testCreateWithMultiValuePropertyVocabulary()
+    {
+        // Given
+        String sessionToken = v3api.login(TEST_USER, PASSWORD);
+
+        final PropertyTypeCreation propertyTypeCreation = new PropertyTypeCreation();
+        propertyTypeCreation.setCode("TYPE-" + System.currentTimeMillis());
+        propertyTypeCreation.setDataType(DataType.CONTROLLEDVOCABULARY);
+        propertyTypeCreation.setLabel("label");
+        propertyTypeCreation.setDescription("description");
+        propertyTypeCreation.setMultiValue(true);
+        propertyTypeCreation.setVocabularyId(new VocabularyPermId("ORGANISM"));
+        PropertyTypePermId propertyType = v3api.createPropertyTypes(sessionToken, Collections.singletonList(propertyTypeCreation)).get(0);
+
+        EntityTypePermId sampleType = createASampleType(sessionToken, true, propertyType, PLATE_GEOMETRY);
+
+        SampleCreation sample = new SampleCreation();
+        sample.setCode("SAMPLE_WITH_MULTI_VOCAB_PROPERTY");
+        sample.setTypeId(sampleType);
+        sample.setSpaceId(new SpacePermId("CISD"));
+        sample.setProperty(PLATE_GEOMETRY.getPermId(), "384_WELLS_16X24");
+        sample.setControlledVocabularyProperty(propertyType.getPermId(), new String[] {"DOG", "HUMAN"});
+
+        // When
+        List<SamplePermId> sampleIds = v3api.createSamples(sessionToken, Arrays.asList(sample));
+
+        // Then
+        assertEquals(sampleIds.size(), 1);
+        SampleFetchOptions fetchOptions = new SampleFetchOptions();
+        fetchOptions.withProperties();
+        fetchOptions.withSampleProperties();
+        Sample sample2 = v3api.getSamples(sessionToken, sampleIds, fetchOptions).get(sampleIds.get(0));
+        assertEquals(sample2.getProperties().get(PLATE_GEOMETRY.getPermId()), "384_WELLS_16X24");
+        String[] vocabProperties = sample2.getControlledVocabularyProperty(propertyType.getPermId());
+        Arrays.sort(vocabProperties);
+        assertEquals(vocabProperties, new String[] {"DOG", "HUMAN"});
+        assertEquals(sample2.getProperties().size(), 2);
+    }
+
+    @Test
+    public void testCreateWithMultiValuePropertySample() {
+        // Given
+        String sessionToken = v3api.login(TEST_USER, PASSWORD);
+
+        //Create new sample that will be used as property
+        PropertyTypePermId propertyType1 = createASamplePropertyType(sessionToken, null);
+        EntityTypePermId sampleType1 = createASampleType(sessionToken, true, propertyType1, PLATE_GEOMETRY);
+
+        SampleCreation sample = new SampleCreation();
+        sample.setCode("NEW_SAMPLE_WITH_SOME_SAMPLE_PROPERTY");
+        sample.setTypeId(sampleType1);
+        sample.setSpaceId(new SpacePermId("CISD"));
+        sample.setProperty(PLATE_GEOMETRY.getPermId(), "384_WELLS_16X24");
+        sample.setProperty(propertyType1.getPermId(), "200811050919915-8");
+
+        List<SamplePermId> testSampleIds = v3api.createSamples(sessionToken, Arrays.asList(sample));
+
+        assertEquals(testSampleIds.size(), 1);
+
+        // Create multi-value sample property
+        final PropertyTypeCreation propertyTypeCreation = new PropertyTypeCreation();
+        propertyTypeCreation.setCode("TYPE-" + System.currentTimeMillis());
+        propertyTypeCreation.setDataType(DataType.SAMPLE);
+        propertyTypeCreation.setLabel("label");
+        propertyTypeCreation.setDescription("description");
+        propertyTypeCreation.setMultiValue(true);
+        PropertyTypePermId propertyType = v3api.createPropertyTypes(sessionToken, Collections.singletonList(propertyTypeCreation)).get(0);
+
+        EntityTypePermId sampleType = createASampleType(sessionToken, true, propertyType, PLATE_GEOMETRY);
+
+        // Create a sample with multi-value sample property
+        SampleCreation sampleCreation = new SampleCreation();
+        sampleCreation.setCode("SAMPLE_WITH_MULTI_SAMPLE_PROPERTY");
+        sampleCreation.setTypeId(sampleType);
+        sampleCreation.setSpaceId(new SpacePermId("CISD"));
+        sampleCreation.setProperty(PLATE_GEOMETRY.getPermId(), "384_WELLS_16X24");
+        sampleCreation.setProperty(propertyType.getPermId(), new String[] {"/CISD/CL1", testSampleIds.get(0).getPermId()});
+
+        // When
+        List<SamplePermId> sampleIds = v3api.createSamples(sessionToken, Arrays.asList(sample));
+
+        // Then
+        assertEquals(sampleIds.size(), 1);
+        SampleFetchOptions fetchOptions = new SampleFetchOptions();
+        fetchOptions.withProperties();
+        fetchOptions.withSampleProperties();
+        Sample sample2 = v3api.getSamples(sessionToken, sampleIds, fetchOptions).get(sampleIds.get(0));
+        assertEquals(sample2.getProperties().get(PLATE_GEOMETRY.getPermId()), "384_WELLS_16X24");
+
+        Map<String, Sample[]> sampleProperties = sample2.getSampleProperties();
+
+        Sample[] samples = sampleProperties.get(propertyType.getPermId());
+        Serializable[] sampleProps = Arrays.stream(samples).map(x -> x.getPermId().getPermId()).sorted().toArray(String[]::new);
+        assertEquals(sampleProps, new Serializable[]{"200811050919915-8", sample2.getPermId().getPermId()});
+
+        sampleProps = (Serializable[]) sample2.getProperties().get(propertyType.getPermId());
+        Arrays.sort(sampleProps);
+        assertEquals(sampleProps, new Serializable[]{"200811050919915-8", sample2.getPermId().getPermId()});
+        assertEquals(sample2.getProperties().size(), 2);
+    }
+
+    @Test
+    public void testCreateWithMultiValuePropertySample2() {
+        // Given
+        String sessionToken = v3api.login(TEST_USER, PASSWORD);
+
+        //Create sample
+        PropertyTypePermId propertyType1 = createASamplePropertyType(sessionToken, null);
+        EntityTypePermId sampleType1 = createASampleType(sessionToken, true, propertyType1, PLATE_GEOMETRY);
+
+        SampleCreation sample = new SampleCreation();
+        sample.setCode("SAMPLE_WITH_SOME_SAMPLE_PROPERTY");
+        sample.setTypeId(sampleType1);
+        sample.setSpaceId(new SpacePermId("CISD"));
+        sample.setProperty(PLATE_GEOMETRY.getPermId(), "384_WELLS_16X24");
+        sample.setProperty(propertyType1.getPermId(), "200811050919915-8");
+
+        List<SamplePermId> testSampleIds = v3api.createSamples(sessionToken, Arrays.asList(sample));
+
+        assertEquals(testSampleIds.size(), 1);
+
+
+        final PropertyTypeCreation propertyTypeCreation = new PropertyTypeCreation();
+        propertyTypeCreation.setCode("TYPE-" + System.currentTimeMillis());
+        propertyTypeCreation.setDataType(DataType.SAMPLE);
+        propertyTypeCreation.setLabel("label");
+        propertyTypeCreation.setDescription("description");
+        propertyTypeCreation.setMultiValue(true);
+        PropertyTypePermId propertyType = v3api.createPropertyTypes(sessionToken, Collections.singletonList(propertyTypeCreation)).get(0);
+
+        EntityTypePermId sampleType = createASampleType(sessionToken, true, propertyType, PLATE_GEOMETRY);
+
+        SampleCreation sampleCreation = new SampleCreation();
+        sampleCreation.setCode("SAMPLE_WITH_MULTI_SAMPLE_PROPERTY2");
+        sampleCreation.setTypeId(sampleType);
+        sampleCreation.setSpaceId(new SpacePermId("CISD"));
+        sampleCreation.setProperty(PLATE_GEOMETRY.getPermId(), "384_WELLS_16X24");
+        sampleCreation.setSampleProperty(propertyType.getPermId(), new SamplePermId[]{ new SamplePermId("/CISD/CL1"), testSampleIds.get(0)});
+
+        // When
+        List<SamplePermId> sampleIds = v3api.createSamples(sessionToken, Arrays.asList(sample));
+
+        // Then
+        assertEquals(sampleIds.size(), 1);
+        SampleFetchOptions fetchOptions = new SampleFetchOptions();
+        fetchOptions.withProperties();
+        fetchOptions.withSampleProperties();
+        Sample sample2 = v3api.getSamples(sessionToken, sampleIds, fetchOptions).get(sampleIds.get(0));
+        assertEquals(sample2.getProperties().get(PLATE_GEOMETRY.getPermId()), "384_WELLS_16X24");
+
+        Map<String, Sample[]> sampleProperties = sample2.getSampleProperties();
+
+        Sample[] samples = sampleProperties.get(propertyType.getPermId());
+        Serializable[] sampleProps = Arrays.stream(samples).map(x -> x.getPermId().getPermId()).sorted().toArray(String[]::new);
+        assertEquals(sampleProps, new Serializable[]{"200811050919915-8", sample2.getPermId().getPermId()});
+
+        sampleProps = (Serializable[]) sample2.getProperties().get(propertyType.getPermId());
+        Arrays.sort(sampleProps);
+        assertEquals(sampleProps, new Serializable[]{"200811050919915-8", sample2.getPermId().getPermId()});
+        assertEquals(sample2.getProperties().size(), 2);
+    }
+
     @Test
     public void testCreateWithMetaData()
     {