Repository: quarkusio/quarkus
Issue: NullPointerException reading mongodb bson date (native image)

Hi,

only native image of my application cause  a NullPointerException parsing mongodb document with date field.

The error occours both with quarkus 1.6.0.final and 1.7.0.final. 

Here the full staktrace:
```
2020-08-21 15:12:12,711 ERROR [org.jbo.res.res.i18n] (executor-thread-1) RESTEASY002020: Unhandled asynchronous exception, sending back 500: org.jboss.resteasy.spi.ApplicationException: java.lang.NullPointerException
	at org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:180)
	at org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:130)
	at org.jboss.resteasy.core.ResourceMethodInvoker.internalInvokeOnTarget(ResourceMethodInvoker.java:638)
	at org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTargetAfterFilter(ResourceMethodInvoker.java:504)
	at org.jboss.resteasy.core.ResourceMethodInvoker.lambda$invokeOnTarget$2(ResourceMethodInvoker.java:454)
	at org.jboss.resteasy.core.interception.jaxrs.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:364)
	at org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTarget(ResourceMethodInvoker.java:456)
	at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:417)
	at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:391)
	at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:68)
	at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:488)
	at org.jboss.resteasy.core.SynchronousDispatcher.lambda$invoke$4(SynchronousDispatcher.java:259)
	at org.jboss.resteasy.core.SynchronousDispatcher.lambda$preprocess$0(SynchronousDispatcher.java:160)
	at org.jboss.resteasy.core.interception.jaxrs.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:364)
	at org.jboss.resteasy.core.SynchronousDispatcher.preprocess(SynchronousDispatcher.java:163)
	at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:245)
	at io.quarkus.resteasy.runtime.standalone.RequestDispatcher.service(RequestDispatcher.java:73)
	at io.quarkus.resteasy.runtime.standalone.VertxRequestHandler.dispatch(VertxRequestHandler.java:132)
	at io.quarkus.resteasy.runtime.standalone.VertxRequestHandler.access$000(VertxRequestHandler.java:37)
	at io.quarkus.resteasy.runtime.standalone.VertxRequestHandler$1.run(VertxRequestHandler.java:94)
	at org.jboss.threads.ContextClassLoaderSavingRunnable.run(ContextClassLoaderSavingRunnable.java:35)
	at org.jboss.threads.EnhancedQueueExecutor.safeRun(EnhancedQueueExecutor.java:2046)
	at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.doRunTask(EnhancedQueueExecutor.java:1578)
	at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1452)
	at org.jboss.threads.DelegatingRunnable.run(DelegatingRunnable.java:29)
	at org.jboss.threads.ThreadLocalResettingRunnable.run(ThreadLocalResettingRunnable.java:29)
	at java.lang.Thread.run(Thread.java:834)
	at org.jboss.threads.JBossThread.run(JBossThread.java:479)
	at com.oracle.svm.core.thread.JavaThreads.threadStartRoutine(JavaThreads.java:517)
	at com.oracle.svm.core.posix.thread.PosixJavaThreads.pthreadStartRoutine(PosixJavaThreads.java:193)
Caused by: java.lang.NullPointerException
	at java.text.SimpleDateFormat.matchZoneString(SimpleDateFormat.java:1695)
	at java.text.SimpleDateFormat.subParseZoneString(SimpleDateFormat.java:1763)
	at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:2169)
	at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1541)
	at org.bson.json.JsonReader.visitISODateTimeConstructor(JsonReader.java:836)
	at org.bson.json.JsonReader.readBsonType(JsonReader.java:225)
	at org.bson.codecs.BsonDocumentCodec.decode(BsonDocumentCodec.java:85)
	at org.bson.codecs.BsonDocumentCodec.decode(BsonDocumentCodec.java:42)
	at org.bson.codecs.BsonDocumentCodec.readValue(BsonDocumentCodec.java:104)
	at org.bson.codecs.BsonDocumentCodec.decode(BsonDocumentCodec.java:87)
	at org.bson.BsonDocument.parse(BsonDocument.java:63)
	at io.quarkus.mongodb.panache.runtime.MongoOperations.executeUpdate(MongoOperations.java:619)
	at io.quarkus.mongodb.panache.runtime.MongoOperations.update(MongoOperations.java:614)
	at it.cineca.model.elaborato.HeartBeat.update(HeartBeat.java)
	at it.cineca.manager.HeartBeatManager.aggiornaBattito(HeartBeatManager.java:64)
	at it.cineca.manager.HeartBeatManager_ClientProxy.aggiornaBattito(HeartBeatManager_ClientProxy.zig:216)
	at it.cineca.web.BeatController.aggiornaBattito(BeatController.java:38)
	at java.lang.reflect.Method.invoke(Method.java:566)
	at org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:167)
	... 29 more
```
I'm compiling native image using maven's quarkus standard configuration with these Resource Configuration File:

