Index: /trunk/src/org/openstreetmap/josm/data/validation/tests/Highways.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/data/validation/tests/Highways.java	(revision 12311)
+++ /trunk/src/org/openstreetmap/josm/data/validation/tests/Highways.java	(revision 12312)
@@ -13,4 +13,5 @@
 import java.util.Map;
 import java.util.Set;
+import java.util.stream.Collectors;
 
 import org.openstreetmap.josm.command.ChangePropertyCommand;
@@ -108,16 +109,24 @@
     private void testWrongRoundabout(Way w) {
         Map<String, List<Way>> map = new HashMap<>();
-        // Count all highways (per type) connected to this roundabout, except links
+        // Count all highways (per type) connected to this roundabout, except correct links
         // As roundabouts are closed ways, take care of not processing the first/last node twice
         for (Node n : new HashSet<>(w.getNodes())) {
             for (Way h : Utils.filteredCollection(n.getReferrers(), Way.class)) {
                 String value = h.get(HIGHWAY);
-                if (h != w && value != null && !value.endsWith("_link")) {
-                    List<Way> list = map.get(value);
-                    if (list == null) {
-                        list = new ArrayList<>();
-                        map.put(value, list);
-                    }
-                    list.add(h);
+                if (h != w && value != null) {
+                    boolean link = value.endsWith("_link");
+                    boolean linkOk = isHighwayLinkOkay(h);
+                    if (link && !linkOk) {
+                        // "Autofix" bad link value to avoid false positive in roundabout check
+                        value = value.replaceAll("_link$", "");
+                    }
+                    if (!link || !linkOk) {
+                        List<Way> list = map.get(value);
+                        if (list == null) {
+                            list = new ArrayList<>();
+                            map.put(value, list);
+                        }
+                        list.add(h);
+                    }
                 }
             }
@@ -146,4 +155,9 @@
     }
 
+    /**
+     * Determines if the given link road is correct, see https://wiki.openstreetmap.org/wiki/Highway_link.
+     * @param way link road
+     * @return {@code true} if the link road is correct or if the check cannot be performed due to missing data
+     */
     public static boolean isHighwayLinkOkay(final Way way) {
         final String highway = way.get(HIGHWAY);
@@ -165,6 +179,20 @@
         }
 
-        return Utils.filteredCollection(referrers, Way.class).stream().anyMatch(
-                otherWay -> !way.equals(otherWay) && otherWay.hasTag(HIGHWAY, highway, highway.replaceAll("_link$", "")));
+        // Find ways of same class (exact class of class_link)
+        List<Way> sameClass = Utils.filteredCollection(referrers, Way.class).stream().filter(
+                otherWay -> !way.equals(otherWay) && otherWay.hasTag(HIGHWAY, highway, highway.replaceAll("_link$", "")))
+                .collect(Collectors.toList());
+        if (sameClass.size() > 1) {
+            // It is possible to have a class_link between 2 segments of same class
+            // in roundabout designs that physically separate a specific turn from the main roundabout
+            // But if we have more than a single adjacent class, and one of them is a roundabout, that's an error
+            for (Way w : sameClass) {
+                if (w.hasTag("junction", "roundabout")) {
+                    return false;
+                }
+            }
+        }
+        // Link roads should always at least one adjacent segment of same class
+        return !sameClass.isEmpty();
     }
 
Index: /trunk/test/unit/org/openstreetmap/josm/data/validation/tests/HighwaysTest.java
===================================================================
--- /trunk/test/unit/org/openstreetmap/josm/data/validation/tests/HighwaysTest.java	(revision 12311)
+++ /trunk/test/unit/org/openstreetmap/josm/data/validation/tests/HighwaysTest.java	(revision 12312)
@@ -5,8 +5,13 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.InputStream;
+import java.util.Collection;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.openstreetmap.josm.JOSMFixture;
+import org.openstreetmap.josm.TestUtils;
 import org.openstreetmap.josm.data.coor.LatLon;
 import org.openstreetmap.josm.data.osm.DataSet;
@@ -14,4 +19,5 @@
 import org.openstreetmap.josm.data.osm.Way;
 import org.openstreetmap.josm.data.validation.TestError;
+import org.openstreetmap.josm.io.OsmReader;
 
 /**
@@ -96,3 +102,25 @@
         assertEquals("GB:nsl_single", link.get("source:maxspeed"));
     }
+
+    /**
+     * Non-regression test for <a href="https://josm.openstreetmap.de/ticket/14891">Bug #14891</a>.
+     * @throws Exception if an error occurs
+     */
+    @Test
+    public void testTicket14891() throws Exception {
+        try (InputStream is = TestUtils.getRegressionDataStream(14891, "14891.osm.bz2")) {
+            Collection<Way> ways = OsmReader.parseDataSet(is, null).getWays();
+            Way roundabout = ways.stream().filter(w -> 10068083 == w.getId()).findFirst().get();
+            Highways test = new Highways();
+            test.visit(roundabout);
+            if (!test.getErrors().isEmpty()) {
+                fail(test.getErrors().get(0).getMessage());
+            }
+            Way w1 = ways.stream().filter(w -> 28508494 == w.getId()).findFirst().get();
+            Way w2 = ways.stream().filter(w -> 28508493 == w.getId()).findFirst().get();
+            test.visit(w1);
+            test.visit(w2);
+            assertEquals(2, test.getErrors().size());
+        }
+    }
 }
