diff --git a/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/common/entity/AbstractEntityPropertyHolder.java b/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/common/entity/AbstractEntityPropertyHolder.java
index e1ada597066e1ffa829e91cf36efd700a7d0e3e2..ecd61bf70d3b8567551875b18f694c1a3d2a61b4 100644
--- a/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/common/entity/AbstractEntityPropertyHolder.java
+++ b/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/common/entity/AbstractEntityPropertyHolder.java
@@ -30,6 +30,8 @@ import java.io.Serializable;
 import java.time.ZonedDateTime;
 import java.time.format.DateTimeFormatter;
 import java.util.*;
+import java.util.function.Function;
+import java.util.stream.Collectors;
 
 @JsonObject("as.dto.common.entity.AbstractEntityPropertyHolder")
 public abstract class AbstractEntityPropertyHolder implements Serializable, IPropertiesHolder
@@ -45,13 +47,19 @@ public abstract class AbstractEntityPropertyHolder implements Serializable, IPro
     public abstract void setProperties(Map<String, Serializable> properties);
 
     @Override
-    public Serializable getProperty(String propertyName) // String!!!
+    public String getPropertyAsString(String propertyName)
     {
         return getProperties() != null ?
                 PropertiesDeserializer.getPropertyAsString(getProperties().get(propertyName)) :
                 null;
     }
 
+    @Override
+    public Serializable getProperty(String propertyName)
+    {
+        return getProperties() != null ? getProperties().get(propertyName) : null;
+    }
+
     @Override
     public void setProperty(String propertyName, Serializable propertyValue)
     {
@@ -65,7 +73,7 @@ public abstract class AbstractEntityPropertyHolder implements Serializable, IPro
     @Override
     public Long getIntegerProperty(String propertyName)
     {
-        String propertyValue = (String) getProperty(propertyName);
+        String propertyValue = getPropertyAsString(propertyName);
         return (propertyValue == null || propertyValue.trim().isEmpty()) ?
                 null :
                 Long.parseLong(propertyValue);
@@ -121,7 +129,10 @@ public abstract class AbstractEntityPropertyHolder implements Serializable, IPro
     public ZonedDateTime getTimestampProperty(String propertyName)
     {
         String propertyValue = (String) getProperty(propertyName);
-        return propertyValue == null ? null : ZonedDateTime.parse(propertyValue);
+        return propertyValue == null ?
+                null :
+                ZonedDateTime.parse(propertyValue,
+                        DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss X"));
     }
 
     @Override
@@ -129,7 +140,7 @@ public abstract class AbstractEntityPropertyHolder implements Serializable, IPro
     {
         String value = (propertyValue == null) ?
                 null :
-                propertyValue.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ssX"));
+                propertyValue.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss Z"));
         setProperty(propertyName, value);
     }
 
@@ -150,56 +161,29 @@ public abstract class AbstractEntityPropertyHolder implements Serializable, IPro
     }
 
     @Override
-    public String[] getControlledVocabularyProperty(String propertyName)
+    public String getControlledVocabularyProperty(String 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 };
-        }
+        return (String) getProperty(propertyName);
     }
 
     @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)
     {
-        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) };
-        }
+
+        String propertyValue = (String) getProperty(propertyName);
+        return new SamplePermId(propertyValue);
     }
 
     @Override
-    public void setSampleProperty(String propertyName, SamplePermId[] propertyValue)
+    public void setSampleProperty(String propertyName, SamplePermId propertyValue)
     {
-        setProperty(propertyName, propertyValue == null ? null : Arrays.stream(propertyValue)
-                .map(ObjectPermId::getPermId)
-                .toArray(String[]::new));
+        setProperty(propertyName, propertyValue == null ? null : propertyValue.getPermId());
     }
 
     @Override
@@ -301,8 +285,9 @@ public abstract class AbstractEntityPropertyHolder implements Serializable, IPro
     public void setTimestampArrayProperty(String propertyName, ZonedDateTime[] propertyValue)
     {
         String[] value = (propertyValue == null) ? null : Arrays.stream(propertyValue)
-                .map(dateTime -> dateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ssX")))
-                        .toArray(String[]::new);
+                .map(dateTime -> dateTime.format(
+                        DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss Z")))
+                .toArray(String[]::new);
         setProperty(propertyName, value);
     }
 
@@ -317,4 +302,359 @@ public abstract class AbstractEntityPropertyHolder implements Serializable, IPro
     {
         setProperty(propertyName, propertyValue);
     }
+
+    private <T> List<T> getListOfValues(Serializable generalValue, Function<Serializable, T> fun)
+    {
+        if (generalValue != null)
+        {
+            if (generalValue.getClass().isArray())
+            {
+                List<T> result = new ArrayList<>();
+                for (Serializable singleValue : (Serializable[]) generalValue)
+                {
+                    result.add(fun.apply(singleValue));
+                }
+                return result;
+            } else
+            {
+                return List.of(fun.apply(generalValue));
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public List<Long> getMultiValueIntegerProperty(String propertyName)
+    {
+        Serializable propertyValue = getProperty(propertyName);
+        return getListOfValues(propertyValue, (x) -> Long.parseLong((String) x));
+    }
+
+    @Override
+    public void setMultiValueIntegerProperty(String propertyName, List<Long> propertyValues)
+    {
+        if (propertyValues != null)
+        {
+            setProperty(propertyName, propertyValues.toArray(Long[]::new));
+        } else
+        {
+            setProperty(propertyName, null);
+        }
+    }
+
+    @Override
+    public List<String> getMultiValueVarcharProperty(String propertyName)
+    {
+        Serializable propertyValue = getProperty(propertyName);
+        return getListOfValues(propertyValue, (x) -> (String) x);
+    }
+
+    @Override
+    public void setMultiValueVarcharProperty(String propertyName, List<String> propertyValues)
+    {
+        if (propertyValues != null)
+        {
+            setProperty(propertyName, propertyValues.toArray(String[]::new));
+        } else
+        {
+            setProperty(propertyName, null);
+        }
+    }
+
+    public List<String> getMultiValueControlledVocabularyProperty(String 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).collect(Collectors.toList());
+        } else
+        {
+            return List.of((String) value);
+        }
+    }
+
+    @Override
+    public void setMultiValueControlledVocabularyProperty(String propertyName,
+            List<String> propertyValues)
+    {
+        if (propertyValues != null)
+        {
+            setProperty(propertyName, propertyValues.toArray(String[]::new));
+        } else
+        {
+            setProperty(propertyName, null);
+        }
+    }
+
+    @Override
+    public List<SamplePermId> getMultiValueSampleProperty(String 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 -> new SamplePermId((String) x))
+                    .collect(Collectors.toList());
+        } else
+        {
+            String propertyValue = (String) value;
+            return List.of(new SamplePermId(propertyValue));
+        }
+    }
+
+    @Override
+    public void setMultiValueSampleProperty(String propertyName, List<SamplePermId> propertyValue)
+    {
+        setProperty(propertyName, propertyValue == null ? null : propertyValue.stream()
+                .map(ObjectPermId::getPermId)
+                .toArray(String[]::new));
+    }
+
+    @Override
+    public List<String> getMultiValueMultilineVarcharProperty(String propertyName)
+    {
+        Serializable propertyValue = getProperty(propertyName);
+        return getListOfValues(propertyValue, (x) -> (String) x);
+    }
+
+    @Override
+    public void setMultiValueMultilineVarcharProperty(String propertyName,
+            List<String> propertyValue)
+    {
+        if (propertyValue != null)
+        {
+            setProperty(propertyName, propertyValue.toArray(String[]::new));
+        } else
+        {
+            setProperty(propertyName, null);
+        }
+    }
+
+    @Override
+    public List<Double> getMultiValueRealProperty(String propertyName)
+    {
+        Serializable propertyValue = getProperty(propertyName);
+        return getListOfValues(propertyValue, (x) -> Double.parseDouble((String) x));
+    }
+
+    @Override
+    public void setMultiValueRealProperty(String propertyName, List<Double> propertyValue)
+    {
+        if (propertyValue != null)
+        {
+            setProperty(propertyName, propertyValue.toArray(Double[]::new));
+        } else
+        {
+            setProperty(propertyName, null);
+        }
+    }
+
+    @Override
+    public List<ZonedDateTime> getMultiValueTimestampProperty(String propertyName)
+    {
+        Serializable propertyValue = getProperty(propertyName);
+        return getListOfValues(propertyValue, (x) -> ZonedDateTime.parse((String)x,
+                DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss X")));
+    }
+
+    @Override
+    public void setMultiValueTimestampProperty(String propertyName,
+            List<ZonedDateTime> propertyValue)
+    {
+        if (propertyValue != null)
+        {
+            setProperty(propertyName, propertyValue.stream()
+                    .map(x -> x.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss Z")))
+                    .toArray(String[]::new));
+        } else
+        {
+            setProperty(propertyName, null);
+        }
+    }
+
+    @Override
+    public List<Boolean> getMultiValueBooleanProperty(String propertyName)
+    {
+        Serializable propertyValue = getProperty(propertyName);
+        return getListOfValues(propertyValue, (x) -> Boolean.parseBoolean((String) x));
+    }
+
+    @Override
+    public void setMultiValueBooleanProperty(String propertyName, List<Boolean> propertyValue)
+    {
+        if (propertyValue != null)
+        {
+            setProperty(propertyName, propertyValue.toArray(Boolean[]::new));
+        } else
+        {
+            setProperty(propertyName, null);
+        }
+    }
+
+    @Override
+    public List<String> getMultiValueHyperlinkProperty(String propertyName)
+    {
+        Serializable propertyValue = getProperty(propertyName);
+        return getListOfValues(propertyValue, (x) -> (String) x);
+    }
+
+    @Override
+    public void setMultiValueHyperlinkProperty(String propertyName, List<String> propertyValue)
+    {
+        if (propertyValue != null)
+        {
+            setProperty(propertyName, propertyValue.toArray(String[]::new));
+        } else
+        {
+            setProperty(propertyName, null);
+        }
+    }
+
+    @Override
+    public List<String> getMultiValueXmlProperty(String propertyName)
+    {
+        Serializable propertyValue = getProperty(propertyName);
+        return getListOfValues(propertyValue, (x) -> (String) x);
+    }
+
+    @Override
+    public void setMultiValueXmlProperty(String propertyName, List<String> propertyValue)
+    {
+        if (propertyValue != null)
+        {
+            setProperty(propertyName, propertyValue.toArray(String[]::new));
+        } else
+        {
+            setProperty(propertyName, null);
+        }
+    }
+
+    @Override
+    public List<Long[]> getMultiValueIntegerArrayProperty(String propertyName)
+    {
+        Serializable propertyValue = getProperty(propertyName);
+        return getListOfValues(propertyValue,
+                (x) -> Arrays.stream((Serializable[]) x)
+                        .map(Serializable::toString)
+                        .map(Long::parseLong)
+                        .toArray(Long[]::new));
+    }
+
+    @Override
+    public void setMultiValueIntegerArrayProperty(String propertyName, List<Long[]> propertyValue)
+    {
+        if (propertyValue != null)
+        {
+            setProperty(propertyName, propertyValue.toArray(Long[][]::new));
+        } else
+        {
+            setProperty(propertyName, null);
+        }
+    }
+
+    @Override
+    public List<Double[]> getMultiValueRealArrayProperty(String propertyName)
+    {
+        Serializable propertyValue = getProperty(propertyName);
+        return getListOfValues(propertyValue,
+                (x) -> Arrays.stream((Serializable[]) x)
+                        .map(Serializable::toString)
+                        .map(Double::parseDouble)
+                        .toArray(Double[]::new));
+    }
+
+    @Override
+    public void setMultiValueRealArrayProperty(String propertyName, List<Double[]> propertyValue)
+    {
+        if (propertyValue != null)
+        {
+            setProperty(propertyName, propertyValue.toArray(Double[][]::new));
+        } else
+        {
+            setProperty(propertyName, null);
+        }
+    }
+
+    @Override
+    public List<String[]> getMultiValueStringArrayProperty(String propertyName)
+    {
+        Serializable propertyValue = getProperty(propertyName);
+        return getListOfValues(propertyValue,
+                (x) -> Arrays.stream((Serializable[]) x)
+                        .map(Serializable::toString)
+                        .toArray(String[]::new));
+    }
+
+    @Override
+    public void setMultiValueStringArrayProperty(String propertyName, List<String[]> propertyValue)
+    {
+        if (propertyValue != null)
+        {
+            setProperty(propertyName, propertyValue.toArray(String[][]::new));
+        } else
+        {
+            setProperty(propertyName, null);
+        }
+    }
+
+    @Override
+    public List<ZonedDateTime[]> getMultiValueTimestampArrayProperty(String propertyName)
+    {
+        Serializable propertyValue = getProperty(propertyName);
+        return getListOfValues(propertyValue,
+                (x) -> Arrays.stream((Serializable[]) x)
+                        .map(Serializable::toString)
+                        .map(dateTime -> ZonedDateTime.parse(dateTime,
+                                DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss X")))
+                        .toArray(ZonedDateTime[]::new));
+    }
+
+    @Override
+    public void setMultiValueTimestampArrayProperty(String propertyName,
+            List<ZonedDateTime[]> propertyValue)
+    {
+        if (propertyValue != null)
+        {
+            setProperty(propertyName, propertyValue.stream()
+                    .map(dateTimeArray -> Arrays.stream(dateTimeArray)
+                            .map(x -> x.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss Z")))
+                            .toArray(String[]::new))
+                    .toArray(String[][]::new));
+        } else
+        {
+            setProperty(propertyName, null);
+        }
+        //        String[] value = (propertyValue == null) ? null : Arrays.stream(propertyValue)
+        //                .map(dateTime -> dateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ssX")))
+        //                .toArray(String[]::new);
+        //        setProperty(propertyName, value);
+    }
+
+    @Override
+    public List<String> getMultiValueJsonProperty(String propertyName)
+    {
+        Serializable propertyValue = getProperty(propertyName);
+        return getListOfValues(propertyValue, (x) -> (String) x);
+    }
+
+    @Override
+    public void setMultiValueJsonProperty(String propertyName, List<String> propertyValue)
+    {
+        if (propertyValue != null)
+        {
+            setProperty(propertyName, propertyValue.toArray(String[]::new));
+        } else
+        {
+            setProperty(propertyName, null);
+        }
+    }
 }
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 7a5346813db382a1c786d6d085486400d355ca0f..a2517eef5b126aabcc4bb7b0eb3a5541709160e4 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
@@ -17,6 +17,7 @@ package ch.ethz.sis.openbis.generic.asapi.v3.dto.common.interfaces;
 
 import java.io.Serializable;
 import java.time.ZonedDateTime;
+import java.util.List;
 import java.util.Map;
 
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.sample.id.SamplePermId;
@@ -33,6 +34,8 @@ public interface IPropertiesHolder
 
     void setProperties(Map<String, Serializable> properties);
 
+    String getPropertyAsString(String propertyName);
+
     Serializable getProperty(String propertyName);
 
     void setProperty(String propertyName, Serializable propertyValue);
@@ -61,13 +64,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);
 
@@ -97,4 +100,66 @@ public interface IPropertiesHolder
 
     void setJsonProperty(String propertyName, String propertyValue);
 
+    // Multi-value properties
+
+    List<Long> getMultiValueIntegerProperty(String propertyName);
+
+    void setMultiValueIntegerProperty(String propertyName, List<Long> propertyValues);
+
+    List<String> getMultiValueVarcharProperty(String propertyName);
+
+    void setMultiValueVarcharProperty(String propertyName, List<String> propertyValues);
+
+    List<String> getMultiValueMultilineVarcharProperty(String propertyName);
+
+    void setMultiValueMultilineVarcharProperty(String propertyName, List<String> propertyValue);
+
+    List<Double> getMultiValueRealProperty(String propertyName);
+
+    void setMultiValueRealProperty(String propertyName, List<Double> propertyValue);
+
+    List<ZonedDateTime> getMultiValueTimestampProperty(String propertyName);
+
+    void setMultiValueTimestampProperty(String propertyName, List<ZonedDateTime> propertyValue);
+
+    List<Boolean> getMultiValueBooleanProperty(String propertyName);
+
+    void setMultiValueBooleanProperty(String propertyName, List<Boolean> propertyValue);
+
+    List<String> getMultiValueHyperlinkProperty(String propertyName);
+
+    void setMultiValueHyperlinkProperty(String propertyName, List<String> propertyValue);
+
+    List<String> getMultiValueXmlProperty(String propertyName);
+
+    void setMultiValueXmlProperty(String propertyName, List<String> propertyValue);
+
+    List<String> getMultiValueControlledVocabularyProperty(String propertyName);
+
+    void setMultiValueControlledVocabularyProperty(String propertyName, List<String> propertyValue);
+
+    List<SamplePermId> getMultiValueSampleProperty(String propertyName);
+
+    void setMultiValueSampleProperty(String propertyName, List<SamplePermId> propertyValue);
+
+    List<Long[]> getMultiValueIntegerArrayProperty(String propertyName);
+
+    void setMultiValueIntegerArrayProperty(String propertyName, List<Long[]> propertyValue);
+
+    List<Double[]> getMultiValueRealArrayProperty(String propertyName);
+
+    void setMultiValueRealArrayProperty(String propertyName, List<Double[]> propertyValue);
+
+    List<String[]> getMultiValueStringArrayProperty(String propertyName);
+
+    void setMultiValueStringArrayProperty(String propertyName, List<String[]> propertyValue);
+
+    List<ZonedDateTime[]> getMultiValueTimestampArrayProperty(String propertyName);
+
+    void setMultiValueTimestampArrayProperty(String propertyName, List<ZonedDateTime[]> propertyValue);
+
+    List<String> getMultiValueJsonProperty(String propertyName);
+
+    void setMultiValueJsonProperty(String propertyName, List<String> propertyValue);
+
 }
diff --git a/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/property/UpdateEntityPropertyExecutor.java b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/property/UpdateEntityPropertyExecutor.java
index 8f0ca454644e5e3f8f5eba830a456a42a56a4cfc..13084d9623d9bb637597c3e97a30e570547449b3 100644
--- a/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/property/UpdateEntityPropertyExecutor.java
+++ b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/property/UpdateEntityPropertyExecutor.java
@@ -463,7 +463,17 @@ public class UpdateEntityPropertyExecutor implements IUpdateEntityPropertyExecut
         {
             entityProperties.add(EntityHelper.createNewProperty(entry.getKey(), entry.getValue()));
         }
-        Set<? extends EntityPropertyPE> existingProperties = propertiesHolder.getProperties();
+        List<? extends EntityPropertyPE> propertiesList = new ArrayList<>(propertiesHolder.getProperties());
+        // Add existing properties in the order of creation
+        Set<EntityPropertyPE> existingProperties = propertiesList.stream()
+                .filter((EntityPropertyPE a) -> a.getId() != null)
+                .sorted(
+                Comparator.comparing((EntityPropertyPE a) -> a.getId())
+        ).collect(Collectors.toCollection(LinkedHashSet::new));
+        // Newly created sample properties doesn't have id so add then at the end
+        propertiesList.stream()
+                .filter((EntityPropertyPE a) -> a.getId() == null)
+                .forEach(existingProperties::add);
         Map<String, List<Object>> existingPropertyValuesByCode =
                 new HashMap<String, List<Object>>();
         for (EntityPropertyPE existingProperty : existingProperties)
diff --git a/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/translator/dataset/DataSetQuery.java b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/translator/dataset/DataSetQuery.java
index fe9a8838d1f67918100f4f3e32c0922d38412c70..698726f000a47a08160123d7ea4dba6633d17427 100644
--- a/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/translator/dataset/DataSetQuery.java
+++ b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/translator/dataset/DataSetQuery.java
@@ -117,7 +117,7 @@ public interface DataSetQuery extends ObjectQuery
                     + "left join material_types mt on m.maty_id = mt.id "
                     + "join data_set_type_property_types etpt on p.dstpt_id = etpt.id "
                     + "join property_types pt on etpt.prty_id = pt.id "
-                    + "where p.ds_id = any(?{1}) order by id", parameterBindings = { LongSetMapper.class },
+                    + "where p.ds_id = any(?{1}) order by id asc", parameterBindings = { LongSetMapper.class },
             resultSetBinding = PropertyRecordDataObjectBinding.class, fetchSize = FETCH_SIZE)
     public List<PropertyRecord> getProperties(LongSet dataSetIds);
 
diff --git a/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/translator/experiment/ExperimentQuery.java b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/translator/experiment/ExperimentQuery.java
index f38a1602e1a941ca3836894e569161356895fa6a..1426d01095239a253c97332cce7ca267ed3f5594 100644
--- a/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/translator/experiment/ExperimentQuery.java
+++ b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/translator/experiment/ExperimentQuery.java
@@ -85,7 +85,7 @@ public interface ExperimentQuery extends ObjectQuery
                     + "left join material_types mt on m.maty_id = mt.id "
                     + "join experiment_type_property_types etpt on p.etpt_id = etpt.id "
                     + "join property_types pt on etpt.prty_id = pt.id "
-                    + "where p.expe_id = any(?{1}) order by id", parameterBindings = { LongSetMapper.class },
+                    + "where p.expe_id = any(?{1}) order by p.id asc", parameterBindings = { LongSetMapper.class },
             resultSetBinding = PropertyRecordDataObjectBinding.class, fetchSize = FETCH_SIZE)
     public List<PropertyRecord> getProperties(LongSet experimentIds);
 
diff --git a/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/translator/sample/SampleQuery.java b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/translator/sample/SampleQuery.java
index 0c77078bfc997d1b5a010c3eb9f0d119f29e4aa1..b81ce66e8c4bcfcd0e222be4436f9cf9b6ffe2dd 100644
--- a/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/translator/sample/SampleQuery.java
+++ b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/translator/sample/SampleQuery.java
@@ -82,7 +82,7 @@ public interface SampleQuery extends ObjectQuery
                     + "left join material_types mt on m.maty_id = mt.id "
                     + "join sample_type_property_types etpt on p.stpt_id = etpt.id "
                     + "join property_types pt on etpt.prty_id = pt.id "
-                    + "where p.samp_id = any(?{1}) order by id", parameterBindings = { LongSetMapper.class },
+                    + "where p.samp_id = any(?{1}) order by id asc", parameterBindings = { LongSetMapper.class },
             resultSetBinding = PropertyRecordDataObjectBinding.class,  fetchSize = FETCH_SIZE)
     public List<PropertyRecord> getProperties(LongSet sampleIds);
 
@@ -101,7 +101,8 @@ public interface SampleQuery extends ObjectQuery
                     + "from sample_properties p "
                     + "join sample_type_property_types etpt on p.stpt_id = etpt.id "
                     + "join property_types pt on etpt.prty_id = pt.id "
-                    + "where p.samp_prop_id is not null and p.samp_id = any(?{1})", parameterBindings = {
+                    + "where p.samp_prop_id is not null and p.samp_id = any(?{1}) "
+                    + "order by p.id asc", parameterBindings = {
             LongSetMapper.class }, fetchSize = FETCH_SIZE)
     public List<SamplePropertyRecord> getSampleProperties(LongSet sampleIds);
 
diff --git a/server-application-server/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/EntityPropertiesConverter.java b/server-application-server/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/EntityPropertiesConverter.java
index 9eef7a021908b618402bd7c41ac53a7929bed908..9870031fab2b1d8e6bf26da063a063cfdf1e55ef 100644
--- a/server-application-server/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/EntityPropertiesConverter.java
+++ b/server-application-server/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/EntityPropertiesConverter.java
@@ -19,9 +19,12 @@ import java.io.Serializable;
 import java.text.SimpleDateFormat;
 import java.util.*;
 import java.util.regex.Pattern;
+import java.util.stream.Collectors;
 
 import ch.systemsx.cisd.common.logging.LogCategory;
 import ch.systemsx.cisd.common.logging.LogFactory;
+import ch.systemsx.cisd.openbis.generic.shared.basic.IIdHolder;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.*;
 import org.apache.log4j.Logger;
 import org.hibernate.Session;
 
@@ -35,10 +38,6 @@ import ch.systemsx.cisd.common.collection.TableMap.UniqueKeyViolationStrategy;
 import ch.systemsx.cisd.common.exceptions.UserFailureException;
 import ch.systemsx.cisd.openbis.generic.server.dataaccess.util.KeyExtractorFactory;
 import ch.systemsx.cisd.openbis.generic.shared.basic.BasicConstant;
-import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DataTypeCode;
-import ch.systemsx.cisd.openbis.generic.shared.basic.dto.IEntityProperty;
-import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ManagedProperty;
-import ch.systemsx.cisd.openbis.generic.shared.basic.dto.MaterialIdentifier;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.api.IManagedInputWidgetDescription;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.api.IManagedProperty;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.api.IPerson;
@@ -82,6 +81,11 @@ public final class EntityPropertiesConverter implements IEntityPropertiesConvert
                 }
             };
 
