Index: src/org/openstreetmap/josm/data/validation/OsmValidator.java
===================================================================
--- src/org/openstreetmap/josm/data/validation/OsmValidator.java	(revision 18524)
+++ src/org/openstreetmap/josm/data/validation/OsmValidator.java	(working copy)
@@ -163,7 +163,9 @@
     public static void addTest(Class<? extends Test> testClass) {
         allTests.add(testClass);
         try {
-            allTestsMap.put(testClass.getName(), testClass.getConstructor().newInstance());
+            Test test = testClass.getConstructor().newInstance();
+            verifyErrorCodes(test);
+            allTestsMap.put(testClass.getName(), test);
         } catch (ReflectiveOperationException e) {
             Logging.error(e);
         }
@@ -170,6 +172,28 @@
     }
 
     /**
+     * Verify that test codes do not overlap
+     * @param test The test to check
+     */
+    private static void verifyErrorCodes(Test test) {
+        int[] codeRange = test.errorCodes();
+        if (Arrays.stream(codeRange).distinct().count() != codeRange.length) {
+            // This should eventually become an exception on or after 2023-01-01.
+            Logging.error("Duplicate test codes in {0}", test.getName());
+        }
+        for (Test other : allTestsMap.values()) {
+            int[] otherCodeRange = other.errorCodes();
+            int[] duplicates = Arrays.stream(codeRange)
+                    .filter(code -> Arrays.stream(otherCodeRange).anyMatch(otherCode -> otherCode == code)).toArray();
+            if (duplicates.length > 0) {
+                // This should eventually become an exception on or after 2023-01-01.
+                Logging.error("Conflicting test codes: {0} and {1} have {2}",
+                        test.getName(), other.getName(), Arrays.toString(duplicates));
+            }
+        }
+    }
+
+    /**
      * Removes a test from the list of available tests. This will not remove
      * core tests.
      *
Index: src/org/openstreetmap/josm/data/validation/Test.java
===================================================================
--- src/org/openstreetmap/josm/data/validation/Test.java	(revision 18524)
+++ src/org/openstreetmap/josm/data/validation/Test.java	(working copy)
@@ -39,7 +39,7 @@
  *
  * @author frsantos
  */
-public class Test implements OsmPrimitiveVisitor {
+public abstract class Test implements OsmPrimitiveVisitor {
 
     protected static final Predicate<OsmPrimitive> IN_DOWNLOADED_AREA = new NotOutsideDataSourceArea();
     protected static final Predicate<OsmPrimitive> IN_DOWNLOADED_AREA_STRICT = new InDataSourceArea(true);
@@ -383,4 +383,14 @@
     public Object getSource() {
         return "Java: " + this.getClass().getName();
     }
+
+    /**
+     * Get the error codes for this check. Used to ensure that this test does not conflict with other tests.
+     * See #21423.
+     * @return The error codes for this check. This array should never be modified. This is a candidate for frozen arrays.
+     * @since xxx (not yet mandatory)
+     */
+    protected int[] errorCodes() {
+        return new int[0];
+    }
 }
Index: src/org/openstreetmap/josm/data/validation/tests/Addresses.java
===================================================================
--- src/org/openstreetmap/josm/data/validation/tests/Addresses.java	(revision 18524)
+++ src/org/openstreetmap/josm/data/validation/tests/Addresses.java	(working copy)
@@ -54,6 +54,15 @@
     protected static final int HOUSE_NUMBER_TOO_FAR = 2605;
     protected static final int OBSOLETE_RELATION = 2606;
 
+    private static final int[] ERROR_CODES = {
+            HOUSE_NUMBER_WITHOUT_STREET,
+            DUPLICATE_HOUSE_NUMBER,
+            MULTIPLE_STREET_NAMES,
+            MULTIPLE_STREET_RELATIONS,
+            HOUSE_NUMBER_TOO_FAR,
+            OBSOLETE_RELATION
+    };
+
     protected static final DoubleProperty MAX_DUPLICATE_DISTANCE = new DoubleProperty("validator.addresses.max_duplicate_distance", 200.0);
     protected static final DoubleProperty MAX_STREET_DISTANCE = new DoubleProperty("validator.addresses.max_street_distance", 200.0);
 
@@ -522,4 +531,9 @@
         return testError.getCode() == OBSOLETE_RELATION;
     }
 
+    @Override
+    protected int[] errorCodes() {
+        return ERROR_CODES;
+    }
+
 }
Index: src/org/openstreetmap/josm/data/validation/tests/ApiCapabilitiesTest.java
===================================================================
--- src/org/openstreetmap/josm/data/validation/tests/ApiCapabilitiesTest.java	(revision 18524)
+++ src/org/openstreetmap/josm/data/validation/tests/ApiCapabilitiesTest.java	(working copy)
@@ -55,4 +55,9 @@
                     .build());
         }
     }
+
+    @Override
+    protected int[] errorCodes() {
+        return new int[] {MAX_WAY_NODES_ERROR};
+    }
 }
Index: src/org/openstreetmap/josm/data/validation/tests/BarriersEntrances.java
===================================================================
--- src/org/openstreetmap/josm/data/validation/tests/BarriersEntrances.java	(revision 18524)
+++ src/org/openstreetmap/josm/data/validation/tests/BarriersEntrances.java	(working copy)
@@ -39,4 +39,9 @@
                     .build());
         }
     }
+
+    @Override
+    protected int[] errorCodes() {
+        return new int[] {BARRIER_ENTRANCE_WITHOUT_BARRIER};
+    }
 }
Index: src/org/openstreetmap/josm/data/validation/tests/Coastlines.java
===================================================================
--- src/org/openstreetmap/josm/data/validation/tests/Coastlines.java	(revision 18524)
+++ src/org/openstreetmap/josm/data/validation/tests/Coastlines.java	(working copy)
@@ -38,6 +38,8 @@
     protected static final int UNCONNECTED_COASTLINE = 903;
     protected static final int WRONG_ORDER_COASTLINE = 904;
 
