diff --git a/resources/images/dialogs/relation/replaceselectedright.svg b/resources/images/dialogs/relation/replaceselectedright.svg
new file mode 100644
index 00000000000..e5e6b5e3e29
--- /dev/null
+++ b/resources/images/dialogs/relation/replaceselectedright.svg
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24" height="24" version="1.0" xmlns="http://www.w3.org/2000/svg">
+ <rect x="1.957" y="2.457" width="11.336" height="19.273" fill="#fff" stroke="#646464" stroke-width="1.0116"/>
+ <path d="m1.75 4.375 6.75-0.0625" fill="none"/>
+ <g stroke-linecap="round">
+  <rect x="2.5767" y="9.8496" width="3.4312" height="3.0508" fill="#8faaff" stroke="#8faaff" stroke-width=".24516"/>
+  <rect x="6.2972" y="9.8321" width="6.393" height="3.0858" fill="#faa" stroke="#faa" stroke-width=".33697"/>
+  <path d="m22.163 19.788v-14.076l-5.9509 7.0229 5.9509 7.053z" fill="#c8c8c8" fill-rule="evenodd" stroke="#c8c8c8" stroke-linejoin="round" stroke-width="1.9865"/>
+ </g>
+ <g>
+  <path d="m21.819 18.694v-14.076l-5.9509 7.0229 5.9509 7.053z" fill="#000080" fill-rule="evenodd" stroke="#000080" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.9865"/>
+  <path d="m2.3225 9.5613 10.605-0.00845" fill="none" stroke="#646464" stroke-width=".62002"/>
+  <path d="m2.3225 13.197 10.605-0.0084" fill="none" stroke="#646464" stroke-width=".62002"/>
+ </g>
+</svg>
diff --git a/src/org/openstreetmap/josm/gui/dialogs/relation/GenericRelationEditor.java b/src/org/openstreetmap/josm/gui/dialogs/relation/GenericRelationEditor.java
index f597a078c0d..74aca9559c6 100644
--- a/src/org/openstreetmap/josm/gui/dialogs/relation/GenericRelationEditor.java
+++ b/src/org/openstreetmap/josm/gui/dialogs/relation/GenericRelationEditor.java
@@ -88,6 +88,7 @@
 import org.openstreetmap.josm.gui.dialogs.relation.actions.RefreshAction;
 import org.openstreetmap.josm.gui.dialogs.relation.actions.RemoveAction;
 import org.openstreetmap.josm.gui.dialogs.relation.actions.RemoveSelectedAction;
+import org.openstreetmap.josm.gui.dialogs.relation.actions.ReplaceSelectedAction;
 import org.openstreetmap.josm.gui.dialogs.relation.actions.ReverseAction;
 import org.openstreetmap.josm.gui.dialogs.relation.actions.SelectAction;
 import org.openstreetmap.josm.gui.dialogs.relation.actions.SelectPrimitivesForSelectedMembersAction;
@@ -737,10 +738,13 @@ protected static JToolBar buildSelectionControlButtonToolbar(IRelationEditorActi
                 new AddSelectedAtEndAction(editorAccess)
                 ));
         groups.add(buildNativeGroup(20,
+                new ReplaceSelectedAction(editorAccess)
+                ));
+        groups.add(buildNativeGroup(30,
                 new SelectedMembersForSelectionAction(editorAccess),
                 new SelectPrimitivesForSelectedMembersAction(editorAccess)
                 ));