+    private static final String SEPARATOR = ",";
+
+    private static final List<DataTypeCode> ARRAY_TYPES = List.of(DataTypeCode.ARRAY_STRING,
+            DataTypeCode.ARRAY_INTEGER, DataTypeCode.ARRAY_REAL, DataTypeCode.ARRAY_TIMESTAMP);
+
     private static final String NO_ENTITY_PROPERTY_VALUE_FOR_S =
             "Value of mandatory property '%s' not specified.";
 
@@ -281,22 +285,24 @@ public final class EntityPropertiesConverter implements IEntityPropertiesConvert
         if (isNullOrBlank(valueOrNull) == false)
         {
             List<T> results = new ArrayList<>();
-            if(propertyType.isMultiValue() && valueOrNull.getClass().isArray()) {
-                for (Serializable value : (Serializable[]) valueOrNull)
+            Serializable parsedValue = translateProperty(propertyType, valueOrNull);
+            if(propertyType.isMultiValue() && parsedValue.getClass().isArray()) {
+                for (Serializable value : (Serializable[]) parsedValue)
                 {
-                    String translatedValue =
-                            extendedETPT.translate(registrator, (String) value);
-                    final String validatedValue =
+                    Serializable translatedValue =
+                            extendedETPT.translate(registrator, value);
+                    final Serializable validatedValue =
                             propertyValueValidator.validatePropertyValue(propertyType,
                                     translatedValue);
                     results.addAll(createEntityProperty(registrator, propertyType,
                             entityTypePropertyTypePE,
                             validatedValue));
+
                 }
             } else {
-                String translatedValue = extendedETPT.translate(registrator, property.tryGetAsString());
+                Serializable translatedValue = extendedETPT.translate(registrator, parsedValue);
 
-                final String validatedValue =
+                final Serializable validatedValue =
                         propertyValueValidator.validatePropertyValue(propertyType, translatedValue);
                 results.addAll(createEntityProperty(registrator, propertyType, entityTypePropertyTypePE,
                         validatedValue));
@@ -306,6 +312,56 @@ public final class EntityPropertiesConverter implements IEntityPropertiesConvert
         return null;
     }
 
+    private Serializable translateProperty(PropertyTypePE propertyType, Serializable value) {
+        if(value == null || !value.getClass().equals(String.class)) {
+            //Nothing to translate
+            return value;
+        }
+
+        String regex = "(?<!\\\\)" + Pattern.quote(SEPARATOR);
+        String propertyValue = value.toString().trim();
+        if(propertyValue.isEmpty()) {
+            return null;
+        }
+
+        if(propertyType.isMultiValue()) {
+            if(propertyValue.startsWith("[")) {
+                propertyValue = propertyValue.substring(1, propertyValue.length()-1).trim();
+            }
+            if(propertyValue.isEmpty()) {
+                return null;
+            }
+            if(ARRAY_TYPES.contains(propertyType.getType().getCode())) {
+                // Multi-value array properties
+                String multiArrayRegex = "\\],\\s*\\[";
+                if(propertyValue.startsWith("[")) {
+                    propertyValue = propertyValue.substring(1, propertyValue.length()-1).trim();
+                }
+                return Arrays.stream(propertyValue.split(multiArrayRegex))
+                        .map(String::trim)
+                        .map(x -> Arrays.stream(x.split(regex))
+                                .map(String::trim)
+                                .toArray(String[]::new))
+                        .toArray(String[][]::new);
+            } else {
+                return Arrays.stream(propertyValue.split(regex))
+                        .map(String::trim)
+                        .toArray(String[]::new);
+            }
+        } else {
+            if(ARRAY_TYPES.contains(propertyType.getType().getCode())) {
+                if(propertyValue.startsWith("[")) {
+                    propertyValue = propertyValue.substring(1, propertyValue.length()-1);
+                }
+                return Arrays.stream(propertyValue.split(regex))
+                                    .map(String::trim)
+                                    .toArray(String[]::new);
+            } else {
+                return propertyValue;
+            }
+        }
+    }
+
     private Serializable getPropertyValue(final IEntityProperty property) {
         Serializable result = property.getValue();
         if(result != null) {
@@ -329,31 +385,12 @@ public final class EntityPropertiesConverter implements IEntityPropertiesConvert
     private final <T extends EntityPropertyPE> List<T> createEntityProperty(
             final PersonPE registrator,
             final PropertyTypePE propertyType,
-            final EntityTypePropertyTypePE entityTypePropertyType, final String value)
+            final EntityTypePropertyTypePE entityTypePropertyType, final Serializable value)
     {
         List<T> entityProperties = new ArrayList<>();
-        String val = value;
-        List<DataTypeCode> arrayTypes = List.of(DataTypeCode.ARRAY_STRING,
-                DataTypeCode.ARRAY_INTEGER, DataTypeCode.ARRAY_REAL, DataTypeCode.ARRAY_TIMESTAMP);
-        if (propertyType.isMultiValue() && !arrayTypes.contains(propertyType.getType().getCode()))
-        {
-            if (val.startsWith("["))
-            {
-                val = val.substring(1, val.length() - 1);
-            }
-            for (String v : val.split(","))
-            {
-                String singleValue = v.trim();
-                final T entityProperty = getEntityPropertyBase(registrator, entityTypePropertyType);
-                setPropertyValue(entityProperty, propertyType, singleValue);
-                entityProperties.add(entityProperty);
-            }
-        } else
-        {
-            final T entityProperty = getEntityPropertyBase(registrator, entityTypePropertyType);
-            setPropertyValue(entityProperty, propertyType, val);
-            entityProperties.add(entityProperty);
-        }
+        final T entityProperty = getEntityPropertyBase(registrator, entityTypePropertyType);
+        setPropertyValue(entityProperty, propertyType, value);
+        entityProperties.add(entityProperty);
         return entityProperties;
     }
 
@@ -500,9 +537,9 @@ public final class EntityPropertiesConverter implements IEntityPropertiesConvert
         }
         if (isNullOrBlank(value) == false)
         {
-            final String validated =
+            final Serializable validated =
                     propertyValueValidator.validatePropertyValue(propertyType, value);
-            return validated;
+            return validated.toString();
         }
         return null;
     }
@@ -519,15 +556,21 @@ public final class EntityPropertiesConverter implements IEntityPropertiesConvert
 
     @Override
     public final <T extends EntityPropertyPE> void setPropertyValue(final T entityProperty,
-            final PropertyTypePE propertyType, final String validatedValue)
+            final PropertyTypePE propertyType, final Serializable validatedValue)
     {
         assert validatedValue != null;
-        if (validatedValue.startsWith(BasicConstant.ERROR_PROPERTY_PREFIX))
-        {
-            // save errors as strings
-            entityProperty.setUntypedValue(validatedValue, null, null, null, null, null, null, null,
-                    null);
-        } else
+
+        if(validatedValue.getClass().equals(String.class)) {
+            String value = (String) validatedValue;
+            if (value.startsWith(BasicConstant.ERROR_PROPERTY_PREFIX))
+            {
+                // save errors as strings
+                entityProperty.setUntypedValue(value, null, null, null, null, null, null, null,
+                        null);
+            }
+        }
+
+
         {
             final VocabularyTermPE vocabularyTerm =
                     complexPropertyValueHelper.tryGetVocabularyTerm(validatedValue, propertyType);
@@ -544,7 +587,7 @@ public final class EntityPropertiesConverter implements IEntityPropertiesConvert
                     complexPropertyValueHelper.tryGetTimestampArray(validatedValue, propertyType);
             final String jsonValue =
                     complexPropertyValueHelper.tryGetJsonValue(validatedValue, propertyType);
-            entityProperty.setUntypedValue(validatedValue, vocabularyTerm, material, sample,
+            entityProperty.setUntypedValue(validatedValue.toString(), vocabularyTerm, material, sample,
                     integerArray, realArray, stringArray, timestampArray, jsonValue);
         }
     }
@@ -565,20 +608,27 @@ public final class EntityPropertiesConverter implements IEntityPropertiesConvert
         {
             // TODO: create null property history entry
         }
-
+        Set<T> oldPropertiesTemp = new HashSet<>(oldProperties);
         final List<T> convertedProperties =
                 convertPropertiesForUpdate(newProperties, entityType.getCode(), author);
-        final Set<T> set = new HashSet<T>();
-        for (T newProperty : convertedProperties)
-        {
+        final Set<T> set = new LinkedHashSet<>();
+
+        for (int i=0; i< convertedProperties.size(); i++) {
+            T newProperty = convertedProperties.get(i);
             PropertyTypePE propertyType = newProperty.getEntityTypePropertyType().getPropertyType();
-            T existingProperty;
-            if (propertyType.isMultiValue())
-            {
-                existingProperty = tryFindMulti(oldProperties, propertyType, newProperty);
-            } else
+            T existingProperty = null;
+            if(!propertyType.isMultiValue())
             {
-                existingProperty = tryFind(oldProperties, propertyType);
+                existingProperty = tryFind(oldPropertiesTemp, propertyType);
+            } else {
+                List<T> oldMulti = oldPropertiesTemp.stream()
+                        .filter(oldProp -> oldProp.getEntityTypePropertyType().getPropertyType().equals(propertyType))
+                        .sorted(Comparator.comparing(IIdHolder::getId))
+                        .collect(Collectors.toList());
+                if(!oldMulti.isEmpty()) {
+                    existingProperty = oldMulti.get(0);
+                    oldPropertiesTemp.remove(existingProperty);
+                }
             }
 
             if (existingProperty != null)
@@ -601,9 +651,13 @@ public final class EntityPropertiesConverter implements IEntityPropertiesConvert
                 set.add(existingProperty);
             } else
             {
+                if (propertyType.isMultiValue()) {
+                    newProperty.setIndex(i);
+                }
                 // TODO: create new property history entry
                 set.add(newProperty);
             }
+
         }
         return set;
     }
