Repository: graphhopper/graphhopper
Pull request #1956 (2020-03-14 22:40:05)

refactor PMap to use string,object instead of string,string
see discussion https://github.com/graphhopper/graphhopper/pull/1949#issuecomment-598701749 with @easbar .

This is cleaner than #1955:

 * For `put(String, String)` the user gets a deprecation notice
 * For `put(String, Object)` the user gets a compilation error and is forced to use putObject
 * get("key") is removed
 * ~get("key", "default") breaks backward compatibility if the value for "key" is a number. This is acceptable IMO as this usage should be rare except maybe for debugging.~ no longer the case as renamed to getString.

parsing parameters from URL should still work due to Helper.toObject(String) 

karussell: I changed `put(String, Object)` into `put(String, String)` to force user moving off this method and use the new `putObject(String, Object)` method instead. As the new method has no camelCase conversion and no "automated deserialization from string".

And I decided against `put(String, Object)` because then we cannot later remove the camelCase conversion and also not the "string to object deserialization" (see new Helper.toObject). I.e. the intend is to move these additional features out of the PMap and force the user to use external methods, if this is necessary at all. The only place where it is necessary is converting the URL parameters from string to object.

The only problem is the `String get(String key, String _default)` that now breaks backward compatibility. E.g. also we used this for EDGE_BASED etc ... maybe I should rename this into getString to force a safe upgrade path?
easbar: > to force user moving off this method 

Ah I see this is a bit more critical because `HintsMap` is also used on the client side. Would it make sense to use an extra class for the client side (with camel case conversion etc.) and refactor more aggressively on the server side? There is a similar problem with `setVehicle/Weighting` vs `setProfile` in #1859. 

> The only problem is the String get(String key, String _default) that now breaks backward compatibility.

What is the problem here?
karussell: > Ah I see this is a bit more critical because HintsMap is also used on the client side. Would it make sense to use an extra class for the client side (with camel case conversion etc.) and refactor more aggressively on the server side? 

We can also remove `put(String, String)` now but I thought to keep it for 1 version.

> There is a similar problem with setVehicle/Weighting vs setProfile in #1859

Yes, for client side we might keep vehicle&weighting for a while ... maybe replace "HintsMap" with "PMap" on the server side and keep using HintsMap for a while for client-hc only?

> > The only problem is the String get(String key, String _default) that now breaks backward compatibility.
> What is the problem here?

E.g. `get(EDGE_BASED, "xy")` (boolean value) returned before the actual value as string, but will now return the default `"xy"` because the value is no String instance.
karussell: > E.g. get(EDGE_BASED, "xy") (boolean value) returned before the actual value as string, but will now return the default "xy" because the value is no String instance.

I will rename this to be on the safe side and if this is really needed one use `getObject(EDGE_BASED, "xy").toString()`
easbar: > Yes, for client side we might keep vehicle&weighting for a while ... maybe replace "HintsMap" with "PMap" on the server side and keep using HintsMap for a while for client-hc only?

Ok yes that makes sense, HintsMap will then be the 'client-side' PMap? Maybe we can then also use `put(String, String)` only in `HintsMap` (and maybe even rename `putObject` back to `put` on the server side ;)) If its easier it should be no problem to no longer let `HintsMap` extend `PMap`? 

> E.g. get(EDGE_BASED, "xy") (boolean value) returned before the actual value as string,

Ah I see, but why would we use `get(EDGE_BASED, "xy")` instead of `getBool(EDGE_BASED, false)`? So far I think I always tried to use `getBool` if I was expecting a bool and only used `get` to retrieve strings?
karussell: > Ah I see, but why would we use get(EDGE_BASED, "xy") instead of getBool(EDGE_BASED, false)?

Old code ... see e.g. GraphHopper.java in #1958
easbar: > Old code ... see e.g. GraphHopper.java in #1958 

Thats not old code, its not even in master yet. And it can/should be replaced with `has(EDGE_BASED)` anyway :)
karussell: > and maybe even rename putObject back to put on the server side ;)

Ok, but we should delay this for 1 or 2 versions to avoid silent migration issues as e.g. `put("key", "true")` had a different meaning before this PR.
easbar: > Ok, but we should delay this for 1 or 2 versions to avoid silent migration issues as e.g. put("key", "true") had a different meaning before this PR.

Ok, another problem might be that we are using `GHRequest` on client+server side and it has a `HintsMap`. 
karussell: > And it can/should be replaced with has(EDGE_BASED) anyway :)

What I meant is that there can be code that worked before and will now (silently) break, which is ugly. So, I renamed `get` into `getString` ... which also makes sense when we later rename `putObject` into `put`, then we could rename `getObject` into `get` (?)
easbar: > What I meant is that there can be code that worked before and will now (silently) break, which is ugly.

Yes you are right that might happen in client code. 

>  So, I renamed `get` into `getString` ... which also makes sense when we later rename `putObject` into `put`, then we could rename `getObject` into `get` (?)

:+1: 


easbar: > Yes you are right that might happen in client code.

This somewhat tells me that it is not a good idea to use 'trivial' classes like `HintsMap` and `GHRequest` both internally and in `client-hc`. It might be better to duplicate these classes to draw a clear boundary between code that is subject to change at any time and code that needs to be kept stable so we do not break external client code.
easbar: > This is cleaner than #1955 but it slightly breaks backward compatibility:

Maybe update the initial post so it can be referenced later?
karussell: > It might be better to duplicate these classes to draw a clear boundary between code that is subject to change at any time and code that needs to be kept stable so we do not break external client code.

The advantage of the client code is that it only breaks existing code if the user upgrades the client dependency. And for a switch from 0.x to 1.0 I would kind of expect that I need to change my usage code. Also now it should be a "safe" upgrade (no silent issues IMO) and the compiler should tell all the problems.

> Maybe update the initial post so it can be referenced later?

Done :+1: 

Patch:
diff --git a/api/src/main/java/com/graphhopper/GHRequest.java b/api/src/main/java/com/graphhopper/GHRequest.java
index 092c53026..d57c627f7 100644
--- a/api/src/main/java/com/graphhopper/GHRequest.java
+++ b/api/src/main/java/com/graphhopper/GHRequest.java
@@ -252,13 +252,14 @@ public class GHRequest {
     }
 
     /**
-     * This method sets a key value pair in the hints and is equivalent to getHints().put(String, String) but unrelated
-     * to the setPointHints method. It is mainly used for deserialization with Jackson.
+     * This method sets a key value pair in the hints and is unrelated to the setPointHints method.
+     * It is mainly used for deserialization with Jackson.
      *
      * @see #setPointHints(List)
      */