```
{
  "bundles": [  {"name":"sun.util.resources.TimeZoneNames"}   ],
  "resources": [     {       "pattern": "org/joda/time/tz/data/.*/.*$"     },
                            {       "pattern": "org/joda/time/tz/data/.*$"     }   ]
}
```
Thank you
Nicola

Changed files:
extensions/panache/mongodb-panache-common/runtime/src/main/java/io/quarkus/mongodb/panache/binder/CommonQueryBinder.java
extensions/panache/mongodb-panache/runtime/src/test/java/io/quarkus/mongodb/panache/runtime/MongoOperationsTest.java

diff --git a/extensions/panache/mongodb-panache-common/runtime/src/main/java/io/quarkus/mongodb/panache/binder/CommonQueryBinder.java b/extensions/panache/mongodb-panache-common/runtime/src/main/java/io/quarkus/mongodb/panache/binder/CommonQueryBinder.java
index b620616af2b..00c602ccea7 100644
--- a/extensions/panache/mongodb-panache-common/runtime/src/main/java/io/quarkus/mongodb/panache/binder/CommonQueryBinder.java
+++ b/extensions/panache/mongodb-panache-common/runtime/src/main/java/io/quarkus/mongodb/panache/binder/CommonQueryBinder.java
@@ -1,15 +1,14 @@
 package io.quarkus.mongodb.panache.binder;
 
-import java.text.SimpleDateFormat;
 import java.time.Instant;
 import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
 import java.time.format.DateTimeFormatter;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Date;
-import java.util.TimeZone;
 import java.util.UUID;
 import java.util.stream.Collectors;
 
