diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/ApplicationServerApi.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/ApplicationServerApi.java
index 3c9ae2e608d48a841dea79a4d9e40a8116306e29..4f649fbe2d6be108d1b18ed9242dc35e54092619 100644
--- a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/ApplicationServerApi.java
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/ApplicationServerApi.java
@@ -42,6 +42,7 @@ import ch.ethz.sis.openbis.generic.server.api.v3.executor.experiment.IUpdateExpe
 import ch.ethz.sis.openbis.generic.server.api.v3.executor.material.ICreateMaterialExecutor;
 import ch.ethz.sis.openbis.generic.server.api.v3.executor.material.IDeleteMaterialExecutor;
 import ch.ethz.sis.openbis.generic.server.api.v3.executor.material.IMapMaterialByIdExecutor;
+import ch.ethz.sis.openbis.generic.server.api.v3.executor.material.IUpdateMaterialExecutor;
 import ch.ethz.sis.openbis.generic.server.api.v3.executor.project.ICreateProjectExecutor;
 import ch.ethz.sis.openbis.generic.server.api.v3.executor.project.IMapProjectByIdExecutor;
 import ch.ethz.sis.openbis.generic.server.api.v3.executor.sample.ICreateSampleExecutor;
@@ -79,6 +80,7 @@ import ch.ethz.sis.openbis.generic.shared.api.v3.dto.entity.experiment.Experimen
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.entity.experiment.ExperimentUpdate;
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.entity.material.Material;
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.entity.material.MaterialCreation;
+import ch.ethz.sis.openbis.generic.shared.api.v3.dto.entity.material.MaterialUpdate;
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.entity.project.Project;
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.entity.project.ProjectCreation;
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.entity.sample.Sample;
@@ -167,6 +169,9 @@ public class ApplicationServerApi extends AbstractServer<IApplicationServerApi>
     @Autowired
     private IUpdateSampleExecutor updateSampleExecutor;
 
+    @Autowired
+    private IUpdateMaterialExecutor updateMaterialExecutor;
+
     @Autowired
     private IUpdateDataSetExecutor updateDataSetExecutor;
 
@@ -435,6 +440,26 @@ public class ApplicationServerApi extends AbstractServer<IApplicationServerApi>
         }
     }
 
+    @Override
+    @Transactional
+    @RolesAllowed(
+    { RoleWithHierarchy.INSTANCE_ADMIN, RoleWithHierarchy.INSTANCE_ETL_SERVER })
+    @Capability("UPDATE_MATERIAL")
+    @DatabaseUpdateModification(value = ObjectKind.MATERIAL)
+    public void updateMaterials(String sessionToken, List<MaterialUpdate> updates)
+    {
+        Session session = getSession(sessionToken);
+        OperationContext context = new OperationContext(session);
+
+        try
+        {
+            updateMaterialExecutor.update(context, updates);
+        } catch (Throwable t)
+        {
+            throw ExceptionUtils.create(context, t);
+        }
+    }
+
     @Override
     @Transactional
     @RolesAllowed({ RoleWithHierarchy.SPACE_POWER_USER, RoleWithHierarchy.SPACE_ETL_SERVER })
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/ApplicationServerApiLogger.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/ApplicationServerApiLogger.java
index 2ab2f86d33bfc530c054cf15ffae59c8525f6c79..4b848d0a08839e7ac36f0f90642a18100a35d8fd 100644
--- a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/ApplicationServerApiLogger.java
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/ApplicationServerApiLogger.java
@@ -33,6 +33,7 @@ import ch.ethz.sis.openbis.generic.shared.api.v3.dto.entity.experiment.Experimen
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.entity.experiment.ExperimentUpdate;
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.entity.material.Material;
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.entity.material.MaterialCreation;
+import ch.ethz.sis.openbis.generic.shared.api.v3.dto.entity.material.MaterialUpdate;
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.entity.project.Project;
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.entity.project.ProjectCreation;
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.entity.sample.Sample;
@@ -173,6 +174,12 @@ public class ApplicationServerApiLogger extends AbstractServerLogger implements
         logAccess(sessionToken, "update-data-sets", "DATA_SET_UPDATES(%s)", dataSetUpdates);
     }
 