+    private static final int[] ERROR_CODES = {UNORDERED_COASTLINE, REVERSED_COASTLINE, UNCONNECTED_COASTLINE, WRONG_ORDER_COASTLINE};
+
     private List<Way> coastlineWays;
 
     /**
@@ -277,4 +279,9 @@
 
         return false;
     }
+
+    @Override
+    protected int[] errorCodes() {
+        return ERROR_CODES;
+    }
 }
Index: src/org/openstreetmap/josm/data/validation/tests/ConditionalKeys.java
===================================================================
--- src/org/openstreetmap/josm/data/validation/tests/ConditionalKeys.java	(revision 18524)
+++ src/org/openstreetmap/josm/data/validation/tests/ConditionalKeys.java	(working copy)
@@ -24,6 +24,10 @@
  */
 public class ConditionalKeys extends Test.TagTest {
 
+    private static final int ERROR_WRONG_SYNTAX = 3201;
+    private static final int ERROR_SYNTAX_ERROR = 3202;
+    private static final int[] ERROR_CODES = {ERROR_WRONG_SYNTAX, ERROR_SYNTAX_ERROR};
+
     private final OpeningHourTest openingHourTest = new OpeningHourTest();
     private static final Set<String> RESTRICTION_TYPES = new HashSet<>(Arrays.asList("oneway", "toll", "noexit", "maxspeed", "minspeed",
             "maxstay", "maxweight", "maxaxleload", "maxheight", "maxwidth", "maxlength", "overtaking", "maxgcweight", "maxgcweightrating",
@@ -56,6 +60,11 @@
         openingHourTest.initialize();
     }
 
+    @Override
+    protected int[] errorCodes() {
+        return ERROR_CODES;
+    }
+
     /**
      * Check if the key is a key for an access restriction
      * @param part The key (or the restriction part of it, e.g. for lanes)
@@ -228,7 +237,7 @@
                 return;
             }
             if (!isKeyValid(key)) {
-                errors.add(TestError.builder(this, Severity.WARNING, 3201)
+                errors.add(TestError.builder(this, Severity.WARNING, ERROR_WRONG_SYNTAX)
                         .message(tr("Wrong syntax in {0} key", key))
                         .primitives(p)
                         .build());
@@ -236,7 +245,7 @@
             }
             final String error = validateValue(key, value);
             if (error != null) {
-                errors.add(TestError.builder(this, Severity.WARNING, 3202)
+                errors.add(TestError.builder(this, Severity.WARNING, ERROR_SYNTAX_ERROR)
                         .message(tr("Error in {0} value: {1}", key, error))
                         .primitives(p)
                         .build());
Index: src/org/openstreetmap/josm/data/validation/tests/ConnectivityRelations.java
===================================================================
--- src/org/openstreetmap/josm/data/validation/tests/ConnectivityRelations.java	(revision 18524)
+++ src/org/openstreetmap/josm/data/validation/tests/ConnectivityRelations.java	(working copy)
@@ -50,6 +50,18 @@
 
     protected static final int CONNECTIVITY_IMPLIED = 3908;
 
+    private static final int[] ERROR_CODES = {
+            INCONSISTENT_LANE_COUNT,
+            UNKNOWN_CONNECTIVITY_ROLE,
+            NO_CONNECTIVITY_TAG,
+            MALFORMED_CONNECTIVITY_TAG,
+            MISSING_COMMA_CONNECTIVITY_TAG,
+            TOO_MANY_ROLES,
+            MISSING_ROLE,
+            MEMBER_MISSING_LANES,
+            CONNECTIVITY_IMPLIED
+    };
+
     private static final String CONNECTIVITY_TAG = "connectivity";
     private static final String VIA = "via";
     private static final String TO = "to";
@@ -140,6 +152,11 @@
         }
     }
 
+    @Override
+    protected int[] errorCodes() {
+        return ERROR_CODES;
+    }
+
     /**
      * Compare lane tags of members to values in the {@code connectivity} tag of the relation
      *
Index: src/org/openstreetmap/josm/data/validation/tests/CrossingWays.java
===================================================================
--- src/org/openstreetmap/josm/data/validation/tests/CrossingWays.java	(revision 18524)
+++ src/org/openstreetmap/josm/data/validation/tests/CrossingWays.java	(working copy)
@@ -111,6 +111,11 @@
         }
 
         @Override
+        protected int[] errorCodes() {
+            return new int[] {CROSSING_WAYS};
+        }
+
+        @Override
         boolean ignoreWaySegmentCombination(Way w1, Way w2) {
             if (w1 == w2)
                 return true;
@@ -261,6 +266,11 @@
                     visit(w);
             }
         }
+
+        @Override
+        protected int[] errorCodes() {
+            return new int[] {CROSSING_BOUNDARIES};
+        }
     }
 
     /**
@@ -281,6 +291,11 @@
         boolean ignoreWaySegmentCombination(Way w1, Way w2) {
             return false; // we should not get here
         }
+
+        @Override
+        protected int[] errorCodes() {
+            return new int[] {CROSSING_SELF};
+        }
     }
 
     /**
Index: src/org/openstreetmap/josm/data/validation/tests/DirectionNodes.java
===================================================================
--- src/org/openstreetmap/josm/data/validation/tests/DirectionNodes.java	(revision 18524)
+++ src/org/openstreetmap/josm/data/validation/tests/DirectionNodes.java	(working copy)
@@ -27,6 +27,13 @@
     private static final int NO_WAY_CODE = 4002;
     private static final int NO_SUITABLE_WAY = 4003;
 
+    private static final int[] ERROR_CODES = {
+            MULTIPLE_WAYS_CODE,
+            END_NODE_CODE,
+            NO_WAY_CODE,
+            NO_SUITABLE_WAY
+    };
+
     private static final String INVALID_USE_MSG = tr("Invalid usage of direction on node");
     private static final String DISPUTED_USE_MSG = tr("Disputed usage of direction on node");
 
@@ -49,6 +56,11 @@
         }
     }
 
+    @Override
+    protected int[] errorCodes() {
+        return ERROR_CODES;
+    }
+
     private static boolean isSuitableParentWay(Way w) {
         return w.hasKey("highway", "railway", "waterway") || w.hasTag("man_made", "pipeline");
     }
Index: src/org/openstreetmap/josm/data/validation/tests/DuplicateNode.java
===================================================================
--- src/org/openstreetmap/josm/data/validation/tests/DuplicateNode.java	(revision 18524)
+++ src/org/openstreetmap/josm/data/validation/tests/DuplicateNode.java	(working copy)
@@ -101,6 +101,20 @@
     protected static final int DUPLICATE_NODE_RAILWAY = 16;
     protected static final int DUPLICATE_NODE_WATERWAY = 17;
 
+    private static final int[] ERROR_CODES = {
+            DUPLICATE_NODE,
+            DUPLICATE_NODE_MIXED,
+            DUPLICATE_NODE_OTHER,
+            DUPLICATE_NODE_BUILDING,
+            DUPLICATE_NODE_BOUNDARY,
+            DUPLICATE_NODE_HIGHWAY,
+            DUPLICATE_NODE_LANDUSE,
+            DUPLICATE_NODE_NATURAL,
+            DUPLICATE_NODE_POWER,
+            DUPLICATE_NODE_RAILWAY,
+            DUPLICATE_NODE_WATERWAY
+    };
+
     private static final String[] TYPES = {
             "none", HIGHWAY, RAILWAY, WATERWAY, "boundary", "power", "natural", "landuse", "building"};
 
@@ -305,4 +319,9 @@
                 && Command.checkOutlyingOrIncompleteOperation(testError.getPrimitives(), null) == Command.IS_OK;
         // everything else is ok to merge
     }
+
+    @Override
+    protected int[] errorCodes() {
+        return ERROR_CODES;
+    }
 }
Index: src/org/openstreetmap/josm/data/validation/tests/DuplicateRelation.java
===================================================================
--- src/org/openstreetmap/josm/data/validation/tests/DuplicateRelation.java	(revision 18524)
+++ src/org/openstreetmap/josm/data/validation/tests/DuplicateRelation.java	(working copy)
@@ -175,6 +175,8 @@
     /** Code number of relation with same members error */
     protected static final int SAME_RELATION = 1902;
 
+    private static final int[] ERROR_CODES = {DUPLICATE_RELATION, SAME_RELATION};
+
     /** MultiMap of all relations */
     private MultiMap<RelationPair, OsmPrimitive> relations;
 
@@ -315,4 +317,9 @@
                 .limit(2)
                 .count() <= 1;
     }
+
+    @Override
+    protected int[] errorCodes() {
+        return ERROR_CODES;
+    }
 }