@@ -39,22 +38,20 @@ static String escape(Object value) {
             return value.toString();
         }
         if (value instanceof Date) {
-            SimpleDateFormat dateFormat = new SimpleDateFormat(ISO_DATE_PATTERN);
-            dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
-            Date dateValue = (Date) value;
-            return "ISODate('" + dateFormat.format(dateValue) + "')";
+            ZonedDateTime zonedDateTime = ((Date) value).toInstant().atZone(ZoneOffset.UTC);
+            return "{\\"$date\\": \\"" + ISO_DATE_FORMATTER.format(zonedDateTime) + "\\"} ";
         }
         if (value instanceof LocalDate) {
-            LocalDate dateValue = (LocalDate) value;
-            return "ISODate('" + DateTimeFormatter.ISO_LOCAL_DATE.format(dateValue) + "')";
+            ZonedDateTime zonedDateTime = ((LocalDate) value).atStartOfDay(ZoneOffset.UTC);
+            return "{\\"$date\\": \\"" + ISO_DATE_FORMATTER.format(zonedDateTime) + "\\"} ";
         }
         if (value instanceof LocalDateTime) {
-            LocalDateTime dateValue = (LocalDateTime) value;
-            return "ISODate('" + ISO_DATE_FORMATTER.format(dateValue.atZone(ZoneOffset.UTC)) + "')";
+            ZonedDateTime zonedDateTime = ((LocalDateTime) value).atZone(ZoneOffset.UTC);
+            return "{\\"$date\\": \\"" + ISO_DATE_FORMATTER.format(zonedDateTime) + "\\"} ";
         }
         if (value instanceof Instant) {
-            Instant dateValue = (Instant) value;
-            return "ISODate('" + ISO_DATE_FORMATTER.format(dateValue.atZone(ZoneOffset.UTC)) + "')";
+            ZonedDateTime zonedDateTime = ((Instant) value).atZone(ZoneOffset.UTC);
+            return "{\\"$date\\": \\"" + ISO_DATE_FORMATTER.format(zonedDateTime) + "\\"} ";
         }
         if (value instanceof UUID) {
             UUID uuidValue = (UUID) value;
diff --git a/extensions/panache/mongodb-panache/runtime/src/test/java/io/quarkus/mongodb/panache/runtime/MongoOperationsTest.java b/extensions/panache/mongodb-panache/runtime/src/test/java/io/quarkus/mongodb/panache/runtime/MongoOperationsTest.java
index 2016dc4e05c..38e01aad928 100644
--- a/extensions/panache/mongodb-panache/runtime/src/test/java/io/quarkus/mongodb/panache/runtime/MongoOperationsTest.java
+++ b/extensions/panache/mongodb-panache/runtime/src/test/java/io/quarkus/mongodb/panache/runtime/MongoOperationsTest.java
@@ -50,18 +50,18 @@ public void testBindShorthandFilter() {
         assertEquals("{'field':true}", query);
 
         query = operations.bindFilter(Object.class, "field", new Object[] { LocalDate.of(2019, 3, 4) });
-        assertEquals("{'field':ISODate('2019-03-04')}", query);
+        assertEquals("{'field':{\\"$date\\": \\"2019-03-04T00:00:00.000Z\\"} }", query);
 
         query = operations.bindFilter(Object.class, "field", new Object[] { LocalDateTime.of(2019, 3, 4, 1, 1, 1) });
-        assertEquals("{'field':ISODate('2019-03-04T01:01:01.000Z')}", query);
+        assertEquals("{'field':{\\"$date\\": \\"2019-03-04T01:01:01.000Z\\"} }", query);
 
         query = operations.bindFilter(Object.class, "field",
                 new Object[] { LocalDateTime.of(2019, 3, 4, 1, 1, 1).toInstant(ZoneOffset.UTC) });
-        assertEquals("{'field':ISODate('2019-03-04T01:01:01.000Z')}", query);
+        assertEquals("{'field':{\\"$date\\": \\"2019-03-04T01:01:01.000Z\\"} }", query);
 
         query = operations.bindFilter(Object.class, "field",
                 new Object[] { toDate(LocalDateTime.of(2019, 3, 4, 1, 1, 1)) });
-        assertEquals("{'field':ISODate('2019-03-04T01:01:01.000Z')}", query);
+        assertEquals("{'field':{\\"$date\\": \\"2019-03-04T01:01:01.000Z\\"} }", query);
 
         query = operations.bindFilter(Object.class, "field",
                 new Object[] { UUID.fromString("7f000101-7370-1f68-8173-70afa71b0000") });
@@ -90,19 +90,19 @@ public void testBindNativeFilterByIndex() {
 
         query = operations.bindFilter(Object.class, "{'field': ?1}",
                 new Object[] { LocalDate.of(2019, 3, 4) });
-        assertEquals("{'field': ISODate('2019-03-04')}", query);
+        assertEquals("{'field': {\\"$date\\": \\"2019-03-04T00:00:00.000Z\\"} }", query);
 
         query = operations.bindFilter(Object.class, "{'field': ?1}",
                 new Object[] { LocalDateTime.of(2019, 3, 4, 1, 1, 1) });
-        assertEquals("{'field': ISODate('2019-03-04T01:01:01.000Z')}", query);
+        assertEquals("{'field': {\\"$date\\": \\"2019-03-04T01:01:01.000Z\\"} }", query);
 
         query = operations.bindFilter(Object.class, "{'field': ?1}",
                 new Object[] { LocalDateTime.of(2019, 3, 4, 1, 1, 1).toInstant(ZoneOffset.UTC) });
-        assertEquals("{'field': ISODate('2019-03-04T01:01:01.000Z')}", query);
+        assertEquals("{'field': {\\"$date\\": \\"2019-03-04T01:01:01.000Z\\"} }", query);
 
         query = operations.bindFilter(Object.class, "{'field': ?1}",
                 new Object[] { toDate(LocalDateTime.of(2019, 3, 4, 1, 1, 1)) });
-        assertEquals("{'field': ISODate('2019-03-04T01:01:01.000Z')}", query);
+        assertEquals("{'field': {\\"$date\\": \\"2019-03-04T01:01:01.000Z\\"} }", query);
 
         query = operations.bindFilter(Object.class, "{'field': ?1}",
                 new Object[] { UUID.fromString("7f000101-7370-1f68-8173-70afa71b0000") });
@@ -148,19 +148,19 @@ public void testBindNativeFilterByName() {
 
         query = operations.bindFilter(Object.class, "{'field': :field}",
                 Parameters.with("field", LocalDate.of(2019, 3, 4)).map());
-        assertEquals("{'field': ISODate('2019-03-04')}", query);
+        assertEquals("{'field': {\\"$date\\": \\"2019-03-04T00:00:00.000Z\\"} }", query);
 
         query = operations.bindFilter(Object.class, "{'field': :field}",
                 Parameters.with("field", LocalDateTime.of(2019, 3, 4, 1, 1, 1)).map());
-        assertEquals("{'field': ISODate('2019-03-04T01:01:01.000Z')}", query);
+        assertEquals("{'field': {\\"$date\\": \\"2019-03-04T01:01:01.000Z\\"} }", query);
 
         query = operations.bindFilter(Object.class, "{'field': :field}",
                 Parameters.with("field", LocalDateTime.of(2019, 3, 4, 1, 1, 1).toInstant(ZoneOffset.UTC)).map());
-        assertEquals("{'field': ISODate('2019-03-04T01:01:01.000Z')}", query);
+        assertEquals("{'field': {\\"$date\\": \\"2019-03-04T01:01:01.000Z\\"} }", query);
 
         query = operations.bindFilter(Object.class, "{'field': :field}",
                 Parameters.with("field", toDate(LocalDateTime.of(2019, 3, 4, 1, 1, 1))).map());
-        assertEquals("{'field': ISODate('2019-03-04T01:01:01.000Z')}", query);
+        assertEquals("{'field': {\\"$date\\": \\"2019-03-04T01:01:01.000Z\\"} }", query);
 
         query = operations.bindFilter(Object.class, "{'field': :field}",
                 Parameters.with("field", UUID.fromString("7f000101-7370-1f68-8173-70afa71b0000")).map());
@@ -209,18 +209,18 @@ public void testBindEnhancedFilterByIndex() {
         assertEquals("{'value':'a value'}", query);
 
         query = operations.bindFilter(Object.class, "field = ?1", new Object[] { LocalDate.of(2019, 3, 4) });
-        assertEquals("{'field':ISODate('2019-03-04')}", query);
+        assertEquals("{'field':{\\"$date\\": \\"2019-03-04T00:00:00.000Z\\"} }", query);
 
         query = operations.bindFilter(Object.class, "field = ?1", new Object[] { LocalDateTime.of(2019, 3, 4, 1, 1, 1) });
-        assertEquals("{'field':ISODate('2019-03-04T01:01:01.000Z')}", query);
+        assertEquals("{'field':{\\"$date\\": \\"2019-03-04T01:01:01.000Z\\"} }", query);
 
         query = operations.bindFilter(Object.class, "field = ?1",
                 new Object[] { LocalDateTime.of(2019, 3, 4, 1, 1, 1).toInstant(ZoneOffset.UTC) });
-        assertEquals("{'field':ISODate('2019-03-04T01:01:01.000Z')}", query);
+        assertEquals("{'field':{\\"$date\\": \\"2019-03-04T01:01:01.000Z\\"} }", query);
 
         query = operations.bindFilter(Object.class, "field = ?1",
                 new Object[] { toDate(LocalDateTime.of(2019, 3, 4, 1, 1, 1)) });
-        assertEquals("{'field':ISODate('2019-03-04T01:01:01.000Z')}", query);
+        assertEquals("{'field':{\\"$date\\": \\"2019-03-04T01:01:01.000Z\\"} }", query);
 
         query = operations.bindFilter(Object.class, "field = ?1",
                 new Object[] { UUID.fromString("7f000101-7370-1f68-8173-70afa71b0000") });
@@ -287,19 +287,19 @@ public void testBindEnhancedFilterByName() {
 
         query = operations.bindFilter(Object.class, "field = :field",
                 Parameters.with("field", LocalDate.of(2019, 3, 4)).map());
-        assertEquals("{'field':ISODate('2019-03-04')}", query);
+        assertEquals("{'field':{\\"$date\\": \\"2019-03-04T00:00:00.000Z\\"} }", query);
 
         query = operations.bindFilter(Object.class, "field = :field",
                 Parameters.with("field", LocalDateTime.of(2019, 3, 4, 1, 1, 1)).map());
-        assertEquals("{'field':ISODate('2019-03-04T01:01:01.000Z')}", query);
+        assertEquals("{'field':{\\"$date\\": \\"2019-03-04T01:01:01.000Z\\"} }", query);
 
         query = operations.bindFilter(Object.class, "field = :field",
                 Parameters.with("field", LocalDateTime.of(2019, 3, 4, 1, 1, 1).toInstant(ZoneOffset.UTC)).map());
-        assertEquals("{'field':ISODate('2019-03-04T01:01:01.000Z')}", query);
+        assertEquals("{'field':{\\"$date\\": \\"2019-03-04T01:01:01.000Z\\"} }", query);
 
         query = operations.bindFilter(Object.class, "field = :field",
                 Parameters.with("field", toDate(LocalDateTime.of(2019, 3, 4, 1, 1, 1))).map());
-        assertEquals("{'field':ISODate('2019-03-04T01:01:01.000Z')}", query);
+        assertEquals("{'field':{\\"$date\\": \\"2019-03-04T01:01:01.000Z\\"} }", query);
 
         query = operations.bindFilter(Object.class, "field = :field",
                 Parameters.with("field", UUID.fromString("7f000101-7370-1f68-8173-70afa71b0000")).map());