@@ -619,6 +673,7 @@ public final class EntityPropertiesConverter implements IEntityPropertiesConvert
                 String oldValue = oldProperty.tryGetUntypedValue();
                 if (oldValue != null && oldValue.equals(propertyValue))
                 {
+                    oldProperties.remove(oldProperty);
                     return oldProperty;
                 }
             }
@@ -715,6 +770,7 @@ public final class EntityPropertiesConverter implements IEntityPropertiesConvert
         {
             if (oldProperty.getEntityTypePropertyType().getPropertyType().equals(propertyType))
             {
+                oldProperties.remove(oldProperty);
                 return oldProperty;
             }
         }
@@ -782,42 +838,47 @@ public final class EntityPropertiesConverter implements IEntityPropertiesConvert
         }
 
         @SuppressWarnings("unchecked")
-        String translate(PersonPE personPE, String propertyValue)
+        Serializable translate(PersonPE personPE, Serializable value)
         {
-            if (inputWidgetDescriptions.isEmpty()
-                    || propertyValue == null
-                    || propertyValue.startsWith(BasicConstant.ERROR_PROPERTY_PREFIX)
-                    || propertyValue.startsWith(
-                    BasicConstant.MANAGED_PROPERTY_JSON_PREFIX) == false)
-            {
-                return propertyValue;
+            if(value == null || !value.getClass().equals(String.class)) {
+                return value;
             }
-            try
+            else
             {
-                List<?> readValue =
-                        new ObjectMapper().readValue(propertyValue
-                                        .substring(BasicConstant.MANAGED_PROPERTY_JSON_PREFIX.length()),
-                                List.class);
-                ManagedProperty managedProperty = new ManagedProperty();
-                IPerson person = PersonTranslator.translateToIPerson(personPE);
+                String propertyValue = (String) value;
+                if (inputWidgetDescriptions.isEmpty()
+                        || propertyValue.startsWith(BasicConstant.ERROR_PROPERTY_PREFIX)
+                        || propertyValue.startsWith(BasicConstant.MANAGED_PROPERTY_JSON_PREFIX) == false)
+                {
+                    return propertyValue;
+                }
+                try
+                {
+                    List<?> readValue =
+                            new ObjectMapper().readValue(propertyValue
+                                            .substring(BasicConstant.MANAGED_PROPERTY_JSON_PREFIX.length()),
+                                    List.class);
+                    ManagedProperty managedProperty = new ManagedProperty();
+                    IPerson person = PersonTranslator.translateToIPerson(personPE);
 
-                List<Map<String, String>> bindingsList = new ArrayList<Map<String, String>>();
+                    List<Map<String, String>> bindingsList = new ArrayList<Map<String, String>>();
 
-                for (Object row : readValue)
-                {
-                    if (row instanceof Map == false)
+                    for (Object row : readValue)
                     {
-                        continue;
+                        if (row instanceof Map == false)
+                        {
+                            continue;
+                        }
+
+                        bindingsList.add((Map<String, String>) row);
                     }
 
-                    bindingsList.add((Map<String, String>) row);
+                    evaluator.updateFromRegistrationForm(managedProperty, person, bindingsList);
+                    return managedProperty.getValue();
+                } catch (Exception ex)
+                {
+                    throw CheckedExceptionTunnel.wrapIfNecessary(ex);
                 }
-
-                evaluator.updateFromRegistrationForm(managedProperty, person, bindingsList);
-                return (String) managedProperty.getValue();
-            } catch (Exception ex)
-            {
-                throw CheckedExceptionTunnel.wrapIfNecessary(ex);
             }
         }
 
@@ -855,12 +916,19 @@ public final class EntityPropertiesConverter implements IEntityPropertiesConvert
             this.entityInfoProvider = entityInfoProvider;
         }
 
-        public SamplePE tryGetSample(String value, PropertyTypePE propertyType)
+        public SamplePE tryGetSample(Serializable val, PropertyTypePE propertyType)
         {
             if (propertyType.getType().getCode() != DataTypeCode.SAMPLE)
             {
                 return null; // this is not a property of SAMPLE type
             }
+            String value;
+            if(val.getClass().equals(Sample.class)) {
+                value = ((Sample)val).getPermId();
+            } else {
+                value = val.toString();
+            }
+
             ISampleDAO sampleDAO = daoFactory.getSampleDAO();
             String samplePermId = value;
             if (samplePermId.startsWith("["))
@@ -885,14 +953,14 @@ public final class EntityPropertiesConverter implements IEntityPropertiesConvert
             return samples.get(0);
         }
 
-        public MaterialPE tryGetMaterial(String value, PropertyTypePE propertyType)
+        public MaterialPE tryGetMaterial(Serializable value, PropertyTypePE propertyType)
         {
             if (propertyType.getType().getCode() != DataTypeCode.MATERIAL)
             {
                 return null; // this is not a property of MATERIAL type
             }
             MaterialIdentifier materialIdentifier =
-                    MaterialIdentifier.tryCreate(value, propertyType.getMaterialType());
+                    MaterialIdentifier.tryCreate((String)value, propertyType.getMaterialType());
             if (materialIdentifier == null)
             {
                 return null;
@@ -917,7 +985,7 @@ public final class EntityPropertiesConverter implements IEntityPropertiesConvert
             return material;
         }
 
-        public VocabularyTermPE tryGetVocabularyTerm(final String value,
+        public VocabularyTermPE tryGetVocabularyTerm(final Serializable value,
                 final PropertyTypePE propertyType)
         {
             if (propertyType.getType().getCode() != DataTypeCode.CONTROLLEDVOCABULARY)
@@ -930,7 +998,7 @@ public final class EntityPropertiesConverter implements IEntityPropertiesConvert
             {
                 return null;
             }
-            final VocabularyTermPE term = vocabulary.tryGetVocabularyTerm(value);
+            final VocabularyTermPE term = vocabulary.tryGetVocabularyTerm((String)value);
             if (term != null)
             {
                 return term;
@@ -940,69 +1008,71 @@ public final class EntityPropertiesConverter implements IEntityPropertiesConvert
                     vocabulary.getCode());
         }
 
-        private static final String SEPARATOR = ",";
 
-        public Long[] tryGetIntegerArray(final String value, final PropertyTypePE propertyType)
+
+        public Long[] tryGetIntegerArray(final Serializable value, final PropertyTypePE propertyType)
         {
             DataTypeCode code = propertyType.getType().getCode();
             if (code != DataTypeCode.ARRAY_INTEGER)
             {
                 return null;
             }
-            if (value == null || value.trim().isEmpty())
+            if (value == null || !value.getClass().isArray() || ((Serializable[])value).length == 0)
             {
                 return null;
             }
-            return Arrays.stream(value.split(SEPARATOR))
-                    .map(x -> Long.parseLong(x.trim()))
+            return Arrays.stream((Serializable[])value)
+                    .map(x -> Long.parseLong(x.toString().trim()))
                     .toArray(Long[]::new);
         }
 
-        public Double[] tryGetRealArray(final String value, final PropertyTypePE propertyType)
+        public Double[] tryGetRealArray(final Serializable value, final PropertyTypePE propertyType)
         {
             DataTypeCode code = propertyType.getType().getCode();
             if (code != DataTypeCode.ARRAY_REAL)
             {
                 return null;
             }
-            if (value == null || value.trim().isEmpty())
+            if (value == null || !value.getClass().isArray() || ((Serializable[])value).length == 0)
             {
                 return null;
             }
-            return Arrays.stream(value.split(SEPARATOR))
-                    .map(x -> Double.parseDouble(x.trim()))
+            return Arrays.stream((Serializable[])value)
+                    .map(x -> Double.parseDouble(x.toString().trim()))
                     .toArray(Double[]::new);
         }
 
-        public String[] tryGetStringArray(final String value, final PropertyTypePE propertyType)
+        public String[] tryGetStringArray(final Serializable value, final PropertyTypePE propertyType)
         {
             DataTypeCode code = propertyType.getType().getCode();
             if (code != DataTypeCode.ARRAY_STRING)
             {
                 return null;
             }
-            if (value == null || value.trim().isEmpty())
+            if (value == null || !value.getClass().isArray() || ((Serializable[])value).length == 0)
             {
                 return null;
             }
-            // Special regex to allow strings with ',' character
-            String regex = "(?<!\\\\)" + Pattern.quote(SEPARATOR);
-            return Arrays.stream(value.split(regex))
-                    .map(String::trim)
+            return Arrays.stream((Serializable[])value)
+                    .map(Serializable::toString)
                     .toArray(String[]::new);
         }
 
-        public Date[] tryGetTimestampArray(final String value, final PropertyTypePE propertyType)
+        public Date[] tryGetTimestampArray(final Serializable value, final PropertyTypePE propertyType)
         {
             DataTypeCode code = propertyType.getType().getCode();
             if (code != DataTypeCode.ARRAY_TIMESTAMP)
             {
                 return null;
             }
+            if (value == null || !value.getClass().isArray() || ((Serializable[])value).length == 0)
+            {
+                return null;
+            }
             SimpleDateFormat format =
                     new SimpleDateFormat(BasicConstant.DATE_HOURS_MINUTES_SECONDS_PATTERN);
-            return Arrays.stream(value.split(SEPARATOR))
-                    .map(x -> parseDateFromString(x, format))
+            return Arrays.stream((Serializable[])value)
+                    .map(x -> parseDateFromString((String)x, format))
                     .toArray(Date[]::new);
         }
 
@@ -1017,14 +1087,14 @@ public final class EntityPropertiesConverter implements IEntityPropertiesConvert
             }
         }
 
-        public String tryGetJsonValue(final String value, final PropertyTypePE propertyType)
+        public String tryGetJsonValue(final Serializable value, final PropertyTypePE propertyType)
         {
             DataTypeCode code = propertyType.getType().getCode();
-            if (code != DataTypeCode.JSON)
+            if (code != DataTypeCode.JSON || value == null)
             {
                 return null;
             }
-            return value;
+            return value.toString();
         }
 
     }
diff --git a/server-application-server/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/IEntityPropertiesConverter.java b/server-application-server/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/IEntityPropertiesConverter.java
index edce2f19e146b6fd4d263fe0fc7ed748fe5ea141..3088939737bd2d67ed4e4c77abe1cab1ed933176 100644
--- a/server-application-server/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/IEntityPropertiesConverter.java
+++ b/server-application-server/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/IEntityPropertiesConverter.java
@@ -15,6 +15,7 @@
  */
 package ch.systemsx.cisd.openbis.generic.server.dataaccess;
 
+import java.io.Serializable;
 import java.util.Collection;
 import java.util.List;
 import java.util.Map;
@@ -62,7 +63,7 @@ public interface IEntityPropertiesConverter
      * Modifies value of given {@link EntityPropertyPE}. Value should be already validated.
      */
     public <T extends EntityPropertyPE> void setPropertyValue(final T entityProperty,
-            final PropertyTypePE propertyType, final String validatedValue);
+            final PropertyTypePE propertyType, final Serializable validatedValue);
 
     /** Updates Set<T> of properties. */
     public <T extends EntityPropertyPE> Set<T> updateProperties(Collection<T> oldProperties,
diff --git a/server-application-server/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/IPropertyValueValidator.java b/server-application-server/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/IPropertyValueValidator.java
index 5cef2a3d4dac33a39efb70c038321707ff902320..f2de1690fae53c2db229ab87f288fbf372e0b0a5 100644
--- a/server-application-server/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/IPropertyValueValidator.java
+++ b/server-application-server/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/IPropertyValueValidator.java
@@ -18,6 +18,8 @@ package ch.systemsx.cisd.openbis.generic.server.dataaccess;
 import ch.systemsx.cisd.common.exceptions.UserFailureException;
 import ch.systemsx.cisd.openbis.generic.shared.dto.PropertyTypePE;
 
+import java.io.Serializable;
+
 /**
  * A property value validator.
  * 
@@ -30,7 +32,7 @@ public interface IPropertyValueValidator
      * 
      * @return the validated value. It does not implicitly equal given <var>value</var>
      */
-    public String validatePropertyValue(final PropertyTypePE propertyType, final String value)
+    public Serializable validatePropertyValue(final PropertyTypePE propertyType, final Serializable value)
             throws UserFailureException;
 
 }
diff --git a/server-application-server/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/PropertyValidator.java b/server-application-server/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/PropertyValidator.java
index adc7c71407627c3cadd1d9241f083f5d8c1987df..10ea6923b3c2642f247097b3ae9618fe7191d567 100644
--- a/server-application-server/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/PropertyValidator.java
+++ b/server-application-server/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/PropertyValidator.java
@@ -15,9 +15,13 @@
  */
 package ch.systemsx.cisd.openbis.generic.server.dataaccess;
 
+import java.io.Serializable;
+import java.util.Arrays;
 import java.util.EnumMap;
 import java.util.Map;
 
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Sample;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.VocabularyTerm;
 import org.apache.commons.lang3.StringUtils;
 import org.w3c.dom.Document;
 
@@ -61,14 +65,14 @@ public final class PropertyValidator implements IPropertyValueValidator
     }
 
     @Override