Index: src/org/openstreetmap/josm/data/validation/tests/DuplicateWay.java
===================================================================
--- src/org/openstreetmap/josm/data/validation/tests/DuplicateWay.java	(revision 18524)
+++ src/org/openstreetmap/josm/data/validation/tests/DuplicateWay.java	(working copy)
@@ -95,6 +95,8 @@
     /** Test identification for identical ways (coordinates only). */
     protected static final int SAME_WAY = 1402;
 
+    private static final int[] ERROR_CODES = {DUPLICATE_WAY, SAME_WAY};
+
     /** Bag of all ways */
     private MultiMap<WayPair, OsmPrimitive> ways;
 
@@ -318,4 +320,9 @@
                 .count();
         return waysWithRelations <= 1;
     }
+
+    @Override
+    protected int[] errorCodes() {
+        return ERROR_CODES;
+    }
 }
Index: src/org/openstreetmap/josm/data/validation/tests/DuplicatedWayNodes.java
===================================================================
--- src/org/openstreetmap/josm/data/validation/tests/DuplicatedWayNodes.java	(revision 18524)
+++ src/org/openstreetmap/josm/data/validation/tests/DuplicatedWayNodes.java	(working copy)
@@ -81,4 +81,9 @@
     public boolean isFixable(TestError testError) {
         return testError.getTester() instanceof DuplicatedWayNodes;
     }
+
+    @Override
+    protected int[] errorCodes() {
+        return new int[] {DUPLICATE_WAY_NODE};
+    }
 }
Index: src/org/openstreetmap/josm/data/validation/tests/Highways.java
===================================================================
--- src/org/openstreetmap/josm/data/validation/tests/Highways.java	(revision 18524)
+++ src/org/openstreetmap/josm/data/validation/tests/Highways.java	(working copy)
@@ -38,6 +38,15 @@
     protected static final int SOURCE_MAXSPEED_CONTEXT_MISMATCH_VS_MAXSPEED = 2705;
     protected static final int SOURCE_MAXSPEED_CONTEXT_MISMATCH_VS_HIGHWAY = 2706;
     protected static final int SOURCE_WRONG_LINK = 2707;
+    private static final int[] ERROR_CODES = {
+            WRONG_ROUNDABOUT_HIGHWAY,
+            MISSING_PEDESTRIAN_CROSSING,
+            SOURCE_MAXSPEED_UNKNOWN_COUNTRY_CODE,
+            SOURCE_MAXSPEED_UNKNOWN_CONTEXT,
+            SOURCE_MAXSPEED_CONTEXT_MISMATCH_VS_MAXSPEED,
+            SOURCE_MAXSPEED_CONTEXT_MISMATCH_VS_HIGHWAY,
+            SOURCE_WRONG_LINK
+    };
 
     protected static final String SOURCE_MAXSPEED = "source:maxspeed";
 
@@ -109,6 +118,11 @@
         }
     }
 
