Repository: eclipse-vertx/vert.x

Problem statement:
Feature Request: JsonArray.add(int index, Object value)
this is easy to add and is available in most json libs
PS: and for JsonArray.set(int index, Object value)
methods add and set have the same semantics as in java's List


Hints:
we might have `JsonArray` extends `List` in v4
If JsonArray extends List, add method is not going to be fluent (it's not going to return itself) because the signature will not be compatible with List interface. I mean, `JsonArray add(Object value)`is not going to be posible anymore. Is this acceptable?

I can work on it :)
I think @pmlopes has a coming PR for it soon.

> On 5 Nov 2019, at 22:06, Rogelio Orts <notifications@github.com> wrote:
> 
> If JsonArray extends List, add method is not going to be fluent (it's not going to return itself) because the signature will not be compatible with List interface. I mean, JsonArray add(Object value)is not going to be posible anymore. Is this acceptable?
> 
> I can work on it :)
> 
> —
> You are receiving this because you commented.
> Reply to this email directly, view it on GitHub <https://github.com/eclipse-vertx/vert.x/issues/3171?email_source=notifications&email_token=AABXDCTXZNBT7PWDAGGDGW3QSHN5HA5CNFSM4JH3UTG2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEDEKF6A#issuecomment-550019832>, or unsubscribe <https://github.com/notifications/unsubscribe-auth/AABXDCVPRS23FFXAJTSGHPLQSHN5HANCNFSM4JH3UTGQ>.
> 



Patch:
diff --git a/src/main/java/io/vertx/core/impl/ConversionHelper.java b/src/main/java/io/vertx/core/impl/ConversionHelper.java
--- a/src/main/java/io/vertx/core/impl/ConversionHelper.java
+++ b/src/main/java/io/vertx/core/impl/ConversionHelper.java
@@ -15,11 +15,11 @@
 import io.vertx.core.json.JsonArray;
 import io.vertx.core.json.JsonObject;
 