-    public final String validatePropertyValue(final PropertyTypePE propertyType, final String value)
+    public final Serializable validatePropertyValue(final PropertyTypePE propertyType, final Serializable value)
             throws UserFailureException
     {
         assert propertyType != null : "Unspecified property type.";
         assert value != null : "Unspecified value.";
 
         // don't validate error messages and placeholders
-        if (value.startsWith(BasicConstant.ERROR_PROPERTY_PREFIX))
+        if (value.getClass().equals(String.class) &&  ((String)value).startsWith(BasicConstant.ERROR_PROPERTY_PREFIX))
         {
             return value;
         }
@@ -118,26 +122,37 @@ public final class PropertyValidator implements IPropertyValueValidator
          * @return the validated value. Note that it can differ from the given one.
          * @throws UserFailureException if given <var>value</var> is not valid.
          */
-        public String validate(final String value) throws UserFailureException;
+        public Serializable validate(final Serializable value) throws UserFailureException;
     }
 
     private final static class SampleValidator implements IDataTypeValidator
     {
         @Override
-        public String validate(String value) throws UserFailureException {
+        public Serializable validate(Serializable value) throws UserFailureException {
             assert value != null : "Unspecified value.";
 
-            if (StringUtils.isBlank(value))
-            {
-                return null;
-            }
-
-            if (value.startsWith("/")) {
-                // Is well formed identifier?
+            if(value.getClass().isArray()) {
+                Serializable[] arrayValues = (Serializable[]) value;
+                if(arrayValues.length == 0) {
+                    return null;
+                }
             } else {
-                // Is well formed permId?
+                String stringValue;
+                if(value.getClass().equals(Sample.class)) {
+                    stringValue = ((Sample) value).getPermId();
+                } else {
+                    stringValue = value.toString();
+                }
+                if (StringUtils.isBlank(stringValue))
+                {
+                    return null;
+                }
+                if (stringValue.startsWith("/")) {
+                    // Is well formed identifier?
+                } else {
+                    // Is well formed permId?
+                }
             }
-
             return value;
         }
     }
@@ -145,14 +160,21 @@ public final class PropertyValidator implements IPropertyValueValidator
     private final static class JsonValidator implements IDataTypeValidator
     {
         @Override
-        public String validate(String value) throws UserFailureException {
+        public Serializable validate(Serializable value) throws UserFailureException {
             assert value != null : "Unspecified value.";
 
-            if (StringUtils.isBlank(value))
-            {
-                return null;
+            if(value.getClass().isArray()) {
+                Serializable[] arrayValues = (Serializable[]) value;
+                if(arrayValues.length == 0) {
+                    return null;
+                }
+            } else {
+                String val = (String) value;
+                if (StringUtils.isBlank(val))
+                {
+                    return null;
+                }
             }
-            //TODO: implement validation for json
             return value;
         }
     }
@@ -167,14 +189,13 @@ public final class PropertyValidator implements IPropertyValueValidator
         }
 
         @Override
-        public String validate(String value) throws UserFailureException {
+        public Serializable validate(Serializable value) throws UserFailureException {
             assert value != null : "Unspecified value.";
 
-            if (StringUtils.isBlank(value))
-            {
-                return null;
+            if(!value.getClass().isArray()) {
+                throw UserFailureException.fromTemplate("Array value '%s' is not valid. "
+                                + "Provided value is not an array", value);
             }
-            //TODO: implement validation for array types
             return value;
         }
     }
@@ -196,29 +217,40 @@ public final class PropertyValidator implements IPropertyValueValidator
         //
 
         @Override
-        public final String validate(final String value) throws UserFailureException
+        public Serializable validate(final Serializable value) throws UserFailureException
         {
             assert value != null : "Unspecified value.";
             assert vocabulary != null : "Unspecified vocabulary.";
 
-            String upperCaseValue = value.toUpperCase();
-            boolean guard = true;
-            if(propertyTypePE.isMultiValue()) {
-                if(upperCaseValue.startsWith("[") && upperCaseValue.endsWith("]")) {
-                    upperCaseValue = upperCaseValue.substring(1, upperCaseValue.length()-1);
-                }
-                final String[] split = upperCaseValue.split(",");
-                for(String singleValue : split) {
-                    guard = guard && hasTerm(singleValue.trim());
+            if(value.getClass().isArray()) {
+                Serializable[] arrayValues = (Serializable[]) value;
+                if(arrayValues.length == 0) {
+                    return null;
                 }
+                return Arrays.stream(arrayValues)
+                        .map(x -> validateSingleValue((String)x))
+                        .toArray(Serializable[]::new);
             } else {
-                guard = hasTerm(upperCaseValue);
+                String val = value.toString();
+                if(value.getClass().equals(VocabularyTerm.class)) {
+                    val = ((VocabularyTerm)value).getCode();
+                }
+
+                if (StringUtils.isBlank(val))
+                {
+                    return null;
+                }
+                return validateSingleValue(val);
             }
-            if(guard) {
+        }
+
+        private Serializable validateSingleValue(final String value) {
+            String upperCaseValue = value.toUpperCase();
+            if(hasTerm(upperCaseValue)) {
                 return upperCaseValue;
             }
             throw UserFailureException.fromTemplate("Vocabulary value '%s' of property '%s' is not valid. "
-                    + "It must exist in '%s' controlled vocabulary %s", upperCaseValue, propertyTypePE.getCode(),
+                            + "It must exist in '%s' controlled vocabulary %s", upperCaseValue, propertyTypePE.getCode(),
                     vocabulary.getCode(), getVocabularyDetails());
         }
 
@@ -277,9 +309,10 @@ public final class PropertyValidator implements IPropertyValueValidator
         //
 
         @Override
-        public final String validate(final String value) throws UserFailureException
+        public final Serializable validate(final Serializable val) throws UserFailureException
         {
-            assert value != null : "Unspecified value.";
+            assert val != null : "Unspecified value.";
+            String value = (String) val;
 
             if (StringUtils.isBlank(value))
             {
@@ -335,10 +368,23 @@ public final class PropertyValidator implements IPropertyValueValidator
         //
 
         @Override
-        public final String validate(final String value) throws UserFailureException
+        public final Serializable validate(final Serializable value) throws UserFailureException
         {
             assert value != null : "Unspecified value.";
 
+            if(value.getClass().isArray()) {
+                for(Serializable singleValue : (Serializable[]) value) {
+                    validateSingleValue(singleValue);
+                }
+            } else {
+                validateSingleValue(value);
+            }
+            // validated value is valid
+            return value;
+        }
+
+        private Serializable validateSingleValue(Serializable val) {
+            String value = (String) val;
             // parsing checks if the value is a well-formed XML document
             Document document = XmlUtils.parseXmlDocument(value);
             if (xmlSchema != null)
@@ -356,7 +402,6 @@ public final class PropertyValidator implements IPropertyValueValidator
                             e.getMessage());
                 }
             }
-
             // validated value is valid
             return value;
         }
diff --git a/server-application-server/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/builders/PropertyBuilder.java b/server-application-server/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/builders/PropertyBuilder.java
index 2365ca8265b9825638945571f4b272a81c7c1df5..c0cc08e92cf2ec026d6ddf38290391771dd7c7e7 100644
--- a/server-application-server/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/builders/PropertyBuilder.java
+++ b/server-application-server/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/builders/PropertyBuilder.java
@@ -17,6 +17,7 @@ package ch.systemsx.cisd.openbis.generic.shared.basic.dto.builders;
 
 import static ch.systemsx.cisd.openbis.generic.shared.basic.BasicConstant.CANONICAL_DATE_FORMAT_PATTERN;
 
+import java.io.Serializable;
 import java.text.SimpleDateFormat;
 import java.util.Date;
 
@@ -109,6 +110,12 @@ public class PropertyBuilder
         return this;
     }
 
+    public PropertyBuilder value(Serializable value)
+    {
+        property.setValue(value);
+        return this;
+    }
+
     public PropertyBuilder value(int value)
     {
         type(DataTypeCode.INTEGER);
diff --git a/server-application-server/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/DataPE.java b/server-application-server/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/DataPE.java
index 33d7fa71720a848731003c4e9fa280beb400fb59..09b2c94428cbb73786a5e7d1488e10745e0713da 100644
--- a/server-application-server/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/DataPE.java
+++ b/server-application-server/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/DataPE.java
@@ -760,9 +760,10 @@ public class DataPE extends AbstractIdAndCodeHolder<DataPE> implements
         this.experimentFrozen = experimentFrozen;
     }
 
-    private Set<DataSetPropertyPE> properties = new HashSet<DataSetPropertyPE>();
+    private Set<DataSetPropertyPE> properties = new LinkedHashSet<>();
 
     @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "entity", orphanRemoval = true)
+    @OrderBy(clause = "id ASC")
     @Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
     @BatchSize(size = 100)
     private Set<DataSetPropertyPE> getDataSetProperties()
diff --git a/server-application-server/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/EntityPropertyPE.java b/server-application-server/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/EntityPropertyPE.java
index 88088a965a04cac746d3b95eecb0140f397bdcc0..da0ae21f8a2312f664257c15d3eb3dcca9b51c8a 100644
--- a/server-application-server/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/EntityPropertyPE.java
+++ b/server-application-server/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/EntityPropertyPE.java
@@ -108,6 +108,11 @@ public abstract class EntityPropertyPE extends HibernateAbstractRegistrationHold
 
     protected boolean entityFrozen;
 
+    /**
+     * Special field for multi-value properties hashcode computing
+     */
+    protected transient Long index;
+
     public <T extends EntityTypePropertyTypePE> void setEntityTypePropertyType(
             final T entityTypePropertyType)
     {
@@ -119,6 +124,11 @@ public abstract class EntityPropertyPE extends HibernateAbstractRegistrationHold
         this.id = id;
     }
 
+    public void setIndex(final long index)
+    {
+        this.index = index;
+    }
+
     public void setEntityFrozen(boolean frozen)
     {
         this.entityFrozen = frozen;
@@ -415,6 +425,8 @@ public abstract class EntityPropertyPE extends HibernateAbstractRegistrationHold
         builder.append(getEntity());
         builder.append(getEntityTypePropertyType());
         builder.append(tryGetUntypedValue());
+        builder.append(index);
+        builder.append(id);
         return builder.toHashCode();
     }
 
diff --git a/server-application-server/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/ExperimentPE.java b/server-application-server/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/ExperimentPE.java
index e3b98e5452b0a233cb3624779936319d2b7bbd3c..3453188e291a41c21b10afc93ff055b308b40d41 100644
--- a/server-application-server/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/ExperimentPE.java
+++ b/server-application-server/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/ExperimentPE.java
@@ -98,7 +98,7 @@ public class ExperimentPE extends AttachmentHolderPE implements
 
     private DeletionPE deletion;
 
-    private Set<ExperimentPropertyPE> properties = new HashSet<ExperimentPropertyPE>();
+    private Set<ExperimentPropertyPE> properties = new LinkedHashSet<>();
 
     private List<DataPE> dataSets = new ArrayList<DataPE>();
 
@@ -326,6 +326,7 @@ public class ExperimentPE extends AttachmentHolderPE implements
     }
 
     @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "entity", orphanRemoval = true)
+    @OrderBy(clause = "id ASC")
     @Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
     @BatchSize(size = 100)
     private Set<ExperimentPropertyPE> getExperimentProperties()
diff --git a/server-application-server/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/SamplePE.java b/server-application-server/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/SamplePE.java
index 3a1449798799f2c911cb376da9e247ff73a224a9..2c0f577783a64b7c3bdbc0b777e9fcfa7d37d84b 100644
--- a/server-application-server/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/SamplePE.java
+++ b/server-application-server/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/SamplePE.java
@@ -241,7 +241,7 @@ public class SamplePE extends AttachmentHolderPE implements IIdAndCodeHolder, Co
      */
     private Integer originalDeletion;
 
-    private Set<SamplePropertyPE> properties = new HashSet<SamplePropertyPE>();
+    private Set<SamplePropertyPE> properties = new LinkedHashSet<>();
 
     /**
      * Person who registered this entity.
@@ -464,6 +464,7 @@ public class SamplePE extends AttachmentHolderPE implements IIdAndCodeHolder, Co
     }
 
     @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "entity", orphanRemoval = true)
+    @OrderBy(clause = "id ASC")
     @Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
     @BatchSize(size = 100)
     private Set<SamplePropertyPE> getSampleProperties()
diff --git a/server-application-server/source/java/ch/systemsx/cisd/openbis/generic/shared/util/SimplePropertyValidator.java b/server-application-server/source/java/ch/systemsx/cisd/openbis/generic/shared/util/SimplePropertyValidator.java
index 2c8f111115c0431ccb7e6e925ef9cbd360c5cd9c..21aaa99cc4486ed2dd971c2dc6a6708f269ff054 100644
--- a/server-application-server/source/java/ch/systemsx/cisd/openbis/generic/shared/util/SimplePropertyValidator.java
+++ b/server-application-server/source/java/ch/systemsx/cisd/openbis/generic/shared/util/SimplePropertyValidator.java
@@ -140,7 +140,7 @@ public class SimplePropertyValidator
             return builder.toString();
         } else
         {
-            String propertyValue = (String) value;
+            String propertyValue = value.toString();
             // don't validate error messages and placeholders
             if (propertyValue.startsWith(BasicConstant.ERROR_PROPERTY_PREFIX))
             {
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 77a6ce3585cf0606b9860f97e35c2cc82bbbc03c..4f3e21974e671cc5dc604e00ed53b70175b0f605 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
@@ -145,6 +145,12 @@ public class UpdateEntityPropertyExecutorTest extends AbstractEntityPropertyExec
                     throw new UnsupportedOperationException();
                 }
 
+                @Override
+                public String getPropertyAsString(String propertyName)
+                {
+                    throw new UnsupportedOperationException();
+                }
+
                 @Override
                 public String getProperty(String propertyName)
                 {
@@ -170,26 +176,52 @@ 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 List<String> getMultiValueControlledVocabularyProperty(String propertyName)
+                {
+                    throw new UnsupportedOperationException();
+                }
+
+                @Override
+                public void setMultiValueControlledVocabularyProperty(String propertyName,
+                        List<String> propertyValue)
+                {
+                    throw new UnsupportedOperationException();
+                }
+
+                @Override
+                public List<SamplePermId> getMultiValueSampleProperty(String propertyName)
                 {
                     throw new UnsupportedOperationException();
                 }
 
                 @Override
-                public SamplePermId[] getSampleProperty(String propertyName)
+                public void setMultiValueSampleProperty(String propertyName,
+                        List<SamplePermId> propertyValue)
                 {
                     throw new UnsupportedOperationException();
                 }
 
                 @Override
-                public void setSampleProperty(String propertyName, SamplePermId[] propertyValue)
+                public SamplePermId getSampleProperty(String propertyName)
+                {
+                    throw new UnsupportedOperationException();
+                }
+
+                @Override
+                public void setSampleProperty(String propertyName, SamplePermId propertyValue)
                 {
                     throw new UnsupportedOperationException();
                 }
@@ -339,6 +371,176 @@ public class UpdateEntityPropertyExecutorTest extends AbstractEntityPropertyExec
                     throw new UnsupportedOperationException();
                 }
 
+                @Override
+                public List<Long> getMultiValueIntegerProperty(String propertyName)
+                {
+                    throw new UnsupportedOperationException();
+                }
+
+                @Override
+                public void setMultiValueIntegerProperty(String propertyName,
+                        List<Long> propertyValues)
+                {
+                    throw new UnsupportedOperationException();
+                }
+
+                @Override
+                public List<String> getMultiValueVarcharProperty(String propertyName)
+                {
+                    throw new UnsupportedOperationException();
+                }
+
+                @Override
+                public void setMultiValueVarcharProperty(String propertyName,
+                        List<String> propertyValues)
+                {
+                    throw new UnsupportedOperationException();
+                }
+
+                @Override
+                public List<String> getMultiValueMultilineVarcharProperty(String propertyName)
+                {
+                    throw new UnsupportedOperationException();
+                }
+
+                @Override
+                public void setMultiValueMultilineVarcharProperty(String propertyName,
+                        List<String> propertyValue)
+                {
+                    throw new UnsupportedOperationException();
+                }
+
+                @Override
+                public List<Double> getMultiValueRealProperty(String propertyName)
+                {
+                    throw new UnsupportedOperationException();
+                }
+
+                @Override
+                public void setMultiValueRealProperty(String propertyName,
+                        List<Double> propertyValue)
+                {
+                    throw new UnsupportedOperationException();
+                }
+
+                @Override
+                public List<ZonedDateTime> getMultiValueTimestampProperty(String propertyName)
+                {
+                    throw new UnsupportedOperationException();
+                }
+
+                @Override
+                public void setMultiValueTimestampProperty(String propertyName,
+                        List<ZonedDateTime> propertyValue)
+                {
+                    throw new UnsupportedOperationException();
+                }
+
+                @Override
+                public List<Boolean> getMultiValueBooleanProperty(String propertyName)
+                {
+                    throw new UnsupportedOperationException();
+                }
+
+                @Override
+                public void setMultiValueBooleanProperty(String propertyName,
+                        List<Boolean> propertyValue)
+                {
+                    throw new UnsupportedOperationException();
+                }
+
+                @Override
+                public List<String> getMultiValueHyperlinkProperty(String propertyName)
+                {
+                    throw new UnsupportedOperationException();
+                }
+
+                @Override
+                public void setMultiValueHyperlinkProperty(String propertyName,
+                        List<String> propertyValue)
+                {
+                    throw new UnsupportedOperationException();
+                }
+
+                @Override
+                public List<String> getMultiValueXmlProperty(String propertyName)
+                {
+                    throw new UnsupportedOperationException();
+                }
+
+                @Override
+                public void setMultiValueXmlProperty(String propertyName,
+                        List<String> propertyValue)
+                {
+                    throw new UnsupportedOperationException();
+                }
+
+                @Override
+                public List<Long[]> getMultiValueIntegerArrayProperty(String propertyName)
+                {
+                    throw new UnsupportedOperationException();
+                }
+
+                @Override
+                public void setMultiValueIntegerArrayProperty(String propertyName,
+                        List<Long[]> propertyValue)
+                {
+                    throw new UnsupportedOperationException();
+                }
+
+                @Override
+                public List<Double[]> getMultiValueRealArrayProperty(String propertyName)
+                {
+                    throw new UnsupportedOperationException();
+                }
+
+                @Override
+                public void setMultiValueRealArrayProperty(String propertyName,
+                        List<Double[]> propertyValue)
+                {
+                    throw new UnsupportedOperationException();
+                }
+
+                @Override
+                public List<String[]> getMultiValueStringArrayProperty(String propertyName)
+                {
+                    throw new UnsupportedOperationException();
+                }
+
+                @Override
+                public void setMultiValueStringArrayProperty(String propertyName,
+                        List<String[]> propertyValue)
+                {
+                    throw new UnsupportedOperationException();
+                }
+
+                @Override
+                public List<ZonedDateTime[]> getMultiValueTimestampArrayProperty(
+                        String propertyName)
+                {
+                    throw new UnsupportedOperationException();
+                }
+
+                @Override
+                public void setMultiValueTimestampArrayProperty(String propertyName,
+                        List<ZonedDateTime[]> propertyValue)
+                {
+                    throw new UnsupportedOperationException();
+                }
+
+                @Override
+                public List<String> getMultiValueJsonProperty(String propertyName)
+                {
+                    throw new UnsupportedOperationException();
+                }
+
+                @Override
+                public void setMultiValueJsonProperty(String propertyName,
+                        List<String> propertyValue)
+                {
+                    throw new UnsupportedOperationException();
+                }
+
             };
         MapBatch<IPropertiesHolder, IEntityInformationWithPropertiesHolder> batch =
                 new MapBatch<>(0, 0, 1, Collections.singletonMap(holder, entity), 1);
diff --git a/server-application-server/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/AbstractSearchPropertyTest.java b/server-application-server/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/AbstractSearchPropertyTest.java
index 30400c294e6d5be9f130e31d5c563d642acc55b8..0d2a3777ec79cd00cf4d973c521f58c23a9b74fa 100644
--- a/server-application-server/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/AbstractSearchPropertyTest.java
+++ b/server-application-server/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/AbstractSearchPropertyTest.java
@@ -839,7 +839,7 @@ public abstract class AbstractSearchPropertyTest extends AbstractTest
         final VocabularyPermId vocabularyPermId = createControlledVocabulary(sessionToken, "SEASONS");
 
         final PropertyTypePermId propertyTypeId = createAPropertyType(sessionToken, DataType.CONTROLLEDVOCABULARY,
-                vocabularyPermId);
+                vocabularyPermId, false);
         final ObjectPermId entityPermId = createEntity(sessionToken, propertyTypeId, value);
         final AbstractEntitySearchCriteria<?> searchCriteria = createSearchCriteria();
         new StringQueryInjector(searchCriteria, propertyTypeId).buildCriteria(queryString);
@@ -875,7 +875,7 @@ public abstract class AbstractSearchPropertyTest extends AbstractTest
         final VocabularyPermId vocabularyPermId = createControlledVocabulary(sessionToken, vocabularyCode);
 
         final PropertyTypePermId propertyTypeId = createAPropertyType(sessionToken, DataType.CONTROLLEDVOCABULARY,
-                vocabularyPermId, propertyTypeCode);
+                vocabularyPermId, propertyTypeCode, false);
         final ObjectPermId entityPermId = createEntity(sessionToken, propertyTypeId, value);
         final AbstractEntitySearchCriteria<?> searchCriteria = createSearchCriteria();
         new VocabularyQueryInjector(searchCriteria, propertyTypeCode).buildCriteria(queryString);
diff --git a/server-application-server/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/AbstractTest.java b/server-application-server/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/AbstractTest.java
index 084f44a657a152f71bf0de45b086ca27b08db991..972d9eced25f0e2c51965d2301e8ff0eb246e290 100644
--- a/server-application-server/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/AbstractTest.java
+++ b/server-application-server/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/AbstractTest.java
@@ -1487,17 +1487,22 @@ public class AbstractTest extends SystemTestCase
 
     protected PropertyTypePermId createAPropertyType(final String sessionToken, final DataType dataType)
     {
-        return createAPropertyType(sessionToken, dataType, new VocabularyPermId("ORGANISM"));
+        return createAPropertyType(sessionToken, dataType, new VocabularyPermId("ORGANISM"), false);
+    }
+
+    protected PropertyTypePermId createAPropertyType(final String sessionToken, final DataType dataType, final boolean multiValue)
+    {
+        return createAPropertyType(sessionToken, dataType, new VocabularyPermId("ORGANISM"), multiValue);
     }
 
     protected PropertyTypePermId createAPropertyType(final String sessionToken, final DataType dataType,
-            final VocabularyPermId vocabularyPermId)
+            final VocabularyPermId vocabularyPermId, final boolean multiValue)
     {
-        return createAPropertyType(sessionToken, dataType, vocabularyPermId, "TYPE-" + System.currentTimeMillis());
+        return createAPropertyType(sessionToken, dataType, vocabularyPermId, "TYPE-" + System.currentTimeMillis(), multiValue);
     }
 
     protected PropertyTypePermId createAPropertyType(final String sessionToken, final DataType dataType,
-            final VocabularyPermId vocabularyPermId, final String code)
+            final VocabularyPermId vocabularyPermId, final String code, final boolean multiValue)
     {
         final PropertyTypeCreation creation = new PropertyTypeCreation();
         creation.setCode(code);
@@ -1508,7 +1513,7 @@ public class AbstractTest extends SystemTestCase
         {
             creation.setVocabularyId(vocabularyPermId);
         }
-        creation.setMultiValue(false);
+        creation.setMultiValue(multiValue);
         return v3api.createPropertyTypes(sessionToken, Collections.singletonList(creation)).get(0);
     }
 
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 59018087e35b98a7fb7d88228b2e9a617e9c6ad6..4d888fd737044d20b0422b09a68edfb6764d485b 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
@@ -16,6 +16,7 @@
 package ch.ethz.sis.openbis.systemtest.asapi.v3;
 
 import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertEqualsNoOrder;
 import static org.testng.Assert.assertNull;
 
 import java.io.Serializable;
@@ -2107,6 +2108,63 @@ public class CreateDataSetTest extends AbstractDataSetTest
         assertEquals(dataSet.getProperties().get(propertyType.getPermId()), "2008-12-24 03:04:00 +0100");
         assertEquals(dataSet.getProperties().size(), 2);
     }
+    
+    @Test
+    public void testCreateWithPropertyOfTypeTimestampDedicatedMethod()
+    {
+        // Given
+        String sessionToken = v3api.login(TEST_USER, PASSWORD);
+        PropertyTypePermId propertyType = createAPropertyType(sessionToken, DataType.TIMESTAMP);
+        EntityTypePermId dataSetType = createADataSetType(sessionToken, true, propertyType, PLATE_GEOMETRY);
+
+        DataSetCreation creation = physicalDataSetCreation();
+        creation.setTypeId(dataSetType);
+        creation.setProperty(PLATE_GEOMETRY.getPermId(), "384_WELLS_16X24");
+        ZonedDateTime time1 = ZonedDateTime.parse("2023-05-16T11:22:33+02");
+        creation.setTimestampProperty(propertyType.getPermId(), time1);
+
+        // 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");
+        assertEquals(dataSet.getTimestampProperty(propertyType.getPermId()), ZonedDateTime.parse("2023-05-16T11:22:33+02:00"));
+        assertEquals(dataSet.getProperties().size(), 2);
+    }
+
+    @Test
+    public void testCreateWithMultiValuePropertyOfTypeTimestamp()
+    {
+        // Given
+        String sessionToken = v3api.login(TEST_USER, PASSWORD);
+        PropertyTypePermId propertyType = createAPropertyType(sessionToken, DataType.TIMESTAMP, true);
+        EntityTypePermId dataSetType = createADataSetType(sessionToken, true, propertyType, PLATE_GEOMETRY);
+
+        DataSetCreation creation = physicalDataSetCreation();
+        creation.setTypeId(dataSetType);
+        creation.setProperty(PLATE_GEOMETRY.getPermId(), "384_WELLS_16X24");
+        ZonedDateTime time1 = ZonedDateTime.parse("2023-05-16T11:22:33+02");
+        ZonedDateTime time2 = ZonedDateTime.parse("2023-05-18T11:17:03+02");
+        creation.setMultiValueTimestampProperty(propertyType.getPermId(), List.of(time1, time2));
+
+        // 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");
+        assertEqualsNoOrder(dataSet.getMultiValueTimestampProperty(propertyType.getPermId()).toArray(ZonedDateTime[]::new), new ZonedDateTime[] {time1, time2});
+        assertEquals(dataSet.getProperties().size(), 2);
+    }
 
     @Test
     public void testCreateWithPropertyOfTypeJson()
@@ -2134,6 +2192,94 @@ public class CreateDataSetTest extends AbstractDataSetTest
         assertEquals(dataSet.getJsonProperty(propertyType.getPermId()), "{\"key\": \"value\", \"array\": [1, 2, 3]}");
         assertEquals(dataSet.getProperties().size(), 2);
     }
+    
+    @Test
+    public void testCreateWithMultiValuePropertyOfTypeJson()
+    {
+        // Given
+        String sessionToken = v3api.login(TEST_USER, PASSWORD);
+        PropertyTypePermId propertyType = createAPropertyType(sessionToken, DataType.JSON, true);
+        EntityTypePermId dataSetType = createADataSetType(sessionToken, true, propertyType, PLATE_GEOMETRY);
+
+        DataSetCreation creation = physicalDataSetCreation();
+        creation.setTypeId(dataSetType);
+        creation.setProperty(PLATE_GEOMETRY.getPermId(), "384_WELLS_16X24");
+        creation.setMultiValueJsonProperty(propertyType.getPermId(), List.of("{\"key\": \"value\", \"array\":[1,2,3]}", "{\"key\": \"value2\", \"array\":[]}"));
+
+        // 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");
+        
+        List<String> properties = dataSet.getMultiValueJsonProperty(propertyType.getPermId());
+        assertEquals(properties.size(), 2);
+        assertEqualsNoOrder(properties.toArray(String[]::new), new String[] {"{\"key\": \"value\", \"array\": [1, 2, 3]}", "{\"key\": \"value2\", \"array\": []}"});
+        assertEquals(dataSet.getProperties().size(), 2);
+    }
+
+    @Test
+    public void testCreateWithMultiValuePropertyOfTypeInteger()
+    {
+        // Given
+        String sessionToken = v3api.login(TEST_USER, PASSWORD);
+        PropertyTypePermId propertyType = createAPropertyType(sessionToken, DataType.INTEGER, true);
+        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 Long[] {1L, 1L, 3L});
+
+        // 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");
+        
+        assertEqualsNoOrder((Serializable[]) dataSet.getProperties().get(propertyType.getPermId()), new Serializable[] { "1", "1", "3" });
+        assertEqualsNoOrder(dataSet.getMultiValueIntegerProperty(propertyType.getPermId()).toArray(Long[]::new), new Long[] { 1L, 1L, 3L });
+        assertEquals(dataSet.getProperties().size(), 2);
+    }
+
+    @Test
+    public void testCreateWithMultiValuePropertyOfTypeIntegerDedicatedMethod()
+    {
+        // Given
+        String sessionToken = v3api.login(TEST_USER, PASSWORD);
+        PropertyTypePermId propertyType = createAPropertyType(sessionToken, DataType.INTEGER, true);
+        EntityTypePermId dataSetType = createADataSetType(sessionToken, true, propertyType, PLATE_GEOMETRY);
+
+        DataSetCreation creation = physicalDataSetCreation();
+        creation.setTypeId(dataSetType);
+        creation.setProperty(PLATE_GEOMETRY.getPermId(), "384_WELLS_16X24");
+        creation.setMultiValueIntegerProperty(propertyType.getPermId(), List.of(1L, 2L, 3L));
+
+        // 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");
+        
+        assertEqualsNoOrder((Serializable[]) dataSet.getProperties().get(propertyType.getPermId()), new Serializable[] { "1", "2", "3" });
+        assertEqualsNoOrder(dataSet.getMultiValueIntegerProperty(propertyType.getPermId()).toArray(Long[]::new), new Long[] { 1L, 2L, 3L });
+        assertEquals(dataSet.getProperties().size(), 2);
+    }
 
     @Test
     public void testCreateWithPropertyOfTypeArrayInteger()
@@ -2161,6 +2307,80 @@ public class CreateDataSetTest extends AbstractDataSetTest
         assertEquals(dataSet.getIntegerArrayProperty(propertyType.getPermId()), new Long[]{1L, 2L, 3L});
         assertEquals(dataSet.getProperties().size(), 2);
     }
+    
+     @Test
+    public void testCreateWithMultiValuePropertyOfTypeArrayInteger()
+    {
+        // Given
+        String sessionToken = v3api.login(TEST_USER, PASSWORD);
+        PropertyTypePermId propertyType = createAPropertyType(sessionToken, DataType.ARRAY_INTEGER, true);
+        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 Long[][]{ new Long[]{1L, 2L, 3L}, new Long[]{4L, 5L, 6L} });
+
+        // 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");
+
+        List<Long[]> props = dataSet.getMultiValueIntegerArrayProperty(propertyType.getPermId());
+        assertEquals(props.size(), 2);
+        for(Long[] prop : props) {
+            if(prop[0] > 3L) {
+                assertEqualsNoOrder(prop, new Long[] {4L, 5L, 6L});
+            } else {
+                assertEqualsNoOrder(prop, new Long[] {1L, 2L, 3L});
+            }
+        }
+
+        assertEquals(dataSet.getProperties().size(), 2);
+    }
+
+    @Test
+    public void testCreateWithMultiValuePropertyOfTypeArrayIntegerDedicatedMethod()
+    {
+        // Given
+        String sessionToken = v3api.login(TEST_USER, PASSWORD);
+        PropertyTypePermId propertyType = createAPropertyType(sessionToken, DataType.ARRAY_INTEGER, true);
+        EntityTypePermId dataSetType = createADataSetType(sessionToken, true, propertyType, PLATE_GEOMETRY);
+
+        DataSetCreation creation = physicalDataSetCreation();
+        creation.setTypeId(dataSetType);
+        creation.setProperty(PLATE_GEOMETRY.getPermId(), "384_WELLS_16X24");
+        creation.setMultiValueIntegerArrayProperty(propertyType.getPermId(), List.of( new Long[]{1L, 2L, 3L}, new Long[]{4L, 5L, 6L} ));
+
+        // 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");
+
+        List<Long[]> props = dataSet.getMultiValueIntegerArrayProperty(propertyType.getPermId());
+        assertEquals(props.size(), 2);
+        for(Long[] prop : props) {
+            if(prop[0] > 3L) {
+                assertEqualsNoOrder(prop, new Long[] {4L, 5L, 6L});
+            } else {
+                assertEqualsNoOrder(prop, new Long[] {1L, 2L, 3L});
+            }
+        }
+
+        assertEquals(dataSet.getProperties().size(), 2);
+    }
 
     @Test
     public void testCreateWithPropertyOfTypeArrayReal()
@@ -2188,6 +2408,80 @@ public class CreateDataSetTest extends AbstractDataSetTest
         assertEquals(dataSet.getRealArrayProperty(propertyType.getPermId()), new Double[]{1.0, 2.0, 3.0});
         assertEquals(dataSet.getProperties().size(), 2);
     }
+    
+    @Test
+    public void testCreateWithMultiValuePropertyOfTypeArrayReal()
+    {
+        // Given
+        String sessionToken = v3api.login(TEST_USER, PASSWORD);
+        PropertyTypePermId propertyType = createAPropertyType(sessionToken, DataType.ARRAY_REAL, true);
+        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 Double[][]{ new Double[]{1.0, 2.0, 3.0}, new Double[]{4.0, 5.0, 6.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");
+
+        List<Long[]> props = dataSet.getMultiValueIntegerArrayProperty(propertyType.getPermId());
+        assertEquals(props.size(), 2);
+        for(Long[] prop : props) {
+            if(prop[0] > 3L) {
+                assertEqualsNoOrder(prop, new Long[] {4L, 5L, 6L});
+            } else {
+                assertEqualsNoOrder(prop, new Long[] {1L, 2L, 3L});
+            }
+        }
+
+        assertEquals(dataSet.getProperties().size(), 2);
+    }
+
+    @Test
+    public void testCreateWithMultiValuePropertyOfTypeArrayRealDedicatedMethod()
+    {
+        // Given
+        String sessionToken = v3api.login(TEST_USER, PASSWORD);
+        PropertyTypePermId propertyType = createAPropertyType(sessionToken, DataType.ARRAY_INTEGER, true);
+        EntityTypePermId dataSetType = createADataSetType(sessionToken, true, propertyType, PLATE_GEOMETRY);
+
+        DataSetCreation creation = physicalDataSetCreation();
+        creation.setTypeId(dataSetType);
+        creation.setProperty(PLATE_GEOMETRY.getPermId(), "384_WELLS_16X24");
+        creation.setMultiValueIntegerArrayProperty(propertyType.getPermId(), List.of( new Long[]{1L, 2L, 3L}, new Long[]{4L, 5L, 6L} ));
+
+        // 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");
+
+        List<Long[]> props = dataSet.getMultiValueIntegerArrayProperty(propertyType.getPermId());
+        assertEquals(props.size(), 2);
+        for(Long[] prop : props) {
+            if(prop[0] > 3L) {
+                assertEqualsNoOrder(prop, new Long[] {4L, 5L, 6L});
+            } else {
+                assertEqualsNoOrder(prop, new Long[] {1L, 2L, 3L});
+            }
+        }
+
+        assertEquals(dataSet.getProperties().size(), 2);
+    }
 
     @Test
     public void testCreateWithPropertyOfTypeArrayString()
@@ -2215,6 +2509,41 @@ public class CreateDataSetTest extends AbstractDataSetTest
         assertEquals(dataSet.getStringArrayProperty(propertyType.getPermId()), new String[]{"a", "b", "c"});
         assertEquals(dataSet.getProperties().size(), 2);
     }
+    
+    @Test
+    public void testCreateWithMultiValuePropertyOfTypeArrayString()
+    {
+        // Given
+        String sessionToken = v3api.login(TEST_USER, PASSWORD);
+        PropertyTypePermId propertyType = createAPropertyType(sessionToken, DataType.ARRAY_STRING, true);
+        EntityTypePermId dataSetType = createADataSetType(sessionToken, true, propertyType, PLATE_GEOMETRY);
+
+        DataSetCreation creation = physicalDataSetCreation();
+        creation.setTypeId(dataSetType);
+        creation.setProperty(PLATE_GEOMETRY.getPermId(), "384_WELLS_16X24");
+        creation.setMultiValueStringArrayProperty(propertyType.getPermId(), List.of(new String[]{"a,a", "b", "c"}, new String[]{"a", "b", "c", "d"}));
+
+        // 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");
+        List<String[]> result = dataSet.getMultiValueStringArrayProperty(propertyType.getPermId());
+        assertEquals(result.size(), 2);
+        for(String[] prop : result) {
+            if(prop.length > 3) {
+                assertEqualsNoOrder(prop, new String[]{"a", "b", "c", "d"});
+            } else {
+                assertEqualsNoOrder(prop, new String[]{"a,a", "b", "c"});
+            }
+        }
+        assertEquals(dataSet.getProperties().size(), 2);
+    }
 
     @Test
     public void testCreateWithPropertyOfTypeArrayTimestamp()
@@ -2244,6 +2573,45 @@ public class CreateDataSetTest extends AbstractDataSetTest
         assertEquals(dataSet.getTimestampArrayProperty(propertyType.getPermId()), new ZonedDateTime[]{time1, time2});
         assertEquals(dataSet.getProperties().size(), 2);
     }
+    
+    @Test
+    public void testCreateWithMultiValuePropertyOfTypeArrayTimestamp()
+    {
+        // Given
+        String sessionToken = v3api.login(TEST_USER, PASSWORD);
+        PropertyTypePermId propertyType = createAPropertyType(sessionToken, DataType.ARRAY_TIMESTAMP, true);
+        EntityTypePermId dataSetType = createADataSetType(sessionToken, true, propertyType, PLATE_GEOMETRY);
+
+        DataSetCreation creation = physicalDataSetCreation();
+        creation.setTypeId(dataSetType);
+        creation.setProperty(PLATE_GEOMETRY.getPermId(), "384_WELLS_16X24");
+        ZonedDateTime time1 = ZonedDateTime.parse("2023-05-16T11:22:33+02");
+        ZonedDateTime time2 = ZonedDateTime.parse("2023-05-18T11:17:03+02");
+        ZonedDateTime time3 = ZonedDateTime.parse("2023-05-20T11:10:03+02");
+        creation.setMultiValueTimestampArrayProperty(propertyType.getPermId(), List.of(new ZonedDateTime[]{time1, time2}, new ZonedDateTime[]{time3, time2, time1}));
+
+        // 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");
+
+        List<ZonedDateTime[]> properties = dataSet.getMultiValueTimestampArrayProperty(propertyType.getPermId());
+        assertEquals(properties.size(), 2);
+        for(ZonedDateTime[] prop : properties) {
+            if(prop.length == 2) {
+                assertEqualsNoOrder(prop, new ZonedDateTime[]{time1, time2});
+            } else {
+                assertEqualsNoOrder(prop, new ZonedDateTime[]{time1, time2, time3});
+            }
+        }
+        assertEquals(dataSet.getProperties().size(), 2);
+    }
 
 
     @Test
@@ -2265,7 +2633,45 @@ public class CreateDataSetTest extends AbstractDataSetTest
         DataSetCreation creation = physicalDataSetCreation();
         creation.setTypeId(dataSetType);
         creation.setProperty(PLATE_GEOMETRY.getPermId(), "384_WELLS_16X24");
-        creation.setControlledVocabularyProperty(propertyType.getPermId(), new String[] {"DOG", "HUMAN"});
+        creation.setMultiValueControlledVocabularyProperty(propertyType.getPermId(), List.of("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");
+        List<String> vocabProperties = dataSet.getMultiValueControlledVocabularyProperty(propertyType.getPermId());
+        Collections.sort(vocabProperties);
+        assertEquals(vocabProperties, List.of("DOG", "HUMAN"));
+        assertEquals(dataSet.getProperties().size(), 2);
+    }
+    
+    @Test
+    public void testCreateWithMultiValuePropertyVocabularyDedicatedMethod()
+    {
+        // 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.setMultiValueControlledVocabularyProperty(propertyType.getPermId(), List.of("DOG", "HUMAN"));
 
         // When
         List<DataSetPermId> dataSetIds = v3api.createDataSets(sessionToken, Arrays.asList(creation));
@@ -2277,9 +2683,9 @@ public class CreateDataSetTest extends AbstractDataSetTest
         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"});
+        List<String> vocabProperties = dataSet.getMultiValueControlledVocabularyProperty(propertyType.getPermId());
+        Collections.sort(vocabProperties);
+        assertEquals(vocabProperties, List.of("DOG", "HUMAN"));
         assertEquals(dataSet.getProperties().size(), 2);
     }
 
@@ -2348,6 +2754,73 @@ public class CreateDataSetTest extends AbstractDataSetTest
         assertEquals(sampleProps, new Serializable[]{"200811050919915-8", sample2.getPermId().getPermId()});
         assertEquals(dataSet.getProperties().size(), 2);
     }
+    
+    @Test
+    public void testCreateWithMultiValuePropertySampleDedicatedMethod()
+    {
+        // 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-" + System.currentTimeMillis());
+        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.setMultiValueSampleProperty(propertyType.getPermId(), List.of(new SamplePermId("200811050919915-8"), 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 testCreateWithMultiValuePropertySample2() {
@@ -2390,7 +2863,7 @@ public class CreateDataSetTest extends AbstractDataSetTest
         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)});
+        creation.setMultiValueSampleProperty(propertyType.getPermId(), List.of(new SamplePermId("/CISD/CL1"), sampleIds.get(0)));
 
         // When
         List<DataSetPermId> dataSetIds = v3api.createDataSets(sessionToken, Arrays.asList(creation));
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 78555ce9665dd53b9bc0bf084430aa3075e573c3..aed6a6ed9198c887e02ec93ca66eb861bab3ca7c 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
@@ -16,19 +16,13 @@
 package ch.ethz.sis.openbis.systemtest.asapi.v3;
 
 import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertEqualsNoOrder;
 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;
-import java.util.Collections;
-import java.util.Date;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.UUID;
+import java.util.*;
 
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.property.create.PropertyTypeCreation;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.sample.fetchoptions.SampleFetchOptions;
@@ -870,6 +864,67 @@ public class CreateExperimentTest extends AbstractExperimentTest
         assertEquals(experiment2.getProperties().size(), 2);
     }
 
+    @Test
+    public void testCreateWithPropertyOfTypeTimestampDedicatedMethod()
+    {
+        // Given
+        String sessionToken = v3api.login(TEST_USER, PASSWORD);
+        PropertyTypePermId propertyType = createAPropertyType(sessionToken, DataType.TIMESTAMP);
+        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");
+        ZonedDateTime time1 = ZonedDateTime.parse("2023-05-16T11:22:33+02");
+        creation.setTimestampProperty(propertyType.getPermId(), time1);
+
+        // 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");
+        assertEquals(experiment2.getTimestampProperty(propertyType.getPermId()), time1);
+        assertEquals(experiment2.getProperties().size(), 2);
+    }
+
+    @Test
+    public void testCreateWithMultiValuePropertyOfTypeTimestamp()
+    {
+        // Given
+        String sessionToken = v3api.login(TEST_USER, PASSWORD);
+        PropertyTypePermId propertyType = createAPropertyType(sessionToken, DataType.TIMESTAMP, true);
+        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");
+        ZonedDateTime time1 = ZonedDateTime.parse("2023-05-16T11:22:33+02");
+        ZonedDateTime time2 = ZonedDateTime.parse("2023-05-18T11:17:03+02");
+        creation.setMultiValueTimestampProperty(propertyType.getPermId(), List.of(time1, time2));
+
+        // 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");
+        assertEqualsNoOrder(experiment2.getMultiValueTimestampProperty(propertyType.getPermId()).toArray(ZonedDateTime[]::new), new ZonedDateTime[] {time1, time2});
+        assertEquals(experiment2.getProperties().size(), 2);
+    }
+
     @Test
     public void testCreateWithPropertyOfTypeJson()
     {
@@ -899,6 +954,99 @@ public class CreateExperimentTest extends AbstractExperimentTest
         assertEquals(experiment2.getProperties().size(), 2);
     }
 
+    @Test
+    public void testCreateWithMultiValuePropertyOfTypeJson()
+    {
+        // Given
+        String sessionToken = v3api.login(TEST_USER, PASSWORD);
+        PropertyTypePermId propertyType = createAPropertyType(sessionToken, DataType.JSON, true);
+        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.setMultiValueJsonProperty(propertyType.getPermId(), List.of("{\"key\": \"value\", \"array\":[1,2,3]}", "{\"key\": \"value2\", \"array\":[]}"));
+
+        // 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");
+        List<String> properties = experiment2.getMultiValueJsonProperty(propertyType.getPermId());
+        assertEquals(properties.size(), 2);
+        assertEqualsNoOrder(properties.toArray(String[]::new), new String[] {"{\"key\": \"value\", \"array\": [1, 2, 3]}", "{\"key\": \"value2\", \"array\": []}"});
+        assertEquals(experiment2.getProperties().size(), 2);
+    }
+
+    @Test
+    public void testCreateWithMultiValuePropertyOfTypeInteger()
+    {
+        // Given
+        String sessionToken = v3api.login(TEST_USER, PASSWORD);
+        PropertyTypePermId propertyType = createAPropertyType(sessionToken, DataType.INTEGER, true);
+        EntityTypePermId experimentType = createAnExperimentType(sessionToken, true, propertyType, PLATE_GEOMETRY);
+
+        ExperimentCreation creation = new ExperimentCreation();
+        creation.setCode("EXPERIMENT_WITH_MULTI_INTEGER_PROPERTY");
+        creation.setTypeId(experimentType);
+        creation.setProjectId(new ProjectIdentifier("/CISD/NEMO"));
+        creation.setProperty(PLATE_GEOMETRY.getPermId(), "384_WELLS_16X24");
+        creation.setProperty(propertyType.getPermId(), new Long[] {1L, 1L, 3L});
+
+        // 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");
+        assertEqualsNoOrder((Serializable[]) experiment2.getProperties().get(propertyType.getPermId()), new Serializable[] { "1", "1", "3" });
+        assertEqualsNoOrder(experiment2.getMultiValueIntegerProperty(propertyType.getPermId()).toArray(Long[]::new), new Long[] { 1L, 1L, 3L });
+        assertEquals(experiment2.getProperties().size(), 2);
+    }
+
+    @Test
+    public void testCreateWithMultiValuePropertyOfTypeIntegerDedicatedMethod()
+    {
+        // Given
+        String sessionToken = v3api.login(TEST_USER, PASSWORD);
+        PropertyTypePermId propertyType = createAPropertyType(sessionToken, DataType.INTEGER, true);
+        EntityTypePermId experimentType = createAnExperimentType(sessionToken, true, propertyType, PLATE_GEOMETRY);
+
+        ExperimentCreation creation = new ExperimentCreation();
+        creation.setCode("EXPERIMENT_WITH_MULTI_INTEGER_PROPERTY");
+        creation.setTypeId(experimentType);
+        creation.setProjectId(new ProjectIdentifier("/CISD/NEMO"));
+        creation.setProperty(PLATE_GEOMETRY.getPermId(), "384_WELLS_16X24");
+        creation.setMultiValueIntegerProperty(propertyType.getPermId(), List.of(1L, 2L, 3L));
+
+        // 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");
+        assertEqualsNoOrder((Serializable[]) experiment2.getProperties().get(propertyType.getPermId()), new Serializable[] { "1", "2", "3" });
+        assertEqualsNoOrder(experiment2.getMultiValueIntegerProperty(propertyType.getPermId()).toArray(Long[]::new), new Long[] { 1L, 2L, 3L });
+        assertEquals(experiment2.getProperties().size(), 2);
+    }
+
     @Test
     public void testCreateWithPropertyOfTypeArrayInteger()
     {
@@ -908,7 +1056,7 @@ public class CreateExperimentTest extends AbstractExperimentTest
         EntityTypePermId experimentType = createAnExperimentType(sessionToken, true, propertyType, PLATE_GEOMETRY);
 
         ExperimentCreation creation = new ExperimentCreation();
-        creation.setCode("EXPERIMENT_WITH_SAMPLE_PROPERTY");
+        creation.setCode("EXPERIMENT_WITH_ARRAY_INTEGER_PROPERTY");
         creation.setTypeId(experimentType);
         creation.setProjectId(new ProjectIdentifier("/CISD/NEMO"));
         creation.setProperty(PLATE_GEOMETRY.getPermId(), "384_WELLS_16X24");
@@ -928,6 +1076,85 @@ public class CreateExperimentTest extends AbstractExperimentTest
         assertEquals(experiment2.getProperties().size(), 2);
     }
 
+
+    @Test
+    public void testCreateWithMultiValuePropertyOfTypeArrayInteger()
+    {
+        // Given
+        String sessionToken = v3api.login(TEST_USER, PASSWORD);
+        PropertyTypePermId propertyType = createAPropertyType(sessionToken, DataType.ARRAY_INTEGER, true);
+        EntityTypePermId experimentType = createAnExperimentType(sessionToken, true, propertyType, PLATE_GEOMETRY);
+
+        ExperimentCreation creation = new ExperimentCreation();
+        creation.setCode("EXPERIMENT_WITH_MULTI_VALUE_INTEGER_ARRAY_PROPERTY");
+        creation.setTypeId(experimentType);
+        creation.setProjectId(new ProjectIdentifier("/CISD/NEMO"));
+        creation.setProperty(PLATE_GEOMETRY.getPermId(), "384_WELLS_16X24");
+        creation.setProperty(propertyType.getPermId(), new Long[][]{ new Long[]{1L, 2L, 3L}, new Long[]{4L, 5L, 6L} });
+
+        // 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");
+
+        List<Long[]> props = experiment2.getMultiValueIntegerArrayProperty(propertyType.getPermId());
+        assertEquals(props.size(), 2);
+        for(Long[] prop : props) {
+            if(prop[0] > 3L) {
+                assertEqualsNoOrder(prop, new Long[] {4L, 5L, 6L});
+            } else {
+                assertEqualsNoOrder(prop, new Long[] {1L, 2L, 3L});
+            }
+        }
+
+        assertEquals(experiment2.getProperties().size(), 2);
+    }
+
+    @Test
+    public void testCreateWithMultiValuePropertyOfTypeArrayIntegerDedicatedMethod()
+    {
+        // Given
+        String sessionToken = v3api.login(TEST_USER, PASSWORD);
+        PropertyTypePermId propertyType = createAPropertyType(sessionToken, DataType.ARRAY_INTEGER, true);
+        EntityTypePermId experimentType = createAnExperimentType(sessionToken, true, propertyType, PLATE_GEOMETRY);
+
+        ExperimentCreation creation = new ExperimentCreation();
+        creation.setCode("EXPERIMENT_WITH_MULTI_VALUE_INTEGER_ARRAY_PROPERTY");
+        creation.setTypeId(experimentType);
+        creation.setProjectId(new ProjectIdentifier("/CISD/NEMO"));
+        creation.setProperty(PLATE_GEOMETRY.getPermId(), "384_WELLS_16X24");
+        creation.setMultiValueIntegerArrayProperty(propertyType.getPermId(), List.of( new Long[]{1L, 2L, 3L}, new Long[]{4L, 5L, 6L} ));
+
+        // 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");
+
+        List<Long[]> props = experiment2.getMultiValueIntegerArrayProperty(propertyType.getPermId());
+        assertEquals(props.size(), 2);
+        for(Long[] prop : props) {
+            if(prop[0] > 3L) {
+                assertEqualsNoOrder(prop, new Long[] {4L, 5L, 6L});
+            } else {
+                assertEqualsNoOrder(prop, new Long[] {1L, 2L, 3L});
+            }
+        }
+
+        assertEquals(experiment2.getProperties().size(), 2);
+    }
+
     @Test
     public void testCreateWithPropertyOfTypeArrayReal()
     {
@@ -937,7 +1164,7 @@ public class CreateExperimentTest extends AbstractExperimentTest
         EntityTypePermId experimentType = createAnExperimentType(sessionToken, true, propertyType, PLATE_GEOMETRY);
 
         ExperimentCreation creation = new ExperimentCreation();
-        creation.setCode("EXPERIMENT_WITH_SAMPLE_PROPERTY");
+        creation.setCode("EXPERIMENT_WITH_REAL_PROPERTY");
         creation.setTypeId(experimentType);
         creation.setProjectId(new ProjectIdentifier("/CISD/NEMO"));
         creation.setProperty(PLATE_GEOMETRY.getPermId(), "384_WELLS_16X24");
@@ -957,6 +1184,84 @@ public class CreateExperimentTest extends AbstractExperimentTest
         assertEquals(experiment2.getProperties().size(), 2);
     }
 
+    @Test
+    public void testCreateWithMultiValuePropertyOfTypeArrayReal()
+    {
+        // Given
+        String sessionToken = v3api.login(TEST_USER, PASSWORD);
+        PropertyTypePermId propertyType = createAPropertyType(sessionToken, DataType.ARRAY_REAL, true);
+        EntityTypePermId experimentType = createAnExperimentType(sessionToken, true, propertyType, PLATE_GEOMETRY);
+
+        ExperimentCreation creation = new ExperimentCreation();
+        creation.setCode("EXPERIMENT_WITH_MULTI_VALUE_REAL_ARRAY_PROPERTY");
+        creation.setTypeId(experimentType);
+        creation.setProjectId(new ProjectIdentifier("/CISD/NEMO"));
+        creation.setProperty(PLATE_GEOMETRY.getPermId(), "384_WELLS_16X24");
+        creation.setProperty(propertyType.getPermId(), new Double[][]{ new Double[]{1.0, 2.0, 3.0}, new Double[]{4.0, 5.0, 6.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");
+
+        List<Long[]> props = experiment2.getMultiValueIntegerArrayProperty(propertyType.getPermId());
+        assertEquals(props.size(), 2);
+        for(Long[] prop : props) {
+            if(prop[0] > 3L) {
+                assertEqualsNoOrder(prop, new Long[] {4L, 5L, 6L});
+            } else {
+                assertEqualsNoOrder(prop, new Long[] {1L, 2L, 3L});
+            }
+        }
+
+        assertEquals(experiment2.getProperties().size(), 2);
+    }
+
+    @Test
+    public void testCreateWithMultiValuePropertyOfTypeArrayRealDedicatedMethod()
+    {
+        // Given
+        String sessionToken = v3api.login(TEST_USER, PASSWORD);
+        PropertyTypePermId propertyType = createAPropertyType(sessionToken, DataType.ARRAY_INTEGER, true);
+        EntityTypePermId experimentType = createAnExperimentType(sessionToken, true, propertyType, PLATE_GEOMETRY);
+
+        ExperimentCreation creation = new ExperimentCreation();
+        creation.setCode("EXPERIMENT_WITH_MULTI_VALUE_INTEGER_ARRAY_PROPERTY");
+        creation.setTypeId(experimentType);
+        creation.setProjectId(new ProjectIdentifier("/CISD/NEMO"));
+        creation.setProperty(PLATE_GEOMETRY.getPermId(), "384_WELLS_16X24");
+        creation.setMultiValueIntegerArrayProperty(propertyType.getPermId(), List.of( new Long[]{1L, 2L, 3L}, new Long[]{4L, 5L, 6L} ));
+
+        // 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");
+
+        List<Long[]> props = experiment2.getMultiValueIntegerArrayProperty(propertyType.getPermId());
+        assertEquals(props.size(), 2);
+        for(Long[] prop : props) {
+            if(prop[0] > 3L) {
+                assertEqualsNoOrder(prop, new Long[] {4L, 5L, 6L});
+            } else {
+                assertEqualsNoOrder(prop, new Long[] {1L, 2L, 3L});
+            }
+        }
+
+        assertEquals(experiment2.getProperties().size(), 2);
+    }
+
     @Test
     public void testCreateWithPropertyOfTypeArrayString()
     {
@@ -966,11 +1271,11 @@ public class CreateExperimentTest extends AbstractExperimentTest
         EntityTypePermId experimentType = createAnExperimentType(sessionToken, true, propertyType, PLATE_GEOMETRY);
 
         ExperimentCreation creation = new ExperimentCreation();
-        creation.setCode("EXPERIMENT_WITH_SAMPLE_PROPERTY");
+        creation.setCode("EXPERIMENT_WITH_ARRAY_STRING_PROPERTY");
         creation.setTypeId(experimentType);
         creation.setProjectId(new ProjectIdentifier("/CISD/NEMO"));
         creation.setProperty(PLATE_GEOMETRY.getPermId(), "384_WELLS_16X24");
-        creation.setStringArrayProperty(propertyType.getPermId(), new String[]{"a", "b", "c"});
+        creation.setStringArrayProperty(propertyType.getPermId(), new String[]{"a,a", "b", "c"});
 
         // When
         List<ExperimentPermId> experimentIds = v3api.createExperiments(sessionToken, Arrays.asList(creation));
@@ -982,7 +1287,44 @@ public class CreateExperimentTest extends AbstractExperimentTest
         fetchOptions.withSampleProperties();
         Experiment experiment2 = v3api.getExperiments(sessionToken, experimentIds, fetchOptions).get(experimentIds.get(0));
         assertEquals(experiment2.getProperties().get(PLATE_GEOMETRY.getPermId()), "384_WELLS_16X24");
-        assertEquals(experiment2.getStringArrayProperty(propertyType.getPermId()), new String[]{"a", "b", "c"});
+        assertEquals(experiment2.getStringArrayProperty(propertyType.getPermId()), new String[]{"a,a", "b", "c"});
+        assertEquals(experiment2.getProperties().size(), 2);
+    }
+
+    @Test
+    public void testCreateWithMultiValuePropertyOfTypeArrayString()
+    {
+        // Given
+        String sessionToken = v3api.login(TEST_USER, PASSWORD);
+        PropertyTypePermId propertyType = createAPropertyType(sessionToken, DataType.ARRAY_STRING, true);
+        EntityTypePermId experimentType = createAnExperimentType(sessionToken, true, propertyType, PLATE_GEOMETRY);
+
+        ExperimentCreation creation = new ExperimentCreation();
+        creation.setCode("EXPERIMENT_WITH_ARRAY_STRING_PROPERTY");
+        creation.setTypeId(experimentType);
+        creation.setProjectId(new ProjectIdentifier("/CISD/NEMO"));
+        creation.setProperty(PLATE_GEOMETRY.getPermId(), "384_WELLS_16X24");
+        creation.setMultiValueStringArrayProperty(propertyType.getPermId(), List.of(new String[]{"a,a", "b", "c"}, new String[]{"a", "b", "c", "d"}));
+
+        // 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");
+        List<String[]> result = experiment2.getMultiValueStringArrayProperty(propertyType.getPermId());
+        assertEquals(result.size(), 2);
+        for(String[] prop : result) {
+            if(prop.length > 3) {
+                assertEqualsNoOrder(prop, new String[]{"a", "b", "c", "d"});
+            } else {
+                assertEqualsNoOrder(prop, new String[]{"a,a", "b", "c"});
+            }
+        }
         assertEquals(experiment2.getProperties().size(), 2);
     }
 
@@ -995,7 +1337,7 @@ public class CreateExperimentTest extends AbstractExperimentTest
         EntityTypePermId experimentType = createAnExperimentType(sessionToken, true, propertyType, PLATE_GEOMETRY);
 
         ExperimentCreation creation = new ExperimentCreation();
-        creation.setCode("EXPERIMENT_WITH_SAMPLE_PROPERTY");
+        creation.setCode("EXPERIMENT_WITH_ARRAY_TIMESTAMP_PROPERTY");
         creation.setTypeId(experimentType);
         creation.setProjectId(new ProjectIdentifier("/CISD/NEMO"));
         creation.setProperty(PLATE_GEOMETRY.getPermId(), "384_WELLS_16X24");
@@ -1017,6 +1359,47 @@ public class CreateExperimentTest extends AbstractExperimentTest
         assertEquals(experiment2.getProperties().size(), 2);
     }
 
+    @Test
+    public void testCreateWithMultiValuePropertyOfTypeArrayTimestamp()
+    {
+        // Given
+        String sessionToken = v3api.login(TEST_USER, PASSWORD);
+        PropertyTypePermId propertyType = createAPropertyType(sessionToken, DataType.ARRAY_TIMESTAMP, true);
+        EntityTypePermId experimentType = createAnExperimentType(sessionToken, true, propertyType, PLATE_GEOMETRY);
+
+        ExperimentCreation creation = new ExperimentCreation();
+        creation.setCode("EXPERIMENT_WITH_ARRAY_TIMESTAMP_PROPERTY");
+        creation.setTypeId(experimentType);
+        creation.setProjectId(new ProjectIdentifier("/CISD/NEMO"));
+        creation.setProperty(PLATE_GEOMETRY.getPermId(), "384_WELLS_16X24");
+        ZonedDateTime time1 = ZonedDateTime.parse("2023-05-16T11:22:33+02");
+        ZonedDateTime time2 = ZonedDateTime.parse("2023-05-18T11:17:03+02");
+        ZonedDateTime time3 = ZonedDateTime.parse("2023-05-20T11:10:03+02");
+        creation.setMultiValueTimestampArrayProperty(propertyType.getPermId(), List.of(new ZonedDateTime[]{time1, time2}, new ZonedDateTime[]{time3, time2, time1}));
+
+        // 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");
+
+        List<ZonedDateTime[]> properties = experiment2.getMultiValueTimestampArrayProperty(propertyType.getPermId());
+        assertEquals(properties.size(), 2);
+        for(ZonedDateTime[] prop : properties) {
+            if(prop.length == 2) {
+                assertEqualsNoOrder(prop, new ZonedDateTime[]{time1, time2});
+            } else {
+                assertEqualsNoOrder(prop, new ZonedDateTime[]{time1, time2, time3});
+            }
+        }
+        assertEquals(experiment2.getProperties().size(), 2);
+    }
+
     @Test
     public void testCreateWithMultiValuePropertyVocabulary()
     {
@@ -1039,7 +1422,7 @@ public class CreateExperimentTest extends AbstractExperimentTest
         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"});
+        creation.setProperty(propertyType.getPermId(), new String[] {"DOG", "HUMAN"});
 
         // When
         List<ExperimentPermId> experimentIds = v3api.createExperiments(sessionToken, Arrays.asList(creation));
@@ -1051,12 +1434,54 @@ public class CreateExperimentTest extends AbstractExperimentTest
         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());
+        String[] vocabProperties = Arrays.stream((Serializable[])experiment2.getProperty(propertyType.getPermId()))
+                                            .map(Serializable::toString)
+                                            .toArray(String[]::new);
         Arrays.sort(vocabProperties);
         assertEquals(vocabProperties, new String[] {"DOG", "HUMAN"});
         assertEquals(experiment2.getProperties().size(), 2);
     }
 
+    @Test
+    public void testCreateWithMultiValuePropertyVocabularyDedicatedMethod()
+    {
+        // 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_MULTI_VOCAB_PROPERTY-" + System.currentTimeMillis());
+        creation.setTypeId(experimentType);
+        creation.setProjectId(new ProjectIdentifier("/CISD/NEMO"));
+        creation.setProperty(PLATE_GEOMETRY.getPermId(), "384_WELLS_16X24");
+        creation.setMultiValueControlledVocabularyProperty(propertyType.getPermId(), List.of("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");
+        List<String> vocabProperties = experiment2.getMultiValueControlledVocabularyProperty(propertyType.getPermId());
+        Collections.sort(vocabProperties);
+        assertEquals(vocabProperties, List.of("DOG", "HUMAN"));
+        assertEquals(experiment2.getProperties().size(), 2);
+    }
+
     @Test
     public void testCreateWithMultiValuePropertySample()
     {
@@ -1124,6 +1549,78 @@ public class CreateExperimentTest extends AbstractExperimentTest
         assertEquals(experiment2.getProperties().size(), 2);
     }
 
+    @Test
+    public void testCreateWithMultiValuePropertySampleDedicatedMethod()
+    {
+        // 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-" + System.currentTimeMillis());
+        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-" + System.currentTimeMillis());
+        creation.setTypeId(experimentType);
+        creation.setProjectId(new ProjectIdentifier("/CISD/NEMO"));
+        creation.setProperty(PLATE_GEOMETRY.getPermId(), "384_WELLS_16X24");
+        creation.setMultiValueSampleProperty(propertyType.getPermId(), List.of(new SamplePermId("200811050919915-8"), 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");
+
+        List<SamplePermId> properties = experiment2.getMultiValueSampleProperty(propertyType.getPermId());
+        assertEquals(properties.size(), 2);
+        assertEqualsNoOrder(properties.stream().map(SamplePermId::getPermId).toArray(String[]::new), new String[] {"200811050919915-8", sample2.getPermId().getPermId()});
+
+        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()
     {
@@ -1167,7 +1664,7 @@ public class CreateExperimentTest extends AbstractExperimentTest
         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)});
+        creation.setMultiValueSampleProperty(propertyType.getPermId(), List.of(new SamplePermId("/CISD/CL1"), sampleIds.get(0)));
 
         // When
         List<ExperimentPermId> experimentIds = v3api.createExperiments(sessionToken, Arrays.asList(creation));
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 e5632a606feab89a3156178b8ed441b5986f9d94..a271155d6e110850df89afeb6705380cfb9eb1f9 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
@@ -16,6 +16,7 @@
 package ch.ethz.sis.openbis.systemtest.asapi.v3;
 
 import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertEqualsNoOrder;
 import static org.testng.Assert.assertNull;
 
 import java.io.Serializable;
@@ -1523,6 +1524,67 @@ public class CreateSampleTest extends AbstractSampleTest
         assertEquals(sample2.getProperties().get(propertyType.getPermId()), "2020-11-05 19:23:00 +0100");
         assertEquals(sample2.getProperties().size(), 2);
     }
+    
+    @Test
+    public void testCreateWithPropertyOfTypeTimestampDedicatedMethod()
+    {
+        // Given
+        String sessionToken = v3api.login(TEST_USER, PASSWORD);
+        PropertyTypePermId propertyType = createAPropertyType(sessionToken, DataType.TIMESTAMP);
+        EntityTypePermId sampleType = createASampleType(sessionToken, true, propertyType, 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");
+        ZonedDateTime time1 = ZonedDateTime.parse("2023-05-16T11:22:33+02");
+        sample.setTimestampProperty(propertyType.getPermId(), time1);
+
+        // 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");
+        assertEquals(sample2.getTimestampProperty(propertyType.getPermId()), ZonedDateTime.parse("2023-05-16T11:22:33+02:00"));
+        assertEquals(sample2.getProperties().size(), 2);
+    }
+
+    @Test
+    public void testCreateWithMultiValuePropertyOfTypeTimestamp()
+    {
+        // Given
+        String sessionToken = v3api.login(TEST_USER, PASSWORD);
+        PropertyTypePermId propertyType = createAPropertyType(sessionToken, DataType.TIMESTAMP, true);
+        EntityTypePermId sampleType = createASampleType(sessionToken, true, propertyType, 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");
+        ZonedDateTime time1 = ZonedDateTime.parse("2023-05-16T11:22:33+02");
+        ZonedDateTime time2 = ZonedDateTime.parse("2023-05-18T11:17:03+02");
+        sample.setMultiValueTimestampProperty(propertyType.getPermId(), List.of(time1, time2));
+
+        // 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");
+        assertEqualsNoOrder(sample2.getMultiValueTimestampProperty(propertyType.getPermId()).toArray(ZonedDateTime[]::new), new ZonedDateTime[] {time1, time2});
+        assertEquals(sample2.getProperties().size(), 2);
+    }
 
     @Test
     public void testCreateWithPropertyOfTypeJson()
@@ -1552,6 +1614,100 @@ public class CreateSampleTest extends AbstractSampleTest
         assertEquals(sample2.getJsonProperty(propertyType.getPermId()), "{\"key\": \"value\", \"array\": [1, 2, 3]}");
         assertEquals(sample2.getProperties().size(), 2);
     }
+    
+    @Test
+    public void testCreateWithMultiValuePropertyOfTypeJson()
+    {
+        // Given
+        String sessionToken = v3api.login(TEST_USER, PASSWORD);
+        PropertyTypePermId propertyType = createAPropertyType(sessionToken, DataType.JSON, true);
+        EntityTypePermId sampleType = createASampleType(sessionToken, true, propertyType, 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.setMultiValueJsonProperty(propertyType.getPermId(), List.of("{\"key\": \"value\", \"array\":[1,2,3]}", "{\"key\": \"value2\", \"array\":[]}"));
+
+        // 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");
+        List<String> properties = sample2.getMultiValueJsonProperty(propertyType.getPermId());
+        assertEquals(properties.size(), 2);
+        assertEqualsNoOrder(properties.toArray(String[]::new), new String[] {"{\"key\": \"value\", \"array\": [1, 2, 3]}", "{\"key\": \"value2\", \"array\": []}"});
+        assertEquals(sample2.getProperties().size(), 2);
+    }
+
+    @Test
+    public void testCreateWithMultiValuePropertyOfTypeInteger()
+    {
+        // Given
+        String sessionToken = v3api.login(TEST_USER, PASSWORD);
+        PropertyTypePermId propertyType = createAPropertyType(sessionToken, DataType.INTEGER, true);
+        EntityTypePermId sampleType = createASampleType(sessionToken, true, propertyType, 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(propertyType.getPermId(), new Long[] {1L, 1L, 3L});
+
+        // 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");
+        assertEqualsNoOrder((Serializable[]) sample2.getProperties().get(propertyType.getPermId()), new Serializable[] { "1", "1", "3" });
+        assertEqualsNoOrder(sample2.getMultiValueIntegerProperty(propertyType.getPermId()).toArray(Long[]::new), new Long[] { 1L, 1L, 3L });
+        assertEquals(sample2.getProperties().size(), 2);
+    }
+
+    @Test
+    public void testCreateWithMultiValuePropertyOfTypeIntegerDedicatedMethod()
+    {
+        // Given
+        String sessionToken = v3api.login(TEST_USER, PASSWORD);
+        PropertyTypePermId propertyType = createAPropertyType(sessionToken, DataType.INTEGER, true);
+        EntityTypePermId sampleType = createASampleType(sessionToken, true, propertyType, 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.setMultiValueIntegerProperty(propertyType.getPermId(), List.of(1L, 2L, 3L));
+
+        // 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");
+        assertEqualsNoOrder((Serializable[]) sample2.getProperties().get(propertyType.getPermId()), new Serializable[] { "1", "2", "3" });
+        assertEqualsNoOrder(sample2.getMultiValueIntegerProperty(propertyType.getPermId()).toArray(Long[]::new), new Long[] { 1L, 2L, 3L });
+        assertEquals(sample2.getProperties().size(), 2);
+    }
 
     @Test
     public void testCreateWithPropertyOfTypeArrayInteger()
@@ -1581,6 +1737,84 @@ public class CreateSampleTest extends AbstractSampleTest
         assertEquals(sample2.getIntegerArrayProperty(propertyType.getPermId()), new Long[]{1L, 2L, 3L});
         assertEquals(sample2.getProperties().size(), 2);
     }
+    
+     @Test
+    public void testCreateWithMultiValuePropertyOfTypeArrayInteger()
+    {
+        // Given
+        String sessionToken = v3api.login(TEST_USER, PASSWORD);
+        PropertyTypePermId propertyType = createAPropertyType(sessionToken, DataType.ARRAY_INTEGER, true);
+        EntityTypePermId sampleType = createASampleType(sessionToken, true, propertyType, 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(propertyType.getPermId(), new Long[][]{ new Long[]{1L, 2L, 3L}, new Long[]{4L, 5L, 6L} });
+
+        // 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");
+
+        List<Long[]> props = sample2.getMultiValueIntegerArrayProperty(propertyType.getPermId());
+        assertEquals(props.size(), 2);
+        for(Long[] prop : props) {
+            if(prop[0] > 3L) {
+                assertEqualsNoOrder(prop, new Long[] {4L, 5L, 6L});
+            } else {
+                assertEqualsNoOrder(prop, new Long[] {1L, 2L, 3L});
+            }
+        }
+
+        assertEquals(sample2.getProperties().size(), 2);
+    }
+
+    @Test
+    public void testCreateWithMultiValuePropertyOfTypeArrayIntegerDedicatedMethod()
+    {
+        // Given
+        String sessionToken = v3api.login(TEST_USER, PASSWORD);
+        PropertyTypePermId propertyType = createAPropertyType(sessionToken, DataType.ARRAY_INTEGER, true);
+        EntityTypePermId sampleType = createASampleType(sessionToken, true, propertyType, 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.setMultiValueIntegerArrayProperty(propertyType.getPermId(), List.of( new Long[]{1L, 2L, 3L}, new Long[]{4L, 5L, 6L} ));
+
+        // 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");
+
+        List<Long[]> props = sample2.getMultiValueIntegerArrayProperty(propertyType.getPermId());
+        assertEquals(props.size(), 2);
+        for(Long[] prop : props) {
+            if(prop[0] > 3L) {
+                assertEqualsNoOrder(prop, new Long[] {4L, 5L, 6L});
+            } else {
+                assertEqualsNoOrder(prop, new Long[] {1L, 2L, 3L});
+            }
+        }
+
+        assertEquals(sample2.getProperties().size(), 2);
+    }
 
     @Test
     public void testCreateWithPropertyOfTypeArrayReal()
@@ -1610,6 +1844,84 @@ public class CreateSampleTest extends AbstractSampleTest
         assertEquals(sample2.getRealArrayProperty(propertyType.getPermId()), new Double[]{1.0, 2.0, 3.0});
         assertEquals(sample2.getProperties().size(), 2);
     }
+    
+    @Test
+    public void testCreateWithMultiValuePropertyOfTypeArrayReal()
+    {
+        // Given
+        String sessionToken = v3api.login(TEST_USER, PASSWORD);
+        PropertyTypePermId propertyType = createAPropertyType(sessionToken, DataType.ARRAY_REAL, true);
+        EntityTypePermId sampleType = createASampleType(sessionToken, true, propertyType, 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(propertyType.getPermId(), new Double[][]{ new Double[]{1.0, 2.0, 3.0}, new Double[]{4.0, 5.0, 6.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");
+
+        List<Long[]> props = sample2.getMultiValueIntegerArrayProperty(propertyType.getPermId());
+        assertEquals(props.size(), 2);
+        for(Long[] prop : props) {
+            if(prop[0] > 3L) {
+                assertEqualsNoOrder(prop, new Long[] {4L, 5L, 6L});
+            } else {
+                assertEqualsNoOrder(prop, new Long[] {1L, 2L, 3L});
+            }
+        }
+
+        assertEquals(sample2.getProperties().size(), 2);
+    }
+
+    @Test
+    public void testCreateWithMultiValuePropertyOfTypeArrayRealDedicatedMethod()
+    {
+        // Given
+        String sessionToken = v3api.login(TEST_USER, PASSWORD);
+        PropertyTypePermId propertyType = createAPropertyType(sessionToken, DataType.ARRAY_INTEGER, true);
+        EntityTypePermId sampleType = createASampleType(sessionToken, true, propertyType, 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.setMultiValueIntegerArrayProperty(propertyType.getPermId(), List.of( new Long[]{1L, 2L, 3L}, new Long[]{4L, 5L, 6L} ));
+
+        // 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");
+
+        List<Long[]> props = sample2.getMultiValueIntegerArrayProperty(propertyType.getPermId());
+        assertEquals(props.size(), 2);
+        for(Long[] prop : props) {
+            if(prop[0] > 3L) {
+                assertEqualsNoOrder(prop, new Long[] {4L, 5L, 6L});
+            } else {
+                assertEqualsNoOrder(prop, new Long[] {1L, 2L, 3L});
+            }
+        }
+
+        assertEquals(sample2.getProperties().size(), 2);
+    }
 
     @Test
     public void testCreateWithPropertyOfTypeArrayString()
@@ -1639,6 +1951,43 @@ public class CreateSampleTest extends AbstractSampleTest
         assertEquals(sample2.getStringArrayProperty(propertyType.getPermId()), new String[]{"a", "b", "c"});
         assertEquals(sample2.getProperties().size(), 2);
     }
+    
+    @Test
+    public void testCreateWithMultiValuePropertyOfTypeArrayString()
+    {
+        // Given
+        String sessionToken = v3api.login(TEST_USER, PASSWORD);
+        PropertyTypePermId propertyType = createAPropertyType(sessionToken, DataType.ARRAY_STRING, true);
+        EntityTypePermId sampleType = createASampleType(sessionToken, true, propertyType, 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.setMultiValueStringArrayProperty(propertyType.getPermId(), List.of(new String[]{"a,a", "b", "c"}, new String[]{"a", "b", "c", "d"}));
+
+        // 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");
+        List<String[]> result = sample2.getMultiValueStringArrayProperty(propertyType.getPermId());
+        assertEquals(result.size(), 2);
+        for(String[] prop : result) {
+            if(prop.length > 3) {
+                assertEqualsNoOrder(prop, new String[]{"a", "b", "c", "d"});
+            } else {
+                assertEqualsNoOrder(prop, new String[]{"a,a", "b", "c"});
+            }
+        }
+        assertEquals(sample2.getProperties().size(), 2);
+    }
 
     @Test
     public void testCreateWithPropertyOfTypeArrayTimestamp()
@@ -1670,6 +2019,47 @@ public class CreateSampleTest extends AbstractSampleTest
         assertEquals(sample2.getTimestampArrayProperty(propertyType.getPermId()), new ZonedDateTime[]{time1, time2});
         assertEquals(sample2.getProperties().size(), 2);
     }
+    
+    @Test
+    public void testCreateWithMultiValuePropertyOfTypeArrayTimestamp()
+    {
+        // Given
+        String sessionToken = v3api.login(TEST_USER, PASSWORD);
+        PropertyTypePermId propertyType = createAPropertyType(sessionToken, DataType.ARRAY_TIMESTAMP, true);
+        EntityTypePermId sampleType = createASampleType(sessionToken, true, propertyType, 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");
+        ZonedDateTime time1 = ZonedDateTime.parse("2023-05-16T11:22:33+02");
+        ZonedDateTime time2 = ZonedDateTime.parse("2023-05-18T11:17:03+02");
+        ZonedDateTime time3 = ZonedDateTime.parse("2023-05-20T11:10:03+02");
+        sample.setMultiValueTimestampArrayProperty(propertyType.getPermId(), List.of(new ZonedDateTime[]{time1, time2}, new ZonedDateTime[]{time3, time2, time1}));
+
+        // 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");
+
+        List<ZonedDateTime[]> properties = sample2.getMultiValueTimestampArrayProperty(propertyType.getPermId());
+        assertEquals(properties.size(), 2);
+        for(ZonedDateTime[] prop : properties) {
+            if(prop.length == 2) {
+                assertEqualsNoOrder(prop, new ZonedDateTime[]{time1, time2});
+            } else {
+                assertEqualsNoOrder(prop, new ZonedDateTime[]{time1, time2, time3});
+            }
+        }
+        assertEquals(sample2.getProperties().size(), 2);
+    }
 
     @Test
     public void testCreateWithMultiValuePropertyVocabulary()
@@ -1693,7 +2083,7 @@ public class CreateSampleTest extends AbstractSampleTest
         sample.setTypeId(sampleType);
         sample.setSpaceId(new SpacePermId("CISD"));
         sample.setProperty(PLATE_GEOMETRY.getPermId(), "384_WELLS_16X24");
-        sample.setControlledVocabularyProperty(propertyType.getPermId(), new String[] {"DOG", "HUMAN"});
+        sample.setMultiValueControlledVocabularyProperty(propertyType.getPermId(), List.of("DOG", "HUMAN"));
 
         // When
         List<SamplePermId> sampleIds = v3api.createSamples(sessionToken, Arrays.asList(sample));
@@ -1705,9 +2095,49 @@ public class CreateSampleTest extends AbstractSampleTest
         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"});
+        List<String> vocabProperties = sample2.getMultiValueControlledVocabularyProperty(propertyType.getPermId());
+        Collections.sort(vocabProperties);
+        assertEquals(vocabProperties, List.of("DOG", "HUMAN"));
+        assertEquals(sample2.getProperties().size(), 2);
+    }
+    
+    @Test
+    public void testCreateWithMultiValuePropertyVocabularyDedicatedMethod()
+    {
+        // 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-" + System.currentTimeMillis());
+        sample.setTypeId(sampleType);
+        sample.setSpaceId(new SpacePermId("CISD"));
+        sample.setProperty(PLATE_GEOMETRY.getPermId(), "384_WELLS_16X24");
+        sample.setMultiValueControlledVocabularyProperty(propertyType.getPermId(), List.of("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");
+        List<String> vocabProperties = sample2.getMultiValueControlledVocabularyProperty(propertyType.getPermId());
+        Collections.sort(vocabProperties);
+        assertEquals(vocabProperties, List.of("DOG", "HUMAN"));
         assertEquals(sample2.getProperties().size(), 2);
     }
 
@@ -1772,6 +2202,69 @@ public class CreateSampleTest extends AbstractSampleTest
         assertEquals(sampleProps, new Serializable[]{"200811050919915-8", testSampleIds.get(0).getPermId()});
         assertEquals(sample2.getProperties().size(), 2);
     }
+    
+    @Test
+    public void testCreateWithMultiValuePropertySampleDedicatedMethod()
+    {
+        // 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("SAMPLE_WITH_SOME_SAMPLE_PROPERTY-" + System.currentTimeMillis());
+        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-" + System.currentTimeMillis());
+        sampleCreation.setTypeId(sampleType);
+        sampleCreation.setSpaceId(new SpacePermId("CISD"));
+        sampleCreation.setProperty(PLATE_GEOMETRY.getPermId(), "384_WELLS_16X24");
+        sampleCreation.setMultiValueSampleProperty(propertyType.getPermId(), List.of(new SamplePermId("200811050919915-8"), testSampleIds.get(0)));
+
+        // When
+        List<SamplePermId> sampleIds = v3api.createSamples(sessionToken, Arrays.asList(sampleCreation));
+
+        // 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", testSampleIds.get(0).getPermId()});
+
+        sampleProps = (Serializable[]) sample2.getProperties().get(propertyType.getPermId());
+        Arrays.sort(sampleProps);
+        assertEquals(sampleProps, new Serializable[]{"200811050919915-8", testSampleIds.get(0).getPermId()});
+        assertEquals(sample2.getProperties().size(), 2);
+    }
 
     @Test
     public void testCreateWithMultiValuePropertySample2() {
@@ -1809,7 +2302,7 @@ public class CreateSampleTest extends AbstractSampleTest
         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)});
+        sampleCreation.setMultiValueSampleProperty(propertyType.getPermId(), List.of(new SamplePermId("/CISD/CL1"), testSampleIds.get(0)));
 
         // When
         List<SamplePermId> sampleIds = v3api.createSamples(sessionToken, Arrays.asList(sampleCreation));
diff --git a/server-application-server/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/SearchMaterialTest.java b/server-application-server/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/SearchMaterialTest.java
index 3e923a3f85135480e25548b4fb52061dd81497b2..0fd28c38d5a436b0a0d6e3fb5465b044ef8090e5 100644
--- a/server-application-server/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/SearchMaterialTest.java
+++ b/server-application-server/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/SearchMaterialTest.java
@@ -1214,7 +1214,7 @@ public class SearchMaterialTest extends AbstractTest
                 v3api.createVocabularies(sessionToken, Collections.singletonList(vocabularyCreation)).get(0);
 
         final PropertyTypePermId propertyTypeId = createAPropertyType(sessionToken, DataType.CONTROLLEDVOCABULARY,
-                vocabularyPermId);
+                vocabularyPermId, false);
         final MaterialPermId entityPermId = createMaterial(sessionToken, propertyTypeId, value);
         final MaterialSearchCriteria searchCriteria = new MaterialSearchCriteria();
         new AbstractSearchPropertyTest.StringQueryInjector(searchCriteria, propertyTypeId).buildCriteria(queryString);