-    public void putHint(String fieldName, Object value) {
-        this.hints.put(fieldName, value);
+    public GHRequest putHint(String fieldName, Object value) {
+        this.hints.putObject(fieldName, value);
+        return this;
     }
 
     public GHRequest setPointHints(List<String> pointHints) {
diff --git a/api/src/main/java/com/graphhopper/GraphHopperConfig.java b/api/src/main/java/com/graphhopper/GraphHopperConfig.java
index 63cca85f2..3a25e2bad 100644
--- a/api/src/main/java/com/graphhopper/GraphHopperConfig.java
+++ b/api/src/main/java/com/graphhopper/GraphHopperConfig.java
@@ -75,7 +75,7 @@ public class GraphHopperConfig {
     }
 
     public GraphHopperConfig put(String key, Object value) {
-        map.put(key, value);
+        map.putObject(key, value);
         return this;
     }
 
@@ -103,8 +103,8 @@ public class GraphHopperConfig {
         return map.getDouble(key, _default);
     }
 
-    public String get(String key, String _default) {
-        return map.get(key, _default);
+    public String getString(String key, String _default) {
+        return map.getString(key, _default);
     }
 
     public PMap asPMap() {
@@ -130,7 +130,7 @@ public class GraphHopperConfig {
             sb.append("\n");
         }
         sb.append("properties:\n");
-        for (Map.Entry<String, String> entry : map.toMap().entrySet()) {
+        for (Map.Entry<String, Object> entry : map.toMap().entrySet()) {
             sb.append(entry.getKey()).append(": ").append(entry.getValue());
             sb.append("\n");
         }
diff --git a/api/src/main/java/com/graphhopper/config/ProfileConfig.java b/api/src/main/java/com/graphhopper/config/ProfileConfig.java
index 5564f97d9..71fcf7e3c 100644
--- a/api/src/main/java/com/graphhopper/config/ProfileConfig.java
+++ b/api/src/main/java/com/graphhopper/config/ProfileConfig.java
@@ -92,7 +92,7 @@ public class ProfileConfig {
     }
 
     public ProfileConfig putHint(String key, Object value) {
-        this.hints.put(key, value);
+        this.hints.putObject(key, value);
         return this;
     }
 
diff --git a/api/src/main/java/com/graphhopper/routing/util/HintsMap.java b/api/src/main/java/com/graphhopper/routing/util/HintsMap.java
index 098325cc5..45367c86e 100644
--- a/api/src/main/java/com/graphhopper/routing/util/HintsMap.java
+++ b/api/src/main/java/com/graphhopper/routing/util/HintsMap.java
@@ -49,13 +49,19 @@ public class HintsMap extends PMap {
     }
 
     @Override
-    public HintsMap put(String key, Object str) {
-        super.put(key, str);
+    public HintsMap putObject(String key, Object object) {
+        super.putObject(key, object);
+        return this;
+    }
+
+    @Override
+    public HintsMap put(String key, String string) {
+        super.put(key, string);
         return this;
     }
 
     public String getWeighting() {
-        return toLowerCase(super.get("weighting", ""));
+        return toLowerCase(super.getString("weighting", ""));
     }
 
     public HintsMap setWeighting(String w) {
@@ -65,7 +71,7 @@ public class HintsMap extends PMap {
     }
 
     public String getVehicle() {
-        return toLowerCase(super.get("vehicle", ""));
+        return toLowerCase(super.getString("vehicle", ""));
     }
 
     public HintsMap setVehicle(String v) {
diff --git a/api/src/main/java/com/graphhopper/util/Helper.java b/api/src/main/java/com/graphhopper/util/Helper.java
index 8695f3ec4..dd0d23566 100644
--- a/api/src/main/java/com/graphhopper/util/Helper.java
+++ b/api/src/main/java/com/graphhopper/util/Helper.java
@@ -410,6 +410,33 @@ public class Helper {
         return (int) x;
     }
 
+    /**
+     * This method probes the specified string for a boolean, int, long, float and double. If all this fails it returns
+     * the unchanged string.
+     */
+    public static Object toObject(String string) {
+        if ("true".equalsIgnoreCase(string) || "false".equalsIgnoreCase(string))
+            return Boolean.parseBoolean(string);
+        try {
+            return Integer.parseInt(string);
+        } catch (NumberFormatException ex) {
+            try {
+                return Long.parseLong(string);
+            } catch (NumberFormatException ex2) {
+                try {
+                    return Float.parseFloat(string);
+                } catch (NumberFormatException ex3) {
+                    try {
+                        return Double.parseDouble(string);
+                    } catch (NumberFormatException ex4) {
+                        // give up and simply return the string
+                        return string;
+                    }
+                }
+            }
+        }
+    }
+
     public static String camelCaseToUnderScore(String key) {
         if (key.isEmpty())
             return key;
diff --git a/api/src/main/java/com/graphhopper/util/PMap.java b/api/src/main/java/com/graphhopper/util/PMap.java
index 00178d6a7..4a7db734a 100644
--- a/api/src/main/java/com/graphhopper/util/PMap.java
+++ b/api/src/main/java/com/graphhopper/util/PMap.java
@@ -18,11 +18,8 @@
 package com.graphhopper.util;
 
 import java.util.HashMap;
-import java.util.LinkedHashMap;
 import java.util.Map;
 
-import static com.graphhopper.util.Helper.toLowerCase;
-
 /**
  * A properties map (String to String) with convenient accessors
  * <p>
@@ -30,7 +27,7 @@ import static com.graphhopper.util.Helper.toLowerCase;
  * @author Peter Karich
  */
 public class PMap {
-    private final Map<String, String> map;
+    private final Map<String, Object> map;
 
     public PMap() {
         this(5);
@@ -40,7 +37,7 @@ public class PMap {
         this(new HashMap<>(capacity));
     }
 
-    public PMap(Map<String, String> map) {
+    public PMap(Map<String, Object> map) {
         this.map = new HashMap<>(map);
     }
 
@@ -66,7 +63,7 @@ public class PMap {
      * Reads a PMap from a string array consisting of key=value pairs
      */
     public static PMap read(String[] args) {
-        Map<String, String> map = new LinkedHashMap<>();
+        PMap map = new PMap();
         for (String arg : args) {
             int index = arg.indexOf("=");
             if (index <= 0) {
@@ -83,12 +80,12 @@ public class PMap {
             }
 
             String value = arg.substring(index + 1);
-            String old = map.put(toLowerCase(key), value);
+            Object old = map.map.put(Helper.camelCaseToUnderScore(key), Helper.toObject(value));
             if (old != null)
-                throw new IllegalArgumentException("Pair '" + toLowerCase(key) + "'='" + value + "' not possible to " +
+                throw new IllegalArgumentException("Pair '" + Helper.camelCaseToUnderScore(key) + "'='" + value + "' not possible to " +
                         "add to the PMap-object as the key already exists with '" + old + "'");
         }
-        return new PMap(map);
+        return map;
     }
 
     public PMap put(PMap map) {
@@ -96,132 +93,78 @@ public class PMap {
         return this;
     }
 
-    public PMap put(String key, Object str) {
+    /**
+     * @deprecated use {@link #putObject(String, Object)} instead
+     */
+    public PMap put(String key, String str) {
         if (str == null)
             throw new NullPointerException("Value cannot be null. Use remove instead.");
-
-        // store in under_score
-        map.put(Helper.camelCaseToUnderScore(key), str.toString());
+        map.put(Helper.camelCaseToUnderScore(key), Helper.toObject(str));
         return this;
     }
 
     public PMap remove(String key) {
-        // query accepts camelCase and under_score
-        map.remove(Helper.camelCaseToUnderScore(key));
+        map.remove(key);
         return this;
     }
 
     public boolean has(String key) {
-        // query accepts camelCase and under_score
-        return map.containsKey(Helper.camelCaseToUnderScore(key));
+        return map.containsKey(key);
     }
 
-    public long getLong(String key, long _default) {
-        String str = get(key);
-        if (!Helper.isEmpty(str)) {
-            try {
-                return Long.parseLong(str);
-            } catch (Exception ex) {
-            }
-        }
-        return _default;
+    public boolean getBool(String key, boolean _default) {
+        Object object = map.get(key);
+        return object instanceof Boolean ? (Boolean) object : _default;
     }
 
     public int getInt(String key, int _default) {
-        String str = get(key);
-        if (!Helper.isEmpty(str)) {
-            try {
-                return Integer.parseInt(str);
-            } catch (Exception ex) {
-            }
-        }
-        return _default;
+        Object object = map.get(key);
+        return object instanceof Number ? ((Number) object).intValue() : _default;
     }
 
-    public boolean getBool(String key, boolean _default) {
-        String str = get(key);
-        if (!Helper.isEmpty(str)) {
-            try {
-                return Boolean.parseBoolean(str);
-            } catch (Exception ex) {
-            }
-        }
-        return _default;
-    }
-
-    public double getDouble(String key, double _default) {
-        String str = get(key);
-        if (!Helper.isEmpty(str)) {
-            try {
-                return Double.parseDouble(str);
-            } catch (Exception ex) {
-            }
-        }
-        return _default;
+    public long getLong(String key, long _default) {
+        Object object = map.get(key);
+        return object instanceof Number ? ((Number) object).longValue() : _default;
     }
 
     public float getFloat(String key, float _default) {
-        String str = get(key);
-        if (!Helper.isEmpty(str)) {
-            try {
-                return Float.parseFloat(str);
-            } catch (Exception ex) {
-            }
-        }
-        return _default;
+        Object object = map.get(key);
+        return object instanceof Number ? ((Number) object).floatValue() : _default;
     }
 
-    public String get(String key, String _default) {
-        String str = get(key);
-        if (Helper.isEmpty(str))
-            return _default;
-
-        return str;
+    public double getDouble(String key, double _default) {
+        Object object = map.get(key);
+        return object instanceof Number ? ((Number) object).doubleValue() : _default;
     }
 
-    String get(String key) {
-        if (Helper.isEmpty(key))
-            return "";
+    public String getString(String key, String _default) {
+        Object object = map.get(key);
+        return object instanceof String ? (String) object : _default;
+    }
 
-        // query accepts camelCase and under_score
-        String val = map.get(Helper.camelCaseToUnderScore(key));
-        if (val == null)
-            return "";
+    public Object getObject(String key, Object _default) {
+        Object object = map.get(key);
+        return object == null ? _default : object;
+    }
 
-        return val;
+    public PMap putObject(String key, Object object) {
+        map.put(key, object);
+        return this;
     }
 
     /**
      * This method copies the underlying structure into a new Map object
      */
-    public Map<String, String> toMap() {
+    public Map<String, Object> toMap() {
         return new HashMap<>(map);
     }
 
-    private Map<String, String> getMap() {
-        return map;
-    }
-
-    public PMap merge(PMap read) {
-        return merge(read.getMap());
-    }
-
-    PMap merge(Map<String, String> map) {
-        for (Map.Entry<String, String> e : map.entrySet()) {
-            if (Helper.isEmpty(e.getKey()))
-                continue;
-
-            put(e.getKey(), e.getValue());
-        }
-        return this;
-    }
-
     public boolean isEmpty() {
         return map.isEmpty();
     }
 
     @Override
     public String toString() {
-        return getMap().toString();
+        return map.toString();
     }
 }
diff --git a/client-hc/src/main/java/com/graphhopper/api/GHMatrixAbstractRequester.java b/client-hc/src/main/java/com/graphhopper/api/GHMatrixAbstractRequester.java
index d36f8cbfb..4078b47cd 100644
--- a/client-hc/src/main/java/com/graphhopper/api/GHMatrixAbstractRequester.java
+++ b/client-hc/src/main/java/com/graphhopper/api/GHMatrixAbstractRequester.java
@@ -106,13 +106,16 @@ public abstract class GHMatrixAbstractRequester {
         // requestJson.put("elevation", ghRequest.getHints().getBool("elevation", false));
         requestJson.put("fail_fast", ghRequest.getFailFast());
 
-        Map<String, String> hintsMap = ghRequest.getHints().toMap();
+        Map<String, Object> hintsMap = ghRequest.getHints().toMap();
         for (String hintKey : hintsMap.keySet()) {
             if (ignoreSet.contains(hintKey))
                 continue;
 
-            String hint = hintsMap.get(hintKey);
-            requestJson.put(hintKey, hint);
+            Object hint = hintsMap.get(hintKey);
+            if (hint instanceof String)
+                requestJson.put(hintKey, (String) hint);
+            else
+                requestJson.putPOJO(hintKey, hint);
         }
         return requestJson;
     }
@@ -282,8 +285,8 @@ public abstract class GHMatrixAbstractRequester {
 
     protected String buildURLNoHints(String path, GHMRequest ghRequest) {
         // allow per request service URLs
-        String url = ghRequest.getHints().get(SERVICE_URL, serviceUrl) + path + "?";
-        String key = ghRequest.getHints().get(KEY, "");
+        String url = ghRequest.getHints().getString(SERVICE_URL, serviceUrl) + path + "?";
+        String key = ghRequest.getHints().getString(KEY, "");
         if (!Helper.isEmpty(key)) {
             url += "key=" + key;
         }
diff --git a/client-hc/src/main/java/com/graphhopper/api/GraphHopperWeb.java b/client-hc/src/main/java/com/graphhopper/api/GraphHopperWeb.java
index 2c749fd72..1e95f1618 100644
--- a/client-hc/src/main/java/com/graphhopper/api/GraphHopperWeb.java
+++ b/client-hc/src/main/java/com/graphhopper/api/GraphHopperWeb.java
@@ -64,7 +64,7 @@ public class GraphHopperWeb implements GraphHopperAPI {
     private boolean calcPoints = true;
     private boolean elevation = false;
     private String optimize = "false";
-    private boolean postRequest = true;
+    boolean postRequest = true;
     int maxUnzippedLength = 1000;
     private final Set<String> ignoreSet;
     private final Set<String> ignoreSetForPost;
@@ -232,7 +232,7 @@ public class GraphHopperWeb implements GraphHopperAPI {
     }
 
     private Request createPostRequest(GHRequest ghRequest) {
-        String tmpServiceURL = ghRequest.getHints().get(SERVICE_URL, routeServiceUrl);
+        String tmpServiceURL = ghRequest.getHints().getString(SERVICE_URL, routeServiceUrl);
         String url = tmpServiceURL + "?";
         if (!Helper.isEmpty(key))
             url += "key=" + key;
@@ -256,15 +256,19 @@ public class GraphHopperWeb implements GraphHopperAPI {
         requestJson.put(INSTRUCTIONS, ghRequest.getHints().getBool(INSTRUCTIONS, instructions));
         requestJson.put(CALC_POINTS, ghRequest.getHints().getBool(CALC_POINTS, calcPoints));
         requestJson.put("elevation", ghRequest.getHints().getBool("elevation", elevation));
-        requestJson.put("optimize", ghRequest.getHints().get("optimize", optimize));
+        requestJson.put("optimize", ghRequest.getHints().getString("optimize", optimize));
 
-        Map<String, String> hintsMap = ghRequest.getHints().toMap();
-        for (String hintKey : hintsMap.keySet()) {
+        Map<String, Object> hintsMap = ghRequest.getHints().toMap();
+        for (Map.Entry<String, Object> entry : hintsMap.entrySet()) {
+            String hintKey = entry.getKey();
             if (ignoreSetForPost.contains(hintKey))
                 continue;
 
-            String hint = hintsMap.get(hintKey);
-            requestJson.put(hintKey, hint);
+            // special case for String required, see testPutPOJO
+            if (entry.getValue() instanceof String)
+                requestJson.put(hintKey, (String) entry.getValue());
+            else
+                requestJson.putPOJO(hintKey, entry.getValue());
         }
         String stringData = requestJson.toString();
         Request.Builder builder = new Request.Builder().url(url).post(RequestBody.create(MT_JSON, stringData));
@@ -277,7 +281,7 @@ public class GraphHopperWeb implements GraphHopperAPI {
     private Request createGetRequest(GHRequest ghRequest) {
         boolean tmpInstructions = ghRequest.getHints().getBool(INSTRUCTIONS, instructions);
         boolean tmpCalcPoints = ghRequest.getHints().getBool(CALC_POINTS, calcPoints);
-        String tmpOptimize = ghRequest.getHints().get("optimize", optimize);
+        String tmpOptimize = ghRequest.getHints().getString("optimize", optimize);
 
         if (tmpInstructions && !tmpCalcPoints) {
             throw new IllegalStateException("Cannot calculate instructions without points (only points without instructions). "
@@ -291,7 +295,7 @@ public class GraphHopperWeb implements GraphHopperAPI {
             places += "point=" + round6(p.lat) + "," + round6(p.lon) + "&";
         }
 
-        String type = ghRequest.getHints().get("type", "json");
+        String type = ghRequest.getHints().getString("type", "json");
 
         String url = routeServiceUrl
                 + "?"
@@ -341,9 +345,9 @@ public class GraphHopperWeb implements GraphHopperAPI {
             url += "&key=" + WebHelper.encodeURL(key);
         }
 
-        for (Map.Entry<String, String> entry : ghRequest.getHints().toMap().entrySet()) {
+        for (Map.Entry<String, Object> entry : ghRequest.getHints().toMap().entrySet()) {
             String urlKey = entry.getKey();
-            String urlValue = entry.getValue();
+            String urlValue = entry.getValue().toString();
 
             // use lower case conversion for check only!
             if (ignoreSet.contains(toLowerCase(urlKey))) {
diff --git a/core/files/changelog.txt b/core/files/changelog.txt
index bfe5b653a..49fdd1c25 100644
--- a/core/files/changelog.txt
+++ b/core/files/changelog.txt
@@ -1,4 +1,5 @@
 1.0
+    PMap refactored. It is recommended to use putObject(String, Object) instead of put, #1956
     removed UnsafeDataAccess as not maintained, see #1620
     add profiles parameter and replace prepare.ch/lm.weightings and prepare.ch.edge_based with profiles_ch/lm config parameters, #1922
     Properties in config.yml have to follow the snake_case, #1918, easy convert via https://github.com/karussell/snake_case
diff --git a/core/src/main/java/com/graphhopper/GraphHopper.java b/core/src/main/java/com/graphhopper/GraphHopper.java
index 40e790355..c1139ff8b 100644
--- a/core/src/main/java/com/graphhopper/GraphHopper.java
+++ b/core/src/main/java/com/graphhopper/GraphHopper.java
@@ -508,11 +508,11 @@ public class GraphHopper implements GraphHopperAPI {
         if (ghConfig.has("osmreader.osm"))
             throw new IllegalArgumentException("Instead osmreader.osm use datareader.file, for other changes see core/files/changelog.txt");
 
-        String tmpOsmFile = ghConfig.get("datareader.file", "");
+        String tmpOsmFile = ghConfig.getString("datareader.file", "");
         if (!isEmpty(tmpOsmFile))
             dataReaderFile = tmpOsmFile;
 
-        String graphHopperFolder = ghConfig.get("graph.location", "");
+        String graphHopperFolder = ghConfig.getString("graph.location", "");
         if (isEmpty(graphHopperFolder) && isEmpty(ghLocation)) {
             if (isEmpty(dataReaderFile))
                 throw new IllegalArgumentException("If no graph.location is provided you need to specify an OSM file.");
@@ -524,7 +524,7 @@ public class GraphHopper implements GraphHopperAPI {
         setGraphHopperLocation(graphHopperFolder);
         defaultSegmentSize = ghConfig.getInt("graph.dataaccess.segment_size", defaultSegmentSize);
 
-        String graphDATypeStr = ghConfig.get("graph.dataaccess", "RAM_STORE");
+        String graphDATypeStr = ghConfig.getString("graph.dataaccess", "RAM_STORE");
         dataAccessType = DAType.fromString(graphDATypeStr);
 
         sortGraph = ghConfig.getBool("graph.do_sort", sortGraph);
@@ -535,7 +535,7 @@ public class GraphHopper implements GraphHopperAPI {
             setEncodingManager(encodingManager);
         }
 
-        if (ghConfig.get("graph.locktype", "native").equals("simple"))
+        if (ghConfig.getString("graph.locktype", "native").equals("simple"))
             lockFactory = new SimpleFSLockFactory();
         else
             lockFactory = new NativeFSLockFactory();
@@ -574,8 +574,8 @@ public class GraphHopper implements GraphHopperAPI {
     }
 
     private EncodingManager createEncodingManager(GraphHopperConfig ghConfig) {
-        String flagEncodersStr = ghConfig.get("graph.flag_encoders", "");
-        String encodedValueStr = ghConfig.get("graph.encoded_values", "");
+        String flagEncodersStr = ghConfig.getString("graph.flag_encoders", "");
+        String encodedValueStr = ghConfig.getString("graph.encoded_values", "");
         if (flagEncodersStr.isEmpty() && encodedValueStr.isEmpty()) {
             return null;
         } else {
@@ -586,34 +586,34 @@ public class GraphHopper implements GraphHopperAPI {
             if (!flagEncodersStr.isEmpty())
                 emBuilder.addAll(flagEncoderFactory, flagEncodersStr);
             emBuilder.setEnableInstructions(ghConfig.getBool("datareader.instructions", true));
-            emBuilder.setPreferredLanguage(ghConfig.get("datareader.preferred_language", ""));
-            emBuilder.setDateRangeParser(DateRangeParser.createInstance(ghConfig.get("datareader.date_range_parser_day", "")));
+            emBuilder.setPreferredLanguage(ghConfig.getString("datareader.preferred_language", ""));
+            emBuilder.setDateRangeParser(DateRangeParser.createInstance(ghConfig.getString("datareader.date_range_parser_day", "")));
             return emBuilder.build();
         }
     }
 
     private static ElevationProvider createElevationProvider(GraphHopperConfig ghConfig) {
-        String eleProviderStr = toLowerCase(ghConfig.get("graph.elevation.provider", "noop"));
+        String eleProviderStr = toLowerCase(ghConfig.getString("graph.elevation.provider", "noop"));
 
         if (ghConfig.has("graph.elevation.calcmean"))
             throw new IllegalArgumentException("graph.elevation.calcmean is deprecated, use graph.elevation.interpolate");
 
         boolean interpolate = ghConfig.has("graph.elevation.interpolate")
-                ? "bilinear".equals(ghConfig.get("graph.elevation.interpolate", "none"))
+                ? "bilinear".equals(ghConfig.getString("graph.elevation.interpolate", "none"))
                 : ghConfig.getBool("graph.elevation.calc_mean", false);
 
-        String cacheDirStr = ghConfig.get("graph.elevation.cache_dir", "");
+        String cacheDirStr = ghConfig.getString("graph.elevation.cache_dir", "");
         if (cacheDirStr.isEmpty())
-            cacheDirStr = ghConfig.get("graph.elevation.cachedir", "");
+            cacheDirStr = ghConfig.getString("graph.elevation.cachedir", "");
 
-        String baseURL = ghConfig.get("graph.elevation.base_url", "");
+        String baseURL = ghConfig.getString("graph.elevation.base_url", "");
         if (baseURL.isEmpty())
-            ghConfig.get("graph.elevation.baseurl", "");
+            ghConfig.getString("graph.elevation.baseurl", "");
 
         boolean removeTempElevationFiles = ghConfig.getBool("graph.elevation.cgiar.clear", true);
         removeTempElevationFiles = ghConfig.getBool("graph.elevation.clear", removeTempElevationFiles);
 
-        DAType elevationDAType = DAType.fromString(ghConfig.get("graph.elevation.dataaccess", "MMAP"));
+        DAType elevationDAType = DAType.fromString(ghConfig.getString("graph.elevation.dataaccess", "MMAP"));
         ElevationProvider elevationProvider = ElevationProvider.NOOP;
         if (eleProviderStr.equalsIgnoreCase("srtm")) {
             elevationProvider = new SRTMProvider(cacheDirStr);
diff --git a/core/src/main/java/com/graphhopper/routing/ProfileResolver.java b/core/src/main/java/com/graphhopper/routing/ProfileResolver.java
index b503b774f..3a088c8c9 100644
--- a/core/src/main/java/com/graphhopper/routing/ProfileResolver.java
+++ b/core/src/main/java/com/graphhopper/routing/ProfileResolver.java
@@ -29,8 +29,6 @@ import com.graphhopper.util.Parameters;
 import java.util.ArrayList;
 import java.util.List;
 
-import static com.graphhopper.routing.weighting.Weighting.INFINITE_U_TURN_COSTS;
-
 public class ProfileResolver {
 
     public ProfileConfig resolveProfile(EncodingManager encodingManager, List<CHProfile> chProfiles, List<LMProfile> lmProfiles, HintsMap hints) {
@@ -200,10 +198,10 @@ public class ProfileResolver {
     }
 
     private Boolean getEdgeBased(HintsMap hintsMap) {
-        return hintsMap.has(Parameters.Routing.EDGE_BASED) ? hintsMap.getBool(Parameters.Routing.EDGE_BASED, false) : null;
+        return (Boolean) hintsMap.getObject(Parameters.Routing.EDGE_BASED, null);
     }
 
     private Integer getUTurnCosts(HintsMap hintsMap) {
-        return hintsMap.has(Parameters.Routing.U_TURN_COSTS) ? hintsMap.getInt(Parameters.Routing.U_TURN_COSTS, INFINITE_U_TURN_COSTS) : null;
+        return (Integer) hintsMap.getObject(Parameters.Routing.U_TURN_COSTS, null);
     }
 }
diff --git a/core/src/main/java/com/graphhopper/routing/RoutingAlgorithmFactorySimple.java b/core/src/main/java/com/graphhopper/routing/RoutingAlgorithmFactorySimple.java
index 9b21170ba..a8a3b1671 100644
--- a/core/src/main/java/com/graphhopper/routing/RoutingAlgorithmFactorySimple.java
+++ b/core/src/main/java/com/graphhopper/routing/RoutingAlgorithmFactorySimple.java
@@ -76,7 +76,7 @@ public class RoutingAlgorithmFactorySimple implements RoutingAlgorithmFactory {
     }
 
     public static WeightApproximator getApproximation(String prop, AlgorithmOptions opts, NodeAccess na) {
-        String approxAsStr = opts.getHints().get(prop + ".approximation", "BeelineSimplification");
+        String approxAsStr = opts.getHints().getString(prop + ".approximation", "BeelineSimplification");
         double epsilon = opts.getHints().getDouble(prop + ".epsilon", 1);
 
         BeelineWeightApproximator approx = new BeelineWeightApproximator(na, opts.getWeighting());
diff --git a/core/src/main/java/com/graphhopper/routing/lm/LMPreparationHandler.java b/core/src/main/java/com/graphhopper/routing/lm/LMPreparationHandler.java
index b838c0d31..cbf2ad4fe 100644
--- a/core/src/main/java/com/graphhopper/routing/lm/LMPreparationHandler.java
+++ b/core/src/main/java/com/graphhopper/routing/lm/LMPreparationHandler.java
@@ -83,7 +83,7 @@ public class LMPreparationHandler {
         logDetails = ghConfig.getBool(Landmark.PREPARE + "log_details", false);
         minNodes = ghConfig.getInt(Landmark.PREPARE + "min_network_size", -1);
 
-        for (String loc : ghConfig.get(Landmark.PREPARE + "suggestions_location", "").split(",")) {
+        for (String loc : ghConfig.getString(Landmark.PREPARE + "suggestions_location", "").split(",")) {
             if (!loc.trim().isEmpty())
                 lmSuggestionsLocations.add(loc.trim());
         }
diff --git a/core/src/main/java/com/graphhopper/routing/template/RoundTripRoutingTemplate.java b/core/src/main/java/com/graphhopper/routing/template/RoundTripRoutingTemplate.java
index 61edf8c43..367599c69 100644
--- a/core/src/main/java/com/graphhopper/routing/template/RoundTripRoutingTemplate.java
+++ b/core/src/main/java/com/graphhopper/routing/template/RoundTripRoutingTemplate.java
@@ -115,7 +115,7 @@ public class RoundTripRoutingTemplate extends AbstractRoutingTemplate implements
         algoOpts = AlgorithmOptions.start(algoOpts).
                 algorithm(Parameters.Algorithms.ASTAR_BI).
                 weighting(avoidPathWeighting).build();
-        algoOpts.getHints().put(Algorithms.AStarBi.EPSILON, 2);
+        algoOpts.getHints().putObject(Algorithms.AStarBi.EPSILON, 2);
 
         long visitedNodesSum = 0L;
         QueryResult start = queryResults.get(0);
@@ -138,8 +138,8 @@ public class RoundTripRoutingTemplate extends AbstractRoutingTemplate implements
             avoidPathWeighting.addEdges(path.calcEdges());
         }
 
-        ghResponse.getHints().put("visited_nodes.sum", visitedNodesSum);
-        ghResponse.getHints().put("visited_nodes.average", (float) visitedNodesSum / (queryResults.size() - 1));
+        ghResponse.getHints().putObject("visited_nodes.sum", visitedNodesSum);
+        ghResponse.getHints().putObject("visited_nodes.average", (float) visitedNodesSum / (queryResults.size() - 1));
 
         return pathList;
     }
diff --git a/core/src/main/java/com/graphhopper/routing/template/ViaRoutingTemplate.java b/core/src/main/java/com/graphhopper/routing/template/ViaRoutingTemplate.java
index c7a8f6b7a..132496ecc 100644
--- a/core/src/main/java/com/graphhopper/routing/template/ViaRoutingTemplate.java
+++ b/core/src/main/java/com/graphhopper/routing/template/ViaRoutingTemplate.java
@@ -205,8 +205,8 @@ public class ViaRoutingTemplate extends AbstractRoutingTemplate implements Routi
             fromQResult = toQResult;
         }
 
-        ghResponse.getHints().put("visited_nodes.sum", visitedNodesSum);
-        ghResponse.getHints().put("visited_nodes.average", (float) visitedNodesSum / (pointsCount - 1));
+        ghResponse.getHints().putObject("visited_nodes.sum", visitedNodesSum);
+        ghResponse.getHints().putObject("visited_nodes.average", (float) visitedNodesSum / (pointsCount - 1));
 
         return pathList;
     }
diff --git a/core/src/main/java/com/graphhopper/storage/GraphEdgeIdFinder.java b/core/src/main/java/com/graphhopper/storage/GraphEdgeIdFinder.java
index 7e95a38ed..21d179d59 100644
--- a/core/src/main/java/com/graphhopper/storage/GraphEdgeIdFinder.java
+++ b/core/src/main/java/com/graphhopper/storage/GraphEdgeIdFinder.java
@@ -87,7 +87,7 @@ public class GraphEdgeIdFinder {
 
     public static GraphEdgeIdFinder.BlockArea createBlockArea(Graph graph, LocationIndex locationIndex,
                                                               List<GHPoint> points, HintsMap hints, EdgeFilter edgeFilter) {
-        String blockAreaStr = hints.get(Parameters.Routing.BLOCK_AREA, "");
+        String blockAreaStr = hints.getString(Parameters.Routing.BLOCK_AREA, "");
         GraphEdgeIdFinder.BlockArea blockArea = new GraphEdgeIdFinder(graph, locationIndex).
                 parseBlockArea(blockAreaStr, edgeFilter, hints.getDouble(Parameters.Routing.BLOCK_AREA + ".edge_id_max_area", 1000 * 1000));
         for (GHPoint p : points) {
diff --git a/reader-gtfs/src/main/java/com/graphhopper/reader/gtfs/GraphHopperGtfs.java b/reader-gtfs/src/main/java/com/graphhopper/reader/gtfs/GraphHopperGtfs.java
index 16604ee1f..96d021e8d 100644
--- a/reader-gtfs/src/main/java/com/graphhopper/reader/gtfs/GraphHopperGtfs.java
+++ b/reader-gtfs/src/main/java/com/graphhopper/reader/gtfs/GraphHopperGtfs.java
@@ -123,7 +123,7 @@ public class GraphHopperGtfs extends GraphHopperOSM {
             getGtfsStorage().create();
             GraphHopperStorage graphHopperStorage = getGraphHopperStorage();
             int idx = 0;
-            List<String> gtfsFiles = ghConfig.has("gtfs.file") ? Arrays.asList(ghConfig.get("gtfs.file", "").split(",")) : Collections.emptyList();
+            List<String> gtfsFiles = ghConfig.has("gtfs.file") ? Arrays.asList(ghConfig.getString("gtfs.file", "").split(",")) : Collections.emptyList();
             for (String gtfsFile : gtfsFiles) {
                 try {
                     getGtfsStorage().loadGtfsFromZipFile("gtfs_" + idx++, new ZipFile(gtfsFile));
diff --git a/reader-gtfs/src/main/java/com/graphhopper/reader/gtfs/PtRouteResource.java b/reader-gtfs/src/main/java/com/graphhopper/reader/gtfs/PtRouteResource.java
index f93c3593d..4d8942418 100644
--- a/reader-gtfs/src/main/java/com/graphhopper/reader/gtfs/PtRouteResource.java
+++ b/reader-gtfs/src/main/java/com/graphhopper/reader/gtfs/PtRouteResource.java
@@ -29,7 +29,6 @@ import com.graphhopper.routing.querygraph.QueryGraph;
 import com.graphhopper.routing.querygraph.VirtualEdgeIteratorState;
 import com.graphhopper.routing.util.DefaultEdgeFilter;
 import com.graphhopper.routing.util.EdgeFilter;
-import com.graphhopper.routing.util.EncodingManager;
 import com.graphhopper.routing.weighting.FastestWeighting;
 import com.graphhopper.routing.weighting.Weighting;
 import com.graphhopper.storage.Graph;
@@ -375,8 +374,8 @@ public final class PtRouteResource {
             if (discoveredSolutions.isEmpty() && router.getVisitedNodes() >= maxVisitedNodesForRequest) {
                 response.addError(new IllegalArgumentException("No path found - maximum number of nodes exceeded: " + maxVisitedNodesForRequest));
             }
-            response.getHints().put("visited_nodes.sum", visitedNodes);
-            response.getHints().put("visited_nodes.average", visitedNodes);
+            response.getHints().putObject("visited_nodes.sum", visitedNodes);
+            response.getHints().putObject("visited_nodes.average", visitedNodes);
             if (discoveredSolutions.isEmpty()) {
                 response.addError(new RuntimeException("No route found"));
             }
diff --git a/tools/src/main/java/com/graphhopper/tools/CHMeasurement.java b/tools/src/main/java/com/graphhopper/tools/CHMeasurement.java
index b3afb4cec..ce2ccda4f 100644
--- a/tools/src/main/java/com/graphhopper/tools/CHMeasurement.java
+++ b/tools/src/main/java/com/graphhopper/tools/CHMeasurement.java
@@ -64,9 +64,9 @@ public class CHMeasurement {
         PMap map = PMap.read(args);
         GraphHopperConfig ghConfig = new GraphHopperConfig(map);
         LOGGER.info("Running analysis with parameters {}", ghConfig);
-        String osmFile = ghConfig.get("map", "local/maps/unterfranken-latest.osm.pbf");
+        String osmFile = ghConfig.getString("map", "local/maps/unterfranken-latest.osm.pbf");
         ghConfig.put("datareader.file", osmFile);
-        final String statsFile = ghConfig.get("stats_file", null);
+        final String statsFile = ghConfig.getString("stats_file", null);
         final int periodicUpdates = ghConfig.getInt("period_updates", 0);
         final int lazyUpdates = ghConfig.getInt("lazy_updates", 100);
         final int neighborUpdates = ghConfig.getInt("neighbor_updates", 0);
@@ -140,7 +140,7 @@ public class CHMeasurement {
         sw.start();
         graphHopper.importOrLoad();
         sw.stop();
-        results.put("_prepare_time", sw.getSeconds());
+        results.putObject("_prepare_time", sw.getSeconds());
         LOGGER.info("Import and preparation took {}s", sw.getMillis() / 1000);
 
         if (!quick) {
@@ -160,7 +160,7 @@ public class CHMeasurement {
 
         graphHopper.close();
 
-        Map<String, String> resultMap = results.toMap();
+        Map<String, Object> resultMap = results.toMap();
         TreeSet<String> sortedKeys = new TreeSet<>(resultMap.keySet());
         for (String key : sortedKeys) {
             LOGGER.info(key + "=" + resultMap.get(key));
@@ -199,7 +199,7 @@ public class CHMeasurement {
         return sb.toString();
     }
 
-    private static String getStatLine(TreeSet<String> keys, Map<String, String> results) {
+    private static String getStatLine(TreeSet<String> keys, Map<String, Object> results) {
         StringBuilder sb = new StringBuilder();
         for (String key : keys) {
             sb.append(results.get(key)).append(";");
@@ -234,24 +234,24 @@ public class CHMeasurement {
                     String avgChTime = fmt(chTime * 1.e-6 / run);
                     String avgNoChTime = fmt(noChTime * 1.e-6 / run);
                     LOGGER.info("Finished all ({}) runs, CH: {}ms, without CH: {}ms", iterations, avgChTime, avgNoChTime);
-                    results.put("_" + algo + ".time_comp_ch", avgChTime);
-                    results.put("_" + algo + ".time_comp", avgNoChTime);
-                    results.put("_" + algo + ".errors_ch", chErrors);
-                    results.put("_" + algo + ".errors", noChErrors);
-                    results.put("_" + algo + ".deviations", chDeviations);
+                    results.putObject("_" + algo + ".time_comp_ch", avgChTime);
+                    results.putObject("_" + algo + ".time_comp", avgNoChTime);
+                    results.putObject("_" + algo + ".errors_ch", chErrors);
+                    results.putObject("_" + algo + ".errors", noChErrors);
+                    results.putObject("_" + algo + ".deviations", chDeviations);
                 }
                 GHRequest req = buildRandomRequest(random, numNodes, nodeAccess);
-                req.getHints().put(Parameters.Routing.EDGE_BASED, withTurnCosts);
-                req.getHints().put(Parameters.CH.DISABLE, false);
-                req.getHints().put(Parameters.Landmark.DISABLE, true);
-                req.getHints().put(Parameters.Routing.U_TURN_COSTS, uTurnCosts);
+                req.getHints().putObject(Parameters.Routing.EDGE_BASED, withTurnCosts);
+                req.getHints().putObject(Parameters.CH.DISABLE, false);
+                req.getHints().putObject(Parameters.Landmark.DISABLE, true);
+                req.getHints().putObject(Parameters.Routing.U_TURN_COSTS, uTurnCosts);
                 req.setAlgorithm(algo);
                 long start = nanoTime();
                 GHResponse chRoute = graphHopper.route(req);
                 if (!warmup)
                     chTime += (nanoTime() - start);
 
-                req.getHints().put(Parameters.CH.DISABLE, true);
+                req.getHints().putObject(Parameters.CH.DISABLE, true);
                 start = nanoTime();
                 GHResponse nonChRoute = graphHopper.route(req);
                 if (!warmup)
@@ -317,13 +317,13 @@ public class CHMeasurement {
                     results.put("_" + algo + ".time_ch", avg);
                 }
                 GHRequest req = buildRandomRequest(random, numNodes, nodeAccess);
-                req.getHints().put(Parameters.Routing.EDGE_BASED, withTurnCosts);
-                req.getHints().put(Parameters.CH.DISABLE, lm);
-                req.getHints().put(Parameters.Landmark.DISABLE, !lm);
+                req.putHint(Parameters.Routing.EDGE_BASED, withTurnCosts);
+                req.putHint(Parameters.CH.DISABLE, lm);
+                req.putHint(Parameters.Landmark.DISABLE, !lm);
                 if (!lm) {
                     req.setAlgorithm(algo);
                 } else {
-                    req.getHints().put(Parameters.Landmark.ACTIVE_COUNT, "8");
+                    req.putHint(Parameters.Landmark.ACTIVE_COUNT, 8);
                     req.setWeighting("fastest"); // why do we need this for lm, but not ch ?
                 }
                 long start = nanoTime();
diff --git a/tools/src/main/java/com/graphhopper/tools/Measurement.java b/tools/src/main/java/com/graphhopper/tools/Measurement.java
index e41e90806..c99f40577 100644
--- a/tools/src/main/java/com/graphhopper/tools/Measurement.java
+++ b/tools/src/main/java/com/graphhopper/tools/Measurement.java
@@ -30,7 +30,10 @@ import com.graphhopper.json.geo.JsonFeatureCollection;
 import com.graphhopper.reader.DataReader;
 import com.graphhopper.reader.osm.GraphHopperOSM;
 import com.graphhopper.routing.util.*;
-import com.graphhopper.routing.util.spatialrules.*;
+import com.graphhopper.routing.util.spatialrules.AbstractSpatialRule;
+import com.graphhopper.routing.util.spatialrules.SpatialRule;
+import com.graphhopper.routing.util.spatialrules.SpatialRuleLookup;
+import com.graphhopper.routing.util.spatialrules.SpatialRuleLookupBuilder;
 import com.graphhopper.routing.util.spatialrules.SpatialRuleLookupBuilder.SpatialRuleFactory;
 import com.graphhopper.storage.*;
 import com.graphhopper.storage.index.LocationIndex;
@@ -40,7 +43,6 @@ import com.graphhopper.util.Parameters.CH;
 import com.graphhopper.util.Parameters.Landmark;
 import com.graphhopper.util.shapes.BBox;
 import com.graphhopper.util.shapes.GHPoint;
-
 import org.locationtech.jts.geom.Polygon;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -85,18 +87,18 @@ public class Measurement {
     // creates properties file in the format key=value
     // Every value is one y-value in a separate diagram with an identical x-value for every Measurement.start call
     void start(PMap args) throws IOException {
-        final String graphLocation = args.get("graph.location", "");
-        final String countryBordersDirectory = args.get("spatial_rules.borders_directory", "");
+        final String graphLocation = args.getString("graph.location", "");
+        final String countryBordersDirectory = args.getString("spatial_rules.borders_directory", "");
         final boolean useJson = args.getBool("measurement.json", false);
         boolean cleanGraph = args.getBool("measurement.clean", false);
-        String summaryLocation = args.get("measurement.summaryfile", "");
+        String summaryLocation = args.getString("measurement.summaryfile", "");
         final String timeStamp = new SimpleDateFormat("yyyy-MM-dd_HH:mm:ss").format(new Date());
         put("measurement.timestamp", timeStamp);
-        String propFolder = args.get("measurement.folder", "");
+        String propFolder = args.getString("measurement.folder", "");
         if (!propFolder.isEmpty()) {
             Files.createDirectories(Paths.get(propFolder));
         }
-        String propFilename = args.get("measurement.filename", "");
+        String propFilename = args.getString("measurement.filename", "");
         if (isEmpty(propFilename)) {
             if (useJson) {
                 // if we start from IDE or otherwise jar was not built using maven the git commit id will be unknown
@@ -108,9 +110,9 @@ public class Measurement {
         }
         final String propLocation = Paths.get(propFolder).resolve(propFilename).toString();
         seed = args.getLong("measurement.seed", 123);
-        put("measurement.gitinfo", args.get("measurement.gitinfo", ""));
+        put("measurement.gitinfo", args.getString("measurement.gitinfo", ""));
         int count = args.getInt("measurement.count", 5000);
-        put("measurement.map", args.get("datareader.file", "unknown"));
+        put("measurement.map", args.getString("datareader.file", "unknown"));
 
         final boolean useMeasurementTimeAsRefTime = args.getBool("measurement.use_measurement_time_as_ref_time", false);
         if (useMeasurementTimeAsRefTime && !useJson) {
@@ -248,7 +250,7 @@ public class Measurement {
             if (!isEmpty(countryBordersDirectory)) {
                 printSpatialRuleLookupTest(countryBordersDirectory, count * 100);
             }
-            
+
         } catch (Exception ex) {
             logger.error("Problem while measuring " + graphLocation, ex);
             put("error", ex.toString());
@@ -274,14 +276,14 @@ public class Measurement {
 
     private GraphHopperConfig createConfigFromArgs(PMap args) {
         GraphHopperConfig ghConfig = new GraphHopperConfig(args);
-        String encodingManagerString = args.get("graph.flag_encoders", "car");
+        String encodingManagerString = args.getString("graph.flag_encoders", "car");
         List<FlagEncoder> tmpEncoders = EncodingManager.create(encodingManagerString).fetchEdgeEncoders();
         if (tmpEncoders.size() != 1) {
             logger.warn("You configured multiple encoders, only the first one is used for the measurements");
         }
         String vehicle = tmpEncoders.get(0).toString();
         boolean turnCosts = tmpEncoders.get(0).supportsTurnCosts();
-        weighting = args.get("measurement.weighting", weighting);
+        weighting = args.getString("measurement.weighting", weighting);
         boolean useCHEdge = args.getBool("measurement.ch.edge", true);
         boolean useCHNode = args.getBool("measurement.ch.node", true);
         boolean useLM = args.getBool("measurement.lm", true);
@@ -473,12 +475,12 @@ public class Measurement {
         }.setIterations(count).start();
         print("unit_tests" + description + ".get_edge_state", miniPerf);
     }
-    
+
     private void printSpatialRuleLookupTest(String countryBordersDirectory, int count) {
         ObjectMapper objectMapper = new ObjectMapper();
         objectMapper.registerModule(new JtsModule());
         objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
-        
+
         List<JsonFeatureCollection> jsonFeatureCollections = new ArrayList<>();
         try (DirectoryStream<Path> stream = Files.newDirectoryStream(Paths.get(countryBordersDirectory), "*.{geojson,json}")) {
             for (Path borderFile : stream) {
@@ -491,9 +493,9 @@ public class Measurement {
             logger.error("Failed to load borders.", e);
             return;
         }
-        
+
         SpatialRuleFactory rulePerCountryFactory = new SpatialRuleFactory() {
-            
+
             @Override
             public SpatialRule createSpatialRule(final String id, final List<Polygon> borders) {
                 return new AbstractSpatialRule(borders) {
@@ -504,9 +506,9 @@ public class Measurement {
                 };
             }
         };
-        
+
         final SpatialRuleLookup spatialRuleLookup = SpatialRuleLookupBuilder.buildIndex(jsonFeatureCollections, "ISO_A3", rulePerCountryFactory);
-    
+
         // generate random points in central Europe
         final List<GHPoint> randomPoints = new ArrayList<>(count);
         for (int i = 0; i < count; i++) {
@@ -514,14 +516,14 @@ public class Measurement {
             double lon = 6d + Math.random() * 21d;
             randomPoints.add(new GHPoint(lat, lon));
         }
-        
+
         MiniPerfTest lookupPerfTest = new MiniPerfTest() {
             @Override
             public int doCalc(boolean warmup, int run) {
                 return spatialRuleLookup.lookupRule(randomPoints.get(run)).hashCode();
             }
         }.setIterations(count).start();
-        
+
         print("spatialrulelookup", lookupPerfTest);
     }
 
@@ -546,7 +548,7 @@ public class Measurement {
                     setAlgorithm(algo);
 
             GHResponse lmRsp = hopper.route(req);
-            req.getHints().put(Landmark.DISABLE, true);
+            req.putHint(Landmark.DISABLE, true);
             GHResponse originalRsp = hopper.route(req);
 
             String locStr = " iteration " + i + ". " + fromLat + "," + fromLon + " -> " + toLat + "," + toLon;
@@ -590,7 +592,7 @@ public class Measurement {
                     setWeighting(weighting).
                     setVehicle(vehicle).
                     setAlgorithm(DIJKSTRA_BI);
-            noSodReq.getHints().put("stall_on_demand", false);
+            noSodReq.putHint("stall_on_demand", false);
 
             GHResponse sodRsp = hopper.route(sodReq);
             GHResponse noSodRsp = hopper.route(noSodReq);
@@ -649,12 +651,12 @@ public class Measurement {
                         setWeighting(weighting).
                         setVehicle(querySettings.vehicle);
 
-                req.getHints().put(CH.DISABLE, !querySettings.ch).
-                        put("stall_on_demand", querySettings.sod).
-                        put(Parameters.Routing.EDGE_BASED, querySettings.edgeBased).
-                        put(Landmark.DISABLE, !querySettings.lm).
-                        put(Landmark.ACTIVE_COUNT, querySettings.activeLandmarks).
-                        put("instructions", querySettings.withInstructions);
+                req.getHints().putObject(CH.DISABLE, !querySettings.ch).
+                        putObject("stall_on_demand", querySettings.sod).
+                        putObject(Parameters.Routing.EDGE_BASED, querySettings.edgeBased).
+                        putObject(Landmark.DISABLE, !querySettings.lm).
+                        putObject(Landmark.ACTIVE_COUNT, querySettings.activeLandmarks).
+                        putObject("instructions", querySettings.withInstructions);
 
                 if (querySettings.alternative)
                     req.setAlgorithm(ALT_ROUTE);
@@ -666,7 +668,7 @@ public class Measurement {
                     req.setPathDetails(Arrays.asList(Parameters.Details.AVERAGE_SPEED, Parameters.Details.EDGE_ID, Parameters.Details.STREET_NAME));
                 } else {
                     // disable path simplification by setting the distance to zero
-                    req.getHints().put(Parameters.Routing.WAY_POINT_MAX_DISTANCE, 0);
+                    req.getHints().putObject(Parameters.Routing.WAY_POINT_MAX_DISTANCE, 0);
                 }
 
                 if (querySettings.blockArea != null)
diff --git a/tools/src/main/java/com/graphhopper/tools/QueryTorture.java b/tools/src/main/java/com/graphhopper/tools/QueryTorture.java
index 24f5eb1c8..c436af44b 100644
--- a/tools/src/main/java/com/graphhopper/tools/QueryTorture.java
+++ b/tools/src/main/java/com/graphhopper/tools/QueryTorture.java
@@ -64,9 +64,9 @@ public class QueryTorture {
     }
 
     public void start(PMap args) {
-        String logfile = args.get("logfile", "");
+        String logfile = args.getString("logfile", "");
         int workers = args.getInt("workers", 1);
-        baseUrl = args.get("baseurl", "");
+        baseUrl = args.getString("baseurl", "");
         tooShortDistance = args.getDouble("too_short_distance", 50d);
         maxQueries = args.getLong("maxqueries", 1000L);
         timeout = args.getInt("timeout", 3000);
diff --git a/web-bundle/src/main/java/com/graphhopper/http/GraphHopperManaged.java b/web-bundle/src/main/java/com/graphhopper/http/GraphHopperManaged.java
index 64ce9cd29..999232b47 100644
--- a/web-bundle/src/main/java/com/graphhopper/http/GraphHopperManaged.java
+++ b/web-bundle/src/main/java/com/graphhopper/http/GraphHopperManaged.java
@@ -53,7 +53,7 @@ public class GraphHopperManaged implements Managed {
     public GraphHopperManaged(GraphHopperConfig configuration, ObjectMapper objectMapper) {
         ObjectMapper localObjectMapper = objectMapper.copy();
         localObjectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
-        String splitAreaLocation = configuration.get(Parameters.Landmark.PREPARE + "split_area_location", "");
+        String splitAreaLocation = configuration.getString(Parameters.Landmark.PREPARE + "split_area_location", "");
         JsonFeatureCollection landmarkSplittingFeatureCollection;
         try (Reader reader = splitAreaLocation.isEmpty() ? new InputStreamReader(LandmarkStorage.class.getResource("map.geo.json").openStream(), UTF_CS) : new InputStreamReader(new FileInputStream(splitAreaLocation), UTF_CS)) {
             landmarkSplittingFeatureCollection = localObjectMapper.readValue(reader, JsonFeatureCollection.class);
@@ -66,12 +66,12 @@ public class GraphHopperManaged implements Managed {
         } else {
             graphHopper = new GraphHopperOSM(landmarkSplittingFeatureCollection).forServer();
         }
-        if (!configuration.get("spatial_rules.location", "").isEmpty()) {
+        if (!configuration.getString("spatial_rules.location", "").isEmpty()) {
             throw new RuntimeException("spatial_rules.location has been deprecated. Please use spatial_rules.borders_directory instead.");
         }
-        String spatialRuleBordersDirLocation = configuration.get("spatial_rules.borders_directory", "");
+        String spatialRuleBordersDirLocation = configuration.getString("spatial_rules.borders_directory", "");
         if (!spatialRuleBordersDirLocation.isEmpty()) {
-            final BBox maxBounds = BBox.parseBBoxString(configuration.get("spatial_rules.max_bbox", "-180, 180, -90, 90"));
+            final BBox maxBounds = BBox.parseBBoxString(configuration.getString("spatial_rules.max_bbox", "-180, 180, -90, 90"));
             final Path bordersDirectory = Paths.get(spatialRuleBordersDirLocation);
             List<JsonFeatureCollection> jsonFeatureCollections = new ArrayList<>();
             try (DirectoryStream<Path> stream = Files.newDirectoryStream(bordersDirectory, "*.{geojson,json}")) {
diff --git a/web-bundle/src/main/java/com/graphhopper/resources/IsochroneResource.java b/web-bundle/src/main/java/com/graphhopper/resources/IsochroneResource.java
index 278117e59..0aeabfaf4 100644
--- a/web-bundle/src/main/java/com/graphhopper/resources/IsochroneResource.java
+++ b/web-bundle/src/main/java/com/graphhopper/resources/IsochroneResource.java
@@ -74,8 +74,8 @@ public class IsochroneResource {
 
         HintsMap hintsMap = new HintsMap();
         RouteResource.initHints(hintsMap, uriInfo.getQueryParameters());
-        hintsMap.put(Parameters.CH.DISABLE, true);
-        hintsMap.put(Parameters.Landmark.DISABLE, true);
+        hintsMap.putObject(Parameters.CH.DISABLE, true);
+        hintsMap.putObject(Parameters.Landmark.DISABLE, true);
         ProfileConfig profile = graphHopper.resolveProfile(hintsMap);
         if (profile.isTurnCosts()) {
             throw new IllegalArgumentException("Isochrone calculation does not support turn costs yet");
diff --git a/web-bundle/src/main/java/com/graphhopper/resources/RouteResource.java b/web-bundle/src/main/java/com/graphhopper/resources/RouteResource.java
index 6d69b71fa..c18063181 100644
--- a/web-bundle/src/main/java/com/graphhopper/resources/RouteResource.java
+++ b/web-bundle/src/main/java/com/graphhopper/resources/RouteResource.java
@@ -23,9 +23,7 @@ import com.graphhopper.GraphHopperAPI;
 import com.graphhopper.MultiException;
 import com.graphhopper.http.WebHelper;
 import com.graphhopper.routing.util.HintsMap;
-import com.graphhopper.util.Constants;
-import com.graphhopper.util.InstructionList;
-import com.graphhopper.util.StopWatch;
+import com.graphhopper.util.*;
 import com.graphhopper.util.gpx.GpxFromInstructions;
 import com.graphhopper.util.shapes.GHPoint;
 import org.slf4j.Logger;
@@ -138,9 +136,9 @@ public class RouteResource {
                 setSnapPreventions(snapPreventions).
                 setPathDetails(pathDetails).
                 getHints().
-                put(CALC_POINTS, calcPoints).
-                put(INSTRUCTIONS, instructions).
-                put(WAY_POINT_MAX_DISTANCE, minPathPrecision);
+                putObject(CALC_POINTS, calcPoints).
+                putObject(INSTRUCTIONS, instructions).
+                putObject(WAY_POINT_MAX_DISTANCE, minPathPrecision);
 
         GHResponse ghResponse = graphHopper.route(request);
 
@@ -181,7 +179,7 @@ public class RouteResource {
         GHResponse ghResponse = graphHopper.route(request);
 
         boolean instructions = request.getHints().getBool(INSTRUCTIONS, true);
-        boolean writeGPX = "gpx".equalsIgnoreCase(request.getHints().get("type", "json"));
+        boolean writeGPX = "gpx".equalsIgnoreCase(request.getHints().getString("type", "json"));
         instructions = writeGPX || instructions;
         boolean enableElevation = request.getHints().getBool("elevation", false);
         boolean calcPoints = request.getHints().getBool(CALC_POINTS, true);
@@ -191,8 +189,8 @@ public class RouteResource {
         boolean withRoute = request.getHints().getBool("gpx.route", true);
         boolean withTrack = request.getHints().getBool("gpx.track", true);
         boolean withWayPoints = request.getHints().getBool("gpx.waypoints", false);
-        String trackName = request.getHints().get("gpx.trackname", "GraphHopper Track");
-        String timeString = request.getHints().get("gpx.millis", "");
+        String trackName = request.getHints().getString("gpx.trackname", "GraphHopper Track");
+        String timeString = request.getHints().getString("gpx.millis", "");
         float took = sw.stop().getSeconds();
         String infoStr = httpReq.getRemoteAddr() + " " + httpReq.getLocale() + " " + httpReq.getHeader("User-Agent");
         String logStr = httpReq.getQueryString() + " " + infoStr + " " + request.getPoints().size() + ", took:"
@@ -224,7 +222,7 @@ public class RouteResource {
             if (!request.getHints().getBool(EDGE_BASED, true)) {
                 throw new IllegalArgumentException("Disabling '" + EDGE_BASED + "' when using '" + CURBSIDE + "' is not allowed");
             } else {
-                request.getHints().put(EDGE_BASED, true);
+                request.getHints().putObject(EDGE_BASED, true);
             }
         }
     }
@@ -254,22 +252,14 @@ public class RouteResource {
     static void initHints(HintsMap m, MultivaluedMap<String, String> parameterMap) {
         for (Map.Entry<String, List<String>> e : parameterMap.entrySet()) {
             if (e.getValue().size() == 1) {
-                m.put(e.getKey(), e.getValue().get(0));
+                m.putObject(Helper.camelCaseToUnderScore(e.getKey()), Helper.toObject(e.getValue().get(0)));
             } else {
-                // Do nothing.
-                // TODO: this is dangerous: I can only silently swallow
-                // the forbidden multiparameter. If I comment-in the line below,
-                // I get an exception, because "point" regularly occurs
-                // multiple times.
-                // I think either unknown parameters (hints) should be allowed
-                // to be multiparameters, too, or we shouldn't use them for
-                // known parameters either, _or_ known parameters
-                // must be filtered before they come to this code point,
-                // _or_ we stop passing unknown parameters alltogether..
-                //
+                // TODO e.g. 'point' parameter occurs multiple times and we cannot throw an exception here
+                //  unknown parameters (hints) should be allowed to be multiparameters, too, or we shouldn't use them for
+                //  known parameters either, _or_ known parameters must be filtered before they come to this code point,
+                //  _or_ we stop passing unknown parameters alltogether.
                 // throw new WebApplicationException(String.format("This query parameter (hint) is not allowed to occur multiple times: %s", e.getKey()));
             }
         }
     }
-
 }
diff --git a/web-bundle/src/main/java/com/graphhopper/resources/SPTResource.java b/web-bundle/src/main/java/com/graphhopper/resources/SPTResource.java
index aa5306886..d31432129 100644
--- a/web-bundle/src/main/java/com/graphhopper/resources/SPTResource.java
+++ b/web-bundle/src/main/java/com/graphhopper/resources/SPTResource.java
@@ -62,8 +62,8 @@ public class SPTResource {
         StopWatch sw = new StopWatch().start();
         HintsMap hintsMap = new HintsMap();
         RouteResource.initHints(hintsMap, uriInfo.getQueryParameters());
-        hintsMap.put(Parameters.CH.DISABLE, true);
-        hintsMap.put(Parameters.Landmark.DISABLE, true);
+        hintsMap.putObject(Parameters.CH.DISABLE, true);
+        hintsMap.putObject(Parameters.Landmark.DISABLE, true);
         ProfileConfig profile = graphHopper.resolveProfile(hintsMap);
         if (profile.isTurnCosts()) {
             throw new IllegalArgumentException("SPT calculation does not support turn costs yet");
diff --git a/web/src/main/java/com/graphhopper/http/GraphHopperApplication.java b/web/src/main/java/com/graphhopper/http/GraphHopperApplication.java
index 49208e2cc..038c62dc7 100644
--- a/web/src/main/java/com/graphhopper/http/GraphHopperApplication.java
+++ b/web/src/main/java/com/graphhopper/http/GraphHopperApplication.java
@@ -53,6 +53,6 @@ public final class GraphHopperApplication extends Application<GraphHopperServerC
         environment.jersey().register(new GHJerseyViolationExceptionMapper());
         environment.jersey().register(new RootResource());
         environment.servlets().addFilter("cors", CORSFilter.class).addMappingForUrlPatterns(EnumSet.allOf(DispatcherType.class), false, "*");
-        environment.servlets().addFilter("ipfilter", new IPFilter(configuration.getGraphHopperConfiguration().get("jetty.whiteips", ""), configuration.getGraphHopperConfiguration().get("jetty.blackips", ""))).addMappingForUrlPatterns(EnumSet.allOf(DispatcherType.class), false, "*");
+        environment.servlets().addFilter("ipfilter", new IPFilter(configuration.getGraphHopperConfiguration().getString("jetty.whiteips", ""), configuration.getGraphHopperConfiguration().getString("jetty.blackips", ""))).addMappingForUrlPatterns(EnumSet.allOf(DispatcherType.class), false, "*");
     }
 }


Test patch:
diff --git a/api/src/test/java/com/graphhopper/util/PMapTest.java b/api/src/test/java/com/graphhopper/util/PMapTest.java
index 806f7d569..a6f75b7f4 100644
--- a/api/src/test/java/com/graphhopper/util/PMapTest.java
+++ b/api/src/test/java/com/graphhopper/util/PMapTest.java
@@ -28,23 +28,23 @@ public class PMapTest {
     public void singleStringPropertyCanBeRetrieved() {
         PMap subject = new PMap("foo=bar");
 
-        Assert.assertEquals("bar", subject.get("foo"));
+        Assert.assertEquals("bar", subject.getString("foo", ""));
     }
 
     @Test
     public void propertyFromStringWithMultiplePropertiesCanBeRetrieved() {
         PMap subject = new PMap("foo=valueA|bar=valueB");
 
-        Assert.assertEquals("valueA", subject.get("foo", ""));
-        Assert.assertEquals("valueB", subject.get("bar", ""));
+        Assert.assertEquals("valueA", subject.getString("foo", ""));
+        Assert.assertEquals("valueB", subject.getString("bar", ""));
     }
 
     @Test
     public void keyCannotHaveAnyCasing() {
         PMap subject = new PMap("foo=valueA|bar=valueB");
 
-        assertEquals("valueA", subject.get("foo", ""));
-        assertEquals("", subject.get("Foo", ""));
+        assertEquals("valueA", subject.getString("foo", ""));
+        assertEquals("", subject.getString("Foo", ""));
     }
 
     @Test
diff --git a/client-hc/src/test/java/com/graphhopper/api/GraphHopperWebIT.java b/client-hc/src/test/java/com/graphhopper/api/GraphHopperWebIT.java
index bc70820c0..cba4365b7 100644
--- a/client-hc/src/test/java/com/graphhopper/api/GraphHopperWebIT.java
+++ b/client-hc/src/test/java/com/graphhopper/api/GraphHopperWebIT.java
@@ -2,6 +2,7 @@ package com.graphhopper.api;
 
 import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
 import com.graphhopper.GHRequest;
 import com.graphhopper.GHResponse;
 import com.graphhopper.PathWrapper;
@@ -53,9 +54,8 @@ public class GraphHopperWebIT {
     public static Collection<Object[]> configs() {
         return Arrays.asList(new Object[][]{
                 {false, -1},
-                // TODO later: test post request against API
-//                {true, 1000},
-//                {true, 0}
+                {true, 1000},
+                {true, 0}
         });
     }
 
@@ -65,9 +65,9 @@ public class GraphHopperWebIT {
         GHRequest req = new GHRequest().
                 addPoint(new GHPoint(49.6724, 11.3494)).
                 addPoint(new GHPoint(49.6550, 11.4180));
-        req.getHints().put("elevation", false);
-        req.getHints().put("instructions", true);
-        req.getHints().put("calc_points", true);
+        req.putHint("elevation", false);
+        req.putHint("instructions", true);
+        req.putHint("calc_points", true);
         GHResponse res = gh.route(req);
         assertFalse("errors:" + res.getErrors().toString(), res.hasErrors());
         PathWrapper alt = res.getBest();
@@ -87,16 +87,30 @@ public class GraphHopperWebIT {
         assertEquals("[0, 1]", alt.getPointsOrder().toString());
     }
 
+    @Test
+    public void testPutPOJO() {
+        ObjectNode requestJson = new ObjectMapper().createObjectNode();
+        requestJson.putPOJO("double", 1.0);
+        requestJson.putPOJO("int", 1);
+        requestJson.putPOJO("boolean", true);
+        // does not work requestJson.putPOJO("string", "test");
+        assertEquals("{\"double\":1.0,\"int\":1,\"boolean\":true}", requestJson.toString());
+    }
+
     @Test
     public void testAlternativeRoute() {
+        // TODO
+        if (gh.postRequest)
+            return;
+
         // https://graphhopper.com/maps/?point=52.042989%2C10.373926&point=52.042289%2C10.384043&algorithm=alternative_route&ch.disable=true
         GHRequest req = new GHRequest().
                 addPoint(new GHPoint(52.042989, 10.373926)).
                 addPoint(new GHPoint(52.042289, 10.384043));
         req.setAlgorithm("alternative_route");
-        req.getHints().put("instructions", true);
-        req.getHints().put("calc_points", true);
-        req.getHints().put("ch.disable", true);
+        req.putHint("instructions", true);
+        req.putHint("calc_points", true);
+        req.putHint("ch.disable", true);
         GHResponse res = gh.route(req);
         assertFalse("errors:" + res.getErrors().toString(), res.hasErrors());
         List<PathWrapper> paths = res.getAll();
@@ -121,7 +135,7 @@ public class GraphHopperWebIT {
         GHResponse res = gh.route(req);
         assertFalse("errors:" + res.getErrors().toString(), res.hasErrors());
 
-        req.getHints().put(GraphHopperWeb.TIMEOUT, 1);
+        req.putHint(GraphHopperWeb.TIMEOUT, 1);
         try {
             gh.route(req);
             fail();
@@ -136,8 +150,8 @@ public class GraphHopperWebIT {
                 addPoint(new GHPoint(49.6724, 11.3494)).
                 addPoint(new GHPoint(49.6550, 11.4180));
 
-        req.getHints().put("instructions", false);
-        req.getHints().put("calc_points", false);
+        req.putHint("instructions", false);
+        req.putHint("calc_points", false);
         GHResponse res = gh.route(req);
         assertFalse("errors:" + res.getErrors().toString(), res.hasErrors());
         PathWrapper alt = res.getBest();
@@ -177,7 +191,7 @@ public class GraphHopperWebIT {
                 "Continue", "Keep left", "Turn right onto B 246", "Turn right onto Dorfaue, K 6156", "Turn right onto B 96"
         ), given);
 
-        req.getHints().put("turn_description", false);
+        req.putHint("turn_description", false);
         res = gh.route(req);
         given = extractInstructionNames(res.getBest(), 5);
         assertEquals(Arrays.asList(
@@ -235,7 +249,7 @@ public class GraphHopperWebIT {
         GHRequest req = new GHRequest().
                 addPoint(new GHPoint(52.261434, 13.485718)).
                 addPoint(new GHPoint(52.399067, 13.469238));
-        req.getHints().put("turn_description", false);
+        req.putHint("turn_description", false);
         GHResponse res = gh.route(req);
         InstructionList instructions = res.getBest().getInstructions();
         String finishInstructionName = instructions.get(instructions.size() - 1).getName();
@@ -247,10 +261,10 @@ public class GraphHopperWebIT {
         GHRequest req = new GHRequest().
                 addPoint(new GHPoint(49.6724, 11.3494)).
                 addPoint(new GHPoint(49.6550, 11.4180));
-        req.getHints().put("elevation", false);
-        req.getHints().put("instructions", true);
-        req.getHints().put("calc_points", true);
-        req.getHints().put("type", "gpx");
+        req.putHint("elevation", false);
+        req.putHint("instructions", true);
+        req.putHint("calc_points", true);
+        req.putHint("type", "gpx");
         String res = gh.export(req);
         assertTrue(res.contains("<gpx"));
         assertTrue(res.contains("<rtept lat="));
@@ -263,11 +277,11 @@ public class GraphHopperWebIT {
         GHRequest req = new GHRequest().
                 addPoint(new GHPoint(49.6724, 11.3494)).
                 addPoint(new GHPoint(49.6550, 11.4180));
-        req.getHints().put("elevation", false);
-        req.getHints().put("instructions", true);
-        req.getHints().put("calc_points", true);
-        req.getHints().put("type", "gpx");
-        req.getHints().put("gpx.track", "false");
+        req.putHint("elevation", false);
+        req.putHint("instructions", true);
+        req.putHint("calc_points", true);
+        req.putHint("type", "gpx");
+        req.putHint("gpx.track", "false");
         String res = gh.export(req);
         assertTrue(res.contains("<gpx"));
         assertTrue(res.contains("<rtept lat="));
@@ -334,11 +348,11 @@ public class GraphHopperWebIT {
         GHMRequest req = new GHMRequest();
         req.addPoint(new GHPoint(49.6724, 11.3494));
         req.addPoint(new GHPoint(49.6550, 11.4180));
-        req.getHints().put("something", "xy");
+        req.putHint("something", "xy");
         ghMatrix.route(req);
 
         // clashing parameter will overwrite!
-        req.getHints().put("vehicle", "xy");
+        req.putHint("vehicle", "xy");
         assertEquals("xy", req.getVehicle());
     }
 
diff --git a/core/src/test/java/com/graphhopper/GraphHopperProfileConfigTest.java b/core/src/test/java/com/graphhopper/GraphHopperProfileConfigTest.java
index 31af3c41a..610e45d88 100644
--- a/core/src/test/java/com/graphhopper/GraphHopperProfileConfigTest.java
+++ b/core/src/test/java/com/graphhopper/GraphHopperProfileConfigTest.java
@@ -47,8 +47,8 @@ public class GraphHopperProfileConfigTest {
         assertEquals("fastest", profileConfig.getWeighting());
         assertTrue(profileConfig.isTurnCosts());
         assertEquals(2, profileConfig.getHints().toMap().size());
-        assertEquals("bar", profileConfig.getHints().get("foo", ""));
-        assertEquals("buzz", profileConfig.getHints().get("baz", ""));
+        assertEquals("bar", profileConfig.getHints().getString("foo", ""));
+        assertEquals("buzz", profileConfig.getHints().getString("baz", ""));
     }
 
     @Test
diff --git a/core/src/test/java/com/graphhopper/routing/DijkstraBidirectionCHTest.java b/core/src/test/java/com/graphhopper/routing/DijkstraBidirectionCHTest.java
index ffa2469e4..d5d478ce9 100644
--- a/core/src/test/java/com/graphhopper/routing/DijkstraBidirectionCHTest.java
+++ b/core/src/test/java/com/graphhopper/routing/DijkstraBidirectionCHTest.java
@@ -259,7 +259,7 @@ public class DijkstraBidirectionCHTest {
 
     private RoutingAlgorithm createCHAlgo(CHGraph chGraph, boolean withSOD, AlgorithmOptions algorithmOptions) {
         if (!withSOD) {
-            algorithmOptions.getHints().put("stall_on_demand", false);
+            algorithmOptions.getHints().putObject("stall_on_demand", false);
         }
         return new CHRoutingAlgorithmFactory(chGraph).createAlgo(chGraph, algorithmOptions);
     }
diff --git a/core/src/test/java/com/graphhopper/routing/RandomCHRoutingTest.java b/core/src/test/java/com/graphhopper/routing/RandomCHRoutingTest.java
index 26c2c9a2a..409d3850f 100644
--- a/core/src/test/java/com/graphhopper/routing/RandomCHRoutingTest.java
+++ b/core/src/test/java/com/graphhopper/routing/RandomCHRoutingTest.java
@@ -166,7 +166,8 @@ public class RandomCHRoutingTest {
                     continue;
                 }
 
-                RoutingAlgorithm algo = pch.getRoutingAlgorithmFactory().createAlgo(chQueryGraph, AlgorithmOptions.start().hints(new PMap().put("stall_on_demand", true)).build());
+                RoutingAlgorithm algo = pch.getRoutingAlgorithmFactory().createAlgo(chQueryGraph, AlgorithmOptions.start().
+                        hints(new PMap().putObject("stall_on_demand", true)).build());
                 Path path = algo.calcPath(from, to);
                 if (!path.isFound()) {
                     fail("path not found for " + from + "->" + to + ", expected weight: " + refWeight);
diff --git a/core/src/test/java/com/graphhopper/routing/RoutingAlgorithmIT.java b/core/src/test/java/com/graphhopper/routing/RoutingAlgorithmIT.java
index b4ea4eec7..05e488105 100644
--- a/core/src/test/java/com/graphhopper/routing/RoutingAlgorithmIT.java
+++ b/core/src/test/java/com/graphhopper/routing/RoutingAlgorithmIT.java
@@ -62,7 +62,7 @@ public class RoutingAlgorithmIT {
         ProfileConfig profile = new ProfileConfig("profile").setVehicle(hints.getVehicle()).setWeighting(hints.getWeighting()).setTurnCosts(tMode.isEdgeBased());
         Weighting weighting = hopper.createWeighting(profile, hints);
 
-        HintsMap defaultHints = new HintsMap().put(Parameters.CH.DISABLE, true).put(Parameters.Landmark.DISABLE, true)
+        HintsMap defaultHints = new HintsMap().putObject(Parameters.CH.DISABLE, true).putObject(Parameters.Landmark.DISABLE, true)
                 .setVehicle(hints.getVehicle()).setWeighting(hints.getWeighting());
 
         AlgorithmOptions defaultOpts = AlgorithmOptions.start(new AlgorithmOptions("", weighting, tMode)).hints(defaultHints).build();
@@ -79,7 +79,7 @@ public class RoutingAlgorithmIT {
 
         // add additional preparations if CH and LM preparation are enabled
         if (hopper.getLMPreparationHandler().isEnabled()) {
-            final HintsMap lmHints = new HintsMap(defaultHints).put(Parameters.Landmark.DISABLE, false);
+            final HintsMap lmHints = new HintsMap(defaultHints).putObject(Parameters.Landmark.DISABLE, false);
             algos.add(new AlgoHelperEntry(ghStorage, AlgorithmOptions.start(astarbiOpts).hints(lmHints).build(), idx, "astarbi|landmarks|" + weighting) {
                 @Override
                 public RoutingAlgorithmFactory createRoutingFactory() {
@@ -90,8 +90,8 @@ public class RoutingAlgorithmIT {
 
         if (hopper.getCHPreparationHandler().isEnabled()) {
             final HintsMap chHints = new HintsMap(defaultHints);
-            chHints.put(Parameters.CH.DISABLE, false);
-            chHints.put(Parameters.Routing.EDGE_BASED, tMode.isEdgeBased());
+            chHints.putObject(Parameters.CH.DISABLE, false);
+            chHints.putObject(Parameters.Routing.EDGE_BASED, tMode.isEdgeBased());
             CHProfile pickedProfile = new ProfileResolver().selectCHProfile(hopper.getCHPreparationHandler().getCHProfiles(), chHints);
             algos.add(new AlgoHelperEntry(ghStorage.getCHGraph(pickedProfile),
                     AlgorithmOptions.start(dijkstrabiOpts).hints(chHints).build(), idx, "dijkstrabi|ch|algos|" + hints.getWeighting()) {
diff --git a/core/src/test/java/com/graphhopper/routing/ch/CHProfileSelectorTest.java b/core/src/test/java/com/graphhopper/routing/ch/CHProfileSelectorTest.java
index ed970cf1c..9c1bc268b 100644
--- a/core/src/test/java/com/graphhopper/routing/ch/CHProfileSelectorTest.java
+++ b/core/src/test/java/com/graphhopper/routing/ch/CHProfileSelectorTest.java
@@ -250,10 +250,10 @@ public class CHProfileSelectorTest {
     private HintsMap createHintsMap(String vehicle, String weighting, Boolean edgeBased, Integer uTurnCosts) {
         HintsMap hintsMap = new HintsMap().setWeighting(weighting).setVehicle(vehicle);
         if (edgeBased != null) {
-            hintsMap.put(Parameters.Routing.EDGE_BASED, edgeBased);
+            hintsMap.putObject(Parameters.Routing.EDGE_BASED, edgeBased);
         }
         if (uTurnCosts != null) {
-            hintsMap.put(Parameters.Routing.U_TURN_COSTS, uTurnCosts);
+            hintsMap.putObject(Parameters.Routing.U_TURN_COSTS, uTurnCosts);
         }
         return hintsMap;
     }
diff --git a/core/src/test/java/com/graphhopper/routing/ch/CHTurnCostTest.java b/core/src/test/java/com/graphhopper/routing/ch/CHTurnCostTest.java
index 5a4395c66..e730fa9df 100644
--- a/core/src/test/java/com/graphhopper/routing/ch/CHTurnCostTest.java
+++ b/core/src/test/java/com/graphhopper/routing/ch/CHTurnCostTest.java
@@ -1137,10 +1137,10 @@ public class CHTurnCostTest {
 
     private RoutingAlgorithmFactory automaticPrepareCH() {
         PMap pMap = new PMap();
-        pMap.put(PERIODIC_UPDATES, 20);
-        pMap.put(LAST_LAZY_NODES_UPDATES, 100);
-        pMap.put(NEIGHBOR_UPDATES, 4);
-        pMap.put(LOG_MESSAGES, 10);
+        pMap.putObject(PERIODIC_UPDATES, 20);
+        pMap.putObject(LAST_LAZY_NODES_UPDATES, 100);
+        pMap.putObject(NEIGHBOR_UPDATES, 4);
+        pMap.putObject(LOG_MESSAGES, 10);
         PrepareContractionHierarchies ch = PrepareContractionHierarchies.fromGraphHopperStorage(graph, chProfile);
         ch.setParams(pMap);
         ch.doWork();
diff --git a/core/src/test/java/com/graphhopper/routing/lm/LMProfileSelectorTest.java b/core/src/test/java/com/graphhopper/routing/lm/LMProfileSelectorTest.java
index a97d47dd8..445e41222 100644
--- a/core/src/test/java/com/graphhopper/routing/lm/LMProfileSelectorTest.java
+++ b/core/src/test/java/com/graphhopper/routing/lm/LMProfileSelectorTest.java
@@ -131,10 +131,10 @@ public class LMProfileSelectorTest {
     private HintsMap createHintsMap(String vehicle, String weighting, Boolean edgeBased, Integer uTurnCosts) {
         HintsMap hintsMap = new HintsMap().setWeighting(weighting).setVehicle(vehicle);
         if (edgeBased != null) {
-            hintsMap.put(Parameters.Routing.EDGE_BASED, edgeBased);
+            hintsMap.putObject(Parameters.Routing.EDGE_BASED, edgeBased);
         }
         if (uTurnCosts != null) {
-            hintsMap.put(Parameters.Routing.U_TURN_COSTS, uTurnCosts);
+            hintsMap.putObject(Parameters.Routing.U_TURN_COSTS, uTurnCosts);
         }
         return hintsMap;
     }
diff --git a/core/src/test/java/com/graphhopper/routing/lm/PrepareLandmarksTest.java b/core/src/test/java/com/graphhopper/routing/lm/PrepareLandmarksTest.java
index c25fe7e85..20e9113e6 100644
--- a/core/src/test/java/com/graphhopper/routing/lm/PrepareLandmarksTest.java
+++ b/core/src/test/java/com/graphhopper/routing/lm/PrepareLandmarksTest.java
@@ -147,8 +147,7 @@ public class PrepareLandmarksTest {
         AStar expectedAlgo = new AStar(graph, weighting, tm);
         Path expectedPath = expectedAlgo.calcPath(41, 183);
 
-        PMap hints = new PMap();
-        hints.put(Parameters.Landmark.ACTIVE_COUNT, 2);
+        PMap hints = new PMap().putObject(Parameters.Landmark.ACTIVE_COUNT, 2);
 
         // landmarks with A*
         RoutingAlgorithm oneDirAlgoWithLandmarks = prepare.getRoutingAlgorithmFactory().createAlgo(graph,
diff --git a/core/src/test/java/com/graphhopper/routing/template/RoundTripRoutingTemplateTest.java b/core/src/test/java/com/graphhopper/routing/template/RoundTripRoutingTemplateTest.java
index 01c41a3dd..28805a96b 100644
--- a/core/src/test/java/com/graphhopper/routing/template/RoundTripRoutingTemplateTest.java
+++ b/core/src/test/java/com/graphhopper/routing/template/RoundTripRoutingTemplateTest.java
@@ -84,8 +84,8 @@ public class RoundTripRoutingTemplateTest {
 
         GHRequest ghRequest =
                 new GHRequest(Collections.singletonList(start), Collections.singletonList(heading));
-        ghRequest.getHints().put(Parameters.Algorithms.RoundTrip.POINTS, numPoints);
-        ghRequest.getHints().put(Parameters.Algorithms.RoundTrip.DISTANCE, roundTripDistance);
+        ghRequest.putHint(Parameters.Algorithms.RoundTrip.POINTS, numPoints);
+        ghRequest.putHint(Parameters.Algorithms.RoundTrip.DISTANCE, roundTripDistance);
         LocationIndex locationIndex = new LocationIndexTree(g, new RAMDirectory()).prepareIndex();
         RoundTripRoutingTemplate routingTemplate =
                 new RoundTripRoutingTemplate(ghRequest, new GHResponse(), locationIndex, em, fastestWeighting, 1);
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 4b1662cb6..54c3f0a2c 100644
--- a/core/src/test/java/com/graphhopper/routing/weighting/FastestWeightingTest.java
+++ b/core/src/test/java/com/graphhopper/routing/weighting/FastestWeightingTest.java
@@ -49,8 +49,7 @@ public class FastestWeightingTest {
 
     @Test
     public void testWeightWrongHeading() {
-        Weighting instance = new FastestWeighting(encoder, new PMap().
-                put(Parameters.Routing.HEADING_PENALTY, "100"));
+        Weighting instance = new FastestWeighting(encoder, new PMap().putObject(Parameters.Routing.HEADING_PENALTY, 100));
 
         VirtualEdgeIteratorState virtEdge = new VirtualEdgeIteratorState(0, 1, 1, 2, 10,
                 GHUtility.setProperties(encodingManager.createEdgeFlags(), encoder, 10, true, false), "test", Helper.createPointList(51, 0, 51, 1), false);
diff --git a/reader-osm/src/test/java/com/graphhopper/GraphHopperIT.java b/reader-osm/src/test/java/com/graphhopper/GraphHopperIT.java
index e1a0fe6b2..8e4b920dc 100644
--- a/reader-osm/src/test/java/com/graphhopper/GraphHopperIT.java
+++ b/reader-osm/src/test/java/com/graphhopper/GraphHopperIT.java
@@ -177,11 +177,11 @@ public class GraphHopperIT {
         GHRequest request = new GHRequest().setAlgorithm(ASTAR).setVehicle(vehicle).setWeighting(weighting);
         request.addPoint(new GHPoint(43.729584, 7.410965));
         request.addPoint(new GHPoint(43.732499, 7.426758));
-        request.getHints().put("instructions", true);
+        request.getHints().putObject("instructions", true);
         GHResponse routeRsp = hopper.route(request);
         int withInstructionsPoints = routeRsp.getBest().getPoints().size();
 
-        request.getHints().put("instructions", false);
+        request.getHints().putObject("instructions", false);
         routeRsp = hopper.route(request);
 
         assertTrue("there should not be more points if instructions are disabled due to simplify but was " + withInstructionsPoints + " vs " + routeRsp.getBest().getPoints().size(),
@@ -259,8 +259,8 @@ public class GraphHopperIT {
             GHRequest req = new GHRequest(43.727687, 7.418737, 43.74958, 7.436566).
                     setWeighting(weighting).
                     setVehicle(vehicle);
-            req.getHints().put(CH.DISABLE, false);
-            req.getHints().put(Landmark.DISABLE, true);
+            req.putHint(CH.DISABLE, false);
+            req.putHint(Landmark.DISABLE, true);
             GHResponse rsp = hopper.route(req);
 
             PathWrapper bestPath = rsp.getBest();
@@ -276,8 +276,8 @@ public class GraphHopperIT {
                     setVehicle(vehicle).
                     setWeighting(weighting).
                     setAlgorithm(Parameters.Algorithms.ASTAR_BI);
-            req.getHints().put(CH.DISABLE, true);
-            req.getHints().put(Landmark.DISABLE, false);
+            req.putHint(CH.DISABLE, true);
+            req.putHint(Landmark.DISABLE, false);
             GHResponse rsp = hopper.route(req);
 
             PathWrapper bestPath = rsp.getBest();
@@ -292,8 +292,8 @@ public class GraphHopperIT {
         GHRequest req = new GHRequest(43.727687, 7.418737, 43.74958, 7.436566).
                 setVehicle(vehicle).
                 setWeighting(weighting);
-        req.getHints().put(CH.DISABLE, true);
-        req.getHints().put(Landmark.DISABLE, true);
+        req.putHint(CH.DISABLE, true);
+        req.putHint(Landmark.DISABLE, true);
         GHResponse rsp = hopper.route(req);
 
         PathWrapper bestPath = rsp.getBest();
@@ -350,8 +350,8 @@ public class GraphHopperIT {
         assertEquals(1310, rsp.getAll().get(0).getTime() / 1000);
         assertEquals(1432, rsp.getAll().get(1).getTime() / 1000);
 
-        req.getHints().put("alternative_route.max_paths", "3");
-        req.getHints().put("alternative_route.min_plateau_factor", "0.1");
+        req.putHint("alternative_route.max_paths", 3);
+        req.putHint("alternative_route.min_plateau_factor", 0.1);
         rsp = hopper.route(req);
         assertFalse(rsp.hasErrors());
         assertEquals(3, rsp.getAll().size());
@@ -372,7 +372,7 @@ public class GraphHopperIT {
 
         GHRequest req = new GHRequest(50.028917, 11.496506, 49.985228, 11.600876).
                 setAlgorithm(ALT_ROUTE).setVehicle(vehicle).setWeighting(weighting);
-        req.getHints().put("alternative_route.max_paths", "3");
+        req.putHint("alternative_route.max_paths", 3);
         GHResponse rsp = hopper.route(req);
         assertFalse(rsp.getErrors().toString(), rsp.hasErrors());
 
@@ -396,7 +396,7 @@ public class GraphHopperIT {
 
         GHRequest req = new GHRequest(50.023513, 11.548862, 49.969441, 11.537876).
                 setAlgorithm(ALT_ROUTE).setVehicle(vehicle).setWeighting(weighting);
-        req.getHints().put("alternative_route.max_paths", "3");
+        req.getHints().putObject("alternative_route.max_paths", 3);
         GHResponse rsp = hopper.route(req);
         assertFalse(rsp.getErrors().toString(), rsp.hasErrors());
 
@@ -479,7 +479,7 @@ public class GraphHopperIT {
         assertEquals(122, rsp.getBest().getDistance(), 1);
 
         // block point 49.985759,11.50687
-        req.getHints().put(Routing.BLOCK_AREA, "49.985759,11.50687");
+        req.putHint(Routing.BLOCK_AREA, "49.985759,11.50687");
         rsp = hopper.route(req);
         assertFalse(rsp.getErrors().toString(), rsp.hasErrors());
         assertEquals(365, rsp.getBest().getDistance(), 1);
@@ -494,30 +494,30 @@ public class GraphHopperIT {
 
         // block by area
         String someArea = "49.97986,11.472902,50.003946,11.534357";
-        req.getHints().put(Routing.BLOCK_AREA, someArea);
+        req.putHint(Routing.BLOCK_AREA, someArea);
         rsp = hopper.route(req);
         assertFalse(rsp.getErrors().toString(), rsp.hasErrors());
         assertEquals(13988, rsp.getBest().getDistance(), 1);
 
         // Add blocked point to above area, to increase detour        
-        req.getHints().put(Routing.BLOCK_AREA, "50.017578,11.547527;" + someArea);
+        req.putHint(Routing.BLOCK_AREA, "50.017578,11.547527;" + someArea);
         rsp = hopper.route(req);
         assertFalse(rsp.getErrors().toString(), rsp.hasErrors());
         assertEquals(14602, rsp.getBest().getDistance(), 1);
 
         // block by edge IDs -> i.e. use small circular area
-        req.getHints().put(Routing.BLOCK_AREA, "49.979929,11.520066,200");
+        req.putHint(Routing.BLOCK_AREA, "49.979929,11.520066,200");
         rsp = hopper.route(req);
         assertFalse(rsp.getErrors().toString(), rsp.hasErrors());
         assertEquals(12173, rsp.getBest().getDistance(), 1);
 
-        req.getHints().put(Routing.BLOCK_AREA, "49.980868,11.516397,150");
+        req.putHint(Routing.BLOCK_AREA, "49.980868,11.516397,150");
         rsp = hopper.route(req);
         assertFalse(rsp.getErrors().toString(), rsp.hasErrors());
         assertEquals(12173, rsp.getBest().getDistance(), 1);
 
         // block by edge IDs -> i.e. use small rectangular area
-        req.getHints().put(Routing.BLOCK_AREA, "49.981875,11.515818,49.979522,11.521407");
+        req.putHint(Routing.BLOCK_AREA, "49.981875,11.515818,49.979522,11.521407");
         rsp = hopper.route(req);
         assertFalse(rsp.getErrors().toString(), rsp.hasErrors());
         assertEquals(12173, rsp.getBest().getDistance(), 1);
@@ -529,7 +529,7 @@ public class GraphHopperIT {
         assertFalse(rsp.getErrors().toString(), rsp.hasErrors());
         assertEquals(1807, rsp.getBest().getDistance(), 1);
 
-        req.getHints().put(Routing.BLOCK_AREA, "50.018277,11.492336");
+        req.putHint(Routing.BLOCK_AREA, "50.018277,11.492336");
         rsp = hopper.route(req);
         assertFalse(rsp.getErrors().toString(), rsp.hasErrors());
         assertEquals(3363, rsp.getBest().getDistance(), 1);
@@ -543,7 +543,7 @@ public class GraphHopperIT {
         assertFalse(rsp.getErrors().toString(), rsp.hasErrors());
         assertEquals(155, rsp.getBest().getDistance(), 10);
 
-        req.getHints().put(Routing.BLOCK_AREA, "49.984434,11.505212,49.985394,11.506333");
+        req.putHint(Routing.BLOCK_AREA, "49.984434,11.505212,49.985394,11.506333");
         rsp = hopper.route(req);
         assertEquals(11.508, rsp.getBest().getWaypoints().getLongitude(0), 0.001);
         assertFalse(rsp.getErrors().toString(), rsp.hasErrors());
@@ -553,7 +553,7 @@ public class GraphHopperIT {
         req = new GHRequest(49.979, 11.516, 49.986107, 11.507202).
                 setVehicle(vehicle).
                 setWeighting(weighting);
-        req.getHints().put(Routing.BLOCK_AREA, "49.981875,11.515818,49.979522,11.521407");
+        req.putHint(Routing.BLOCK_AREA, "49.981875,11.515818,49.979522,11.521407");
         rsp = hopper.route(req);
         assertTrue("expected errors", rsp.hasErrors());
     }
@@ -681,7 +681,7 @@ public class GraphHopperIT {
                 addPoint(new GHPoint(43.741069, 7.426854), 0.).
                 addPoint(new GHPoint(43.744445, 7.429483), 190.).
                 setVehicle(vehicle).setWeighting(weighting);
-        req.getHints().put(Routing.HEADING_PENALTY, "300");
+        req.putHint(Routing.HEADING_PENALTY, "300");
         GHResponse rsp = hopper.route(req);
 
         PathWrapper arsp = rsp.getBest();
@@ -704,7 +704,7 @@ public class GraphHopperIT {
                 addPoint(from).
                 addPoint(to).
                 setVehicle(vehicle).setWeighting(weighting);
-        req.getHints().put(Routing.MAX_VISITED_NODES, 5);
+        req.putHint(Routing.MAX_VISITED_NODES, 5);
         GHResponse rsp = hopper.route(req);
 
         assertTrue(rsp.hasErrors());
@@ -803,7 +803,7 @@ public class GraphHopperIT {
                 addPoint(new GHPoint(43.740371, 7.426946)).
                 addPoint(new GHPoint(43.740794, 7.427294)).
                 setVehicle(vehicle).setWeighting(weighting);
-        rq.getHints().put(Routing.PASS_THROUGH, true);
+        rq.putHint(Routing.PASS_THROUGH, true);
         GHResponse rsp = hopper.route(rq);
 
         PathWrapper arsp = rsp.getBest();
@@ -816,7 +816,7 @@ public class GraphHopperIT {
                 addPoint(new GHPoint(43.741069, 7.426854)).
                 addPoint(new GHPoint(43.740371, 7.426946)).
                 setVehicle(vehicle).setWeighting(weighting);
-        rq.getHints().put(Routing.PASS_THROUGH, true);
+        rq.putHint(Routing.PASS_THROUGH, true);
         rsp = hopper.route(rq);
         assertEquals(91, rsp.getBest().getDistance(), 5.);
     }
@@ -1215,8 +1215,8 @@ public class GraphHopperIT {
                 addPoint(new GHPoint(43.741069, 7.426854), 50).
                 setVehicle(vehicle).setWeighting(weighting).
                 setAlgorithm(ROUND_TRIP);
-        rq.getHints().put(RoundTrip.DISTANCE, 1000);
-        rq.getHints().put(RoundTrip.SEED, 0);
+        rq.putHint(RoundTrip.DISTANCE, 1000);
+        rq.putHint(RoundTrip.SEED, 0);
 
         GHResponse rsp = hopper.route(rq);
 
@@ -1291,8 +1291,8 @@ public class GraphHopperIT {
                 setVehicle(vehicle).
                 setWeighting(weighting);
         // request speed mode
-        req.getHints().put(Landmark.DISABLE, true);
-        req.getHints().put(CH.DISABLE, false);
+        req.putHint(Landmark.DISABLE, true);
+        req.putHint(CH.DISABLE, false);
 
         GHResponse rsp = hopper.route(req);
         long chSum = rsp.getHints().getLong("visited_nodes.sum", 0);
@@ -1303,8 +1303,8 @@ public class GraphHopperIT {
 
         // request flex mode
         req.setAlgorithm(Parameters.Algorithms.ASTAR_BI);
-        req.getHints().put(Landmark.DISABLE, true);
-        req.getHints().put(CH.DISABLE, true);
+        req.putHint(Landmark.DISABLE, true);
+        req.putHint(CH.DISABLE, true);
         rsp = hopper.route(req);
         long flexSum = rsp.getHints().getLong("visited_nodes.sum", 0);
         assertTrue("Too few visited nodes for flex mode " + flexSum, flexSum > 60);
@@ -1314,8 +1314,8 @@ public class GraphHopperIT {
         assertEquals(91, bestPath.getPoints().getSize());
 
         // request hybrid mode
-        req.getHints().put(Landmark.DISABLE, false);
-        req.getHints().put(CH.DISABLE, true);
+        req.putHint(Landmark.DISABLE, false);
+        req.putHint(CH.DISABLE, true);
         rsp = hopper.route(req);
 
         long hSum = rsp.getHints().getLong("visited_nodes.sum", 0);
@@ -1356,22 +1356,22 @@ public class GraphHopperIT {
                 setWeighting("short_fastest");
 
         // try with CH
-        req.getHints().put(CH.DISABLE, false);
-        req.getHints().put(Landmark.DISABLE, false);
+        req.putHint(CH.DISABLE, false);
+        req.putHint(Landmark.DISABLE, false);
         GHResponse res = hopper.route(req);
         assertTrue(res.getErrors().toString(), res.hasErrors());
         assertTrue("there should be an error", res.getErrors().get(0).getMessage().contains("Cannot find matching CH profile for your request"));
 
         // try with LM
-        req.getHints().put(CH.DISABLE, true);
-        req.getHints().put(Landmark.DISABLE, false);
+        req.putHint(CH.DISABLE, true);
+        req.putHint(Landmark.DISABLE, false);
         res = hopper.route(req);
         assertTrue(res.getErrors().toString(), res.hasErrors());
         assertTrue("there should be an error", res.getErrors().get(0).getMessage().contains("Cannot find matching LM profile for your request"));
 
         // falling back to non-prepared algo works
-        req.getHints().put(CH.DISABLE, true);
-        req.getHints().put(Landmark.DISABLE, true);
+        req.putHint(CH.DISABLE, true);
+        req.putHint(Landmark.DISABLE, true);
         res = hopper.route(req);
         assertFalse(res.getErrors().toString(), res.hasErrors());
         assertEquals(3587, res.getBest().getDistance(), 1);
@@ -1399,11 +1399,11 @@ public class GraphHopperIT {
                 setVehicle(vehicle).
                 setWeighting(weighting);
 
-        req.getHints().put(Landmark.DISABLE, false);
+        req.putHint(Landmark.DISABLE, false);
         GHResponse res = hopper.route(req);
         assertTrue(res.getHints().getInt("visited_nodes.sum", 0) < 150);
 
-        req.getHints().put(Landmark.DISABLE, true);
+        req.putHint(Landmark.DISABLE, true);
         res = hopper.route(req);
         assertTrue(res.getHints().getInt("visited_nodes.sum", 0) > 200);
     }
@@ -1472,7 +1472,7 @@ public class GraphHopperIT {
         // without CH -> also edge-based
         GHResponse rsp2 = assertMoscowEdgeBased(hopper, "true", true);
         // just a quick check that we did not run the same algorithm twice
-        assertNotEquals(rsp1.getHints().get("visited_nodes.sum", "_"), rsp2.getHints().get("visited_nodes.sum", "_"));
+        assertNotEquals(rsp1.getHints().getInt("visited_nodes.sum", -1), rsp2.getHints().getInt("visited_nodes.sum", -1));
     }
 
     @Test
@@ -1553,7 +1553,7 @@ public class GraphHopperIT {
         } else {
             req.getHints().remove(Routing.EDGE_BASED);
         }
-        req.getHints().put(CH.DISABLE, !ch);
+        req.putHint(CH.DISABLE, !ch);
         req.setVehicle("car");
         req.setWeighting("fastest");
         return hopper.route(req);
@@ -1569,7 +1569,7 @@ public class GraphHopperIT {
         GHPoint p = new GHPoint(43.727687, 7.418737);
         GHPoint q = new GHPoint(43.74958, 7.436566);
         GHRequest req = new GHRequest(p, q);
-        req.getHints().put(Routing.EDGE_BASED, true);
+        req.putHint(Routing.EDGE_BASED, true);
         req.setVehicle(vehicle);
         GHResponse rsp = hopper.route(req);
         assertTrue(rsp.hasErrors());
@@ -1701,8 +1701,8 @@ public class GraphHopperIT {
 
     private GHResponse calcCurbsidePath(GraphHopper hopper, GHPoint source, GHPoint target, List<String> curbsides, boolean force) {
         GHRequest req = new GHRequest(source, target);
-        req.getHints().put(Routing.EDGE_BASED, true);
-        req.getHints().put(Routing.FORCE_CURBSIDE, force);
+        req.putHint(Routing.EDGE_BASED, true);
+        req.putHint(Routing.FORCE_CURBSIDE, force);
         req.setCurbsides(curbsides);
         return hopper.route(req);
     }
@@ -1754,7 +1754,7 @@ public class GraphHopperIT {
                 .addPoint(new GHPoint(50.016895, 11.4923))
                 .addPoint(new GHPoint(50.003464, 11.49157))
                 .setPathDetails(Arrays.asList("street_name", "max_speed"));
-        req.putHint("elevation", "true");
+        req.putHint("elevation", true);
 
         GHResponse rsp = hopper.route(req);
         assertFalse(rsp.getErrors().toString(), rsp.hasErrors());
diff --git a/reader-osm/src/test/java/com/graphhopper/reader/osm/GraphHopperOSMTest.java b/reader-osm/src/test/java/com/graphhopper/reader/osm/GraphHopperOSMTest.java
index 31e525fa2..213585fcb 100644
--- a/reader-osm/src/test/java/com/graphhopper/reader/osm/GraphHopperOSMTest.java
+++ b/reader-osm/src/test/java/com/graphhopper/reader/osm/GraphHopperOSMTest.java
@@ -390,11 +390,11 @@ public class GraphHopperOSMTest {
         GHRequest req = new GHRequest(51.2492152, 9.4317166, 51.2, 9.4);
         req.setVehicle(vehicle).setWeighting(weighting);
         boolean old = instance.getEncodingManager().isEnableInstructions();
-        req.getHints().put("instructions", true);
+        req.putHint("instructions", true);
         instance.route(req);
         assertEquals(old, instance.getEncodingManager().isEnableInstructions());
 
-        req.getHints().put("instructions", false);
+        req.putHint("instructions", false);
         instance.route(req);
         assertEquals("route method should not change instance field", old, instance.getEncodingManager().isEnableInstructions());
     }
@@ -769,7 +769,7 @@ public class GraphHopperOSMTest {
         // Via Point betweeen 8-3
         GHPoint via = new GHPoint(0.0015, 0.001);
         GHRequest req = new GHRequest().addPoint(start).addPoint(via).addPoint(end).setVehicle("car").setWeighting("fastest");
-        req.getHints().put(Routing.PASS_THROUGH, true);
+        req.putHint(Routing.PASS_THROUGH, true);
         GHResponse response = new GHResponse();
         List<Path> paths = instance.calcPaths(req, response);
         assertFalse(response.hasErrors());
@@ -790,7 +790,7 @@ public class GraphHopperOSMTest {
         // First go south and than come from west to via-point at 7-6. Then go back over previously punished (11)-4 edge
         GHPoint via = new GHPoint(0.000, 0.0015);
         GHRequest req = new GHRequest().addPoint(start, 0.).addPoint(via, 3.14 / 2).addPoint(end).setVehicle("car").setWeighting("fastest");
-        req.getHints().put(Routing.PASS_THROUGH, true);
+        req.putHint(Routing.PASS_THROUGH, true);
         GHResponse response = new GHResponse();
         List<Path> paths = instance.calcPaths(req, response);
         assertFalse(response.hasErrors());
diff --git a/web/src/test/java/com/graphhopper/http/resources/ChangeGraphResourceTest.java b/web/src/test/java/com/graphhopper/http/resources/ChangeGraphResourceTest.java
index 48141244e..7ab12867d 100644
--- a/web/src/test/java/com/graphhopper/http/resources/ChangeGraphResourceTest.java
+++ b/web/src/test/java/com/graphhopper/http/resources/ChangeGraphResourceTest.java
@@ -42,7 +42,7 @@ public class ChangeGraphResourceTest {
     static {
         config.getGraphHopperConfiguration().
                 put("graph.flag_encoders", "car").
-                put("web.change_graph.enabled", "true").
+                put("web.change_graph.enabled", true).
                 put("graph.location", DIR).
                 put("datareader.file", "../core/files/andorra.osm.pbf");
     }
diff --git a/web/src/test/java/com/graphhopper/http/resources/MvtResourceTest.java b/web/src/test/java/com/graphhopper/http/resources/MvtResourceTest.java
index 43ffc193d..76ae53519 100644
--- a/web/src/test/java/com/graphhopper/http/resources/MvtResourceTest.java
+++ b/web/src/test/java/com/graphhopper/http/resources/MvtResourceTest.java
@@ -56,8 +56,8 @@ public class MvtResourceTest {
         config.getGraphHopperConfiguration().
                 put("graph.flag_encoders", "car").
                 put("graph.encoded_values", "road_class,road_environment,max_speed,surface").
-                put("prepare.min_network_size", "0").
-                put("prepare.min_one_way_network_size", "0").
+                put("prepare.min_network_size", 0).
+                put("prepare.min_one_way_network_size", 0).
                 put("datareader.file", "../core/files/andorra.osm.pbf").
                 put("graph.location", DIR);
     }
diff --git a/web/src/test/java/com/graphhopper/http/resources/NearestResourceWithEleTest.java b/web/src/test/java/com/graphhopper/http/resources/NearestResourceWithEleTest.java
index e81f3022e..83dacdf12 100644
--- a/web/src/test/java/com/graphhopper/http/resources/NearestResourceWithEleTest.java
+++ b/web/src/test/java/com/graphhopper/http/resources/NearestResourceWithEleTest.java
@@ -45,7 +45,7 @@ public class NearestResourceWithEleTest {
         config.getGraphHopperConfiguration().
                 put("graph.elevation.provider", "srtm").
                 put("graph.elevation.cachedir", "../core/files/").
-                put("prepare.min_one_way_network_size", "0").
+                put("prepare.min_one_way_network_size", 0).
                 put("graph.flag_encoders", "car").
                 put("datareader.file", "../core/files/monaco.osm.gz").
                 put("graph.location", dir);
diff --git a/web/src/test/java/com/graphhopper/http/resources/RouteResourceIssue1574Test.java b/web/src/test/java/com/graphhopper/http/resources/RouteResourceIssue1574Test.java
index cc1c81663..2eeff2945 100644
--- a/web/src/test/java/com/graphhopper/http/resources/RouteResourceIssue1574Test.java
+++ b/web/src/test/java/com/graphhopper/http/resources/RouteResourceIssue1574Test.java
@@ -49,8 +49,8 @@ public class RouteResourceIssue1574Test {
         // this is the reason we put this test into an extra file: we can only reproduce the bug of issue 1574 by increasing the one-way-network size
         config.getGraphHopperConfiguration().
                 put("graph.flag_encoders", "car").
-                put("prepare.min_network_size", "0").
-                put("prepare.min_one_way_network_size", "12").
+                put("prepare.min_network_size", 0).
+                put("prepare.min_one_way_network_size", 12).
                 put("datareader.file", "../core/files/andorra.osm.pbf").
                 put("graph.location", DIR)
                 .setProfiles(Collections.singletonList(
diff --git a/web/src/test/java/com/graphhopper/http/resources/RouteResourceTest.java b/web/src/test/java/com/graphhopper/http/resources/RouteResourceTest.java
index b98c65a11..d9f307e29 100644
--- a/web/src/test/java/com/graphhopper/http/resources/RouteResourceTest.java
+++ b/web/src/test/java/com/graphhopper/http/resources/RouteResourceTest.java
@@ -65,9 +65,9 @@ public class RouteResourceTest {
     static {
         config.getGraphHopperConfiguration().
                 put("graph.flag_encoders", "car").
-                put("routing.ch.disabling_allowed", "true").
-                put("prepare.min_network_size", "0").
-                put("prepare.min_one_way_network_size", "0").
+                put("routing.ch.disabling_allowed", true).
+                put("prepare.min_network_size", 0).
+                put("prepare.min_one_way_network_size", 0).
                 put("datareader.file", "../core/files/andorra.osm.pbf").
                 put("graph.encoded_values", "road_class,surface,road_environment,max_speed").
                 put("graph.location", DIR)
@@ -314,7 +314,7 @@ public class RouteResourceTest {
         GHResponse rsp = hopper.route(request);
         assertEquals("Continue onto Carrer Antoni Fiter i Rossell", rsp.getBest().getInstructions().get(3).getName());
 
-        request.getHints().put("turn_description", false);
+        request.getHints().putObject("turn_description", false);
         rsp = hopper.route(request);
         assertFalse(rsp.hasErrors());
         assertEquals("Carrer Antoni Fiter i Rossell", rsp.getBest().getInstructions().get(3).getName());
diff --git a/web/src/test/java/com/graphhopper/http/resources/RouteResourceWithEleTest.java b/web/src/test/java/com/graphhopper/http/resources/RouteResourceWithEleTest.java
index ee7333224..3161e621e 100644
--- a/web/src/test/java/com/graphhopper/http/resources/RouteResourceWithEleTest.java
+++ b/web/src/test/java/com/graphhopper/http/resources/RouteResourceWithEleTest.java
@@ -44,7 +44,7 @@ public class RouteResourceWithEleTest {
         config.getGraphHopperConfiguration().
                 put("graph.elevation.provider", "srtm").
                 put("graph.elevation.cachedir", "../core/files/").
-                put("prepare.min_one_way_network_size", "0").
+                put("prepare.min_one_way_network_size", 0).
                 put("graph.flag_encoders", "car").
                 put("datareader.file", "../core/files/monaco.osm.gz").
                 put("graph.location", dir);