Repository: eclipse/leshan
Pull request #184 (2016-10-06 10:05:54)

Add a way to get coapResponse at LWM2M level
See [LwM2mResponse change](https://github.com/eclipse/leshan/pull/184/files#diff-cc367c2b7dbc1d6033e7c4cc77a0298d).


Patch:
diff --git a/leshan-client-core/src/main/java/org/eclipse/leshan/client/resource/BaseObjectEnabler.java b/leshan-client-core/src/main/java/org/eclipse/leshan/client/resource/BaseObjectEnabler.java
index 8abcb91b..e029cb86 100644
--- a/leshan-client-core/src/main/java/org/eclipse/leshan/client/resource/BaseObjectEnabler.java
+++ b/leshan-client-core/src/main/java/org/eclipse/leshan/client/resource/BaseObjectEnabler.java
@@ -327,7 +327,7 @@ public abstract class BaseObjectEnabler implements LwM2mObjectEnabler {
     @Override
     public synchronized ObserveResponse observe(ServerIdentity identity, ObserveRequest request) {
         ReadResponse readResponse = this.read(identity, new ReadRequest(request.getPath().toString()));
-        return new ObserveResponse(readResponse.getCode(), readResponse.getContent(), null,
+        return new ObserveResponse(readResponse.getCode(), readResponse.getContent(), null, null,
                 readResponse.getErrorMessage());
     }
 
diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/node/TimestampedLwM2mNode.java b/leshan-core/src/main/java/org/eclipse/leshan/core/node/TimestampedLwM2mNode.java
index abc1572e..31d71889 100644
--- a/leshan-core/src/main/java/org/eclipse/leshan/core/node/TimestampedLwM2mNode.java
+++ b/leshan-core/src/main/java/org/eclipse/leshan/core/node/TimestampedLwM2mNode.java
@@ -37,7 +37,43 @@ public class TimestampedLwM2mNode {
         return node;
     }
 
-    public boolean isTimespamped() {
+    public boolean isTimestamped() {
         return timestamp != null && timestamp >= 0;
     }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((node == null) ? 0 : node.hashCode());
+        result = prime * result + ((timestamp == null) ? 0 : timestamp.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        TimestampedLwM2mNode other = (TimestampedLwM2mNode) obj;
+        if (node == null) {
+            if (other.node != null)
+                return false;
+        } else if (!node.equals(other.node))
+            return false;
+        if (timestamp == null) {
+            if (other.timestamp != null)
+                return false;
+        } else if (!timestamp.equals(other.timestamp))
+            return false;
+        return true;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("TimestampedLwM2mNode [timestamp=%s, node=%s]", timestamp, node);
+    }
 }
diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/response/AbstractLwM2mResponse.java b/leshan-core/src/main/java/org/eclipse/leshan/core/response/AbstractLwM2mResponse.java
index 0833562b..3dac7454 100644
--- a/leshan-core/src/main/java/org/eclipse/leshan/core/response/AbstractLwM2mResponse.java
+++ b/leshan-core/src/main/java/org/eclipse/leshan/core/response/AbstractLwM2mResponse.java
@@ -25,13 +25,15 @@ public abstract class AbstractLwM2mResponse implements LwM2mResponse {
 
     protected final ResponseCode code;
     protected final String errorMessage;
+    private final Object coapResponse;
 
-    public AbstractLwM2mResponse(final ResponseCode code, final String errorMessage) {
+    public AbstractLwM2mResponse(ResponseCode code, String errorMessage, Object coapResponse) {
         Validate.notNull(code);
         if (errorMessage != null)
             Validate.isTrue(code.isError(), "Only error response could have an error message.");
         this.code = code;
         this.errorMessage = errorMessage;
+        this.coapResponse = coapResponse;
     }
 
     @Override
@@ -39,6 +41,11 @@ public abstract class AbstractLwM2mResponse implements LwM2mResponse {
         return this.code;
     }
 
+    @Override
+    public Object getCoapResponse() {
+        return coapResponse;
+    }
+
     @Override
     public String getErrorMessage() {
         return errorMessage;
diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/response/BootstrapDeleteResponse.java b/leshan-core/src/main/java/org/eclipse/leshan/core/response/BootstrapDeleteResponse.java
index f7be35d2..857c7c1b 100644
--- a/leshan-core/src/main/java/org/eclipse/leshan/core/response/BootstrapDeleteResponse.java
+++ b/leshan-core/src/main/java/org/eclipse/leshan/core/response/BootstrapDeleteResponse.java
@@ -23,7 +23,11 @@ import org.eclipse.leshan.ResponseCode;
 public class BootstrapDeleteResponse extends AbstractLwM2mResponse {
 
     public BootstrapDeleteResponse(ResponseCode code, String errorMessage) {
-        super(code, errorMessage);
+        this(code, errorMessage, null);
+    }
+
+    public BootstrapDeleteResponse(ResponseCode code, String errorMessage, Object coapResponse) {
+        super(code, errorMessage, coapResponse);
     }
 
     @Override
diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/response/BootstrapFinishResponse.java b/leshan-core/src/main/java/org/eclipse/leshan/core/response/BootstrapFinishResponse.java
index f494c656..6f94a0de 100644
--- a/leshan-core/src/main/java/org/eclipse/leshan/core/response/BootstrapFinishResponse.java
+++ b/leshan-core/src/main/java/org/eclipse/leshan/core/response/BootstrapFinishResponse.java
@@ -23,7 +23,11 @@ import org.eclipse.leshan.ResponseCode;
 public class BootstrapFinishResponse extends AbstractLwM2mResponse {
 
     public BootstrapFinishResponse(ResponseCode code, String errorMessage) {
-        super(code, errorMessage);
+        this(code, errorMessage, null);
+    }
+
+    public BootstrapFinishResponse(ResponseCode code, String errorMessage, Object coapResponse) {
+        super(code, errorMessage, coapResponse);
     }
 
     @Override
diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/response/BootstrapResponse.java b/leshan-core/src/main/java/org/eclipse/leshan/core/response/BootstrapResponse.java
index e0715493..85e3f8da 100644
--- a/leshan-core/src/main/java/org/eclipse/leshan/core/response/BootstrapResponse.java
+++ b/leshan-core/src/main/java/org/eclipse/leshan/core/response/BootstrapResponse.java
@@ -22,8 +22,12 @@ import org.eclipse.leshan.ResponseCode;
  */
 public class BootstrapResponse extends AbstractLwM2mResponse {
 
-    public BootstrapResponse(final ResponseCode code, final String errorMessage) {
-        super(code, errorMessage);
+    public BootstrapResponse(ResponseCode code, String errorMessage) {
+        this(code, errorMessage, null);
+    }
+
+    public BootstrapResponse(ResponseCode code, String errorMessage, Object coapResponse) {
+        super(code, errorMessage, coapResponse);
     }
 
     @Override
diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/response/BootstrapWriteResponse.java b/leshan-core/src/main/java/org/eclipse/leshan/core/response/BootstrapWriteResponse.java
index 8b467304..c3ee0e08 100644
--- a/leshan-core/src/main/java/org/eclipse/leshan/core/response/BootstrapWriteResponse.java
+++ b/leshan-core/src/main/java/org/eclipse/leshan/core/response/BootstrapWriteResponse.java
@@ -23,7 +23,11 @@ import org.eclipse.leshan.ResponseCode;
 public class BootstrapWriteResponse extends AbstractLwM2mResponse {
 
     public BootstrapWriteResponse(ResponseCode code, String errorMessage) {
-        super(code, errorMessage);
+        this(code, errorMessage, null);
+    }
+
+    public BootstrapWriteResponse(ResponseCode code, String errorMessage, Object coapResponse) {
+        super(code, errorMessage, coapResponse);
     }
 
     @Override
diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/response/CreateResponse.java b/leshan-core/src/main/java/org/eclipse/leshan/core/response/CreateResponse.java
index 71f50af8..c9649f11 100644
--- a/leshan-core/src/main/java/org/eclipse/leshan/core/response/CreateResponse.java
+++ b/leshan-core/src/main/java/org/eclipse/leshan/core/response/CreateResponse.java
@@ -22,7 +22,11 @@ public class CreateResponse extends AbstractLwM2mResponse {
     private String location;
 
     public CreateResponse(ResponseCode code, String location, String errorMessage) {
-        super(code, errorMessage);
+        this(code, location, errorMessage, null);
+    }
+
+    public CreateResponse(ResponseCode code, String location, String errorMessage, Object coapResponse) {
+        super(code, errorMessage, coapResponse);
         this.location = location;
     }
 
diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/response/DeleteResponse.java b/leshan-core/src/main/java/org/eclipse/leshan/core/response/DeleteResponse.java
index 0212a515..d0e525f9 100644
--- a/leshan-core/src/main/java/org/eclipse/leshan/core/response/DeleteResponse.java
+++ b/leshan-core/src/main/java/org/eclipse/leshan/core/response/DeleteResponse.java
@@ -19,8 +19,12 @@ import org.eclipse.leshan.ResponseCode;
 
 public class DeleteResponse extends AbstractLwM2mResponse {
 
-    public DeleteResponse(final ResponseCode code, final String errorMessage) {
-        super(code, errorMessage);
+    public DeleteResponse(ResponseCode code, String errorMessage){
+        this(code, errorMessage, null);
+    }
+
+    public DeleteResponse(ResponseCode code, String errorMessage, Object coapResponse) {
+        super(code, errorMessage, coapResponse);
     }
 
     @Override
diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/response/DeregisterResponse.java b/leshan-core/src/main/java/org/eclipse/leshan/core/response/DeregisterResponse.java
index 986fa20e..528d93ed 100644
--- a/leshan-core/src/main/java/org/eclipse/leshan/core/response/DeregisterResponse.java
+++ b/leshan-core/src/main/java/org/eclipse/leshan/core/response/DeregisterResponse.java
@@ -19,8 +19,12 @@ import org.eclipse.leshan.ResponseCode;
 
 public class DeregisterResponse extends AbstractLwM2mResponse {
 
-    public DeregisterResponse(final ResponseCode code, final String errorMessage) {
-        super(code, errorMessage);
+    public DeregisterResponse(ResponseCode code, String errorMessage) {
+        this(code, errorMessage, null);
+    }
+
+    public DeregisterResponse(ResponseCode code, String errorMessage, Object coapResponse) {
+        super(code, errorMessage, coapResponse);
     }
 
     @Override
diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/response/DiscoverResponse.java b/leshan-core/src/main/java/org/eclipse/leshan/core/response/DiscoverResponse.java
index 32945188..3a36558f 100644
--- a/leshan-core/src/main/java/org/eclipse/leshan/core/response/DiscoverResponse.java
+++ b/leshan-core/src/main/java/org/eclipse/leshan/core/response/DiscoverResponse.java
@@ -26,7 +26,11 @@ public class DiscoverResponse extends AbstractLwM2mResponse {
     private final LinkObject[] links;
 
     public DiscoverResponse(ResponseCode code, LinkObject[] links, String errorMessage) {
-        super(code, errorMessage);
+        this(code, links, errorMessage, null);
+    }
+
+    public DiscoverResponse(ResponseCode code, LinkObject[] links, String errorMessage, Object coapResponse) {
+        super(code, errorMessage, coapResponse);
         if (ResponseCode.CONTENT.equals(code)) {
             Validate.notNull(links);
             this.links = Arrays.copyOf(links, links.length);
diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/response/ExecuteResponse.java b/leshan-core/src/main/java/org/eclipse/leshan/core/response/ExecuteResponse.java
index a618f996..3c9739e4 100644
--- a/leshan-core/src/main/java/org/eclipse/leshan/core/response/ExecuteResponse.java
+++ b/leshan-core/src/main/java/org/eclipse/leshan/core/response/ExecuteResponse.java
@@ -19,8 +19,12 @@ import org.eclipse.leshan.ResponseCode;
 
 public class ExecuteResponse extends AbstractLwM2mResponse {
 
-    public ExecuteResponse(final ResponseCode code, final String errorMessage) {
-        super(code, errorMessage);
+    public ExecuteResponse(ResponseCode code, String errorMessage) {
+        this(code, errorMessage, null);
+    }
+
+    public ExecuteResponse(ResponseCode code, String errorMessage, Object coapResponse) {
+        super(code, errorMessage, coapResponse);
     }
 
     @Override
diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/response/LwM2mResponse.java b/leshan-core/src/main/java/org/eclipse/leshan/core/response/LwM2mResponse.java
index 2a9588f2..c49af1a9 100644
--- a/leshan-core/src/main/java/org/eclipse/leshan/core/response/LwM2mResponse.java
+++ b/leshan-core/src/main/java/org/eclipse/leshan/core/response/LwM2mResponse.java
@@ -37,6 +37,15 @@ public interface LwM2mResponse {
      */
     String getErrorMessage();
 
+    /**
+     * Get the underlying CoAP response. The object type depends of the chosen CoAP implementation. (e.g with
+     * Californium implementation <code>getCoapResponse()</code> will returns
+     * <code>a org.eclipse.californium.core.coap.Response)</code>).
+     * 
+     * @return the CoAP response
+     */
+    Object getCoapResponse();
+
     /**
      * @return true if the request was successfully done.
      */
diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/response/ObserveResponse.java b/leshan-core/src/main/java/org/eclipse/leshan/core/response/ObserveResponse.java
index 03875412..41372331 100644
--- a/leshan-core/src/main/java/org/eclipse/leshan/core/response/ObserveResponse.java
+++ b/leshan-core/src/main/java/org/eclipse/leshan/core/response/ObserveResponse.java
@@ -15,8 +15,11 @@
  *******************************************************************************/
 package org.eclipse.leshan.core.response;
 
+import java.util.List;
+
 import org.eclipse.leshan.ResponseCode;
 import org.eclipse.leshan.core.node.LwM2mNode;
+import org.eclipse.leshan.core.node.TimestampedLwM2mNode;
 import org.eclipse.leshan.core.observation.Observation;
 
 /**
@@ -24,19 +27,57 @@ import org.eclipse.leshan.core.observation.Observation;
  *
  * This can be useful to listen to updates on the specific Observation.
  */
-public class ObserveResponse extends ReadResponse {
+public class ObserveResponse extends AbstractLwM2mResponse {
+
+    private final LwM2mNode content;
+    private final Observation observation;
+    private final List<TimestampedLwM2mNode> timestampedValues;
+
+    public ObserveResponse(ResponseCode code, LwM2mNode content, List<TimestampedLwM2mNode> timestampedValues,
+            Observation observation, String errorMessage) {
+        this(code, content, timestampedValues, observation, errorMessage, null);
+    }
 
-    private Observation observation;
+    public ObserveResponse(ResponseCode code, LwM2mNode content, List<TimestampedLwM2mNode> timestampedValues,
+            Observation observation, String errorMessage, Object coapResponse) {
+        super(code, errorMessage, coapResponse);
 
-    public ObserveResponse(ResponseCode code, LwM2mNode content, Observation observation, String errorMessage) {
-        super(code, content, errorMessage);
+        if (ResponseCode.CONTENT.equals(code) && content == null && timestampedValues == null) {
+            throw new IllegalArgumentException("Content response must have a not null content or timestampedValues.");
+        }
+        this.content = content;
         this.observation = observation;
+        this.timestampedValues = timestampedValues;
+    }
+
+    /**
+     * @return the value of the LWM2M node observed. In case of timestamped value returns the most recent one.
+     */
+    public LwM2mNode getContent() {
+        if (content != null)
+            return content;
+        else if (timestampedValues != null && !timestampedValues.isEmpty()) {
+            return timestampedValues.get(0).getNode();
+        }
+        return null;
+    }
+
+    public List<TimestampedLwM2mNode> getTimestampedLwM2mNode() {
+        return timestampedValues;
+    }
+
+    @Override
+    public boolean isSuccess() {
+        return getCode() == ResponseCode.CONTENT;
     }
 
     @Override
     public String toString() {
         if (errorMessage != null)
             return String.format("ObserveResponse [code=%s, errormessage=%s]", code, errorMessage);
+        else if (timestampedValues != null)
+            return String.format("ObserveResponse [code=%s, content=%s, observation=%s, timestampedValues= %d nodes]",
+                    code, content, observation, timestampedValues.size());
         else
             return String.format("ObserveResponse [code=%s, content=%s, observation=%s]", code, content, observation);
     }
@@ -48,30 +89,34 @@ public class ObserveResponse extends ReadResponse {
     // Syntactic sugar static constructors :
 
     public static ObserveResponse success(LwM2mNode content) {
-        return new ObserveResponse(ResponseCode.CONTENT, content, null, null);
+        return new ObserveResponse(ResponseCode.CONTENT, content, null, null, null);
+    }
+
+    public static ObserveResponse success(List<TimestampedLwM2mNode> timestampedValues) {
+        return new ObserveResponse(ResponseCode.CONTENT, null, timestampedValues, null, null);
     }
 
     public static ObserveResponse badRequest(String errorMessage) {
-        return new ObserveResponse(ResponseCode.BAD_REQUEST, null, null, errorMessage);
+        return new ObserveResponse(ResponseCode.BAD_REQUEST, null, null, null, errorMessage);
     }
 
     public static ObserveResponse notFound() {
-        return new ObserveResponse(ResponseCode.NOT_FOUND, null, null, null);
+        return new ObserveResponse(ResponseCode.NOT_FOUND, null, null, null, null);
     }
 
     public static ObserveResponse unauthorized() {
-        return new ObserveResponse(ResponseCode.UNAUTHORIZED, null, null, null);
+        return new ObserveResponse(ResponseCode.UNAUTHORIZED, null, null, null, null);
     }
 
     public static ObserveResponse methodNotAllowed() {
-        return new ObserveResponse(ResponseCode.METHOD_NOT_ALLOWED, null, null, null);
+        return new ObserveResponse(ResponseCode.METHOD_NOT_ALLOWED, null, null, null, null);
     }
 
     public static ObserveResponse notAcceptable() {
-        return new ObserveResponse(ResponseCode.NOT_ACCEPTABLE, null, null, null);
+        return new ObserveResponse(ResponseCode.NOT_ACCEPTABLE, null, null, null, null);
     }
 
     public static ObserveResponse internalServerError(String errorMessage) {
-        return new ObserveResponse(ResponseCode.INTERNAL_SERVER_ERROR, null, null, errorMessage);
+        return new ObserveResponse(ResponseCode.INTERNAL_SERVER_ERROR, null, null, null, errorMessage);
     }
 }
diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/response/ReadResponse.java b/leshan-core/src/main/java/org/eclipse/leshan/core/response/ReadResponse.java
index 9371e041..db072982 100644
--- a/leshan-core/src/main/java/org/eclipse/leshan/core/response/ReadResponse.java
+++ b/leshan-core/src/main/java/org/eclipse/leshan/core/response/ReadResponse.java
@@ -31,7 +31,11 @@ public class ReadResponse extends AbstractLwM2mResponse {
     protected final LwM2mNode content;
 
     public ReadResponse(ResponseCode code, LwM2mNode content, String errorMessage) {
-        super(code, errorMessage);
+        this(code, content, errorMessage, null);
+    }
+
+    public ReadResponse(ResponseCode code, LwM2mNode content, String errorMessage, Object coapResponse) {
+        super(code, errorMessage, coapResponse);
 
         if (ResponseCode.CONTENT.equals(code)) {
             Validate.notNull(content);
@@ -64,7 +68,7 @@ public class ReadResponse extends AbstractLwM2mResponse {
     // Syntactic sugar static constructors :
 
     public static ReadResponse success(LwM2mNode content) {
-        return new ReadResponse(ResponseCode.CONTENT, content, null);
+        return new ReadResponse(ResponseCode.CONTENT, content, null, null);
     }
 
     public static ReadResponse success(int resourceId, String value) {
diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/response/RegisterResponse.java b/leshan-core/src/main/java/org/eclipse/leshan/core/response/RegisterResponse.java
index 59dfd038..0ff9c9fe 100644
--- a/leshan-core/src/main/java/org/eclipse/leshan/core/response/RegisterResponse.java
+++ b/leshan-core/src/main/java/org/eclipse/leshan/core/response/RegisterResponse.java
@@ -25,7 +25,11 @@ public class RegisterResponse extends AbstractLwM2mResponse {
     private final String registrationID;
 
     public RegisterResponse(ResponseCode code, String registrationID, String errorMessage) {
-        super(code, errorMessage);
+        this(code, registrationID, errorMessage, null);
+    }
+
+    public RegisterResponse(ResponseCode code, String registrationID, String errorMessage, Object coapResponse) {
+        super(code, errorMessage, coapResponse);
         this.registrationID = registrationID;
     }
 
diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/response/UpdateResponse.java b/leshan-core/src/main/java/org/eclipse/leshan/core/response/UpdateResponse.java
index 52f76cee..23536c3d 100644
--- a/leshan-core/src/main/java/org/eclipse/leshan/core/response/UpdateResponse.java
+++ b/leshan-core/src/main/java/org/eclipse/leshan/core/response/UpdateResponse.java
@@ -20,7 +20,11 @@ import org.eclipse.leshan.ResponseCode;
 public class UpdateResponse extends AbstractLwM2mResponse {
 
     public UpdateResponse(final ResponseCode code, final String errorMessage) {
-        super(code, errorMessage);
+        this(code, errorMessage, null);
+    }
+
+    public UpdateResponse(final ResponseCode code, final String errorMessage, Object coapResponse) {
+        super(code, errorMessage, coapResponse);
     }
 
     @Override
diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/response/WriteAttributesResponse.java b/leshan-core/src/main/java/org/eclipse/leshan/core/response/WriteAttributesResponse.java
index d8643bb7..8e218c10 100644
--- a/leshan-core/src/main/java/org/eclipse/leshan/core/response/WriteAttributesResponse.java
+++ b/leshan-core/src/main/java/org/eclipse/leshan/core/response/WriteAttributesResponse.java
@@ -19,8 +19,12 @@ import org.eclipse.leshan.ResponseCode;
 
 public class WriteAttributesResponse extends AbstractLwM2mResponse {
 
-    public WriteAttributesResponse(final ResponseCode code, final String errorMessage) {
-        super(code, errorMessage);
+    public WriteAttributesResponse(ResponseCode code, String errorMessage) {
+        super(code, errorMessage, null);
+    }
+
+    public WriteAttributesResponse(ResponseCode code, String errorMessage, Object coapResponse) {
+        super(code, errorMessage, coapResponse);
     }
 
     @Override
diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/response/WriteResponse.java b/leshan-core/src/main/java/org/eclipse/leshan/core/response/WriteResponse.java
index defcd9e5..65d0d41e 100644
--- a/leshan-core/src/main/java/org/eclipse/leshan/core/response/WriteResponse.java
+++ b/leshan-core/src/main/java/org/eclipse/leshan/core/response/WriteResponse.java
@@ -19,8 +19,12 @@ import org.eclipse.leshan.ResponseCode;
 
 public class WriteResponse extends AbstractLwM2mResponse {
 
-    public WriteResponse(final ResponseCode code, final String errorMessage) {
-        super(code, errorMessage);
+    public WriteResponse(ResponseCode code, String errorMessage) {
+        super(code, errorMessage, null);
+    }
+
+    public WriteResponse(ResponseCode code, String errorMessage, Object coapResponse) {
+        super(code, errorMessage, coapResponse);
     }
 
     @Override
diff --git a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/impl/CaliforniumObservationRegistryImpl.java b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/impl/CaliforniumObservationRegistryImpl.java
index d7712cbb..18eb5d36 100644
--- a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/impl/CaliforniumObservationRegistryImpl.java
+++ b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/impl/CaliforniumObservationRegistryImpl.java
@@ -33,6 +33,7 @@ import org.eclipse.californium.core.coap.Request;
 import org.eclipse.californium.core.coap.Response;
 import org.eclipse.californium.core.network.Endpoint;
 import org.eclipse.californium.core.observe.ObservationStore;
+import org.eclipse.leshan.ResponseCode;
 import org.eclipse.leshan.core.model.LwM2mModel;
 import org.eclipse.leshan.core.node.LwM2mPath;
 import org.eclipse.leshan.core.node.TimestampedLwM2mNode;
@@ -40,6 +41,7 @@ import org.eclipse.leshan.core.node.codec.InvalidValueException;
 import org.eclipse.leshan.core.node.codec.LwM2mNodeDecoder;
 import org.eclipse.leshan.core.observation.Observation;
 import org.eclipse.leshan.core.request.ContentFormat;
+import org.eclipse.leshan.core.response.ObserveResponse;
 import org.eclipse.leshan.server.californium.CaliforniumObservationRegistry;
 import org.eclipse.leshan.server.client.Client;
 import org.eclipse.leshan.server.client.ClientRegistry;
@@ -272,18 +274,22 @@ public class CaliforniumObservationRegistryImpl implements CaliforniumObservatio
                 }
 
                 // decode response
-                List<TimestampedLwM2mNode> content = decoder.decodeTimestampedData(coapResponse.getPayload(),
+                List<TimestampedLwM2mNode> timestampedNodes = decoder.decodeTimestampedData(coapResponse.getPayload(),
                         contentFormat, observation.getPath(), model);
 
+                // create lwm2m response
+                ObserveResponse response;
+                if (timestampedNodes.size() == 1 && !timestampedNodes.get(0).isTimestamped()) {
+                    response = new ObserveResponse(ResponseCode.CONTENT, timestampedNodes.get(0).getNode(), null,
+                            observation, null, coapResponse);
+                } else {
+                    response = new ObserveResponse(ResponseCode.CONTENT, null, timestampedNodes, observation, null,
+                            coapResponse);
+                }
+
                 // notify all listeners
                 for (ObservationRegistryListener listener : listeners) {
-                    if (content.isEmpty()) {
-                        listener.newValue(observation, null, content);
-                    } else if (content.size() == 1 && content.get(0).isTimespamped()) {
-                        listener.newValue(observation, content.get(0).getNode(), null);
-                    } else {
-                        listener.newValue(observation, content.get(0).getNode(), content);
-                    }
+                    listener.newValue(observation, response);
                 }
             } catch (InvalidValueException e) {
                 LOG.debug(String.format("[%s] ([%s])", e.getMessage(), e.getPath().toString()));
diff --git a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/impl/LwM2mResponseBuilder.java b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/impl/LwM2mResponseBuilder.java
index 240da348..de033b69 100644
--- a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/impl/LwM2mResponseBuilder.java
+++ b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/impl/LwM2mResponseBuilder.java
@@ -120,7 +120,7 @@ public class LwM2mResponseBuilder<T extends LwM2mResponse> implements DownlinkRe
         switch (coapResponse.getCode()) {
         case CONTENT:
             LwM2mNode content = decodeCoapResponse(request.getPath(), coapResponse);
-            lwM2mresponse = ReadResponse.success(content);
+            lwM2mresponse = new ReadResponse(ResponseCode.CONTENT, content, null, coapResponse);
             break;
         case BAD_REQUEST:
         case UNAUTHORIZED:
@@ -129,7 +129,7 @@ public class LwM2mResponseBuilder<T extends LwM2mResponse> implements DownlinkRe
         case NOT_ACCEPTABLE:
         case INTERNAL_SERVER_ERROR:
             lwM2mresponse = new ReadResponse(fromCoapCode(coapResponse.getCode().value), null,
-                    coapResponse.getPayloadString());
+                    coapResponse.getPayloadString(), coapResponse);
             break;
         default:
             handleUnexpectedResponseCode(client.getEndpoint(), coapRequest, coapResponse);
@@ -149,7 +149,7 @@ public class LwM2mResponseBuilder<T extends LwM2mResponse> implements DownlinkRe
             } else {
                 links = LinkObject.parse(coapResponse.getPayload());
             }
-            lwM2mresponse = DiscoverResponse.success(links);
+            lwM2mresponse = new DiscoverResponse(ResponseCode.CONTENT, links, null, coapResponse);
             break;
         case BAD_REQUEST:
         case NOT_FOUND:
@@ -157,7 +157,7 @@ public class LwM2mResponseBuilder<T extends LwM2mResponse> implements DownlinkRe
         case METHOD_NOT_ALLOWED:
         case INTERNAL_SERVER_ERROR:
             lwM2mresponse = new DiscoverResponse(fromCoapCode(coapResponse.getCode().value), null,
-                    coapResponse.getPayloadString());
+                    coapResponse.getPayloadString(), coapResponse);
             break;
         default:
             handleUnexpectedResponseCode(client.getEndpoint(), coapRequest, coapResponse);
@@ -168,7 +168,7 @@ public class LwM2mResponseBuilder<T extends LwM2mResponse> implements DownlinkRe
     public void visit(final WriteRequest request) {
         switch (coapResponse.getCode()) {
         case CHANGED:
-            lwM2mresponse = WriteResponse.success();
+            lwM2mresponse = new WriteResponse(ResponseCode.CHANGED, null, coapResponse);
             break;
         case BAD_REQUEST:
         case NOT_FOUND:
@@ -177,7 +177,7 @@ public class LwM2mResponseBuilder<T extends LwM2mResponse> implements DownlinkRe
         case UNSUPPORTED_CONTENT_FORMAT:
         case INTERNAL_SERVER_ERROR:
             lwM2mresponse = new WriteResponse(fromCoapCode(coapResponse.getCode().value),
-                    coapResponse.getPayloadString());
+                    coapResponse.getPayloadString(), coapResponse);
             break;
         default:
             handleUnexpectedResponseCode(client.getEndpoint(), coapRequest, coapResponse);
@@ -188,7 +188,7 @@ public class LwM2mResponseBuilder<T extends LwM2mResponse> implements DownlinkRe
     public void visit(final WriteAttributesRequest request) {
         switch (coapResponse.getCode()) {
         case CHANGED:
-            lwM2mresponse = WriteAttributesResponse.success();
+            lwM2mresponse = new WriteAttributesResponse(ResponseCode.CHANGED, null, coapResponse);
             break;
         case BAD_REQUEST:
         case NOT_FOUND:
@@ -196,7 +196,7 @@ public class LwM2mResponseBuilder<T extends LwM2mResponse> implements DownlinkRe
         case METHOD_NOT_ALLOWED:
         case INTERNAL_SERVER_ERROR:
             lwM2mresponse = new WriteAttributesResponse(fromCoapCode(coapResponse.getCode().value),
-                    coapResponse.getPayloadString());
+                    coapResponse.getPayloadString(), coapResponse);
             break;
         default:
             handleUnexpectedResponseCode(client.getEndpoint(), coapRequest, coapResponse);
@@ -207,7 +207,7 @@ public class LwM2mResponseBuilder<T extends LwM2mResponse> implements DownlinkRe
     public void visit(final ExecuteRequest request) {
         switch (coapResponse.getCode()) {
         case CHANGED:
-            lwM2mresponse = ExecuteResponse.success();
+            lwM2mresponse = new ExecuteResponse(ResponseCode.CHANGED, null, coapResponse);
             break;
         case BAD_REQUEST:
         case UNAUTHORIZED:
@@ -215,7 +215,7 @@ public class LwM2mResponseBuilder<T extends LwM2mResponse> implements DownlinkRe
         case METHOD_NOT_ALLOWED:
         case INTERNAL_SERVER_ERROR:
             lwM2mresponse = new ExecuteResponse(fromCoapCode(coapResponse.getCode().value),
-                    coapResponse.getPayloadString());
+                    coapResponse.getPayloadString(), coapResponse);
             break;
         default:
             handleUnexpectedResponseCode(client.getEndpoint(), coapRequest, coapResponse);
@@ -227,7 +227,8 @@ public class LwM2mResponseBuilder<T extends LwM2mResponse> implements DownlinkRe
     public void visit(final CreateRequest request) {
         switch (coapResponse.getCode()) {
         case CREATED:
-            lwM2mresponse = CreateResponse.success(coapResponse.getOptions().getLocationPathString());
+            lwM2mresponse = new CreateResponse(ResponseCode.CREATED, coapResponse.getOptions().getLocationPathString(),
+                    null, coapResponse);
             break;
         case BAD_REQUEST:
         case UNAUTHORIZED:
@@ -236,7 +237,7 @@ public class LwM2mResponseBuilder<T extends LwM2mResponse> implements DownlinkRe
         case UNSUPPORTED_CONTENT_FORMAT:
         case INTERNAL_SERVER_ERROR:
             lwM2mresponse = new CreateResponse(fromCoapCode(coapResponse.getCode().value), null,
-                    coapResponse.getPayloadString());
+                    coapResponse.getPayloadString(), coapResponse);
             break;
         default:
             handleUnexpectedResponseCode(client.getEndpoint(), coapRequest, coapResponse);
@@ -247,7 +248,7 @@ public class LwM2mResponseBuilder<T extends LwM2mResponse> implements DownlinkRe
     public void visit(final DeleteRequest request) {
         switch (coapResponse.getCode()) {
         case DELETED:
-            lwM2mresponse = DeleteResponse.success();
+            lwM2mresponse = new DeleteResponse(ResponseCode.DELETED, null, coapResponse);
             break;
         case UNAUTHORIZED:
         case NOT_FOUND:
@@ -255,7 +256,7 @@ public class LwM2mResponseBuilder<T extends LwM2mResponse> implements DownlinkRe
         case BAD_REQUEST:
         case INTERNAL_SERVER_ERROR:
             lwM2mresponse = new DeleteResponse(fromCoapCode(coapResponse.getCode().value),
-                    coapResponse.getPayloadString());
+                    coapResponse.getPayloadString(), coapResponse);
             break;
         default:
             handleUnexpectedResponseCode(client.getEndpoint(), coapRequest, coapResponse);
@@ -278,9 +279,10 @@ public class LwM2mResponseBuilder<T extends LwM2mResponse> implements DownlinkRe
                         request.getPath(), request.getContext());
                 observationRegistry.addObservation(observation);
                 // add the observation to an ObserveResponse instance
-                lwM2mresponse = new ObserveResponse(ResponseCode.CONTENT, content, observation, null);
+                lwM2mresponse = new ObserveResponse(ResponseCode.CONTENT, content, null, observation, null,
+                        coapResponse);
             } else {
-                lwM2mresponse = ObserveResponse.success(content);
+                lwM2mresponse = new ObserveResponse(ResponseCode.CONTENT, content, null, null, null, coapResponse);
             }
             break;
         case BAD_REQUEST:
@@ -289,8 +291,8 @@ public class LwM2mResponseBuilder<T extends LwM2mResponse> implements DownlinkRe
         case METHOD_NOT_ALLOWED:
         case NOT_ACCEPTABLE:
         case INTERNAL_SERVER_ERROR:
-            lwM2mresponse = new ObserveResponse(fromCoapCode(coapResponse.getCode().value), null, null,
-                    coapResponse.getPayloadString());
+            lwM2mresponse = new ObserveResponse(fromCoapCode(coapResponse.getCode().value), null, null, null,
+                    coapResponse.getPayloadString(), coapResponse);
             break;
         default:
             handleUnexpectedResponseCode(client.getEndpoint(), coapRequest, coapResponse);
@@ -301,13 +303,13 @@ public class LwM2mResponseBuilder<T extends LwM2mResponse> implements DownlinkRe
     public void visit(final BootstrapWriteRequest request) {
         switch (coapResponse.getCode()) {
         case CHANGED:
-            lwM2mresponse = BootstrapWriteResponse.success();
+            lwM2mresponse = new BootstrapWriteResponse(ResponseCode.CHANGED, null, coapResponse);
             break;
         case UNSUPPORTED_CONTENT_FORMAT:
         case BAD_REQUEST:
         case INTERNAL_SERVER_ERROR:
             lwM2mresponse = new BootstrapWriteResponse(fromCoapCode(coapResponse.getCode().value),
-                    coapResponse.getPayloadString());
+                    coapResponse.getPayloadString(), coapResponse);
             break;
         default:
             handleUnexpectedResponseCode(client.getEndpoint(), coapRequest, coapResponse);
@@ -318,12 +320,12 @@ public class LwM2mResponseBuilder<T extends LwM2mResponse> implements DownlinkRe
     public void visit(final BootstrapDeleteRequest request) {
         switch (coapResponse.getCode()) {
         case DELETED:
-            lwM2mresponse = BootstrapDeleteResponse.success();
+            lwM2mresponse = new BootstrapDeleteResponse(ResponseCode.DELETED, null, coapResponse);
             break;
         case BAD_REQUEST:
         case INTERNAL_SERVER_ERROR:
             lwM2mresponse = new BootstrapDeleteResponse(fromCoapCode(coapResponse.getCode().value),
-                    coapResponse.getPayloadString());
+                    coapResponse.getPayloadString(), coapResponse);
             break;
         default:
             handleUnexpectedResponseCode(client.getEndpoint(), coapRequest, coapResponse);
@@ -334,12 +336,12 @@ public class LwM2mResponseBuilder<T extends LwM2mResponse> implements DownlinkRe
     public void visit(final BootstrapFinishRequest request) {
         switch (coapResponse.getCode()) {
         case CHANGED:
-            lwM2mresponse = BootstrapFinishResponse.success();
+            lwM2mresponse = new BootstrapFinishResponse(ResponseCode.CHANGED, null, coapResponse);
             break;
         case BAD_REQUEST:
         case INTERNAL_SERVER_ERROR:
             lwM2mresponse = new BootstrapFinishResponse(fromCoapCode(coapResponse.getCode().value),
-                    coapResponse.getPayloadString());
+                    coapResponse.getPayloadString(), coapResponse);
             break;
         default:
             handleUnexpectedResponseCode(client.getEndpoint(), coapRequest, coapResponse);
diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/observation/ObservationRegistryListener.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/observation/ObservationRegistryListener.java
index f3bb49e8..7a4a869f 100644
--- a/leshan-server-core/src/main/java/org/eclipse/leshan/server/observation/ObservationRegistryListener.java
+++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/observation/ObservationRegistryListener.java
@@ -15,11 +15,8 @@
  *******************************************************************************/
 package org.eclipse.leshan.server.observation;
 
-import java.util.List;
-
-import org.eclipse.leshan.core.node.LwM2mNode;
-import org.eclipse.leshan.core.node.TimestampedLwM2mNode;
 import org.eclipse.leshan.core.observation.Observation;
+import org.eclipse.leshan.core.response.ObserveResponse;
 
 public interface ObservationRegistryListener {
     void newObservation(Observation observation);
@@ -30,8 +27,7 @@ public interface ObservationRegistryListener {
      * Called on new notification.
      * 
      * @param observation the observation for which new data are received
-     * @param mostRecentValue the most recent value
-     * @param timestampedValues the list of time-stamped value (could be null)
+     * @param reponse the lwm2m response received
      */
-    void newValue(Observation observation, LwM2mNode mostRecentValue, List<TimestampedLwM2mNode> timestampedValues);
+    void newValue(Observation observation, ObserveResponse response);
 }
diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/queue/impl/QueuedRequestSender.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/queue/impl/QueuedRequestSender.java
index 724c0afa..c1c3c356 100644
--- a/leshan-server-core/src/main/java/org/eclipse/leshan/server/queue/impl/QueuedRequestSender.java
+++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/queue/impl/QueuedRequestSender.java
@@ -25,14 +25,13 @@ import java.util.concurrent.TimeUnit;
 import java.util.concurrent.locks.ReadWriteLock;
 import java.util.concurrent.locks.ReentrantReadWriteLock;
 
-import org.eclipse.leshan.core.node.LwM2mNode;
-import org.eclipse.leshan.core.node.TimestampedLwM2mNode;
 import org.eclipse.leshan.core.observation.Observation;
 import org.eclipse.leshan.core.request.DownlinkRequest;
 import org.eclipse.leshan.core.request.exception.RequestCanceledException;
 import org.eclipse.leshan.core.request.exception.TimeoutException;
 import org.eclipse.leshan.core.response.ErrorCallback;
 import org.eclipse.leshan.core.response.LwM2mResponse;
+import org.eclipse.leshan.core.response.ObserveResponse;
 import org.eclipse.leshan.core.response.ResponseCallback;
 import org.eclipse.leshan.server.Stoppable;
 import org.eclipse.leshan.server.client.Client;
@@ -261,7 +260,7 @@ public class QueuedRequestSender implements LwM2mRequestSender, Stoppable {
 
     private final class QueueModeObservationRegistryListener implements ObservationRegistryListener {
         @Override
-        public void newValue(Observation observation, LwM2mNode value, List<TimestampedLwM2mNode> timestampedValues) {
+        public void newValue(Observation observation, ObserveResponse response) {
             Client client = clientRegistry.findByRegistrationId(observation.getRegistrationId());
             // if client de-registered already, do nothing.
             if (client == null) {
diff --git a/leshan-server-demo/src/main/java/org/eclipse/leshan/server/demo/servlet/EventServlet.java b/leshan-server-demo/src/main/java/org/eclipse/leshan/server/demo/servlet/EventServlet.java
index 5c0edcfd..d5f466a1 100644
--- a/leshan-server-demo/src/main/java/org/eclipse/leshan/server/demo/servlet/EventServlet.java
+++ b/leshan-server-demo/src/main/java/org/eclipse/leshan/server/demo/servlet/EventServlet.java
@@ -17,7 +17,6 @@ package org.eclipse.leshan.server.demo.servlet;
 
 import java.io.IOException;
 import java.util.Collections;
-import java.util.List;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 
@@ -25,8 +24,8 @@ import javax.servlet.http.HttpServletRequest;
 
 import org.eclipse.californium.core.network.Endpoint;
 import org.eclipse.leshan.core.node.LwM2mNode;
-import org.eclipse.leshan.core.node.TimestampedLwM2mNode;
 import org.eclipse.leshan.core.observation.Observation;
+import org.eclipse.leshan.core.response.ObserveResponse;
 import org.eclipse.leshan.server.californium.impl.LeshanServer;
 import org.eclipse.leshan.server.client.Client;
 import org.eclipse.leshan.server.client.ClientRegistryListener;
@@ -100,16 +99,17 @@ public class EventServlet extends EventSourceServlet {
         }
 
         @Override
-        public void newValue(Observation observation, LwM2mNode value, List<TimestampedLwM2mNode> timestampedValues) {
+        public void newValue(Observation observation, ObserveResponse response) {
             if (LOG.isDebugEnabled()) {
                 LOG.debug("Received notification from [{}] containing value [{}]", observation.getPath(),
-                        value.toString());
+                        response.getContent().toString());
             }
             Client client = server.getClientRegistry().findByRegistrationId(observation.getRegistrationId());
 
             if (client != null) {
                 String data = new StringBuffer("{\"ep\":\"").append(client.getEndpoint()).append("\",\"res\":\"")
-                        .append(observation.getPath().toString()).append("\",\"val\":").append(gson.toJson(value))
+                        .append(observation.getPath().toString()).append("\",\"val\":")
+                        .append(gson.toJson(response.getContent()))
                         .append("}").toString();
 
                 sendEvent(EVENT_NOTIFICATION, data, client.getEndpoint());


Test patch:
diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/CreateTest.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/CreateTest.java
index 5f49def6..0d5a198f 100644
--- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/CreateTest.java
+++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/CreateTest.java
@@ -17,10 +17,13 @@
 
 package org.eclipse.leshan.integration.tests;
 
-import static org.junit.Assert.assertEquals;
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsInstanceOf.instanceOf;
+import static org.junit.Assert.*;
 
 import java.util.Arrays;
 
+import org.eclipse.californium.core.coap.Response;
 import org.eclipse.leshan.ResponseCode;
 import org.eclipse.leshan.core.node.LwM2mObjectInstance;
 import org.eclipse.leshan.core.node.LwM2mResource;
@@ -65,6 +68,8 @@ public class CreateTest {
         // verify result
         assertEquals(ResponseCode.CREATED, response.getCode());
         assertEquals("2/0", response.getLocation());
+        assertNotNull(response.getCoapResponse());
+        assertThat(response.getCoapResponse(), is(instanceOf(Response.class)));
 
         // create a second ACL instance
         response = helper.server.send(helper.getCurrentRegistration(), new CreateRequest(2,
@@ -73,6 +78,8 @@ public class CreateTest {
         // verify result
         assertEquals(ResponseCode.CREATED, response.getCode());
         assertEquals("2/1", response.getLocation());
+        assertNotNull(response.getCoapResponse());
+        assertThat(response.getCoapResponse(), is(instanceOf(Response.class)));
 
     }
 
@@ -86,6 +93,8 @@ public class CreateTest {
         // verify result
         assertEquals(ResponseCode.CREATED, response.getCode());
         assertEquals("2/12", response.getLocation());
+        assertNotNull(response.getCoapResponse());
+        assertThat(response.getCoapResponse(), is(instanceOf(Response.class)));
     }
 
     @Test
@@ -99,6 +108,8 @@ public class CreateTest {
         // verify result
         assertEquals(ResponseCode.CREATED, response.getCode());
         assertEquals("2/12", response.getLocation());
+        assertNotNull(response.getCoapResponse());
+        assertThat(response.getCoapResponse(), is(instanceOf(Response.class)));
     }
 
     @Test
@@ -109,6 +120,8 @@ public class CreateTest {
 
         // verify result
         assertEquals(ResponseCode.NOT_FOUND, response.getCode());
+        assertNotNull(response.getCoapResponse());
+        assertThat(response.getCoapResponse(), is(instanceOf(Response.class)));
     }
 
     // TODO not sure all the writable mandatory resource should be present
@@ -123,11 +136,15 @@ public class CreateTest {
 
         // verify result
         assertEquals(ResponseCode.BAD_REQUEST, response.getCode());
+        assertNotNull(response.getCoapResponse());
+        assertThat(response.getCoapResponse(), is(instanceOf(Response.class)));
 
         // try to read to check if the instance is not created
         // client registration
         ReadResponse readResponse = helper.server.send(helper.getCurrentRegistration(), new ReadRequest(2, 0));
         assertEquals(ResponseCode.NOT_FOUND, readResponse.getCode());
+        assertNotNull(response.getCoapResponse());
+        assertThat(response.getCoapResponse(), is(instanceOf(Response.class)));
     }
 
     // TODO we must probably implement this.
@@ -142,11 +159,15 @@ public class CreateTest {
 
         // verify result
         assertEquals(ResponseCode.BAD_REQUEST, response.getCode());
+        assertNotNull(response.getCoapResponse());
+        assertThat(response.getCoapResponse(), is(instanceOf(Response.class)));
 
         // try to read to check if the instance is not created
         // client registration
         ReadResponse readResponse = helper.server.send(helper.getCurrentRegistration(), new ReadRequest(2, 0));
         assertEquals(ResponseCode.NOT_FOUND, readResponse.getCode());
+        assertNotNull(response.getCoapResponse());
+        assertThat(response.getCoapResponse(), is(instanceOf(Response.class)));
     }
 
     // TODO I'm not sure we can do use only writable resource on create
@@ -164,6 +185,8 @@ public class CreateTest {
 
         // verify result
         assertEquals(ResponseCode.METHOD_NOT_ALLOWED, response.getCode());
+        assertNotNull(response.getCoapResponse());
+        assertThat(response.getCoapResponse(), is(instanceOf(Response.class)));
     }
 
     @Test
@@ -173,6 +196,8 @@ public class CreateTest {
 
         // verify result
         assertEquals(ResponseCode.NOT_FOUND, response.getCode());
+        assertNotNull(response.getCoapResponse());
+        assertThat(response.getCoapResponse(), is(instanceOf(Response.class)));
     }
 
 }
diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/DeleteTest.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/DeleteTest.java
index ad58a729..ffaf172c 100644
--- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/DeleteTest.java
+++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/DeleteTest.java
@@ -18,10 +18,13 @@
 
 package org.eclipse.leshan.integration.tests;
 
-import static org.junit.Assert.assertEquals;
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsInstanceOf.instanceOf;
+import static org.junit.Assert.*;
 
 import java.util.Arrays;
 
+import org.eclipse.californium.core.coap.Response;
 import org.eclipse.leshan.ResponseCode;
 import org.eclipse.leshan.core.node.LwM2mObjectInstance;
 import org.eclipse.leshan.core.node.LwM2mResource;
@@ -63,10 +66,12 @@ public class DeleteTest {
                         .newIntegerResource(0, 123) }))));
 
         // try to delete this instance
-        DeleteResponse deleteResponse = helper.server.send(helper.getCurrentRegistration(), new DeleteRequest(2, 0));
+        DeleteResponse response = helper.server.send(helper.getCurrentRegistration(), new DeleteRequest(2, 0));
 
         // verify result
-        assertEquals(ResponseCode.DELETED, deleteResponse.getCode());
+        assertEquals(ResponseCode.DELETED, response.getCode());
+        assertNotNull(response.getCoapResponse());
+        assertThat(response.getCoapResponse(), is(instanceOf(Response.class)));
     }
 
     @Test
@@ -78,10 +83,12 @@ public class DeleteTest {
                         .newIntegerResource(0, 123) }))));
 
         // try to delete this instance
-        DeleteResponse deleteResponse = helper.server.send(helper.getCurrentRegistration(), new DeleteRequest("/2/0/0"));
+        DeleteResponse response = helper.server.send(helper.getCurrentRegistration(), new DeleteRequest("/2/0/0"));
 
         // verify result
-        assertEquals(ResponseCode.METHOD_NOT_ALLOWED, deleteResponse.getCode());
+        assertEquals(ResponseCode.METHOD_NOT_ALLOWED, response.getCode());
+        assertNotNull(response.getCoapResponse());
+        assertThat(response.getCoapResponse(), is(instanceOf(Response.class)));
     }
 
     @Test
@@ -91,6 +98,8 @@ public class DeleteTest {
 
         // verify result
         assertEquals(ResponseCode.NOT_FOUND, response.getCode());
+        assertNotNull(response.getCoapResponse());
+        assertThat(response.getCoapResponse(), is(instanceOf(Response.class)));
     }
 
     @Test
@@ -100,6 +109,8 @@ public class DeleteTest {
 
         // verify result
         assertEquals(ResponseCode.METHOD_NOT_ALLOWED, response.getCode());
+        assertNotNull(response.getCoapResponse());
+        assertThat(response.getCoapResponse(), is(instanceOf(Response.class)));
     }
 
     @Test
@@ -108,6 +119,8 @@ public class DeleteTest {
 
         // verify result
         assertEquals(ResponseCode.NOT_FOUND, response.getCode());
+        assertNotNull(response.getCoapResponse());
+        assertThat(response.getCoapResponse(), is(instanceOf(Response.class)));
     }
 
 }
diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/DiscoverTest.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/DiscoverTest.java
index 50b749de..8e330b01 100644
--- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/DiscoverTest.java
+++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/DiscoverTest.java
@@ -17,8 +17,11 @@
 package org.eclipse.leshan.integration.tests;
 
 import static org.eclipse.leshan.ResponseCode.*;
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsInstanceOf.instanceOf;
 import static org.junit.Assert.*;
 
+import org.eclipse.californium.core.coap.Response;
 import org.eclipse.leshan.LinkObject;
 import org.eclipse.leshan.core.request.DiscoverRequest;
 import org.eclipse.leshan.core.response.DiscoverResponse;
@@ -53,6 +56,8 @@ public class DiscoverTest {
 
         // verify result
         assertEquals(CONTENT, response.getCode());
+        assertNotNull(response.getCoapResponse());
+        assertThat(response.getCoapResponse(), is(instanceOf(Response.class)));
 
         LinkObject[] payload = response.getObjectLinks();
         assertArrayEquals(LinkObject.parse("</2>, </2/0/0>, </2/0/1>, </2/0/2>, </2/0/3>".getBytes()), payload);
@@ -65,6 +70,8 @@ public class DiscoverTest {
 
         // verify result
         assertEquals(NOT_FOUND, response.getCode());
+        assertNotNull(response.getCoapResponse());
+        assertThat(response.getCoapResponse(), is(instanceOf(Response.class)));
     }
 
     @Test
@@ -74,6 +81,8 @@ public class DiscoverTest {
 
         // verify result
         assertEquals(CONTENT, response.getCode());
+        assertNotNull(response.getCoapResponse());
+        assertThat(response.getCoapResponse(), is(instanceOf(Response.class)));
 
         LinkObject[] payload = response.getObjectLinks();
         assertArrayEquals(LinkObject.parse("</3/0>".getBytes()), payload);
@@ -86,6 +95,8 @@ public class DiscoverTest {
 
         // verify result
         assertEquals(NOT_FOUND, response.getCode());
+        assertNotNull(response.getCoapResponse());
+        assertThat(response.getCoapResponse(), is(instanceOf(Response.class)));
     }
 
     @Test
@@ -95,6 +106,8 @@ public class DiscoverTest {
 
         // verify result
         assertEquals(CONTENT, response.getCode());
+        assertNotNull(response.getCoapResponse());
+        assertThat(response.getCoapResponse(), is(instanceOf(Response.class)));
 
         LinkObject[] payload = response.getObjectLinks();
         assertArrayEquals(LinkObject.parse("</3/0/0>".getBytes()), payload);
@@ -107,6 +120,8 @@ public class DiscoverTest {
 
         // verify result
         assertEquals(NOT_FOUND, response.getCode());
+        assertNotNull(response.getCoapResponse());
+        assertThat(response.getCoapResponse(), is(instanceOf(Response.class)));
     }
 
     @Test
@@ -116,6 +131,8 @@ public class DiscoverTest {
 
         // verify result
         assertEquals(NOT_FOUND, response.getCode());
+        assertNotNull(response.getCoapResponse());
+        assertThat(response.getCoapResponse(), is(instanceOf(Response.class)));
     }
 
     @Test
@@ -125,6 +142,8 @@ public class DiscoverTest {
 
         // verify result
         assertEquals(NOT_FOUND, response.getCode());
+        assertNotNull(response.getCoapResponse());
+        assertThat(response.getCoapResponse(), is(instanceOf(Response.class)));
     }
 
     @Test
@@ -134,5 +153,7 @@ public class DiscoverTest {
 
         // verify result
         assertEquals(NOT_FOUND, response.getCode());
+        assertNotNull(response.getCoapResponse());
+        assertThat(response.getCoapResponse(), is(instanceOf(Response.class)));
     }
 }
diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/ExecuteTest.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/ExecuteTest.java
index ab07868a..0dd2e84a 100644
--- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/ExecuteTest.java
+++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/ExecuteTest.java
@@ -18,8 +18,11 @@
 
 package org.eclipse.leshan.integration.tests;
 
-import static org.junit.Assert.assertEquals;
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsInstanceOf.instanceOf;
+import static org.junit.Assert.*;
 
+import org.eclipse.californium.core.coap.Response;
 import org.eclipse.leshan.ResponseCode;
 import org.eclipse.leshan.core.request.ExecuteRequest;
 import org.eclipse.leshan.core.response.ExecuteResponse;
@@ -55,6 +58,8 @@ public class ExecuteTest {
 
         // verify result
         assertEquals(ResponseCode.METHOD_NOT_ALLOWED, response.getCode());
+        assertNotNull(response.getCoapResponse());
+        assertThat(response.getCoapResponse(), is(instanceOf(Response.class)));
     }
 
     @Test
@@ -64,6 +69,8 @@ public class ExecuteTest {
 
         // verify result
         assertEquals(ResponseCode.METHOD_NOT_ALLOWED, response.getCode());
+        assertNotNull(response.getCoapResponse());
+        assertThat(response.getCoapResponse(), is(instanceOf(Response.class)));
     }
 
     @Test
@@ -75,6 +82,8 @@ public class ExecuteTest {
 
         // verify result
         assertEquals(ResponseCode.NOT_FOUND, response.getCode());
+        assertNotNull(response.getCoapResponse());
+        assertThat(response.getCoapResponse(), is(instanceOf(Response.class)));
     }
 
     @Test
@@ -85,6 +94,8 @@ public class ExecuteTest {
 
         // verify result
         assertEquals(ResponseCode.NOT_FOUND, response.getCode());
+        assertNotNull(response.getCoapResponse());
+        assertThat(response.getCoapResponse(), is(instanceOf(Response.class)));
     }
 
     @Test
@@ -93,6 +104,8 @@ public class ExecuteTest {
 
         // verify result
         assertEquals(ResponseCode.NOT_FOUND, response.getCode());
+        assertNotNull(response.getCoapResponse());
+        assertThat(response.getCoapResponse(), is(instanceOf(Response.class)));
     }
 
     @Test
@@ -102,6 +115,8 @@ public class ExecuteTest {
 
         // verify result
         assertEquals(ResponseCode.CHANGED, response.getCode());
+        assertNotNull(response.getCoapResponse());
+        assertThat(response.getCoapResponse(), is(instanceOf(Response.class)));
     }
 
     @Test
@@ -111,6 +126,7 @@ public class ExecuteTest {
 
         // verify result
         assertEquals(ResponseCode.CHANGED, response.getCode());
+        assertNotNull(response.getCoapResponse());
+        assertThat(response.getCoapResponse(), is(instanceOf(Response.class)));
     }
-
 }
diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/ObserveTest.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/ObserveTest.java
index 60ff8f9f..10bcf72f 100644
--- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/ObserveTest.java
+++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/ObserveTest.java
@@ -16,20 +16,34 @@
 
 package org.eclipse.leshan.integration.tests;
 
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsInstanceOf.instanceOf;
 import static org.junit.Assert.*;
 
+import java.io.IOException;
+import java.net.DatagramPacket;
+import java.net.DatagramSocket;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
 
+import org.eclipse.californium.core.coap.CoAP.Type;
+import org.eclipse.californium.core.coap.OptionSet;
+import org.eclipse.californium.core.coap.Response;
+import org.eclipse.californium.core.network.serialization.UdpDataSerializer;
+import org.eclipse.californium.elements.RawData;
 import org.eclipse.leshan.ResponseCode;
-import org.eclipse.leshan.core.node.LwM2mNode;
+import org.eclipse.leshan.core.model.LwM2mModel;
 import org.eclipse.leshan.core.node.LwM2mObject;
 import org.eclipse.leshan.core.node.LwM2mObjectInstance;
+import org.eclipse.leshan.core.node.LwM2mPath;
 import org.eclipse.leshan.core.node.LwM2mSingleResource;
 import org.eclipse.leshan.core.node.TimestampedLwM2mNode;
+import org.eclipse.leshan.core.node.codec.json.LwM2mNodeJsonEncoder;
 import org.eclipse.leshan.core.observation.Observation;
+import org.eclipse.leshan.core.request.ContentFormat;
 import org.eclipse.leshan.core.request.ObserveRequest;
 import org.eclipse.leshan.core.request.ReadRequest;
 import org.eclipse.leshan.core.request.WriteRequest;
@@ -70,6 +84,8 @@ public class ObserveTest {
         // observe device timezone
         ObserveResponse observeResponse = helper.server.send(helper.getCurrentRegistration(), new ObserveRequest(3, 0, 15));
         assertEquals(ResponseCode.CONTENT, observeResponse.getCode());
+        assertNotNull(observeResponse.getCoapResponse());
+        assertThat(observeResponse.getCoapResponse(), is(instanceOf(Response.class)));
 
         // an observation response should have been sent
         Observation observation = observeResponse.getObservation();
@@ -84,7 +100,9 @@ public class ObserveTest {
         listener.waitForNotification(2000);
         assertEquals(ResponseCode.CHANGED, writeResponse.getCode());
         assertTrue(listener.receivedNotify().get());
-        assertEquals(LwM2mSingleResource.newStringResource(15, "Europe/Paris"), listener.getContent());
+        assertEquals(LwM2mSingleResource.newStringResource(15, "Europe/Paris"), listener.getResponse().getContent());
+        assertNotNull(listener.getResponse().getCoapResponse());
+        assertThat(listener.getResponse().getCoapResponse(), is(instanceOf(Response.class)));
     }
 
     @Test
@@ -95,6 +113,8 @@ public class ObserveTest {
         // observe device timezone
         ObserveResponse observeResponse = helper.server.send(helper.getCurrentRegistration(), new ObserveRequest(3, 0));
         assertEquals(ResponseCode.CONTENT, observeResponse.getCode());
+        assertNotNull(observeResponse.getCoapResponse());
+        assertThat(observeResponse.getCoapResponse(), is(instanceOf(Response.class)));
 
         // an observation response should have been sent
         Observation observation = observeResponse.getObservation();
@@ -109,12 +129,14 @@ public class ObserveTest {
         listener.waitForNotification(2000);
         assertEquals(ResponseCode.CHANGED, writeResponse.getCode());
         assertTrue(listener.receivedNotify().get());
-        assertTrue(listener.getContent() instanceof LwM2mObjectInstance);
+        assertTrue(listener.getResponse().getContent() instanceof LwM2mObjectInstance);
+        assertNotNull(listener.getResponse().getCoapResponse());
+        assertThat(listener.getResponse().getCoapResponse(), is(instanceOf(Response.class)));
 
         // try to read the object instance for comparing
         ReadResponse readResp = helper.server.send(helper.getCurrentRegistration(), new ReadRequest(3, 0));
 
-        assertEquals(readResp.getContent(), listener.getContent());
+        assertEquals(readResp.getContent(), listener.getResponse().getContent());
     }
 
     @Test
@@ -125,6 +147,8 @@ public class ObserveTest {
         // observe device timezone
         ObserveResponse observeResponse = helper.server.send(helper.getCurrentRegistration(), new ObserveRequest(3));
         assertEquals(ResponseCode.CONTENT, observeResponse.getCode());
+        assertNotNull(observeResponse.getCoapResponse());
+        assertThat(observeResponse.getCoapResponse(), is(instanceOf(Response.class)));
 
         // an observation response should have been sent
         Observation observation = observeResponse.getObservation();
@@ -139,30 +163,214 @@ public class ObserveTest {
         listener.waitForNotification(2000);
         assertEquals(ResponseCode.CHANGED, writeResponse.getCode());
         assertTrue(listener.receivedNotify().get());
-        assertTrue(listener.getContent() instanceof LwM2mObject);
+        assertTrue(listener.getResponse().getContent() instanceof LwM2mObject);
+        assertNotNull(listener.getResponse().getCoapResponse());
+        assertThat(listener.getResponse().getCoapResponse(), is(instanceOf(Response.class)));
 
         // try to read the object for comparing
         ReadResponse readResp = helper.server.send(helper.getCurrentRegistration(), new ReadRequest(3));
 
-        assertEquals(readResp.getContent(), listener.getContent());
+        assertEquals(readResp.getContent(), listener.getResponse().getContent());
+    }
+
+    @Test
+    public void can_observe_timestamped_resource() throws InterruptedException {
+        TestObservationListener listener = new TestObservationListener();
+        helper.server.getObservationRegistry().addListener(listener);
+
+        // observe device timezone
+        ObserveResponse observeResponse = helper.server.send(helper.getCurrentRegistration(),
+                new ObserveRequest(3, 0, 15));
+        assertEquals(ResponseCode.CONTENT, observeResponse.getCode());
+        assertNotNull(observeResponse.getCoapResponse());
+        assertThat(observeResponse.getCoapResponse(), is(instanceOf(Response.class)));
+
+        // an observation response should have been sent
+        Observation observation = observeResponse.getObservation();
+        assertEquals("/3/0/15", observation.getPath().toString());
+        assertEquals(helper.getCurrentRegistration().getRegistrationId(), observation.getRegistrationId());
+
+        // *** HACK send time-stamped notification as Leshan client does not support it *** //
+        // create time-stamped nodes
+        TimestampedLwM2mNode mostRecentNode = new TimestampedLwM2mNode(System.currentTimeMillis(),
+                LwM2mSingleResource.newStringResource(15, "Paris"));
+        List<TimestampedLwM2mNode> timestampedNodes = new ArrayList<>();
+        timestampedNodes.add(mostRecentNode);
+        timestampedNodes.add(new TimestampedLwM2mNode(mostRecentNode.getTimestamp() - 2,
+                LwM2mSingleResource.newStringResource(15, "Londres")));
+
+        // encode and send it
+        try (DatagramSocket clientSocket = new DatagramSocket()) {
+            byte[] payload = LwM2mNodeJsonEncoder.encodeTimestampedData(timestampedNodes, new LwM2mPath("/3/0/15"),
+                    new LwM2mModel(helper.createObjectModels()));
+
+            // create observe response
+            Response firstCoapResponse = (Response) observeResponse.getCoapResponse();
+            Response response = new Response(org.eclipse.californium.core.coap.CoAP.ResponseCode.CONTENT);
+            response.setType(Type.NON);
+            response.setPayload(payload);
+            response.setMID(firstCoapResponse.getMID() + 1);
+            response.setToken(firstCoapResponse.getToken());
+            response.setOptions(new OptionSet().setContentFormat(ContentFormat.JSON_CODE));
+
+            // serialize response
+            UdpDataSerializer serializer = new UdpDataSerializer();
+            RawData data = serializer.serializeResponse(response);
+
+            // send it
+            clientSocket.send(new DatagramPacket(data.bytes, data.bytes.length,
+                    helper.server.getNonSecureAddress().getAddress(), helper.server.getNonSecureAddress().getPort()));
+        } catch (IOException e) {
+            throw new AssertionError("Error while timestamped notification", e);
+        }
+        // *** Hack End *** //
+
+        // verify result
+        listener.waitForNotification(2000);
+        assertTrue(listener.receivedNotify().get());
+        assertEquals(mostRecentNode.getNode(), listener.getResponse().getContent());
+        assertEquals(timestampedNodes, listener.getResponse().getTimestampedLwM2mNode());
+        assertNotNull(listener.getResponse().getCoapResponse());
+        assertThat(listener.getResponse().getCoapResponse(), is(instanceOf(Response.class)));
+    }
+
+    @Test
+    public void can_observe_timestamped_instance() throws InterruptedException {
+        TestObservationListener listener = new TestObservationListener();
+        helper.server.getObservationRegistry().addListener(listener);
+
+        // observe device timezone
+        ObserveResponse observeResponse = helper.server.send(helper.getCurrentRegistration(),
+                new ObserveRequest(3, 0));
+        assertEquals(ResponseCode.CONTENT, observeResponse.getCode());
+        assertNotNull(observeResponse.getCoapResponse());
+        assertThat(observeResponse.getCoapResponse(), is(instanceOf(Response.class)));
+
+        // an observation response should have been sent
+        Observation observation = observeResponse.getObservation();
+        assertEquals("/3/0", observation.getPath().toString());
+        assertEquals(helper.getCurrentRegistration().getRegistrationId(), observation.getRegistrationId());
+
+        // *** HACK send time-stamped notification as Leshan client does not support it *** //
+        // create time-stamped nodes
+        TimestampedLwM2mNode mostRecentNode = new TimestampedLwM2mNode(System.currentTimeMillis(),
+                new LwM2mObjectInstance(0, LwM2mSingleResource.newStringResource(15, "Paris")));
+        List<TimestampedLwM2mNode> timestampedNodes = new ArrayList<>();
+        timestampedNodes.add(mostRecentNode);
+        timestampedNodes.add(new TimestampedLwM2mNode(mostRecentNode.getTimestamp() - 2,
+                new LwM2mObjectInstance(0, LwM2mSingleResource.newStringResource(15, "Londres"))));
+
+        // encode and send it
+        try (DatagramSocket clientSocket = new DatagramSocket()) {
+            byte[] payload = LwM2mNodeJsonEncoder.encodeTimestampedData(timestampedNodes, new LwM2mPath("/3/0"),
+                    new LwM2mModel(helper.createObjectModels()));
+
+            // create observe response
+            Response firstCoapResponse = (Response) observeResponse.getCoapResponse();
+            Response response = new Response(org.eclipse.californium.core.coap.CoAP.ResponseCode.CONTENT);
+            response.setType(Type.NON);
+            response.setPayload(payload);
+            response.setMID(firstCoapResponse.getMID() + 1);
+            response.setToken(firstCoapResponse.getToken());
+            response.setOptions(new OptionSet().setContentFormat(ContentFormat.JSON_CODE));
+
+            // serialize response
+            UdpDataSerializer serializer = new UdpDataSerializer();
+            RawData data = serializer.serializeResponse(response);
+
+            // send it
+            clientSocket.send(new DatagramPacket(data.bytes, data.bytes.length,
+                    helper.server.getNonSecureAddress().getAddress(), helper.server.getNonSecureAddress().getPort()));
+        } catch (IOException e) {
+            throw new AssertionError("Error while timestamped notification", e);
+        }
+        // *** Hack End *** //
+
+        // verify result
+        listener.waitForNotification(2000);
+        assertTrue(listener.receivedNotify().get());
+        assertEquals(mostRecentNode.getNode(), listener.getResponse().getContent());
+        assertEquals(timestampedNodes, listener.getResponse().getTimestampedLwM2mNode());
+        assertNotNull(listener.getResponse().getCoapResponse());
+        assertThat(listener.getResponse().getCoapResponse(), is(instanceOf(Response.class)));
+    }
+
+    @Test
+    public void can_observe_timestamped_object() throws InterruptedException {
+        TestObservationListener listener = new TestObservationListener();
+        helper.server.getObservationRegistry().addListener(listener);
+
+        // observe device timezone
+        ObserveResponse observeResponse = helper.server.send(helper.getCurrentRegistration(),
+                new ObserveRequest(3));
+        assertEquals(ResponseCode.CONTENT, observeResponse.getCode());
+        assertNotNull(observeResponse.getCoapResponse());
+        assertThat(observeResponse.getCoapResponse(), is(instanceOf(Response.class)));
+
+        // an observation response should have been sent
+        Observation observation = observeResponse.getObservation();
+        assertEquals("/3", observation.getPath().toString());
+        assertEquals(helper.getCurrentRegistration().getRegistrationId(), observation.getRegistrationId());
+
+        // *** HACK send time-stamped notification as Leshan client does not support it *** //
+        // create time-stamped nodes
+        TimestampedLwM2mNode mostRecentNode = new TimestampedLwM2mNode(System.currentTimeMillis(),
+                new LwM2mObject(3, new LwM2mObjectInstance(0, LwM2mSingleResource.newStringResource(15, "Paris"))));
+        List<TimestampedLwM2mNode> timestampedNodes = new ArrayList<>();
+        timestampedNodes.add(mostRecentNode);
+        timestampedNodes.add(new TimestampedLwM2mNode(mostRecentNode.getTimestamp() - 2,
+                new LwM2mObject(3, new LwM2mObjectInstance(0, LwM2mSingleResource.newStringResource(15, "Londres")))));
+
+        // encode and send it
+        try (DatagramSocket clientSocket = new DatagramSocket()) {
+            byte[] payload = LwM2mNodeJsonEncoder.encodeTimestampedData(timestampedNodes, new LwM2mPath("/3"),
+                    new LwM2mModel(helper.createObjectModels()));
+
+            // create observe response
+            Response firstCoapResponse = (Response) observeResponse.getCoapResponse();
+            Response response = new Response(org.eclipse.californium.core.coap.CoAP.ResponseCode.CONTENT);
+            response.setType(Type.NON);
+            response.setPayload(payload);
+            response.setMID(firstCoapResponse.getMID() + 1);
+            response.setToken(firstCoapResponse.getToken());
+            response.setOptions(new OptionSet().setContentFormat(ContentFormat.JSON_CODE));
+
+            // serialize response
+            UdpDataSerializer serializer = new UdpDataSerializer();
+            RawData data = serializer.serializeResponse(response);
+
+            // send it
+            clientSocket.send(new DatagramPacket(data.bytes, data.bytes.length,
+                    helper.server.getNonSecureAddress().getAddress(), helper.server.getNonSecureAddress().getPort()));
+        } catch (IOException e) {
+            throw new AssertionError("Error while timestamped notification", e);
+        }
+        // *** Hack End *** //
+
+        // verify result
+        listener.waitForNotification(2000);
+        assertTrue(listener.receivedNotify().get());
+        assertEquals(mostRecentNode.getNode(), listener.getResponse().getContent());
+        assertEquals(timestampedNodes, listener.getResponse().getTimestampedLwM2mNode());
+        assertNotNull(listener.getResponse().getCoapResponse());
+        assertThat(listener.getResponse().getCoapResponse(), is(instanceOf(Response.class)));
     }
 
     private final class TestObservationListener implements ObservationRegistryListener {
 
         private final CountDownLatch latch = new CountDownLatch(1);
         private final AtomicBoolean receivedNotify = new AtomicBoolean();
-        private LwM2mNode content;
+        private ObserveResponse response;
 
         @Override
-        public void newValue(final Observation observation, final LwM2mNode value,
-                List<TimestampedLwM2mNode> timestampedValues) {
+        public void newValue(Observation observation, ObserveResponse response) {
             receivedNotify.set(true);
-            content = value;
+            this.response = response;
             latch.countDown();
         }
 
         @Override
-        public void cancelled(final Observation observation) {
+        public void cancelled(Observation observation) {
             latch.countDown();
         }
 
@@ -174,8 +382,8 @@ public class ObserveTest {
             return receivedNotify;
         }
 
-        public LwM2mNode getContent() {
-            return content;
+        public ObserveResponse getResponse() {
+            return response;
         }
 
         public void waitForNotification(long timeout) throws InterruptedException {
diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/QueueModeTest.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/QueueModeTest.java
index b5b8474c..0c9a0573 100644
--- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/QueueModeTest.java
+++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/QueueModeTest.java
@@ -22,15 +22,12 @@ package org.eclipse.leshan.integration.tests;
 
 import static org.junit.Assert.*;
 
-import java.util.List;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 import org.eclipse.californium.core.server.resources.CoapExchange;
 import org.eclipse.leshan.ResponseCode;
-import org.eclipse.leshan.core.node.LwM2mNode;
-import org.eclipse.leshan.core.node.TimestampedLwM2mNode;
 import org.eclipse.leshan.core.observation.Observation;
 import org.eclipse.leshan.core.request.ObserveRequest;
 import org.eclipse.leshan.core.request.ReadRequest;
@@ -674,8 +671,7 @@ public class QueueModeTest {
         private final AtomicBoolean receivedNotify = new AtomicBoolean();
 
         @Override
-        public void newValue(Observation observation, LwM2mNode mostRecentvalue,
-                List<TimestampedLwM2mNode> timestampedValues) {
+        public void newValue(Observation observation, ObserveResponse response) {
             receivedNotify.set(true);
             latch.countDown();
         }
diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/ReadTest.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/ReadTest.java
index 4616b56c..2284cb6e 100644
--- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/ReadTest.java
+++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/ReadTest.java
@@ -18,8 +18,11 @@
 package org.eclipse.leshan.integration.tests;
 
 import static org.eclipse.leshan.ResponseCode.*;
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsInstanceOf.instanceOf;
 import static org.junit.Assert.*;
 
+import org.eclipse.californium.core.coap.Response;
 import org.eclipse.leshan.core.node.LwM2mObject;
 import org.eclipse.leshan.core.node.LwM2mObjectInstance;
 import org.eclipse.leshan.core.node.LwM2mResource;
@@ -57,6 +60,8 @@ public class ReadTest {
 
         // verify result
         assertEquals(CONTENT, response.getCode());
+        assertNotNull(response.getCoapResponse());
+        assertThat(response.getCoapResponse(), is(instanceOf(Response.class)));
 
         LwM2mObject object = (LwM2mObject) response.getContent();
         assertEquals(2, object.getId());
@@ -70,6 +75,8 @@ public class ReadTest {
 
         // verify result
         assertEquals(CONTENT, response.getCode());
+        assertNotNull(response.getCoapResponse());
+        assertThat(response.getCoapResponse(), is(instanceOf(Response.class)));
 
         LwM2mObject object = (LwM2mObject) response.getContent();
         assertEquals(3, object.getId());
@@ -85,6 +92,8 @@ public class ReadTest {
 
         // verify result
         assertEquals(CONTENT, response.getCode());
+        assertNotNull(response.getCoapResponse());
+        assertThat(response.getCoapResponse(), is(instanceOf(Response.class)));
 
         LwM2mObjectInstance instance = (LwM2mObjectInstance) response.getContent();
         assertEquals(0, instance.getId());
@@ -97,6 +106,8 @@ public class ReadTest {
 
         // verify result
         assertEquals(CONTENT, response.getCode());
+        assertNotNull(response.getCoapResponse());
+        assertThat(response.getCoapResponse(), is(instanceOf(Response.class)));
 
         LwM2mResource resource = (LwM2mResource) response.getContent();
         assertEquals(1, resource.getId());
@@ -110,6 +121,8 @@ public class ReadTest {
 
         // verify result
         assertEquals(METHOD_NOT_ALLOWED, response.getCode());
+        assertNotNull(response.getCoapResponse());
+        assertThat(response.getCoapResponse(), is(instanceOf(Response.class)));
     }
 
     @Test
@@ -119,6 +132,8 @@ public class ReadTest {
 
         // verify result
         assertEquals(NOT_FOUND, response.getCode());
+        assertNotNull(response.getCoapResponse());
+        assertThat(response.getCoapResponse(), is(instanceOf(Response.class)));
     }
 
     @Test
@@ -128,6 +143,8 @@ public class ReadTest {
 
         // verify result
         assertEquals(NOT_FOUND, response.getCode());
+        assertNotNull(response.getCoapResponse());
+        assertThat(response.getCoapResponse(), is(instanceOf(Response.class)));
     }
 
     @Test
@@ -137,6 +154,8 @@ public class ReadTest {
 
         // verify result
         assertEquals(NOT_FOUND, response.getCode());
+        assertNotNull(response.getCoapResponse());
+        assertThat(response.getCoapResponse(), is(instanceOf(Response.class)));
     }
 
     @Test
@@ -146,5 +165,7 @@ public class ReadTest {
 
         // verify result
         assertEquals(NOT_FOUND, response.getCode());
+        assertNotNull(response.getCoapResponse());
+        assertThat(response.getCoapResponse(), is(instanceOf(Response.class)));
     }
 }
diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/WriteTest.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/WriteTest.java
index 8c2f9b5d..55686fc1 100644
--- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/WriteTest.java
+++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/WriteTest.java
@@ -19,12 +19,15 @@
 package org.eclipse.leshan.integration.tests;
 
 import static org.eclipse.leshan.integration.tests.IntegrationTestHelper.*;
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsInstanceOf.instanceOf;
 import static org.junit.Assert.*;
 
 import java.util.Date;
 import java.util.HashMap;
 import java.util.Map;
 
+import org.eclipse.californium.core.coap.Response;
 import org.eclipse.leshan.ResponseCode;
 import org.eclipse.leshan.core.model.ResourceModel.Type;
 import org.eclipse.leshan.core.node.LwM2mMultipleResource;
@@ -86,6 +89,8 @@ public class WriteTest {
 
         // verify result
         assertEquals(ResponseCode.CHANGED, response.getCode());
+        assertNotNull(response.getCoapResponse());
+        assertThat(response.getCoapResponse(), is(instanceOf(Response.class)));
 
         // read resource to check the value changed
         ReadResponse readResponse = helper.server.send(helper.getCurrentRegistration(),
@@ -117,6 +122,8 @@ public class WriteTest {
 
         // verify result
         assertEquals(ResponseCode.CHANGED, response.getCode());
+        assertNotNull(response.getCoapResponse());
+        assertThat(response.getCoapResponse(), is(instanceOf(Response.class)));
 
         // read resource to check the value changed
         ReadResponse readResponse = helper.server.send(helper.getCurrentRegistration(),
@@ -148,6 +155,8 @@ public class WriteTest {
 
         // verify result
         assertEquals(ResponseCode.CHANGED, response.getCode());
+        assertNotNull(response.getCoapResponse());
+        assertThat(response.getCoapResponse(), is(instanceOf(Response.class)));
 
         // read resource to check the value changed
         ReadResponse readResponse = helper.server.send(helper.getCurrentRegistration(),
@@ -179,6 +188,8 @@ public class WriteTest {
 
         // verify result
         assertEquals(ResponseCode.CHANGED, response.getCode());
+        assertNotNull(response.getCoapResponse());
+        assertThat(response.getCoapResponse(), is(instanceOf(Response.class)));
 
         // read resource to check the value changed
         ReadResponse readResponse = helper.server.send(helper.getCurrentRegistration(),
@@ -210,6 +221,8 @@ public class WriteTest {
 
         // verify result
         assertEquals(ResponseCode.CHANGED, response.getCode());
+        assertNotNull(response.getCoapResponse());
+        assertThat(response.getCoapResponse(), is(instanceOf(Response.class)));
 
         // read resource to check the value changed
         ReadResponse readResponse = helper.server.send(helper.getCurrentRegistration(),
@@ -241,6 +254,8 @@ public class WriteTest {
 
         // verify result
         assertEquals(ResponseCode.CHANGED, response.getCode());
+        assertNotNull(response.getCoapResponse());
+        assertThat(response.getCoapResponse(), is(instanceOf(Response.class)));
 
         // read resource to check the value changed
         ReadResponse readResponse = helper.server.send(helper.getCurrentRegistration(),
@@ -257,6 +272,8 @@ public class WriteTest {
 
         // verify result
         assertEquals(ResponseCode.METHOD_NOT_ALLOWED, response.getCode());
+        assertNotNull(response.getCoapResponse());
+        assertThat(response.getCoapResponse(), is(instanceOf(Response.class)));
     }
 
     @Test
@@ -267,6 +284,8 @@ public class WriteTest {
 
         // verify result
         assertEquals(ResponseCode.NOT_FOUND, response.getCode());
+        assertNotNull(response.getCoapResponse());
+        assertThat(response.getCoapResponse(), is(instanceOf(Response.class)));
     }
 
     @Test
@@ -279,6 +298,8 @@ public class WriteTest {
 
         // verify result
         assertEquals(ResponseCode.CHANGED, response.getCode());
+        assertNotNull(response.getCoapResponse());
+        assertThat(response.getCoapResponse(), is(instanceOf(Response.class)));
 
         // read the timezone to check the value changed
         ReadResponse readResponse = helper.server.send(helper.getCurrentRegistration(), new ReadRequest(3, 0));
@@ -294,6 +315,8 @@ public class WriteTest {
 
         // verify result
         assertEquals(ResponseCode.CHANGED, response.getCode());
+        assertNotNull(response.getCoapResponse());
+        assertThat(response.getCoapResponse(), is(instanceOf(Response.class)));
 
         // write server object
         LwM2mResource lifetime = LwM2mSingleResource.newIntegerResource(1, 120);
@@ -305,6 +328,8 @@ public class WriteTest {
 
         // verify result
         assertEquals(ResponseCode.CHANGED, response.getCode());
+        assertNotNull(response.getCoapResponse());
+        assertThat(response.getCoapResponse(), is(instanceOf(Response.class)));
 
         // read the values to check the value changed
         ReadResponse readResponse = helper.server.send(helper.getCurrentRegistration(), new ReadRequest(1, 0));
@@ -326,6 +351,8 @@ public class WriteTest {
 
         // verify result
         assertEquals(ResponseCode.BAD_REQUEST, response.getCode());
+        assertNotNull(response.getCoapResponse());
+        assertThat(response.getCoapResponse(), is(instanceOf(Response.class)));
     }
 
     @Test
@@ -335,6 +362,8 @@ public class WriteTest {
 
         // verify result
         assertEquals(ResponseCode.CHANGED, response.getCode());
+        assertNotNull(response.getCoapResponse());
+        assertThat(response.getCoapResponse(), is(instanceOf(Response.class)));
         // write server object
         LwM2mResource lifetime = LwM2mSingleResource.newIntegerResource(1, 120);
         LwM2mResource defaultMinPeriod = LwM2mSingleResource.newIntegerResource(2, 10);
@@ -343,6 +372,8 @@ public class WriteTest {
 
         // verify result
         assertEquals(ResponseCode.CHANGED, response.getCode());
+        assertNotNull(response.getCoapResponse());
+        assertThat(response.getCoapResponse(), is(instanceOf(Response.class)));
 
         // read the values to check the value changed
         ReadResponse readResponse = helper.server.send(helper.getCurrentRegistration(), new ReadRequest(1, 0));
@@ -365,6 +396,8 @@ public class WriteTest {
 
         // verify result
         assertEquals(ResponseCode.CHANGED, response.getCode());
+        assertNotNull(response.getCoapResponse());
+        assertThat(response.getCoapResponse(), is(instanceOf(Response.class)));
 
         // read the timezone to check the value changed
         ReadResponse readResponse = helper.server.send(helper.getCurrentRegistration(), new ReadRequest(3, 0));
@@ -387,6 +420,8 @@ public class WriteTest {
 
         // Verify Write result
         assertEquals(ResponseCode.CHANGED, response.getCode());
+        assertNotNull(response.getCoapResponse());
+        assertThat(response.getCoapResponse(), is(instanceOf(Response.class)));
 
         // Reading back the written OBJLNK value
         ReadResponse readResponse = helper.server.send(helper.getCurrentRegistration(), new ReadRequest(
@@ -413,6 +448,8 @@ public class WriteTest {
 
         // Verify Write result
         assertEquals(ResponseCode.CHANGED, response.getCode());
+        assertNotNull(response.getCoapResponse());
+        assertThat(response.getCoapResponse(), is(instanceOf(Response.class)));
 
         // Reading back the written OBJLNK value
         ReadResponse readResponse = helper.server.send(helper.getCurrentRegistration(), new ReadRequest(
@@ -433,6 +470,8 @@ public class WriteTest {
 
         // Verify Write result
         assertEquals(ResponseCode.CHANGED, response.getCode());
+        assertNotNull(response.getCoapResponse());
+        assertThat(response.getCoapResponse(), is(instanceOf(Response.class)));
 
         // Reading back the written OBJLNK value
         ReadResponse readResponse = helper.server.send(helper.getCurrentRegistration(), new ReadRequest(ContentFormat.TEXT,