+    @Override
+    public void updateMaterials(String sessionToken, List<MaterialUpdate> materialUpdates)
+    {
+        logAccess(sessionToken, "update-materials", "MATERIAL_UPDATES(%s)", materialUpdates);
+    }
+
     @Override
     public Map<ISpaceId, Space> mapSpaces(String sessionToken, List<? extends ISpaceId> spaceIds, SpaceFetchOptions fetchOptions)
     {
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/material/IUpdateMaterialExecutor.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/material/IUpdateMaterialExecutor.java
new file mode 100644
index 0000000000000000000000000000000000000000..97a94a521de7d1e43f298b3ef4506133279aa873
--- /dev/null
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/material/IUpdateMaterialExecutor.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2014 ETH Zuerich, Scientific IT Services
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.ethz.sis.openbis.generic.server.api.v3.executor.material;
+
+import ch.ethz.sis.openbis.generic.server.api.v3.executor.entity.IUpdateEntityExecutor;
+import ch.ethz.sis.openbis.generic.shared.api.v3.dto.entity.material.MaterialUpdate;
+
+/**
+ * @author Jakub Straszewski
+ */
+public interface IUpdateMaterialExecutor extends IUpdateEntityExecutor<MaterialUpdate>
+{
+
+}
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/material/UpdateMaterialExecutor.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/material/UpdateMaterialExecutor.java
new file mode 100644
index 0000000000000000000000000000000000000000..d14bcd1b0261d4687d0296ab6cb558e1a68516c6
--- /dev/null
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/material/UpdateMaterialExecutor.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2014 ETH Zuerich, CISD
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.ethz.sis.openbis.generic.server.api.v3.executor.material;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.DataAccessException;
+import org.springframework.stereotype.Component;
+
+import ch.ethz.sis.openbis.generic.server.api.v3.executor.IOperationContext;
+import ch.ethz.sis.openbis.generic.server.api.v3.executor.entity.AbstractUpdateEntityExecutor;
+import ch.ethz.sis.openbis.generic.server.api.v3.executor.property.IUpdateEntityPropertyExecutor;
+import ch.ethz.sis.openbis.generic.server.api.v3.executor.tag.IUpdateTagForEntityExecutor;
+import ch.ethz.sis.openbis.generic.shared.api.v3.dto.entity.material.MaterialUpdate;
+import ch.ethz.sis.openbis.generic.shared.api.v3.dto.id.material.IMaterialId;
+import ch.ethz.sis.openbis.generic.shared.api.v3.exceptions.UnauthorizedObjectAccessException;
+import ch.systemsx.cisd.common.exceptions.UserFailureException;
+import ch.systemsx.cisd.openbis.generic.server.authorization.validator.SampleByIdentiferValidator;
+import ch.systemsx.cisd.openbis.generic.server.business.bo.DataAccessExceptionTranslator;
+import ch.systemsx.cisd.openbis.generic.server.dataaccess.IDAOFactory;
+import ch.systemsx.cisd.openbis.generic.shared.dto.IEntityPropertiesHolder;
+import ch.systemsx.cisd.openbis.generic.shared.dto.MaterialPE;
+import ch.systemsx.cisd.openbis.generic.shared.dto.properties.EntityKind;
+
+/**
+ * @author Jakub Straszewski
+ */
+@Component
+public class UpdateMaterialExecutor extends AbstractUpdateEntityExecutor<MaterialUpdate, MaterialPE, IMaterialId> implements IUpdateMaterialExecutor
+{
+
+    @Autowired
+    private IDAOFactory daoFactory;
+
+    @Autowired
+    private IMapMaterialByIdExecutor mapMaterialByIdExecutor;
+
+    @Autowired
+    private IUpdateEntityPropertyExecutor updateEntityPropertyExecutor;
+
+    @Autowired
+    private IUpdateTagForEntityExecutor updateTagForEntityExecutor;
+
+    @Autowired
+    private IVerifyMaterialExecutor verifyMaterialExecutor;
+
+    @Override
+    protected IMaterialId getId(MaterialUpdate update)
+    {
+        return update.getMaterialId();
+    }
+
+    @Override
+    protected void checkData(IOperationContext context, MaterialUpdate update)
+    {
+        if (update.getMaterialId() == null)
+        {
+            throw new UserFailureException("Sample id cannot be null.");
+        }
+    }
+
+    @Override
+    protected void checkAccess(IOperationContext context, IMaterialId id, MaterialPE entity)
+    {
+        if (false == new SampleByIdentiferValidator().doValidation(context.getSession().tryGetPerson(), entity))
+        {
+            throw new UnauthorizedObjectAccessException(id);
+        }
+    }
+
+    @Override
+    protected void checkBusinessRules(IOperationContext context, Collection<MaterialPE> entities)
+    {
+        verifyMaterialExecutor.verify(context, entities);
+    }
+
+    @Override
+    protected void updateBatch(IOperationContext context, Map<MaterialUpdate, MaterialPE> entitiesMap)
+    {
+        Map<IEntityPropertiesHolder, Map<String, String>> propertyMap = new HashMap<IEntityPropertiesHolder, Map<String, String>>();
+        for (Map.Entry<MaterialUpdate, MaterialPE> entry : entitiesMap.entrySet())
+        {
+            MaterialUpdate update = entry.getKey();
+            MaterialPE entity = entry.getValue();
+            updateTagForEntityExecutor.update(context, entity, update.getTagIds());
+            propertyMap.put(entity, update.getProperties());
+        }
+
+        updateEntityPropertyExecutor.update(context, propertyMap);
+    }
+
+    @Override
+    protected void updateAll(IOperationContext context, Map<MaterialUpdate, MaterialPE> entitiesMap)
+    {
+    }
+
+    @Override
+    protected Map<IMaterialId, MaterialPE> map(IOperationContext context, Collection<IMaterialId> ids)
+    {
+        return mapMaterialByIdExecutor.map(context, ids);
+    }
+
+    @Override
+    protected List<MaterialPE> list(IOperationContext context, Collection<Long> ids)
+    {
+        return daoFactory.getMaterialDAO().listMaterialsById(ids);
+    }
+
+    @Override
+    protected void save(IOperationContext context, List<MaterialPE> entities, boolean clearCache)
+    {
+        daoFactory.getMaterialDAO().createOrUpdateMaterials(entities);
+    }
+
+    @Override
+    protected void handleException(DataAccessException e)
+    {
+        DataAccessExceptionTranslator.throwException(e, EntityKind.SAMPLE.getLabel(), EntityKind.SAMPLE);
+    }
+
+}
diff --git a/openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/api/v3/CreateMaterialTest.java b/openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/api/v3/CreateMaterialTest.java
index 2781aafbc44aa9c65320cd18d7b5bb1ee1b02099..f46266234e219e2acb74d39747da18864fa577ee 100644
--- a/openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/api/v3/CreateMaterialTest.java
+++ b/openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/api/v3/CreateMaterialTest.java
@@ -57,15 +57,70 @@ public class CreateMaterialTest extends AbstractSampleTest
         AssertionUtil.assertCollectionSize(map.values(), 2);
 
         Material material = map.get(new MaterialPermId("1982", "GENE"));
-
         assertEquals(material.getCode(), "1982");
         assertEquals(material.getPermId().getTypeCode(), "GENE");
+
+        material = map.get(new MaterialPermId("1984", "GENE"));
+        assertEquals(material.getCode(), "1984");
+        assertEquals(material.getPermId().getTypeCode(), "GENE");
+    }
+
+    // @Test broken
+    public void testCreateTwoMaterialsWithMaterialLinks()
+    {
+        String sessionToken = v3api.login(TEST_USER, PASSWORD);
+
+        MaterialPermId m1id = new MaterialPermId("FIRST", "SELF_REF");
+        MaterialCreation m1 = materialCreation(m1id);
+        HashMap<String, String> properties1 = new HashMap<String, String>();
+        properties1.put("ANY_MATERIAL", "SECOND (SELF_REF)");
+        properties1.put("DESCRIPTION", "mandatory material decsription");
+        m1.setProperties(properties1);
+
+        MaterialPermId m2id = new MaterialPermId("SECOND", "SELF_REF");
+        MaterialCreation m2 = materialCreation(m2id);
+        HashMap<String, String> properties2 = new HashMap<String, String>();
+        properties2.put("ANY_MATERIAL", "FIRST (SELF_REF)");
+        properties2.put("DESCRIPTION", "mandatory material decsription");
+        m2.setProperties(properties2);
+
+        List<MaterialPermId> materialIds = v3api.createMaterials(sessionToken, Arrays.asList(m1, m2));
+
+        AssertionUtil.assertCollectionContainsOnly(materialIds, m1id, m2id);
+
+        // circular materialProperties
+        MaterialFetchOptions fetchOptions = new MaterialFetchOptions();
+        fetchOptions.withMaterialPropertiesUsing(fetchOptions);
+
+        Map<IMaterialId, Material> map = v3api.mapMaterials(sessionToken, Arrays.asList(m1id), fetchOptions);
+
+        AssertionUtil.assertCollectionSize(map.values(), 1);
+
+        Material resultm1 = map.get(m1id);
+
+        assertEquals(resultm1.getPermId(), m1id);
+        Material resultm2 = resultm1.getMaterialProperties().get("ANY_MATERIAL");
+        assertEquals(resultm2.getPermId(), m2id);
+        Material resultm3 = resultm2.getMaterialProperties().get("ANY_MATERIAL");
+        assertEquals(resultm1, resultm3);
     }
 
     // all potential error scenarios
 
     // create mateiral with mateiral properties
 