-import java.util.ArrayList;
-import java.util.Base64;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
+import java.time.Instant;
+import java.util.*;
+
+import static io.vertx.core.json.impl.JsonUtil.BASE64_ENCODER;
+import static java.time.format.DateTimeFormatter.ISO_INSTANT;
 
 /**
  * An internal conversion helper, later it could be merged with JsonObject mapFrom/mapTo and moved in Json class
@@ -47,7 +47,7 @@ private static Object toJsonElement(Object obj) {
     } else if (obj instanceof CharSequence) {
       return obj.toString();
     } else if (obj instanceof Buffer) {
-      return Base64.getEncoder().encodeToString(((Buffer)obj).getBytes());
+      return BASE64_ENCODER.encodeToString(((Buffer) obj).getBytes());
     }
     return obj;
   }
@@ -66,7 +66,7 @@ public static JsonArray toJsonArray(List<Object> list) {
       return null;
     }
     list = new ArrayList<>(list);
-    for (int i = 0;i < list.size();i++) {
+    for (int i = 0; i < list.size(); i++) {
       list.set(i, toJsonElement(list.get(i)));
     }
     return new JsonArray(list);
@@ -75,11 +75,18 @@ public static JsonArray toJsonArray(List<Object> list) {
   @SuppressWarnings("unchecked")
   public static <T> T fromObject(Object obj) {
     if (obj instanceof JsonObject) {
-      return (T)fromJsonObject((JsonObject)obj);
+      return (T) fromJsonObject((JsonObject) obj);
     } else if (obj instanceof JsonArray) {
-      return (T)fromJsonArray((JsonArray)obj);
+      return (T) fromJsonArray((JsonArray) obj);
+    } else if (obj instanceof Instant) {
+      return (T) ISO_INSTANT.format((Instant) obj);
+    } else if (obj instanceof byte[]) {
+      return (T) BASE64_ENCODER.encodeToString((byte[]) obj);
+    } else if (obj instanceof Enum) {
+      return (T) ((Enum) obj).name();
     }
-    return (T)obj;
+
+    return (T) obj;
   }
 
   public static Map<String, Object> fromJsonObject(JsonObject json) {
@@ -98,7 +105,7 @@ public static List<Object> fromJsonArray(JsonArray json) {
       return null;
     }
     List<Object> list = new ArrayList<>(json.getList());
-    for (int i = 0;i < list.size();i++) {
+    for (int i = 0; i < list.size(); i++) {
       list.set(i, fromObject(list.get(i)));
     }
     return list;
diff --git a/src/main/java/io/vertx/core/json/JsonArray.java b/src/main/java/io/vertx/core/json/JsonArray.java
--- a/src/main/java/io/vertx/core/json/JsonArray.java
+++ b/src/main/java/io/vertx/core/json/JsonArray.java
@@ -19,17 +19,18 @@
 import java.util.*;
 import java.util.stream.Stream;
 
+import static io.vertx.core.json.impl.JsonUtil.*;
 import static java.time.format.DateTimeFormatter.ISO_INSTANT;
 
 /**
  * A representation of a <a href="http://json.org/">JSON</a> array in Java.
- * <p>
+ *
  * Unlike some other languages Java does not have a native understanding of JSON. To enable JSON to be used easily
  * in Vert.x code we use this class to encapsulate the notion of a JSON array.
  *
  * The implementation adheres to the <a href="http://rfc-editor.org/rfc/rfc7493.txt">RFC-7493</a> to support Temporal
  * data types as well as binary data.
- * <p>
+ *
  * Please see the documentation for more information.
  *
  * @author <a href="http://tfox.org">Tim Fox</a>
@@ -78,7 +79,7 @@ public JsonArray(List list) {
   /**
    * Create an instance from a Buffer of JSON.
    *
-   * @param buf  the buffer of JSON.
+   * @param buf the buffer of JSON.
    */
   public JsonArray(Buffer buf) {
     if (buf == null) {
@@ -93,28 +94,43 @@ public JsonArray(Buffer buf) {
   /**
    * Get the String at position {@code pos} in the array,
    *
-   * @param pos  the position in the array
-   * @return  the String, or null if a null value present
+   * @param pos the position in the array
+   * @return the String, or null if a null value present
    * @throws java.lang.ClassCastException if the value cannot be converted to String
    */
   public String getString(int pos) {
-    CharSequence cs = (CharSequence)list.get(pos);
-    return cs == null ? null : cs.toString();
+    Object val = list.get(pos);
+
+    if (val == null) {
+      return null;
+    }
+
+    if (val instanceof CharSequence) {
+      return val.toString();
+    } else if (val instanceof Instant) {
+      return ISO_INSTANT.format((Instant) val);
+    } else if (val instanceof byte[]) {
+      return BASE64_ENCODER.encodeToString((byte[]) val);
+    } else if (val instanceof Enum) {
+      return ((Enum) val).name();
+    }
+
+    throw new ClassCastException("class " + val.getClass().getName() + " cannot be cast to class java.lang.String");
   }
 
   /**
    * Get the Integer at position {@code pos} in the array,
    *
-   * @param pos  the position in the array
-   * @return  the Integer, or null if a null value present
+   * @param pos the position in the array
+   * @return the Integer, or null if a null value present
    * @throws java.lang.ClassCastException if the value cannot be converted to Integer
    */
   public Integer getInteger(int pos) {
-    Number number = (Number)list.get(pos);
+    Number number = (Number) list.get(pos);
     if (number == null) {
       return null;
     } else if (number instanceof Integer) {
-      return (Integer)number; // Avoids unnecessary unbox/box
+      return (Integer) number; // Avoids unnecessary unbox/box
     } else {
       return number.intValue();
     }
@@ -123,16 +139,16 @@ public Integer getInteger(int pos) {
   /**
    * Get the Long at position {@code pos} in the array,
    *
-   * @param pos  the position in the array
-   * @return  the Long, or null if a null value present
+   * @param pos the position in the array
+   * @return the Long, or null if a null value present
    * @throws java.lang.ClassCastException if the value cannot be converted to Long
    */
   public Long getLong(int pos) {
-    Number number = (Number)list.get(pos);
+    Number number = (Number) list.get(pos);
     if (number == null) {
       return null;
     } else if (number instanceof Long) {
-      return (Long)number; // Avoids unnecessary unbox/box
+      return (Long) number; // Avoids unnecessary unbox/box
     } else {
       return number.longValue();
     }
@@ -141,16 +157,16 @@ public Long getLong(int pos) {
   /**
    * Get the Double at position {@code pos} in the array,
    *
-   * @param pos  the position in the array
-   * @return  the Double, or null if a null value present
+   * @param pos the position in the array
+   * @return the Double, or null if a null value present
    * @throws java.lang.ClassCastException if the value cannot be converted to Double
    */
   public Double getDouble(int pos) {
-    Number number = (Number)list.get(pos);
+    Number number = (Number) list.get(pos);
     if (number == null) {
       return null;
     } else if (number instanceof Double) {
-      return (Double)number; // Avoids unnecessary unbox/box
+      return (Double) number; // Avoids unnecessary unbox/box
     } else {
       return number.doubleValue();
     }
@@ -159,16 +175,16 @@ public Double getDouble(int pos) {
   /**
    * Get the Float at position {@code pos} in the array,
    *
-   * @param pos  the position in the array
-   * @return  the Float, or null if a null value present
+   * @param pos the position in the array
+   * @return the Float, or null if a null value present
    * @throws java.lang.ClassCastException if the value cannot be converted to Float
    */
   public Float getFloat(int pos) {
-    Number number = (Number)list.get(pos);
+    Number number = (Number) list.get(pos);
     if (number == null) {
       return null;
     } else if (number instanceof Float) {
-      return (Float)number; // Avoids unnecessary unbox/box
+      return (Float) number; // Avoids unnecessary unbox/box
     } else {
       return number.floatValue();
     }
@@ -177,391 +193,167 @@ public Float getFloat(int pos) {
   /**
    * Get the Boolean at position {@code pos} in the array,
    *
-   * @param pos  the position in the array
-   * @return  the Boolean, or null if a null value present
+   * @param pos the position in the array
+   * @return the Boolean, or null if a null value present
    * @throws java.lang.ClassCastException if the value cannot be converted to Integer
    */
   public Boolean getBoolean(int pos) {
-    return (Boolean)list.get(pos);
+    return (Boolean) list.get(pos);
   }
 
   /**
    * Get the JsonObject at position {@code pos} in the array.
    *
-   * @param pos  the position in the array
-   * @return  the JsonObject, or null if a null value present
+   * @param pos the position in the array
+   * @return the JsonObject, or null if a null value present
    * @throws java.lang.ClassCastException if the value cannot be converted to JsonObject
    */
   public JsonObject getJsonObject(int pos) {
     Object val = list.get(pos);
     if (val instanceof Map) {
-      val = new JsonObject((Map)val);
+      val = new JsonObject((Map) val);
     }
-    return (JsonObject)val;
+    return (JsonObject) val;
   }
 
   /**
    * Get the JsonArray at position {@code pos} in the array.
    *
-   * @param pos  the position in the array
-   * @return  the Integer, or null if a null value present
+   * @param pos the position in the array
+   * @return the Integer, or null if a null value present
    * @throws java.lang.ClassCastException if the value cannot be converted to JsonArray
    */
   public JsonArray getJsonArray(int pos) {
     Object val = list.get(pos);
     if (val instanceof List) {
-      val = new JsonArray((List)val);
+      val = new JsonArray((List) val);
     }
-    return (JsonArray)val;
+    return (JsonArray) val;
   }
 
   /**
    * Get the byte[] at position {@code pos} in the array.
-   * <p>
+   *
    * JSON itself has no notion of a binary, so this method assumes there is a String value and
    * it contains a Base64 encoded binary, which it decodes if found and returns.
-   * <p>
-   * This method should be used in conjunction with {@link #add(byte[])}
    *
-   * @param pos  the position in the array
-   * @return  the byte[], or null if a null value present
-   * @throws java.lang.ClassCastException if the value cannot be converted to String
+   * @param pos the position in the array
+   * @return the byte[], or null if a null value present
+   * @throws java.lang.ClassCastException       if the value cannot be converted to String
    * @throws java.lang.IllegalArgumentException if the String value is not a legal Base64 encoded value
    */
   public byte[] getBinary(int pos) {
-    String val = (String)list.get(pos);
+    Object val = list.get(pos);
+    // no-op
     if (val == null) {
       return null;
-    } else {
-      return Base64.getDecoder().decode(val);
     }
+    // no-op if value is already an byte[]
+    if (val instanceof byte[]) {
+      return (byte[]) val;
+    }
+    // assume that the value is in String format as per RFC
+    String encoded = (String) val;
+    // parse to proper type
+    return BASE64_DECODER.decode(encoded);
   }
 
   /**
    * Get the Instant at position {@code pos} in the array.
-   * <p>
+   *
    * JSON itself has no notion of a temporal types, this extension complies to the RFC-7493, so this method assumes
    * there is a String value and it contains an ISO 8601 encoded date and time format such as "2017-04-03T10:25:41Z",
    * which it decodes if found and returns.
-   * <p>
-   * This method should be used in conjunction with {@link #add(Instant)}
    *
-   * @param pos  the position in the array
-   * @return  the Instant, or null if a null value present
-   * @throws java.lang.ClassCastException if the value cannot be converted to String
+   * @param pos the position in the array
+   * @return the Instant, or null if a null value present
+   * @throws java.lang.ClassCastException            if the value cannot be converted to String
    * @throws java.time.format.DateTimeParseException if the String value is not a legal ISO 8601 encoded value
    */
   public Instant getInstant(int pos) {
-    String val = (String)list.get(pos);
+    Object val = list.get(pos);
+    // no-op
     if (val == null) {
       return null;
-    } else {
-      return Instant.from(ISO_INSTANT.parse(val));
     }
+    // no-op if value is already an Instant
+    if (val instanceof Instant) {
+      return (Instant) val;
+    }
+    // assume that the value is in String format as per RFC
+    String encoded = (String) val;
+    // parse to proper type
+    return Instant.from(ISO_INSTANT.parse(encoded));
   }
 
   /**
-   * Get the Object value at position {@code pos} in the array.
+   * Get the value with the specified key, as an Object with types respecting the limitations of JSON.
+   * <ul>
+   *   <li>{@code Map} will be wrapped to {@code JsonObject}</li>
+   *   <li>{@code List} will be wrapped to {@code JsonArray}</li>
+   *   <li>{@code Instant} will be converted to {@code String}</li>
+   *   <li>{@code byte[]} will be converted to {@code String}</li>
+   *   <li>{@code Enum} will be converted to {@code String}</li>
+   * </ul>
    *
-   * @param pos  the position in the array
-   * @return  the Integer, or null if a null value present
+   * @param pos the position in the array
+   * @return the Integer, or null if a null value present
    */
   public Object getValue(int pos) {
-    Object val = list.get(pos);
-    if (val instanceof Map) {
-      val = new JsonObject((Map)val);
-    } else if (val instanceof List) {
-      val = new JsonArray((List)val);
-    }
-    return val;
+    return wrapJsonValue(list.get(pos));
   }
 
   /**
    * Is there a null value at position pos?
    *
-   * @param pos  the position in the array
+   * @param pos the position in the array
    * @return true if null value present, false otherwise
    */
   public boolean hasNull(int pos) {
     return list.get(pos) == null;
   }
 
-  /**
-   * Add an enum to the JSON array.
-   * <p>
-   * JSON has no concept of encoding Enums, so the Enum will be converted to a String using the {@link java.lang.Enum#name()}
-   * method and the value added as a String.
-   *
-   * @param value  the value
-   * @return  a reference to this, so the API can be used fluently
-   */
-  public JsonArray add(Enum value) {
-    list.add(value != null ? value.name() : null);
-    return this;
-  }
-
-  /**
-   * Add a CharSequence to the JSON array.
-   *
-   * @param value  the value
-   * @return  a reference to this, so the API can be used fluently
-   */
-  public JsonArray add(CharSequence value) {
-    list.add(value != null ? value.toString() : null);
-    return this;
-  }
-
-  /**
-   * Add a String to the JSON array.
-   *
-   * @param value  the value
-   * @return  a reference to this, so the API can be used fluently
-   */
-  public JsonArray add(String value) {
-    list.add(value);
-    return this;
-  }
-
-  /**
-   * Add an Integer to the JSON array.
-   *
-   * @param value  the value
-   * @return  a reference to this, so the API can be used fluently
-   */
-  public JsonArray add(Integer value) {
-    list.add(value);
-    return this;
-  }
-
-  /**
-   * Add a Long to the JSON array.
-   *
-   * @param value  the value
-   * @return  a reference to this, so the API can be used fluently
-   */
-  public JsonArray add(Long value) {
-    list.add(value);
-    return this;
-  }
-
-  /**
-   * Add a Double to the JSON array.
-   *
-   * @param value  the value
-   * @return  a reference to this, so the API can be used fluently
-   */
-  public JsonArray add(Double value) {
-    list.add(value);
-    return this;
-  }
-
-  /**
-   * Add a Float to the JSON array.
-   *
-   * @param value  the value
-   * @return  a reference to this, so the API can be used fluently
-   */
-  public JsonArray add(Float value) {
-    list.add(value);
-    return this;
-  }
-
-  /**
-   * Add a Boolean to the JSON array.
-   *
-   * @param value  the value
-   * @return  a reference to this, so the API can be used fluently
-   */
-  public JsonArray add(Boolean value) {
-    list.add(value);
-    return this;
-  }
-
   /**
    * Add a null value to the JSON array.
    *
-   * @return  a reference to this, so the API can be used fluently
+   * @return a reference to this, so the API can be used fluently
    */
   public JsonArray addNull() {
     list.add(null);
     return this;
   }
 
-  /**
-   * Add a JSON object to the JSON array.
-   *
-   * @param value  the value
-   * @return  a reference to this, so the API can be used fluently
-   */
-  public JsonArray add(JsonObject value) {
-    list.add(value);
-    return this;
-  }
-
-  /**
-   * Add another JSON array to the JSON array.
-   *
-   * @param value  the value
-   * @return  a reference to this, so the API can be used fluently
-   */
-  public JsonArray add(JsonArray value) {
-    list.add(value);
-    return this;
-  }
-
-  /**
-   * Add a binary value to the JSON array.
-   * <p>
-   * JSON has no notion of binary so the binary will be base64 encoded to a String, and the String added.
-   *
-   * @param value  the value
-   * @return  a reference to this, so the API can be used fluently
-   */
-  public JsonArray add(byte[] value) {
-    list.add(value != null ? Base64.getEncoder().encodeToString(value) : null);
-    return this;
-  }
-
-  /**
-   * Add a Instant value to the JSON array.
-   * <p>
-   * JSON has no notion of Temporal data so the Instant will be ISOString encoded, and the String added.
-   *
-   * @param value  the value
-   * @return  a reference to this, so the API can be used fluently
-   */
-  public JsonArray add(Instant value) {
-    list.add(value != null ? ISO_INSTANT.format(value) : null);
-    return this;
-  }
-
   /**
    * Add an Object to the JSON array.
    *
-   * @param value  the value
-   * @return  a reference to this, so the API can be used fluently
-   */
-  public JsonArray add(Object value) {
-    value = JsonObject.checkAndCopy(value, false);
-    list.add(value);
-    return this;
-  }
-
-  /**
-   * Appends all of the elements in the specified array to the end of this JSON array.
-   *
-   * @param array the array
-   * @return  a reference to this, so the API can be used fluently
-   */
-  public JsonArray addAll(JsonArray array) {
-    list.addAll(array.list);
-    return this;
-  }
-
-  /**
-   * Set an enum to the JSON array at position {@code pos}.
-   * <p>
-   * JSON has no concept of encoding Enums, so the Enum will be converted to a String using the {@link java.lang.Enum#name()}
-   * method and the value added as a String.
-   *
-   * @param pos position in the array
-   * @param value the value
-   *
-   * @return a reference to this, so the API can be used fluently
-   */
-  public JsonArray set(int pos, Enum value) {
-    list.set(pos, value != null ? value.name() : null);
-    return this;
-  }
-
-  /**
-   * Set a CharSequence to the JSON array at position {@code pos}.
-   *
-   * @param pos position in the array
-   * @param value the value
-   *
-   * @return a reference to this, so the API can be used fluently
-   */
-  public JsonArray set(int pos, CharSequence value) {
-    list.set(pos, value != null ? value.toString() : null);
-    return this;
-  }
-
-  /**
-   * Set a String to the JSON array at position {@code pos}.
-   *
-   * @param pos position in the array
-   * @param value the value
-   *
-   * @return a reference to this, so the API can be used fluently
-   */
-  public JsonArray set(int pos, String value) {
-    list.set(pos, value);
-    return this;
-  }
-
-  /**
-   * Set an Integer to the JSON array at position {@code pos}.
-   *
-   * @param pos position in the array
    * @param value the value
-   *
-   * @return a reference to this, so the API can be used fluently
-   */
-  public JsonArray set(int pos, Integer value) {
-    list.set(pos, value);
-    return this;
-  }
-
-  /**
-   * Set a Long to the JSON array at position {@code pos}.
-   *
-   * @param pos position in the array
-   * @param value the value
-   *
-   * @return a reference to this, so the API can be used fluently
-   */
-  public JsonArray set(int pos, Long value) {
-    list.set(pos, value);
-    return this;
-  }
-
-  /**
-   * Set a Double to the JSON array at position {@code pos}.
-   *
-   * @param pos position in the array
-   * @param value the value
-   *
    * @return a reference to this, so the API can be used fluently
    */
-  public JsonArray set(int pos, Double value) {
-    list.set(pos, value);
+  public JsonArray add(Object value) {
+    list.add(value);
     return this;
   }
 
   /**
-   * Set a Float to the JSON array at position {@code pos}.
+   * Add an Object to the JSON array at given position {@code pos}.
    *
-   * @param pos position in the array
+   * @param pos the position
    * @param value the value
-   *
    * @return a reference to this, so the API can be used fluently
    */
-  public JsonArray set(int pos, Float value) {
-    list.set(pos, value);
+  public JsonArray add(int pos, Object value) {
+    list.add(pos, value);
     return this;
   }
 
   /**
-   * Set a Boolean to the JSON array at position {@code pos}.
-   *
-   * @param pos position in the array
-   * @param value the value
+   * Appends all of the elements in the specified array to the end of this JSON array.
    *
+   * @param array the array
    * @return a reference to this, so the API can be used fluently
    */
-  public JsonArray set(int pos, Boolean value) {
-    list.set(pos, value);
+  public JsonArray addAll(JsonArray array) {
+    list.addAll(array.list);
     return this;
   }
 
@@ -575,72 +367,14 @@ public JsonArray setNull(int pos) {
     return this;
   }
 
-  /**
-   * Set a JSON object to the JSON array at position {@code pos}.
-   *
-   * @param pos position in the array
-   * @param value the value
-   *
-   * @return a reference to this, so the API can be used fluently
-   */
-  public JsonArray set(int pos, JsonObject value) {
-    list.set(pos, value);
-    return this;
-  }
-
-  /**
-   * Set another JSON array to the JSON array at position {@code pos}.
-   *
-   * @param pos position in the array
-   * @param value the value
-   *
-   * @return a reference to this, so the API can be used fluently
-   */
-  public JsonArray set(int pos, JsonArray value) {
-    list.set(pos, value);
-    return this;
-  }
-
-  /**
-   * Set a binary value to the JSON array at position {@code pos}.
-   * <p>
-   * JSON has no notion of binary so the binary will be base64 encoded to a String, and the String added.
-   *
-   * @param pos position in the array
-   * @param value the value
-   *
-   * @return a reference to this, so the API can be used fluently
-   */
-  public JsonArray set(int pos, byte[] value) {
-    list.set(pos, value != null ? Base64.getEncoder().encodeToString(value) : null);
-    return this;
-  }
-
-  /**
-   * Set a Instant value to the JSON array at position {@code pos}.
-   * <p>
-   * JSON has no notion of Temporal data so the Instant will be ISOString encoded, and the String added.
-   *
-   * @param pos position in the array
-   * @param value the value
-   *
-   * @return a reference to this, so the API can be used fluently
-   */
-  public JsonArray set(int pos, Instant value) {
-    list.set(pos, value != null ? ISO_INSTANT.format(value) : null);
-    return this;
-  }
-
   /**
    * Set an Object to the JSON array at position {@code pos}.
    *
-   * @param pos position in the array
+   * @param pos   position in the array
    * @param value the value
-   *
    * @return a reference to this, so the API can be used fluently
    */
   public JsonArray set(int pos, Object value) {
-    value = JsonObject.checkAndCopy(value, false);
     list.set(pos, value);
     return this;
   }
@@ -649,8 +383,8 @@ public JsonArray set(int pos, Object value) {
    * Does the JSON array contain the specified value? This method will scan the entire array until it finds a value
    * or reaches the end.
    *
-   * @param value  the value
-   * @return  true if it contains the value, false if not
+   * @param value the value
+   * @return true if it contains the value, false if not
    */
   public boolean contains(Object value) {
     return list.contains(value);
@@ -660,28 +394,38 @@ public boolean contains(Object value) {
    * Remove the specified value from the JSON array. This method will scan the entire array until it finds a value
    * or reaches the end.
    *
-   * @param value  the value to remove
+   * @param value the value to remove
    * @return true if it removed it, false if not found
    */
   public boolean remove(Object value) {
-    return list.remove(value);
+    final Object wrappedValue = wrapJsonValue(value);
+    for (int i = 0; i < list.size(); i++) {
+      // perform comparision on wrapped types
+      final Object otherWrapperValue = getValue(i);
+      if (wrappedValue == null) {
+        if (otherWrapperValue == null) {
+          list.remove(i);
+          return true;
+        }
+      } else {
+        if (wrappedValue.equals(otherWrapperValue)) {
+          list.remove(i);
+          return true;
+        }
+      }
+    }
+    return false;
   }
 
   /**
    * Remove the value at the specified position in the JSON array.
    *
-   * @param pos  the position to remove the value at
+   * @param pos the position to remove the value at
    * @return the removed value if removed, null otherwise. If the value is a Map, a {@link JsonObject} is built from
    * this Map and returned. It the value is a List, a {@link JsonArray} is built form this List and returned.
    */
   public Object remove(int pos) {
-    Object removed = list.remove(pos);
-    if (removed instanceof Map) {
-      return new JsonObject((Map) removed);
-    } else if (removed instanceof ArrayList) {
-      return new JsonArray((List) removed);
-    }
-    return removed;
+    return wrapJsonValue(list.remove(pos));
   }
 
   /**
@@ -705,7 +449,7 @@ public boolean isEmpty() {
   /**
    * Get the unerlying List
    *
-   * @return  the underlying List
+   * @return the underlying List
    */
   public List getList() {
     return list;
@@ -714,7 +458,7 @@ public List getList() {
   /**
    * Remove all entries from the JSON array
    *
-   * @return  a reference to this, so the API can be used fluently
+   * @return a reference to this, so the API can be used fluently
    */
   public JsonArray clear() {
     list.clear();
@@ -766,9 +510,8 @@ public String encodePrettily() {
   @Override
   public JsonArray copy() {
     List<Object> copiedList = new ArrayList<>(list.size());
-    for (Object val: list) {
-      val = JsonObject.checkAndCopy(val, true);
-      copiedList.add(val);
+    for (Object val : list) {
+      copiedList.add(checkAndCopy(val));
     }
     return new JsonArray(copiedList);
   }
@@ -789,32 +532,64 @@ public String toString() {
 
   @Override
   public boolean equals(Object o) {
+    // null check
+    if (o == null)
+      return false;
+    // self check
     if (this == o)
       return true;
-    if (o == null || getClass() != o.getClass())
+    // type check and cast
+    if (getClass() != o.getClass())
       return false;
-    return arrayEquals(list, o);
-  }
 
-  static boolean arrayEquals(List<?> l1, Object o2) {
-    List<?> l2;
-    if (o2 instanceof JsonArray) {
-      l2 = ((JsonArray) o2).list;
-    } else if (o2 instanceof List<?>) {
-      l2 = (List<?>) o2;
-    } else {
-      return false;
-    }
-    if (l1.size() != l2.size()) {
+    JsonArray other = (JsonArray) o;
+    // size check
+    if (this.size() != other.size())
       return false;
-    }
-    Iterator<?> iter = l2.iterator();
-    for (Object entry : l1) {
-      Object other = iter.next();
-      if (entry == null ? other != null : !JsonObject.equals(entry, other)) {
+    // value comparison
+    for (int i = 0; i < this.size(); i++) {
+      Object thisValue = this.getValue(i);
+      Object otherValue = other.getValue(i);
+      // identity check
+      if (thisValue == otherValue) {
+        continue;
+      }
+      // special case for numbers
+      if (thisValue instanceof Number && otherValue instanceof Number && thisValue.getClass() != otherValue.getClass()) {
+        Number n1 = (Number) thisValue;
+        Number n2 = (Number) otherValue;
+        // floating point values
+        if (thisValue instanceof Float || thisValue instanceof Double || otherValue instanceof Float || otherValue instanceof Double) {
+          // compare as floating point double
+          if (n1.doubleValue() == n2.doubleValue()) {
+            // same value check the next entry
+            continue;
+          }
+        }
+        if (thisValue instanceof Integer || thisValue instanceof Long || otherValue instanceof Integer || otherValue instanceof Long) {
+          // compare as integer long
+          if (n1.longValue() == n2.longValue()) {
+            // same value check the next entry
+            continue;
+          }
+        }
+      }
+      // special case for char sequences
+      if (thisValue instanceof CharSequence && otherValue instanceof CharSequence && thisValue.getClass() != otherValue.getClass()) {
+        CharSequence s1 = (CharSequence) thisValue;
+        CharSequence s2 = (CharSequence) otherValue;
+
+        if (Objects.equals(s1.toString(), s2.toString())) {
+          // same value check the next entry
+          continue;
+        }
+      }
+      // fallback to standard object equals checks
+      if (!Objects.equals(thisValue, otherValue)) {
         return false;
       }
     }
+    // all checks passed
     return true;
   }
 
@@ -848,7 +623,7 @@ private void fromBuffer(Buffer buf) {
     list = Json.CODEC.fromBuffer(buf, List.class);
   }
 
-  private class Iter implements Iterator<Object> {
+  private static class Iter implements Iterator<Object> {
 
     final Iterator<Object> listIter;
 
@@ -863,13 +638,7 @@ public boolean hasNext() {
 
     @Override
     public Object next() {
-      Object val = listIter.next();
-      if (val instanceof Map) {
-        val = new JsonObject((Map)val);
-      } else if (val instanceof List) {
-        val = new JsonArray((List)val);
-      }
-      return val;
+      return wrapJsonValue(listIter.next());
     }
 
     @Override
@@ -877,6 +646,4 @@ public void remove() {
       listIter.remove();
     }
   }
-
-
 }
diff --git a/src/main/java/io/vertx/core/json/JsonObject.java b/src/main/java/io/vertx/core/json/JsonObject.java
--- a/src/main/java/io/vertx/core/json/JsonObject.java
+++ b/src/main/java/io/vertx/core/json/JsonObject.java
@@ -14,26 +14,25 @@
 import io.vertx.core.buffer.Buffer;
 import io.vertx.core.shareddata.Shareable;
 import io.vertx.core.shareddata.impl.ClusterSerializable;
-import io.vertx.core.spi.json.JsonCodec;
 
-import java.math.BigDecimal;
 import java.nio.charset.StandardCharsets;
 import java.time.Instant;
 import java.util.*;
 import java.util.stream.Stream;
 import java.util.stream.StreamSupport;
 
+import static io.vertx.core.json.impl.JsonUtil.*;
 import static java.time.format.DateTimeFormatter.ISO_INSTANT;
 
 /**
  * A representation of a <a href="http://json.org/">JSON</a> object in Java.
- * <p>
+ *
  * Unlike some other languages Java does not have a native understanding of JSON. To enable JSON to be used easily
  * in Vert.x code we use this class to encapsulate the notion of a JSON object.
  *
  * The implementation adheres to the <a href="http://rfc-editor.org/rfc/rfc7493.txt">RFC-7493</a> to support Temporal
  * data types as well as binary data.
- * <p>
+ *
  * Please see the documentation for more information.
  *
  * @author <a href="http://tfox.org">Tim Fox</a>
@@ -45,7 +44,7 @@ public class JsonObject implements Iterable<Map.Entry<String, Object>>, ClusterS
   /**
    * Create an instance from a string of JSON
    *
-   * @param json  the string of JSON
+   * @param json the string of JSON
    */
   public JsonObject(String json) {
     if (json == null) {
@@ -67,7 +66,7 @@ public JsonObject() {
   /**
    * Create an instance from a Map. The Map is not copied.
    *
-   * @param map  the map to create the instance from.
+   * @param map the map to create the instance from.
    */
   public JsonObject(Map<String, Object> map) {
     if (map == null) {
@@ -79,7 +78,7 @@ public JsonObject(Map<String, Object> map) {
   /**
    * Create an instance from a buffer.
    *
-   * @param buf  the buffer to create the instance from.
+   * @param buf the buffer to create the instance from.
    */
   public JsonObject(Buffer buf) {
     if (buf == null) {
@@ -95,12 +94,10 @@ public JsonObject(Buffer buf) {
    * Create a JsonObject from the fields of a Java object.
    * Faster than calling `new JsonObject(Json.encode(obj))`.
    * <p/
-   * Returns {@ode null} when {@code obj} is {@code null}.
+   * Returns {@code null} when {@code obj} is {@code null}.
    *
-   * @param obj
-   *          The object to convert to a JsonObject.
-   * @throws IllegalArgumentException
-   *          if conversion fails due to an incompatible type.
+   * @param obj The object to convert to a JsonObject.
+   * @throws IllegalArgumentException if conversion fails due to an incompatible type.
    */
   @SuppressWarnings("unchecked")
   public static JsonObject mapFrom(Object obj) {
@@ -115,42 +112,55 @@ public static JsonObject mapFrom(Object obj) {
    * Instantiate a Java object from a JsonObject.
    * Faster than calling `Json.decodeValue(Json.encode(jsonObject), type)`.
    *
-   * @param type
-   *          The type to instantiate from the JsonObject.
-   * @throws IllegalArgumentException
-   *          if the type cannot be instantiated.
+   * @param type The type to instantiate from the JsonObject.
+   * @throws IllegalArgumentException if the type cannot be instantiated.
    */
   public <T> T mapTo(Class<T> type) {
     return Json.CODEC.fromValue(map, type);
   }
 
   /**
-   * Get the string value with the specified key
+   * Get the string value with the specified key, special cases are addressed for extended JSON types {@code Instant},
+   * {@code byte[]} and {@code Enum} which can be converted to String.
    *
-   * @param key  the key to return the value for
+   * @param key the key to return the value for
    * @return the value or null if no value for that key
    * @throws java.lang.ClassCastException if the value is not a String
    */
   public String getString(String key) {
     Objects.requireNonNull(key);
-    CharSequence cs = (CharSequence)map.get(key);
-    return cs == null ? null : cs.toString();
+    Object val = map.get(key);
+    if (val == null) {
+      return null;
+    }
+
+    if (val instanceof CharSequence) {
+      return val.toString();
+    } else if (val instanceof Instant) {
+      return ISO_INSTANT.format((Instant) val);
+    } else if (val instanceof byte[]) {
+      return BASE64_ENCODER.encodeToString((byte[]) val);
+    } else if (val instanceof Enum) {
+      return ((Enum) val).name();
+    }
+
+    throw new ClassCastException("class " + val.getClass().getName() + " cannot be cast to class java.lang.String");
   }
 
   /**
    * Get the Integer value with the specified key
    *
-   * @param key  the key to return the value for
+   * @param key the key to return the value for
    * @return the value or null if no value for that key
    * @throws java.lang.ClassCastException if the value is not an Integer
    */
   public Integer getInteger(String key) {
     Objects.requireNonNull(key);
-    Number number = (Number)map.get(key);
+    Number number = (Number) map.get(key);
     if (number == null) {
       return null;
     } else if (number instanceof Integer) {
-      return (Integer)number;  // Avoids unnecessary unbox/box
+      return (Integer) number;  // Avoids unnecessary unbox/box
     } else {
       return number.intValue();
     }
@@ -159,17 +169,17 @@ public Integer getInteger(String key) {
   /**
    * Get the Long value with the specified key
    *
-   * @param key  the key to return the value for
+   * @param key the key to return the value for
    * @return the value or null if no value for that key
    * @throws java.lang.ClassCastException if the value is not a Long
    */
   public Long getLong(String key) {
     Objects.requireNonNull(key);
-    Number number = (Number)map.get(key);
+    Number number = (Number) map.get(key);
     if (number == null) {
       return null;
     } else if (number instanceof Long) {
-      return (Long)number;  // Avoids unnecessary unbox/box
+      return (Long) number;  // Avoids unnecessary unbox/box
     } else {
       return number.longValue();
     }
@@ -178,17 +188,17 @@ public Long getLong(String key) {
   /**
    * Get the Double value with the specified key
    *
-   * @param key  the key to return the value for
+   * @param key the key to return the value for
    * @return the value or null if no value for that key
    * @throws java.lang.ClassCastException if the value is not a Double
    */
   public Double getDouble(String key) {
     Objects.requireNonNull(key);
-    Number number = (Number)map.get(key);
+    Number number = (Number) map.get(key);
     if (number == null) {
       return null;
     } else if (number instanceof Double) {
-      return (Double)number;  // Avoids unnecessary unbox/box
+      return (Double) number;  // Avoids unnecessary unbox/box
     } else {
       return number.doubleValue();
     }
@@ -197,17 +207,17 @@ public Double getDouble(String key) {
   /**
    * Get the Float value with the specified key
    *
-   * @param key  the key to return the value for
+   * @param key the key to return the value for
    * @return the value or null if no value for that key
    * @throws java.lang.ClassCastException if the value is not a Float
    */
   public Float getFloat(String key) {
     Objects.requireNonNull(key);
-    Number number = (Number)map.get(key);
+    Number number = (Number) map.get(key);
     if (number == null) {
       return null;
     } else if (number instanceof Float) {
-      return (Float)number;  // Avoids unnecessary unbox/box
+      return (Float) number;  // Avoids unnecessary unbox/box
     } else {
       return number.floatValue();
     }
@@ -216,19 +226,19 @@ public Float getFloat(String key) {
   /**
    * Get the Boolean value with the specified key
    *
-   * @param key  the key to return the value for
+   * @param key the key to return the value for
    * @return the value or null if no value for that key
    * @throws java.lang.ClassCastException if the value is not a Boolean
    */
   public Boolean getBoolean(String key) {
     Objects.requireNonNull(key);
-    return (Boolean)map.get(key);
+    return (Boolean) map.get(key);
   }
 
   /**
    * Get the JsonObject value with the specified key
    *
-   * @param key  the key to return the value for
+   * @param key the key to return the value for
    * @return the value or null if no value for that key
    * @throws java.lang.ClassCastException if the value is not a JsonObject
    */
@@ -236,15 +246,15 @@ public JsonObject getJsonObject(String key) {
     Objects.requireNonNull(key);
     Object val = map.get(key);
     if (val instanceof Map) {
-      val = new JsonObject((Map)val);
+      val = new JsonObject((Map) val);
     }
-    return (JsonObject)val;
+    return (JsonObject) val;
   }
 
   /**
    * Get the JsonArray value with the specified key
    *
-   * @param key  the key to return the value for
+   * @param key the key to return the value for
    * @return the value or null if no value for that key
    * @throws java.lang.ClassCastException if the value is not a JsonArray
    */
@@ -252,252 +262,266 @@ public JsonArray getJsonArray(String key) {
     Objects.requireNonNull(key);
     Object val = map.get(key);
     if (val instanceof List) {
-      val = new JsonArray((List)val);
+      val = new JsonArray((List) val);
     }
-    return (JsonArray)val;
+    return (JsonArray) val;
   }
 
   /**
    * Get the binary value with the specified key.
-   * <p>
+   *
    * JSON itself has no notion of a binary, this extension complies to the RFC-7493, so this method assumes there is a
    * String value with the key and it contains a Base64 encoded binary, which it decodes if found and returns.
-   * <p>
-   * This method should be used in conjunction with {@link #put(String, byte[])}
    *
-   * @param key  the key to return the value for
+   * @param key the key to return the value for
    * @return the value or null if no value for that key
-   * @throws java.lang.ClassCastException if the value is not a String
+   * @throws java.lang.ClassCastException       if the value is not a String
    * @throws java.lang.IllegalArgumentException if the String value is not a legal Base64 encoded value
    */
   public byte[] getBinary(String key) {
     Objects.requireNonNull(key);
-    String encoded = (String) map.get(key);
-    return encoded == null ? null : Base64.getDecoder().decode(encoded);
+    Object val = map.get(key);
+    // no-op
+    if (val == null) {
+      return null;
+    }
+    // no-op if value is already an byte[]
+    if (val instanceof byte[]) {
+      return (byte[]) val;
+    }
+    // assume that the value is in String format as per RFC
+    String encoded = (String) val;
+    // parse to proper type
+    return BASE64_DECODER.decode(encoded);
   }
 
   /**
    * Get the instant value with the specified key.
-   * <p>
+   *
    * JSON itself has no notion of a temporal types, this extension complies to the RFC-7493, so this method assumes
    * there is a String value with the key and it contains an ISO 8601 encoded date and time format
    * such as "2017-04-03T10:25:41Z", which it decodes if found and returns.
-   * <p>
-   * This method should be used in conjunction with {@link #put(String, java.time.Instant)}
    *
-   * @param key  the key to return the value for
+   * @param key the key to return the value for
    * @return the value or null if no value for that key
-   * @throws java.lang.ClassCastException if the value is not a String
+   * @throws java.lang.ClassCastException            if the value is not a String
    * @throws java.time.format.DateTimeParseException if the String value is not a legal ISO 8601 encoded value
    */
   public Instant getInstant(String key) {
     Objects.requireNonNull(key);
-    String encoded = (String) map.get(key);
-    return encoded == null ? null : Instant.from(ISO_INSTANT.parse(encoded));
+    Object val = map.get(key);
+    // no-op
+    if (val == null) {
+      return null;
+    }
+    // no-op if value is already an Instant
+    if (val instanceof Instant) {
+      return (Instant) val;
+    }
+    // assume that the value is in String format as per RFC
+    String encoded = (String) val;
+    // parse to proper type
+    return Instant.from(ISO_INSTANT.parse(encoded));
   }
 
   /**
-   * Get the value with the specified key, as an Object
-   * @param key  the key to lookup
+   * Get the value with the specified key, as an Object with types respecting the limitations of JSON.
+   * <ul>
+   *   <li>{@code Map} will be wrapped to {@code JsonObject}</li>
+   *   <li>{@code List} will be wrapped to {@code JsonArray}</li>
+   *   <li>{@code Instant} will be converted to {@code String}</li>
+   *   <li>{@code byte[]} will be converted to {@code String}</li>
+   *   <li>{@code Enum} will be converted to {@code String}</li>
+   * </ul>
+   *
+   * @param key the key to lookup
    * @return the value
    */
   public Object getValue(String key) {
     Objects.requireNonNull(key);
-    Object val = map.get(key);
-    if (val instanceof Map) {
-      val = new JsonObject((Map)val);
-    } else if (val instanceof List) {
-      val = new JsonArray((List)val);
-    }
-    return val;
+    return wrapJsonValue(map.get(key));
   }
 
   /**
    * Like {@link #getString(String)} but specifying a default value to return if there is no entry.
    *
-   * @param key  the key to lookup
-   * @param def  the default value to use if the entry is not present
+   * @param key the key to lookup
+   * @param def the default value to use if the entry is not present
    * @return the value or {@code def} if no entry present
    */
   public String getString(String key, String def) {
     Objects.requireNonNull(key);
-    CharSequence cs = (CharSequence)map.get(key);
-    return cs != null || map.containsKey(key) ? cs == null ? null : cs.toString() : def;
+    if (map.containsKey(key)) {
+      return getString(key);
+    } else {
+      return def;
+    }
   }
 
   /**
    * Like {@link #getInteger(String)} but specifying a default value to return if there is no entry.
    *
-   * @param key  the key to lookup
-   * @param def  the default value to use if the entry is not present
+   * @param key the key to lookup
+   * @param def the default value to use if the entry is not present
    * @return the value or {@code def} if no entry present
    */
   public Integer getInteger(String key, Integer def) {
     Objects.requireNonNull(key);
-    Number val = (Number)map.get(key);
-    if (val == null) {
-      if (map.containsKey(key)) {
-        return null;
-      } else {
-        return def;
-      }
-    } else if (val instanceof Integer) {
-      return (Integer)val;  // Avoids unnecessary unbox/box
+    if (map.containsKey(key)) {
+      return getInteger(key);
     } else {
-      return val.intValue();
+      return def;
     }
   }
 
   /**
    * Like {@link #getLong(String)} but specifying a default value to return if there is no entry.
    *
-   * @param key  the key to lookup
-   * @param def  the default value to use if the entry is not present
+   * @param key the key to lookup
+   * @param def the default value to use if the entry is not present
    * @return the value or {@code def} if no entry present
    */
   public Long getLong(String key, Long def) {
     Objects.requireNonNull(key);
-    Number val = (Number)map.get(key);
-    if (val == null) {
-      if (map.containsKey(key)) {
-        return null;
-      } else {
-        return def;
-      }
-    } else if (val instanceof Long) {
-      return (Long)val;  // Avoids unnecessary unbox/box
+    if (map.containsKey(key)) {
+      return getLong(key);
     } else {
-      return val.longValue();
+      return def;
     }
   }
 
   /**
    * Like {@link #getDouble(String)} but specifying a default value to return if there is no entry.
    *
-   * @param key  the key to lookup
-   * @param def  the default value to use if the entry is not present
+   * @param key the key to lookup
+   * @param def the default value to use if the entry is not present
    * @return the value or {@code def} if no entry present
    */
   public Double getDouble(String key, Double def) {
     Objects.requireNonNull(key);
-    Number val = (Number)map.get(key);
-    if (val == null) {
-      if (map.containsKey(key)) {
-        return null;
-      } else {
-        return def;
-      }
-    } else if (val instanceof Double) {
-      return (Double)val;  // Avoids unnecessary unbox/box
+    if (map.containsKey(key)) {
+      return getDouble(key);
     } else {
-      return val.doubleValue();
+      return def;
     }
   }
 
   /**
    * Like {@link #getFloat(String)} but specifying a default value to return if there is no entry.
    *
-   * @param key  the key to lookup
-   * @param def  the default value to use if the entry is not present
+   * @param key the key to lookup
+   * @param def the default value to use if the entry is not present
    * @return the value or {@code def} if no entry present
    */
   public Float getFloat(String key, Float def) {
     Objects.requireNonNull(key);
-    Number val = (Number)map.get(key);
-    if (val == null) {
-      if (map.containsKey(key)) {
-        return null;
-      } else {
-        return def;
-      }
-    } else if (val instanceof Float) {
-      return (Float)val;  // Avoids unnecessary unbox/box
+    if (map.containsKey(key)) {
+      return getFloat(key);
     } else {
-      return val.floatValue();
+      return def;
     }
   }
 
   /**
    * Like {@link #getBoolean(String)} but specifying a default value to return if there is no entry.
    *
-   * @param key  the key to lookup
-   * @param def  the default value to use if the entry is not present
+   * @param key the key to lookup
+   * @param def the default value to use if the entry is not present
    * @return the value or {@code def} if no entry present
    */
   public Boolean getBoolean(String key, Boolean def) {
     Objects.requireNonNull(key);
-    Object val = map.get(key);
-    return val != null || map.containsKey(key) ? (Boolean)val : def;
+    if (map.containsKey(key)) {
+      return getBoolean(key);
+    } else {
+      return def;
+    }
   }
 
   /**
    * Like {@link #getJsonObject(String)} but specifying a default value to return if there is no entry.
    *
-   * @param key  the key to lookup
-   * @param def  the default value to use if the entry is not present
+   * @param key the key to lookup
+   * @param def the default value to use if the entry is not present
    * @return the value or {@code def} if no entry present
    */
   public JsonObject getJsonObject(String key, JsonObject def) {
-    JsonObject val = getJsonObject(key);
-    return val != null || map.containsKey(key) ? val : def;
+    Objects.requireNonNull(key);
+    if (map.containsKey(key)) {
+      return getJsonObject(key);
+    } else {
+      return def;
+    }
   }
 
   /**
    * Like {@link #getJsonArray(String)} but specifying a default value to return if there is no entry.
    *
-   * @param key  the key to lookup
-   * @param def  the default value to use if the entry is not present
+   * @param key the key to lookup
+   * @param def the default value to use if the entry is not present
    * @return the value or {@code def} if no entry present
    */
   public JsonArray getJsonArray(String key, JsonArray def) {
-    JsonArray val = getJsonArray(key);
-    return val != null || map.containsKey(key) ? val : def;
+    Objects.requireNonNull(key);
+    if (map.containsKey(key)) {
+      return getJsonArray(key);
+    } else {
+      return def;
+    }
   }
 
   /**
    * Like {@link #getBinary(String)} but specifying a default value to return if there is no entry.
    *
-   * @param key  the key to lookup
-   * @param def  the default value to use if the entry is not present
+   * @param key the key to lookup
+   * @param def the default value to use if the entry is not present
    * @return the value or {@code def} if no entry present
    */
   public byte[] getBinary(String key, byte[] def) {
     Objects.requireNonNull(key);
-    Object val = map.get(key);
-    return val != null || map.containsKey(key) ? (val == null ? null : Base64.getDecoder().decode((String)val)) : def;
+    if (map.containsKey(key)) {
+      return getBinary(key);
+    } else {
+      return def;
+    }
   }
 
   /**
    * Like {@link #getInstant(String)} but specifying a default value to return if there is no entry.
    *
-   * @param key  the key to lookup
-   * @param def  the default value to use if the entry is not present
+   * @param key the key to lookup
+   * @param def the default value to use if the entry is not present
    * @return the value or {@code def} if no entry present
    */
   public Instant getInstant(String key, Instant def) {
     Objects.requireNonNull(key);
-    Object val = map.get(key);
-    return val != null || map.containsKey(key) ?
-        (val == null ? null : Instant.from(ISO_INSTANT.parse((String) val))) : def;
+    if (map.containsKey(key)) {
+      return getInstant(key);
+    } else {
+      return def;
+    }
   }
 
   /**
    * Like {@link #getValue(String)} but specifying a default value to return if there is no entry.
    *
-   * @param key  the key to lookup
-   * @param def  the default value to use if the entry is not present
+   * @param key the key to lookup
+   * @param def the default value to use if the entry is not present
    * @return the value or {@code def} if no entry present
    */
   public Object getValue(String key, Object def) {
     Objects.requireNonNull(key);
-    Object val = getValue(key);
-    return val != null || map.containsKey(key) ? val : def;
+    if (map.containsKey(key)) {
+      return getValue(key);
+    } else {
+      return def;
+    }
   }
 
   /**
    * Does the JSON object contain the specified key?
    *
-   * @param key  the key
+   * @param key the key
    * @return true if it contains the key, false if not.
    */
   public boolean containsKey(String key) {
@@ -514,113 +538,6 @@ public Set<String> fieldNames() {
     return map.keySet();
   }
 
-  /**
-   * Put an Enum into the JSON object with the specified key.
-   * <p>
-   * JSON has no concept of encoding Enums, so the Enum will be converted to a String using the {@link java.lang.Enum#name()}
-   * method and the value put as a String.
-   *
-   * @param key  the key
-   * @param value  the value
-   * @return  a reference to this, so the API can be used fluently
-   */
-  public JsonObject put(String key, Enum value) {
-    Objects.requireNonNull(key);
-    map.put(key, value == null ? null : value.name());
-    return this;
-  }
-
-  /**
-   * Put an CharSequence into the JSON object with the specified key.
-   *
-   * @param key  the key
-   * @param value  the value
-   * @return  a reference to this, so the API can be used fluently
-   */
-  public JsonObject put(String key, CharSequence value) {
-    Objects.requireNonNull(key);
-    map.put(key, value == null ? null : value.toString());
-    return this;
-  }
-
-  /**
-   * Put a String into the JSON object with the specified key.
-   *
-   * @param key  the key
-   * @param value  the value
-   * @return  a reference to this, so the API can be used fluently
-   */
-  public JsonObject put(String key, String value) {
-    Objects.requireNonNull(key);
-    map.put(key, value);
-    return this;
-  }
-
-  /**
-   * Put an Integer into the JSON object with the specified key.
-   *
-   * @param key  the key
-   * @param value  the value
-   * @return  a reference to this, so the API can be used fluently
-   */
-  public JsonObject put(String key, Integer value) {
-    Objects.requireNonNull(key);
-    map.put(key, value);
-    return this;
-  }
-
-  /**
-   * Put a Long into the JSON object with the specified key.
-   *
-   * @param key  the key
-   * @param value  the value
-   * @return  a reference to this, so the API can be used fluently
-   */
-  public JsonObject put(String key, Long value) {
-    Objects.requireNonNull(key);
-    map.put(key, value);
-    return this;
-  }
-
-  /**
-   * Put a Double into the JSON object with the specified key.
-   *
-   * @param key  the key
-   * @param value  the value
-   * @return  a reference to this, so the API can be used fluently
-   */
-  public JsonObject put(String key, Double value) {
-    Objects.requireNonNull(key);
-    map.put(key, value);
-    return this;
-  }
-
-  /**
-   * Put a Float into the JSON object with the specified key.
-   *
-   * @param key  the key
-   * @param value  the value
-   * @return  a reference to this, so the API can be used fluently
-   */
-  public JsonObject put(String key, Float value) {
-    Objects.requireNonNull(key);
-    map.put(key, value);
-    return this;
-  }
-
-  /**
-   * Put a Boolean into the JSON object with the specified key.
-   *
-   * @param key  the key
-   * @param value  the value
-   * @return  a reference to this, so the API can be used fluently
-   */
-  public JsonObject put(String key, Boolean value) {
-    Objects.requireNonNull(key);
-    map.put(key, value);
-    return this;
-  }
-
   /**
    * Put a null value into the JSON object with the specified key.
    *
@@ -633,73 +550,15 @@ public JsonObject putNull(String key) {
     return this;
   }
 
-  /**
-   * Put another JSON object into the JSON object with the specified key.
-   *
-   * @param key  the key
-   * @param value  the value
-   * @return  a reference to this, so the API can be used fluently
-   */
-  public JsonObject put(String key, JsonObject value) {
-    Objects.requireNonNull(key);
-    map.put(key, value);
-    return this;
-  }
-
-  /**
-   * Put a JSON array into the JSON object with the specified key.
-   *
-   * @param key  the key
-   * @param value  the value
-   * @return  a reference to this, so the API can be used fluently
-   */
-  public JsonObject put(String key, JsonArray value) {
-    Objects.requireNonNull(key);
-    map.put(key, value);
-    return this;
-  }
-
-  /**
-   * Put a byte[] into the JSON object with the specified key.
-   * <p>
-   * JSON extension RFC7493, binary will first be Base64 encoded before being put as a String.
-   *
-   * @param key  the key
-   * @param value  the value
-   * @return  a reference to this, so the API can be used fluently
-   */
-  public JsonObject put(String key, byte[] value) {
-    Objects.requireNonNull(key);
-    map.put(key, value == null ? null : Base64.getEncoder().encodeToString(value));
-    return this;
-  }
-
-  /**
-   * Put a Instant into the JSON object with the specified key.
-   * <p>
-   * JSON extension RFC7493, instant will first be encoded to ISO 8601 date and time
-   * String such as "2017-04-03T10:25:41Z".
-   *
-   * @param key  the key
-   * @param value  the value
-   * @return  a reference to this, so the API can be used fluently
-   */
-  public JsonObject put(String key, Instant value) {
-    Objects.requireNonNull(key);
-    map.put(key, value == null ? null : ISO_INSTANT.format(value));
-    return this;
-  }
-
   /**
    * Put an Object into the JSON object with the specified key.
    *
-   * @param key  the key
-   * @param value  the value
-   * @return  a reference to this, so the API can be used fluently
+   * @param key   the key
+   * @param value the value
+   * @return a reference to this, so the API can be used fluently
    */
   public JsonObject put(String key, Object value) {
     Objects.requireNonNull(key);
-    value = checkAndCopy(value, false);
     map.put(key, value);
     return this;
   }
@@ -707,19 +566,21 @@ public JsonObject put(String key, Object value) {
   /**
    * Remove an entry from this object.
    *
-   * @param key  the key
+   * @param key the key
    * @return the value that was removed, or null if none
    */
   public Object remove(String key) {
-    return map.remove(key);
+    Objects.requireNonNull(key);
+    return wrapJsonValue(map.remove(key));
   }
 
   /**
    * Merge in another JSON object.
-   * <p>
+   *
    * This is the equivalent of putting all the entries of the other JSON object into this object. This is not a deep
    * merge, entries containing (sub) JSON objects will be replaced entirely.
-   * @param other  the other JSON object
+   *
+   * @param other the other JSON object
    * @return a reference to this, so the API can be used fluently
    */
   public JsonObject mergeIn(JsonObject other) {
@@ -730,8 +591,9 @@ public JsonObject mergeIn(JsonObject other) {
    * Merge in another JSON object.
    * A deep merge (recursive) matches (sub) JSON objects in the existing tree and replaces all
    * matching entries. JsonArrays are treated like any other entry, i.e. replaced entirely.
+   *
    * @param other the other JSON object
-   * @param deep if true, a deep merge is performed
+   * @param deep  if true, a deep merge is performed
    * @return a reference to this, so the API can be used fluently
    */
   public JsonObject mergeIn(JsonObject other, boolean deep) {
@@ -742,6 +604,7 @@ public JsonObject mergeIn(JsonObject other, boolean deep) {
    * Merge in another JSON object.
    * The merge is deep (recursive) to the specified level. If depth is 0, no merge is performed,
    * if depth is greater than the depth of one of the objects, a full deep merge is performed.
+   *
    * @param other the other JSON object
    * @param depth depth of merge
    * @return a reference to this, so the API can be used fluently
@@ -755,19 +618,19 @@ public JsonObject mergeIn(JsonObject other, int depth) {
       map.putAll(other.map);
       return this;
     }
-    for (Map.Entry<String, Object> e: other.map.entrySet()) {
-      if (e.getValue() == null){
+    for (Map.Entry<String, Object> e : other.map.entrySet()) {
+      if (e.getValue() == null) {
         map.put(e.getKey(), null);
       } else {
         map.merge(e.getKey(), e.getValue(), (oldVal, newVal) -> {
           if (oldVal instanceof Map) {
-            oldVal = new JsonObject((Map)oldVal);
+            oldVal = new JsonObject((Map) oldVal);
           }
           if (newVal instanceof Map) {
-            newVal = new JsonObject((Map)newVal);
+            newVal = new JsonObject((Map) newVal);
           }
           if (oldVal instanceof JsonObject && newVal instanceof JsonObject) {
-            return ((JsonObject) oldVal).mergeIn((JsonObject)newVal, depth - 1);
+            return ((JsonObject) oldVal).mergeIn((JsonObject) newVal, depth - 1);
           }
           return newVal;
         });
@@ -817,9 +680,8 @@ public JsonObject copy() {
     } else {
       copiedMap = new HashMap<>(map.size());
     }
-    for (Map.Entry<String, Object> entry: map.entrySet()) {
-      Object val = entry.getValue();
-      val = checkAndCopy(val, true);
+    for (Map.Entry<String, Object> entry : map.entrySet()) {
+      Object val = checkAndCopy(entry.getValue());
       copiedMap.put(entry.getKey(), val);
     }
     return new JsonObject(copiedMap);
@@ -857,6 +719,7 @@ public Iterator<Map.Entry<String, Object>> iterator() {
 
   /**
    * Get the number of entries in the JSON object
+   *
    * @return the number of entries
    */
   public int size() {
@@ -888,61 +751,69 @@ public String toString() {
 
   @Override
   public boolean equals(Object o) {
+    // null check
+    if (o == null)
+      return false;
+    // self check
     if (this == o)
       return true;
-    if (o == null || getClass() != o.getClass())
+    // type check and cast
+    if (getClass() != o.getClass())
       return false;
-    return objectEquals(map, o);
-  }
 
-  private static boolean objectEquals(Map<?, ?> m1, Object o2) {
-    Map<?, ?> m2;
-    if (o2 instanceof JsonObject) {
-      m2 = ((JsonObject) o2).map;
-    } else if (o2 instanceof Map<?, ?>) {
-      m2 = (Map<?, ?>) o2;
-    } else {
-      return false;
-    }
-    if (!m1.keySet().equals(m2.keySet())) {
+    JsonObject other = (JsonObject) o;
+    // size check
+    if (this.size() != other.size())
       return false;
-    }
-    for (Map.Entry<?, ?> entry : m1.entrySet()) {
-      Object val1 = entry.getValue();
-      Object val2 = m2.get(entry.getKey());
-      if (val1 == null ? val2 != null : !equals(val1, val2)) {
+    // value comparison
+    for (String key : map.keySet()) {
+      if (!other.containsKey(key)) {
         return false;
       }
-    }
-    return true;
-  }
 
-  static boolean equals(Object o1, Object o2) {
-    if (o1 == o2) {
-      return true;
-    }
-    if (o1 instanceof JsonObject) {
-      return objectEquals(((JsonObject) o1).map, o2);
-    }
-    if (o1 instanceof Map<?, ?>) {
-      return objectEquals((Map<?, ?>) o1, o2);
-    }
-    if (o1 instanceof JsonArray) {
-      return JsonArray.arrayEquals(((JsonArray) o1).getList(), o2);
-    }
-    if (o1 instanceof List<?>) {
-      return JsonArray.arrayEquals((List<?>) o1, o2);
-    }
-    if (o1 instanceof Number && o2 instanceof Number && o1.getClass() != o2.getClass()) {
-      Number n1 = (Number) o1;
-      Number n2 = (Number) o2;
-      if (o1 instanceof Float || o1 instanceof Double || o2 instanceof Float || o2 instanceof Double) {
-        return n1.doubleValue() == n2.doubleValue();
-      } else {
-        return n1.longValue() == n2.longValue();
+      Object thisValue = this.getValue(key);
+      Object otherValue = other.getValue(key);
+      // identity check
+      if (thisValue == otherValue) {
+        continue;
+      }
+      // special case for numbers
+      if (thisValue instanceof Number && otherValue instanceof Number && thisValue.getClass() != otherValue.getClass()) {
+        Number n1 = (Number) thisValue;
+        Number n2 = (Number) otherValue;
+        // floating point values
+        if (thisValue instanceof Float || thisValue instanceof Double || otherValue instanceof Float || otherValue instanceof Double) {
+          // compare as floating point double
+          if (n1.doubleValue() == n2.doubleValue()) {
+            // same value check the next entry
+            continue;
+          }
+        }
+        if (thisValue instanceof Integer || thisValue instanceof Long || otherValue instanceof Integer || otherValue instanceof Long) {
+          // compare as integer long
+          if (n1.longValue() == n2.longValue()) {
+            // same value check the next entry
+            continue;
+          }
+        }
+      }
+      // special case for char sequences
+      if (thisValue instanceof CharSequence && otherValue instanceof CharSequence && thisValue.getClass() != otherValue.getClass()) {
+        CharSequence s1 = (CharSequence) thisValue;
+        CharSequence s2 = (CharSequence) otherValue;
+
+        if (Objects.equals(s1.toString(), s2.toString())) {
+          // same value check the next entry
+          continue;
+        }
+      }
+      // fallback to standard object equals checks
+      if (!Objects.equals(thisValue, otherValue)) {
+        return false;
       }
     }
-    return o1.equals(o2);
+    // all checks passed
+    return true;
   }
 
   @Override
@@ -975,7 +846,7 @@ private void fromBuffer(Buffer buf) {
     map = Json.CODEC.fromBuffer(buf, Map.class);
   }
 
-  private class Iter implements Iterator<Map.Entry<String, Object>> {
+  private static class Iter implements Iterator<Map.Entry<String, Object>> {
 
     final Iterator<Map.Entry<String, Object>> mapIter;
 
@@ -990,12 +861,15 @@ public boolean hasNext() {
 
     @Override
     public Map.Entry<String, Object> next() {
-      Map.Entry<String, Object> entry = mapIter.next();
-      if (entry.getValue() instanceof Map) {
-        return new Entry(entry.getKey(), new JsonObject((Map)entry.getValue()));
-      } else if (entry.getValue() instanceof List) {
-        return new Entry(entry.getKey(), new JsonArray((List) entry.getValue()));
+      final Map.Entry<String, Object> entry = mapIter.next();
+      final Object val = entry.getValue();
+      // perform wrapping
+      final Object wrapped = wrapJsonValue(val);
+
+      if (val != wrapped) {
+        return new Entry(entry.getKey(), wrapped);
       }
+
       return entry;
     }
 
@@ -1030,50 +904,6 @@ public Object setValue(Object value) {
     }
   }
 
-  @SuppressWarnings("unchecked")
-  static Object checkAndCopy(Object val, boolean copy) {
-    if (val == null) {
-      // OK
-    } else if (val instanceof Number && !(val instanceof BigDecimal)) {
-      // OK
-    } else if (val instanceof Boolean) {
-      // OK
-    } else if (val instanceof String) {
-      // OK
-    } else if (val instanceof Character) {
-      // OK
-    } else if (val instanceof CharSequence) {
-      val = val.toString();
-    } else if (val instanceof JsonObject) {
-      if (copy) {
-        val = ((JsonObject) val).copy();
-      }
-    } else if (val instanceof JsonArray) {
-      if (copy) {
-        val = ((JsonArray) val).copy();
-      }
-    } else if (val instanceof Map) {
-      if (copy) {
-        val = (new JsonObject((Map)val)).copy();
-      } else {
-        val = new JsonObject((Map)val);
-      }
-    } else if (val instanceof List) {
-      if (copy) {
-        val = (new JsonArray((List)val)).copy();
-      } else {
-        val = new JsonArray((List)val);
-      }
-    } else if (val instanceof byte[]) {
-      val = Base64.getEncoder().encodeToString((byte[])val);
-    } else if (val instanceof Instant) {
-      val = ISO_INSTANT.format((Instant) val);
-    } else {
-      throw new IllegalStateException("Illegal type in JsonObject: " + val.getClass());
-    }
-    return val;
-  }
-
   static <T> Stream<T> asStream(Iterator<T> sourceIterator) {
     Iterable<T> iterable = () -> sourceIterator;
     return StreamSupport.stream(iterable.spliterator(), false);
diff --git a/src/main/java/io/vertx/core/json/impl/JsonUtil.java b/src/main/java/io/vertx/core/json/impl/JsonUtil.java
new file mode 100644
--- /dev/null
+++ b/src/main/java/io/vertx/core/json/impl/JsonUtil.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
+ * which is available at https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+ */
+package io.vertx.core.json.impl;
+
+import io.vertx.core.json.JsonArray;
+import io.vertx.core.json.JsonObject;
+
+import java.math.BigDecimal;
+import java.time.Instant;
+import java.util.Base64;
+import java.util.List;
+import java.util.Map;
+
+import static java.time.format.DateTimeFormatter.ISO_INSTANT;
+
+/**
+ * Implementation utilities (details) affecting the way JSON objects are wrapped.
+ */
+public final class JsonUtil {
+
+  public static final Base64.Encoder BASE64_ENCODER;
+  public static final Base64.Decoder BASE64_DECODER;
+
+  static {
+    /*
+     * Vert.x 3.x Json supports RFC-7493, however the JSON encoder/decoder format was incorrect.
+     * Users who might need to interop with Vert.x 3.x applications should set the system property
+     * {@code vertx.json.base64} to {@code legacy}.
+     */
+    if ("legacy".equalsIgnoreCase(System.getProperty("vertx.json.base64"))) {
+      BASE64_ENCODER = Base64.getEncoder();
+      BASE64_DECODER = Base64.getDecoder();
+    } else {
+      BASE64_ENCODER = Base64.getUrlEncoder().withoutPadding();
+      BASE64_DECODER = Base64.getUrlDecoder();
+    }
+  }
+
+  /**
+   * Wraps well known java types to adhere to the Json expected types.
+   * <ul>
+   *   <li>{@code Map} will be wrapped to {@code JsonObject}</li>
+   *   <li>{@code List} will be wrapped to {@code JsonArray}</li>
+   *   <li>{@code Instant} will be converted to iso date {@code String}</li>
+   *   <li>{@code byte[]} will be converted to base64 {@code String}</li>
+   *   <li>{@code Enum} will be converted to enum name {@code String}</li>
+   * </ul>
+   *
+   * @param val java type
+   * @return wrapped type or {@code val} if not applicable.
+   */
+  public static Object wrapJsonValue(Object val) {
+    if (val == null) {
+      return null;
+    }
+
+    // perform wrapping
+    if (val instanceof Map) {
+      val = new JsonObject((Map) val);
+    } else if (val instanceof List) {
+      val = new JsonArray((List) val);
+    } else if (val instanceof Instant) {
+      val = ISO_INSTANT.format((Instant) val);
+    } else if (val instanceof byte[]) {
+      val = BASE64_ENCODER.encodeToString((byte[]) val);
+    } else if (val instanceof Enum) {
+      val = ((Enum) val).name();
+    }
+
+    return val;
+  }
+
+  @SuppressWarnings("unchecked")
+  public static Object checkAndCopy(Object val) {
+    if (val == null) {
+      // OK
+    } else if (val instanceof Number && !(val instanceof BigDecimal)) {
+      // OK
+    } else if (val instanceof Boolean) {
+      // OK
+    } else if (val instanceof String) {
+      // OK
+    } else if (val instanceof Character) {
+      // OK
+    } else if (val instanceof CharSequence) {
+      val = val.toString();
+    } else if (val instanceof JsonObject) {
+      val = ((JsonObject) val).copy();
+    } else if (val instanceof JsonArray) {
+      val = ((JsonArray) val).copy();
+    } else if (val instanceof Map) {
+      val = (new JsonObject((Map) val)).copy();
+    } else if (val instanceof List) {
+      val = (new JsonArray((List) val)).copy();
+    } else if (val instanceof byte[]) {
+      // OK
+    } else if (val instanceof Instant) {
+      // OK
+    } else if (val instanceof Enum) {
+      // OK
+    } else {
+      throw new IllegalStateException("Illegal type in Json: " + val.getClass());
+    }
+    return val;
+  }
+}
diff --git a/src/main/java/io/vertx/core/json/jackson/ByteArrayDeserializer.java b/src/main/java/io/vertx/core/json/jackson/ByteArrayDeserializer.java
--- a/src/main/java/io/vertx/core/json/jackson/ByteArrayDeserializer.java
+++ b/src/main/java/io/vertx/core/json/jackson/ByteArrayDeserializer.java
@@ -18,7 +18,8 @@
 
 import java.io.IOException;
 import java.time.Instant;
-import java.util.Base64;
+
+import static io.vertx.core.json.impl.JsonUtil.BASE64_DECODER;
 
 class ByteArrayDeserializer extends JsonDeserializer<byte[]> {
 
@@ -26,7 +27,7 @@ class ByteArrayDeserializer extends JsonDeserializer<byte[]> {
   public byte[] deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
     String text = p.getText();
     try {
-      return Base64.getDecoder().decode(text);
+      return BASE64_DECODER.decode(text);
     } catch (IllegalArgumentException e) {
       throw new InvalidFormatException(p, "Expected a base64 encoded byte array", text, Instant.class);
     }
diff --git a/src/main/java/io/vertx/core/json/jackson/ByteArraySerializer.java b/src/main/java/io/vertx/core/json/jackson/ByteArraySerializer.java
--- a/src/main/java/io/vertx/core/json/jackson/ByteArraySerializer.java
+++ b/src/main/java/io/vertx/core/json/jackson/ByteArraySerializer.java
@@ -15,12 +15,13 @@
 import com.fasterxml.jackson.databind.SerializerProvider;
 
 import java.io.IOException;
-import java.util.Base64;
+
+import static io.vertx.core.json.impl.JsonUtil.BASE64_ENCODER;
 
 class ByteArraySerializer extends JsonSerializer<byte[]> {
 
   @Override
   public void serialize(byte[] value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
-    jgen.writeString(Base64.getEncoder().encodeToString(value));
+    jgen.writeString(BASE64_ENCODER.encodeToString(value));
   }
 }
diff --git a/src/main/java/io/vertx/core/json/jackson/JacksonCodec.java b/src/main/java/io/vertx/core/json/jackson/JacksonCodec.java
--- a/src/main/java/io/vertx/core/json/jackson/JacksonCodec.java
+++ b/src/main/java/io/vertx/core/json/jackson/JacksonCodec.java
@@ -37,11 +37,11 @@
 import java.lang.reflect.Type;
 import java.time.Instant;
 import java.util.ArrayList;
-import java.util.Base64;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 
+import static io.vertx.core.json.impl.JsonUtil.BASE64_ENCODER;
 import static java.time.format.DateTimeFormatter.ISO_INSTANT;
 
 /**
@@ -274,8 +274,8 @@ private static void encodeJson(Object json, JsonGenerator generator) throws Enco
           encodeJson(item, generator);
         }
         generator.writeEndArray();
-      } else if (json instanceof String) {
-        generator.writeString((String)json);
+      } else if (json instanceof CharSequence) {
+        generator.writeString(((CharSequence) json).toString());
       } else if (json instanceof Number) {
         if (json instanceof Short) {
           generator.writeNumber((Short) json);
@@ -295,7 +295,7 @@ private static void encodeJson(Object json, JsonGenerator generator) throws Enco
       } else if (json instanceof Instant) {
         generator.writeString((ISO_INSTANT.format((Instant)json)));
       } else if (json instanceof byte[]) {
-        generator.writeString(Base64.getEncoder().encodeToString((byte[]) json));
+        generator.writeString(BASE64_ENCODER.encodeToString((byte[]) json));
       } else if (json == null) {
         generator.writeNull();
       } else {
diff --git a/src/main/java/io/vertx/core/parsetools/impl/JsonEventImpl.java b/src/main/java/io/vertx/core/parsetools/impl/JsonEventImpl.java
--- a/src/main/java/io/vertx/core/parsetools/impl/JsonEventImpl.java
+++ b/src/main/java/io/vertx/core/parsetools/impl/JsonEventImpl.java
@@ -21,8 +21,8 @@
 import io.vertx.core.parsetools.JsonEventType;
 
 import java.time.Instant;
-import java.util.Base64;
 
+import static io.vertx.core.json.impl.JsonUtil.BASE64_DECODER;
 import static java.time.format.DateTimeFormatter.ISO_INSTANT;
 
 /**
@@ -167,7 +167,7 @@ public String stringValue() {
 
   @Override
   public Buffer binaryValue() {
-    return value != null ? Buffer.buffer(Base64.getDecoder().decode((String) value)) : null;
+    return value != null ? Buffer.buffer(BASE64_DECODER.decode((String) value)) : null;
   }
 
   @Override


Test patch:
diff --git a/src/test/java/io/vertx/core/ConversionHelperTest.java b/src/test/java/io/vertx/core/ConversionHelperTest.java
--- a/src/test/java/io/vertx/core/ConversionHelperTest.java
+++ b/src/test/java/io/vertx/core/ConversionHelperTest.java
@@ -21,12 +21,12 @@
 import java.math.BigInteger;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Base64;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
+import static io.vertx.core.json.impl.JsonUtil.BASE64_DECODER;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
@@ -106,7 +106,7 @@ public void testFromJsonObject() {
     assertEquals("the_string", map.get("string"));
     assertEquals(4, map.get("integer"));
     assertEquals(true, map.get("boolean"));
-    assertEquals("hello", new String(Base64.getDecoder().decode((String)map.get("binary"))));
+    assertEquals("hello", new String(BASE64_DECODER.decode((String)map.get("binary"))));
     assertEquals(Collections.singletonMap("nested", 4), map.get("object"));
     assertEquals(Arrays.asList(1, 2, 3), map.get("array"));
   }
@@ -125,7 +125,7 @@ public void testFromJsonArray() {
     assertEquals("the_string", map.get(0));
     assertEquals(4, map.get(1));
     assertEquals(true, map.get(2));
-    assertEquals("hello", new String(Base64.getDecoder().decode((String)map.get(3))));
+    assertEquals("hello", new String(BASE64_DECODER.decode((String)map.get(3))));
     assertEquals(Collections.singletonMap("nested", 4), map.get(4));
     assertEquals(Arrays.asList(1, 2, 3), map.get(5));
   }
diff --git a/src/test/java/io/vertx/core/json/JacksonDatabindTest.java b/src/test/java/io/vertx/core/json/JacksonDatabindTest.java
--- a/src/test/java/io/vertx/core/json/JacksonDatabindTest.java
+++ b/src/test/java/io/vertx/core/json/JacksonDatabindTest.java
@@ -24,6 +24,7 @@
 import java.time.Instant;
 import java.util.*;
 
+import static io.vertx.core.json.impl.JsonUtil.BASE64_ENCODER;
 import static java.time.format.DateTimeFormatter.ISO_INSTANT;
 
 /**
@@ -85,7 +86,7 @@ public void testNullInstantDecoding() {
   public void testBytesDecoding() {
     Pojo original = new Pojo();
     original.bytes = TestUtils.randomByteArray(12);
-    Pojo decoded = Json.decodeValue("{\"bytes\":\"" + Base64.getEncoder().encodeToString(original.bytes) + "\"}", Pojo.class);
+    Pojo decoded = Json.decodeValue("{\"bytes\":\"" + BASE64_ENCODER.encodeToString(original.bytes) + "\"}", Pojo.class);
     assertArrayEquals(original.bytes, decoded.bytes);
   }
 
diff --git a/src/test/java/io/vertx/core/json/JsonArrayTest.java b/src/test/java/io/vertx/core/json/JsonArrayTest.java
--- a/src/test/java/io/vertx/core/json/JsonArrayTest.java
+++ b/src/test/java/io/vertx/core/json/JsonArrayTest.java
@@ -21,6 +21,8 @@
 import java.util.*;
 import java.util.stream.Collectors;
 
+import static io.vertx.core.json.impl.JsonUtil.BASE64_DECODER;
+import static io.vertx.core.json.impl.JsonUtil.BASE64_ENCODER;
 import static java.time.format.DateTimeFormatter.ISO_INSTANT;
 import static org.junit.Assert.*;
 
@@ -233,8 +235,8 @@ public void testGetBinary() {
     byte[] bytes = TestUtils.randomByteArray(10);
     jsonArray.add(bytes);
     assertArrayEquals(bytes, jsonArray.getBinary(0));
-    assertEquals(Base64.getEncoder().encodeToString(bytes), jsonArray.getValue(0));
-    assertArrayEquals(bytes, Base64.getDecoder().decode(jsonArray.getString(0)));
+    assertEquals(BASE64_ENCODER.encodeToString(bytes), jsonArray.getValue(0));
+    assertArrayEquals(bytes, BASE64_DECODER.decode(jsonArray.getString(0)));
     try {
       jsonArray.getBinary(-1);
       fail();
@@ -381,7 +383,7 @@ public void testGetValue() {
     assertEquals(arr, jsonArray.getValue(8));
     byte[] bytes = TestUtils.randomByteArray(100);
     jsonArray.add(bytes);
-    assertEquals(Base64.getEncoder().encodeToString(bytes), jsonArray.getValue(9));
+    assertEquals(BASE64_ENCODER.encodeToString(bytes), jsonArray.getValue(9));
     Instant now = Instant.now();
     jsonArray.add(now);
     assertEquals(now, jsonArray.getInstant(10));
@@ -519,7 +521,7 @@ public void testAddBinary() {
     byte[] bytes = TestUtils.randomByteArray(10);
     assertSame(jsonArray, jsonArray.add(bytes));
     assertArrayEquals(bytes, jsonArray.getBinary(0));
-    assertEquals(Base64.getEncoder().encodeToString(bytes), jsonArray.getValue(0));
+    assertEquals(BASE64_ENCODER.encodeToString(bytes), jsonArray.getValue(0));
     jsonArray.add((byte[])null);
     assertNull(jsonArray.getValue(1));
     assertEquals(2, jsonArray.size());
@@ -559,28 +561,28 @@ public void testAddObject() {
     assertEquals(Double.valueOf(1.23d), jsonArray.getDouble(4));
     assertEquals(true, jsonArray.getBoolean(5));
     assertArrayEquals(bytes, jsonArray.getBinary(6));
-    assertEquals(Base64.getEncoder().encodeToString(bytes), jsonArray.getValue(6));
+    assertEquals(BASE64_ENCODER.encodeToString(bytes), jsonArray.getValue(6));
     assertEquals(now, jsonArray.getInstant(7));
     assertEquals(now.toString(), jsonArray.getValue(7));
     assertEquals(obj, jsonArray.getJsonObject(8));
     assertEquals(arr, jsonArray.getJsonArray(9));
     try {
       jsonArray.add(new SomeClass());
+      // OK (we can put anything, yet it should fail to encode if a codec is missing)
+    } catch (RuntimeException e) {
       fail();
-    } catch (IllegalStateException e) {
-      // OK
     }
     try {
       jsonArray.add(new BigDecimal(123));
+      // OK (we can put anything, yet it should fail to encode if a codec is missing)
+    } catch (RuntimeException e) {
       fail();
-    } catch (IllegalStateException e) {
-      // OK
     }
     try {
       jsonArray.add(new Date());
+      // OK (we can put anything, yet it should fail to encode if a codec is missing)
+    } catch (RuntimeException e) {
       fail();
-    } catch (IllegalStateException e) {
-      // OK
     }
 
   }
@@ -648,6 +650,23 @@ public void testRemoveByObject() {
     assertTrue(jsonArray.isEmpty());
   }
 
+  @Test
+  public void testRemoveByWrappedObject() {
+    JsonArray arr = new JsonArray("[1, 2, 3]");
+    jsonArray.add(arr);
+    assertEquals(1, jsonArray.size());
+    assertTrue(jsonArray.remove(arr));
+    assertEquals(0, jsonArray.size());
+    assertTrue(jsonArray.isEmpty());
+    // this is OK,
+    // now test using unwrapped objects
+    jsonArray.add(arr.getList());
+    assertEquals(1, jsonArray.size());
+    assertTrue(jsonArray.remove(arr));
+    assertEquals(0, jsonArray.size());
+    assertTrue(jsonArray.isEmpty());
+  }
+
   @Test
   public void testRemoveByPos() {
     jsonArray.add("wibble");
@@ -1137,7 +1156,7 @@ public void testSetBinary() {
     }
     jsonArray.add("bar");
     assertSame(jsonArray, jsonArray.set(0, bytes));
-    assertEquals(Base64.getEncoder().encodeToString(bytes), jsonArray.getValue(0));
+    assertEquals(BASE64_ENCODER.encodeToString(bytes), jsonArray.getValue(0));
     assertEquals(1, jsonArray.size());
   }
 
@@ -1161,21 +1180,21 @@ public void testSetObject() {
     jsonArray.add("bar");
     try {
       jsonArray.set(0, new SomeClass());
+      // OK (we can put anything, yet it should fail to encode if a codec is missing)
+    } catch (RuntimeException e) {
       fail();
-    } catch (IllegalStateException e) {
-      // OK
     }
     try {
       jsonArray.set(0, new BigDecimal(123));
+      // OK (we can put anything, yet it should fail to encode if a codec is missing)
+    } catch (RuntimeException e) {
       fail();
-    } catch (IllegalStateException e) {
-      // OK
     }
     try {
       jsonArray.set(0, new Date());
+      // OK (we can put anything, yet it should fail to encode if a codec is missing)
+    } catch (RuntimeException e) {
       fail();
-    } catch (IllegalStateException e) {
-      // OK
     }
   }
 
@@ -1192,4 +1211,43 @@ public void testSetNull() {
     assertNull(jsonArray.getString(0));
     assertEquals(1, jsonArray.size());
   }
+
+  @Test
+  public void testAddWithPos() {
+    JsonArray arr = new JsonArray()
+      .add(1)
+      .add(2)
+      .add(3);
+
+    assertEquals(3, arr.size());
+
+    assertEquals(1, arr.getValue(0));
+    assertEquals(2, arr.getValue(1));
+    assertEquals(3, arr.getValue(2));
+
+    // add some values by index
+    arr.add(3, 4);
+
+    // assert that the new length changed
+    assertEquals(4, arr.size());
+    // assert the value got added
+    assertEquals(4, arr.getValue(3));
+  }
+
+  @Test
+  public void testNoEncode() {
+    Instant now = Instant.now();
+    JsonArray json = new JsonArray();
+    // bypass any custom validation
+    json.getList().add(now);
+    assertEquals(now, json.getInstant(0));
+    assertSame(now, json.getInstant(0));
+
+    // same for byte[]
+    byte[] bytes = "bytes".getBytes();
+    // bypass any custom validation
+    json.getList().add(bytes);
+    assertEquals(bytes, json.getBinary(1));
+    assertSame(bytes, json.getBinary(1));
+  }
 }
diff --git a/src/test/java/io/vertx/core/json/JsonCodecTest.java b/src/test/java/io/vertx/core/json/JsonCodecTest.java
--- a/src/test/java/io/vertx/core/json/JsonCodecTest.java
+++ b/src/test/java/io/vertx/core/json/JsonCodecTest.java
@@ -25,12 +25,12 @@
 import java.nio.charset.StandardCharsets;
 import java.time.Instant;
 import java.util.Arrays;
-import java.util.Base64;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.Collection;
 
+import static io.vertx.core.json.impl.JsonUtil.BASE64_ENCODER;
 import static java.time.format.DateTimeFormatter.ISO_INSTANT;
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
@@ -81,7 +81,7 @@ public void testEncodeJsonObject() {
     jsonObject.putNull("mynull");
     jsonObject.put("myobj", new JsonObject().put("foo", "bar"));
     jsonObject.put("myarr", new JsonArray().add("foo").add(123));
-    String strBytes = Base64.getEncoder().encodeToString(bytes);
+    String strBytes = BASE64_ENCODER.encodeToString(bytes);
     String expected = "{\"mystr\":\"foo\",\"mycharsequence\":\"oob\",\"myint\":123,\"mylong\":1234,\"myfloat\":1.23,\"mydouble\":2.34,\"" +
       "myboolean\":true,\"mybinary\":\"" + strBytes + "\",\"myinstant\":\"" + ISO_INSTANT.format(now) + "\",\"mynull\":null,\"myobj\":{\"foo\":\"bar\"},\"myarr\":[\"foo\",123]}";
     String json = mapper.toString(jsonObject);
@@ -102,7 +102,7 @@ public void testEncodeJsonArray() {
     jsonArray.addNull();
     jsonArray.add(new JsonObject().put("foo", "bar"));
     jsonArray.add(new JsonArray().add("foo").add(123));
-    String strBytes = Base64.getEncoder().encodeToString(bytes);
+    String strBytes = BASE64_ENCODER.encodeToString(bytes);
     String expected = "[\"foo\",123,1234,1.23,2.34,true,\"" + strBytes + "\",null,{\"foo\":\"bar\"},[\"foo\",123]]";
     String json = mapper.toString(jsonArray);
     assertEquals(expected, json);
@@ -125,7 +125,7 @@ public void testEncodeJsonObjectToBuffer() {
     jsonObject.putNull("mynull");
     jsonObject.put("myobj", new JsonObject().put("foo", "bar"));
     jsonObject.put("myarr", new JsonArray().add("foo").add(123));
-    String strBytes = Base64.getEncoder().encodeToString(bytes);
+    String strBytes = BASE64_ENCODER.encodeToString(bytes);
 
     Buffer expected = Buffer.buffer("{\"mystr\":\"foo\",\"mycharsequence\":\"oob\",\"myint\":123,\"mylong\":1234,\"myfloat\":1.23,\"mydouble\":2.34,\"" +
       "myboolean\":true,\"mybinary\":\"" + strBytes + "\",\"myinstant\":\"" + ISO_INSTANT.format(now) + "\",\"mynull\":null,\"myobj\":{\"foo\":\"bar\"},\"myarr\":[\"foo\",123]}", "UTF-8");
@@ -148,7 +148,7 @@ public void testEncodeJsonArrayToBuffer() {
     jsonArray.addNull();
     jsonArray.add(new JsonObject().put("foo", "bar"));
     jsonArray.add(new JsonArray().add("foo").add(123));
-    String strBytes = Base64.getEncoder().encodeToString(bytes);
+    String strBytes = BASE64_ENCODER.encodeToString(bytes);
     Buffer expected = Buffer.buffer("[\"foo\",123,1234,1.23,2.34,true,\"" + strBytes + "\",null,{\"foo\":\"bar\"},[\"foo\",123]]", "UTF-8");
     Buffer json = mapper.toBuffer(jsonArray);
     assertArrayEquals(expected.getBytes(), json.getBytes());
@@ -170,7 +170,7 @@ public void testEncodeJsonObjectPrettily() {
     jsonObject.put("myinstant", now);
     jsonObject.put("myobj", new JsonObject().put("foo", "bar"));
     jsonObject.put("myarr", new JsonArray().add("foo").add(123));
-    String strBytes = Base64.getEncoder().encodeToString(bytes);
+    String strBytes = BASE64_ENCODER.encodeToString(bytes);
     String strInstant = ISO_INSTANT.format(now);
     String expected = "{" + Utils.LINE_SEPARATOR +
       "  \"mystr\" : \"foo\"," + Utils.LINE_SEPARATOR +
@@ -204,7 +204,7 @@ public void testEncodeJsonArrayPrettily() {
     jsonArray.addNull();
     jsonArray.add(new JsonObject().put("foo", "bar"));
     jsonArray.add(new JsonArray().add("foo").add(123));
-    String strBytes = Base64.getEncoder().encodeToString(bytes);
+    String strBytes = BASE64_ENCODER.encodeToString(bytes);
     String expected = "[ \"foo\", 123, 1234, 1.23, 2.34, true, \"" + strBytes + "\", null, {" + Utils.LINE_SEPARATOR +
       "  \"foo\" : \"bar\"" + Utils.LINE_SEPARATOR +
       "}, [ \"foo\", 123 ] ]";
@@ -215,7 +215,7 @@ public void testEncodeJsonArrayPrettily() {
   @Test
   public void testDecodeJsonObject() {
     byte[] bytes = TestUtils.randomByteArray(10);
-    String strBytes = Base64.getEncoder().encodeToString(bytes);
+    String strBytes = BASE64_ENCODER.encodeToString(bytes);
     Instant now = Instant.now();
     String strInstant = ISO_INSTANT.format(now);
     String json = "{\"mystr\":\"foo\",\"myint\":123,\"mylong\":1234,\"myfloat\":1.23,\"mydouble\":2.34,\"" +
@@ -229,7 +229,7 @@ public void testDecodeJsonObject() {
     assertEquals(Double.valueOf(2.34d), obj.getDouble("mydouble"));
     assertTrue(obj.getBoolean("myboolean"));
     assertArrayEquals(bytes, obj.getBinary("mybinary"));
-    assertEquals(Base64.getEncoder().encodeToString(bytes), obj.getValue("mybinary"));
+    assertEquals(BASE64_ENCODER.encodeToString(bytes), obj.getValue("mybinary"));
     assertEquals(now, obj.getInstant("myinstant"));
     assertEquals(now.toString(), obj.getValue("myinstant"));
     assertTrue(obj.containsKey("mynull"));
@@ -243,7 +243,7 @@ public void testDecodeJsonObject() {
   @Test
   public void testDecodeJsonArray() {
     byte[] bytes = TestUtils.randomByteArray(10);
-    String strBytes = Base64.getEncoder().encodeToString(bytes);
+    String strBytes = BASE64_ENCODER.encodeToString(bytes);
     Instant now = Instant.now();
     String strInstant = ISO_INSTANT.format(now);
     String json = "[\"foo\",123,1234,1.23,2.34,true,\"" + strBytes + "\",\"" + strInstant + "\",null,{\"foo\":\"bar\"},[\"foo\",123]]";
@@ -255,7 +255,7 @@ public void testDecodeJsonArray() {
     assertEquals(Double.valueOf(2.34d), arr.getDouble(4));
     assertEquals(true, arr.getBoolean(5));
     assertArrayEquals(bytes, arr.getBinary(6));
-    assertEquals(Base64.getEncoder().encodeToString(bytes), arr.getValue(6));
+    assertEquals(BASE64_ENCODER.encodeToString(bytes), arr.getValue(6));
     assertEquals(now, arr.getInstant(7));
     assertEquals(now.toString(), arr.getValue(7));
     assertTrue(arr.hasNull(8));
@@ -357,7 +357,7 @@ public void encodeCustomTypeBinary() {
     String json = mapper.toString(data);
     assertNotNull(json);
     // base64 encoded hello
-    assertEquals("\"aGVsbG8=\"", json);
+    assertEquals("\"aGVsbG8\"", json);
   }
 
   @Test
diff --git a/src/test/java/io/vertx/core/json/JsonObjectTest.java b/src/test/java/io/vertx/core/json/JsonObjectTest.java
--- a/src/test/java/io/vertx/core/json/JsonObjectTest.java
+++ b/src/test/java/io/vertx/core/json/JsonObjectTest.java
@@ -12,6 +12,7 @@
 package io.vertx.core.json;
 
 import io.vertx.core.buffer.Buffer;
+import io.vertx.core.http.HttpMethod;
 import io.vertx.test.core.TestUtils;
 import org.junit.Before;
 import org.junit.Test;
@@ -22,6 +23,8 @@
 import java.util.*;
 import java.util.stream.Collectors;
 
+import static io.vertx.core.json.impl.JsonUtil.BASE64_DECODER;
+import static io.vertx.core.json.impl.JsonUtil.BASE64_ENCODER;
 import static java.time.format.DateTimeFormatter.ISO_INSTANT;
 import static org.junit.Assert.*;
 
@@ -442,12 +445,12 @@ public void testGetBinary() {
     byte[] bytes = TestUtils.randomByteArray(100);
     jsonObject.put("foo", bytes);
     assertArrayEquals(bytes, jsonObject.getBinary("foo"));
-    assertEquals(Base64.getEncoder().encodeToString(bytes), jsonObject.getValue("foo"));
+    assertEquals(BASE64_ENCODER.encodeToString(bytes), jsonObject.getValue("foo"));
 
     // Can also get as string:
     String val = jsonObject.getString("foo");
     assertNotNull(val);
-    byte[] retrieved = Base64.getDecoder().decode(val);
+    byte[] retrieved = BASE64_DECODER.decode(val);
     assertTrue(TestUtils.byteArraysEqual(bytes, retrieved));
 
     jsonObject.put("foo", 123);
@@ -535,9 +538,9 @@ public void testGetBinaryDefault() {
     byte[] defBytes = TestUtils.randomByteArray(100);
     jsonObject.put("foo", bytes);
     assertArrayEquals(bytes, jsonObject.getBinary("foo", defBytes));
-    assertEquals(Base64.getEncoder().encodeToString(bytes), jsonObject.getValue("foo", Base64.getEncoder().encode(defBytes)));
+    assertEquals(BASE64_ENCODER.encodeToString(bytes), jsonObject.getValue("foo", BASE64_ENCODER.encode(defBytes)));
     assertArrayEquals(bytes, jsonObject.getBinary("foo", null));
-    assertEquals(Base64.getEncoder().encodeToString(bytes), jsonObject.getValue("foo", null));
+    assertEquals(BASE64_ENCODER.encodeToString(bytes), jsonObject.getValue("foo", null));
 
     jsonObject.put("foo", 123);
     try {
@@ -732,7 +735,7 @@ public void testGetValue() {
     assertEquals(arr, jsonObject.getValue("foo"));
     byte[] bytes = TestUtils.randomByteArray(100);
     jsonObject.put("foo", bytes);
-    assertTrue(TestUtils.byteArraysEqual(bytes, Base64.getDecoder().decode((String) jsonObject.getValue("foo"))));
+    assertTrue(TestUtils.byteArraysEqual(bytes, BASE64_DECODER.decode((String) jsonObject.getValue("foo"))));
     jsonObject.putNull("foo");
     assertNull(jsonObject.getValue("foo"));
     assertNull(jsonObject.getValue("absent"));
@@ -787,8 +790,8 @@ public void testGetValueDefault() {
     assertEquals(arr, jsonObject.getValue("foo", null));
     byte[] bytes = TestUtils.randomByteArray(100);
     jsonObject.put("foo", bytes);
-    assertTrue(TestUtils.byteArraysEqual(bytes, Base64.getDecoder().decode((String) jsonObject.getValue("foo", "blah"))));
-    assertTrue(TestUtils.byteArraysEqual(bytes, Base64.getDecoder().decode((String)jsonObject.getValue("foo", null))));
+    assertTrue(TestUtils.byteArraysEqual(bytes, BASE64_DECODER.decode((String) jsonObject.getValue("foo", "blah"))));
+    assertTrue(TestUtils.byteArraysEqual(bytes, BASE64_DECODER.decode((String)jsonObject.getValue("foo", null))));
     jsonObject.putNull("foo");
     assertNull(jsonObject.getValue("foo", "blah"));
     assertNull(jsonObject.getValue("foo", null));
@@ -1044,15 +1047,15 @@ public void testPutBinary() {
 
     assertSame(jsonObject, jsonObject.put("foo", bin1));
     assertArrayEquals(bin1, jsonObject.getBinary("foo"));
-    assertEquals(Base64.getEncoder().encodeToString(bin1), jsonObject.getValue("foo"));
+    assertEquals(BASE64_ENCODER.encodeToString(bin1), jsonObject.getValue("foo"));
     jsonObject.put("quux", bin2);
     assertArrayEquals(bin2, jsonObject.getBinary("quux"));
-    assertEquals(Base64.getEncoder().encodeToString(bin2), jsonObject.getValue("quux"));
+    assertEquals(BASE64_ENCODER.encodeToString(bin2), jsonObject.getValue("quux"));
     assertArrayEquals(bin1, jsonObject.getBinary("foo"));
-    assertEquals(Base64.getEncoder().encodeToString(bin1), jsonObject.getValue("foo"));
+    assertEquals(BASE64_ENCODER.encodeToString(bin1), jsonObject.getValue("foo"));
     jsonObject.put("foo", bin3);
     assertArrayEquals(bin3, jsonObject.getBinary("foo"));
-    assertEquals(Base64.getEncoder().encodeToString(bin3), jsonObject.getValue("foo"));
+    assertEquals(BASE64_ENCODER.encodeToString(bin3), jsonObject.getValue("foo"));
 
     jsonObject.put("foo", (byte[]) null);
     assertTrue(jsonObject.containsKey("foo"));
@@ -1130,28 +1133,28 @@ public void testPutValue() {
     assertEquals(Float.valueOf(1.23f), jsonObject.getFloat("float"));
     assertEquals(Double.valueOf(1.23d), jsonObject.getDouble("double"));
     assertArrayEquals(bytes, jsonObject.getBinary("binary"));
-    assertEquals(Base64.getEncoder().encodeToString(bytes), jsonObject.getValue("binary"));
+    assertEquals(BASE64_ENCODER.encodeToString(bytes), jsonObject.getValue("binary"));
     assertEquals(now, jsonObject.getInstant("instant"));
     assertEquals(now.toString(), jsonObject.getValue("instant"));
     assertEquals(obj, jsonObject.getJsonObject("obj"));
     assertEquals(arr, jsonObject.getJsonArray("arr"));
     try {
       jsonObject.put("inv", new SomeClass());
+      // OK (we can put anything, yet it should fail to encode if a codec is missing)
+    } catch (RuntimeException e) {
       fail();
-    } catch (IllegalStateException e) {
-      // OK
     }
     try {
       jsonObject.put("inv", new BigDecimal(123));
+      // OK (we can put anything, yet it should fail to encode if a codec is missing)
+    } catch (RuntimeException e) {
       fail();
-    } catch (IllegalStateException e) {
-      // OK
     }
     try {
       jsonObject.put("inv", new Date());
+      // OK (we can put anything, yet it should fail to encode if a codec is missing)
+    } catch (RuntimeException e) {
       fail();
-    } catch (IllegalStateException e) {
-      // OK
     }
 
   }
@@ -1714,6 +1717,23 @@ public void testEquals() {
     assertNotEquals(new JsonObject().putNull("a"), new JsonObject().put("a", 1));
     assertEquals(new JsonObject().putNull("a"), new JsonObject().putNull("a"));
   }
+
+  @Test
+  public void testNoEncode() {
+    Instant now = Instant.now();
+    JsonObject json = new JsonObject();
+    // bypass any custom validation
+    json.getMap().put("now", now);
+    assertEquals(now, json.getInstant("now"));
+    assertSame(now, json.getInstant("now"));
+
+    // same for byte[]
+    byte[] bytes = "bytes".getBytes();
+    // bypass any custom validation
+    json.getMap().put("bytes", bytes);
+    assertEquals(bytes, json.getBinary("bytes"));
+    assertSame(bytes, json.getBinary("bytes"));
+  }
 }
 
 
diff --git a/src/test/java/io/vertx/core/parsetools/JsonParserTest.java b/src/test/java/io/vertx/core/parsetools/JsonParserTest.java
--- a/src/test/java/io/vertx/core/parsetools/JsonParserTest.java
+++ b/src/test/java/io/vertx/core/parsetools/JsonParserTest.java
@@ -24,7 +24,6 @@
 import java.time.format.DateTimeParseException;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Base64;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.LinkedList;
@@ -35,6 +34,7 @@
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.Consumer;
 
+import static io.vertx.core.json.impl.JsonUtil.BASE64_ENCODER;
 import static java.time.format.DateTimeFormatter.*;
 import static junit.framework.TestCase.assertFalse;
 import static org.junit.Assert.assertEquals;
@@ -248,7 +248,7 @@ public void testInstantValue() {
   @Test
   public void testBinaryValue() {
     byte[] value = TestUtils.randomByteArray(10);
-    String encoded = Base64.getEncoder().encodeToString(value);
+    String encoded = BASE64_ENCODER.encodeToString(value);
     testValue('"' + encoded + '"', event -> {
       assertEquals(encoded, event.value());
       assertFalse(event.isArray());