Repository: graphhopper/graphhopper (language: java)

Issue:
Enum for foot, bike and psv access tags
Create `foot_road_access` and `bike_road_access` encoded value enums for access tags like we already have for motor vehicles (road_access or hgv). Otherwise we cannot properly move from parsers to custom models. See #2820.

- fix cargo_bike.json
- fix hike.json
- psv_road_access enum too: https://github.com/graphhopper/graphhopper/pull/2710
- finally remove block private options #2780 (had to be reverted due to a similar problem)
- rename hgv to hgv_road_access? (discussion #2744)
- duplicate https://github.com/graphhopper/graphhopper/issues/2460 ?


Discussion / hints:

Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index df8117b4acf..c879faffa24 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,8 @@
 ### 11.0 [not yet released]
 
+- the list of restrictions for BIKE returned from OSMRoadAccessParser.toOSMRestrictions is again `[bicycle, vehicle, access]` and not `[bicycle, access]` like before #2981
+- road_access now contains value of highest transportation mode for CAR, i.e. access=private, motorcar=yes will now return YES and not PRIVATE
+- car.json by default avoids private roads
 - maxspeed<5 is ignored, maxspeed=none is ignored with some exceptions, maxspeed parsing and related constants were renamed #3077
 
 ### 10.0 [5 Nov 2024]
diff --git a/core/src/main/java/com/graphhopper/routing/ev/BikeRoadAccess.java b/core/src/main/java/com/graphhopper/routing/ev/BikeRoadAccess.java
new file mode 100644
index 00000000000..3fa37da0b14
--- /dev/null
+++ b/core/src/main/java/com/graphhopper/routing/ev/BikeRoadAccess.java
@@ -0,0 +1,50 @@
+/*
+ *  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.Helper;
+
+import java.util.Arrays;
+import java.util.List;
+
+public enum BikeRoadAccess {
+    MISSING, YES, DESTINATION, DESIGNATED, USE_SIDEPATH, DISMOUNT, PRIVATE, NO;
+
+    public static final String KEY = "bike_road_access";
+
+    public static EnumEncodedValue<BikeRoadAccess> create() {
+        return new EnumEncodedValue<>(BikeRoadAccess.KEY, BikeRoadAccess.class);
+    }
+
+    @Override
+    public String toString() {
+        return Helper.toLowerCase(super.toString());
+    }
+
+    public static BikeRoadAccess find(String name) {
+        if (name == null || name.isEmpty())
+            return MISSING;
+        if (name.equalsIgnoreCase("permit") || name.equalsIgnoreCase("customers"))
+            return PRIVATE;
+        try {
+            return BikeRoadAccess.valueOf(Helper.toUpperCase(name));
+        } catch (IllegalArgumentException ex) {
+            return YES;
+        }
+    }
+}
diff --git a/core/src/main/java/com/graphhopper/routing/ev/DefaultImportRegistry.java b/core/src/main/java/com/graphhopper/routing/ev/DefaultImportRegistry.java
index efc94d40285..ca254a82d42 100644
--- a/core/src/main/java/com/graphhopper/routing/ev/DefaultImportRegistry.java
+++ b/core/src/main/java/com/graphhopper/routing/ev/DefaultImportRegistry.java
@@ -51,11 +51,28 @@ else if (RoadEnvironment.KEY.equals(name))
                     (lookup, props) -> new OSMRoadEnvironmentParser(
                             lookup.getEnumEncodedValue(RoadEnvironment.KEY, RoadEnvironment.class))
             );
+        else if (FootRoadAccess.KEY.equals(name))
+            return ImportUnit.create(name, props -> FootRoadAccess.create(),
+                    (lookup, props) -> new OSMRoadAccessParser<>(
+                            lookup.getEnumEncodedValue(FootRoadAccess.KEY, FootRoadAccess.class),
+                            OSMRoadAccessParser.toOSMRestrictions(TransportationMode.FOOT),
+                            (readerWay, accessValue) -> accessValue,
+                            FootRoadAccess::find)
+            );
+        else if (BikeRoadAccess.KEY.equals(name))
+            return ImportUnit.create(name, props -> BikeRoadAccess.create(),
+                    (lookup, props) -> new OSMRoadAccessParser<>(
+                            lookup.getEnumEncodedValue(BikeRoadAccess.KEY, BikeRoadAccess.class),
+                            OSMRoadAccessParser.toOSMRestrictions(TransportationMode.BIKE),
+                            (readerWay, accessValue) -> accessValue,
+                            BikeRoadAccess::find)
+            );
         else if (RoadAccess.KEY.equals(name))
             return ImportUnit.create(name, props -> RoadAccess.create(),
-                    (lookup, props) -> new OSMRoadAccessParser(
+                    (lookup, props) -> new OSMRoadAccessParser<>(
                             lookup.getEnumEncodedValue(RoadAccess.KEY, RoadAccess.class),
-                            OSMRoadAccessParser.toOSMRestrictions(TransportationMode.CAR))
+                            OSMRoadAccessParser.toOSMRestrictions(TransportationMode.CAR),
+                            RoadAccess::countryHook, RoadAccess::find)
             );
         else if (MaxSpeed.KEY.equals(name))
             return ImportUnit.create(name, props -> MaxSpeed.create(),
@@ -212,7 +229,7 @@ else if (BikeNetwork.KEY.equals(name) || MtbNetwork.KEY.equals(name) || FootNetw
 
         else if (BusAccess.KEY.equals(name))
             return ImportUnit.create(name, props -> BusAccess.create(),
-                    (lookup, props) -> new ModeAccessParser(TransportationMode.BUS,
+                    (lookup, props) -> new ModeAccessParser(OSMRoadAccessParser.toOSMRestrictions(TransportationMode.BUS),
                             lookup.getBooleanEncodedValue(name), true, lookup.getBooleanEncodedValue(Roundabout.KEY),
                             PMap.toSet(props.getString("restrictions", "")), PMap.toSet(props.getString("barriers", ""))),
                     "roundabout"
@@ -220,7 +237,7 @@ else if (BusAccess.KEY.equals(name))
 
         else if (HovAccess.KEY.equals(name))
             return ImportUnit.create(name, props -> HovAccess.create(),
-                    (lookup, props) -> new ModeAccessParser(TransportationMode.HOV,
+                    (lookup, props) -> new ModeAccessParser(OSMRoadAccessParser.toOSMRestrictions(TransportationMode.HOV),
                             lookup.getBooleanEncodedValue(name), true, lookup.getBooleanEncodedValue(Roundabout.KEY),
                             PMap.toSet(props.getString("restrictions", "")), PMap.toSet(props.getString("barriers", ""))),
                     "roundabout"
diff --git a/core/src/main/java/com/graphhopper/routing/ev/FootRoadAccess.java b/core/src/main/java/com/graphhopper/routing/ev/FootRoadAccess.java
new file mode 100644
index 00000000000..9bd971874f0
--- /dev/null
+++ b/core/src/main/java/com/graphhopper/routing/ev/FootRoadAccess.java
@@ -0,0 +1,47 @@
+/*
+ *  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.Helper;
+
+public enum FootRoadAccess {
+    MISSING, YES, DESTINATION, DESIGNATED, USE_SIDEPATH, PRIVATE, NO;
+
+    public static final String KEY = "foot_road_access";
+
+    public static EnumEncodedValue<FootRoadAccess> create() {
+        return new EnumEncodedValue<>(FootRoadAccess.KEY, FootRoadAccess.class);
+    }
+
+    @Override
+    public String toString() {
+        return Helper.toLowerCase(super.toString());
+    }
+
+    public static FootRoadAccess find(String name) {
+        if (name == null || name.isEmpty())
+            return MISSING;
+        if (name.equalsIgnoreCase("permit") || name.equalsIgnoreCase("customers"))
+            return PRIVATE;
+        try {
+            return FootRoadAccess.valueOf(Helper.toUpperCase(name));
+        } catch (IllegalArgumentException ex) {
+            return YES;
+        }
+    }
+}
diff --git a/core/src/main/java/com/graphhopper/routing/ev/RoadAccess.java b/core/src/main/java/com/graphhopper/routing/ev/RoadAccess.java
index 768beb6cdbd..4b254bb10b8 100644
--- a/core/src/main/java/com/graphhopper/routing/ev/RoadAccess.java
+++ b/core/src/main/java/com/graphhopper/routing/ev/RoadAccess.java
@@ -17,15 +17,18 @@
  */
 package com.graphhopper.routing.ev;
 
+import com.graphhopper.reader.ReaderWay;
+import com.graphhopper.routing.util.TransportationMode;
+import com.graphhopper.routing.util.countryrules.CountryRule;
 import com.graphhopper.util.Helper;
 
 /**
- * This enum defines the road access of an edge. Most edges are accessible from everyone and so the default value is
- * YES. But some have restrictions like "accessible only for customers" or when delivering. Unknown tags will get the
- * value OTHER. The NO value does not permit any access.
+ * This enum defines the road access of an edge. Most edges are accessible from everyone and so the
+ * default value is YES. But some have restrictions like "accessible only for customers" or when
+ * delivering. The NO value does not permit any access.
  */
 public enum RoadAccess {
-    YES, DESTINATION, CUSTOMERS, DELIVERY, FORESTRY, AGRICULTURAL, PRIVATE, OTHER, NO;
+    YES, DESTINATION, CUSTOMERS, DELIVERY, FORESTRY, AGRICULTURAL, PRIVATE, NO;
 
     public static final String KEY = "road_access";
 
@@ -41,6 +44,8 @@ public String toString() {
     public static RoadAccess find(String name) {
         if (name == null)
             return YES;
+        if (name.equalsIgnoreCase("permit"))
+            return PRIVATE;
         try {
             // public and permissive will be converted into "yes"
             return RoadAccess.valueOf(Helper.toUpperCase(name));
@@ -48,4 +53,9 @@ public static RoadAccess find(String name) {
             return YES;
         }
     }
+
+    public static RoadAccess countryHook(ReaderWay readerWay, RoadAccess roadAccess) {
+        CountryRule countryRule = readerWay.getTag("country_rule", null);
+        return countryRule == null ? roadAccess : countryRule.getAccess(readerWay, TransportationMode.CAR, roadAccess == null ? RoadAccess.YES : roadAccess);
+    }
 }
diff --git a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/HungaryCountryRule.java b/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/HungaryCountryRule.java
index 0161d3d9ea3..93d9f934d7b 100644
--- a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/HungaryCountryRule.java
+++ b/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/HungaryCountryRule.java
@@ -38,8 +38,7 @@ public RoadAccess getAccess(ReaderWay readerWay, TransportationMode transportati
             return currentRoadAccess;
         }
 
-        // Override only bogus "yes" and missing/other
-        if (currentRoadAccess != RoadAccess.YES && currentRoadAccess != RoadAccess.OTHER) {
+        if (currentRoadAccess != RoadAccess.YES) {
             return currentRoadAccess;
         }
 
@@ -48,7 +47,7 @@ public RoadAccess getAccess(ReaderWay readerWay, TransportationMode transportati
             return RoadAccess.DESTINATION;
         }
 
-        return currentRoadAccess;
+        return RoadAccess.YES;
     }
 
     @Override
diff --git a/core/src/main/java/com/graphhopper/routing/util/parsers/AbstractAccessParser.java b/core/src/main/java/com/graphhopper/routing/util/parsers/AbstractAccessParser.java
index e1edb6526c9..9ad774a396d 100644
--- a/core/src/main/java/com/graphhopper/routing/util/parsers/AbstractAccessParser.java
+++ b/core/src/main/java/com/graphhopper/routing/util/parsers/AbstractAccessParser.java
@@ -1,3 +1,20 @@
+/*
+ *  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.reader.ReaderNode;
@@ -14,7 +31,7 @@ public abstract class AbstractAccessParser implements TagParser {
     static final Collection<String> INTENDED = Arrays.asList("yes", "designated", "official", "permissive");
 
     // order is important
-    protected final List<String> restrictionKeys = new ArrayList<>(5);
+    protected final List<String> restrictionKeys;
     protected final Set<String> restrictedValues = new HashSet<>(5);
 
     protected final Set<String> intendedValues = new HashSet<>(INTENDED); // possible to add "private" later
@@ -23,8 +40,9 @@ public abstract class AbstractAccessParser implements TagParser {
     protected final BooleanEncodedValue accessEnc;
     private boolean blockFords = true;
 
-    protected AbstractAccessParser(BooleanEncodedValue accessEnc, TransportationMode transportationMode) {
+    protected AbstractAccessParser(BooleanEncodedValue accessEnc, List<String> restrictionKeys) {
         this.accessEnc = accessEnc;
+        this.restrictionKeys = restrictionKeys;
 
         restrictedValues.add("no");
         restrictedValues.add("restricted");
@@ -32,8 +50,6 @@ protected AbstractAccessParser(BooleanEncodedValue accessEnc, TransportationMode
         restrictedValues.add("emergency");
         restrictedValues.add("private");
         restrictedValues.add("permit");
-
-        restrictionKeys.addAll(OSMRoadAccessParser.toOSMRestrictions(transportationMode));
     }
 
     public boolean isBlockFords() {
diff --git a/core/src/main/java/com/graphhopper/routing/util/parsers/BikeCommonAccessParser.java b/core/src/main/java/com/graphhopper/routing/util/parsers/BikeCommonAccessParser.java
index c93d493c454..a74d73631bd 100644
--- a/core/src/main/java/com/graphhopper/routing/util/parsers/BikeCommonAccessParser.java
+++ b/core/src/main/java/com/graphhopper/routing/util/parsers/BikeCommonAccessParser.java
@@ -4,7 +4,6 @@
 import com.graphhopper.routing.ev.BooleanEncodedValue;
 import com.graphhopper.routing.ev.EdgeIntAccess;
 import com.graphhopper.routing.util.FerrySpeedCalculator;
-import com.graphhopper.routing.util.TransportationMode;
 import com.graphhopper.routing.util.WayAccess;
 
 import java.util.*;
@@ -17,8 +16,14 @@ public abstract class BikeCommonAccessParser extends AbstractAccessParser implem
     private final Set<String> allowedHighways = new HashSet<>();
     private final BooleanEncodedValue roundaboutEnc;
 
+    /**
+     * The access restriction list returned from OSMRoadAccessParser.toOSMRestrictions(TransportationMode.Bike)
+     * contains "vehicle". But here we want to allow walking via dismount.
+     */
+    private static final List<String> RESTRICTIONS = Arrays.asList("bicycle", "access");
+
     protected BikeCommonAccessParser(BooleanEncodedValue accessEnc, BooleanEncodedValue roundaboutEnc) {
-        super(accessEnc, TransportationMode.BIKE);
+        super(accessEnc, RESTRICTIONS);
 
         this.roundaboutEnc = roundaboutEnc;
 
diff --git a/core/src/main/java/com/graphhopper/routing/util/parsers/CarAccessParser.java b/core/src/main/java/com/graphhopper/routing/util/parsers/CarAccessParser.java
index ca93a68ca4a..e82ff8918a2 100644
--- a/core/src/main/java/com/graphhopper/routing/util/parsers/CarAccessParser.java
+++ b/core/src/main/java/com/graphhopper/routing/util/parsers/CarAccessParser.java
@@ -46,7 +46,7 @@ public CarAccessParser(EncodedValueLookup lookup, PMap properties) {
     public CarAccessParser(BooleanEncodedValue accessEnc,
                            BooleanEncodedValue roundaboutEnc, PMap properties,
                            TransportationMode transportationMode) {
-        super(accessEnc, transportationMode);
+        super(accessEnc, OSMRoadAccessParser.toOSMRestrictions(transportationMode));
         this.roundaboutEnc = roundaboutEnc;
         restrictedValues.add("agricultural");
         restrictedValues.add("forestry");
diff --git a/core/src/main/java/com/graphhopper/routing/util/parsers/FootAccessParser.java b/core/src/main/java/com/graphhopper/routing/util/parsers/FootAccessParser.java
index 2252214e7f4..5fcf56a3ec8 100644
--- a/core/src/main/java/com/graphhopper/routing/util/parsers/FootAccessParser.java
+++ b/core/src/main/java/com/graphhopper/routing/util/parsers/FootAccessParser.java
@@ -43,7 +43,7 @@ public FootAccessParser(EncodedValueLookup lookup, PMap properties) {
     }
 
     protected FootAccessParser(BooleanEncodedValue accessEnc) {
-        super(accessEnc, TransportationMode.FOOT);
+        super(accessEnc, OSMRoadAccessParser.toOSMRestrictions(TransportationMode.FOOT));
 
         sidewalkValues.add("yes");
         sidewalkValues.add("both");
diff --git a/core/src/main/java/com/graphhopper/routing/util/parsers/ModeAccessParser.java b/core/src/main/java/com/graphhopper/routing/util/parsers/ModeAccessParser.java
index b213a39fe3d..61e0ec57bbd 100644
--- a/core/src/main/java/com/graphhopper/routing/util/parsers/ModeAccessParser.java
+++ b/core/src/main/java/com/graphhopper/routing/util/parsers/ModeAccessParser.java
@@ -30,12 +30,12 @@ public class ModeAccessParser implements TagParser {
     private final boolean skipEmergency;
     private final Set<String> barriers;
 
-    public ModeAccessParser(TransportationMode mode, BooleanEncodedValue accessEnc,
+    public ModeAccessParser(List<String> restrictionKeys, BooleanEncodedValue accessEnc,
                             boolean skipEmergency, BooleanEncodedValue roundaboutEnc,
                             Set<String> restrictions, Set<String> barriers) {
         this.accessEnc = accessEnc;
         this.roundaboutEnc = roundaboutEnc;
-        restrictionKeys = OSMRoadAccessParser.toOSMRestrictions(mode);
+        this.restrictionKeys = restrictionKeys;
         vehicleForward = restrictionKeys.stream().map(r -> r + ":forward").toList();
         vehicleBackward = restrictionKeys.stream().map(r -> r + ":backward").toList();
         ignoreOnewayKeys = restrictionKeys.stream().map(r -> "oneway:" + r).toList();
diff --git a/core/src/main/java/com/graphhopper/routing/util/parsers/OSMRoadAccessParser.java b/core/src/main/java/com/graphhopper/routing/util/parsers/OSMRoadAccessParser.java
index c3d05adb9ec..48cab0881e4 100644
--- a/core/src/main/java/com/graphhopper/routing/util/parsers/OSMRoadAccessParser.java
+++ b/core/src/main/java/com/graphhopper/routing/util/parsers/OSMRoadAccessParser.java
@@ -20,57 +20,59 @@
 import com.graphhopper.reader.ReaderWay;
 import com.graphhopper.routing.ev.EdgeIntAccess;
 import com.graphhopper.routing.ev.EnumEncodedValue;
-import com.graphhopper.routing.ev.RoadAccess;
 import com.graphhopper.routing.util.TransportationMode;
-import com.graphhopper.routing.util.countryrules.CountryRule;
 import com.graphhopper.storage.IntsRef;
 
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
+import java.util.function.BiFunction;
+import java.util.function.Function;
 
-import static com.graphhopper.routing.ev.RoadAccess.YES;
-
-public class OSMRoadAccessParser implements TagParser {
-    protected final EnumEncodedValue<RoadAccess> roadAccessEnc;
+public class OSMRoadAccessParser<T extends Enum> implements TagParser {
+    protected final EnumEncodedValue<T> accessEnc;
     private final List<String> restrictions;
+    private final Function<String, T> valueFinder;
+    private final BiFunction<ReaderWay, T, T> countryHook;
 
-    public OSMRoadAccessParser(EnumEncodedValue<RoadAccess> roadAccessEnc, List<String> restrictions) {
-        this.roadAccessEnc = roadAccessEnc;
+    public OSMRoadAccessParser(EnumEncodedValue<T> accessEnc, List<String> restrictions,
+                               BiFunction<ReaderWay, T, T> countryHook,
+                               Function<String, T> valueFinder) {
+        this.accessEnc = accessEnc;
         this.restrictions = restrictions;
+        this.valueFinder = valueFinder;
+        this.countryHook = countryHook;
     }
 
     @Override
     public void handleWayTags(int edgeId, EdgeIntAccess edgeIntAccess, ReaderWay readerWay, IntsRef relationFlags) {
-        RoadAccess accessValue = YES;
+        T accessValue = null;
 
         List<Map<String, Object>> nodeTags = readerWay.getTag("node_tags", Collections.emptyList());
         // a barrier edge has the restriction in both nodes and the tags are the same
         if (readerWay.hasTag("gh:barrier_edge"))
             for (String restriction : restrictions) {
                 Object value = nodeTags.get(0).get(restriction);
-                if (value != null) accessValue = getRoadAccess((String) value, accessValue);
+                accessValue = getRoadAccess((String) value, accessValue);
+                if(accessValue != null) break;
             }
 
         for (String restriction : restrictions) {
             accessValue = getRoadAccess(readerWay.getTag(restriction), accessValue);
+            if(accessValue != null) break;
         }
 
-        CountryRule countryRule = readerWay.getTag("country_rule", null);
-        if (countryRule != null)
-            accessValue = countryRule.getAccess(readerWay, TransportationMode.CAR, accessValue);
-
-        roadAccessEnc.setEnum(false, edgeId, edgeIntAccess, accessValue);
+        accessValue = countryHook.apply(readerWay, accessValue);
+        if (accessValue != null)
+            accessEnc.setEnum(false, edgeId, edgeIntAccess, accessValue);
     }
 
-    private RoadAccess getRoadAccess(String tagValue, RoadAccess accessValue) {
-        RoadAccess tmpAccessValue;
+    private T getRoadAccess(String tagValue, T accessValue) {
+        T tmpAccessValue;
         if (tagValue != null) {
             String[] complex = tagValue.split(";");
             for (String simple : complex) {
-                tmpAccessValue = simple.equals("permit") ? RoadAccess.PRIVATE : RoadAccess.find(simple);
-                if (tmpAccessValue != null && tmpAccessValue.ordinal() > accessValue.ordinal()) {
+                tmpAccessValue = valueFinder.apply(simple);
+                if (tmpAccessValue == null) continue;
+                if (accessValue == null || tmpAccessValue.ordinal() > accessValue.ordinal()) {
                     accessValue = tmpAccessValue;
                 }
             }
@@ -85,7 +87,7 @@ public static List<String> toOSMRestrictions(TransportationMode mode) {
             case VEHICLE:
                 return Arrays.asList("vehicle", "access");
             case BIKE:
-                return Arrays.asList("bicycle", "access");
+                return Arrays.asList("bicycle", "vehicle", "access");
             case CAR:
                 return Arrays.asList("motorcar", "motor_vehicle", "vehicle", "access");
             case MOTORCYCLE:
diff --git a/core/src/main/java/com/graphhopper/util/GHUtility.java b/core/src/main/java/com/graphhopper/util/GHUtility.java
index 0639d155b64..7b1de4d3092 100644
--- a/core/src/main/java/com/graphhopper/util/GHUtility.java
+++ b/core/src/main/java/com/graphhopper/util/GHUtility.java
@@ -667,7 +667,7 @@ public static CustomModel loadCustomModelFromJar(String name) {
             ObjectMapper objectMapper = Jackson.newObjectMapper();
             return objectMapper.readValue(json, CustomModel.class);
         } catch (IOException e) {
-            throw new IllegalArgumentException("Could not load built-in custom model '" + name + "'");
+            throw new IllegalArgumentException("Could not load built-in custom model '" + name + "'", e);
         }
     }
 }
diff --git a/core/src/main/resources/com/graphhopper/custom_models/bike.json b/core/src/main/resources/com/graphhopper/custom_models/bike.json
index 259de1ea5f8..5a1c5f9bc22 100644
--- a/core/src/main/resources/com/graphhopper/custom_models/bike.json
+++ b/core/src/main/resources/com/graphhopper/custom_models/bike.json
@@ -1,6 +1,6 @@
 // to use this custom model you need to set the following option in the config.yml
 // graph.elevation.provider: srtm   # enables elevation
-// graph.encoded_values: bike_priority, mtb_rating, bike_access, roundabout, bike_average_speed, average_slope, hike_rating
+// graph.encoded_values: bike_priority, bike_access, roundabout, bike_average_speed, bike_road_access, foot_road_access, average_slope, hike_rating
 // profiles:
 //    - name: bike
 //      custom_model_files: [bike.json, bike_elevation.json]
@@ -8,6 +8,7 @@
 {
   "priority": [
     { "if": "true",  "multiply_by": "bike_priority" },
+    { "if": "bike_road_access == PRIVATE && foot_road_access != YES", "multiply_by": "0" },
     { "if": "mtb_rating > 2",  "multiply_by": "0" },
     { "if": "hike_rating > 1",  "multiply_by": "0" },
     { "if": "!bike_access && (!backward_bike_access || roundabout)",  "multiply_by": "0" },
@@ -15,6 +16,7 @@
   ],
   "speed": [
     { "if": "true", "limit_to": "bike_average_speed" },
-    { "if": "!bike_access && backward_bike_access", "limit_to": "5" }
+    { "if": "!bike_access && backward_bike_access", "limit_to": "5" },
+    { "if": "bike_road_access == PRIVATE && foot_road_access == YES", "limit_to": "5" }
   ]
 }
diff --git a/core/src/main/resources/com/graphhopper/custom_models/bike_tc.json b/core/src/main/resources/com/graphhopper/custom_models/bike_tc.json
index 5ee390a3856..20d04b1a56f 100644
--- a/core/src/main/resources/com/graphhopper/custom_models/bike_tc.json
+++ b/core/src/main/resources/com/graphhopper/custom_models/bike_tc.json
@@ -4,7 +4,7 @@
 //
 // to use this custom model you need to set the following option in the config.yml
 // graph.elevation.provider: srtm   # enables elevation
-// graph.encoded_values: bike_priority, bike_access, roundabout, bike_average_speed, average_slope, orientation
+// graph.encoded_values: bike_priority, bike_access, roundabout, bike_average_speed, bike_road_access, average_slope, orientation
 // profiles:
 //    - name: bike
 //      turn_costs:
@@ -19,6 +19,7 @@
 {
   "priority": [
     { "if": "true",  "multiply_by": "bike_priority" },
+    { "if": "bike_road_access == PRIVATE", "multiply_by": "0" },
     { "if": "!bike_access && (!backward_bike_access || roundabout)",  "multiply_by": "0" },
     { "else_if": "!bike_access && backward_bike_access",  "multiply_by": "0.2" }
   ],
diff --git a/core/src/main/resources/com/graphhopper/custom_models/bus.json b/core/src/main/resources/com/graphhopper/custom_models/bus.json
index b062a300623..3642d3e0b18 100644
--- a/core/src/main/resources/com/graphhopper/custom_models/bus.json
+++ b/core/src/main/resources/com/graphhopper/custom_models/bus.json
@@ -9,6 +9,7 @@
 {
   "distance_influence": 90,
   "priority": [
+    { "if": "road_access == PRIVATE", "multiply_by": "0" },
     { "if": "max_weight < 5 || max_width < 3 || max_height < 4", "multiply_by": "0" },
     { "if": "bus_access && (road_class == MOTORWAY || road_class == TRUNK || road_class == PRIMARY || road_class == SECONDARY || road_class == TERTIARY || road_class == UNCLASSIFIED || road_class == LIVING_STREET || road_class == RESIDENTIAL || road_class == SERVICE || road_class == ROAD)",
       "multiply_by": "1"
diff --git a/core/src/main/resources/com/graphhopper/custom_models/car.json b/core/src/main/resources/com/graphhopper/custom_models/car.json
index 93027b5dbf4..1677dc8e52b 100644
--- a/core/src/main/resources/com/graphhopper/custom_models/car.json
+++ b/core/src/main/resources/com/graphhopper/custom_models/car.json
@@ -1,5 +1,5 @@
 // to use this custom model you need to set the following option in the config.yml
-// graph.encoded_values: car_access, car_average_speed
+// graph.encoded_values: car_access, car_average_speed, road_access
 // profiles:
 //    - name: car
 //      turn_costs:
@@ -9,7 +9,8 @@
 {
   "distance_influence": 90,
   "priority": [
-    { "if": "!car_access", "multiply_by": "0" }
+    { "if": "!car_access", "multiply_by": "0" },
+    { "if": "road_access == PRIVATE", "multiply_by": "0.1" }
   ],
   "speed": [
     { "if": "true", "limit_to": "car_average_speed" }
diff --git a/core/src/main/resources/com/graphhopper/custom_models/car4wd.json b/core/src/main/resources/com/graphhopper/custom_models/car4wd.json
index 4b5fde56da0..618989a8498 100644
--- a/core/src/main/resources/com/graphhopper/custom_models/car4wd.json
+++ b/core/src/main/resources/com/graphhopper/custom_models/car4wd.json
@@ -1,5 +1,5 @@
 // to use this custom model you need to set the following option in the config.yml
-// graph.encoded_values: car_access, car_average_speed, track_type
+// graph.encoded_values: car_access, car_average_speed, track_type, road_access
 // profiles:
 //    - name: car4wd
 //      custom_model_files: [car4wd.json]
@@ -7,6 +7,7 @@
 {
   "distance_influence": 1,
   "priority": [
+    { "if": "road_access == PRIVATE", "multiply_by": "0" },
     { "if": "track_type != GRADE4 && track_type != GRADE5 && car_access == false", "multiply_by": "0" }
   ],
   "speed": [
diff --git a/core/src/main/resources/com/graphhopper/custom_models/cargo_bike.json b/core/src/main/resources/com/graphhopper/custom_models/cargo_bike.json
index ce8cfa00b82..fda9eeaea69 100644
--- a/core/src/main/resources/com/graphhopper/custom_models/cargo_bike.json
+++ b/core/src/main/resources/com/graphhopper/custom_models/cargo_bike.json
@@ -3,7 +3,7 @@
 
 {
   "priority": [
-    { "if": "road_access == PRIVATE",  "multiply_by": "0" },
+    { "if": "bike_road_access == PRIVATE",  "multiply_by": "0" },
     { "if": "road_class == STEPS", "multiply_by": 0 },
     { "if": "surface == SAND", "multiply_by": 0.5 },
     { "if": "track_type != MISSING && track_type != GRADE1", "multiply_by": 0.9 },
@@ -15,4 +15,4 @@
     { "if": "road_class == PRIMARY", "limit_to": 28 },
     { "else": "", "limit_to": 25 }
   ]
-}
\ No newline at end of file
+}
diff --git a/core/src/main/resources/com/graphhopper/custom_models/foot.json b/core/src/main/resources/com/graphhopper/custom_models/foot.json
index 32f0d7e7cc6..d3fba8ce839 100644
--- a/core/src/main/resources/com/graphhopper/custom_models/foot.json
+++ b/core/src/main/resources/com/graphhopper/custom_models/foot.json
@@ -1,15 +1,15 @@
 // to use this custom model you need to set the following option in the config.yml
 // graph.elevation.provider: srtm   # enables elevation
-// graph.encoded_values: foot_access, hike_rating, mtb_rating, foot_priority, foot_average_speed, average_slope
+// graph.encoded_values: foot_access, foot_priority, foot_average_speed, foot_road_access, hike_rating, mtb_rating, average_slope
 // profiles:
 //    - name: foot
 //      custom_model_files: [foot.json, foot_elevation.json]
 
 {
   "priority": [
-    { "if": "!foot_access || hike_rating >= 2", "multiply_by": "0" },
-    { "if": "mtb_rating > 2",  "multiply_by": "0" },
-    { "else": "", "multiply_by": "foot_priority"}
+    { "if": "!foot_access || hike_rating >= 2 || mtb_rating > 2", "multiply_by": "0" },
+    { "else": "", "multiply_by": "foot_priority"},
+    { "if": "foot_road_access == PRIVATE", "multiply_by": "0" }
   ],
   "speed": [
     { "if": "true", "limit_to": "foot_average_speed" }
diff --git a/core/src/main/resources/com/graphhopper/custom_models/hike.json b/core/src/main/resources/com/graphhopper/custom_models/hike.json
index d133dbac13b..040dfd3359b 100644
--- a/core/src/main/resources/com/graphhopper/custom_models/hike.json
+++ b/core/src/main/resources/com/graphhopper/custom_models/hike.json
@@ -1,6 +1,6 @@
 // to use this custom model you set the following option in the config.yml:
 // graph.elevation.provider: srtm   # enables elevation
-// graph.encoded_values: foot_access, hike_rating, foot_priority, foot_network, foot_average_speed, average_slope
+// graph.encoded_values: foot_access, foot_priority, foot_network, foot_average_speed, foot_road_access, hike_rating, average_slope
 // profiles:
 //    - name: hike
 //      custom_model_files: [hike.json, foot_elevation.json]
@@ -9,6 +9,7 @@
   "priority": [
     { "if": "!foot_access || hike_rating >= 5", "multiply_by": "0"},
     { "else": "", "multiply_by": "foot_priority"},
+    { "if": "foot_road_access == PRIVATE", "multiply_by": "0" },
     { "if": "foot_network == INTERNATIONAL || foot_network == NATIONAL", "multiply_by": "1.7"},
     { "else_if": "foot_network == REGIONAL || foot_network == LOCAL", "multiply_by": "1.5"}
   ],
diff --git a/core/src/main/resources/com/graphhopper/custom_models/mtb.json b/core/src/main/resources/com/graphhopper/custom_models/mtb.json
index bebe0935248..861f662f9b3 100644
--- a/core/src/main/resources/com/graphhopper/custom_models/mtb.json
+++ b/core/src/main/resources/com/graphhopper/custom_models/mtb.json
@@ -1,6 +1,6 @@
 // to use this custom model you need to set the following option in the config.yml
 // graph.elevation.provider: srtm   # enables elevation
-// graph.encoded_values: mtb_priority, mtb_access, roundabout, mtb_average_speed, average_slope, mtb_rating, hike_rating
+// graph.encoded_values: mtb_priority, mtb_access, roundabout, mtb_average_speed, bike_road_access, average_slope, mtb_rating, hike_rating
 // profiles:
 //    - name: mtb
 //      custom_model_files: [mtb.json, bike_elevation.json]
@@ -12,7 +12,8 @@
     { "if": "mtb_rating > 3",  "multiply_by": "0.5" },
     { "if": "hike_rating > 4",  "multiply_by": "0" },
     { "if": "!mtb_access && (!backward_mtb_access || roundabout)",  "multiply_by": "0" },
-    { "else_if": "!mtb_access && backward_mtb_access",  "multiply_by": "0.2" }
+    { "else_if": "!mtb_access && backward_mtb_access",  "multiply_by": "0.2" },
+    { "if": "bike_road_access == PRIVATE",  "multiply_by": "0" }
   ],
   "speed": [
     { "if": "true", "limit_to": "mtb_average_speed" },
diff --git a/core/src/main/resources/com/graphhopper/custom_models/racingbike.json b/core/src/main/resources/com/graphhopper/custom_models/racingbike.json
index 94589d41d6a..e610e0dc4dc 100644
--- a/core/src/main/resources/com/graphhopper/custom_models/racingbike.json
+++ b/core/src/main/resources/com/graphhopper/custom_models/racingbike.json
@@ -1,6 +1,6 @@
 // to use this custom model you need to set the following option in the config.yml
 // graph.elevation.provider: srtm   # enables elevation
-// graph.encoded_values: racingbike_priority, racingbike_access, roundabout, racingbike_average_speed, average_slope, mtb_rating, sac_scale
+// graph.encoded_values: racingbike_priority, racingbike_access, roundabout, racingbike_average_speed, bike_road_access, average_slope, mtb_rating, sac_scale
 // profiles:
 //    - name: racingbike
 //      custom_model_files: [racingbike.json, bike_elevation.json]
@@ -8,6 +8,7 @@
 {
   "priority": [
     { "if": "true",  "multiply_by": "racingbike_priority" },
+    { "if": "bike_road_access == PRIVATE", "multiply_by": "0" },
     { "if": "mtb_rating > 2",  "multiply_by": "0" },
     { "if": "mtb_rating == 2",  "multiply_by": "0.5" },
     { "if": "hike_rating > 1",  "multiply_by": "0" },
diff --git a/core/src/main/resources/com/graphhopper/custom_models/truck.json b/core/src/main/resources/com/graphhopper/custom_models/truck.json
index 47432d29849..a31db3fe3d6 100644
--- a/core/src/main/resources/com/graphhopper/custom_models/truck.json
+++ b/core/src/main/resources/com/graphhopper/custom_models/truck.json
@@ -9,8 +9,8 @@
 {
   "distance_influence": 1,
   "priority": [
-    { "if": "road_access == PRIVATE",  "multiply_by": "0" },
-    { "if": "car_access == false || hgv == NO || max_width < 3 || max_height < 4", "multiply_by": "0" }
+    { "if": "car_access == false || road_access == PRIVATE || hgv == NO",  "multiply_by": "0" },
+    { "if": "max_width < 3 || max_height < 4", "multiply_by": "0" }
   ],
   "speed": [
     { "if": "true", "limit_to": "car_average_speed * 0.9" },
diff --git a/example/src/main/java/com/graphhopper/example/LocationIndexExample.java b/example/src/main/java/com/graphhopper/example/LocationIndexExample.java
index b4f4aa12c3c..2aa745a8c39 100644
--- a/example/src/main/java/com/graphhopper/example/LocationIndexExample.java
+++ b/example/src/main/java/com/graphhopper/example/LocationIndexExample.java
@@ -23,7 +23,7 @@ public static void main(String[] args) {
 
     public static void graphhopperLocationIndex(String relDir) {
         GraphHopper hopper = new GraphHopper();
-        hopper.setEncodedValuesString("car_access, car_average_speed");
+        hopper.setEncodedValuesString("car_access, car_average_speed, road_access");
         hopper.setProfiles(new Profile("car").setCustomModel(GHUtility.loadCustomModelFromJar("car.json")));
         hopper.setOSMFile(relDir + "core/files/andorra.osm.pbf");
         hopper.setGraphHopperLocation("./target/locationindex-graph-cache");
diff --git a/example/src/main/java/com/graphhopper/example/RoutingExample.java b/example/src/main/java/com/graphhopper/example/RoutingExample.java
index 4dcf5b2aeca..b22eb3d65e7 100644
--- a/example/src/main/java/com/graphhopper/example/RoutingExample.java
+++ b/example/src/main/java/com/graphhopper/example/RoutingExample.java
@@ -36,7 +36,7 @@ static GraphHopper createGraphHopperInstance(String ghLoc) {
         hopper.setGraphHopperLocation("target/routing-graph-cache");
 
         // add all encoded values that are used in the custom model, these are also available as path details or for client-side custom models
-        hopper.setEncodedValuesString("car_access, car_average_speed");
+        hopper.setEncodedValuesString("car_access, car_average_speed, road_access");
         // see docs/core/profiles.md to learn more about profiles
         hopper.setProfiles(new Profile("car").setCustomModel(GHUtility.loadCustomModelFromJar("car.json")));
 
@@ -109,7 +109,7 @@ public static void customizableRouting(String ghLoc) {
         GraphHopper hopper = new GraphHopper();
         hopper.setOSMFile(ghLoc);
         hopper.setGraphHopperLocation("target/routing-custom-graph-cache");
-        hopper.setEncodedValuesString("car_access, car_average_speed");
+        hopper.setEncodedValuesString("car_access, car_average_speed, road_access");
         hopper.setProfiles(new Profile("car_custom").setCustomModel(GHUtility.loadCustomModelFromJar("car.json")));
 
         // The hybrid mode uses the "landmark algorithm" and is up to 15x faster than the flexible mode (Dijkstra).
diff --git a/example/src/main/java/com/graphhopper/example/RoutingExampleTC.java b/example/src/main/java/com/graphhopper/example/RoutingExampleTC.java
index 30cc1c4c7b1..0b6f279427f 100644
--- a/example/src/main/java/com/graphhopper/example/RoutingExampleTC.java
+++ b/example/src/main/java/com/graphhopper/example/RoutingExampleTC.java
@@ -72,7 +72,7 @@ static GraphHopper createGraphHopperInstance(String ghLoc) {
         hopper.setOSMFile(ghLoc);
         hopper.setGraphHopperLocation("target/routing-tc-graph-cache");
         // add all encoded values that are used in the custom model, these are also available as path details or for client-side custom models
-        hopper.setEncodedValuesString("car_access, car_average_speed");
+        hopper.setEncodedValuesString("car_access, car_average_speed, road_access");
         Profile profile = new Profile("car").setCustomModel(GHUtility.loadCustomModelFromJar("car.json"))
                 // enabling turn costs means OSM turn restriction constraints like 'no_left_turn' will be taken into account for the specified access restrictions
                 // we can also set u_turn_costs (in seconds). i.e. we will consider u-turns at all junctions with a 40s time penalty


Test patch:
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 35a217a3cd8..1c3e2d6f7a2 100644
--- a/core/src/test/java/com/graphhopper/reader/osm/OSMReaderTest.java
+++ b/core/src/test/java/com/graphhopper/reader/osm/OSMReaderTest.java
@@ -21,7 +21,6 @@
 import com.graphhopper.GHResponse;
 import com.graphhopper.GraphHopper;
 import com.graphhopper.GraphHopperTest;
-import com.graphhopper.util.TurnCostsConfig;
 import com.graphhopper.reader.ReaderElement;
 import com.graphhopper.reader.ReaderRelation;
 import com.graphhopper.reader.ReaderWay;
@@ -936,7 +935,8 @@ public void testCountries() throws IOException {
         EnumEncodedValue<RoadAccess> roadAccessEnc = RoadAccess.create();
         EncodingManager em = new EncodingManager.Builder().add(roadAccessEnc).build();
         OSMParsers osmParsers = new OSMParsers();
-        osmParsers.addWayTagParser(new OSMRoadAccessParser(roadAccessEnc, OSMRoadAccessParser.toOSMRestrictions(CAR)));
+        osmParsers.addWayTagParser(new OSMRoadAccessParser<>(roadAccessEnc,
+                OSMRoadAccessParser.toOSMRestrictions(CAR), RoadAccess::countryHook, RoadAccess::find));
         BaseGraph graph = new BaseGraph.Builder(em).create();
         OSMReader reader = new OSMReader(graph, osmParsers, new OSMReaderConfig());
         reader.setCountryRuleFactory(new CountryRuleFactory());
diff --git a/core/src/test/java/com/graphhopper/routing/RoutingAlgorithmWithOSMTest.java b/core/src/test/java/com/graphhopper/routing/RoutingAlgorithmWithOSMTest.java
index f473b2009ae..fc3c47fa61d 100644
--- a/core/src/test/java/com/graphhopper/routing/RoutingAlgorithmWithOSMTest.java
+++ b/core/src/test/java/com/graphhopper/routing/RoutingAlgorithmWithOSMTest.java
@@ -710,7 +710,8 @@ private GraphHopper createHopper(String osmFile, Profile... profiles) {
                         "foot_access, foot_priority, foot_average_speed, " +
                         "bike_access, bike_priority, bike_average_speed, foot_network, roundabout, " +
                         "mtb_access, mtb_priority, mtb_average_speed, mtb_rating, " +
-                        "racingbike_access, racingbike_priority, racingbike_average_speed").
+                        "racingbike_access, racingbike_priority, racingbike_average_speed, " +
+                        "foot_road_access, bike_road_access").
                 setGraphHopperLocation(GH_LOCATION);
         hopper.getRouterConfig().setSimplifyResponse(false);
         hopper.setMinNetworkSize(0);
diff --git a/core/src/test/java/com/graphhopper/routing/util/parsers/BikeCustomModelTest.java b/core/src/test/java/com/graphhopper/routing/util/parsers/BikeCustomModelTest.java
index bbfb7726a0e..ffc0a7b8b1f 100644
--- a/core/src/test/java/com/graphhopper/routing/util/parsers/BikeCustomModelTest.java
+++ b/core/src/test/java/com/graphhopper/routing/util/parsers/BikeCustomModelTest.java
@@ -28,19 +28,21 @@ public void setup() {
         IntEncodedValue hikeRating = HikeRating.create();
         em = new EncodingManager.Builder().
                 add(VehicleAccess.create("bike")).
-                add(VehicleAccess.create("mtb")).
-                add(VehicleAccess.create("racingbike")).
                 add(VehicleSpeed.create("bike", 4, 2, false)).
-                add(VehicleSpeed.create("mtb", 4, 2, false)).
-                add(VehicleSpeed.create("racingbike", 4, 2, false)).
                 add(VehiclePriority.create("bike", 4, PriorityCode.getFactor(1), false)).
+                add(VehicleAccess.create("mtb")).
+                add(VehicleSpeed.create("mtb", 4, 2, false)).
                 add(VehiclePriority.create("mtb", 4, PriorityCode.getFactor(1), false)).
+                add(VehicleAccess.create("racingbike")).
+                add(VehicleSpeed.create("racingbike", 4, 2, false)).
                 add(VehiclePriority.create("racingbike", 4, PriorityCode.getFactor(1), false)).
                 add(FerrySpeed.create()).
                 add(RouteNetwork.create(BikeNetwork.KEY)).
                 add(Roundabout.create()).
                 add(Smoothness.create()).
                 add(RoadAccess.create()).
+                add(BikeRoadAccess.create()).
+                add(FootRoadAccess.create()).
                 add(bikeRating).
                 add(hikeRating).build();
 
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 2c085e8530a..f64964ffe74 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
@@ -5,6 +5,7 @@
 import com.graphhopper.routing.util.EncodingManager;
 import com.graphhopper.routing.util.OSMParsers;
 import com.graphhopper.routing.util.PriorityCode;
+import com.graphhopper.routing.util.TransportationMode;
 import com.graphhopper.routing.weighting.custom.CustomModelParser;
 import com.graphhopper.routing.weighting.custom.CustomWeighting;
 import com.graphhopper.storage.BaseGraph;
@@ -31,7 +32,7 @@ public void setup() {
                 add(VehiclePriority.create("foot", 4, PriorityCode.getFactor(1), false)).
                 add(FerrySpeed.create()).
                 add(RouteNetwork.create(FootNetwork.KEY)).
-                add(RoadAccess.create()).
+                add(FootRoadAccess.create()).
                 add(hikeRating).build();
 
         parsers = new OSMParsers().
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 1fb9eee2f35..c3910418e07 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
@@ -14,7 +14,7 @@
 class ModeAccessParserTest {
 
     private final EncodingManager em = new EncodingManager.Builder().add(Roundabout.create()).add(BusAccess.create()).build();
-    private final ModeAccessParser parser = new ModeAccessParser(TransportationMode.BUS,
+    private final ModeAccessParser parser = new ModeAccessParser(OSMRoadAccessParser.toOSMRestrictions(TransportationMode.BUS),
             em.getBooleanEncodedValue(BusAccess.KEY), true,
             em.getBooleanEncodedValue(Roundabout.KEY), Set.of(), Set.of());
     private final BooleanEncodedValue busAccessEnc = em.getBooleanEncodedValue(BusAccess.KEY);
@@ -178,7 +178,8 @@ public void testBarrier() {
         // this special mode ignores all barriers except kissing_gate
         BooleanEncodedValue tmpAccessEnc = new SimpleBooleanEncodedValue("tmp_access", true);
         EncodingManager tmpEM = new EncodingManager.Builder().add(tmpAccessEnc).add(Roundabout.create()).build();
-        ModeAccessParser tmpParser = new ModeAccessParser(TransportationMode.CAR, tmpAccessEnc, true,
+        ModeAccessParser tmpParser = new ModeAccessParser(OSMRoadAccessParser.toOSMRestrictions(TransportationMode.CAR),
+                tmpAccessEnc, true,
                 tmpEM.getBooleanEncodedValue(Roundabout.KEY), Set.of(), Set.of("kissing_gate"));
 
         way = new ReaderWay(1);
@@ -216,7 +217,8 @@ public void testPsvYes() {
     public void testMotorcycleYes() {
         BooleanEncodedValue mcAccessEnc = new SimpleBooleanEncodedValue("motorcycle_access", true);
         EncodingManager mcEM = new EncodingManager.Builder().add(mcAccessEnc).add(Roundabout.create()).build();
-        ModeAccessParser mcParser = new ModeAccessParser(TransportationMode.MOTORCYCLE, mcAccessEnc, true,
+        ModeAccessParser mcParser = new ModeAccessParser(OSMRoadAccessParser.toOSMRestrictions(TransportationMode.MOTORCYCLE),
+                mcAccessEnc, true,
                 mcEM.getBooleanEncodedValue(Roundabout.KEY), Set.of(), Set.of());
 
         int edgeId = 0;
diff --git a/core/src/test/java/com/graphhopper/routing/util/parsers/OSMRoadAccessParserTest.java b/core/src/test/java/com/graphhopper/routing/util/parsers/OSMRoadAccessParserTest.java
index d88115dcc47..64314d54ca4 100644
--- a/core/src/test/java/com/graphhopper/routing/util/parsers/OSMRoadAccessParserTest.java
+++ b/core/src/test/java/com/graphhopper/routing/util/parsers/OSMRoadAccessParserTest.java
@@ -30,18 +30,25 @@
 
 class OSMRoadAccessParserTest {
 
-    EnumEncodedValue<RoadAccess> roadAccessEnc = RoadAccess.create();
-    private OSMRoadAccessParser parser;
+    private final EnumEncodedValue<RoadAccess> roadAccessEnc = RoadAccess.create();
+    private OSMRoadAccessParser<RoadAccess> parser;
+    private final EnumEncodedValue<BikeRoadAccess> bikeRAEnc = BikeRoadAccess.create();
+    private OSMRoadAccessParser<BikeRoadAccess> bikeRAParser;
 
     @BeforeEach
     public void setup() {
         roadAccessEnc.init(new EncodedValue.InitializerConfig());
-        parser = new OSMRoadAccessParser(roadAccessEnc, OSMRoadAccessParser.toOSMRestrictions(TransportationMode.CAR));
+        bikeRAEnc.init(new EncodedValue.InitializerConfig());
+        parser = new OSMRoadAccessParser<>(roadAccessEnc, OSMRoadAccessParser.toOSMRestrictions(TransportationMode.CAR),
+                RoadAccess::countryHook, RoadAccess::find);
+        bikeRAParser = new OSMRoadAccessParser<>(bikeRAEnc, OSMRoadAccessParser.toOSMRestrictions(TransportationMode.BIKE),
+                (ignr, access) -> access, BikeRoadAccess::find);
     }
+
     @Test
     void countryRule() {
         IntsRef relFlags = new IntsRef(2);
-        ReaderWay way = new ReaderWay(27L);
+        ReaderWay way = new ReaderWay(1L);
         way.setTag("highway", "track");
         way.setTag("country_rule", new CountryRule() {
             @Override
@@ -74,10 +81,46 @@ public RoadAccess getAccess(ReaderWay readerWay, TransportationMode transportati
     public void testPermit() {
         ArrayEdgeIntAccess edgeIntAccess = new ArrayEdgeIntAccess(1);
         int edgeId = 0;
-        ReaderWay way = new ReaderWay(27L);
+        ReaderWay way = new ReaderWay(1L);
         way.setTag("motor_vehicle", "permit");
         parser.handleWayTags(edgeId, edgeIntAccess, way, new IntsRef(1));
         assertEquals(RoadAccess.PRIVATE, roadAccessEnc.getEnum(false, edgeId, edgeIntAccess));
     }
 
-}
\ No newline at end of file
+    @Test
+    public void testCar() {
+        ArrayEdgeIntAccess edgeIntAccess = new ArrayEdgeIntAccess(1);
+        int edgeId = 0;
+        ReaderWay way = new ReaderWay(1L);
+        way.setTag("access", "private");
+        parser.handleWayTags(edgeId, edgeIntAccess, way, new IntsRef(1));
+        assertEquals(RoadAccess.PRIVATE, roadAccessEnc.getEnum(false, edgeId, edgeIntAccess));
+
+        edgeIntAccess = new ArrayEdgeIntAccess(1);
+        way.setTag("motorcar", "yes");
+        parser.handleWayTags(edgeId, edgeIntAccess, way, new IntsRef(1));
+        assertEquals(RoadAccess.YES, roadAccessEnc.getEnum(false, edgeId, edgeIntAccess));
+    }
+
+    @Test
+    public void testBike() {
+        ArrayEdgeIntAccess edgeIntAccess = new ArrayEdgeIntAccess(1);
+        int edgeId = 0;
+        ReaderWay way = new ReaderWay(1L);
+        way.setTag("access", "private");
+        bikeRAParser.handleWayTags(edgeId, edgeIntAccess, way, new IntsRef(1));
+        assertEquals(BikeRoadAccess.PRIVATE, bikeRAEnc.getEnum(false, edgeId, edgeIntAccess));
+
+        way = new ReaderWay(1L);
+        edgeIntAccess = new ArrayEdgeIntAccess(1);
+        way.setTag("vehicle", "private");
+        bikeRAParser.handleWayTags(edgeId, edgeIntAccess, way, new IntsRef(1));
+        assertEquals(BikeRoadAccess.PRIVATE, bikeRAEnc.getEnum(false, edgeId, edgeIntAccess));
+
+        edgeIntAccess = new ArrayEdgeIntAccess(1);
+        way.setTag("bicycle", "yes");
+        bikeRAParser.handleWayTags(edgeId, edgeIntAccess, way, new IntsRef(1));
+        assertEquals(BikeRoadAccess.YES, bikeRAEnc.getEnum(false, edgeId, edgeIntAccess));
+    }
+
+}
diff --git a/web/src/test/java/com/graphhopper/application/resources/RouteResourceCustomModelTest.java b/web/src/test/java/com/graphhopper/application/resources/RouteResourceCustomModelTest.java
index 5c0b7aef7ed..d462c0f9956 100644
--- a/web/src/test/java/com/graphhopper/application/resources/RouteResourceCustomModelTest.java
+++ b/web/src/test/java/com/graphhopper/application/resources/RouteResourceCustomModelTest.java
@@ -65,8 +65,12 @@ private static GraphHopperServerConfiguration createConfig() {
                 putObject("graph.location", DIR).
                 putObject("custom_areas.directory", "./src/test/resources/com/graphhopper/application/resources/areas").
                 putObject("import.osm.ignored_highways", "").
-                putObject("graph.encoded_values", "max_height, max_weight, max_width, hazmat, toll, surface, track_type, hgv, average_slope, max_slope, bus_access, " +
-                        "car_access, car_average_speed, bike_access, bike_priority, bike_average_speed, road_class, road_access, get_off_bike, roundabout, foot_access, foot_priority, foot_average_speed, country, orientation, mtb_rating, hike_rating").
+                putObject("graph.encoded_values", "car_access, car_average_speed, road_access, " +
+                        "bike_access, bike_priority, bike_average_speed, bike_road_access, " +
+                        "foot_access, foot_priority, foot_average_speed, foot_road_access, " +
+                        "max_height, max_weight, max_width, hazmat, toll, surface, track_type, hgv, " +
+                        "average_slope, max_slope, bus_access, road_class, get_off_bike, roundabout, " +
+                        "country, orientation, mtb_rating, hike_rating").
                 setProfiles(List.of(
                         TestProfiles.constantSpeed("roads", 120),
                         new Profile("car").setCustomModel(TestProfiles.accessAndSpeed("unused", "car").