+    private MaterialCreation materialCreation(MaterialPermId permId)
+    {
+        String code = permId.getCode();
+        String type = permId.getTypeCode();
+        MaterialCreation materialCreation = new MaterialCreation();
+        materialCreation.setCode(code);
+        materialCreation.setTypeId(new EntityTypePermId(type));
+        materialCreation.setCreationId(new CreationId("creation " + code));
+        materialCreation.setDescription("Material with code " + code);
+        return materialCreation;
+    }
+
     private MaterialCreation geneCreation(String code)
     {
         MaterialCreation materialCreation = new MaterialCreation();
diff --git a/openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/api/v3/UpdateMaterialTest.java b/openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/api/v3/UpdateMaterialTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..c480dce4f468cad0fd6d9747b9ee7727e749b42c
--- /dev/null
+++ b/openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/api/v3/UpdateMaterialTest.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2014 ETH Zuerich, Scientific IT Services
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.ethz.sis.openbis.systemtest.api.v3;
+
+import static org.testng.Assert.assertEquals;
+
+import java.util.Arrays;
+import java.util.Map;
+
+import org.testng.annotations.Test;
+
+import ch.ethz.sis.openbis.generic.shared.api.v3.dto.entity.material.Material;
+import ch.ethz.sis.openbis.generic.shared.api.v3.dto.entity.material.MaterialUpdate;
+import ch.ethz.sis.openbis.generic.shared.api.v3.dto.fetchoptions.material.MaterialFetchOptions;
+import ch.ethz.sis.openbis.generic.shared.api.v3.dto.id.material.IMaterialId;
+import ch.ethz.sis.openbis.generic.shared.api.v3.dto.id.material.MaterialPermId;
+
+/**
+ * @author pkupczyk
+ */
+public class UpdateMaterialTest extends AbstractSampleTest
+{
+
+    @Test
+    public void testSimpleMaterialUpdate()
+    {
+        String sessionToken = v3api.login(TEST_USER, PASSWORD);
+
+        MaterialUpdate update = new MaterialUpdate();
+        update.setMaterialId(new MaterialPermId("1", "GENE"));
+        String description = "This is amodified gene description";
+        update.setProperty("DESCRIPTION", description);
+
+        v3api.updateMaterials(sessionToken, Arrays.asList(update));
+
+        MaterialFetchOptions fetchOptions = new MaterialFetchOptions();
+        fetchOptions.withProperties();
+
+        Map<IMaterialId, Material> map = v3api.mapMaterials(sessionToken, Arrays.asList(update.getMaterialId()), fetchOptions);
+
+        Material material = map.get(update.getMaterialId());
+        assertEquals(material.getProperties().get("DESCRIPTION"), description);
+    }
+
+}
diff --git a/openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/api/v3/UpdateSampleTest.java b/openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/api/v3/UpdateSampleTest.java
index d00c37b6c12eecad2216605a6018a2e5c93bb625..5edb3f5703913f3055bbdc05fd6da5ea9ec04c33 100644
--- a/openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/api/v3/UpdateSampleTest.java
+++ b/openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/api/v3/UpdateSampleTest.java
@@ -32,6 +32,7 @@ import org.testng.annotations.Test;
 
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.entity.attachment.Attachment;
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.entity.attachment.AttachmentCreation;
+import ch.ethz.sis.openbis.generic.shared.api.v3.dto.entity.material.Material;
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.entity.sample.Sample;
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.entity.sample.SampleCreation;
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.entity.sample.SampleUpdate;
@@ -41,6 +42,7 @@ import ch.ethz.sis.openbis.generic.shared.api.v3.dto.id.attachment.AttachmentFil
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.id.entitytype.EntityTypePermId;
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.id.experiment.ExperimentPermId;
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.id.experiment.IExperimentId;
+import ch.ethz.sis.openbis.generic.shared.api.v3.dto.id.material.MaterialPermId;
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.id.sample.ISampleId;
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.id.sample.SampleIdentifier;
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.id.sample.SamplePermId;
@@ -1241,4 +1243,34 @@ public class UpdateSampleTest extends AbstractSampleTest
         assertTags(sample2.getTags(), "/test/TEST_TAG_2", "/test/TEST_TAG_3");
     }
 