+    @Override
+    protected int[] errorCodes() {
+        return ERROR_CODES;
+    }
+
     private void testWrongRoundabout(Way w) {
         Map<String, List<Way>> map = new HashMap<>();
         // Count all highways (per type) connected to this roundabout, except correct links
Index: src/org/openstreetmap/josm/data/validation/tests/InternetTags.java
===================================================================
--- src/org/openstreetmap/josm/data/validation/tests/InternetTags.java	(revision 18524)
+++ src/org/openstreetmap/josm/data/validation/tests/InternetTags.java	(working copy)
@@ -28,6 +28,7 @@
     public static final int INVALID_URL = 3301;
     /** Error code for an invalid e-mail */
     public static final int INVALID_EMAIL = 3302;
+    private static final int[] ERROR_CODES = {INVALID_URL, INVALID_EMAIL};
 
     /**
      * List of keys subject to URL validation.
@@ -129,4 +130,9 @@
             }
         });
     }
+
+    @Override
+    protected int[] errorCodes() {
+        return ERROR_CODES;
+    }
 }
Index: src/org/openstreetmap/josm/data/validation/tests/Lanes.java
===================================================================
--- src/org/openstreetmap/josm/data/validation/tests/Lanes.java	(revision 18524)
+++ src/org/openstreetmap/josm/data/validation/tests/Lanes.java	(working copy)
@@ -20,6 +20,9 @@
  * @since 6592
  */
 public class Lanes extends Test.TagTest {
+    private static final int ERROR_LANE_COUNT_MISMATCH = 3100;
+    private static final int ERROR_LANE_COUNT_MISMATCH_FORWARD_BACKWARD = 3101;
+    private static final int[] ERROR_CODES = {ERROR_LANE_COUNT_MISMATCH, ERROR_LANE_COUNT_MISMATCH_FORWARD_BACKWARD};
 
     private static final String[] BLACKLIST = {
         "source:lanes",
@@ -50,7 +53,7 @@
 
         if (lanesCount.size() > 1) {
             // if not all numbers are the same
-            errors.add(TestError.builder(this, Severity.WARNING, 3100)
+            errors.add(TestError.builder(this, Severity.WARNING, ERROR_LANE_COUNT_MISMATCH)
                     .message(message)
                     .primitives(p)
                     .build());
@@ -58,7 +61,7 @@
             // ensure that lanes <= *:lanes
             try {
                 if (Integer.parseInt(p.get(lanesKey)) > lanesCount.iterator().next()) {
-                    errors.add(TestError.builder(this, Severity.WARNING, 3100)
+                    errors.add(TestError.builder(this, Severity.WARNING, ERROR_LANE_COUNT_MISMATCH)
                             .message(tr("Number of {0} greater than {1}", lanesKey, "*:" + lanesKey))
                             .primitives(p)
                             .build());
@@ -76,7 +79,7 @@
         final String backward = Utils.firstNonNull(p.get("lanes:backward"), "0");
         try {
         if (Integer.parseInt(lanes) < Integer.parseInt(forward) + Integer.parseInt(backward)) {
-            errors.add(TestError.builder(this, Severity.WARNING, 3101)
+            errors.add(TestError.builder(this, Severity.WARNING, ERROR_LANE_COUNT_MISMATCH_FORWARD_BACKWARD)
                     .message(tr("Number of {0} greater than {1}", tr("{0}+{1}", "lanes:forward", "lanes:backward"), "lanes"))
                     .primitives(p)
                     .build());
@@ -98,4 +101,9 @@
     public boolean isPrimitiveUsable(OsmPrimitive p) {
         return p.isTagged() && p instanceof Way && p.hasTag("highway") && super.isPrimitiveUsable(p);
     }
+
+    @Override
+    protected int[] errorCodes() {
+        return ERROR_CODES;
+    }
 }
Index: src/org/openstreetmap/josm/data/validation/tests/LongSegment.java
===================================================================
--- src/org/openstreetmap/josm/data/validation/tests/LongSegment.java	(revision 18524)
+++ src/org/openstreetmap/josm/data/validation/tests/LongSegment.java	(working copy)
@@ -78,6 +78,11 @@
         testWay(w);
     }
 
+    @Override
+    protected int[] errorCodes() {
+        return new int[] {LONG_SEGMENT};
+    }
+
     private void testWay(Way w) {
         for (int i = 0; i < w.getNodesCount() - 1; i++) {
             visitWaySegment(w, i);
Index: src/org/openstreetmap/josm/data/validation/tests/MapCSSTagChecker.java
===================================================================
--- src/org/openstreetmap/josm/data/validation/tests/MapCSSTagChecker.java	(revision 18524)
+++ src/org/openstreetmap/josm/data/validation/tests/MapCSSTagChecker.java	(working copy)
@@ -64,6 +64,8 @@
     static final boolean ALL_TESTS = true;
     static final boolean ONLY_SELECTED_TESTS = false;
 
+    static final int ERROR_MAPCSS = 3000;
+
     /**
      * Cached version of {@link ValidatorPrefHelper#PREF_OTHER}, see #20745.
      */
@@ -384,6 +386,11 @@
         visit(selection, null);
     }
 
+    @Override
+    protected int[] errorCodes() {
+        return new int[] {ERROR_MAPCSS};
+    }
+
     /**
      * Execute the rules from the URLs matching the given predicate.
      * @param selection collection of primitives
Index: src/org/openstreetmap/josm/data/validation/tests/MapCSSTagCheckerRule.java
===================================================================
--- src/org/openstreetmap/josm/data/validation/tests/MapCSSTagCheckerRule.java	(revision 18524)
+++ src/org/openstreetmap/josm/data/validation/tests/MapCSSTagCheckerRule.java	(working copy)
@@ -388,7 +388,7 @@
             final String description1 = group == null ? description : group;
             final String description2 = group == null ? null : description;
             final String selector = matchingSelector.toString();
-            TestError.Builder errorBuilder = TestError.builder(tester, getSeverity(), 3000)
+            TestError.Builder errorBuilder = TestError.builder(tester, getSeverity(), MapCSSTagChecker.ERROR_MAPCSS)
                     .messageWithManuallyTranslatedDescription(description1, description2, selector);
             if (fix != null) {
                 errorBuilder.fix(() -> fix);
@@ -398,7 +398,7 @@
             } else if (env.children != null) {
                 for (IPrimitive c : env.children) {
                     if (c instanceof OsmPrimitive) {
-                        errorBuilder = TestError.builder(tester, getSeverity(), 3000)
+                        errorBuilder = TestError.builder(tester, getSeverity(), MapCSSTagChecker.ERROR_MAPCSS)
                                 .messageWithManuallyTranslatedDescription(description1, description2, selector);
                         if (fix != null) {
                             errorBuilder.fix(() -> fix);
Index: src/org/openstreetmap/josm/data/validation/tests/MultipolygonTest.java
===================================================================
--- src/org/openstreetmap/josm/data/validation/tests/MultipolygonTest.java	(revision 18524)
+++ src/org/openstreetmap/josm/data/validation/tests/MultipolygonTest.java	(working copy)
@@ -77,6 +77,23 @@
     /** Incomplete multipolygon was modified */
     public static final int MODIFIED_INCOMPLETE = 1618;
 
+    private static final int[] ERROR_CODES = {
+            WRONG_MEMBER_TYPE,
+            WRONG_MEMBER_ROLE,
+            NON_CLOSED_WAY,
+            INNER_WAY_OUTSIDE,
+            CROSSING_WAYS,
+            OUTER_STYLE_MISMATCH,
+            INNER_STYLE_MISMATCH,
+            NO_STYLE,
+            OUTER_STYLE,
+            REPEATED_MEMBER_SAME_ROLE,
+            REPEATED_MEMBER_DIFF_ROLE,
+            EQUAL_RINGS,
+            RINGS_SHARE_NODES,
+            MODIFIED_INCOMPLETE
+    };
+
     private static final int FOUND_INSIDE = 1;
     private static final int FOUND_OUTSIDE = 2;
 
@@ -769,6 +786,11 @@
         return testError.getCode() == REPEATED_MEMBER_SAME_ROLE;
     }
 
+    @Override
+    protected int[] errorCodes() {
+        return ERROR_CODES;
+    }
+
     /**
      * Find nesting levels of polygons. Logic taken from class MultipolygonBuilder, uses different structures.
      */
Index: src/org/openstreetmap/josm/data/validation/tests/NameMismatch.java
===================================================================
--- src/org/openstreetmap/josm/data/validation/tests/NameMismatch.java	(revision 18524)
+++ src/org/openstreetmap/josm/data/validation/tests/NameMismatch.java	(working copy)
@@ -35,6 +35,7 @@
 public class NameMismatch extends Test.TagTest {
     protected static final int NAME_MISSING = 1501;
     protected static final int NAME_TRANSLATION_MISSING = 1502;
+    private static final int[] ERROR_CODES = {NAME_MISSING, NAME_TRANSLATION_MISSING};
     private static final Pattern NAME_SPLIT_PATTERN = Pattern.compile(" - ");
 
     private static final List<String> EXCLUSIONS = Arrays.asList(
@@ -121,4 +122,9 @@
         return p.isTagged() && super.isPrimitiveUsable(p);
     }
 
+    @Override
+    protected int[] errorCodes() {
+        return ERROR_CODES;
+    }
+
 }
Index: src/org/openstreetmap/josm/data/validation/tests/OpeningHourTest.java
===================================================================
--- src/org/openstreetmap/josm/data/validation/tests/OpeningHourTest.java	(revision 18524)
+++ src/org/openstreetmap/josm/data/validation/tests/OpeningHourTest.java	(working copy)
@@ -39,6 +39,7 @@
  */
 public class OpeningHourTest extends TagTest {
 
+    private static final int ERROR_OPENING_HOURS_SYNTAX = 2901;
     private static final Collection<String> KEYS_TO_CHECK = Arrays.asList("opening_hours", "collection_times", "service_times");
     private static final BooleanProperty PREF_STRICT_MODE =
             new BooleanProperty(ValidatorPrefHelper.PREFIX + "." + OpeningHourTest.class.getSimpleName() + "." + "strict", false);
@@ -63,7 +64,7 @@
      * @return The real test error given to JOSM validator. Can be fixable or not if a prettified values has been determined.
      */
     private TestError createTestError(Severity severity, String message, String key, String value, String prettifiedValue, OsmPrimitive p) {
-        final TestError.Builder error = TestError.builder(this, severity, 2901)
+        final TestError.Builder error = TestError.builder(this, severity, ERROR_OPENING_HOURS_SYNTAX)
                 .message(tr("Opening hours syntax"), message) // todo obtain English message for ignore functionality
                 .primitives(p != null ? new OsmPrimitive[] {p} : new OsmPrimitive[] {});
         if (p == null || prettifiedValue == null || prettifiedValue.equals(value)) {
@@ -161,4 +162,9 @@
         PREF_STRICT_MODE.put(checkboxStrictMode.isSelected());
         return false;
     }
+
+    @Override
+    protected int[] errorCodes() {
+        return new int[] {ERROR_OPENING_HOURS_SYNTAX};
+    }
 }
Index: src/org/openstreetmap/josm/data/validation/tests/OverlappingWays.java
===================================================================
--- src/org/openstreetmap/josm/data/validation/tests/OverlappingWays.java	(revision 18524)
+++ src/org/openstreetmap/josm/data/validation/tests/OverlappingWays.java	(working copy)
@@ -63,6 +63,20 @@
     protected static final int OVERLAPPING_HIGHWAY_LINEAR_WAY = 131;
     protected static final int OVERLAPPING_RAILWAY_LINEAR_WAY = 132;
     protected static final int OVERLAPPING_WATERWAY_LINEAR_WAY = 133;
+    private static final int[] ERROR_CODES = {
+            OVERLAPPING_HIGHWAY,
+            OVERLAPPING_RAILWAY,
+            OVERLAPPING_WAY,
+            OVERLAPPING_WATERWAY,
+            OVERLAPPING_HIGHWAY_AREA,
+            OVERLAPPING_RAILWAY_AREA,
+            OVERLAPPING_WAY_AREA,
+            OVERLAPPING_WATERWAY_AREA,
+            DUPLICATE_WAY_SEGMENT,
+            OVERLAPPING_HIGHWAY_LINEAR_WAY,
+            OVERLAPPING_RAILWAY_LINEAR_WAY,
+            OVERLAPPING_WATERWAY_LINEAR_WAY
+    };
 
     protected static final ListProperty IGNORED_KEYS = new ListProperty(
             "overlapping-ways.ignored-keys", Arrays.asList(
@@ -285,4 +299,9 @@
             lastN = n;
         }
     }
+
+    @Override
+    protected int[] errorCodes() {
+        return ERROR_CODES;
+    }
 }
Index: src/org/openstreetmap/josm/data/validation/tests/PowerLines.java
===================================================================
--- src/org/openstreetmap/josm/data/validation/tests/PowerLines.java	(revision 18524)
+++ src/org/openstreetmap/josm/data/validation/tests/PowerLines.java	(working copy)
@@ -33,6 +33,7 @@
     /** Test identifier */
     protected static final int POWER_LINES = 2501;
     protected static final int POWER_CONNECTION = 2502;
+    private static final int[] ERROR_CODES = {POWER_LINES, POWER_CONNECTION};
 
     /** Values for {@code power} key interpreted as power lines */
     static final Collection<String> POWER_LINE_TAGS = Arrays.asList("line", "minor_line");
@@ -111,6 +112,11 @@
     }
 
     @Override
+    protected int[] errorCodes() {
+        return ERROR_CODES;
+    }
+
+    @Override
     public void startTest(ProgressMonitor progressMonitor) {
         super.startTest(progressMonitor);
         clearCollections();
Index: src/org/openstreetmap/josm/data/validation/tests/PublicTransportRouteTest.java
===================================================================
--- src/org/openstreetmap/josm/data/validation/tests/PublicTransportRouteTest.java	(revision 18524)
+++ src/org/openstreetmap/josm/data/validation/tests/PublicTransportRouteTest.java	(working copy)
@@ -22,6 +22,10 @@
  * Tests for <a href="https://wiki.openstreetmap.org/wiki/Proposed_features/Public_Transport">public transport routes</a>.
  */
 public class PublicTransportRouteTest extends Test {
+    private static final int ERROR_BAD_ROLE = 3601;
+    private static final int ERROR_GAP = 3602;
+    private static final int ERROR_STOP_NOT_IN_ROUTE = 3603;
+    private static final int[] ERROR_CODES = {ERROR_BAD_ROLE, ERROR_GAP, ERROR_STOP_NOT_IN_ROUTE};
 
     private final WayConnectionTypeCalculator connectionTypeCalculator = new WayConnectionTypeCalculator();
 
@@ -46,7 +50,7 @@
         final Set<Node> routeNodes = new HashSet<>();
         for (RelationMember member : r.getMembers()) {
             if (member.hasRole("forward", "backward", "alternate")) {
-                errors.add(TestError.builder(this, Severity.ERROR, 3601)
+                errors.add(TestError.builder(this, Severity.ERROR, ERROR_BAD_ROLE)
                         .message(tr("Route relation contains a ''{0}'' role", "forward/backward/alternate"))
                         .primitives(r)
                         .build());
@@ -68,7 +72,7 @@
                     || link.direction == null
                     || WayConnectionType.Direction.NONE == link.direction;
             if (hasError) {
-                errors.add(TestError.builder(this, Severity.WARNING, 3602)
+                errors.add(TestError.builder(this, Severity.WARNING, ERROR_GAP)
                         .message(tr("Route relation contains a gap"))
                         .primitives(r)
                         .build());
@@ -80,7 +84,7 @@
             if (member.hasRole("stop", "stop_exit_only", "stop_entry_only")
                     && OsmPrimitiveType.NODE == member.getType()
                     && !routeNodes.contains(member.getNode())) {
-                errors.add(TestError.builder(this, Severity.WARNING, 3603)
+                errors.add(TestError.builder(this, Severity.WARNING, ERROR_STOP_NOT_IN_ROUTE)
                         .message(tr("Stop position not part of route"))
                         .primitives(member.getMember(), r)
                         .build());
@@ -93,4 +97,9 @@
         connectionTypeCalculator.clear();
         super.clear();
     }
+
+    @Override
+    protected int[] errorCodes() {
+        return ERROR_CODES;
+    }
 }
Index: src/org/openstreetmap/josm/data/validation/tests/RelationChecker.java
===================================================================
--- src/org/openstreetmap/josm/data/validation/tests/RelationChecker.java	(revision 18524)
+++ src/org/openstreetmap/josm/data/validation/tests/RelationChecker.java	(working copy)
@@ -68,6 +68,18 @@
     public static final int RELATION_LOOP    = 1710;
     /** Relation is empty */
     public static final int RELATION_EMPTY   = 1711; // was 1708 up to r18505
+    private static final int[] ERROR_CODES = {
+            ROLE_UNKNOWN,
+            ROLE_EMPTY,
+            HIGH_COUNT,
+            LOW_COUNT,
+            ROLE_MISSING,
+            RELATION_UNKNOWN,
+            WRONG_ROLE,
+            WRONG_TYPE,
+            RELATION_LOOP,
+            RELATION_EMPTY
+    };
     // CHECKSTYLE.ON: SingleSpaceSeparator
 
     // see 19312 comment:17
@@ -401,6 +413,11 @@
     }
 
     @Override
+    protected int[] errorCodes() {
+        return ERROR_CODES;
+    }
+
+    @Override
     public void taggingPresetsModified() {
         relationpresets.clear();
         initializePresets();
Index: src/org/openstreetmap/josm/data/validation/tests/RightAngleBuildingTest.java
===================================================================
--- src/org/openstreetmap/josm/data/validation/tests/RightAngleBuildingTest.java	(revision 18524)
+++ src/org/openstreetmap/josm/data/validation/tests/RightAngleBuildingTest.java	(working copy)
@@ -21,6 +21,7 @@
  * @since 13670
  */
 public class RightAngleBuildingTest extends Test {
+    private static final int ERROR_ALMOST_SQUARE_ANGLE = 3701;
 
     /** Maximum angle difference from right angle that is considered as invalid. */
     protected double maxAngleDelta;
@@ -43,7 +44,7 @@
         List<Pair<Double, Node>> angles = w.getAngles();
         for (Pair<Double, Node> pair: angles) {
             if (checkAngle(pair.a)) {
-                TestError.Builder builder = TestError.builder(this, Severity.OTHER, 3701)
+                TestError.Builder builder = TestError.builder(this, Severity.OTHER, ERROR_ALMOST_SQUARE_ANGLE)
                                                      .message(tr("Building with an almost square angle"))
                                                      .primitives(w)
                                                      .highlight(pair.b);
@@ -54,6 +55,11 @@
     }
 
     @Override
+    protected int[] errorCodes() {
+        return new int[]{ERROR_ALMOST_SQUARE_ANGLE};
+    }
+
+    @Override
     public void startTest(ProgressMonitor monitor) {
         super.startTest(monitor);
         maxAngleDelta = Config.getPref().getDouble("validator.RightAngleBuilding.maximumDelta", 10.0);
Index: src/org/openstreetmap/josm/data/validation/tests/SelfIntersectingWay.java
===================================================================
--- src/org/openstreetmap/josm/data/validation/tests/SelfIntersectingWay.java	(revision 18524)
+++ src/org/openstreetmap/josm/data/validation/tests/SelfIntersectingWay.java	(working copy)
@@ -63,6 +63,11 @@
         }
     }
 
+    @Override
+    protected int[] errorCodes() {
+        return new int[]{SELF_INTERSECT};
+    }
+
     /**
      * Check if the given way is self-intersecting
      * @param way the way to check
Index: src/org/openstreetmap/josm/data/validation/tests/SharpAngles.java
===================================================================
--- src/org/openstreetmap/josm/data/validation/tests/SharpAngles.java	(revision 18524)
+++ src/org/openstreetmap/josm/data/validation/tests/SharpAngles.java	(working copy)
@@ -55,6 +55,11 @@
         }
     }
 
+    @Override
+    protected int[] errorCodes() {
+        return new int[]{SHARP_ANGLES};
+    }
+
     /**
      * Check whether or not a way should be checked for sharp angles
      * @param way The way that needs to be checked
Index: src/org/openstreetmap/josm/data/validation/tests/SimilarNamedWays.java
===================================================================
--- src/org/openstreetmap/josm/data/validation/tests/SimilarNamedWays.java	(revision 18524)
+++ src/org/openstreetmap/josm/data/validation/tests/SimilarNamedWays.java	(working copy)
@@ -109,6 +109,11 @@
         }
     }
 
+    @Override
+    protected int[] errorCodes() {
+        return new int[]{SIMILAR_NAMED};
+    }
+
     /**
      * Add a regular expression rule.
      * @param regExpr the regular expression to search for
Index: src/org/openstreetmap/josm/data/validation/tests/TagChecker.java
===================================================================
--- src/org/openstreetmap/josm/data/validation/tests/TagChecker.java	(revision 18524)
+++ src/org/openstreetmap/josm/data/validation/tests/TagChecker.java	(working copy)
@@ -211,6 +211,29 @@
     protected static final int MULTIPOLYGON_MAYBE_NO_AREA       = 1220;
     protected static final int MULTIPOLYGON_SAME_TAG_ON_OUTER   = 1221;
     // CHECKSTYLE.ON: SingleSpaceSeparator
+    private static final int[] ERROR_CODES = {
+            EMPTY_VALUES,
+            INVALID_KEY,
+            INVALID_VALUE,
+            FIXME,
+            INVALID_SPACE,
+            INVALID_KEY_SPACE,
+            INVALID_HTML,
+            LONG_VALUE,
+            LONG_KEY,
+            LOW_CHAR_VALUE,
+            LOW_CHAR_KEY,
+            MISSPELLED_VALUE,
+            MISSPELLED_KEY,
+            MULTIPLE_SPACES,
+            MISSPELLED_VALUE_NO_FIX,
+            UNUSUAL_UNICODE_CHAR_VALUE,
+            INVALID_PRESETS_TYPE,
+            MULTIPOLYGON_NO_AREA,
+            MULTIPOLYGON_INCOMPLETE,
+            MULTIPOLYGON_MAYBE_NO_AREA,
+            MULTIPOLYGON_SAME_TAG_ON_OUTER
+    };
 
     protected EditableList sourcesList;
 
@@ -1256,6 +1279,11 @@
     }
 
     @Override
+    protected int[] errorCodes() {
+        return ERROR_CODES;
+    }
+
+    @Override
     public void taggingPresetsModified() {
         try {
             initializeData();
Index: src/org/openstreetmap/josm/data/validation/tests/TurnrestrictionTest.java
===================================================================
--- src/org/openstreetmap/josm/data/validation/tests/TurnrestrictionTest.java	(revision 18524)
+++ src/org/openstreetmap/josm/data/validation/tests/TurnrestrictionTest.java	(working copy)
@@ -44,6 +44,27 @@
     protected static final int UNKNOWN_RESTRICTION = 1817;
     protected static final int TO_CLOSED_WAY = 1818;
     protected static final int FROM_CLOSED_WAY = 1819;
+    private static final int[] ERROR_CODES = {
+            NO_VIA,
+            NO_FROM,
+            NO_TO,
+            MORE_VIA,
+            MORE_FROM,
+            MORE_TO,
+            UNEXPECTED_ROLE,
+            UNEXPECTED_TYPE,
+            FROM_VIA_NODE,
+            TO_VIA_NODE,
+            FROM_VIA_WAY,
+            TO_VIA_WAY,
+            MIX_VIA,
+            UNCONNECTED_VIA,
+            SUPERFLUOUS,
+            FROM_EQUALS_TO,
+            UNKNOWN_RESTRICTION,
+            TO_CLOSED_WAY,
+            FROM_CLOSED_WAY
+    };
 
     private static final List<String> SUPPORTED_RESTRICTIONS = Arrays.asList(
             "no_right_turn", "no_left_turn", "no_u_turn", "no_straight_on",
@@ -303,6 +324,11 @@
         }
     }
 
+    @Override
+    protected int[] errorCodes() {
+        return ERROR_CODES;
+    }
+
     private static boolean isFullOneway(Way w) {
         return w.isOneway() != 0 && !w.hasTag("oneway:bicycle", "no");
     }
Index: src/org/openstreetmap/josm/data/validation/tests/UnclosedWays.java
===================================================================
--- src/org/openstreetmap/josm/data/validation/tests/UnclosedWays.java	(revision 18524)
+++ src/org/openstreetmap/josm/data/validation/tests/UnclosedWays.java	(working copy)
@@ -140,32 +140,63 @@
         }
     }
 
+    private static final int CODE_NATURAL = 1101;
+    private static final int CODE_LANDUSE = 1102;
+    private static final int CODE_AMENITY = 1103;
+    private static final int CODE_SPORT = 1104;
+    private static final int CODE_TOURISM = 1105;
+    private static final int CODE_SHOP = 1106;
+    private static final int CODE_LEISURE = 1107;
+    private static final int CODE_WATERWAY = 1108;
+    private static final int CODE_BOUNDARY = 1109;
+    private static final int CODE_AREA_HIGHWAY = 1110;
+    private static final int CODE_PLACE = 1111;
+    private static final int CODE_BUILDING = 1120;
+    private static final int CODE_AREA = 1130;
+    private static final int CODE_AREA_OTHER = 1131;
+    private static final int[] ERROR_CODES = {
+            CODE_NATURAL,
+            CODE_LANDUSE,
+            CODE_AMENITY,
+            CODE_SPORT,
+            CODE_TOURISM,
+            CODE_SHOP,
+            CODE_LEISURE,
+            CODE_WATERWAY,
+            CODE_BOUNDARY,
+            CODE_AREA_HIGHWAY,
+            CODE_PLACE,
+            CODE_BUILDING,
+            CODE_AREA,
+            CODE_AREA_OTHER
+    };
+
     private static final UnclosedWaysCheck[] checks = {
         // CHECKSTYLE.OFF: SingleSpaceSeparator
         // list contains natural tag allowed on unclosed ways as well as those only allowed on nodes to avoid
         // duplicate warnings
-        new UnclosedWaysCheck(1101, "natural", marktr("natural type {0}"),
+        new UnclosedWaysCheck(CODE_NATURAL, "natural", marktr("natural type {0}"),
             new HashSet<>(Arrays.asList("arete", "bay", "cave", "cliff", "coastline", "earth_bank", "gorge", "gully",
                     "mountain_range", "peak", "ridge", "saddle", "strait", "tree", "tree_row", "valley", "volcano"))),
 
-        new UnclosedWaysCheck(1102, "landuse", marktr("landuse type {0}")),
-        new UnclosedWaysCheck(1103, "amenity", marktr("amenity type {0}"),
+        new UnclosedWaysCheck(CODE_LANDUSE, "landuse", marktr("landuse type {0}")),
+        new UnclosedWaysCheck(CODE_AMENITY, "amenity", marktr("amenity type {0}"),
                 new HashSet<>(Arrays.asList("bench", "bicycle_parking", "weighbridge"))),
-        new UnclosedWaysCheck(1104, "sport",     marktr("sport type {0}"),
+        new UnclosedWaysCheck(CODE_SPORT, "sport",     marktr("sport type {0}"),
                 new HashSet<>(Arrays.asList("water_slide", "climbing", "skiing", "toboggan", "bobsleigh", "karting", "motor", "motocross",
                             "cycling", "running"))),
-        new UnclosedWaysCheck(1105, "tourism",   marktr("tourism type {0}"),
+        new UnclosedWaysCheck(CODE_TOURISM, "tourism",   marktr("tourism type {0}"),
                 new HashSet<>(Arrays.asList("attraction", "artwork"))),
-        new UnclosedWaysCheck(1106, "shop",      marktr("shop type {0}")),
-        new UnclosedWaysCheck(1107, "leisure",   marktr("leisure type {0}"),
+        new UnclosedWaysCheck(CODE_SHOP, "shop",      marktr("shop type {0}")),
+        new UnclosedWaysCheck(CODE_LEISURE, "leisure",   marktr("leisure type {0}"),
                 new HashSet<>(Arrays.asList("track", "slipway", "barefoot"))),
-        new UnclosedWaysCheck(1108, "waterway",  marktr("waterway type {0}"),
-                new HashSet<>(Arrays.asList("riverbank")), false),
-        new UnclosedWaysCheck(1109, "boundary", marktr("boundary type {0}")),
-        new UnclosedWaysCheck(1110, "area:highway", marktr("area:highway type {0}")),
-        new UnclosedWaysCheck(1111, "place", marktr("place type {0}")),
-        new UnclosedWaysBooleanCheck(1120, "building", marktr("building")),
-        new UnclosedWaysBooleanCheck(1130, "area",     marktr("area")),
+        new UnclosedWaysCheck(CODE_WATERWAY, "waterway",  marktr("waterway type {0}"),
+                new HashSet<>(Collections.singleton("riverbank")), false),
+        new UnclosedWaysCheck(CODE_BOUNDARY, "boundary", marktr("boundary type {0}")),
+        new UnclosedWaysCheck(CODE_AREA_HIGHWAY, "area:highway", marktr("area:highway type {0}")),
+        new UnclosedWaysCheck(CODE_PLACE, "place", marktr("place type {0}")),
+        new UnclosedWaysBooleanCheck(CODE_BUILDING, "building", marktr("building")),
+        new UnclosedWaysBooleanCheck(CODE_AREA, "area",     marktr("area")),
         // 1131: Area style way is not closed
         // CHECKSTYLE.ON: SingleSpaceSeparator
     };
@@ -196,7 +227,7 @@
         }
         // code 1131: other area style ways
         if (ElemStyles.hasOnlyAreaElements(w) && !w.getNodes().isEmpty()) {
-            errors.add(TestError.builder(this, Severity.WARNING, 1131)
+            errors.add(TestError.builder(this, Severity.WARNING, CODE_AREA_OTHER)
                     .message(tr("Unclosed way"), marktr("Area style way is not closed"), new Object())
                     .primitives(w)
                     .highlight(Arrays.asList(w.firstNode(), w.lastNode()))
@@ -203,4 +234,9 @@
                     .build());
         }
     }
+
+    @Override
+    protected int[] errorCodes() {
+        return ERROR_CODES;
+    }
 }
Index: src/org/openstreetmap/josm/data/validation/tests/UnconnectedWays.java
===================================================================
--- src/org/openstreetmap/josm/data/validation/tests/UnconnectedWays.java	(revision 18524)
+++ src/org/openstreetmap/josm/data/validation/tests/UnconnectedWays.java	(working copy)
@@ -635,6 +635,11 @@
         }
     }
 
+    @Override
+    protected int[] errorCodes() {
+        return new int[] {this.code};
+    }
+
     private void addNode(Node n, Set<Node> s) {
         boolean m = middlenodes.contains(n);
         boolean e = endnodes.contains(n);
Index: src/org/openstreetmap/josm/data/validation/tests/UntaggedNode.java
===================================================================
--- src/org/openstreetmap/josm/data/validation/tests/UntaggedNode.java	(revision 18524)
+++ src/org/openstreetmap/josm/data/validation/tests/UntaggedNode.java	(working copy)
@@ -29,6 +29,15 @@
     protected static final int UNTAGGED_NODE_WATCH = 205;
     protected static final int UNTAGGED_NODE_SOURCE = 206;
     protected static final int UNTAGGED_NODE_OTHER = 207;
+    private static final int[] ERROR_CODES = {
+            UNTAGGED_NODE_BLANK,
+            UNTAGGED_NODE_FIXME,
+            UNTAGGED_NODE_NOTE,
+            UNTAGGED_NODE_CREATED_BY,
+            UNTAGGED_NODE_WATCH,
+            UNTAGGED_NODE_SOURCE,
+            UNTAGGED_NODE_OTHER
+    };
     protected static final String ERROR_MESSAGE = tr("Unconnected nodes without physical tags");
 
     /**
@@ -121,4 +130,9 @@
         }
         return false;
     }
+
+    @Override
+    protected int[] errorCodes() {
+        return ERROR_CODES;
+    }
 }
Index: src/org/openstreetmap/josm/data/validation/tests/UntaggedWay.java
===================================================================
--- src/org/openstreetmap/josm/data/validation/tests/UntaggedWay.java	(revision 18524)
+++ src/org/openstreetmap/josm/data/validation/tests/UntaggedWay.java	(working copy)
@@ -41,6 +41,15 @@
     protected static final int UNNAMED_JUNCTION = 305;
     /** Untagged, but commented way error */
     protected static final int COMMENTED_WAY    = 306;
+
+    private static final int[] ERROR_CODES = {
+            EMPTY_WAY,
+            UNTAGGED_WAY,
+            UNNAMED_WAY,
+            ONE_NODE_WAY,
+            UNNAMED_JUNCTION,
+            COMMENTED_WAY
+    };
     // CHECKSTYLE.ON: SingleSpaceSeparator
 
     private Set<Way> waysUsedInRelations;
@@ -177,6 +186,11 @@
     }
 
     @Override
+    protected int[] errorCodes() {
+        return ERROR_CODES;
+    }
+
+    @Override
     public Command fixError(TestError testError) {
         return deletePrimitivesIfNeeded(testError.getPrimitives());
     }
Index: src/org/openstreetmap/josm/data/validation/tests/WayConnectedToArea.java
===================================================================
--- src/org/openstreetmap/josm/data/validation/tests/WayConnectedToArea.java	(revision 18524)
+++ src/org/openstreetmap/josm/data/validation/tests/WayConnectedToArea.java	(working copy)
@@ -20,6 +20,7 @@
  * @since 4682
  */
 public class WayConnectedToArea extends Test {
+    private static final int ERROR_WAY_TERMINATES_ON_AREA = 2301;
 
     /**
      * Constructs a new {@code WayConnectedToArea} test.
@@ -50,6 +51,11 @@
         }
     }
 
+    @Override
+    protected int[] errorCodes() {
+        return new int[] {ERROR_WAY_TERMINATES_ON_AREA};
+    }
+
     private void testForError(Way w, Node wayNode, OsmPrimitive p) {
         if (wayNode.isOutsideDownloadArea()
                 || wayNode.getReferrers().stream().anyMatch(p1 -> p1.hasTag("route", "ferry"))) {
@@ -77,7 +83,7 @@
             // Avoid "legal" case (see #17036)
             return;
         }
-        errors.add(TestError.builder(this, Severity.WARNING, 2301)
+        errors.add(TestError.builder(this, Severity.WARNING, ERROR_WAY_TERMINATES_ON_AREA)
                 .message(tr("Way terminates on Area"))
                 .primitives(w, p)
                 .highlight(wayNode)
Index: src/org/openstreetmap/josm/data/validation/tests/WronglyOrderedWays.java
===================================================================
--- src/org/openstreetmap/josm/data/validation/tests/WronglyOrderedWays.java	(revision 18524)
+++ src/org/openstreetmap/josm/data/validation/tests/WronglyOrderedWays.java	(working copy)
@@ -20,6 +20,7 @@
     protected static final int WRONGLY_ORDERED_COAST = 1001;
     protected static final int WRONGLY_ORDERED_LAND  = 1003;
     // CHECKSTYLE.ON: SingleSpaceSeparator
+    private static final int[] ERROR_CODES = {WRONGLY_ORDERED_COAST, WRONGLY_ORDERED_LAND};
 
     /**
      * Constructor
@@ -45,6 +46,11 @@
         }
     }
 
+    @Override
+    protected int[] errorCodes() {
+        return ERROR_CODES;
+    }
+
     private void reportError(Way w, String msg, int type) {
         errors.add(TestError.builder(this, Severity.WARNING, type)
                 .message(msg)