-        groups.add(buildNativeGroup(30,
+        groups.add(buildNativeGroup(40,
                 new RemoveSelectedAction(editorAccess)
                 ));
         groups.addAll(RelationEditorHooks.getSelectActions());
diff --git a/src/org/openstreetmap/josm/gui/dialogs/relation/MemberTableModel.java b/src/org/openstreetmap/josm/gui/dialogs/relation/MemberTableModel.java
index 962ccc25aff..1c5bcfe8c83 100644
--- a/src/org/openstreetmap/josm/gui/dialogs/relation/MemberTableModel.java
+++ b/src/org/openstreetmap/josm/gui/dialogs/relation/MemberTableModel.java
@@ -520,6 +520,42 @@ public void updateRole(int[] idx, String role) {
         addToSelectedMembers(selected);
     }
 
+    /**
+     * updates the referenced primitive of the members given by the index in <code>index</code>
+     *
+     * @param index the index to update
+     * @param newPrimitive the new primitive
+     * @since xxx
+     */
+    public void updateMemberPrimitive(int index, OsmPrimitive newPrimitive) {
+        if (index >= members.size()) {
+            return;
+        }
+
+        RelationMember newMember = new RelationMember(members.get(index).getRole(), newPrimitive);
+        updateMember(index, newMember);
+    }
+
+    /**
+     * replace the member at <code>index</code> with a new one
+     *
+     * @param index the index to update
+     * @param newMember the new member
+     * @since xxx
+     */
+    public void updateMember(int index, RelationMember newMember) {
+        if (index >= members.size()) {
+            return;
+        }
+
+        RelationMember oldMember = members.get(index);
+        if (oldMember.equals(newMember))
+            return;
+
+        setValue(index, newMember);
+        fireTableDataChanged();
+    }
+
     /**
      * Get the currently selected relation members
      *
diff --git a/src/org/openstreetmap/josm/gui/dialogs/relation/actions/AddFromSelectionAction.java b/src/org/openstreetmap/josm/gui/dialogs/relation/actions/AddFromSelectionAction.java
index b1740cc06ac..2d729b840c0 100644
--- a/src/org/openstreetmap/josm/gui/dialogs/relation/actions/AddFromSelectionAction.java
+++ b/src/org/openstreetmap/josm/gui/dialogs/relation/actions/AddFromSelectionAction.java
@@ -29,28 +29,61 @@ protected boolean isPotentialDuplicate(OsmPrimitive primitive) {
         return editorAccess.getMemberTableModel().hasMembersReferringTo(Collections.singleton(primitive));
     }
 
+    /**
+     * Check and filter a list of primitives before adding them as relation members.
+     * Prompt users for confirmation when duplicates are detected and prevent relation loops.
+     *
+     * @param primitives The primitives to be checked and filtered
+     * @return The primitives to add to the relation. Never {@code null}, but may be an empty list.
+     * @throws AddAbortException when a relation loop is detected
+     */
     protected List<OsmPrimitive> filterConfirmedPrimitives(List<OsmPrimitive> primitives) throws AddAbortException {
+        return filterConfirmedPrimitives(primitives, false);
+    }
+
+    /**
+     * Check and filter a list of primitives before adding them as relation members.
+     * Prompt users for confirmation when duplicates are detected and prevent relation loops.
+     *
+     * @param primitives The primitives to be checked and filtered
+     * @param abortOnSkip If the user decides to not add a primitive or adding a primitive would 
+     *                    cause a relation loop, abort (throw {@code AddAbortException})
+     * @return The primitives to add to the relation. Never {@code null}, but may be an empty list.
+     * @throws AddAbortException when a relation loop is detected or {@code abortOnSkip} is 
+     *                           {@code true} <i>and</i> the user decides to not add a primitive.
+     * @since xxx
+     */
+    protected List<OsmPrimitive> filterConfirmedPrimitives(List<OsmPrimitive> primitives, boolean abortOnSkip) throws AddAbortException {
         if (Utils.isEmpty(primitives))
             return primitives;
         List<OsmPrimitive> ret = new ArrayList<>();
         ConditionalOptionPaneUtil.startBulkOperation("add_primitive_to_relation");
-        for (OsmPrimitive primitive : primitives) {
-            if (primitive instanceof Relation) {
-                List<Relation> loop = RelationChecker.checkAddMember(editorAccess.getEditor().getRelation(), (Relation) primitive);
-                if (!loop.isEmpty() && loop.get(0).equals(loop.get(loop.size() - 1))) {
-                    GenericRelationEditor.warnOfCircularReferences(primitive, loop);
-                    continue;
+        try {
+            for (OsmPrimitive primitive : primitives) {
+                if (primitive instanceof Relation) {
+                    List<Relation> loop = RelationChecker.checkAddMember(editorAccess.getEditor().getRelation(), (Relation) primitive);
+                    if (!loop.isEmpty() && loop.get(0).equals(loop.get(loop.size() - 1))) {
+                        GenericRelationEditor.warnOfCircularReferences(primitive, loop);
+                        if (abortOnSkip) {
+                            throw new AddAbortException();
+                        }
+                        continue;
+                    }
                 }
-            }
-            if (isPotentialDuplicate(primitive)) {
-                if (GenericRelationEditor.confirmAddingPrimitive(primitive)) {
+                if (isPotentialDuplicate(primitive)) {
+                    if (GenericRelationEditor.confirmAddingPrimitive(primitive)) {
+                        ret.add(primitive);
+                    } else if (abortOnSkip) {
+                        throw new AddAbortException();
+                    }
+                } else {
                     ret.add(primitive);
                 }
-            } else {
-                ret.add(primitive);
             }
+        } finally {
+            ConditionalOptionPaneUtil.endBulkOperation("add_primitive_to_relation");
         }
-        ConditionalOptionPaneUtil.endBulkOperation("add_primitive_to_relation");
+
         return ret;
     }
 }
diff --git a/src/org/openstreetmap/josm/gui/dialogs/relation/actions/ReplaceSelectedAction.java b/src/org/openstreetmap/josm/gui/dialogs/relation/actions/ReplaceSelectedAction.java
new file mode 100644
index 00000000000..097225878d3
--- /dev/null
+++ b/src/org/openstreetmap/josm/gui/dialogs/relation/actions/ReplaceSelectedAction.java
@@ -0,0 +1,57 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.dialogs.relation.actions;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.event.ActionEvent;
+import java.util.List;
+
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.gui.dialogs.relation.GenericRelationEditor.AddAbortException;
+import org.openstreetmap.josm.tools.ImageProvider;
+import org.openstreetmap.josm.tools.Logging;
+
+/**
+ * Replace selected relation members with the objects selected in the current dataset
+ * @since xxx
+ */
+public class ReplaceSelectedAction extends AddFromSelectionAction {
+
+    /**
+     * Constructs a new {@code ReplaceSelectedAction}.
+     * @param editorAccess An interface to access the relation editor contents.
+     */
+    public ReplaceSelectedAction(IRelationEditorActionAccess editorAccess) {
+        super(editorAccess, IRelationEditorUpdateOn.MEMBER_TABLE_SELECTION, IRelationEditorUpdateOn.SELECTION_TABLE_CHANGE);
+        putValue(SHORT_DESCRIPTION, tr("Replace selected members with selected objects"));
+        new ImageProvider("dialogs/relation", "replaceselectedright").getResource().attachImageIcon(this, true);
+        updateEnabledState();
+    }
+
+    @Override
+    protected void updateEnabledState() {
+        int numSelected = getSelectionTableModel().getRowCount();
+        setEnabled(numSelected > 0 &&
+                   numSelected == getMemberTableModel().getSelectedIndices().length);
+    }
+
+    @Override
+    public void actionPerformed(ActionEvent e) {
+        try {
+            int[] selectedMemberIndices = getMemberTableModel().getSelectedIndices();
+            List<OsmPrimitive> selection = getSelectionTableModel().getSelection();
+            int numSelectedPrimitives = selection.size();
+            if (numSelectedPrimitives != selectedMemberIndices.length) {
+                return;
+            }
+
+            List<OsmPrimitive> filteredSelection = filterConfirmedPrimitives(selection, true);
+
+            for (int i = 0; i < selectedMemberIndices.length; i++) {
+                getMemberTableModel().updateMemberPrimitive(selectedMemberIndices[i], filteredSelection.get(i));
+            }
+        } catch (AddAbortException ex) {
+            Logging.trace(ex);
+        }
+    }
+}