+    @Test
+    public void testWithMaterialProperties()
+    {
+        String sessionToken = v3api.login(TEST_USER, PASSWORD);
+
+        SampleFetchOptions fetchOptions = new SampleFetchOptions();
+        fetchOptions.withMaterialProperties().withRegistrator();
+        fetchOptions.withProperties();
+
+        SamplePermId permId = new SamplePermId("200902091219327-1025");
+
+        SampleUpdate update1 = new SampleUpdate();
+        update1.setSampleId(permId);
+        update1.setProperty("ANY_MATERIAL", "BACTERIUM-X (BACTERIUM)");
+
+        v3api.updateSamples(sessionToken, Arrays.asList(update1));
+
+        Map<ISampleId, Sample> map = v3api.mapSamples(sessionToken, Arrays.asList(permId), fetchOptions);
+
+        Sample sample = map.get(permId);
+
+        assertEquals(sample.getProperties().get("BACTERIUM"), "BACTERIUM-X (BACTERIUM)");
+        assertEquals(sample.getProperties().get("ANY_MATERIAL"), "BACTERIUM-X (BACTERIUM)");
+
+        Map<String, Material> materialProperties = sample.getMaterialProperties();
+
+        Material updatedBacterium = materialProperties.get("ANY_MATERIAL");
+        assertEquals(updatedBacterium.getPermId(), new MaterialPermId("BACTERIUM-X", "BACTERIUM"));
+    }
+
 }