Repository: graphhopper/graphhopper
Pull request #2935 (2024-01-24 11:33:40)

Automatically add encoded values and tag parsers used in custom models
With this PR we automatically determine the required encoded values and tag parsers, in the correct order, based on the profiles' custom models. Additionally, encoded values and tag parsers can still be added (or configured) via the `graph.encoded_values` configuration option, but this is no longer required for encoded values that are used in the custom models.

(Vehicle)EncodedValuesFactory and (Vehicle)TagParserFactory are all gone and replaced by the new ImportRegistry that maps the strings appearing in the custom models and `graph.encoded_values` to 'import units' which specify the corresponding encoded values and the associated tag parsers.

Closes #2595 where @otbutz already implemented a mechanism to automatically determine the tag parser dependencies.
I took the TopoSorter and the code that parses the encoded values from the custom models from this [branch](https://github.com/graphhopper/graphhopper/tree/create_weighting_legacy).


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 951edb144..065c29f87 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,7 @@
 ### 9.0 [not yet released]
 
+- replaced (Vehicle)EncodedValueFactory and (Vehicle)TagParserFactory with ImportRegistry, #2935
+- encoded values used in custom models are added automatically, no need to add them to graph.encoded_values anymore, #2935
 - removed the ability to sort the graph (graph.do_sort) due to incomplete support, #2919
 - minor changes for import hooks, #2917
 - removed wheelchair vehicle and related parsers, with currently no complete replacement as it needs to be redone properly with a custom model
diff --git a/core/src/main/java/com/graphhopper/GraphHopper.java b/core/src/main/java/com/graphhopper/GraphHopper.java
index 0cdb0afe1..7d77bc2a3 100644
--- a/core/src/main/java/com/graphhopper/GraphHopper.java
+++ b/core/src/main/java/com/graphhopper/GraphHopper.java
@@ -41,7 +41,9 @@ import com.graphhopper.routing.util.*;
 import com.graphhopper.routing.util.countryrules.CountryRuleFactory;
 import com.graphhopper.routing.util.parsers.*;
 import com.graphhopper.routing.weighting.Weighting;
+import com.graphhopper.routing.weighting.custom.CustomModelParser;
 import com.graphhopper.routing.weighting.custom.CustomWeighting;
+import com.graphhopper.routing.weighting.custom.NameValidator;
 import com.graphhopper.storage.*;
 import com.graphhopper.storage.index.LocationIndex;
 import com.graphhopper.storage.index.LocationIndexTree;
@@ -60,6 +62,8 @@ import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.text.DateFormat;
 import java.util.*;
+import java.util.function.BiFunction;
+import java.util.function.Function;
 import java.util.stream.Collectors;
 
 import static com.graphhopper.util.GHUtility.readCountries;
@@ -122,10 +126,7 @@ public class GraphHopper {
     // for data reader
     private String osmFile;
     private ElevationProvider eleProvider = ElevationProvider.NOOP;
-    private VehicleEncodedValuesFactory vehicleEncodedValuesFactory = new DefaultVehicleEncodedValuesFactory();
-    private VehicleTagParserFactory vehicleTagParserFactory = new DefaultVehicleTagParserFactory();
-    private EncodedValueFactory encodedValueFactory = new DefaultEncodedValueFactory();
-    private TagParserFactory tagParserFactory = new DefaultTagParserFactory();
+    private ImportRegistry importRegistry = new DefaultImportRegistry();
     private PathDetailsBuilderFactory pathBuilderFactory = new PathDetailsBuilderFactory();
 
     private String dateRangeParserString = "";
@@ -421,36 +422,13 @@ public class GraphHopper {
         return trMap;
     }
 
-    public GraphHopper setVehicleEncodedValuesFactory(VehicleEncodedValuesFactory factory) {
-        this.vehicleEncodedValuesFactory = factory;
+    public GraphHopper setImportRegistry(ImportRegistry importRegistry) {
+        this.importRegistry = importRegistry;
         return this;
     }
 
-    public EncodedValueFactory getEncodedValueFactory() {
-        return this.encodedValueFactory;
-    }
-
-    public GraphHopper setEncodedValueFactory(EncodedValueFactory factory) {
-        this.encodedValueFactory = factory;
-        return this;
-    }
-
-    public VehicleTagParserFactory getVehicleTagParserFactory() {
-        return this.vehicleTagParserFactory;
-    }
-
-    public GraphHopper setVehicleTagParserFactory(VehicleTagParserFactory factory) {
-        this.vehicleTagParserFactory = factory;
-        return this;
-    }
-
-    public TagParserFactory getTagParserFactory() {
-        return this.tagParserFactory;
-    }
-
-    public GraphHopper setTagParserFactory(TagParserFactory factory) {
-        this.tagParserFactory = factory;
-        return this;
+    public ImportRegistry getImportRegistry() {
+        return importRegistry;
     }
 
     public GraphHopper setCustomAreasDirectory(String customAreasDirectory) {
@@ -621,106 +599,102 @@ public class GraphHopper {
         return this;
     }
 
-    protected EncodingManager buildEncodingManager(Map<String, PMap> vehiclePropsByVehicle, List<String> encodedValueStrings,
-                                                   boolean withUrbanDensity, boolean withMaxSpeedEst, Collection<Profile> profiles) {
+    protected EncodingManager buildEncodingManager(Map<String, PMap> encodedValuesWithProps, Map<String, ImportUnit> activeImportUnits, Map<String, PMap> vehiclePropsByVehicle) {
+        List<EncodedValue> encodedValues = new ArrayList<>(activeImportUnits.entrySet().stream()
+                .map(e -> {
+                    Function<PMap, EncodedValue> f = e.getValue().getCreateEncodedValue();
+                    return f == null ? null : f.apply(encodedValuesWithProps.getOrDefault(e.getKey(), new PMap()));
+                })
+                .filter(Objects::nonNull)
+                .toList());
+        profilesByName.values().forEach(profile -> encodedValues.add(Subnetwork.create(profile.getName())));
+
+        // sort the encoded values, just so it is easier to compare with previous versions...
+        List<String> sortedEVs = new ArrayList<>();
+        vehiclePropsByVehicle.keySet().forEach(vehicle -> {
+            sortedEVs.add(VehicleAccess.key(vehicle));
+            sortedEVs.add(VehicleSpeed.key(vehicle));
+            sortedEVs.add(VehiclePriority.key(vehicle));
+        });
+        profilesByName.keySet().forEach(profile -> {
+            sortedEVs.add(Subnetwork.key(profile));
+        });
+        sortedEVs.add(MaxSpeedEstimated.KEY);
+        sortedEVs.add(UrbanDensity.KEY);
+        sortedEVs.addAll(List.of("max_speed", "road_class", "road_class_link", "road_environment", "road_access", "surface", "smoothness",
+                "hazmat", "hazmat_tunnel", "hazmat_water", "toll", "track_type", "max_weight", "max_width", "max_height", "max_length", "lanes",
+                "hike_rating", "mtb_rating", "horse_rating", "average_slope", "max_slope", "curvature", "bike_network", "mtb_network", "foot_network",
+                "country", "urban_ee", "hgv", "crossing", "roundabout", "ferry_speed", "get_off_bike"));
+        encodedValues.sort(Comparator.comparingInt(ev -> sortedEVs.indexOf(ev.getName())));
+
         EncodingManager.Builder emBuilder = new EncodingManager.Builder();
-        vehiclePropsByVehicle.forEach((name, props) -> emBuilder.add(vehicleEncodedValuesFactory.createVehicleEncodedValues(name, props)));
-        profiles.forEach(profile -> emBuilder.add(Subnetwork.create(profile.getName())));
-        if (withMaxSpeedEst)
-            emBuilder.add(MaxSpeedEstimated.create());
-        if (withUrbanDensity)
-            emBuilder.add(UrbanDensity.create());
-        encodedValueStrings.forEach(s -> emBuilder.add(encodedValueFactory.create(s, new PMap())));
+        encodedValues.forEach(emBuilder::add);
+        vehiclePropsByVehicle.entrySet().stream()
+                .filter(e -> e.getValue().getBool("turn_costs", false))
+                .forEach(e -> emBuilder.addTurnCostEncodedValue(TurnRestriction.create(e.getKey())));
         return emBuilder.build();
     }
 
-    protected OSMParsers buildOSMParsers(Map<String, PMap> vehiclePropsByVehicle, List<String> encodedValueStrings,
+    protected OSMParsers buildOSMParsers(Map<String, PMap> encodedValuesWithProps, Map<String, ImportUnit> activeImportUnits, Map<String, PMap> vehiclesWithProps,
                                          List<String> ignoredHighways, String dateRangeParserString) {
+        ImportUnitSorter sorter = new ImportUnitSorter(activeImportUnits);
+        Map<String, ImportUnit> sortedImportUnits = new LinkedHashMap<>();
+        sorter.sort().forEach(name -> sortedImportUnits.put(name, activeImportUnits.get(name)));
+        DateRangeParser dateRangeParser = DateRangeParser.createInstance(dateRangeParserString);
+        List<TagParser> sortedParsers = new ArrayList<>();
+        sortedImportUnits.forEach((name, importUnit) -> {
+            BiFunction<EncodedValueLookup, PMap, TagParser> createTagParser = importUnit.getCreateTagParser();
+            if (createTagParser != null)
+                sortedParsers.add(createTagParser.apply(encodingManager, encodedValuesWithProps.getOrDefault(name, new PMap().putObject("date_range_parser", dateRangeParser))));
+        });
+
         OSMParsers osmParsers = new OSMParsers();
         ignoredHighways.forEach(osmParsers::addIgnoredHighway);
-        for (String s : encodedValueStrings) {
-            TagParser tagParser = tagParserFactory.create(encodingManager, s, new PMap());
-            if (tagParser != null)
-                osmParsers.addWayTagParser(tagParser);
-        }
-
-        // this needs to be in sync with the default EVs added in EncodingManager.Builder#build. ideally I would like to remove
-        // all these defaults and just use the config as the single source of truth
-        if (!encodedValueStrings.contains(Roundabout.KEY))
-            osmParsers.addWayTagParser(new OSMRoundaboutParser(encodingManager.getBooleanEncodedValue(Roundabout.KEY)));
-        if (!encodedValueStrings.contains(RoadClass.KEY))
-            osmParsers.addWayTagParser(new OSMRoadClassParser(encodingManager.getEnumEncodedValue(RoadClass.KEY, RoadClass.class)));
-        if (!encodedValueStrings.contains(RoadClassLink.KEY))
-            osmParsers.addWayTagParser(new OSMRoadClassLinkParser(encodingManager.getBooleanEncodedValue(RoadClassLink.KEY)));
-        if (!encodedValueStrings.contains(RoadEnvironment.KEY))
-            osmParsers.addWayTagParser(new OSMRoadEnvironmentParser(encodingManager.getEnumEncodedValue(RoadEnvironment.KEY, RoadEnvironment.class)));
-        if (!encodedValueStrings.contains(MaxSpeed.KEY))
-            osmParsers.addWayTagParser(new OSMMaxSpeedParser(encodingManager.getDecimalEncodedValue(MaxSpeed.KEY)));
-        if (!encodedValueStrings.contains(RoadAccess.KEY))
-            osmParsers.addWayTagParser(new OSMRoadAccessParser(encodingManager.getEnumEncodedValue(RoadAccess.KEY, RoadAccess.class), OSMRoadAccessParser.toOSMRestrictions(TransportationMode.CAR)));
-        if (!encodedValueStrings.contains(FerrySpeed.KEY))
-            osmParsers.addWayTagParser(new FerrySpeedCalculator(encodingManager.getDecimalEncodedValue(FerrySpeed.KEY)));
-        if (encodingManager.hasEncodedValue(AverageSlope.KEY) || encodingManager.hasEncodedValue(MaxSlope.KEY)) {
-            if (!encodingManager.hasEncodedValue(AverageSlope.KEY) || !encodingManager.hasEncodedValue(MaxSlope.KEY))
-                throw new IllegalArgumentException("Enable both, average_slope and max_slope");
-            osmParsers.addWayTagParser(new SlopeCalculator(encodingManager.getDecimalEncodedValue(MaxSlope.KEY),
-                    encodingManager.getDecimalEncodedValue(AverageSlope.KEY)));
-        }
+        sortedParsers.forEach(osmParsers::addWayTagParser);
 
         if (maxSpeedCalculator != null) {
             maxSpeedCalculator.checkEncodedValues(encodingManager);
             osmParsers.addWayTagParser(maxSpeedCalculator.getParser());
         }
 
-        if (encodingManager.hasEncodedValue(Curvature.KEY))
-            osmParsers.addWayTagParser(new CurvatureCalculator(encodingManager.getDecimalEncodedValue(Curvature.KEY)));
-
-        DateRangeParser dateRangeParser = DateRangeParser.createInstance(dateRangeParserString);
-        Set<String> added = new HashSet<>();
-        vehiclePropsByVehicle.forEach((name, props) -> {
-            VehicleTagParsers vehicleTagParsers = vehicleTagParserFactory.createParsers(encodingManager, name,
-                    new PMap(props).putObject("date_range_parser", dateRangeParser));
-            if (vehicleTagParsers == null)
-                return;
-            vehicleTagParsers.getTagParsers().forEach(tagParser -> {
-                if (tagParser == null) return;
-                if (tagParser instanceof BikeCommonAccessParser) {
-                    if (encodingManager.hasEncodedValue(BikeNetwork.KEY) && added.add(BikeNetwork.KEY))
-                        osmParsers.addRelationTagParser(relConfig -> new OSMBikeNetworkTagParser(encodingManager.getEnumEncodedValue(BikeNetwork.KEY, RouteNetwork.class), relConfig));
-                    if (encodingManager.hasEncodedValue(MtbNetwork.KEY) && added.add(MtbNetwork.KEY))
-                        osmParsers.addRelationTagParser(relConfig -> new OSMMtbNetworkTagParser(encodingManager.getEnumEncodedValue(MtbNetwork.KEY, RouteNetwork.class), relConfig));
-                    if (encodingManager.hasEncodedValue(Smoothness.KEY) && added.add(Smoothness.KEY))
-                        osmParsers.addWayTagParser(new OSMSmoothnessParser(encodingManager.getEnumEncodedValue(Smoothness.KEY, Smoothness.class)));
-                } else if (tagParser instanceof FootAccessParser) {
-                    if (encodingManager.hasEncodedValue(FootNetwork.KEY) && added.add(FootNetwork.KEY))
-                        osmParsers.addRelationTagParser(relConfig -> new OSMFootNetworkTagParser(encodingManager.getEnumEncodedValue(FootNetwork.KEY, RouteNetwork.class), relConfig));
-                }
-                String turnRestrictionKey = TurnRestriction.key(name);
-                if (encodingManager.hasTurnEncodedValue(turnRestrictionKey)
-                        // need to make sure we do not add the same restriction parsers multiple times
-                        && osmParsers.getRestrictionTagParsers().stream().noneMatch(r -> r.getTurnRestrictionEnc().getName().equals(turnRestrictionKey))) {
-                    List<String> restrictions = tagParser instanceof AbstractAccessParser
-                            ? ((AbstractAccessParser) tagParser).getRestrictions()
-                            : OSMRoadAccessParser.toOSMRestrictions(TransportationMode.valueOf(props.getString("transportation_mode", "VEHICLE")));
-                    osmParsers.addRestrictionTagParser(new RestrictionTagParser(restrictions, encodingManager.getTurnBooleanEncodedValue(turnRestrictionKey)));
-                }
-            });
-            vehicleTagParsers.getTagParsers().forEach(tagParser -> {
-                if (tagParser == null) return;
-                osmParsers.addWayTagParser(tagParser);
-
-                if (tagParser instanceof BikeCommonAccessParser && encodingManager.hasEncodedValue(GetOffBike.KEY) && added.add(GetOffBike.KEY))
-                    osmParsers.addWayTagParser(new OSMGetOffBikeParser(encodingManager.getBooleanEncodedValue(GetOffBike.KEY), ((BikeCommonAccessParser) tagParser).getAccessEnc()));
-            });
-        });
+        // todo: no real need to make this dependent on 'vehicles', but keep it as it used to be for now
+        final boolean hasBike = vehiclesWithProps.containsKey("bike") || vehiclesWithProps.containsKey("mtb") || vehiclesWithProps.containsKey("racingbike");
+        final boolean hasFoot = vehiclesWithProps.containsKey("foot");
+        if (hasBike && encodingManager.hasEncodedValue(BikeNetwork.KEY))
+            osmParsers.addRelationTagParser(relConfig -> new OSMBikeNetworkTagParser(encodingManager.getEnumEncodedValue(BikeNetwork.KEY, RouteNetwork.class), relConfig));
+        if (hasBike && encodingManager.hasEncodedValue(MtbNetwork.KEY))
+            osmParsers.addRelationTagParser(relConfig -> new OSMMtbNetworkTagParser(encodingManager.getEnumEncodedValue(MtbNetwork.KEY, RouteNetwork.class), relConfig));
+        if (hasFoot && encodingManager.hasEncodedValue(FootNetwork.KEY))
+            osmParsers.addRelationTagParser(relConfig -> new OSMFootNetworkTagParser(encodingManager.getEnumEncodedValue(FootNetwork.KEY, RouteNetwork.class), relConfig));
+
+        vehiclesWithProps.entrySet().stream()
+                .filter(e -> e.getValue().getBool("turn_costs", false))
+                .forEach(e -> {
+                    List<String> osmRestrictions = getTurnRestrictionsForVehicle(e.getKey(), e.getValue());
+                    osmParsers.addRestrictionTagParser(new RestrictionTagParser(
+                            osmRestrictions, encodingManager.getTurnBooleanEncodedValue(TurnRestriction.key(e.getKey()))));
+                });
         return osmParsers;
     }
 
-    public static List<String> getEncodedValueStrings(String encodedValuesStr) {
-        return Arrays.stream(encodedValuesStr.split(","))
-                .map(String::trim)
-                .filter(s -> !s.isEmpty())
-                .collect(Collectors.toList());
+    protected List<String> getTurnRestrictionsForVehicle(String vehicle, PMap props) {
+        return switch (vehicle) {
+            case "car" -> OSMRoadAccessParser.toOSMRestrictions(TransportationMode.CAR);
+            case "bike", "mtb", "racingbike" ->
+                    OSMRoadAccessParser.toOSMRestrictions(TransportationMode.BIKE);
+            case "foot" -> OSMRoadAccessParser.toOSMRestrictions(TransportationMode.FOOT);
+            case "roads" ->
+                    OSMRoadAccessParser.toOSMRestrictions(TransportationMode.valueOf(props.getString("transportation_mode", "VEHICLE")));
+            default -> throw new IllegalArgumentException("Unknown vehicle: " + vehicle);
+        };
+    }
+
+    public static Map<String, PMap> parseEncodedValueString(String encodedValuesStr) {
+        Map<String, PMap> encodedValuesWithProps = new LinkedHashMap<>();
+        Arrays.stream(encodedValuesStr.split(","))
+                .filter(evStr -> !evStr.isBlank())
+                .forEach(evStr -> encodedValuesWithProps.put(evStr.trim().split("\\|")[0], new PMap(evStr)));
+        return encodedValuesWithProps;
     }
 
     public static Map<String, PMap> getVehiclePropsByVehicle(String vehiclesStr, Collection<Profile> profiles) {
@@ -863,15 +837,11 @@ public class GraphHopper {
      * Creates the graph from OSM data.
      */
     protected void process(boolean closeEarly) {
+        prepareImport();
+        if (encodingManager == null)
+            throw new IllegalStateException("The EncodingManager must be created in `prepareImport()`");
         GHDirectory directory = new GHDirectory(ghLocation, dataAccessDefaultType);
         directory.configure(dataAccessConfig);
-        boolean withUrbanDensity = urbanDensityCalculationThreads > 0;
-        boolean withMaxSpeedEstimation = maxSpeedCalculator != null;
-        Map<String, PMap> vehiclePropsByVehicle = getVehiclePropsByVehicle(vehiclesString, profilesByName.values());
-        List<String> encodedValueStrings = getEncodedValueStrings(encodedValuesString);
-        encodingManager = buildEncodingManager(vehiclePropsByVehicle, encodedValueStrings, withUrbanDensity,
-                withMaxSpeedEstimation, profilesByName.values());
-        osmParsers = buildOSMParsers(vehiclePropsByVehicle, encodedValueStrings, osmReaderConfig.getIgnoredHighways(), dateRangeParserString);
         baseGraph = new BaseGraph.Builder(getEncodingManager())
                 .setDir(directory)
                 .set3D(hasElevation())
@@ -906,13 +876,82 @@ public class GraphHopper {
         }
     }
 
+    protected void prepareImport() {
+        Map<String, PMap> encodedValuesWithProps = parseEncodedValueString(encodedValuesString);
+        NameValidator nameValidator = s -> importRegistry.createImportUnit(s) != null;
+        profilesByName.values().
+                forEach(profile -> CustomModelParser.findVariablesForEncodedValuesString(profile.getCustomModel(), nameValidator, encodingManager).
+                        forEach(var -> encodedValuesWithProps.putIfAbsent(var, new PMap())));
+
+        // these are used in the snap prevention filter (avoid motorway, tunnel, etc.) so they have to be there
+        encodedValuesWithProps.putIfAbsent(RoadClass.KEY, new PMap());
+        encodedValuesWithProps.putIfAbsent(RoadEnvironment.KEY, new PMap());
+        // used by instructions...
+        encodedValuesWithProps.putIfAbsent(Roundabout.KEY, new PMap());
+        encodedValuesWithProps.putIfAbsent(RoadClassLink.KEY, new PMap());
+        encodedValuesWithProps.putIfAbsent(MaxSpeed.KEY, new PMap());
+
+        // we still need these as long as we use vehicle in profiles instead of explicit xyz_access/average_speed/priority
+        Map<String, PMap> vehiclesWithProps = getVehiclePropsByVehicle(vehiclesString, profilesByName.values());
+        vehiclesWithProps.forEach((vehicle, props) -> addEncodedValuesWithPropsForVehicle(vehicle, props, encodedValuesWithProps));
+
+        if (urbanDensityCalculationThreads > 0)
+            encodedValuesWithProps.put(UrbanDensity.KEY, new PMap());
+        if (maxSpeedCalculator != null)
+            encodedValuesWithProps.put(MaxSpeedEstimated.KEY, new PMap());
+
+        Map<String, ImportUnit> activeImportUnits = new LinkedHashMap<>();
+        ArrayDeque<String> deque = new ArrayDeque<>(encodedValuesWithProps.keySet());
+        while (!deque.isEmpty()) {
+            String ev = deque.removeFirst();
+            ImportUnit importUnit = importRegistry.createImportUnit(ev);
+            if (importUnit == null)
+                throw new IllegalArgumentException("Unknown encoded value: " + ev);
+            if (activeImportUnits.put(ev, importUnit) == null)
+                deque.addAll(importUnit.getRequiredImportUnits());
+        }
+        encodingManager = buildEncodingManager(encodedValuesWithProps, activeImportUnits, vehiclesWithProps);
+        osmParsers = buildOSMParsers(encodedValuesWithProps, activeImportUnits, vehiclesWithProps, osmReaderConfig.getIgnoredHighways(), dateRangeParserString);
+    }
+
+    protected void addEncodedValuesWithPropsForVehicle(String vehicle, PMap props, Map<String, PMap> encodedValuesWithProps) {
+        if (List.of("car", "roads").contains(vehicle)) {
+            encodedValuesWithProps.merge(VehicleAccess.key(vehicle), props, PMap::putAll);
+            encodedValuesWithProps.merge(VehicleSpeed.key(vehicle), props, PMap::putAll);
+        } else if (List.of("bike", "racingbike", "mtb").contains(vehicle)) {
+            encodedValuesWithProps.merge(VehicleAccess.key(vehicle), props, PMap::putAll);
+            encodedValuesWithProps.merge(VehicleSpeed.key(vehicle), props, PMap::putAll);
+            encodedValuesWithProps.merge(VehiclePriority.key(vehicle), props, PMap::putAll);
+            encodedValuesWithProps.merge(BikeNetwork.KEY, props, PMap::putAll);
+            encodedValuesWithProps.merge(MtbNetwork.KEY, props, PMap::putAll);
+            encodedValuesWithProps.merge(GetOffBike.KEY, props, PMap::putAll);
+            encodedValuesWithProps.merge(Smoothness.KEY, props, PMap::putAll);
+        } else if ("foot".equals(vehicle)) {
+            encodedValuesWithProps.merge(VehicleAccess.key(vehicle), props, PMap::putAll);
+            encodedValuesWithProps.merge(VehicleSpeed.key(vehicle), props, PMap::putAll);
+            encodedValuesWithProps.merge(VehiclePriority.key(vehicle), props, PMap::putAll);
+            encodedValuesWithProps.merge(FootNetwork.KEY, props, PMap::putAll);
+        } else if ("car4wd".equals(vehicle)) {
+            throw new IllegalArgumentException("Vehicle 'car4wd' is no longer supported, use custom_models/car4wd.json instead. See #2651");
+        } else if ("bike2".equals(vehicle)) {
+            throw new IllegalArgumentException("Vehicle 'bike2' is no longer supported, use custom_models/bike.json instead. See #2668");
+        } else if ("hike".equals(vehicle)) {
+            throw new IllegalArgumentException("Vehicle 'hike' is no longer supported, use custom_models/hike.json instead. See #2759");
+        } else if ("motorcycle".equals(vehicle)) {
+            throw new IllegalArgumentException("Vehicle 'motorcycle' is no longer supported, use custom_models/motorcycle.json instead. See #2781");
+        } else if ("wheelchair".equals(vehicle)) {
+            throw new IllegalArgumentException("Vehicle 'wheelchair' is no longer supported. See #2900");
+        } else {
+            throw new IllegalArgumentException("Unknown vehicle: '" + vehicle + "'");
+        }
+    }
+
     protected void postImportOSM() {
         // Important note: To deal with via-way turn restrictions we introduce artificial edges in OSMReader (#2689).
         // These are simply copies of real edges. Any further modifications of the graph edges must take care of keeping
         // the artificial edges in sync with their real counterparts. So if an edge attribute shall be changed this change
         // must also be applied to the corresponding artificial edge.
 
-
         calculateUrbanDensity();
 
         if (maxSpeedCalculator != null) {
diff --git a/core/src/main/java/com/graphhopper/routing/DefaultWeightingFactory.java b/core/src/main/java/com/graphhopper/routing/DefaultWeightingFactory.java
index accbf5653..a95333eb6 100644
--- a/core/src/main/java/com/graphhopper/routing/DefaultWeightingFactory.java
+++ b/core/src/main/java/com/graphhopper/routing/DefaultWeightingFactory.java
@@ -21,7 +21,6 @@ package com.graphhopper.routing;
 import com.graphhopper.config.Profile;
 import com.graphhopper.routing.ev.*;
 import com.graphhopper.routing.util.EncodingManager;
-import com.graphhopper.routing.util.VehicleEncodedValues;
 import com.graphhopper.routing.weighting.DefaultTurnCostProvider;
 import com.graphhopper.routing.weighting.TurnCostProvider;
 import com.graphhopper.routing.weighting.Weighting;
@@ -101,7 +100,4 @@ public class DefaultWeightingFactory implements WeightingFactory {
         return weighting;
     }
 
-    public boolean isOutdoorVehicle(String name) {
-        return VehicleEncodedValues.OUTDOOR_VEHICLES.contains(name);
-    }
 }
diff --git a/core/src/main/java/com/graphhopper/routing/ev/DefaultEncodedValueFactory.java b/core/src/main/java/com/graphhopper/routing/ev/DefaultEncodedValueFactory.java
deleted file mode 100644
index 4d58a172f..000000000
--- a/core/src/main/java/com/graphhopper/routing/ev/DefaultEncodedValueFactory.java
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- *  Licensed to GraphHopper GmbH under one or more contributor
- *  license agreements. See the NOTICE file distributed with this work for
- *  additional information regarding copyright ownership.
- *
- *  GraphHopper GmbH licenses this file to you 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 com.graphhopper.routing.ev;
-
-import com.graphhopper.util.PMap;
-
-public class DefaultEncodedValueFactory implements EncodedValueFactory {
-
-    @Override
-    public EncodedValue create(String name, PMap properties) {
-        if (Roundabout.KEY.equals(name)) {
-            return Roundabout.create();
-        } else if (GetOffBike.KEY.equals(name)) {
-            return GetOffBike.create();
-        } else if (RoadClass.KEY.equals(name)) {
-            return RoadClass.create();
-        } else if (RoadClassLink.KEY.equals(name)) {
-            return RoadClassLink.create();
-        } else if (RoadEnvironment.KEY.equals(name)) {
-            return RoadEnvironment.create();
-        } else if (RoadAccess.KEY.equals(name)) {
-            return RoadAccess.create();
-        } else if (MaxSpeed.KEY.equals(name)) {
-            return MaxSpeed.create();
-        } else if (MaxSpeedEstimated.KEY.equals(name)) {
-            return MaxSpeedEstimated.create();
-        } else if (MaxWeight.KEY.equals(name)) {
-            return MaxWeight.create();
-        } else if (MaxWeightExcept.KEY.equals(name)) {
-            return MaxWeightExcept.create();
-        } else if (MaxHeight.KEY.equals(name)) {
-            return MaxHeight.create();
-        } else if (MaxWidth.KEY.equals(name)) {
-            return MaxWidth.create();
-        } else if (MaxAxleLoad.KEY.equals(name)) {
-            return MaxAxleLoad.create();
-        } else if (MaxLength.KEY.equals(name)) {
-            return MaxLength.create();
-        } else if (Hgv.KEY.equals(name)) {
-            return Hgv.create();
-        } else if (Surface.KEY.equals(name)) {
-            return Surface.create();
-        } else if (Smoothness.KEY.equals(name)) {
-            return Smoothness.create();
-        } else if (Toll.KEY.equals(name)) {
-            return Toll.create();
-        } else if (TrackType.KEY.equals(name)) {
-            return TrackType.create();
-        } else if (BikeNetwork.KEY.equals(name) || MtbNetwork.KEY.equals(name) || FootNetwork.KEY.equals(name)) {
-            return RouteNetwork.create(name);
-        } else if (Hazmat.KEY.equals(name)) {
-            return Hazmat.create();
-        } else if (HazmatTunnel.KEY.equals(name)) {
-            return HazmatTunnel.create();
-        } else if (HazmatWater.KEY.equals(name)) {
-            return HazmatWater.create();
-        } else if (Lanes.KEY.equals(name)) {
-            return Lanes.create();
-        } else if (Footway.KEY.equals(name)) {
-            return Footway.create();
-        } else if (OSMWayID.KEY.equals(name)) {
-            return OSMWayID.create();
-        } else if (MtbRating.KEY.equals(name)) {
-            return MtbRating.create();
-        } else if (HikeRating.KEY.equals(name)) {
-            return HikeRating.create();
-        } else if (HorseRating.KEY.equals(name)) {
-            return HorseRating.create();
-        } else if (Country.KEY.equals(name)) {
-            return Country.create();
-        } else if (State.KEY.equals(name)) {
-            return State.create();
-        } else if (name.endsWith(Subnetwork.key(""))) {
-            return Subnetwork.create(name);
-        } else if (MaxSlope.KEY.equals(name)) {
-            return MaxSlope.create();
-        } else if (AverageSlope.KEY.equals(name)) {
-            return AverageSlope.create();
-        } else if (Curvature.KEY.equals(name)) {
-            return Curvature.create();
-        } else if (Crossing.KEY.equals(name)) {
-            return Crossing.create();
-        } else if (FerrySpeed.KEY.equals(name)) {
-            return FerrySpeed.create();
-        } else if (BusAccess.KEY.equals(name)) {
-            return BusAccess.create();
-        } else {
-            throw new IllegalArgumentException("DefaultEncodedValueFactory cannot find EncodedValue " + name);
-        }
-    }
-}
diff --git a/core/src/main/java/com/graphhopper/routing/ev/DefaultImportRegistry.java b/core/src/main/java/com/graphhopper/routing/ev/DefaultImportRegistry.java
new file mode 100644
index 000000000..72728c12b
--- /dev/null
+++ b/core/src/main/java/com/graphhopper/routing/ev/DefaultImportRegistry.java
@@ -0,0 +1,299 @@
+/*
+ *  Licensed to GraphHopper GmbH under one or more contributor
+ *  license agreements. See the NOTICE file distributed with this work for
+ *  additional information regarding copyright ownership.
+ *
+ *  GraphHopper GmbH licenses this file to you 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 com.graphhopper.routing.ev;
+
+import com.graphhopper.reader.osm.conditional.DateRangeParser;
+import com.graphhopper.routing.util.*;
+import com.graphhopper.routing.util.parsers.*;
+
+public class DefaultImportRegistry implements ImportRegistry {
+    @Override
+    public ImportUnit createImportUnit(String name) {
+        if (Roundabout.KEY.equals(name))
+            return ImportUnit.create(name, props -> Roundabout.create(),
+                    (lookup, props) -> new OSMRoundaboutParser(
+                            lookup.getBooleanEncodedValue(Roundabout.KEY))
+            );
+        else if (GetOffBike.KEY.equals(name))
+            return ImportUnit.create(name, props -> GetOffBike.create(),
+                    (lookup, pros) -> new OSMGetOffBikeParser(
+                            lookup.getBooleanEncodedValue(GetOffBike.KEY),
+                            lookup.getBooleanEncodedValue("bike_access")
+                    ), "bike_access");
+        else if (RoadClass.KEY.equals(name))
+            return ImportUnit.create(name, props -> RoadClass.create(),
+                    (lookup, props) -> new OSMRoadClassParser(
+                            lookup.getEnumEncodedValue(RoadClass.KEY, RoadClass.class))
+            );
+        else if (RoadClassLink.KEY.equals(name))
+            return ImportUnit.create(name, props -> RoadClassLink.create(),
+                    (lookup, props) -> new OSMRoadClassLinkParser(
+                            lookup.getBooleanEncodedValue(RoadClassLink.KEY))
+            );
+        else if (RoadEnvironment.KEY.equals(name))
+            return ImportUnit.create(name, props -> RoadEnvironment.create(),
+                    (lookup, props) -> new OSMRoadEnvironmentParser(
+                            lookup.getEnumEncodedValue(RoadEnvironment.KEY, RoadEnvironment.class))
+            );
+        else if (RoadAccess.KEY.equals(name))
+            return ImportUnit.create(name, props -> RoadAccess.create(),
+                    (lookup, props) -> new OSMRoadAccessParser(
+                            lookup.getEnumEncodedValue(RoadAccess.KEY, RoadAccess.class),
+                            OSMRoadAccessParser.toOSMRestrictions(TransportationMode.CAR))
+            );
+        else if (MaxSpeed.KEY.equals(name))
+            return ImportUnit.create(name, props -> MaxSpeed.create(),
+                    (lookup, props) -> new OSMMaxSpeedParser(
+                            lookup.getDecimalEncodedValue(MaxSpeed.KEY))
+            );
+        else if (MaxSpeedEstimated.KEY.equals(name))
+            return ImportUnit.create(name, props -> MaxSpeedEstimated.create(),
+                    null, Country.KEY, UrbanDensity.KEY);
+        else if (UrbanDensity.KEY.equals(name))
+            return ImportUnit.create(name, props -> UrbanDensity.create(),
+                    null);
+        else if (MaxWeight.KEY.equals(name))
+            return ImportUnit.create(name, props -> MaxWeight.create(),
+                    (lookup, props) -> new OSMMaxWeightParser(
+                            lookup.getDecimalEncodedValue(MaxWeight.KEY))
+            );
+        else if (MaxWeightExcept.KEY.equals(name))
+            return ImportUnit.create(name, props -> MaxWeightExcept.create(),
+                    (lookup, props) -> new MaxWeightExceptParser(
+                            lookup.getEnumEncodedValue(MaxWeightExcept.KEY, MaxWeightExcept.class))
+            );
+        else if (MaxHeight.KEY.equals(name))
+            return ImportUnit.create(name, props -> MaxHeight.create(),
+                    (lookup, props) -> new OSMMaxHeightParser(
+                            lookup.getDecimalEncodedValue(MaxHeight.KEY))
+            );
+        else if (MaxWidth.KEY.equals(name))
+            return ImportUnit.create(name, props -> MaxWidth.create(),
+                    (lookup, props) -> new OSMMaxWidthParser(
+                            lookup.getDecimalEncodedValue(MaxWidth.KEY))
+            );
+        else if (MaxAxleLoad.KEY.equals(name))
+            return ImportUnit.create(name, props -> MaxAxleLoad.create(),
+                    (lookup, props) -> new OSMMaxAxleLoadParser(
+                            lookup.getDecimalEncodedValue(MaxAxleLoad.KEY))
+            );
+        else if (MaxLength.KEY.equals(name))
+            return ImportUnit.create(name, props -> MaxLength.create(),
+                    (lookup, props) -> new OSMMaxLengthParser(
+                            lookup.getDecimalEncodedValue(MaxLength.KEY))
+            );
+        else if (Surface.KEY.equals(name))
+            return ImportUnit.create(name, props -> Surface.create(),
+                    (lookup, props) -> new OSMSurfaceParser(
+                            lookup.getEnumEncodedValue(Surface.KEY, Surface.class))
+            );
+        else if (Smoothness.KEY.equals(name))
+            return ImportUnit.create(name, props -> Smoothness.create(),
+                    (lookup, props) -> new OSMSmoothnessParser(
+                            lookup.getEnumEncodedValue(Smoothness.KEY, Smoothness.class))
+            );
+        else if (Hgv.KEY.equals(name))
+            return ImportUnit.create(name, props -> Hgv.create(),
+                    (lookup, props) -> new OSMHgvParser(
+                            lookup.getEnumEncodedValue(Hgv.KEY, Hgv.class)
+                    ));
+        else if (Toll.KEY.equals(name))
+            return ImportUnit.create(name, props -> Toll.create(),
+                    (lookup, props) -> new OSMTollParser(
+                            lookup.getEnumEncodedValue(Toll.KEY, Toll.class))
+            );
+        else if (TrackType.KEY.equals(name))
+            return ImportUnit.create(name, props -> TrackType.create(),
+                    (lookup, props) -> new OSMTrackTypeParser(
+                            lookup.getEnumEncodedValue(TrackType.KEY, TrackType.class))
+            );
+        else if (Hazmat.KEY.equals(name))
+            return ImportUnit.create(name, props -> Hazmat.create(),
+                    (lookup, props) -> new OSMHazmatParser(
+                            lookup.getEnumEncodedValue(Hazmat.KEY, Hazmat.class))
+            );
+        else if (HazmatTunnel.KEY.equals(name))
+            return ImportUnit.create(name, props -> HazmatTunnel.create(),
+                    (lookup, props) -> new OSMHazmatTunnelParser(
+                            lookup.getEnumEncodedValue(HazmatTunnel.KEY, HazmatTunnel.class))
+            );
+        else if (HazmatWater.KEY.equals(name))
+            return ImportUnit.create(name, props -> HazmatWater.create(),
+                    (lookup, props) -> new OSMHazmatWaterParser(
+                            lookup.getEnumEncodedValue(HazmatWater.KEY, HazmatWater.class))
+            );
+        else if (Lanes.KEY.equals(name))
+            return ImportUnit.create(name, props -> Lanes.create(),
+                    (lookup, props) -> new OSMLanesParser(
+                            lookup.getIntEncodedValue(Lanes.KEY))
+            );
+        else if (Footway.KEY.equals(name))
+            return ImportUnit.create(name, props -> Footway.create(),
+                    (lookup, props) -> new OSMFootwayParser(
+                            lookup.getEnumEncodedValue(Footway.KEY, Footway.class))
+            );
+        else if (OSMWayID.KEY.equals(name))
+            return ImportUnit.create(name, props -> OSMWayID.create(),
+                    (lookup, props) -> new OSMWayIDParser(
+                            lookup.getIntEncodedValue(OSMWayID.KEY))
+            );
+        else if (MtbRating.KEY.equals(name))
+            return ImportUnit.create(name, props -> MtbRating.create(),
+                    (lookup, props) -> new OSMMtbRatingParser(
+                            lookup.getIntEncodedValue(MtbRating.KEY))
+            );
+        else if (HikeRating.KEY.equals(name))
+            return ImportUnit.create(name, props -> HikeRating.create(),
+                    (lookup, props) -> new OSMHikeRatingParser(
+                            lookup.getIntEncodedValue(HikeRating.KEY))
+            );
+        else if (HorseRating.KEY.equals(name))
+            return ImportUnit.create(name, props -> HorseRating.create(),
+                    (lookup, props) -> new OSMHorseRatingParser(
+                            lookup.getIntEncodedValue(HorseRating.KEY))
+            );
+        else if (Country.KEY.equals(name))
+            return ImportUnit.create(name, props -> Country.create(),
+                    (lookup, props) -> new CountryParser(
+                            lookup.getEnumEncodedValue(Country.KEY, Country.class))
+            );
+        else if (State.KEY.equals(name))
+            return ImportUnit.create(name, props -> State.create(),
+                    (lookup, props) -> new StateParser(
+                            lookup.getEnumEncodedValue(State.KEY, State.class))
+            );
+        else if (Crossing.KEY.equals(name))
+            return ImportUnit.create(name, props -> Crossing.create(),
+                    (lookup, props) -> new OSMCrossingParser(
+                            lookup.getEnumEncodedValue(Crossing.KEY, Crossing.class))
+            );
+        else if (FerrySpeed.KEY.equals(name))
+            return ImportUnit.create(name, props -> FerrySpeed.create(),
+                    (lookup, props) -> new FerrySpeedCalculator(
+                            lookup.getDecimalEncodedValue(FerrySpeed.KEY)));
+        else if (Curvature.KEY.equals(name))
+            return ImportUnit.create(name, props -> Curvature.create(),
+                    (lookup, props) -> new CurvatureCalculator(
+                            lookup.getDecimalEncodedValue(Curvature.KEY))
+            );
+        else if (AverageSlope.KEY.equals(name))
+            return ImportUnit.create(name, props -> AverageSlope.create(), null, "slope_calculator");
+        else if (MaxSlope.KEY.equals(name))
+            return ImportUnit.create(name, props -> MaxSlope.create(), null, "slope_calculator");
+        else if ("slope_calculator".equals(name))
+            return ImportUnit.create(name, null,
+                    (lookup, props) -> new SlopeCalculator(
+                            lookup.hasEncodedValue(MaxSlope.KEY) ? lookup.getDecimalEncodedValue(MaxSlope.KEY) : null,
+                            lookup.hasEncodedValue(AverageSlope.KEY) ? lookup.getDecimalEncodedValue(AverageSlope.KEY) : null
+                    ));
+        else if (BikeNetwork.KEY.equals(name) || MtbNetwork.KEY.equals(name) || FootNetwork.KEY.equals(name))
+            return ImportUnit.create(name, props -> RouteNetwork.create(name), null);
+
+        else if (BusAccess.KEY.equals(name))
+            return ImportUnit.create(name, props -> BusAccess.create(),
+                    (lookup, props) -> new ModeAccessParser(TransportationMode.BUS, lookup.getBooleanEncodedValue(BusAccess.KEY), lookup.getBooleanEncodedValue(Roundabout.KEY)),
+                    "roundabout"
+            );
+
+        else if (VehicleAccess.key("car").equals(name))
+            return ImportUnit.create(name, props -> VehicleAccess.create("car"),
+                    (lookup, props) -> new CarAccessParser(lookup, props).init(props.getObject("date_range_parser", new DateRangeParser())),
+                    "roundabout"
+            );
+        else if (VehicleAccess.key("roads").equals(name))
+            return ImportUnit.create(name, props -> VehicleAccess.create("roads"),
+                    (lookup, props) -> new RoadsAccessParser(lookup)
+            );
+        else if (VehicleAccess.key("bike").equals(name))
+            return ImportUnit.create(name, props -> VehicleAccess.create("bike"),
+                    (lookup, props) -> new BikeAccessParser(lookup, props).init(props.getObject("date_range_parser", new DateRangeParser())),
+                    "roundabout"
+            );
+        else if (VehicleAccess.key("racingbike").equals(name))
+            return ImportUnit.create(name, props -> VehicleAccess.create("racingbike"),
+                    (lookup, props) -> new RacingBikeAccessParser(lookup, props).init(props.getObject("date_range_parser", new DateRangeParser())),
+                    "roundabout"
+            );
+        else if (VehicleAccess.key("mtb").equals(name))
+            return ImportUnit.create(name, props -> VehicleAccess.create("mtb"),
+                    (lookup, props) -> new MountainBikeAccessParser(lookup, props).init(props.getObject("date_range_parser", new DateRangeParser())),
+                    "roundabout"
+            );
+        else if (VehicleAccess.key("foot").equals(name))
+            return ImportUnit.create(name, props -> VehicleAccess.create("foot"),
+                    (lookup, props) -> new FootAccessParser(lookup, props).init(props.getObject("date_range_parser", new DateRangeParser())));
+
+        else if (VehicleSpeed.key("car").equals(name))
+            return ImportUnit.create(name, props -> new DecimalEncodedValueImpl(
+                            name, props.getInt("speed_bits", 7), props.getDouble("speed_factor", 2), true),
+                    (lookup, props) -> new CarAverageSpeedParser(lookup),
+                    "ferry_speed"
+            );
+        else if (VehicleSpeed.key("roads").equals(name))
+            return ImportUnit.create(name, props -> new DecimalEncodedValueImpl(
+                            name, props.getInt("speed_bits", 7), props.getDouble("speed_factor", 2), true),
+                    (lookup, props) -> new RoadsAverageSpeedParser(lookup)
+            );
+        else if (VehicleSpeed.key("bike").equals(name))
+            return ImportUnit.create(name, props -> new DecimalEncodedValueImpl(
+                            name, props.getInt("speed_bits", 4), props.getDouble("speed_factor", 2), false),
+                    (lookup, props) -> new BikeAverageSpeedParser(lookup),
+                    "ferry_speed", "smoothness"
+            );
+        else if (VehicleSpeed.key("racingbike").equals(name))
+            return ImportUnit.create(name, props -> new DecimalEncodedValueImpl(
+                            name, props.getInt("speed_bits", 4), props.getDouble("speed_factor", 2), false),
+                    (lookup, props) -> new RacingBikeAverageSpeedParser(lookup),
+                    "ferry_speed", "smoothness"
+            );
+        else if (VehicleSpeed.key("mtb").equals(name))
+            return ImportUnit.create(name, props -> new DecimalEncodedValueImpl(
+                            name, props.getInt("speed_bits", 4), props.getDouble("speed_factor", 2), false),
+                    (lookup, props) -> new MountainBikeAverageSpeedParser(lookup),
+                    "ferry_speed", "smoothness"
+            );
+        else if (VehicleSpeed.key("foot").equals(name))
+            return ImportUnit.create(name, props -> new DecimalEncodedValueImpl(
+                            name, props.getInt("speed_bits", 4), props.getDouble("speed_factor", 1), false),
+                    (lookup, props) -> new FootAverageSpeedParser(lookup),
+                    "ferry_speed"
+            );
+        else if (VehiclePriority.key("foot").equals(name))
+            return ImportUnit.create(name, props -> VehiclePriority.create("foot", 4, PriorityCode.getFactor(1), false),
+                    (lookup, props) -> new FootPriorityParser(lookup),
+                    RouteNetwork.key("foot")
+            );
+        else if (VehiclePriority.key("bike").equals(name))
+            return ImportUnit.create(name, props -> VehiclePriority.create("bike", 4, PriorityCode.getFactor(1), false),
+                    (lookup, props) -> new BikePriorityParser(lookup),
+                    VehicleSpeed.key("bike"), BikeNetwork.KEY
+            );
+        else if (VehiclePriority.key("racingbike").equals(name))
+            return ImportUnit.create(name, props -> VehiclePriority.create("racingbike", 4, PriorityCode.getFactor(1), false),
+                    (lookup, props) -> new RacingBikePriorityParser(lookup),
+                    VehicleSpeed.key("racingbike"), BikeNetwork.KEY
+            );
+        else if (VehiclePriority.key("mtb").equals(name))
+            return ImportUnit.create(name, props -> VehiclePriority.create("mtb", 4, PriorityCode.getFactor(1), false),
+                    (lookup, props) -> new MountainBikePriorityParser(lookup),
+                    VehicleSpeed.key("mtb"), BikeNetwork.KEY
+            );
+        return null;
+    }
+}
diff --git a/core/src/main/java/com/graphhopper/routing/ev/EncodedValueFactory.java b/core/src/main/java/com/graphhopper/routing/ev/ImportRegistry.java
similarity index 86%
rename from core/src/main/java/com/graphhopper/routing/ev/EncodedValueFactory.java
rename to core/src/main/java/com/graphhopper/routing/ev/ImportRegistry.java
index c8fcbcc99..283321e35 100644
--- a/core/src/main/java/com/graphhopper/routing/ev/EncodedValueFactory.java
+++ b/core/src/main/java/com/graphhopper/routing/ev/ImportRegistry.java
@@ -15,10 +15,9 @@
  *  See the License for the specific language governing permissions and
  *  limitations under the License.
  */
-package com.graphhopper.routing.ev;
 
-import com.graphhopper.util.PMap;
+package com.graphhopper.routing.ev;
 
-public interface EncodedValueFactory {
-    EncodedValue create(String name, PMap properties);
+public interface ImportRegistry {
+    ImportUnit createImportUnit(String name);
 }
diff --git a/core/src/main/java/com/graphhopper/routing/ev/ImportUnit.java b/core/src/main/java/com/graphhopper/routing/ev/ImportUnit.java
new file mode 100644
index 000000000..04fd08a38
--- /dev/null
+++ b/core/src/main/java/com/graphhopper/routing/ev/ImportUnit.java
@@ -0,0 +1,61 @@
+/*
+ *  Licensed to GraphHopper GmbH under one or more contributor
+ *  license agreements. See the NOTICE file distributed with this work for
+ *  additional information regarding copyright ownership.
+ *
+ *  GraphHopper GmbH licenses this file to you 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 com.graphhopper.routing.ev;
+
+import com.graphhopper.routing.util.parsers.TagParser;
+import com.graphhopper.util.PMap;
+
+import java.util.List;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+
+public class ImportUnit {
+    private final String name;
+    private final Function<PMap, EncodedValue> createEncodedValue;
+    private final BiFunction<EncodedValueLookup, PMap, TagParser> createTagParser;
+    private final List<String> requiredImportUnits;
+
+    public static ImportUnit create(String name, Function<PMap, EncodedValue> createEncodedValue, BiFunction<EncodedValueLookup, PMap, TagParser> createTagParser, String... requiredImportUnits) {
+        return new ImportUnit(name, createEncodedValue, createTagParser, List.of(requiredImportUnits));
+    }
+
+    private ImportUnit(String name, Function<PMap, EncodedValue> createEncodedValue, BiFunction<EncodedValueLookup, PMap, TagParser> createTagParser, List<String> requiredImportUnits) {
+        this.name = name;
+        this.createEncodedValue = createEncodedValue;
+        this.createTagParser = createTagParser;
+        this.requiredImportUnits = requiredImportUnits;
+    }
+
+    public Function<PMap, EncodedValue> getCreateEncodedValue() {
+        return createEncodedValue;
+    }
+
+    public BiFunction<EncodedValueLookup, PMap, TagParser> getCreateTagParser() {
+        return createTagParser;
+    }
+
+    public List<String> getRequiredImportUnits() {
+        return requiredImportUnits;
+    }
+
+    @Override
+    public String toString() {
+        return "ImportUnit: " + name + " (requires: " + requiredImportUnits + ")";
+    }
+}
diff --git a/core/src/main/java/com/graphhopper/routing/ev/ImportUnitSorter.java b/core/src/main/java/com/graphhopper/routing/ev/ImportUnitSorter.java
new file mode 100644
index 000000000..de15618ab
--- /dev/null
+++ b/core/src/main/java/com/graphhopper/routing/ev/ImportUnitSorter.java
@@ -0,0 +1,57 @@
+/*
+ *  Licensed to GraphHopper GmbH under one or more contributor
+ *  license agreements. See the NOTICE file distributed with this work for
+ *  additional information regarding copyright ownership.
+ *
+ *  GraphHopper GmbH licenses this file to you 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 com.graphhopper.routing.ev;
+
+import java.util.*;
+
+// topological sort with a depth first search
+public class ImportUnitSorter {
+    Set<String> permanentMarked = new HashSet<>();
+    Set<String> temporaryMarked = new HashSet<>();
+    List<String> result = new ArrayList<>();
+    final Map<String, ImportUnit> map;
+
+    public ImportUnitSorter(Map<String, ImportUnit> map) {
+        this.map = map;
+    }
+
+    public List<String> sort() {
+        for (String strN : map.keySet()) {
+            visit(strN);
+        }
+        return result;
+    }
+
+    private void visit(String strN) {
+        if (permanentMarked.contains(strN)) return;
+        ImportUnit importUnit = map.get(strN);
+        if (importUnit == null)
+            throw new IllegalArgumentException("cannot find reg " + strN);
+        if (temporaryMarked.contains(strN))
+            throw new IllegalArgumentException("cyclic required parsers are not allowed: " + importUnit + " " + importUnit.getRequiredImportUnits());
+
+        temporaryMarked.add(strN);
+        for (String strM : importUnit.getRequiredImportUnits()) {
+            visit(strM);
+        }
+        temporaryMarked.remove(strN);
+        permanentMarked.add(strN);
+        result.add(strN);
+    }
+}
diff --git a/core/src/main/java/com/graphhopper/routing/util/DefaultVehicleEncodedValuesFactory.java b/core/src/main/java/com/graphhopper/routing/util/DefaultVehicleEncodedValuesFactory.java
deleted file mode 100644
index f0773a321..000000000
--- a/core/src/main/java/com/graphhopper/routing/util/DefaultVehicleEncodedValuesFactory.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- *  Licensed to GraphHopper GmbH under one or more contributor
- *  license agreements. See the NOTICE file distributed with this work for
- *  additional information regarding copyright ownership.
- *
- *  GraphHopper GmbH licenses this file to you 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 com.graphhopper.routing.util;
-
-import com.graphhopper.util.PMap;
-
-/**
- * This class creates vehicle encoded values that are already included in the GraphHopper distribution.
- *
- * @author Peter Karich
- */
-public class DefaultVehicleEncodedValuesFactory implements VehicleEncodedValuesFactory {
-    @Override
-    public VehicleEncodedValues createVehicleEncodedValues(String name, PMap configuration) {
-        if (name.equals(ROADS))
-            return VehicleEncodedValues.roads(configuration);
-
-        if (name.equals(CAR))
-            return VehicleEncodedValues.car(configuration);
-
-        if (name.equals("car4wd"))
-            throw new IllegalArgumentException("Instead of car4wd use custom_models/car4wd.json");
-
-        if (name.equals(BIKE))
-            return VehicleEncodedValues.bike(configuration);
-
-        if (name.equals("bike2"))
-            throw new IllegalArgumentException("Instead of bike2 use custom_models/bike.json, see #2668");
-
-        if (name.equals(RACINGBIKE))
-            return VehicleEncodedValues.racingbike(configuration);
-
-        if (name.equals(MOUNTAINBIKE))
-            return VehicleEncodedValues.mountainbike(configuration);
-
-        if (name.equals(FOOT))
-            return VehicleEncodedValues.foot(configuration);
-
-        if (name.equals("hike"))
-            throw new IllegalArgumentException("Instead of hike use custom_models/hike.json, see #2759");
-
-        if (name.equals("motorcycle"))
-            throw new IllegalArgumentException("Instead of motorcycle use custom_models/motorcycle.json, see #2781");
-
-        if (name.equals("wheelchair"))
-            throw new IllegalArgumentException("wheelchair is no longer supported, see #");
-
-        throw new IllegalArgumentException("entry in vehicle list not supported: " + name);
-    }
-}
diff --git a/core/src/main/java/com/graphhopper/routing/util/DefaultVehicleTagParserFactory.java b/core/src/main/java/com/graphhopper/routing/util/DefaultVehicleTagParserFactory.java
deleted file mode 100644
index aba279bcd..000000000
--- a/core/src/main/java/com/graphhopper/routing/util/DefaultVehicleTagParserFactory.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- *  Licensed to GraphHopper GmbH under one or more contributor
- *  license agreements. See the NOTICE file distributed with this work for
- *  additional information regarding copyright ownership.
- *
- *  GraphHopper GmbH licenses this file to you 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 com.graphhopper.routing.util;
-
-import com.graphhopper.routing.ev.EncodedValueLookup;
-import com.graphhopper.util.PMap;
-
-import static com.graphhopper.routing.util.VehicleEncodedValuesFactory.*;
-
-public class DefaultVehicleTagParserFactory implements VehicleTagParserFactory {
-    public VehicleTagParsers createParsers(EncodedValueLookup lookup, String name, PMap configuration) {
-        if (name.equals(ROADS))
-            return VehicleTagParsers.roads(lookup);
-        if (name.equals(CAR))
-            return VehicleTagParsers.car(lookup, configuration);
-        if (name.equals(BIKE))
-            return VehicleTagParsers.bike(lookup, configuration);
-        if (name.equals(RACINGBIKE))
-            return VehicleTagParsers.racingbike(lookup, configuration);
-        if (name.equals(MOUNTAINBIKE))
-            return VehicleTagParsers.mtb(lookup, configuration);
-        if (name.equals(FOOT))
-            return VehicleTagParsers.foot(lookup, configuration);
-
-        throw new IllegalArgumentException("Unknown name for vehicle tag parsers: " + name);
-    }
-}
diff --git a/core/src/main/java/com/graphhopper/routing/util/EncodingManager.java b/core/src/main/java/com/graphhopper/routing/util/EncodingManager.java
index a810aeb06..8eb157aaa 100644
--- a/core/src/main/java/com/graphhopper/routing/util/EncodingManager.java
+++ b/core/src/main/java/com/graphhopper/routing/util/EncodingManager.java
@@ -24,10 +24,12 @@ import com.graphhopper.routing.ev.*;
 import com.graphhopper.storage.IntsRef;
 import com.graphhopper.storage.StorableProperties;
 import com.graphhopper.util.Constants;
-import com.graphhopper.util.PMap;
 
 import java.io.UncheckedIOException;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
 import java.util.stream.Collectors;
 
 /**
@@ -126,18 +128,6 @@ public class EncodingManager implements EncodedValueLookup {
         private final EncodedValue.InitializerConfig turnCostConfig = new EncodedValue.InitializerConfig();
         private EncodingManager em = new EncodingManager();
 
-        public Builder add(VehicleEncodedValues v) {
-            checkNotBuiltAlready();
-            List<EncodedValue> list = new ArrayList<>();
-            v.createEncodedValues(list);
-            list.forEach(this::add);
-
-            list = new ArrayList<>();
-            v.createTurnCostEncodedValues(list);
-            list.forEach(this::addTurnCostEncodedValue);
-            return this;
-        }
-
         public Builder add(EncodedValue encodedValue) {
             checkNotBuiltAlready();
             if (em.hasEncodedValue(encodedValue.getName()))
@@ -167,9 +157,6 @@ public class EncodingManager implements EncodedValueLookup {
 
         public EncodingManager build() {
             checkNotBuiltAlready();
-            addDefaultEncodedValues();
-            if (em.encodedValueMap.isEmpty())
-                throw new IllegalStateException("No EncodedValues were added to the EncodingManager");
             em.intsForFlags = edgeConfig.getRequiredInts();
             em.intsForTurnCostFlags = turnCostConfig.getRequiredInts();
             EncodingManager result = em;
@@ -177,31 +164,6 @@ public class EncodingManager implements EncodedValueLookup {
             return result;
         }
 
-        private void addDefaultEncodedValues() {
-            // todo: I think ultimately these should all be removed and must be added explicitly
-            List<String> keys = new ArrayList<>(Arrays.asList(
-                    Roundabout.KEY,
-                    RoadClass.KEY,
-                    RoadClassLink.KEY,
-                    RoadEnvironment.KEY,
-                    MaxSpeed.KEY,
-                    RoadAccess.KEY,
-                    FerrySpeed.KEY
-            ));
-            if (em.getVehicles().stream().anyMatch(vehicle -> vehicle.contains("bike") || vehicle.contains("mtb") || vehicle.contains("racingbike"))) {
-                keys.add(BikeNetwork.KEY);
-                keys.add(MtbNetwork.KEY);
-                keys.add(GetOffBike.KEY);
-                keys.add(Smoothness.KEY);
-            }
-            if (em.getVehicles().stream().anyMatch(vehicle -> vehicle.contains("foot") || vehicle.contains("hike")))
-                keys.add(FootNetwork.KEY);
-
-            DefaultEncodedValueFactory evFactory = new DefaultEncodedValueFactory();
-            for (String key : keys)
-                if (!em.hasEncodedValue(key))
-                    add(evFactory.create(key, new PMap()));
-        }
     }
 
     public int getIntsForFlags() {
@@ -290,7 +252,7 @@ public class EncodingManager implements EncodedValueLookup {
         EncodedValue ev = encodedValueMap.get(key);
         // todo: why do we not just return null when EV is missing? just like java.util.Map? -> https://github.com/graphhopper/graphhopper/pull/2561#discussion_r859770067
         if (ev == null)
-            throw new IllegalArgumentException("Cannot find EncodedValue " + key + " in collection: " + encodedValueMap.keySet());
+            throw new IllegalArgumentException("Cannot find EncodedValue '" + key + "' in collection: " + encodedValueMap.keySet());
         return (T) ev;
     }
 
diff --git a/core/src/main/java/com/graphhopper/routing/util/SlopeCalculator.java b/core/src/main/java/com/graphhopper/routing/util/SlopeCalculator.java
index d6e2b6ea3..454efb033 100644
--- a/core/src/main/java/com/graphhopper/routing/util/SlopeCalculator.java
+++ b/core/src/main/java/com/graphhopper/routing/util/SlopeCalculator.java
@@ -24,15 +24,17 @@ public class SlopeCalculator implements TagParser {
         PointList pointList = way.getTag("point_list", null);
         if (pointList != null) {
             if (pointList.isEmpty() || !pointList.is3D()) {
-                averageSlopeEnc.setDecimal(false, edgeId, edgeIntAccess, 0);
+                if (averageSlopeEnc != null)
+                    averageSlopeEnc.setDecimal(false, edgeId, edgeIntAccess, 0);
                 return;
             }
             // Calculate 2d distance, although pointList might be 3D.
             // This calculation is a bit expensive and edge_distance is available already, but this would be in 3D
             double distance2D = DistanceCalcEarth.calcDistance(pointList, false);
             if (distance2D < MIN_LENGTH) {
-                // default is minimum of average_slope is negative so we have to explicitly set it to 0
-                averageSlopeEnc.setDecimal(false, edgeId, edgeIntAccess, 0);
+                if (averageSlopeEnc != null)
+                    // default is minimum of average_slope is negative so we have to explicitly set it to 0
+                    averageSlopeEnc.setDecimal(false, edgeId, edgeIntAccess, 0);
                 return;
             }
 
@@ -40,39 +42,43 @@ public class SlopeCalculator implements TagParser {
             if (Double.isNaN(towerNodeSlope))
                 throw new IllegalArgumentException("average_slope was NaN for OSM way ID " + way.getId());
 
-            if (towerNodeSlope >= 0)
-                averageSlopeEnc.setDecimal(false, edgeId, edgeIntAccess, Math.min(towerNodeSlope, averageSlopeEnc.getMaxStorableDecimal()));
-            else
-                averageSlopeEnc.setDecimal(true, edgeId, edgeIntAccess, Math.min(Math.abs(towerNodeSlope), averageSlopeEnc.getMaxStorableDecimal()));
+            if (averageSlopeEnc != null) {
+                if (towerNodeSlope >= 0)
+                    averageSlopeEnc.setDecimal(false, edgeId, edgeIntAccess, Math.min(towerNodeSlope, averageSlopeEnc.getMaxStorableDecimal()));
+                else
+                    averageSlopeEnc.setDecimal(true, edgeId, edgeIntAccess, Math.min(Math.abs(towerNodeSlope), averageSlopeEnc.getMaxStorableDecimal()));
+            }
 
-            // max_slope is more error-prone as the shorter distances increase the fluctuation
-            // so apply some more filtering (here we use the average elevation delta of the previous two points)
-            double maxSlope = 0, prevDist = 0, prevLat = pointList.getLat(0), prevLon = pointList.getLon(0);
-            for (int i = 1; i < pointList.size(); i++) {
-                double pillarDistance2D = DistanceCalcEarth.DIST_EARTH.calcDist(prevLat, prevLon, pointList.getLat(i), pointList.getLon(i));
-                if (i > 1 && prevDist > MIN_LENGTH) {
-                    double averagedPrevEle = (pointList.getEle(i - 1) + pointList.getEle(i - 2)) / 2;
-                    double tmpSlope = calcSlope(pointList.getEle(i) - averagedPrevEle, pillarDistance2D + prevDist / 2);
-                    maxSlope = Math.max(maxSlope, Math.abs(tmpSlope));
+            if (maxSlopeEnc != null) {
+                // max_slope is more error-prone as the shorter distances increase the fluctuation
+                // so apply some more filtering (here we use the average elevation delta of the previous two points)
+                double maxSlope = 0, prevDist = 0, prevLat = pointList.getLat(0), prevLon = pointList.getLon(0);
+                for (int i = 1; i < pointList.size(); i++) {
+                    double pillarDistance2D = DistanceCalcEarth.DIST_EARTH.calcDist(prevLat, prevLon, pointList.getLat(i), pointList.getLon(i));
+                    if (i > 1 && prevDist > MIN_LENGTH) {
+                        double averagedPrevEle = (pointList.getEle(i - 1) + pointList.getEle(i - 2)) / 2;
+                        double tmpSlope = calcSlope(pointList.getEle(i) - averagedPrevEle, pillarDistance2D + prevDist / 2);
+                        maxSlope = Math.max(maxSlope, Math.abs(tmpSlope));
+                    }
+                    prevDist = pillarDistance2D;
+                    prevLat = pointList.getLat(i);
+                    prevLon = pointList.getLon(i);
                 }
-                prevDist = pillarDistance2D;
-                prevLat = pointList.getLat(i);
-                prevLon = pointList.getLon(i);
-            }
 
-            // For tunnels and bridges we cannot trust the pillar node elevation and ignore all changes.
-            // Probably we should somehow recalculate even the average_slope after elevation interpolation? See EdgeElevationInterpolator
-            if (way.hasTag("tunnel", "yes") || way.hasTag("bridge", "yes") || way.hasTag("highway", "steps"))
-                maxSlope = Math.abs(towerNodeSlope);
-            else
-                maxSlope = Math.max(Math.abs(towerNodeSlope), maxSlope);
+                // For tunnels and bridges we cannot trust the pillar node elevation and ignore all changes.
+                // Probably we should somehow recalculate even the average_slope after elevation interpolation? See EdgeElevationInterpolator
+                if (way.hasTag("tunnel", "yes") || way.hasTag("bridge", "yes") || way.hasTag("highway", "steps"))
+                    maxSlope = Math.abs(towerNodeSlope);
+                else
+                    maxSlope = Math.max(Math.abs(towerNodeSlope), maxSlope);
 
-            if (Double.isNaN(maxSlope))
-                throw new IllegalArgumentException("max_slope was NaN for OSM way ID " + way.getId());
+                if (Double.isNaN(maxSlope))
+                    throw new IllegalArgumentException("max_slope was NaN for OSM way ID " + way.getId());
 
-            // TODO Use two independent values for both directions to store if it is a gain or loss and not just the absolute change.
-            // TODO To save space then it would be nice to have an encoded value that can store two different values which are swapped when the reverse direction is used
-            maxSlopeEnc.setDecimal(false, edgeId, edgeIntAccess, Math.min(maxSlope, maxSlopeEnc.getMaxStorableDecimal()));
+                // TODO Use two independent values for both directions to store if it is a gain or loss and not just the absolute change.
+                // TODO To save space then it would be nice to have an encoded value that can store two different values which are swapped when the reverse direction is used
+                maxSlopeEnc.setDecimal(false, edgeId, edgeIntAccess, Math.min(maxSlope, maxSlopeEnc.getMaxStorableDecimal()));
+            }
         }
     }
 
diff --git a/core/src/main/java/com/graphhopper/routing/util/VehicleEncodedValues.java b/core/src/main/java/com/graphhopper/routing/util/VehicleEncodedValues.java
deleted file mode 100644
index 37f922190..000000000
--- a/core/src/main/java/com/graphhopper/routing/util/VehicleEncodedValues.java
+++ /dev/null
@@ -1,145 +0,0 @@
-/*
- *  Licensed to GraphHopper GmbH under one or more contributor
- *  license agreements. See the NOTICE file distributed with this work for
- *  additional information regarding copyright ownership.
- *
- *  GraphHopper GmbH licenses this file to you 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 com.graphhopper.routing.util;
-
-import com.graphhopper.routing.ev.*;
-import com.graphhopper.util.PMap;
-
-import java.util.Arrays;
-import java.util.List;
-
-import static com.graphhopper.routing.util.VehicleEncodedValuesFactory.*;
-
-public class VehicleEncodedValues {
-    public static final List<String> OUTDOOR_VEHICLES = Arrays.asList(BIKE, RACINGBIKE, MOUNTAINBIKE, FOOT);
-
-    private final String name;
-    private final BooleanEncodedValue accessEnc;
-    private final DecimalEncodedValue avgSpeedEnc;
-    private final DecimalEncodedValue priorityEnc;
-    private final BooleanEncodedValue turnRestrictionEnc;
-
-    public static VehicleEncodedValues foot(PMap properties) {
-        String name = "foot";
-        int speedBits = properties.getInt("speed_bits", 4);
-        double speedFactor = properties.getDouble("speed_factor", 1);
-        boolean speedTwoDirections = properties.getBool("speed_two_directions", false);
-        boolean turnCosts = properties.getBool("turn_costs", false);
-        BooleanEncodedValue accessEnc = VehicleAccess.create(name);
-        DecimalEncodedValue speedEnc = VehicleSpeed.create(name, speedBits, speedFactor, speedTwoDirections);
-        DecimalEncodedValue priorityEnc = VehiclePriority.create(name, 4, PriorityCode.getFactor(1), false);
-        BooleanEncodedValue turnRestrictionEnc = turnCosts ? TurnRestriction.create(name) : null;
-        return new VehicleEncodedValues(name, accessEnc, speedEnc, priorityEnc, turnRestrictionEnc);
-    }
-
-    public static VehicleEncodedValues bike(PMap properties) {
-        return createBike(properties, "bike");
-    }
-
-    public static VehicleEncodedValues racingbike(PMap properties) {
-        return createBike(properties, "racingbike");
-    }
-
-    public static VehicleEncodedValues mountainbike(PMap properties) {
-        return createBike(properties, "mtb");
-    }
-
-    private static VehicleEncodedValues createBike(PMap properties, String name) {
-        int speedBits = properties.getInt("speed_bits", 4);
-        double speedFactor = properties.getDouble("speed_factor", 2);
-        boolean speedTwoDirections = properties.getBool("speed_two_directions", false);
-        boolean turnCosts = properties.getBool("turn_costs", false);
-        BooleanEncodedValue accessEnc = VehicleAccess.create(name);
-        DecimalEncodedValue speedEnc = VehicleSpeed.create(name, speedBits, speedFactor, speedTwoDirections);
-        DecimalEncodedValue priorityEnc = VehiclePriority.create(name, 4, PriorityCode.getFactor(1), false);
-        BooleanEncodedValue turnRestrictionEnc = turnCosts ? TurnRestriction.create(name) : null;
-        return new VehicleEncodedValues(name, accessEnc, speedEnc, priorityEnc, turnRestrictionEnc);
-    }
-
-    public static VehicleEncodedValues car(PMap properties) {
-        String name = "car";
-        int speedBits = properties.getInt("speed_bits", 7);
-        double speedFactor = properties.getDouble("speed_factor", 2);
-        boolean turnCosts = properties.getBool("turn_costs", false);
-        BooleanEncodedValue accessEnc = VehicleAccess.create(name);
-        DecimalEncodedValue speedEnc = VehicleSpeed.create(name, speedBits, speedFactor, true);
-        BooleanEncodedValue turnRestrictionEnc = turnCosts ? TurnRestriction.create(name) : null;
-        return new VehicleEncodedValues(name, accessEnc, speedEnc, null, turnRestrictionEnc);
-    }
-
-    public static VehicleEncodedValues roads(PMap properties) {
-        String name = "roads";
-        int speedBits = properties.getInt("speed_bits", 7);
-        double speedFactor = properties.getDouble("speed_factor", 2);
-        boolean speedTwoDirections = properties.getBool("speed_two_directions", true);
-        boolean turnCosts = properties.getBool("turn_costs", false);
-        BooleanEncodedValue accessEnc = VehicleAccess.create(name);
-        DecimalEncodedValue speedEnc = VehicleSpeed.create(name, speedBits, speedFactor, speedTwoDirections);
-        BooleanEncodedValue turnRestrictionEnc = turnCosts ? TurnRestriction.create(name) : null;
-        return new VehicleEncodedValues(name, accessEnc, speedEnc, null, turnRestrictionEnc);
-    }
-
-    public VehicleEncodedValues(String name, BooleanEncodedValue accessEnc, DecimalEncodedValue avgSpeedEnc,
-                                DecimalEncodedValue priorityEnc, BooleanEncodedValue turnRestrictionEnc) {
-        this.name = name;
-        this.accessEnc = accessEnc;
-        this.avgSpeedEnc = avgSpeedEnc;
-        this.priorityEnc = priorityEnc;
-        this.turnRestrictionEnc = turnRestrictionEnc;
-    }
-
-    public void createEncodedValues(List<EncodedValue> registerNewEncodedValue) {
-        if (accessEnc != null)
-            registerNewEncodedValue.add(accessEnc);
-        if (avgSpeedEnc != null)
-            registerNewEncodedValue.add(avgSpeedEnc);
-        if (priorityEnc != null)
-            registerNewEncodedValue.add(priorityEnc);
-    }
-
-    public void createTurnCostEncodedValues(List<EncodedValue> registerNewTurnCostEncodedValues) {
-        if (turnRestrictionEnc != null)
-            registerNewTurnCostEncodedValues.add(turnRestrictionEnc);
-    }
-
-    public BooleanEncodedValue getAccessEnc() {
-        return accessEnc;
-    }
-
-    public DecimalEncodedValue getAverageSpeedEnc() {
-        return avgSpeedEnc;
-    }
-
-    public DecimalEncodedValue getPriorityEnc() {
-        return priorityEnc;
-    }
-
-    public BooleanEncodedValue getTurnRestrictionEnc() {
-        return turnRestrictionEnc;
-    }
-
-    public String getName() {
-        return name;
-    }
-
-    @Override
-    public String toString() {
-        return getName();
-    }
-}
diff --git a/core/src/main/java/com/graphhopper/routing/util/VehicleEncodedValuesFactory.java b/core/src/main/java/com/graphhopper/routing/util/VehicleEncodedValuesFactory.java
deleted file mode 100644
index dee644ade..000000000
--- a/core/src/main/java/com/graphhopper/routing/util/VehicleEncodedValuesFactory.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- *  Licensed to GraphHopper GmbH under one or more contributor
- *  license agreements. See the NOTICE file distributed with this work for
- *  additional information regarding copyright ownership.
- *
- *  GraphHopper GmbH licenses this file to you 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 com.graphhopper.routing.util;
-
-import com.graphhopper.util.PMap;
-
-/**
- * @author Peter Karich
- */
-public interface VehicleEncodedValuesFactory {
-    String ROADS = "roads";
-    String CAR = "car";
-    String BIKE = "bike";
-    String RACINGBIKE = "racingbike";
-    String MOUNTAINBIKE = "mtb";
-    String FOOT = "foot";
-
-    VehicleEncodedValues createVehicleEncodedValues(String name, PMap configuration);
-
-}
diff --git a/core/src/main/java/com/graphhopper/routing/util/VehicleTagParserFactory.java b/core/src/main/java/com/graphhopper/routing/util/VehicleTagParserFactory.java
deleted file mode 100644
index 4998a9609..000000000
--- a/core/src/main/java/com/graphhopper/routing/util/VehicleTagParserFactory.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- *  Licensed to GraphHopper GmbH under one or more contributor
- *  license agreements. See the NOTICE file distributed with this work for
- *  additional information regarding copyright ownership.
- *
- *  GraphHopper GmbH licenses this file to you 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 com.graphhopper.routing.util;
-
-import com.graphhopper.routing.ev.EncodedValueLookup;
-import com.graphhopper.util.PMap;
-
-public interface VehicleTagParserFactory {
-    VehicleTagParsers createParsers(EncodedValueLookup lookup, String name, PMap configuration);
-}
diff --git a/core/src/main/java/com/graphhopper/routing/util/VehicleTagParsers.java b/core/src/main/java/com/graphhopper/routing/util/VehicleTagParsers.java
deleted file mode 100644
index f0106f904..000000000
--- a/core/src/main/java/com/graphhopper/routing/util/VehicleTagParsers.java
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- *  Licensed to GraphHopper GmbH under one or more contributor
- *  license agreements. See the NOTICE file distributed with this work for
- *  additional information regarding copyright ownership.
- *
- *  GraphHopper GmbH licenses this file to you 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 com.graphhopper.routing.util;
-
-import com.graphhopper.reader.osm.conditional.DateRangeParser;
-import com.graphhopper.routing.ev.EncodedValueLookup;
-import com.graphhopper.routing.util.parsers.*;
-import com.graphhopper.util.PMap;
-
-import java.util.Arrays;
-import java.util.List;
-
-public class VehicleTagParsers {
-    private final TagParser accessParser;
-    private final TagParser speedParser;
-    private final TagParser priorityParser;
-
-    public static VehicleTagParsers roads(EncodedValueLookup lookup) {
-        return new VehicleTagParsers(
-                new RoadsAccessParser(lookup),
-                new RoadsAverageSpeedParser(lookup),
-                null
-        );
-    }
-
-    public static VehicleTagParsers car(EncodedValueLookup lookup, PMap properties) {
-        return new VehicleTagParsers(
-                new CarAccessParser(lookup, properties).init(properties.getObject("date_range_parser", new DateRangeParser())),
-                new CarAverageSpeedParser(lookup),
-                null
-        );
-    }
-
-    public static VehicleTagParsers bike(EncodedValueLookup lookup, PMap properties) {
-        return new VehicleTagParsers(
-                new BikeAccessParser(lookup, properties).init(properties.getObject("date_range_parser", new DateRangeParser())),
-                new BikeAverageSpeedParser(lookup),
-                new BikePriorityParser(lookup)
-        );
-    }
-
-    public static VehicleTagParsers racingbike(EncodedValueLookup lookup, PMap properties) {
-        return new VehicleTagParsers(
-                new RacingBikeAccessParser(lookup, properties).init(properties.getObject("date_range_parser", new DateRangeParser())),
-                new RacingBikeAverageSpeedParser(lookup),
-                new RacingBikePriorityParser(lookup)
-        );
-    }
-
-    public static VehicleTagParsers mtb(EncodedValueLookup lookup, PMap properties) {
-        return new VehicleTagParsers(
-                new MountainBikeAccessParser(lookup, properties).init(properties.getObject("date_range_parser", new DateRangeParser())),
-                new MountainBikeAverageSpeedParser(lookup),
-                new MountainBikePriorityParser(lookup)
-        );
-    }
-
-    public static VehicleTagParsers foot(EncodedValueLookup lookup, PMap properties) {
-        return new VehicleTagParsers(
-                new FootAccessParser(lookup, properties).init(properties.getObject("date_range_parser", new DateRangeParser())),
-                new FootAverageSpeedParser(lookup),
-                new FootPriorityParser(lookup)
-        );
-    }
-
-    public VehicleTagParsers(TagParser accessParser, TagParser speedParser, TagParser priorityParser) {
-        this.accessParser = accessParser;
-        this.speedParser = speedParser;
-        this.priorityParser = priorityParser;
-    }
-
-    public TagParser getAccessParser() {
-        return accessParser;
-    }
-
-    public TagParser getSpeedParser() {
-        return speedParser;
-    }
-
-    public TagParser getPriorityParser() {
-        return priorityParser;
-    }
-
-    public List<TagParser> getTagParsers() {
-        return priorityParser == null ? Arrays.asList(accessParser, speedParser) : Arrays.asList(accessParser, speedParser, priorityParser);
-    }
-
-}
diff --git a/core/src/main/java/com/graphhopper/routing/util/parsers/DefaultTagParserFactory.java b/core/src/main/java/com/graphhopper/routing/util/parsers/DefaultTagParserFactory.java
deleted file mode 100644
index 48e7ee593..000000000
--- a/core/src/main/java/com/graphhopper/routing/util/parsers/DefaultTagParserFactory.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- *  Licensed to GraphHopper GmbH under one or more contributor
- *  license agreements. See the NOTICE file distributed with this work for
- *  additional information regarding copyright ownership.
- *
- *  GraphHopper GmbH licenses this file to you 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 com.graphhopper.routing.util.parsers;
-
-import com.graphhopper.routing.ev.*;
-import com.graphhopper.routing.util.FerrySpeedCalculator;
-import com.graphhopper.routing.util.TransportationMode;
-import com.graphhopper.util.PMap;
-
-public class DefaultTagParserFactory implements TagParserFactory {
-
-    @Override
-    public TagParser create(EncodedValueLookup lookup, String name, PMap properties) {
-        if (Roundabout.KEY.equals(name))
-            return new OSMRoundaboutParser(lookup.getBooleanEncodedValue(Roundabout.KEY));
-        else if (name.equals(RoadClass.KEY))
-            return new OSMRoadClassParser(lookup.getEnumEncodedValue(RoadClass.KEY, RoadClass.class));
-        else if (name.equals(RoadClassLink.KEY))
-            return new OSMRoadClassLinkParser(lookup.getBooleanEncodedValue(RoadClassLink.KEY));
-        else if (name.equals(RoadEnvironment.KEY))
-            return new OSMRoadEnvironmentParser(lookup.getEnumEncodedValue(RoadEnvironment.KEY, RoadEnvironment.class));
-        else if (name.equals(RoadAccess.KEY))
-            return new OSMRoadAccessParser(lookup.getEnumEncodedValue(RoadAccess.KEY, RoadAccess.class), OSMRoadAccessParser.toOSMRestrictions(TransportationMode.CAR));
-        else if (name.equals(MaxSpeed.KEY))
-            return new OSMMaxSpeedParser(lookup.getDecimalEncodedValue(MaxSpeed.KEY));
-        else if (name.equals(MaxWeight.KEY))
-            return new OSMMaxWeightParser(lookup.getDecimalEncodedValue(MaxWeight.KEY));
-        else if (name.equals(MaxWeightExcept.KEY))
-            return new MaxWeightExceptParser(lookup.getEnumEncodedValue(MaxWeightExcept.KEY, MaxWeightExcept.class));
-        else if (name.equals(MaxHeight.KEY))
-            return new OSMMaxHeightParser(lookup.getDecimalEncodedValue(MaxHeight.KEY));
-        else if (name.equals(MaxWidth.KEY))
-            return new OSMMaxWidthParser(lookup.getDecimalEncodedValue(MaxWidth.KEY));
-        else if (name.equals(MaxAxleLoad.KEY))
-            return new OSMMaxAxleLoadParser(lookup.getDecimalEncodedValue(MaxAxleLoad.KEY));
-        else if (name.equals(MaxLength.KEY))
-            return new OSMMaxLengthParser(lookup.getDecimalEncodedValue(MaxLength.KEY));
-        else if (name.equals(Surface.KEY))
-            return new OSMSurfaceParser(lookup.getEnumEncodedValue(Surface.KEY, Surface.class));
-        else if (name.equals(Smoothness.KEY))
-            return new OSMSmoothnessParser(lookup.getEnumEncodedValue(Smoothness.KEY, Smoothness.class));
-        else if (name.equals(Toll.KEY))
-            return new OSMTollParser(lookup.getEnumEncodedValue(Toll.KEY, Toll.class));
-        else if (name.equals(TrackType.KEY))
-            return new OSMTrackTypeParser(lookup.getEnumEncodedValue(TrackType.KEY, TrackType.class));
-        else if (name.equals(Hgv.KEY))
-            return new OSMHgvParser(lookup.getEnumEncodedValue(Hgv.KEY, Hgv.class));
-        else if (name.equals(Hazmat.KEY))
-            return new OSMHazmatParser(lookup.getEnumEncodedValue(Hazmat.KEY, Hazmat.class));
-        else if (name.equals(HazmatTunnel.KEY))
-            return new OSMHazmatTunnelParser(lookup.getEnumEncodedValue(HazmatTunnel.KEY, HazmatTunnel.class));
-        else if (name.equals(HazmatWater.KEY))
-            return new OSMHazmatWaterParser(lookup.getEnumEncodedValue(HazmatWater.KEY, HazmatWater.class));
-        else if (name.equals(Lanes.KEY))
-            return new OSMLanesParser(lookup.getIntEncodedValue(Lanes.KEY));
-        else if (name.equals(OSMWayID.KEY))
-            return new OSMWayIDParser(lookup.getIntEncodedValue(OSMWayID.KEY));
-        else if (name.equals(MtbRating.KEY))
-            return new OSMMtbRatingParser(lookup.getIntEncodedValue(MtbRating.KEY));
-        else if (name.equals(HikeRating.KEY))
-            return new OSMHikeRatingParser(lookup.getIntEncodedValue(HikeRating.KEY));
-        else if (name.equals(HorseRating.KEY))
-            return new OSMHorseRatingParser(lookup.getIntEncodedValue(HorseRating.KEY));
-        else if (name.equals(Footway.KEY))
-            return new OSMFootwayParser(lookup.getEnumEncodedValue(Footway.KEY, Footway.class));
-        else if (name.equals(Country.KEY))
-            return new CountryParser(lookup.getEnumEncodedValue(Country.KEY, Country.class));
-        else if (name.equals(State.KEY))
-            return new StateParser(lookup.getEnumEncodedValue(State.KEY, State.class));
-        else if (name.equals(Crossing.KEY))
-            return new OSMCrossingParser(lookup.getEnumEncodedValue(Crossing.KEY, Crossing.class));
-        else if (name.equals(FerrySpeed.KEY))
-            return new FerrySpeedCalculator(lookup.getDecimalEncodedValue(FerrySpeed.KEY));
-        else if (name.equals(BusAccess.KEY))
-            return new ModeAccessParser(TransportationMode.BUS, lookup.getBooleanEncodedValue(BusAccess.KEY), lookup.getBooleanEncodedValue(Roundabout.KEY));
-        return null;
-    }
-}
diff --git a/core/src/main/java/com/graphhopper/routing/util/parsers/FootAverageSpeedParser.java b/core/src/main/java/com/graphhopper/routing/util/parsers/FootAverageSpeedParser.java
index 0fb194205..55fccf289 100644
--- a/core/src/main/java/com/graphhopper/routing/util/parsers/FootAverageSpeedParser.java
+++ b/core/src/main/java/com/graphhopper/routing/util/parsers/FootAverageSpeedParser.java
@@ -20,7 +20,7 @@ public class FootAverageSpeedParser extends AbstractAverageSpeedParser implement
                 lookup.getDecimalEncodedValue(FerrySpeed.KEY));
     }
 
-    protected FootAverageSpeedParser(DecimalEncodedValue speedEnc, DecimalEncodedValue ferrySpeedEnc) {
+    public FootAverageSpeedParser(DecimalEncodedValue speedEnc, DecimalEncodedValue ferrySpeedEnc) {
         super(speedEnc, ferrySpeedEnc);
 
         routeMap.put(INTERNATIONAL, UNCHANGED.getValue());
diff --git a/core/src/main/java/com/graphhopper/routing/util/parsers/TagParserFactory.java b/core/src/main/java/com/graphhopper/routing/util/parsers/TagParserFactory.java
deleted file mode 100644
index 2a0a676cd..000000000
--- a/core/src/main/java/com/graphhopper/routing/util/parsers/TagParserFactory.java
+++ /dev/null
@@ -1,8 +0,0 @@
-package com.graphhopper.routing.util.parsers;
-
-import com.graphhopper.routing.ev.EncodedValueLookup;
-import com.graphhopper.util.PMap;
-
-public interface TagParserFactory {
-    TagParser create(EncodedValueLookup lookup, String name, PMap properties);
-}
diff --git a/core/src/main/java/com/graphhopper/routing/weighting/custom/CustomModelParser.java b/core/src/main/java/com/graphhopper/routing/weighting/custom/CustomModelParser.java
index 75e580359..d9e0e9910 100644
--- a/core/src/main/java/com/graphhopper/routing/weighting/custom/CustomModelParser.java
+++ b/core/src/main/java/com/graphhopper/routing/weighting/custom/CustomModelParser.java
@@ -38,6 +38,8 @@ import java.io.*;
 import java.util.*;
 import java.util.concurrent.atomic.AtomicLong;
 
+import static com.graphhopper.json.Statement.Keyword.IF;
+
 public class CustomModelParser {
     private static final AtomicLong longVal = new AtomicLong(1);
     static final String IN_AREA_PREFIX = "in_";
@@ -168,6 +170,55 @@ public class CustomModelParser {
         }
     }
 
+    public static List<String> findVariablesForEncodedValuesString(CustomModel model,
+                                                                   NameValidator nameValidator,
+                                                                   EncodedValueLookup lookup) {
+        Set<String> variables = new LinkedHashSet<>();
+        // avoid parsing exception for backward_xy or in_xy ...
+        NameValidator nameValidatorIntern = s -> {
+            // some literals are no variables and would throw an exception (encoded value not found)
+            if (Character.isUpperCase(s.charAt(0)) || s.startsWith(BACKWARD_PREFIX) || s.startsWith(IN_AREA_PREFIX))
+                return true;
+            if (nameValidator.isValid(s)) {
+                variables.add(s);
+                return true;
+            }
+            return false;
+        };
+        ClassHelper helper = key -> getReturnType(lookup.getEncodedValue(key, EncodedValue.class));
+        findVariablesForEncodedValuesString(model.getPriority(), nameValidatorIntern, helper);
+        findVariablesForEncodedValuesString(model.getSpeed(), nameValidatorIntern, helper);
+        return new ArrayList<>(variables);
+    }
+
+    private static void findVariablesForEncodedValuesString(List<Statement> statements,
+                                                            NameValidator nameValidator,
+                                                            ClassHelper helper) {
+        List<List<Statement>> blocks = CustomModelParser.splitIntoBlocks(statements);
+        for (List<Statement> block : blocks) {
+            for (Statement statement : block) {
+                // ignore potential problems; collect only variables in this step
+                ConditionalExpressionVisitor.parse(statement.getCondition(), nameValidator, helper);
+                ValueExpressionVisitor.parse(statement.getValue(), nameValidator);
+            }
+        }
+    }
+
+    /**
+     * Splits the specified list into several list of statements starting with if
+     */
+    static List<List<Statement>> splitIntoBlocks(List<Statement> statements) {
+        List<List<Statement>> result = new ArrayList<>();
+        List<Statement> block = null;
+        for (Statement st : statements) {
+            if (IF.equals(st.getKeyword())) result.add(block = new ArrayList<>());
+            if (block == null)
+                throw new IllegalArgumentException("Every block must start with an if-statement");
+            block.add(st);
+        }
+        return result;
+    }
+
     /**
      * Parse the expressions from CustomModel relevant for the method getSpeed - see createClassTemplate.
      *
diff --git a/core/src/main/java/com/graphhopper/routing/weighting/custom/NameValidator.java b/core/src/main/java/com/graphhopper/routing/weighting/custom/NameValidator.java
index 058e7ce80..4fb4e51a5 100644
--- a/core/src/main/java/com/graphhopper/routing/weighting/custom/NameValidator.java
+++ b/core/src/main/java/com/graphhopper/routing/weighting/custom/NameValidator.java
@@ -1,5 +1,5 @@
 package com.graphhopper.routing.weighting.custom;
 
-interface NameValidator {
+public interface NameValidator {
     boolean isValid(String name);
 }


Test patch:
diff --git a/core/src/test/java/com/graphhopper/GraphHopperProfileTest.java b/core/src/test/java/com/graphhopper/GraphHopperProfileTest.java
index 38381a494..4396cf882 100644
--- a/core/src/test/java/com/graphhopper/GraphHopperProfileTest.java
+++ b/core/src/test/java/com/graphhopper/GraphHopperProfileTest.java
@@ -63,14 +63,7 @@ public class GraphHopperProfileTest {
         final GraphHopper hopper = new GraphHopper();
         hopper.setGraphHopperLocation(GH_LOCATION).setStoreOnFlush(false).
                 setProfiles(new Profile("profile").setVehicle("your_car"));
-        assertIllegalArgument(hopper::importOrLoad, "entry in vehicle list not supported: your_car");
-    }
-
-    @Test
-    public void vehicleDoesNotExist_error2() {
-        final GraphHopper hopper = new GraphHopper().setGraphHopperLocation(GH_LOCATION).setStoreOnFlush(false).
-                setProfiles(new Profile("profile").setVehicle("your_car"));
-        assertIllegalArgument(hopper::importOrLoad, "entry in vehicle list not supported: your_car");
+        assertIllegalArgument(hopper::importOrLoad, "Unknown vehicle: 'your_car'");
     }
 
     @Test
diff --git a/core/src/test/java/com/graphhopper/GraphHopperTest.java b/core/src/test/java/com/graphhopper/GraphHopperTest.java
index 568fbcee6..d408ebf24 100644
--- a/core/src/test/java/com/graphhopper/GraphHopperTest.java
+++ b/core/src/test/java/com/graphhopper/GraphHopperTest.java
@@ -23,17 +23,12 @@ import com.graphhopper.config.Profile;
 import com.graphhopper.reader.ReaderWay;
 import com.graphhopper.reader.dem.SRTMProvider;
 import com.graphhopper.reader.dem.SkadiProvider;
-import com.graphhopper.routing.ev.EdgeIntAccess;
-import com.graphhopper.routing.ev.EncodedValueLookup;
-import com.graphhopper.routing.ev.RoadEnvironment;
-import com.graphhopper.routing.ev.Subnetwork;
+import com.graphhopper.routing.ev.*;
 import com.graphhopper.routing.util.AllEdgesIterator;
 import com.graphhopper.routing.util.DefaultSnapFilter;
 import com.graphhopper.routing.util.EdgeFilter;
 import com.graphhopper.routing.util.countryrules.CountryRuleFactory;
-import com.graphhopper.routing.util.parsers.DefaultTagParserFactory;
 import com.graphhopper.routing.util.parsers.OSMRoadEnvironmentParser;
-import com.graphhopper.routing.util.parsers.TagParser;
 import com.graphhopper.routing.weighting.Weighting;
 import com.graphhopper.search.KVStorage;
 import com.graphhopper.storage.IntsRef;
@@ -1143,21 +1138,21 @@ public class GraphHopperTest {
                 setStoreOnFlush(true);
 
         if (!withTunnelInterpolation) {
-            hopper.setTagParserFactory(new DefaultTagParserFactory() {
+            hopper.setImportRegistry(new DefaultImportRegistry() {
                 @Override
-                public TagParser create(EncodedValueLookup lookup, String name, PMap properties) {
-                    TagParser parser = super.create(lookup, name, properties);
-                    if (name.equals("road_environment"))
-                        parser = new OSMRoadEnvironmentParser(lookup.getEnumEncodedValue(RoadEnvironment.KEY, RoadEnvironment.class)) {
-                            @Override
-                            public void handleWayTags(int edgeId, EdgeIntAccess edgeIntAccess, ReaderWay readerWay, IntsRef relationFlags) {
-                                // do not change RoadEnvironment to avoid triggering tunnel interpolation
-                            }
-                        };
-                    return parser;
+                public ImportUnit createImportUnit(String name) {
+                    ImportUnit ImportUnit = super.createImportUnit(name);
+                    if ("road_environment".equals(name))
+                        ImportUnit = ImportUnit.create(name, props -> RoadEnvironment.create(),
+                                (lookup, props) -> new OSMRoadEnvironmentParser(lookup.getEnumEncodedValue(RoadEnvironment.KEY, RoadEnvironment.class)) {
+                                    @Override
+                                    public void handleWayTags(int edgeId, EdgeIntAccess edgeIntAccess, ReaderWay readerWay, IntsRef relationFlags) {
+                                        // do not change RoadEnvironment to avoid triggering tunnel interpolation
+                                    }
+                                });
+                    return ImportUnit;
                 }
             });
-            hopper.setEncodedValuesString("road_environment");
         }
 
         hopper.setElevationProvider(new SRTMProvider(DIR));
@@ -2510,6 +2505,7 @@ public class GraphHopperTest {
                 setGraphHopperLocation(GH_LOCATION).
                 setOSMFile("../map-matching/files/leipzig_germany.osm.pbf").
                 setVehiclesString("car|block_private=false").
+                setEncodedValuesString("road_access").
                 setProfiles(
                         new Profile("car").setVehicle("car"),
                         new Profile("bike").setVehicle("bike"),
diff --git a/core/src/test/java/com/graphhopper/reader/dem/EdgeElevationInterpolatorTest.java b/core/src/test/java/com/graphhopper/reader/dem/EdgeElevationInterpolatorTest.java
index fc7302f40..751e25c19 100644
--- a/core/src/test/java/com/graphhopper/reader/dem/EdgeElevationInterpolatorTest.java
+++ b/core/src/test/java/com/graphhopper/reader/dem/EdgeElevationInterpolatorTest.java
@@ -45,7 +45,7 @@ public abstract class EdgeElevationInterpolatorTest {
     public void setUp() {
         accessEnc = new SimpleBooleanEncodedValue("access", true);
         speedEnc = new DecimalEncodedValueImpl("speed", 5, 5, false);
-        encodingManager = EncodingManager.start().add(accessEnc).add(speedEnc).build();
+        encodingManager = EncodingManager.start().add(accessEnc).add(speedEnc).add(RoadEnvironment.create()).build();
         graph = new BaseGraph.Builder(encodingManager).set3D(true).create();
         roadEnvEnc = encodingManager.getEnumEncodedValue(RoadEnvironment.KEY, RoadEnvironment.class);
         edgeElevationInterpolator = createEdgeElevationInterpolator();
diff --git a/core/src/test/java/com/graphhopper/reader/osm/GraphHopperOSMTest.java b/core/src/test/java/com/graphhopper/reader/osm/GraphHopperOSMTest.java
index 8f800f218..bf98f046b 100644
--- a/core/src/test/java/com/graphhopper/reader/osm/GraphHopperOSMTest.java
+++ b/core/src/test/java/com/graphhopper/reader/osm/GraphHopperOSMTest.java
@@ -490,7 +490,7 @@ public class GraphHopperOSMTest {
                 setGraphHopperLocation(ghLoc);
         instance.load();
         assertEquals(5, instance.getBaseGraph().getNodes());
-        assertEquals("foot_access,foot_average_speed,foot_priority,car_access,car_average_speed,foot_subnetwork,car_subnetwork,roundabout,road_class,road_class_link,road_environment,max_speed,road_access,ferry_speed,foot_network",
+        assertEquals("foot_access,foot_average_speed,foot_priority,car_access,car_average_speed,foot_subnetwork,car_subnetwork,max_speed,road_class,road_class_link,road_environment,foot_network,roundabout,ferry_speed",
                 instance.getEncodingManager().getEncodedValues().stream().map(EncodedValue::getName).collect(Collectors.joining(",")));
     }
 
@@ -530,7 +530,7 @@ public class GraphHopperOSMTest {
                 setOSMFile(testOsm3);
         instance.load();
         assertEquals(5, instance.getBaseGraph().getNodes());
-        assertEquals("foot_access,foot_average_speed,foot_priority,car_access,car_average_speed,foot_subnetwork,car_subnetwork,roundabout,road_class,road_class_link,road_environment,max_speed,road_access,ferry_speed,foot_network", instance.getEncodingManager().getEncodedValues().stream().map(EncodedValue::getName).collect(Collectors.joining(",")));
+        assertEquals("foot_access,foot_average_speed,foot_priority,car_access,car_average_speed,foot_subnetwork,car_subnetwork,max_speed,road_class,road_class_link,road_environment,foot_network,roundabout,ferry_speed", instance.getEncodingManager().getEncodedValues().stream().map(EncodedValue::getName).collect(Collectors.joining(",")));
     }
 
     @Test
diff --git a/core/src/test/java/com/graphhopper/reader/osm/OSMReaderTest.java b/core/src/test/java/com/graphhopper/reader/osm/OSMReaderTest.java
index 5f98c2d4a..7b8e39371 100644
--- a/core/src/test/java/com/graphhopper/reader/osm/OSMReaderTest.java
+++ b/core/src/test/java/com/graphhopper/reader/osm/OSMReaderTest.java
@@ -923,7 +923,7 @@ public class OSMReaderTest {
 
     @Test
     public void testCountries() throws IOException {
-        EncodingManager em = new EncodingManager.Builder().build();
+        EncodingManager em = new EncodingManager.Builder().add(RoadAccess.create()).build();
         EnumEncodedValue<RoadAccess> roadAccessEnc = em.getEnumEncodedValue(RoadAccess.KEY, RoadAccess.class);
         OSMParsers osmParsers = new OSMParsers();
         osmParsers.addWayTagParser(new OSMRoadAccessParser(roadAccessEnc, OSMRoadAccessParser.toOSMRestrictions(TransportationMode.CAR)));
@@ -955,7 +955,6 @@ public class OSMReaderTest {
         // see https://discuss.graphhopper.com/t/country-of-way-is-wrong-on-road-near-border-with-curvature/6908/2
         EnumEncodedValue<Country> countryEnc = Country.create();
         EncodingManager em = EncodingManager.start()
-                .add(VehicleEncodedValues.car(new PMap()))
                 .add(countryEnc)
                 .build();
         OSMParsers osmParsers = new OSMParsers()
diff --git a/core/src/test/java/com/graphhopper/routing/HeadingRoutingTest.java b/core/src/test/java/com/graphhopper/routing/HeadingRoutingTest.java
index 4640500d7..981a733cd 100644
--- a/core/src/test/java/com/graphhopper/routing/HeadingRoutingTest.java
+++ b/core/src/test/java/com/graphhopper/routing/HeadingRoutingTest.java
@@ -56,7 +56,13 @@ class HeadingRoutingTest {
         // Test enforce start direction
         BooleanEncodedValue accessEnc = VehicleAccess.create("car");
         DecimalEncodedValue speedEnc = VehicleSpeed.create("car", 5, 5, false);
-        EncodingManager encodingManager = new EncodingManager.Builder().add(accessEnc).add(speedEnc).add(Subnetwork.create("profile")).build();
+        EncodingManager encodingManager = new EncodingManager.Builder().add(accessEnc).add(speedEnc)
+                .add(RoadClass.create())
+                .add(RoadClassLink.create())
+                .add(RoadEnvironment.create())
+                .add(Roundabout.create())
+                .add(MaxSpeed.create())
+                .add(Subnetwork.create("profile")).build();
         BaseGraph graph = createSquareGraph(encodingManager, accessEnc, speedEnc);
         Router router = createRouter(graph, encodingManager);
 
@@ -80,7 +86,13 @@ class HeadingRoutingTest {
         // Test enforce south start direction and east end direction
         BooleanEncodedValue accessEnc = VehicleAccess.create("car");
         DecimalEncodedValue speedEnc = VehicleSpeed.create("car", 5, 5, false);
-        EncodingManager encodingManager = new EncodingManager.Builder().add(accessEnc).add(speedEnc).add(Subnetwork.create("profile")).build();
+        EncodingManager encodingManager = new EncodingManager.Builder().add(accessEnc).add(speedEnc)
+                .add(RoadClass.create())
+                .add(RoadClassLink.create())
+                .add(RoadEnvironment.create())
+                .add(Roundabout.create())
+                .add(MaxSpeed.create())
+                .add(Subnetwork.create("profile")).build();
         BaseGraph graph = createSquareGraph(encodingManager, accessEnc, speedEnc);
         Router router = createRouter(graph, encodingManager);
 
@@ -108,7 +120,13 @@ class HeadingRoutingTest {
     public void headingTest3() {
         BooleanEncodedValue accessEnc = VehicleAccess.create("car");
         DecimalEncodedValue speedEnc = VehicleSpeed.create("car", 5, 5, false);
-        EncodingManager encodingManager = new EncodingManager.Builder().add(accessEnc).add(speedEnc).add(Subnetwork.create("profile")).build();
+        EncodingManager encodingManager = new EncodingManager.Builder().add(accessEnc).add(speedEnc)
+                .add(RoadClass.create())
+                .add(RoadClassLink.create())
+                .add(RoadEnvironment.create())
+                .add(Roundabout.create())
+                .add(MaxSpeed.create())
+                .add(Subnetwork.create("profile")).build();
         BaseGraph graph = createSquareGraph(encodingManager, accessEnc, speedEnc);
         Router router = createRouter(graph, encodingManager);
 
@@ -134,7 +152,13 @@ class HeadingRoutingTest {
         // Test straight via routing
         BooleanEncodedValue accessEnc = VehicleAccess.create("car");
         DecimalEncodedValue speedEnc = VehicleSpeed.create("car", 5, 5, false);
-        EncodingManager encodingManager = new EncodingManager.Builder().add(accessEnc).add(speedEnc).add(Subnetwork.create("profile")).build();
+        EncodingManager encodingManager = new EncodingManager.Builder().add(accessEnc).add(speedEnc)
+                .add(RoadClass.create())
+                .add(RoadClassLink.create())
+                .add(RoadEnvironment.create())
+                .add(Roundabout.create())
+                .add(MaxSpeed.create())
+                .add(Subnetwork.create("profile")).build();
         BaseGraph graph = createSquareGraph(encodingManager, accessEnc, speedEnc);
         Router router = createRouter(graph, encodingManager);
 
@@ -160,7 +184,13 @@ class HeadingRoutingTest {
         // Test independence of previous enforcement for subsequent paths
         BooleanEncodedValue accessEnc = VehicleAccess.create("car");
         DecimalEncodedValue speedEnc = VehicleSpeed.create("car", 5, 5, false);
-        EncodingManager encodingManager = new EncodingManager.Builder().add(accessEnc).add(speedEnc).add(Subnetwork.create("profile")).build();
+        EncodingManager encodingManager = new EncodingManager.Builder().add(accessEnc).add(speedEnc)
+                .add(RoadClass.create())
+                .add(RoadClassLink.create())
+                .add(RoadEnvironment.create())
+                .add(Roundabout.create())
+                .add(MaxSpeed.create())
+                .add(Subnetwork.create("profile")).build();
         BaseGraph graph = createSquareGraph(encodingManager, accessEnc, speedEnc);
         Router router = createRouter(graph, encodingManager);
 
@@ -185,7 +215,13 @@ class HeadingRoutingTest {
     public void testHeadingWithSnapFilter() {
         BooleanEncodedValue accessEnc = VehicleAccess.create("car");
         DecimalEncodedValue speedEnc = VehicleSpeed.create("car", 5, 5, false);
-        EncodingManager encodingManager = new EncodingManager.Builder().add(accessEnc).add(speedEnc).add(Subnetwork.create("profile")).build();
+        EncodingManager encodingManager = new EncodingManager.Builder().add(accessEnc).add(speedEnc)
+                .add(RoadClass.create())
+                .add(RoadClassLink.create())
+                .add(RoadEnvironment.create())
+                .add(Roundabout.create())
+                .add(MaxSpeed.create())
+                .add(Subnetwork.create("profile")).build();
         BaseGraph graph = createSquareGraphWithTunnel(encodingManager, accessEnc, speedEnc);
         Router router = createRouter(graph, encodingManager);
         // Start at 8 (slightly north to make it independent on some edge ordering and always use 8-3 or 3-8 as fallback)
@@ -248,7 +284,13 @@ class HeadingRoutingTest {
     public void testHeadingWithSnapFilter2() {
         BooleanEncodedValue accessEnc = VehicleAccess.create("car");
         DecimalEncodedValue speedEnc = VehicleSpeed.create("car", 5, 5, false);
-        EncodingManager encodingManager = new EncodingManager.Builder().add(accessEnc).add(speedEnc).add(Subnetwork.create("profile")).build();
+        EncodingManager encodingManager = new EncodingManager.Builder().add(accessEnc).add(speedEnc)
+                .add(RoadClass.create())
+                .add(RoadClassLink.create())
+                .add(RoadEnvironment.create())
+                .add(Roundabout.create())
+                .add(MaxSpeed.create())
+                .add(Subnetwork.create("profile")).build();
         BaseGraph graph = createSquareGraphWithTunnel(encodingManager, accessEnc, speedEnc);
         Router router = createRouter(graph, encodingManager);
         // Start at 8 (slightly east to snap to edge 1->5 per default)
@@ -282,7 +324,13 @@ class HeadingRoutingTest {
         // Test if snaps at tower nodes are ignored
         BooleanEncodedValue accessEnc = VehicleAccess.create("car");
         DecimalEncodedValue speedEnc = VehicleSpeed.create("car", 5, 5, false);
-        EncodingManager encodingManager = new EncodingManager.Builder().add(accessEnc).add(speedEnc).add(Subnetwork.create("profile")).build();
+        EncodingManager encodingManager = new EncodingManager.Builder().add(accessEnc).add(speedEnc)
+                .add(RoadClass.create())
+                .add(RoadClassLink.create())
+                .add(RoadEnvironment.create())
+                .add(Roundabout.create())
+                .add(MaxSpeed.create())
+                .add(Subnetwork.create("profile")).build();
         BaseGraph graph = createSquareGraph(encodingManager, accessEnc, speedEnc);
         Router router = createRouter(graph, encodingManager);
 
diff --git a/core/src/test/java/com/graphhopper/routing/PathTest.java b/core/src/test/java/com/graphhopper/routing/PathTest.java
index 1ee48734d..a04369130 100644
--- a/core/src/test/java/com/graphhopper/routing/PathTest.java
+++ b/core/src/test/java/com/graphhopper/routing/PathTest.java
@@ -46,13 +46,18 @@ import static org.junit.jupiter.api.Assertions.*;
 public class PathTest {
     private final BooleanEncodedValue carAccessEnc = new SimpleBooleanEncodedValue("access", true);
     private final DecimalEncodedValue carAvSpeedEnc = new DecimalEncodedValueImpl("speed", 5, 5, false);
-    private final EncodingManager carManager = EncodingManager.start().add(carAccessEnc).add(carAvSpeedEnc).build();
+    private final EncodingManager carManager = EncodingManager.start().add(carAccessEnc).add(carAvSpeedEnc)
+            .add(Roundabout.create()).add(RoadClass.create()).add(RoadClassLink.create()).add(MaxSpeed.create()).build();
     private final BooleanEncodedValue mixedCarAccessEnc = new SimpleBooleanEncodedValue("mixed_car_access", true);
     private final DecimalEncodedValue mixedCarSpeedEnc = new DecimalEncodedValueImpl("mixed_car_speed", 5, 5, false);
     private final BooleanEncodedValue mixedFootAccessEnc = new SimpleBooleanEncodedValue("mixed_foot_access", true);
     private final DecimalEncodedValue mixedFootSpeedEnc = new DecimalEncodedValueImpl("mixed_foot_speed", 4, 1, false);
     private final EncodingManager mixedEncodingManager = EncodingManager.start().add(mixedCarAccessEnc).
-            add(mixedCarSpeedEnc).add(mixedFootAccessEnc).add(mixedFootSpeedEnc).build();
+            add(mixedCarSpeedEnc).add(mixedFootAccessEnc).add(mixedFootSpeedEnc).
+            add(RoadClass.create()).
+            add(RoadClassLink.create()).
+            add(MaxSpeed.create()).
+            add(Roundabout.create()).build();
     private final TranslationMap trMap = TranslationMapTest.SINGLETON;
     private final Translation tr = trMap.getWithFallBack(Locale.US);
     private final RoundaboutGraph roundaboutGraph = new RoundaboutGraph();
diff --git a/core/src/test/java/com/graphhopper/routing/PriorityRoutingTest.java b/core/src/test/java/com/graphhopper/routing/PriorityRoutingTest.java
index 5f1c51917..ddee361fc 100644
--- a/core/src/test/java/com/graphhopper/routing/PriorityRoutingTest.java
+++ b/core/src/test/java/com/graphhopper/routing/PriorityRoutingTest.java
@@ -43,7 +43,7 @@ public class PriorityRoutingTest {
         BooleanEncodedValue accessEnc = new SimpleBooleanEncodedValue("access", false);
         DecimalEncodedValue speedEnc = new DecimalEncodedValueImpl("speed", 4, 2, false);
         DecimalEncodedValue priorityEnc = new DecimalEncodedValueImpl("priority", 4, PriorityCode.getFactor(1), false);
-        EncodingManager em = EncodingManager.start().add(accessEnc).add(speedEnc).add(priorityEnc).build();
+        EncodingManager em = EncodingManager.start().add(accessEnc).add(speedEnc).add(priorityEnc).add(RoadClass.create()).build();
         BaseGraph graph = new BaseGraph.Builder(em).create();
         NodeAccess na = graph.getNodeAccess();
         na.setNode(0, 48.0, 11.0);
diff --git a/core/src/test/java/com/graphhopper/routing/util/EncodingManagerTest.java b/core/src/test/java/com/graphhopper/routing/util/EncodingManagerTest.java
index 2ecac791e..23b1a5a17 100644
--- a/core/src/test/java/com/graphhopper/routing/util/EncodingManagerTest.java
+++ b/core/src/test/java/com/graphhopper/routing/util/EncodingManagerTest.java
@@ -17,10 +17,7 @@
  */
 package com.graphhopper.routing.util;
 
-import com.graphhopper.routing.ev.DecimalEncodedValueImpl;
-import com.graphhopper.routing.ev.RoadAccess;
-import com.graphhopper.routing.ev.VehicleAccess;
-import com.graphhopper.routing.ev.VehicleSpeed;
+import com.graphhopper.routing.ev.*;
 import com.graphhopper.routing.util.parsers.BikeAccessParser;
 import com.graphhopper.routing.util.parsers.CarAccessParser;
 import com.graphhopper.routing.util.parsers.FootAccessParser;
@@ -39,10 +36,11 @@ public class EncodingManagerTest {
     @Test
     public void testSupportFords() {
         EncodingManager manager = new EncodingManager.Builder()
-                .add(VehicleEncodedValues.car(new PMap()))
-                .add(VehicleEncodedValues.bike(new PMap()))
-                .add(VehicleEncodedValues.foot(new PMap())).
-                build();
+                .add(VehicleAccess.create("car"))
+                .add(VehicleAccess.create("bike"))
+                .add(VehicleAccess.create("foot"))
+                .add(Roundabout.create())
+                .build();
 
         // 1) default -> no block fords
         assertFalse(new CarAccessParser(manager, new PMap()).isBlockFords());
diff --git a/core/src/test/java/com/graphhopper/routing/util/SnapPreventionEdgeFilterTest.java b/core/src/test/java/com/graphhopper/routing/util/SnapPreventionEdgeFilterTest.java
index ae55b7548..dccf95567 100644
--- a/core/src/test/java/com/graphhopper/routing/util/SnapPreventionEdgeFilterTest.java
+++ b/core/src/test/java/com/graphhopper/routing/util/SnapPreventionEdgeFilterTest.java
@@ -17,7 +17,7 @@ public class SnapPreventionEdgeFilterTest {
     @Test
     public void accept() {
         EdgeFilter trueFilter = edgeState -> true;
-        EncodingManager em = new EncodingManager.Builder().build();
+        EncodingManager em = new EncodingManager.Builder().add(RoadClass.create()).add(RoadEnvironment.create()).build();
         EnumEncodedValue<RoadClass> rcEnc = em.getEnumEncodedValue(RoadClass.KEY, RoadClass.class);
         EnumEncodedValue<RoadEnvironment> reEnc = em.getEnumEncodedValue(RoadEnvironment.KEY, RoadEnvironment.class);
         SnapPreventionEdgeFilter filter = new SnapPreventionEdgeFilter(trueFilter, rcEnc, reEnc, Arrays.asList("motorway", "ferry"));
@@ -35,4 +35,4 @@ public class SnapPreventionEdgeFilterTest {
         edge.set(rcEnc, RoadClass.MOTORWAY);
         assertFalse(filter.accept(edge));
     }
-}
\ No newline at end of file
+}
diff --git a/core/src/test/java/com/graphhopper/routing/util/parsers/AbstractBikeTagParserTester.java b/core/src/test/java/com/graphhopper/routing/util/parsers/AbstractBikeTagParserTester.java
index f412ff724..aa319f80a 100644
--- a/core/src/test/java/com/graphhopper/routing/util/parsers/AbstractBikeTagParserTester.java
+++ b/core/src/test/java/com/graphhopper/routing/util/parsers/AbstractBikeTagParserTester.java
@@ -24,7 +24,6 @@ import com.graphhopper.routing.ev.*;
 import com.graphhopper.routing.util.EncodingManager;
 import com.graphhopper.routing.util.OSMParsers;
 import com.graphhopper.routing.util.PriorityCode;
-import com.graphhopper.routing.util.VehicleTagParsers;
 import com.graphhopper.storage.IntsRef;
 import com.graphhopper.util.Helper;
 import com.graphhopper.util.PMap;
@@ -54,10 +53,9 @@ public abstract class AbstractBikeTagParserTester {
     @BeforeEach
     public void setUp() {
         encodingManager = createEncodingManager();
-        VehicleTagParsers parsers = createBikeTagParsers(encodingManager, new PMap("block_fords=true"));
-        accessParser = (BikeCommonAccessParser) parsers.getAccessParser();
-        speedParser = (BikeCommonAverageSpeedParser) parsers.getSpeedParser();
-        priorityParser = (BikeCommonPriorityParser) parsers.getPriorityParser();
+        accessParser = createAccessParser(encodingManager, new PMap("block_fords=true"));
+        speedParser = createAverageSpeedParser(encodingManager);
+        priorityParser = createPriorityParser(encodingManager);
         osmParsers = new OSMParsers()
                 .addRelationTagParser(relConfig -> new OSMBikeNetworkTagParser(encodingManager.getEnumEncodedValue(BikeNetwork.KEY, RouteNetwork.class), relConfig))
                 .addRelationTagParser(relConfig -> new OSMMtbNetworkTagParser(encodingManager.getEnumEncodedValue(MtbNetwork.KEY, RouteNetwork.class), relConfig))
@@ -70,7 +68,11 @@ public abstract class AbstractBikeTagParserTester {
 
     protected abstract EncodingManager createEncodingManager();
 
-    protected abstract VehicleTagParsers createBikeTagParsers(EncodedValueLookup lookup, PMap pMap);
+    protected abstract BikeCommonAccessParser createAccessParser(EncodedValueLookup lookup, PMap pMap);
+
+    protected abstract BikeCommonAverageSpeedParser createAverageSpeedParser(EncodedValueLookup lookup);
+
+    protected abstract BikeCommonPriorityParser createPriorityParser(EncodedValueLookup lookup);
 
     protected void assertPriority(PriorityCode expectedPrio, ReaderWay way) {
         IntsRef relFlags = osmParsers.handleRelationTags(new ReaderRelation(0), osmParsers.createRelationFlags());
@@ -528,7 +530,7 @@ public abstract class AbstractBikeTagParserTester {
     @Test
     void privateAndFords() {
         // defaults: do not block fords, block private
-        BikeCommonAccessParser bike = (BikeCommonAccessParser) createBikeTagParsers(encodingManager, new PMap()).getAccessParser();
+        BikeCommonAccessParser bike = createAccessParser(encodingManager, new PMap());
         assertFalse(bike.isBlockFords());
         assertTrue(bike.restrictedValues.contains("private"));
         assertFalse(bike.intendedValues.contains("private"));
@@ -537,7 +539,7 @@ public abstract class AbstractBikeTagParserTester {
         assertTrue(bike.isBarrier(node));
 
         // block fords, unblock private
-        bike = (BikeCommonAccessParser) createBikeTagParsers(encodingManager, new PMap("block_fords=true|block_private=false")).getAccessParser();
+        bike = createAccessParser(encodingManager, new PMap("block_fords=true|block_private=false"));
         assertTrue(bike.isBlockFords());
         assertFalse(bike.restrictedValues.contains("private"));
         assertTrue(bike.intendedValues.contains("private"));
diff --git a/core/src/test/java/com/graphhopper/routing/util/parsers/BikeTagParserTest.java b/core/src/test/java/com/graphhopper/routing/util/parsers/BikeTagParserTest.java
index ab2baba8c..5571e8329 100644
--- a/core/src/test/java/com/graphhopper/routing/util/parsers/BikeTagParserTest.java
+++ b/core/src/test/java/com/graphhopper/routing/util/parsers/BikeTagParserTest.java
@@ -20,11 +20,10 @@ package com.graphhopper.routing.util.parsers;
 import com.graphhopper.reader.ReaderNode;
 import com.graphhopper.reader.ReaderRelation;
 import com.graphhopper.reader.ReaderWay;
+import com.graphhopper.reader.osm.conditional.DateRangeParser;
 import com.graphhopper.routing.ev.*;
 import com.graphhopper.routing.util.EncodingManager;
 import com.graphhopper.routing.util.PriorityCode;
-import com.graphhopper.routing.util.VehicleEncodedValues;
-import com.graphhopper.routing.util.VehicleTagParsers;
 import com.graphhopper.storage.IntsRef;
 import com.graphhopper.util.PMap;
 import org.junit.jupiter.api.Test;
@@ -42,12 +41,31 @@ public class BikeTagParserTest extends AbstractBikeTagParserTester {
 
     @Override
     protected EncodingManager createEncodingManager() {
-        return new EncodingManager.Builder().add(VehicleEncodedValues.bike(new PMap())).build();
+        return new EncodingManager.Builder()
+                .add(VehicleAccess.create("bike"))
+                .add(VehicleSpeed.create("bike", 4, 2, false))
+                .add(VehiclePriority.create("bike", 4, PriorityCode.getFactor(1), false))
+                .add(Roundabout.create())
+                .add(Smoothness.create())
+                .add(FerrySpeed.create())
+                .add(RouteNetwork.create(BikeNetwork.KEY))
+                .add(RouteNetwork.create(MtbNetwork.KEY))
+                .build();
     }
 
     @Override
-    protected VehicleTagParsers createBikeTagParsers(EncodedValueLookup lookup, PMap pMap) {
-        return VehicleTagParsers.bike(lookup, pMap);
+    protected BikeCommonAccessParser createAccessParser(EncodedValueLookup lookup, PMap pMap) {
+        return (BikeCommonAccessParser) new BikeAccessParser(lookup, pMap).init(new DateRangeParser());
+    }
+
+    @Override
+    protected BikeCommonAverageSpeedParser createAverageSpeedParser(EncodedValueLookup lookup) {
+        return new BikeAverageSpeedParser(lookup);
+    }
+
+    @Override
+    protected BikeCommonPriorityParser createPriorityParser(EncodedValueLookup lookup) {
+        return new BikePriorityParser(lookup);
     }
 
     @Test
diff --git a/core/src/test/java/com/graphhopper/routing/util/parsers/CarTagParserTest.java b/core/src/test/java/com/graphhopper/routing/util/parsers/CarTagParserTest.java
index 14b722696..ffd34c273 100644
--- a/core/src/test/java/com/graphhopper/routing/util/parsers/CarTagParserTest.java
+++ b/core/src/test/java/com/graphhopper/routing/util/parsers/CarTagParserTest.java
@@ -57,6 +57,8 @@ public class CarTagParserTest {
                 .add(VehiclePriority.create("bike", 4, PriorityCode.getFactor(1), false))
                 .add(RouteNetwork.create(BikeNetwork.KEY))
                 .add(Smoothness.create())
+                .add(Roundabout.create())
+                .add(FerrySpeed.create())
                 .build();
     }
 
@@ -606,6 +608,7 @@ public class CarTagParserTest {
         EncodingManager em = new EncodingManager.Builder()
                 .add(new SimpleBooleanEncodedValue("car_access", true))
                 .add(smallFactorSpeedEnc)
+                .add(FerrySpeed.create())
                 .addTurnCostEncodedValue(TurnCost.create("car", 1))
                 .build();
         CarAverageSpeedParser speedParser = new CarAverageSpeedParser(em);
@@ -688,6 +691,7 @@ public class CarTagParserTest {
         EncodingManager lowFactorEm = new EncodingManager.Builder()
                 .add(new SimpleBooleanEncodedValue(VehicleAccess.key("car"), true))
                 .add(lowFactorSpeedEnc)
+                .add(FerrySpeed.create())
                 .build();
         edgeIntAccess = new ArrayEdgeIntAccess(lowFactorEm.getIntsForFlags());
         new CarAverageSpeedParser(lowFactorEm).handleWayTags(edgeId, edgeIntAccess, way);
diff --git a/core/src/test/java/com/graphhopper/routing/util/parsers/FootTagParserTest.java b/core/src/test/java/com/graphhopper/routing/util/parsers/FootTagParserTest.java
index becc43da2..93a058f7d 100644
--- a/core/src/test/java/com/graphhopper/routing/util/parsers/FootTagParserTest.java
+++ b/core/src/test/java/com/graphhopper/routing/util/parsers/FootTagParserTest.java
@@ -53,6 +53,7 @@ public class FootTagParserTest {
             .add(footAccessEnc).add(footAvgSpeedEnc).add(footPriorityEnc).add(RouteNetwork.create(FootNetwork.KEY))
             .add(bikeAccessEnc).add(bikeAvgSpeedEnc).add(RouteNetwork.create(BikeNetwork.KEY))
             .add(carAccessEnc).add(carAvSpeedEnc)
+            .add(FerrySpeed.create())
             .build();
     private final FootAccessParser accessParser = new FootAccessParser(encodingManager, new PMap());
     private final FootAverageSpeedParser speedParser = new FootAverageSpeedParser(encodingManager);
diff --git a/core/src/test/java/com/graphhopper/routing/util/parsers/HikeCustomModelTest.java b/core/src/test/java/com/graphhopper/routing/util/parsers/HikeCustomModelTest.java
index 234f0a257..3d2986484 100644
--- a/core/src/test/java/com/graphhopper/routing/util/parsers/HikeCustomModelTest.java
+++ b/core/src/test/java/com/graphhopper/routing/util/parsers/HikeCustomModelTest.java
@@ -2,11 +2,11 @@ package com.graphhopper.routing.util.parsers;
 
 import com.graphhopper.jackson.Jackson;
 import com.graphhopper.reader.ReaderWay;
+import com.graphhopper.reader.osm.conditional.DateRangeParser;
 import com.graphhopper.routing.ev.*;
 import com.graphhopper.routing.util.EncodingManager;
 import com.graphhopper.routing.util.OSMParsers;
-import com.graphhopper.routing.util.VehicleEncodedValues;
-import com.graphhopper.routing.util.VehicleTagParsers;
+import com.graphhopper.routing.util.PriorityCode;
 import com.graphhopper.routing.weighting.custom.CustomModelParser;
 import com.graphhopper.routing.weighting.custom.CustomWeighting;
 import com.graphhopper.storage.BaseGraph;
@@ -28,18 +28,26 @@ public class HikeCustomModelTest {
     public void setup() {
         IntEncodedValue hikeRating = HikeRating.create();
         em = new EncodingManager.Builder().
-                add(VehicleEncodedValues.roads(new PMap())).
-                add(VehicleEncodedValues.foot(new PMap())).
+                add(VehicleAccess.create("roads")).
+                add(VehicleSpeed.create("roads", 7, 2, true)).
+                add(VehicleAccess.create("foot")).
+                add(VehicleSpeed.create("foot", 4, 1, false)).
+                add(VehiclePriority.create("foot", 4, PriorityCode.getFactor(1), false)).
+                add(FerrySpeed.create()).
+                add(RouteNetwork.create(FootNetwork.KEY)).
+                add(RoadAccess.create()).
                 add(hikeRating).build();
 
         roadsSpeedEnc = em.getDecimalEncodedValue(VehicleSpeed.key("roads"));
         parsers = new OSMParsers().
                 addWayTagParser(new OSMHikeRatingParser(hikeRating));
 
-        for (TagParser p : VehicleTagParsers.foot(em, new PMap()).getTagParsers())
-            parsers.addWayTagParser(p);
-        for (TagParser p : VehicleTagParsers.roads(em).getTagParsers())
-            parsers.addWayTagParser(p);
+        parsers.addWayTagParser(new FootAccessParser(em, new PMap()).init(new DateRangeParser()));
+        parsers.addWayTagParser(new FootAverageSpeedParser(em));
+        parsers.addWayTagParser(new FootPriorityParser(em));
+
+        parsers.addWayTagParser(new RoadsAccessParser(em));
+        parsers.addWayTagParser(new RoadsAverageSpeedParser(em));
     }
 
     EdgeIteratorState createEdge(ReaderWay way) {
diff --git a/core/src/test/java/com/graphhopper/routing/util/parsers/ModeAccessParserTest.java b/core/src/test/java/com/graphhopper/routing/util/parsers/ModeAccessParserTest.java
index 646a4f3e4..3e075f3cd 100644
--- a/core/src/test/java/com/graphhopper/routing/util/parsers/ModeAccessParserTest.java
+++ b/core/src/test/java/com/graphhopper/routing/util/parsers/ModeAccessParserTest.java
@@ -15,7 +15,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
 
 class ModeAccessParserTest {
 
-    private final EncodingManager em = new EncodingManager.Builder().add(BusAccess.create()).build();
+    private final EncodingManager em = new EncodingManager.Builder().add(Roundabout.create()).add(BusAccess.create()).build();
     private final ModeAccessParser parser = new ModeAccessParser(TransportationMode.BUS, em.getBooleanEncodedValue(BusAccess.KEY), em.getBooleanEncodedValue(Roundabout.KEY));
     private final BooleanEncodedValue busAccessEnc = em.getBooleanEncodedValue(BusAccess.KEY);
 
@@ -147,7 +147,7 @@ class ModeAccessParserTest {
     @Test
     public void testMotorcycleYes() {
         BooleanEncodedValue mcAccessEnc = new SimpleBooleanEncodedValue("motorcycle_access", true);
-        EncodingManager mcEM = new EncodingManager.Builder().add(mcAccessEnc).build();
+        EncodingManager mcEM = new EncodingManager.Builder().add(mcAccessEnc).add(Roundabout.create()).build();
         ModeAccessParser mcParser = new ModeAccessParser(TransportationMode.MOTORCYCLE, mcAccessEnc, mcEM.getBooleanEncodedValue(Roundabout.KEY));
 
         int edgeId = 0;
diff --git a/core/src/test/java/com/graphhopper/routing/util/parsers/MountainBikeTagParserTest.java b/core/src/test/java/com/graphhopper/routing/util/parsers/MountainBikeTagParserTest.java
index af1cc5f2a..ab3acc6fe 100644
--- a/core/src/test/java/com/graphhopper/routing/util/parsers/MountainBikeTagParserTest.java
+++ b/core/src/test/java/com/graphhopper/routing/util/parsers/MountainBikeTagParserTest.java
@@ -20,10 +20,10 @@ package com.graphhopper.routing.util.parsers;
 import com.graphhopper.reader.ReaderNode;
 import com.graphhopper.reader.ReaderRelation;
 import com.graphhopper.reader.ReaderWay;
-import com.graphhopper.routing.ev.EncodedValueLookup;
+import com.graphhopper.reader.osm.conditional.DateRangeParser;
+import com.graphhopper.routing.ev.*;
 import com.graphhopper.routing.util.EncodingManager;
-import com.graphhopper.routing.util.VehicleEncodedValues;
-import com.graphhopper.routing.util.VehicleTagParsers;
+import com.graphhopper.routing.util.PriorityCode;
 import com.graphhopper.util.PMap;
 import org.junit.jupiter.api.Test;
 
@@ -35,12 +35,31 @@ import static org.junit.jupiter.api.Assertions.*;
 public class MountainBikeTagParserTest extends AbstractBikeTagParserTester {
     @Override
     protected EncodingManager createEncodingManager() {
-        return new EncodingManager.Builder().add(VehicleEncodedValues.mountainbike(new PMap())).build();
+        return new EncodingManager.Builder()
+                .add(VehicleAccess.create("mtb"))
+                .add(VehicleSpeed.create("mtb", 4, 2, false))
+                .add(VehiclePriority.create("mtb", 4, PriorityCode.getFactor(1), false))
+                .add(Roundabout.create())
+                .add(Smoothness.create())
+                .add(FerrySpeed.create())
+                .add(RouteNetwork.create(BikeNetwork.KEY))
+                .add(RouteNetwork.create(MtbNetwork.KEY))
+                .build();
     }
 
     @Override
-    protected VehicleTagParsers createBikeTagParsers(EncodedValueLookup lookup, PMap pMap) {
-        return VehicleTagParsers.mtb(lookup, pMap);
+    protected BikeCommonAccessParser createAccessParser(EncodedValueLookup lookup, PMap pMap) {
+        return (BikeCommonAccessParser) new MountainBikeAccessParser(lookup, pMap).init(new DateRangeParser());
+    }
+
+    @Override
+    protected BikeCommonAverageSpeedParser createAverageSpeedParser(EncodedValueLookup lookup) {
+        return new MountainBikeAverageSpeedParser(lookup);
+    }
+
+    @Override
+    protected BikeCommonPriorityParser createPriorityParser(EncodedValueLookup lookup) {
+        return new MountainBikePriorityParser(lookup);
     }
 
     @Test
diff --git a/core/src/test/java/com/graphhopper/routing/util/parsers/OSMGetOffBikeParserTest.java b/core/src/test/java/com/graphhopper/routing/util/parsers/OSMGetOffBikeParserTest.java
index c3dc0a29e..39111f7fa 100644
--- a/core/src/test/java/com/graphhopper/routing/util/parsers/OSMGetOffBikeParserTest.java
+++ b/core/src/test/java/com/graphhopper/routing/util/parsers/OSMGetOffBikeParserTest.java
@@ -2,12 +2,8 @@ package com.graphhopper.routing.util.parsers;
 
 import com.graphhopper.reader.ReaderWay;
 import com.graphhopper.reader.osm.conditional.DateRangeParser;
-import com.graphhopper.routing.ev.ArrayEdgeIntAccess;
-import com.graphhopper.routing.ev.BooleanEncodedValue;
-import com.graphhopper.routing.ev.EdgeIntAccess;
-import com.graphhopper.routing.ev.GetOffBike;
+import com.graphhopper.routing.ev.*;
 import com.graphhopper.routing.util.EncodingManager;
-import com.graphhopper.routing.util.VehicleEncodedValues;
 import com.graphhopper.storage.IntsRef;
 import com.graphhopper.util.PMap;
 import org.junit.jupiter.api.Test;
@@ -21,7 +17,7 @@ public class OSMGetOffBikeParserTest {
     private final OSMGetOffBikeParser getOffParser;
 
     public OSMGetOffBikeParserTest() {
-        EncodingManager em = new EncodingManager.Builder().add(offBikeEnc).add(VehicleEncodedValues.bike(new PMap()).getAccessEnc()).build();
+        EncodingManager em = new EncodingManager.Builder().add(offBikeEnc).add(VehicleAccess.create("bike")).add(Roundabout.create()).build();
         accessParser = new BikeAccessParser(em, new PMap());
         accessParser.init(new DateRangeParser());
         getOffParser = new OSMGetOffBikeParser(offBikeEnc, accessParser.getAccessEnc());
@@ -134,4 +130,4 @@ public class OSMGetOffBikeParserTest {
         getOffParser.handleWayTags(edgeId, edgeIntAccess, way, rel);
         return offBikeEnc.getBool(false, edgeId, edgeIntAccess);
     }
-}
\ No newline at end of file
+}
diff --git a/core/src/test/java/com/graphhopper/routing/util/parsers/RacingBikeTagParserTest.java b/core/src/test/java/com/graphhopper/routing/util/parsers/RacingBikeTagParserTest.java
index a0ab5f29a..feef39f2b 100644
--- a/core/src/test/java/com/graphhopper/routing/util/parsers/RacingBikeTagParserTest.java
+++ b/core/src/test/java/com/graphhopper/routing/util/parsers/RacingBikeTagParserTest.java
@@ -19,11 +19,10 @@ package com.graphhopper.routing.util.parsers;
 
 import com.graphhopper.reader.ReaderRelation;
 import com.graphhopper.reader.ReaderWay;
+import com.graphhopper.reader.osm.conditional.DateRangeParser;
 import com.graphhopper.routing.ev.*;
 import com.graphhopper.routing.util.EncodingManager;
 import com.graphhopper.routing.util.PriorityCode;
-import com.graphhopper.routing.util.VehicleEncodedValues;
-import com.graphhopper.routing.util.VehicleTagParsers;
 import com.graphhopper.util.PMap;
 import org.junit.jupiter.api.Test;
 
@@ -42,12 +41,31 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
 public class RacingBikeTagParserTest extends AbstractBikeTagParserTester {
     @Override
     protected EncodingManager createEncodingManager() {
-        return new EncodingManager.Builder().add(VehicleEncodedValues.racingbike(new PMap())).build();
+        return new EncodingManager.Builder()
+                .add(VehicleAccess.create("racingbike"))
+                .add(VehicleSpeed.create("racingbike", 4, 2, false))
+                .add(VehiclePriority.create("racingbike", 4, PriorityCode.getFactor(1), false))
+                .add(Roundabout.create())
+                .add(Smoothness.create())
+                .add(FerrySpeed.create())
+                .add(RouteNetwork.create(BikeNetwork.KEY))
+                .add(RouteNetwork.create(MtbNetwork.KEY))
+                .build();
+    }
+
+    @Override
+    protected BikeCommonAccessParser createAccessParser(EncodedValueLookup lookup, PMap pMap) {
+        return (BikeCommonAccessParser) new RacingBikeAccessParser(lookup, pMap).init(new DateRangeParser());
+    }
+
+    @Override
+    protected BikeCommonAverageSpeedParser createAverageSpeedParser(EncodedValueLookup lookup) {
+        return new RacingBikeAverageSpeedParser(lookup);
     }
 
     @Override
-    protected VehicleTagParsers createBikeTagParsers(EncodedValueLookup lookup, PMap pMap) {
-        return VehicleTagParsers.racingbike(lookup, pMap);
+    protected BikeCommonPriorityParser createPriorityParser(EncodedValueLookup lookup) {
+        return new RacingBikePriorityParser(lookup);
     }
 
     @Test
@@ -220,6 +238,7 @@ public class RacingBikeTagParserTest extends AbstractBikeTagParserTester {
                 .add(accessEnc).add(speedEnc).add(priorityEnc)
                 .add(RouteNetwork.create(BikeNetwork.KEY))
                 .add(Smoothness.create())
+                .add(FerrySpeed.create())
                 .build();
         List<TagParser> parsers = Arrays.asList(
                 new RacingBikeAverageSpeedParser(encodingManager),
diff --git a/core/src/test/java/com/graphhopper/routing/util/parsers/RoadsTagParserTest.java b/core/src/test/java/com/graphhopper/routing/util/parsers/RoadsTagParserTest.java
index da394ece9..5c69a4f79 100644
--- a/core/src/test/java/com/graphhopper/routing/util/parsers/RoadsTagParserTest.java
+++ b/core/src/test/java/com/graphhopper/routing/util/parsers/RoadsTagParserTest.java
@@ -5,15 +5,13 @@ import com.graphhopper.routing.ev.ArrayEdgeIntAccess;
 import com.graphhopper.routing.ev.EdgeIntAccess;
 import com.graphhopper.routing.ev.VehicleSpeed;
 import com.graphhopper.routing.util.EncodingManager;
-import com.graphhopper.routing.util.VehicleEncodedValues;
-import com.graphhopper.util.PMap;
 import org.junit.jupiter.api.Test;
 
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 class RoadsTagParserTest {
 
-    private final EncodingManager encodingManager = new EncodingManager.Builder().add(VehicleEncodedValues.roads(new PMap())).build();
+    private final EncodingManager encodingManager = new EncodingManager.Builder().add(VehicleSpeed.create("roads", 7, 2, true)).build();
     private final RoadsAverageSpeedParser parser;
 
     public RoadsTagParserTest() {
diff --git a/core/src/test/java/com/graphhopper/routing/util/parsers/TagParsingTest.java b/core/src/test/java/com/graphhopper/routing/util/parsers/TagParsingTest.java
index 45b7087ea..fc3341d0f 100644
--- a/core/src/test/java/com/graphhopper/routing/util/parsers/TagParsingTest.java
+++ b/core/src/test/java/com/graphhopper/routing/util/parsers/TagParsingTest.java
@@ -54,6 +54,7 @@ class TagParsingTest {
                 .add(bike2AccessEnc).add(bike2SpeedEnc).add(bike2PriorityEnc)
                 .add(bikeNetworkEnc)
                 .add(Smoothness.create())
+                .add(RoadClass.create())
                 .build();
         BikePriorityParser bike1Parser = new BikePriorityParser(bike1PriorityEnc, bike1SpeedEnc, bikeNetworkEnc);
         BikePriorityParser bike2Parser = new BikePriorityParser(bike2PriorityEnc, bike2SpeedEnc, bikeNetworkEnc) {
@@ -102,6 +103,7 @@ class TagParsingTest {
                 .add(mtbAccessEnc).add(mtbSpeedEnc).add(mtbPriorityEnc)
                 .add(bikeNetworkEnc)
                 .add(Smoothness.create())
+                .add(RoadClass.create())
                 .build();
         BikePriorityParser bikeTagParser = new BikePriorityParser(em);
         MountainBikePriorityParser mtbTagParser = new MountainBikePriorityParser(em);
@@ -138,6 +140,7 @@ class TagParsingTest {
                 .add(mtbAccessEnc).add(VehicleSpeed.create("mtb", 4, 2, false)).add(VehiclePriority.create("mtb", 4, PriorityCode.getFactor(1), false))
                 .add(RouteNetwork.create(FootNetwork.KEY))
                 .add(RouteNetwork.create(BikeNetwork.KEY))
+                .add(Roundabout.create())
                 .add(Smoothness.create())
                 .build();
 
diff --git a/core/src/test/java/com/graphhopper/routing/weighting/FastestWeightingTest.java b/core/src/test/java/com/graphhopper/routing/weighting/FastestWeightingTest.java
index 7a0b72a3f..b02f3d8f0 100644
--- a/core/src/test/java/com/graphhopper/routing/weighting/FastestWeightingTest.java
+++ b/core/src/test/java/com/graphhopper/routing/weighting/FastestWeightingTest.java
@@ -143,7 +143,8 @@ public class FastestWeightingTest {
         DecimalEncodedValue carSpeedEnc = new DecimalEncodedValueImpl("car_speed", 5, 5, false);
         BooleanEncodedValue bikeAccessEnc = new SimpleBooleanEncodedValue("bike_access", true);
         DecimalEncodedValue bikeSpeedEnc = new DecimalEncodedValueImpl("bike_speed", 4, 2, false);
-        EncodingManager em = EncodingManager.start().add(carAccessEnc).add(carSpeedEnc).add(bikeAccessEnc).add(bikeSpeedEnc).build();
+        EncodingManager em = EncodingManager.start().add(carAccessEnc).add(carSpeedEnc)
+                .add(bikeAccessEnc).add(bikeSpeedEnc).add(RoadAccess.create()).build();
         BaseGraph graph = new BaseGraph.Builder(em).create();
         EdgeIteratorState edge = graph.edge(0, 1).setDistance(1000);
         edge.set(carAccessEnc, true, true);
@@ -173,7 +174,8 @@ public class FastestWeightingTest {
         DecimalEncodedValue carSpeedEnc = new DecimalEncodedValueImpl("car_speed", 5, 5, false);
         BooleanEncodedValue bikeAccessEnc = new SimpleBooleanEncodedValue("bike_access", true);
         DecimalEncodedValue bikeSpeedEnc = new DecimalEncodedValueImpl("bike_speed", 4, 2, false);
-        EncodingManager em = EncodingManager.start().add(carAccessEnc).add(carSpeedEnc).add(bikeAccessEnc).add(bikeSpeedEnc).build();
+        EncodingManager em = EncodingManager.start().add(carAccessEnc).add(carSpeedEnc).add(bikeAccessEnc).add(bikeSpeedEnc).
+                add(RoadAccess.create()).build();
         BaseGraph graph = new BaseGraph.Builder(em).create();
         EdgeIteratorState edge = graph.edge(0, 1).setDistance(1000);
         edge.set(carAccessEnc, true, true);
diff --git a/core/src/test/java/com/graphhopper/routing/weighting/custom/CustomModelParserTest.java b/core/src/test/java/com/graphhopper/routing/weighting/custom/CustomModelParserTest.java
index 0b927fa7d..5c3cbb91b 100644
--- a/core/src/test/java/com/graphhopper/routing/weighting/custom/CustomModelParserTest.java
+++ b/core/src/test/java/com/graphhopper/routing/weighting/custom/CustomModelParserTest.java
@@ -62,7 +62,7 @@ class CustomModelParserTest {
         countryEnc = Country.create();
         stateEnc = State.create();
         encodingManager = new EncodingManager.Builder().add(accessEnc).add(avgSpeedEnc).add(new EnumEncodedValue<>("bus", MyBus.class))
-                .add(stateEnc).add(countryEnc).add(MaxSpeed.create()).add(Surface.create()).build();
+                .add(stateEnc).add(countryEnc).add(MaxSpeed.create()).add(Surface.create()).add(RoadClass.create()).add(RoadEnvironment.create()).build();
         graph = new BaseGraph.Builder(encodingManager).create();
         roadClassEnc = encodingManager.getEnumEncodedValue(RoadClass.KEY, RoadClass.class);
         maxSpeed = 140;
diff --git a/core/src/test/java/com/graphhopper/routing/weighting/custom/CustomWeightingHelperTest.java b/core/src/test/java/com/graphhopper/routing/weighting/custom/CustomWeightingHelperTest.java
index e26edc614..0625d01b2 100644
--- a/core/src/test/java/com/graphhopper/routing/weighting/custom/CustomWeightingHelperTest.java
+++ b/core/src/test/java/com/graphhopper/routing/weighting/custom/CustomWeightingHelperTest.java
@@ -1,11 +1,11 @@
 package com.graphhopper.routing.weighting.custom;
 
-import com.graphhopper.json.MinMax;
 import com.graphhopper.routing.ev.VehicleSpeed;
 import com.graphhopper.routing.util.EncodingManager;
-import com.graphhopper.routing.util.VehicleEncodedValues;
 import com.graphhopper.storage.BaseGraph;
-import com.graphhopper.util.*;
+import com.graphhopper.util.CustomModel;
+import com.graphhopper.util.EdgeIteratorState;
+import com.graphhopper.util.Helper;
 import com.graphhopper.util.shapes.Polygon;
 import org.junit.jupiter.api.Test;
 
@@ -59,7 +59,7 @@ class CustomWeightingHelperTest {
         customModel.addToSpeed(Else(MULTIPLY, "-0.5"));
 
         CustomWeightingHelper helper = new CustomWeightingHelper();
-        EncodingManager lookup = new EncodingManager.Builder().add(VehicleEncodedValues.car(new PMap())).build();
+        EncodingManager lookup = new EncodingManager.Builder().add(VehicleSpeed.create("car", 7, 2, false)).build();
         helper.init(customModel, lookup, lookup.getDecimalEncodedValue(VehicleSpeed.key("car")), null, null);
         IllegalArgumentException ret = assertThrows(IllegalArgumentException.class,
                 helper::calcMaxSpeed);
diff --git a/core/src/test/java/com/graphhopper/routing/weighting/custom/CustomWeightingTest.java b/core/src/test/java/com/graphhopper/routing/weighting/custom/CustomWeightingTest.java
index 981d902ff..233e039ea 100644
--- a/core/src/test/java/com/graphhopper/routing/weighting/custom/CustomWeightingTest.java
+++ b/core/src/test/java/com/graphhopper/routing/weighting/custom/CustomWeightingTest.java
@@ -41,6 +41,9 @@ class CustomWeightingTest {
                 .add(Toll.create())
                 .add(Hazmat.create())
                 .add(RouteNetwork.create(BikeNetwork.KEY))
+                .add(MaxSpeed.create())
+                .add(RoadClass.create())
+                .add(RoadClassLink.create())
                 .addTurnCostEncodedValue(turnRestrictionEnc)
                 .build();
         maxSpeedEnc = encodingManager.getDecimalEncodedValue(MaxSpeed.KEY);
@@ -445,7 +448,7 @@ class CustomWeightingTest {
         DecimalEncodedValue carSpeedEnc = new DecimalEncodedValueImpl("car_speed", 5, 5, false);
         BooleanEncodedValue bikeAccessEnc = new SimpleBooleanEncodedValue("bike_access", true);
         DecimalEncodedValue bikeSpeedEnc = new DecimalEncodedValueImpl("bike_speed", 4, 2, false);
-        EncodingManager em = EncodingManager.start().add(carAccessEnc).add(carSpeedEnc).add(bikeAccessEnc).add(bikeSpeedEnc).build();
+        EncodingManager em = EncodingManager.start().add(carAccessEnc).add(carSpeedEnc).add(bikeAccessEnc).add(bikeSpeedEnc).add(RoadAccess.create()).build();
         BaseGraph graph = new BaseGraph.Builder(em).create();
         EdgeIteratorState edge = graph.edge(0, 1).setDistance(1000);
         edge.set(carAccessEnc, true, true);
@@ -476,7 +479,7 @@ class CustomWeightingTest {
         DecimalEncodedValue carSpeedEnc = new DecimalEncodedValueImpl("car_speed", 5, 5, false);
         BooleanEncodedValue bikeAccessEnc = new SimpleBooleanEncodedValue("bike_access", true);
         DecimalEncodedValue bikeSpeedEnc = new DecimalEncodedValueImpl("bike_speed", 4, 2, false);
-        EncodingManager em = EncodingManager.start().add(carAccessEnc).add(carSpeedEnc).add(bikeAccessEnc).add(bikeSpeedEnc).build();
+        EncodingManager em = EncodingManager.start().add(carAccessEnc).add(carSpeedEnc).add(bikeAccessEnc).add(bikeSpeedEnc).add(RoadAccess.create()).build();
         BaseGraph graph = new BaseGraph.Builder(em).create();
         EdgeIteratorState edge = graph.edge(0, 1).setDistance(1000);
         edge.set(carAccessEnc, true, true);
diff --git a/core/src/test/java/com/graphhopper/routing/weighting/custom/FindMinMaxTest.java b/core/src/test/java/com/graphhopper/routing/weighting/custom/FindMinMaxTest.java
index b81055286..772bc9613 100644
--- a/core/src/test/java/com/graphhopper/routing/weighting/custom/FindMinMaxTest.java
+++ b/core/src/test/java/com/graphhopper/routing/weighting/custom/FindMinMaxTest.java
@@ -3,6 +3,7 @@ package com.graphhopper.routing.weighting.custom;
 import com.graphhopper.json.MinMax;
 import com.graphhopper.json.Statement;
 import com.graphhopper.routing.ev.EncodedValueLookup;
+import com.graphhopper.routing.ev.RoadEnvironment;
 import com.graphhopper.routing.util.EncodingManager;
 import com.graphhopper.util.CustomModel;
 import org.junit.jupiter.api.BeforeEach;
@@ -10,7 +11,6 @@ import org.junit.jupiter.api.Test;
 
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.HashSet;
 import java.util.List;
 
 import static com.graphhopper.json.Statement.*;
@@ -26,7 +26,7 @@ class FindMinMaxTest {
 
     @BeforeEach
     void setup() {
-        lookup = new EncodingManager.Builder().build();
+        lookup = new EncodingManager.Builder().add(RoadEnvironment.create()).build();
     }
 
     @Test
diff --git a/core/src/test/java/com/graphhopper/storage/AbstractGraphStorageTester.java b/core/src/test/java/com/graphhopper/storage/AbstractGraphStorageTester.java
index c0edccbbc..f580cfde7 100644
--- a/core/src/test/java/com/graphhopper/storage/AbstractGraphStorageTester.java
+++ b/core/src/test/java/com/graphhopper/storage/AbstractGraphStorageTester.java
@@ -17,10 +17,7 @@
  */
 package com.graphhopper.storage;
 
-import com.graphhopper.routing.ev.BooleanEncodedValue;
-import com.graphhopper.routing.ev.DecimalEncodedValue;
-import com.graphhopper.routing.ev.DecimalEncodedValueImpl;
-import com.graphhopper.routing.ev.SimpleBooleanEncodedValue;
+import com.graphhopper.routing.ev.*;
 import com.graphhopper.routing.util.AccessFilter;
 import com.graphhopper.routing.util.EdgeFilter;
 import com.graphhopper.routing.util.EncodingManager;
@@ -57,6 +54,7 @@ public abstract class AbstractGraphStorageTester {
         return new EncodingManager.Builder()
                 .add(carAccessEnc).add(carSpeedEnc)
                 .add(footAccessEnc).add(footSpeedEnc)
+                .add(RoadClass.create())
                 .build();
     }
 
diff --git a/core/src/test/java/com/graphhopper/storage/BaseGraphWithTurnCostsTest.java b/core/src/test/java/com/graphhopper/storage/BaseGraphWithTurnCostsTest.java
index addeff1bf..68df0eec0 100644
--- a/core/src/test/java/com/graphhopper/storage/BaseGraphWithTurnCostsTest.java
+++ b/core/src/test/java/com/graphhopper/storage/BaseGraphWithTurnCostsTest.java
@@ -18,6 +18,7 @@
 package com.graphhopper.storage;
 
 import com.graphhopper.routing.ev.DecimalEncodedValue;
+import com.graphhopper.routing.ev.RoadClass;
 import com.graphhopper.routing.ev.TurnCost;
 import com.graphhopper.routing.util.EncodingManager;
 import com.graphhopper.search.KVStorage.KeyValue;
@@ -44,6 +45,7 @@ public class BaseGraphWithTurnCostsTest extends BaseGraphTest {
         return EncodingManager.start()
                 .add(carAccessEnc).add(carSpeedEnc).addTurnCostEncodedValue(turnCostEnc)
                 .add(footAccessEnc).add(footSpeedEnc)
+                .add(RoadClass.create())
                 .build();
     }
 
diff --git a/core/src/test/java/com/graphhopper/util/InstructionListTest.java b/core/src/test/java/com/graphhopper/util/InstructionListTest.java
index 26717a1e2..c31d2a53a 100644
--- a/core/src/test/java/com/graphhopper/util/InstructionListTest.java
+++ b/core/src/test/java/com/graphhopper/util/InstructionListTest.java
@@ -26,7 +26,10 @@ import com.graphhopper.routing.ev.*;
 import com.graphhopper.routing.util.EncodingManager;
 import com.graphhopper.routing.util.PriorityCode;
 import com.graphhopper.routing.util.TraversalMode;
-import com.graphhopper.routing.weighting.*;
+import com.graphhopper.routing.weighting.DefaultTurnCostProvider;
+import com.graphhopper.routing.weighting.SpeedWeighting;
+import com.graphhopper.routing.weighting.TurnCostProvider;
+import com.graphhopper.routing.weighting.Weighting;
 import com.graphhopper.routing.weighting.custom.CustomModelParser;
 import com.graphhopper.storage.BaseGraph;
 import com.graphhopper.storage.Graph;
@@ -56,7 +59,8 @@ public class InstructionListTest {
     @BeforeEach
     public void setUp() {
         speedEnc = new DecimalEncodedValueImpl("speed", 5, 5, true);
-        carManager = EncodingManager.start().add(speedEnc).build();
+        carManager = EncodingManager.start().add(speedEnc).add(Roundabout.create())
+                .add(MaxSpeed.create()).add(RoadClass.create()).add(RoadClassLink.create()).build();
     }
 
     private static List<String> getTurnDescriptions(InstructionList instructionList) {
@@ -299,7 +303,8 @@ public class InstructionListTest {
     @Test
     public void testNoInstructionIfSlightTurnAndAlternativeIsSharp3() {
         DecimalEncodedValue speedEnc = new DecimalEncodedValueImpl("speed", 4, 2, true);
-        EncodingManager tmpEM = new EncodingManager.Builder().add(speedEnc).build();
+        EncodingManager tmpEM = new EncodingManager.Builder().add(speedEnc).add(RoadClass.create())
+                .add(RoadClassLink.create()).add(Roundabout.create()).add(MaxSpeed.create()).build();
         EnumEncodedValue<RoadClass> rcEV = tmpEM.getEnumEncodedValue(RoadClass.KEY, RoadClass.class);
         BaseGraph g = new BaseGraph.Builder(tmpEM).create();
         // real world example: https://graphhopper.com/maps/?point=48.411549,15.599567&point=48.411663%2C15.600527&profile=bike
@@ -337,7 +342,7 @@ public class InstructionListTest {
     @Test
     public void testInstructionIfTurn() {
         DecimalEncodedValue speedEnc = new DecimalEncodedValueImpl("speed", 4, 2, true);
-        EncodingManager tmpEM = new EncodingManager.Builder().add(speedEnc).build();
+        EncodingManager tmpEM = new EncodingManager.Builder().add(speedEnc).add(RoadClass.create()).add(RoadClassLink.create()).add(Roundabout.create()).add(MaxSpeed.create()).build();
         EnumEncodedValue<RoadClass> rcEV = tmpEM.getEnumEncodedValue(RoadClass.KEY, RoadClass.class);
         BaseGraph g = new BaseGraph.Builder(tmpEM).create();
         // real world example: https://graphhopper.com/maps/?point=48.412169%2C15.604888&point=48.412251%2C15.60543&profile=bike
@@ -376,7 +381,8 @@ public class InstructionListTest {
         BooleanEncodedValue accessEnc = new SimpleBooleanEncodedValue("access", true);
         DecimalEncodedValue speedEnc = new DecimalEncodedValueImpl("speed", 4, 1, false);
         DecimalEncodedValue priorityEnc = new DecimalEncodedValueImpl("priority", 4, PriorityCode.getFactor(1), false);
-        EncodingManager tmpEM = new EncodingManager.Builder().add(accessEnc).add(speedEnc).add(priorityEnc).build();
+        EncodingManager tmpEM = new EncodingManager.Builder().add(accessEnc).add(speedEnc).add(priorityEnc)
+                .add(Roundabout.create()).add(RoadClass.create()).add(RoadClassLink.create()).add(MaxSpeed.create()).build();
         BaseGraph g = new BaseGraph.Builder(tmpEM).create();
         // real world example: https://graphhopper.com/maps/?point=43.729379,7.417697&point=43.729798,7.417263&profile=foot
         // From 4 to 3 and 4 to 1
@@ -428,7 +434,8 @@ public class InstructionListTest {
     public void testInstructionWithHighlyCustomProfileWithRoadsBase() {
         BooleanEncodedValue roadsAccessEnc = new SimpleBooleanEncodedValue("access", true);
         DecimalEncodedValue roadsSpeedEnc = new DecimalEncodedValueImpl("speed", 7, 2, true);
-        EncodingManager tmpEM = EncodingManager.start().add(roadsAccessEnc).add(roadsSpeedEnc).build();
+        EncodingManager tmpEM = EncodingManager.start().add(roadsAccessEnc).add(roadsSpeedEnc)
+                .add(RoadClass.create()).add(Roundabout.create()).add(RoadClassLink.create()).add(MaxSpeed.create()).build();
         EnumEncodedValue<RoadClass> rcEV = tmpEM.getEnumEncodedValue(RoadClass.KEY, RoadClass.class);
         BaseGraph g = new BaseGraph.Builder(tmpEM).create();
         // real world example: https://graphhopper.com/maps/?point=55.691214%2C12.57065&point=55.689957%2C12.570387
diff --git a/core/src/test/java/com/graphhopper/util/PathSimplificationTest.java b/core/src/test/java/com/graphhopper/util/PathSimplificationTest.java
index 378908cfc..852b4ecff 100644
--- a/core/src/test/java/com/graphhopper/util/PathSimplificationTest.java
+++ b/core/src/test/java/com/graphhopper/util/PathSimplificationTest.java
@@ -21,10 +21,7 @@ import com.graphhopper.ResponsePath;
 import com.graphhopper.routing.Dijkstra;
 import com.graphhopper.routing.InstructionsFromEdges;
 import com.graphhopper.routing.Path;
-import com.graphhopper.routing.ev.BooleanEncodedValue;
-import com.graphhopper.routing.ev.DecimalEncodedValue;
-import com.graphhopper.routing.ev.DecimalEncodedValueImpl;
-import com.graphhopper.routing.ev.SimpleBooleanEncodedValue;
+import com.graphhopper.routing.ev.*;
 import com.graphhopper.routing.util.EncodingManager;
 import com.graphhopper.routing.util.TraversalMode;
 import com.graphhopper.routing.weighting.ShortestWeighting;
@@ -57,7 +54,8 @@ public class PathSimplificationTest {
     public void testScenario() {
         BooleanEncodedValue accessEnc = new SimpleBooleanEncodedValue("access", true);
         DecimalEncodedValue speedEnc = new DecimalEncodedValueImpl("speed", 5, 5, false);
-        EncodingManager carManager = EncodingManager.start().add(accessEnc).add(speedEnc).build();
+        EncodingManager carManager = EncodingManager.start().add(accessEnc).add(speedEnc)
+                .add(Roundabout.create()).add(RoadClass.create()).add(RoadClassLink.create()).add(MaxSpeed.create()).build();
         BaseGraph g = new BaseGraph.Builder(carManager).create();
         // 0-1-2
         // | | |
diff --git a/web-bundle/src/test/java/com/graphhopper/gpx/GpxConversionsTest.java b/web-bundle/src/test/java/com/graphhopper/gpx/GpxConversionsTest.java
index 8aa517dbe..1bc266c89 100644
--- a/web-bundle/src/test/java/com/graphhopper/gpx/GpxConversionsTest.java
+++ b/web-bundle/src/test/java/com/graphhopper/gpx/GpxConversionsTest.java
@@ -21,10 +21,7 @@ package com.graphhopper.gpx;
 import com.graphhopper.routing.Dijkstra;
 import com.graphhopper.routing.InstructionsFromEdges;
 import com.graphhopper.routing.Path;
-import com.graphhopper.routing.ev.BooleanEncodedValue;
-import com.graphhopper.routing.ev.DecimalEncodedValue;
-import com.graphhopper.routing.ev.DecimalEncodedValueImpl;
-import com.graphhopper.routing.ev.SimpleBooleanEncodedValue;
+import com.graphhopper.routing.ev.*;
 import com.graphhopper.routing.util.EncodingManager;
 import com.graphhopper.routing.util.TraversalMode;
 import com.graphhopper.routing.weighting.ShortestWeighting;
@@ -62,7 +59,7 @@ public class GpxConversionsTest {
     public void setUp() {
         accessEnc = new SimpleBooleanEncodedValue("access", true);
         speedEnc = new DecimalEncodedValueImpl("speed", 5, 5, false);
-        carManager = EncodingManager.start().add(accessEnc).add(speedEnc).build();
+        carManager = EncodingManager.start().add(accessEnc).add(speedEnc).add(Roundabout.create()).add(RoadClass.create()).add(RoadClassLink.create()).add(MaxSpeed.create()).build();
         trMap = new TranslationMap().doImport();
     }