Repository: eclipse/leshan
Pull request #136 (2016-06-01 15:03:15)

 Add a way to choose content format in LeshanServerDemo


Patch:
diff --git a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/impl/ObjectResource.java b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/impl/ObjectResource.java
index c8356281..3bbdda79 100644
--- a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/impl/ObjectResource.java
+++ b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/impl/ObjectResource.java
@@ -19,7 +19,8 @@
  *******************************************************************************/
 package org.eclipse.leshan.client.californium.impl;
 
-import static org.eclipse.leshan.client.californium.impl.ResourceUtil.*;
+import static org.eclipse.leshan.client.californium.impl.ResourceUtil.extractServerIdentity;
+import static org.eclipse.leshan.client.californium.impl.ResourceUtil.fromLwM2mCode;
 
 import java.util.List;
 
@@ -46,7 +47,6 @@ import org.eclipse.leshan.core.node.codec.LwM2mNodeDecoder;
 import org.eclipse.leshan.core.node.codec.LwM2mNodeEncoder;
 import org.eclipse.leshan.core.request.BootstrapWriteRequest;
 import org.eclipse.leshan.core.request.ContentFormat;
-import org.eclipse.leshan.core.request.ContentFormatHelper;
 import org.eclipse.leshan.core.request.CreateRequest;
 import org.eclipse.leshan.core.request.DeleteRequest;
 import org.eclipse.leshan.core.request.DiscoverRequest;
@@ -110,35 +110,46 @@ public class ObjectResource extends CoapResource implements NotifySender {
                 exchange.respond(fromLwM2mCode(response.getCode()), LinkObject.serialize(response.getObjectLinks()),
                         MediaTypeRegistry.APPLICATION_LINK_FORMAT);
             }
-        }
-        // Manage Observe Request
-        else if (exchange.getRequestOptions().hasObserve()) {
-            ObserveResponse response = nodeEnabler.observe(identity, new ObserveRequest(URI));
-            if (response.getCode() == org.eclipse.leshan.ResponseCode.CONTENT) {
-                LwM2mPath path = new LwM2mPath(URI);
-                LwM2mNode content = response.getContent();
-                LwM2mModel model = new LwM2mModel(nodeEnabler.getObjectModel());
-                ContentFormat contentFormat = ContentFormatHelper.compute(path, content, model);
-                exchange.respond(ResponseCode.CONTENT, LwM2mNodeEncoder.encode(content, contentFormat, path, model));
-                return;
-            } else {
-                exchange.respond(fromLwM2mCode(response.getCode()), response.getErrorMessage());
-                return;
+        } else {
+            // handle content format for Read and Observe Request
+            ContentFormat format = ContentFormat.TLV; // use TLV as default format
+            if (exchange.getRequestOptions().hasAccept()) {
+                format = ContentFormat.fromCode(exchange.getRequestOptions().getAccept());
+                if (format == null) {
+                    exchange.respond(ResponseCode.NOT_ACCEPTABLE);
+                    return;
+                }
             }
-        }
-        // Manage Read Request
-        else {
-            ReadResponse response = nodeEnabler.read(identity, new ReadRequest(URI));
-            if (response.getCode() == org.eclipse.leshan.ResponseCode.CONTENT) {
-                LwM2mPath path = new LwM2mPath(URI);
-                LwM2mNode content = response.getContent();
-                LwM2mModel model = new LwM2mModel(nodeEnabler.getObjectModel());
-                ContentFormat contentFormat = ContentFormatHelper.compute(path, content, model);
-                exchange.respond(ResponseCode.CONTENT, LwM2mNodeEncoder.encode(content, contentFormat, path, model));
-                return;
-            } else {
-                exchange.respond(fromLwM2mCode(response.getCode()), response.getErrorMessage());
-                return;
+
+            // Manage Observe Request
+            if (exchange.getRequestOptions().hasObserve()) {
+                ObserveResponse response = nodeEnabler.observe(identity, new ObserveRequest(URI));
+                if (response.getCode() == org.eclipse.leshan.ResponseCode.CONTENT) {
+                    LwM2mPath path = new LwM2mPath(URI);
+                    LwM2mNode content = response.getContent();
+                    LwM2mModel model = new LwM2mModel(nodeEnabler.getObjectModel());
+                    exchange.respond(ResponseCode.CONTENT, LwM2mNodeEncoder.encode(content, format, path, model),
+                            format.getCode());
+                    return;
+                } else {
+                    exchange.respond(fromLwM2mCode(response.getCode()), response.getErrorMessage());
+                    return;
+                }
+            }
+            // Manage Read Request
+            else {
+                ReadResponse response = nodeEnabler.read(identity, new ReadRequest(URI));
+                if (response.getCode() == org.eclipse.leshan.ResponseCode.CONTENT) {
+                    LwM2mPath path = new LwM2mPath(URI);
+                    LwM2mNode content = response.getContent();
+                    LwM2mModel model = new LwM2mModel(nodeEnabler.getObjectModel());
+                    exchange.respond(ResponseCode.CONTENT, LwM2mNodeEncoder.encode(content, format, path, model),
+                            format.getCode());
+                    return;
+                } else {
+                    exchange.respond(fromLwM2mCode(response.getCode()), response.getErrorMessage());
+                    return;
+                }
             }
         }
     }
@@ -157,26 +168,30 @@ public class ObjectResource extends CoapResource implements NotifySender {
 
         // Manage Write Attributes Request
         if (spec != null) {
-            WriteAttributesResponse response = nodeEnabler.writeAttributes(identity, new WriteAttributesRequest(URI,
-                    spec));
+            WriteAttributesResponse response = nodeEnabler.writeAttributes(identity,
+                    new WriteAttributesRequest(URI, spec));
             coapExchange.respond(fromLwM2mCode(response.getCode()), response.getErrorMessage());
             return;
         }
-        // Manage Write and Bootstrap Write Request (replace) and
+        // Manage Write and Bootstrap Write Request (replace)
         else {
             LwM2mPath path = new LwM2mPath(URI);
             ContentFormat contentFormat = ContentFormat.fromCode(coapExchange.getRequestOptions().getContentFormat());
+            if (contentFormat == null) {
+                coapExchange.respond(ResponseCode.UNSUPPORTED_CONTENT_FORMAT);
+                return;
+            }
             LwM2mNode lwM2mNode;
             try {
                 LwM2mModel model = new LwM2mModel(nodeEnabler.getObjectModel());
                 lwM2mNode = LwM2mNodeDecoder.decode(coapExchange.getRequestPayload(), contentFormat, path, model);
                 if (identity.isLwm2mBootstrapServer()) {
-                    BootstrapWriteResponse response = nodeEnabler.write(identity, new BootstrapWriteRequest(path,
-                            lwM2mNode, contentFormat));
+                    BootstrapWriteResponse response = nodeEnabler.write(identity,
+                            new BootstrapWriteRequest(path, lwM2mNode, contentFormat));
                     coapExchange.respond(fromLwM2mCode(response.getCode()), response.getErrorMessage());
                 } else {
-                    WriteResponse response = nodeEnabler.write(identity, new WriteRequest(Mode.REPLACE, contentFormat,
-                            URI, lwM2mNode));
+                    WriteResponse response = nodeEnabler.write(identity,
+                            new WriteRequest(Mode.REPLACE, contentFormat, URI, lwM2mNode));
                     coapExchange.respond(fromLwM2mCode(response.getCode()), response.getErrorMessage());
                 }
 
@@ -205,15 +220,20 @@ public class ObjectResource extends CoapResource implements NotifySender {
             return;
         }
 
+        // handle content format for Write (Update) and Create request
         ContentFormat contentFormat = ContentFormat.fromCode(exchange.getRequestOptions().getContentFormat());
+        if (contentFormat == null) {
+            exchange.respond(ResponseCode.UNSUPPORTED_CONTENT_FORMAT);
+            return;
+        }
         LwM2mModel model = new LwM2mModel(nodeEnabler.getObjectModel());
 
         // Manage Update Instance
         if (path.isObjectInstance()) {
             try {
                 LwM2mNode lwM2mNode = LwM2mNodeDecoder.decode(exchange.getRequestPayload(), contentFormat, path, model);
-                WriteResponse response = nodeEnabler.write(identity, new WriteRequest(Mode.UPDATE, contentFormat, URI,
-                        lwM2mNode));
+                WriteResponse response = nodeEnabler.write(identity,
+                        new WriteRequest(Mode.UPDATE, contentFormat, URI, lwM2mNode));
                 exchange.respond(fromLwM2mCode(response.getCode()), response.getErrorMessage());
             } catch (InvalidValueException e) {
                 LOG.warn("Unable to decode payload to write", e);
@@ -240,8 +260,8 @@ public class ObjectResource extends CoapResource implements NotifySender {
             } else {
                 // the instance Id was not part of the create request payload.
                 // will be assigned by the client.
-                createRequest = new CreateRequest(contentFormat, path.getObjectId(), newInstance.getResources()
-                        .values());
+                createRequest = new CreateRequest(contentFormat, path.getObjectId(),
+                        newInstance.getResources().values());
             }
 
             CreateResponse response = nodeEnabler.create(identity, createRequest);
diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/node/LwM2mPath.java b/leshan-core/src/main/java/org/eclipse/leshan/core/node/LwM2mPath.java
index 56340930..f793b956 100644
--- a/leshan-core/src/main/java/org/eclipse/leshan/core/node/LwM2mPath.java
+++ b/leshan-core/src/main/java/org/eclipse/leshan/core/node/LwM2mPath.java
@@ -18,13 +18,23 @@ package org.eclipse.leshan.core.node;
 import org.eclipse.leshan.util.Validate;
 
 /**
- * A path pointing to a LwM2M node (object, object instance or resource).
+ * A path pointing to a LwM2M node (root, object, object instance, resource or resource instance).
  */
 public class LwM2mPath {
 
-    private final int objectId;
+    private final Integer objectId;
     private final Integer objectInstanceId;
     private final Integer resourceId;
+    private final Integer resourceInstanceId;
+
+    public final static LwM2mPath ROOTPATH = new LwM2mPath();
+
+    private LwM2mPath() {
+        this.objectId = null;
+        this.objectInstanceId = null;
+        this.resourceId = null;
+        this.resourceInstanceId = null;
+    }
 
     /**
      * Create a path to an object
@@ -35,31 +45,49 @@ public class LwM2mPath {
         this.objectId = objectId;
         this.objectInstanceId = null;
         this.resourceId = null;
+        this.resourceInstanceId = null;
     }
 
     /**
      * Create a path to an object instance
      *
      * @param objectId the object identifier
-     * @param objectInstanceId the instance
+     * @param objectInstanceId the instance identifier
      */
     public LwM2mPath(int objectId, int objectInstanceId) {
         this.objectId = objectId;
         this.objectInstanceId = objectInstanceId;
         this.resourceId = null;
+        this.resourceInstanceId = null;
     }
 
     /**
      * Create a path to a resource of a given object instance
      *
      * @param objectId the object identifier
-     * @param objectInstanceId
+     * @param objectInstanceId the instance identifier
      * @param resourceId the resource identifier
      */
     public LwM2mPath(int objectId, int objectInstanceId, int resourceId) {
         this.objectId = objectId;
         this.objectInstanceId = objectInstanceId;
         this.resourceId = resourceId;
+        this.resourceInstanceId = null;
+    }
+
+    /**
+     * Create a path to a resource instance of a given resource
+     *
+     * @param objectId the object identifier
+     * @param objectInstanceId the instance identifier
+     * @param resourceId the resource identifier
+     * @param resourceInstanceId the resource instance identifier
+     */
+    public LwM2mPath(int objectId, int objectInstanceId, int resourceId, int resourceInstanceId) {
+        this.objectId = objectId;
+        this.objectInstanceId = objectInstanceId;
+        this.resourceId = resourceId;
+        this.resourceInstanceId = resourceInstanceId;
     }
 
     /**
@@ -72,32 +100,49 @@ public class LwM2mPath {
         if (path.startsWith("/")) {
             path = path.substring(1);
         }
+        if (path.endsWith("/")) {
+            path = path.substring(0, path.length() - 1);
+        }
         String[] p = path.split("/");
-        if (p.length < 1 || p.length > 3) {
+        if (0 > p.length || p.length > 4) {
             throw new IllegalArgumentException("Invalid length for path: " + path);
         }
         try {
-            this.objectId = Integer.valueOf(p[0]);
+            this.objectId = (p.length >= 1 && !p[0].isEmpty()) ? Integer.valueOf(p[0]) : null;
             this.objectInstanceId = (p.length >= 2) ? Integer.valueOf(p[1]) : null;
-            this.resourceId = (p.length == 3) ? Integer.valueOf(p[2]) : null;
+            this.resourceId = (p.length >= 3) ? Integer.valueOf(p[2]) : null;
+            this.resourceInstanceId = (p.length == 4) ? Integer.valueOf(p[3]) : null;
         } catch (NumberFormatException e) {
             throw new IllegalArgumentException("Invalid elements in path: " + path, e);
         }
     }
 
+    /**
+     * @param path the end of the new path
+     * @return a new path which is the concatenation of this path and the given one in parameter.
+     */
+    public LwM2mPath append(String path) {
+        LwM2mPath pathToAdd = new LwM2mPath(path);
+        if (isRoot()) {
+            return pathToAdd;
+        } else {
+            return new LwM2mPath(this.toString() + pathToAdd.toString());
+        }
+    }
+
     /**
      * Returns the object ID in the path.
      *
-     * @return the object ID
+     * @return the object ID. Can be <code>null</code> when this is an root path.
      */
-    public int getObjectId() {
+    public Integer getObjectId() {
         return objectId;
     }
 
     /**
      * Returns the object instance ID in the path.
      *
-     * @return the object instance ID. Can be <code>null</code> when this is an object path.
+     * @return the object instance ID. Can be <code>null</code> when this is an root/object path.
      */
     public Integer getObjectInstanceId() {
         return objectInstanceId;
@@ -106,45 +151,74 @@ public class LwM2mPath {
     /**
      * Returns the resource ID in the request path.
      *
-     * @return the resource ID. Can be <code>null</code> when this is a object/object instance path.
+     * @return the resource ID. Can be <code>null</code> when this is a root/object/object instance path.
      */
     public Integer getResourceId() {
         return resourceId;
     }
 
+    /**
+     * Returns the resource instance ID in the request path.
+     *
+     * @return the resource instance ID. Can be <code>null</code> when this is a root/object/object instance/resource
+     *         path.
+     */
+    public Integer getResourceInstanceId() {
+        return resourceInstanceId;
+    }
+
+    /**
+     * @return <code>true</code> if this is the root path ("/").
+     */
+    public boolean isRoot() {
+        return objectId == null && objectInstanceId == null && resourceId == null && resourceInstanceId == null;
+    }
+
     /**
      * @return <code>true</code> if this is an Object path.
      */
     public boolean isObject() {
-        return objectInstanceId == null && resourceId == null;
+        return objectId != null && objectInstanceId == null && resourceId == null && resourceInstanceId == null;
     }
 
     /**
      * @return <code>true</code> if this is an ObjectInstance path.
      */
     public boolean isObjectInstance() {
-        return objectInstanceId != null && resourceId == null;
+        return objectId != null && objectInstanceId != null && resourceId == null && resourceInstanceId == null;
     }
 
     /**
      * @return <code>true</code> if this is a Resource path.
      */
     public boolean isResource() {
-        return objectInstanceId != null && resourceId != null;
+        return objectId != null && objectInstanceId != null && resourceId != null && resourceInstanceId == null;
     }
 
     /**
-     * The string representation of the path: /{Object ID}/{ObjectInstance ID}/{Resource ID}
+     * @return <code>true</code> if this is a Resource instance path.
+     */
+    public boolean isResourceInstance() {
+        return objectId != null && objectInstanceId != null && resourceId != null && resourceInstanceId != null;
+    }
+
+    /**
+     * The string representation of the path: /{Object ID}/{ObjectInstance ID}/{Resource ID}/{ResourceInstance ID}
      */
     @Override
     public String toString() {
         StringBuilder b = new StringBuilder();
         b.append("/");
-        b.append(getObjectId());
-        if (getObjectInstanceId() != null) {
-            b.append("/").append(getObjectInstanceId());
-            if (getResourceId() != null) {
-                b.append("/").append(getResourceId());
+        if (getObjectId() != null) {
+            b.append(getObjectId());
+            if (getObjectInstanceId() != null) {
+                b.append("/").append(getObjectInstanceId());
+                if (getResourceId() != null) {
+                    b.append("/").append(getResourceId());
+                    if (getResourceInstanceId() != null) {
+                        b.append("/").append(getResourceInstanceId());
+                    }
+                }
             }
         }
         return b.toString();
@@ -157,6 +231,7 @@ public class LwM2mPath {
         result = prime * result + objectId;
         result = prime * result + ((objectInstanceId == null) ? 0 : objectInstanceId.hashCode());
         result = prime * result + ((resourceId == null) ? 0 : resourceId.hashCode());
+        result = prime * result + ((resourceInstanceId == null) ? 0 : resourceInstanceId.hashCode());
         return result;
     }
 
@@ -172,7 +247,11 @@ public class LwM2mPath {
             return false;
         }
         LwM2mPath other = (LwM2mPath) obj;
-        if (objectId != other.objectId) {
+        if (objectId == null) {
+            if (other.objectId != null) {
+                return false;
+            }
+        } else if (!objectId.equals(other.objectId)) {
             return false;
         }
         if (objectInstanceId == null) {
@@ -189,6 +268,13 @@ public class LwM2mPath {
         } else if (!resourceId.equals(other.resourceId)) {
             return false;
         }
+        if (resourceInstanceId == null) {
+            if (other.resourceInstanceId != null) {
+                return false;
+            }
+        } else if (!resourceInstanceId.equals(other.resourceInstanceId)) {
+            return false;
+        }
         return true;
     }
 
diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/LwM2mNodeDecoder.java b/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/LwM2mNodeDecoder.java
index d46573ba..5a4c8b23 100644
--- a/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/LwM2mNodeDecoder.java
+++ b/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/LwM2mNodeDecoder.java
@@ -66,8 +66,8 @@ public class LwM2mNodeDecoder {
      * @throws InvalidValueException
      */
     @SuppressWarnings("unchecked")
-    public static <T extends LwM2mNode> T decode(byte[] content, ContentFormat format, LwM2mPath path,
-            LwM2mModel model, Class<T> nodeClass) throws InvalidValueException {
+    public static <T extends LwM2mNode> T decode(byte[] content, ContentFormat format, LwM2mPath path, LwM2mModel model,
+            Class<T> nodeClass) throws InvalidValueException {
 
         LOG.debug("Decoding value for path {} and format {}: {}", path, format, content);
         Validate.notNull(path);
@@ -99,7 +99,7 @@ public class LwM2mNodeDecoder {
         case OPAQUE:
             return (T) LwM2mNodeOpaqueDecoder.decode(content, path, model);
         case JSON:
-            return (T) LwM2mNodeJsonDecoder.decode(content, path, model);
+            return LwM2mNodeJsonDecoder.decode(content, path, model, nodeClass);
         case LINK:
             throw new UnsupportedOperationException("Content format " + format + " not yet implemented '" + path + "'");
         }
diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/json/LwM2mNodeJsonDecoder.java b/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/json/LwM2mNodeJsonDecoder.java
index a029d23e..f65c7c90 100644
--- a/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/json/LwM2mNodeJsonDecoder.java
+++ b/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/json/LwM2mNodeJsonDecoder.java
@@ -25,6 +25,7 @@ import org.eclipse.leshan.core.model.ResourceModel;
 import org.eclipse.leshan.core.model.ResourceModel.Type;
 import org.eclipse.leshan.core.node.LwM2mMultipleResource;
 import org.eclipse.leshan.core.node.LwM2mNode;
+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.LwM2mResource;
@@ -42,187 +43,136 @@ public class LwM2mNodeJsonDecoder {
 
     private static final Logger LOG = LoggerFactory.getLogger(LwM2mNodeJsonDecoder.class);
 
-    public static LwM2mNode decode(byte[] content, LwM2mPath path, LwM2mModel model) throws InvalidValueException {
+    public static <T extends LwM2mNode> T decode(byte[] content, LwM2mPath path, LwM2mModel model, Class<T> nodeClass)
+            throws InvalidValueException {
         try {
             String jsonStrValue = new String(content);
             JsonRootObject json = LwM2mJson.fromJsonLwM2m(jsonStrValue);
-            return parseJSON(json, path, model);
+            return parseJSON(json, path, model, nodeClass);
         } catch (LwM2mJsonException e) {
             throw new InvalidValueException("Unable to deSerialize json", path, e);
         }
     }
 
-    private static LwM2mNode parseJSON(JsonRootObject jsonObject, LwM2mPath path, LwM2mModel model)
-            throws InvalidValueException {
+    @SuppressWarnings("unchecked")
+    private static <T extends LwM2mNode> T parseJSON(JsonRootObject jsonObject, LwM2mPath path, LwM2mModel model,
+            Class<T> nodeClass) throws InvalidValueException {
         LOG.trace("Parsing JSON content for path {}: {}", path, jsonObject);
 
-        if (path.isObject()) {
+        if (nodeClass == LwM2mObject.class) {
             // TODO
             // If bn is present will have multiple object instances in JSON payload
             // If JSON contains ObjLnk -> this method should return List<LwM2mNode> ?
             throw new UnsupportedOperationException("JSON object level decoding is not implemented");
-        } else if (path.isObjectInstance()) {
-            // object instance level request
-            Map<Integer, LwM2mResource> resourceMap = parseJsonPayLoadLwM2mResources(jsonObject, path, model);
-            return new LwM2mObjectInstance(path.getObjectInstanceId(), resourceMap.values());
-        } else {
+        } else if (nodeClass == LwM2mObjectInstance.class) {
+            // object instance level request,
+            LwM2mPath baseName = extractAndValidateBaseName(jsonObject, path);
+            Map<Integer, LwM2mResource> resourceMap = parseJsonPayLoadLwM2mResources(jsonObject, path, baseName, model);
+            // try to find instance Id
+            int objectInstanceID = LwM2mObjectInstance.UNDEFINED;
+            if (path.getObjectInstanceId() != null) {
+                objectInstanceID = path.getObjectInstanceId();
+            } else if (baseName != null && baseName.getObjectInstanceId() != null) {
+                objectInstanceID = baseName.getObjectInstanceId();
+            }
+            return (T) new LwM2mObjectInstance(objectInstanceID, resourceMap.values());
+        } else if (nodeClass == LwM2mResource.class) {
             // resource level request
-            Map<Integer, LwM2mResource> resourceMap = parseJsonPayLoadLwM2mResources(jsonObject, path, model);
-            return resourceMap.values().iterator().next();
+            LwM2mPath baseName = extractAndValidateBaseName(jsonObject, path);
+            Map<Integer, LwM2mResource> resourceMap = parseJsonPayLoadLwM2mResources(jsonObject, path, baseName, model);
+            return (T) resourceMap.values().iterator().next();
+        } else {
+            throw new IllegalArgumentException("invalid node class: " + nodeClass);
         }
     }
 
-    private static Map<Integer, LwM2mResource> parseJsonPayLoadLwM2mResources(JsonRootObject jsonObject, LwM2mPath path,
-            LwM2mModel model) throws InvalidValueException {
-
+    private static LwM2mPath extractAndValidateBaseName(JsonRootObject jsonObject, LwM2mPath requestPath)
+            throws InvalidValueException {
         // Check baseName is valid
         if (jsonObject.getBaseName() != null && !jsonObject.getBaseName().isEmpty()) {
-            if (!jsonObject.getBaseName().startsWith("/")) {
-                throw new IllegalArgumentException("Invalid basename path." + path);
-            }
-            if (jsonObject.getBaseName().length() > 1) {
-                LwM2mPath bnPath = new LwM2mPath(jsonObject.getBaseName());
+            LwM2mPath bnPath = new LwM2mPath(jsonObject.getBaseName());
 
-                // check returned base name path is under requested path
-                if (bnPath.getObjectId() != path.getObjectId()) {
+            // check returned base name path is under requested path
+            if (requestPath.getObjectId() != null && bnPath.getObjectId() != null) {
+                if (!bnPath.getObjectId().equals(requestPath.getObjectId())) {
                     throw new InvalidValueException("Basename path does not match requested path.", bnPath);
                 }
-                if (path.getObjectInstanceId() != null) {
-                    if (bnPath.getObjectInstanceId() != null) {
-                        if (bnPath.getObjectInstanceId() != path.getObjectInstanceId()) {
+                if (requestPath.getObjectInstanceId() != null && bnPath.getObjectInstanceId() != null) {
+                    if (!bnPath.getObjectInstanceId().equals(requestPath.getObjectInstanceId())) {
+                        throw new InvalidValueException("Basename path does not match requested path.", bnPath);
+                    }
+                    if (requestPath.getResourceId() != null && bnPath.getResourceId() != null) {
+                        if (!bnPath.getResourceId().equals(requestPath.getResourceId())) {
                             throw new InvalidValueException("Basename path does not match requested path.", bnPath);
                         }
-                        if (path.getResourceId() != null) {
-                            if (bnPath.getResourceId() != null) {
-                                if (bnPath.getResourceId() != path.getResourceId()) {
-                                    throw new InvalidValueException("Basename path does not match requested path.",
-                                            bnPath);
-                                }
-                            }
-                        }
                     }
                 }
             }
+            return bnPath;
         }
+        return null;
+
+    }
+
+    private static Map<Integer, LwM2mResource> parseJsonPayLoadLwM2mResources(JsonRootObject jsonObject,
+            LwM2mPath requestPath, LwM2mPath baseName, LwM2mModel model) throws InvalidValueException {
 
         // Extract LWM2M resources from JSON resource list
         Map<Integer, LwM2mResource> lwM2mResourceMap = new HashMap<>();
-        Map<Integer, Map<Integer, JsonArrayEntry>> multiResourceMap = new HashMap<>();
-        Integer objectId = null;
-        Integer instanceId = null;
+        Map<LwM2mPath, Map<Integer, JsonArrayEntry>> multiResourceMap = new HashMap<>();
         for (int i = 0; i < jsonObject.getResourceList().size(); i++) {
             JsonArrayEntry resourceElt = jsonObject.getResourceList().get(i);
-            Integer resourceId = null;
-            Integer resourceInstanceId = null;
 
             // Build resource path
-            String[] resourcePath;
-            if (jsonObject.getBaseName() != null && !jsonObject.getBaseName().isEmpty()) {
-                // we have a baseName
-                String fullPathStr = jsonObject.getBaseName() + resourceElt.getName();
-                fullPathStr = fullPathStr.substring(1);
-                resourcePath = fullPathStr.split("/");
-                switch (resourcePath.length) {
-                case 3:
-                    objectId = Integer.valueOf(resourcePath[0]);
-                    instanceId = Integer.valueOf(resourcePath[1]);
-                    resourceId = Integer.valueOf(resourcePath[2]);
-                    resourceInstanceId = null;
-                    break;
-                case 4:
-                    objectId = Integer.valueOf(resourcePath[0]);
-                    instanceId = Integer.valueOf(resourcePath[1]);
-                    resourceId = Integer.valueOf(resourcePath[2]);
-                    resourceInstanceId = Integer.valueOf(resourcePath[3]);
-                    ;
-                    break;
-                default:
-                    throw new InvalidValueException("0 Invalid resource path. " + fullPathStr, path);
-                }
+            LwM2mPath nodePath;
+            if (baseName != null) {
+                nodePath = baseName.append(resourceElt.getName());
             } else {
                 // we don't have a baseName so use request path
-                resourcePath = resourceElt.getName().split("/");
-                objectId = path.getObjectId();
-                if (path.isObject()) {
-                    switch (resourcePath.length) {
-                    case 2:
-                        instanceId = Integer.valueOf(resourcePath[0]);
-                        resourceId = Integer.valueOf(resourcePath[1]);
-                        resourceInstanceId = null;
-                        break;
-                    case 3:
-                        instanceId = Integer.valueOf(resourcePath[0]);
-                        resourceId = Integer.valueOf(resourcePath[1]);
-                        resourceInstanceId = Integer.valueOf(resourcePath[2]);
-                        break;
-                    default:
-                        throw new InvalidValueException("1 Resource path does not match requested path."
-                                + resourceElt.getName() + "  " + path.toString(), path);
-                    }
-                } else if (path.isObjectInstance()) {
-                    switch (resourcePath.length) {
-                    case 1:
-                        instanceId = path.getObjectInstanceId();
-                        resourceId = Integer.valueOf(resourcePath[0]);
-                        resourceInstanceId = null;
-                        break;
-                    case 2:
-                        instanceId = path.getObjectInstanceId();
-                        resourceId = Integer.valueOf(resourcePath[0]);
-                        resourceInstanceId = Integer.valueOf(resourcePath[1]);
-                        break;
-                    default:
-                        throw new InvalidValueException("2 Resource path does not match requested path."
-                                + resourceElt.getName() + "  " + path.toString(), path);
-                    }
-                } else if (path.isResource()) {
-                    switch (resourcePath.length) {
-                    case 1:
-                        instanceId = path.getObjectInstanceId();
-                        resourceId = path.getResourceId();
-                        resourceInstanceId = Integer.valueOf(resourcePath[0]);
-                        break;
-                    default:
-                        throw new InvalidValueException("3 Resource path does not match requested path."
-                                + resourceElt.getName() + "  " + path.toString(), path);
-                    }
-                }
+                nodePath = requestPath.append(resourceElt.getName());
             }
 
-            if (resourceInstanceId != null) {
+            // handle LWM2M resources
+            if (nodePath.isResourceInstance()) {
                 // Multi-instance resource
                 // Store multi-instance resource values in a map
-                Map<Integer, JsonArrayEntry> multiResource = multiResourceMap.get(resourceId);
+                // we will deal with it later
+                LwM2mPath resourcePath = new LwM2mPath(nodePath.getObjectId(), nodePath.getObjectInstanceId(),
+                        nodePath.getResourceId());
+                Map<Integer, JsonArrayEntry> multiResource = multiResourceMap.get(resourcePath);
                 if (multiResource == null) {
                     multiResource = new HashMap<>();
-                    multiResourceMap.put(resourceId, multiResource);
+                    multiResourceMap.put(resourcePath, multiResource);
                 }
-                multiResource.put(resourceInstanceId, resourceElt);
-            } else {
+                multiResource.put(nodePath.getResourceInstanceId(), resourceElt);
+            } else if (nodePath.isResource()) {
                 // Single resource
-                LwM2mPath rscPath = new LwM2mPath(objectId, instanceId, resourceId);
-                Type expectedType = getResourceType(rscPath, model, resourceElt);
-                LwM2mResource res = LwM2mSingleResource.newResource(resourceId,
-                        parseJsonValue(resourceElt.getResourceValue(), expectedType, rscPath), expectedType);
-                lwM2mResourceMap.put(resourceId, res);
+                Type expectedType = getResourceType(nodePath, model, resourceElt);
+                LwM2mResource res = LwM2mSingleResource.newResource(nodePath.getResourceId(),
+                        parseJsonValue(resourceElt.getResourceValue(), expectedType, nodePath), expectedType);
+                lwM2mResourceMap.put(nodePath.getResourceId(), res);
+            } else {
+                throw new InvalidValueException(
+                        "Invalid path for resource, it should be a resource or a resource instance path", nodePath);
             }
         }
 
         // Handle multi-instance resource.
-        for (Map.Entry<Integer, Map<Integer, JsonArrayEntry>> entry : multiResourceMap.entrySet()) {
-            Integer key = entry.getKey();
+        for (Map.Entry<LwM2mPath, Map<Integer, JsonArrayEntry>> entry : multiResourceMap.entrySet()) {
+            LwM2mPath resourcePath = entry.getKey();
             Map<Integer, JsonArrayEntry> jsonEntries = entry.getValue();
 
             if (jsonEntries != null && !jsonEntries.isEmpty()) {
-                LwM2mPath rscPath = new LwM2mPath(objectId, instanceId, key);
-                Type expectedType = getResourceType(rscPath, model, jsonEntries.values().iterator().next());
+                Type expectedType = getResourceType(resourcePath, model, jsonEntries.values().iterator().next());
                 Map<Integer, Object> values = new HashMap<>();
                 for (Entry<Integer, JsonArrayEntry> e : jsonEntries.entrySet()) {
-
-                    values.put(e.getKey(), parseJsonValue(e.getValue().getResourceValue(), expectedType, rscPath));
+                    Integer resourceInstanceId = e.getKey();
+                    values.put(resourceInstanceId,
+                            parseJsonValue(e.getValue().getResourceValue(), expectedType, resourcePath));
                 }
-                LwM2mResource res = LwM2mMultipleResource.newResource(key, values, expectedType);
-                lwM2mResourceMap.put(key, res);
+                LwM2mResource resource = LwM2mMultipleResource.newResource(resourcePath.getResourceId(), values,
+                        expectedType);
+                lwM2mResourceMap.put(resourcePath.getResourceId(), resource);
             }
         }
         return lwM2mResourceMap;
diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/json/LwM2mNodeJsonEncoder.java b/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/json/LwM2mNodeJsonEncoder.java
index 134c2771..d8384747 100644
--- a/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/json/LwM2mNodeJsonEncoder.java
+++ b/leshan-core/src/main/java/org/eclipse/leshan/core/node/codec/json/LwM2mNodeJsonEncoder.java
@@ -49,6 +49,7 @@ public class LwM2mNodeJsonEncoder {
         InternalEncoder internalEncoder = new InternalEncoder();
         internalEncoder.objectId = path.getObjectId();
         internalEncoder.model = model;
+        internalEncoder.path = path;
         node.accept(internalEncoder);
         return internalEncoder.encoded;
     }
@@ -57,6 +58,7 @@ public class LwM2mNodeJsonEncoder {
         // visitor inputs
         private int objectId;
         private LwM2mModel model;
+        private LwM2mPath path;
 
         // visitor output
         private byte[] encoded = null;
@@ -78,6 +80,9 @@ public class LwM2mNodeJsonEncoder {
                 resourceList.addAll(lwM2mResourceToJsonArrayEntry(res));
             }
             jsonObject = new JsonRootObject(resourceList);
+            if (path.isObject()) {
+                jsonObject.setBaseName(new LwM2mPath(path.getObjectId(), instance.getId()).toString() + "/");
+            }
             String json = LwM2mJson.toJsonLwM2m(jsonObject);
             encoded = json.getBytes();
         }
@@ -87,6 +92,7 @@ public class LwM2mNodeJsonEncoder {
             LOG.trace("Encoding resource {} into JSON", resource);
             JsonRootObject jsonObject = null;
             jsonObject = new JsonRootObject(lwM2mResourceToJsonArrayEntry(resource));
+            jsonObject.setBaseName(new LwM2mPath(path.getObjectId(), path.getObjectInstanceId()).toString() + "/");
             String json = LwM2mJson.toJsonLwM2m(jsonObject);
             encoded = json.getBytes();
         }
@@ -136,6 +142,7 @@ public class LwM2mNodeJsonEncoder {
                 break;
             case OPAQUE:
                 jsonResource.setStringValue(Base64.encodeBase64String((byte[]) value));
+                break;
             default:
                 throw new IllegalArgumentException("Invalid value type: " + type);
             }
diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/request/AbstractDownlinkRequest.java b/leshan-core/src/main/java/org/eclipse/leshan/core/request/AbstractDownlinkRequest.java
index 75367e98..ce60ffe7 100644
--- a/leshan-core/src/main/java/org/eclipse/leshan/core/request/AbstractDownlinkRequest.java
+++ b/leshan-core/src/main/java/org/eclipse/leshan/core/request/AbstractDownlinkRequest.java
@@ -30,6 +30,13 @@ public abstract class AbstractDownlinkRequest<T extends LwM2mResponse> implement
 
     protected AbstractDownlinkRequest(final LwM2mPath path) {
         Validate.notNull(path);
+        if (path.isRoot()) {
+            throw new IllegalArgumentException("downlink request cannot target root path: " + path.toString());
+        }
+        if (path.isResourceInstance()) {
+            throw new IllegalArgumentException(
+                    "downlink request cannot target resource instance path: " + path.toString());
+        }
         this.path = path;
     }
 
diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/request/BootstrapWriteRequest.java b/leshan-core/src/main/java/org/eclipse/leshan/core/request/BootstrapWriteRequest.java
index 5f16cb95..468991c3 100644
--- a/leshan-core/src/main/java/org/eclipse/leshan/core/request/BootstrapWriteRequest.java
+++ b/leshan-core/src/main/java/org/eclipse/leshan/core/request/BootstrapWriteRequest.java
@@ -15,6 +15,7 @@
  *******************************************************************************/
 package org.eclipse.leshan.core.request;
 
+import org.eclipse.leshan.core.model.ResourceModel.Type;
 import org.eclipse.leshan.core.node.LwM2mNode;
 import org.eclipse.leshan.core.node.LwM2mObject;
 import org.eclipse.leshan.core.node.LwM2mObjectInstance;
@@ -56,18 +57,31 @@ public class BootstrapWriteRequest extends AbstractDownlinkRequest<BootstrapWrit
         // Validate content format
         if (ContentFormat.TEXT == format || ContentFormat.OPAQUE == format) {
             if (!getPath().isResource()) {
-                throw new IllegalArgumentException(String.format("%s format must be used only for single resources",
-                        format.toString()));
+                throw new IllegalArgumentException(
+                        String.format("%s format must be used only for single resources", format.toString()));
             } else {
-                if (((LwM2mResource) node).isMultiInstances()) {
-                    throw new IllegalArgumentException(String.format(
-                            "%s format must be used only for single resources", format.toString()));
+                LwM2mResource resource = (LwM2mResource) node;
+                if (resource.isMultiInstances()) {
+                    throw new IllegalArgumentException(
+                            String.format("%s format must be used only for single resources", format.toString()));
+                } else {
+                    if (resource.getType() == Type.OPAQUE && format == ContentFormat.TEXT) {
+                        throw new IllegalArgumentException(
+                                "TEXT format must not be used for byte array single resources");
+                    } else if (resource.getType() != Type.OPAQUE && format == ContentFormat.OPAQUE) {
+                        throw new IllegalArgumentException(
+                                "OPAQUE format must be used only for byte array single resources");
+                    }
                 }
             }
         }
 
         this.node = node;
-        this.contentFormat = format;
+        if (format == null) {
+            this.contentFormat = ContentFormat.TLV; // use TLV as default content type
+        } else {
+            this.contentFormat = format;
+        }
     }
 
     public LwM2mNode getNode() {
diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/request/ContentFormatHelper.java b/leshan-core/src/main/java/org/eclipse/leshan/core/request/ContentFormatHelper.java
deleted file mode 100644
index bed970bb..00000000
--- a/leshan-core/src/main/java/org/eclipse/leshan/core/request/ContentFormatHelper.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2015 Sierra Wireless and others.
- * 
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the Eclipse Public License v1.0
- * and Eclipse Distribution License v1.0 which accompany this distribution.
- * 
- * The Eclipse Public License is available at
- *    http://www.eclipse.org/legal/epl-v10.html
- * and the Eclipse Distribution License is available at
- *    http://www.eclipse.org/org/documents/edl-v10.html.
- * 
- * Contributors:
- *     Sierra Wireless - initial API and implementation
- *******************************************************************************/
-package org.eclipse.leshan.core.request;
-
-import org.eclipse.leshan.core.model.LwM2mModel;
-import org.eclipse.leshan.core.model.ResourceModel;
-import org.eclipse.leshan.core.model.ResourceModel.Type;
-import org.eclipse.leshan.core.node.LwM2mNode;
-import org.eclipse.leshan.core.node.LwM2mPath;
-import org.eclipse.leshan.core.node.LwM2mResource;
-
-public class ContentFormatHelper {
-
-    /**
-     * Guess the content format for a given payload.
-     * 
-     * By default, {@link ContentFormat#TLV} is used for multiple values payload.
-     */
-    public static ContentFormat compute(LwM2mPath path, LwM2mNode node, LwM2mModel model) {
-        ContentFormat format;
-
-        // Use text for single resource ...
-        if (path.isResource()) {
-            // Use resource description to guess
-            final ResourceModel description = model.getResourceModel(path.getObjectId(), path.getResourceId());
-            if (description != null) {
-                if (description.multiple) {
-                    format = ContentFormat.TLV;
-                } else {
-                    format = description.type == Type.OPAQUE ? ContentFormat.OPAQUE : ContentFormat.TEXT;
-                }
-            }
-            // If no object description available, use 'node' to guess
-            else {
-                LwM2mResource resourceNode = ((LwM2mResource) node);
-                if (resourceNode.isMultiInstances()) {
-                    format = ContentFormat.TLV;
-                } else {
-                    format = resourceNode.getType() == Type.OPAQUE ? ContentFormat.OPAQUE : ContentFormat.TEXT;
-                }
-            }
-        }
-        // ... and TLV for other ones.
-        else {
-            format = ContentFormat.TLV;
-        }
-
-        return format;
-    }
-
-}
diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/request/CreateRequest.java b/leshan-core/src/main/java/org/eclipse/leshan/core/request/CreateRequest.java
index bda051ea..836f947b 100644
--- a/leshan-core/src/main/java/org/eclipse/leshan/core/request/CreateRequest.java
+++ b/leshan-core/src/main/java/org/eclipse/leshan/core/request/CreateRequest.java
@@ -35,7 +35,7 @@ public class CreateRequest extends AbstractDownlinkRequest<CreateResponse> {
     private final List<LwM2mResource> resources;
     private final ContentFormat contentFormat;
 
-    // ***************** constructors without object id ******************* /
+    // ***************** constructors without object instance id ******************* /
 
     /**
      * Creates a request for creating an instance of a particular object without specifying the id of this new instance.
@@ -93,8 +93,8 @@ public class CreateRequest extends AbstractDownlinkRequest<CreateResponse> {
      * @param instance the object instance
      */
     public CreateRequest(ContentFormat contentFormat, int objectId, LwM2mObjectInstance instance) {
-        this(contentFormat, new LwM2mPath(objectId), instance.getId(), instance.getResources().values()
-                .toArray(new LwM2mResource[0]));
+        this(contentFormat, new LwM2mPath(objectId), instance.getId(),
+                instance.getResources().values().toArray(new LwM2mResource[0]));
     }
 
     /**
@@ -109,9 +109,11 @@ public class CreateRequest extends AbstractDownlinkRequest<CreateResponse> {
 
     // ***************** string path constructor ******************* /
     /**
-     * Creates a request for creating an instance of a particular object using the default TLV content format.
+     * Creates a request for creating an instance of a particular object using the default TLV content format. </br>
+     * If the path is an object path, the instance id will be chosen by the client and accessible in the CreateResponse.
+     * To choose instance id at server side, the path must be an object instance path.
      * 
-     * @param path the target path
+     * @param path the target path (object or object instance)
      * @param resources the resource values for the new instance
      */
     public CreateRequest(String path, Collection<LwM2mResource> resources) {
@@ -119,10 +121,12 @@ public class CreateRequest extends AbstractDownlinkRequest<CreateResponse> {
     }
 
     /**
-     * Creates a request for creating an instance of a particular object.
+     * Creates a request for creating an instance of a particular object. </br>
+     * If the path is an object path, the instance id will be chosen by the client and accessible in the CreateResponse.
+     * To choose instance id at server side, the path must be an object instance path.
      * 
-     * @param contentFormat the payload format
-     * @param path the target path
+     * @param contentFormat the payload format (TLV or JSON)
+     * @param path the target path (object or object instance)
      * @param resources the resource values for the new instance
      */
     public CreateRequest(ContentFormat contentFormat, String path, Collection<LwM2mResource> resources) {
@@ -130,9 +134,11 @@ public class CreateRequest extends AbstractDownlinkRequest<CreateResponse> {
     }
 
     /**
-     * Creates a request for creating an instance of a particular object using the default TLV content format.
+     * Creates a request for creating an instance of a particular object using the default TLV content format. </br>
+     * If the path is an object path, the instance id will be chosen by the client and accessible in the CreateResponse.
+     * To choose instance id at server side, the path must be an object instance path.
      * 
-     * @param path the target path
+     * @param path the target path (object or object instance)
      * @param resources the resource values for the new instance
      */
     public CreateRequest(String path, LwM2mResource... resources) {
@@ -140,26 +146,70 @@ public class CreateRequest extends AbstractDownlinkRequest<CreateResponse> {
     }
 
     /**
-     * Creates a request for creating an instance of a particular object.
+     * Creates a request for creating an instance of a particular object.</br>
+     * If the path is an object path, the instance id will be chosen by the client and accessible in the CreateResponse.
+     * To choose instance id at server side, the path must be an object instance path.
      * 
-     * @param contentFormat the payload format
-     * @param path the target path
+     * @param contentFormat the payload format (TLV or JSON)
+     * @param path the target path (object or object instance)
      * @param resources the resource values for the new instance
      */
     public CreateRequest(ContentFormat contentFormat, String path, LwM2mResource... resources) {
         this(contentFormat, new LwM2mPath(path), null, resources);
     }
 
-    // ***************** generic constructor ******************* /
+    /**
+     * Creates a request for creating an instance of a particular object.</br>
+     * If the path is an object path, the instance id will be chosen by the client and accessible in the CreateResponse.
+     * To choose instance id at server side, the path must be an object instance path.
+     * 
+     * @param path the target path (object or object instance)
+     * @param instance the object instance
+     */
+    public CreateRequest(String path, LwM2mObjectInstance instance) {
+        this(null, new LwM2mPath(path), instance.getId(),
+                instance.getResources().values().toArray((new LwM2mResource[instance.getResources().size()])));
+    }
 
+    /**
+     * Creates a request for creating an instance of a particular object.</br>
+     * If the path is an object path, the instance id will be chosen by the client and accessible in the CreateResponse.
+     * To choose instance id at server side, the path must be an object instance path.
+     * 
+     * @param contentFormat the payload format (TLV or JSON)
+     * @param path the target path (object or object instance)
+     * @param instance the object instance
+     */
+    public CreateRequest(ContentFormat contentFormat, String path, LwM2mObjectInstance instance) {
+        this(contentFormat, new LwM2mPath(path), instance.getId(),
+                instance.getResources().values().toArray((new LwM2mResource[instance.getResources().size()])));
+    }
+
+    // ***************** generic constructor ******************* /
     private CreateRequest(ContentFormat format, LwM2mPath target, Integer instanceId, LwM2mResource[] resources) {
         super(target);
 
-        if (!target.isObject()) {
-            throw new IllegalArgumentException("Create request must target an object");
+        // accept only object and object instance path
+        if (!target.isObject() && !target.isObjectInstance()) {
+            throw new IllegalArgumentException("Create request must target an object or object instance");
         }
 
+        // validate instance id
+        if (instanceId != null && instanceId == LwM2mObjectInstance.UNDEFINED) {
+            instanceId = null;
+        }
+        if (target.isObjectInstance()) {
+            if (instanceId == null) {
+                instanceId = target.getObjectInstanceId();
+            } else {
+                if (instanceId != target.getObjectInstanceId()) {
+                    throw new IllegalArgumentException("Conflict between path instance id and node instance id");
+                }
+            }
+        }
         Validate.isTrue(instanceId == null || instanceId >= 0, "Invalid instance id: " + instanceId);
+
+        // store attributes
         this.instanceId = instanceId;
         this.resources = Collections.unmodifiableList(Arrays.asList(resources));
         this.contentFormat = format != null ? format : ContentFormat.TLV; // default to TLV
diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/request/ObserveRequest.java b/leshan-core/src/main/java/org/eclipse/leshan/core/request/ObserveRequest.java
index b132faa0..8a54a155 100644
--- a/leshan-core/src/main/java/org/eclipse/leshan/core/request/ObserveRequest.java
+++ b/leshan-core/src/main/java/org/eclipse/leshan/core/request/ObserveRequest.java
@@ -24,23 +24,25 @@ import org.eclipse.leshan.core.response.ObserveResponse;
  */
 public class ObserveRequest extends AbstractDownlinkRequest<ObserveResponse> {
 
+    private ContentFormat format;
+
     /**
-     * Creates a request for observing future changes of a particular LWM2M node (object, object instance or resource).
+     * Creates a request for observing future changes of all instances of a particular object of a client.
      * 
-     * @param path the path to the LWM2M node to observe
-     * @throw IllegalArgumentException if the path is not valid
+     * @param objectId the object ID of the resource
      */
-    public ObserveRequest(String path) {
-        super(new LwM2mPath(path));
+    public ObserveRequest(int objectId) {
+        this(null, new LwM2mPath(objectId));
     }
 
     /**
      * Creates a request for observing future changes of all instances of a particular object of a client.
      * 
+     * @param format the desired format for the response (TLV or JSON)
      * @param objectId the object ID of the resource
      */
-    public ObserveRequest(int objectId) {
-        super(new LwM2mPath(objectId));
+    public ObserveRequest(ContentFormat format, int objectId) {
+        this(format, new LwM2mPath(objectId));
     }
 
     /**
@@ -50,7 +52,18 @@ public class ObserveRequest extends AbstractDownlinkRequest<ObserveResponse> {
      * @param objectInstanceId the object instance ID
      */
     public ObserveRequest(int objectId, int objectInstanceId) {
-        super(new LwM2mPath(objectId, objectInstanceId));
+        this(null, new LwM2mPath(objectId, objectInstanceId));
+    }
+
+    /**
+     * Creates a request for observing future changes of a particular object instance of a client.
+     * 
+     * @param format the desired format for the response (TLV or JSON)
+     * @param objectId the object ID of the resource
+     * @param objectInstanceId the object instance ID
+     */
+    public ObserveRequest(ContentFormat format, int objectId, int objectInstanceId) {
+        this(format, new LwM2mPath(objectId, objectInstanceId));
     }
 
     /**
@@ -61,7 +74,52 @@ public class ObserveRequest extends AbstractDownlinkRequest<ObserveResponse> {
      * @param resourceId the (individual) resource's ID
      */
     public ObserveRequest(int objectId, int objectInstanceId, int resourceId) {
-        super(new LwM2mPath(objectId, objectInstanceId, resourceId));
+        this(null, new LwM2mPath(objectId, objectInstanceId, resourceId));
+    }
+
+    /**
+     * Creates a request for observing future changes of a specific resource of a client.
+     *
+     * @param format the desired format for the response (TLV, JSON, TEXT or OPAQUE)
+     * @param objectId the object ID of the resource
+     * @param objectInstanceId the object instance ID
+     * @param resourceId the (individual) resource's ID
+     */
+    public ObserveRequest(ContentFormat format, int objectId, int objectInstanceId, int resourceId) {
+        this(format, new LwM2mPath(objectId, objectInstanceId, resourceId));
+    }
+
+    /**
+     * Creates a request for observing future changes of a particular LWM2M node (object, object instance or resource).
+     * 
+     * @param path the path to the LWM2M node to observe
+     * @throw IllegalArgumentException if the path is not valid
+     */
+    public ObserveRequest(String path) {
+        this(null, new LwM2mPath(path));
+    }
+
+    /**
+     * Creates a request for observing future changes of a particular LWM2M node (object, object instance or resource).
+     * 
+     * @param format the desired format for the response
+     * @param path the path to the LWM2M node to observe
+     * @throw IllegalArgumentException if the path is not valid
+     */
+    public ObserveRequest(ContentFormat format, String path) {
+        this(format, new LwM2mPath(path));
+    }
+
+    private ObserveRequest(ContentFormat format, LwM2mPath target) {
+        super(target);
+        this.format = format;
+    }
+
+    /**
+     * @return the desired format of the resource to read
+     */
+    public ContentFormat getFormat() {
+        return format;
     }
 
     @Override
@@ -71,7 +129,7 @@ public class ObserveRequest extends AbstractDownlinkRequest<ObserveResponse> {
 
     @Override
     public final String toString() {
-        return String.format("ObserveRequest [%s]", getPath());
+        return String.format("ObserveRequest [path=%s format=%s]", getPath(), format);
     }
 
 }
diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/request/ReadRequest.java b/leshan-core/src/main/java/org/eclipse/leshan/core/request/ReadRequest.java
index 47e3de87..b780b112 100644
--- a/leshan-core/src/main/java/org/eclipse/leshan/core/request/ReadRequest.java
+++ b/leshan-core/src/main/java/org/eclipse/leshan/core/request/ReadRequest.java
@@ -26,13 +26,25 @@ import org.eclipse.leshan.core.response.ReadResponse;
  */
 public class ReadRequest extends AbstractDownlinkRequest<ReadResponse> {
 
+    private ContentFormat format;
+
     /**
      * Creates a request for reading all instances of a particular object from a client.
      * 
      * @param objectId the object ID of the resource
      */
     public ReadRequest(int objectId) {
-        this(new LwM2mPath(objectId));
+        this(null, new LwM2mPath(objectId));
+    }
+
+    /**
+     * Creates a request for reading all instances of a particular object from a client.
+     * 
+     * @param format the desired format for the response (TLV or JSON)
+     * @param objectId the object ID of the resource
+     */
+    public ReadRequest(ContentFormat format, int objectId) {
+        this(format, new LwM2mPath(objectId));
     }
 
     /**
@@ -42,7 +54,18 @@ public class ReadRequest extends AbstractDownlinkRequest<ReadResponse> {
      * @param objectInstanceId the object instance ID
      */
     public ReadRequest(int objectId, int objectInstanceId) {
-        this(new LwM2mPath(objectId, objectInstanceId));
+        this(null, new LwM2mPath(objectId, objectInstanceId));
+    }
+
+    /**
+     * Creates a request for reading a particular object instance from a client.
+     *
+     * @param format the desired format for the response (TLV or JSON)
+     * @param objectId the object ID of the resource
+     * @param objectInstanceId the object instance ID
+     */
+    public ReadRequest(ContentFormat format, int objectId, int objectInstanceId) {
+        this(format, new LwM2mPath(objectId, objectInstanceId));
     }
 
     /**
@@ -53,7 +76,19 @@ public class ReadRequest extends AbstractDownlinkRequest<ReadResponse> {
      * @param resourceId the (individual) resource's ID
      */
     public ReadRequest(int objectId, int objectInstanceId, int resourceId) {
-        this(new LwM2mPath(objectId, objectInstanceId, resourceId));
+        this(null, new LwM2mPath(objectId, objectInstanceId, resourceId));
+    }
+
+    /**
+     * Creates a request for reading a specific resource from a client.
+     * 
+     * @param format the desired format for the response (TLV, JSON, TEXT or OPAQUE)
+     * @param objectId the object ID of the resource
+     * @param objectInstanceId the object instance ID
+     * @param resourceId the (individual) resource's ID
+     */
+    public ReadRequest(ContentFormat format, int objectId, int objectInstanceId, int resourceId) {
+        this(format, new LwM2mPath(objectId, objectInstanceId, resourceId));
     }
 
     /**
@@ -63,16 +98,35 @@ public class ReadRequest extends AbstractDownlinkRequest<ReadResponse> {
      * @throws IllegalArgumentException if the target path is not valid
      */
     public ReadRequest(String path) {
-        super(new LwM2mPath(path));
+        this(null, new LwM2mPath(path));
     }
 
-    private ReadRequest(LwM2mPath target) {
+    /**
+     * Create a request for reading an object/instance/resource targeted by a specific path.
+     *
+     * @param format the desired format for the response
+     * @param path the path to the LWM2M node to read
+     * @throws IllegalArgumentException if the target path is not valid
+     */
+    public ReadRequest(ContentFormat format, String path) {
+        this(format, new LwM2mPath(path));
+    }
+
+    private ReadRequest(ContentFormat format, LwM2mPath target) {
         super(target);
+        this.format = format;
+    }
+
+    /**
+     * @return the desired format of the resource to read
+     */
+    public ContentFormat getFormat() {
+        return format;
     }
 
     @Override
     public final String toString() {
-        return String.format("ReadRequest [%s]", getPath());
+        return String.format("ReadRequest [path=%s format=%s]", getPath(), format);
     }
 
     @Override
diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/request/WriteRequest.java b/leshan-core/src/main/java/org/eclipse/leshan/core/request/WriteRequest.java
index eea89fe5..dd68ff72 100644
--- a/leshan-core/src/main/java/org/eclipse/leshan/core/request/WriteRequest.java
+++ b/leshan-core/src/main/java/org/eclipse/leshan/core/request/WriteRequest.java
@@ -117,50 +117,98 @@ public class WriteRequest extends AbstractDownlinkRequest<WriteResponse> {
 
     // ***************** write single value resource ****************** //
     /**
-     * Request to write a <b>String Single-Instance Resource</b> using the TEXT content format.
+     * Request to write a <b>String Single-Instance Resource</b> using the TLV content format.
      */
     public WriteRequest(final int objectId, final int objectInstanceId, final int resourceId, String value) {
-        this(Mode.REPLACE, ContentFormat.TEXT, new LwM2mPath(objectId, objectInstanceId, resourceId),
+        this(ContentFormat.TLV, objectId, objectInstanceId, resourceId, value);
+    }
+
+    /**
+     * Request to write a <b>String Single-Instance Resource</b> using the given content format (TEXT, TLV, JSON).
+     */
+    public WriteRequest(final ContentFormat contentFormat, final int objectId, final int objectInstanceId,
+            final int resourceId, String value) {
+        this(Mode.REPLACE, contentFormat, new LwM2mPath(objectId, objectInstanceId, resourceId),
                 LwM2mSingleResource.newStringResource(resourceId, value));
     }
 
     /**
-     * Request to write a <b>Boolean Single-Instance Resource</b> using the TEXT content format.
+     * Request to write a <b>Boolean Single-Instance Resource</b> using the TLV content format.
      */
     public WriteRequest(final int objectId, final int objectInstanceId, final int resourceId, boolean value) {
-        this(Mode.REPLACE, ContentFormat.TEXT, new LwM2mPath(objectId, objectInstanceId, resourceId),
+        this(ContentFormat.TLV, objectId, objectInstanceId, resourceId, value);
+    }
+
+    /**
+     * Request to write a <b>Boolean Single-Instance Resource</b> using the given content format (TEXT, TLV, JSON).
+     */
+    public WriteRequest(final ContentFormat contentFormat, final int objectId, final int objectInstanceId,
+            final int resourceId, boolean value) {
+        this(Mode.REPLACE, contentFormat, new LwM2mPath(objectId, objectInstanceId, resourceId),
                 LwM2mSingleResource.newBooleanResource(resourceId, value));
     }
 
     /**
-     * Request to write a <b>Integer Single-Instance Resource</b> using the TEXT content format.
+     * Request to write a <b>Integer Single-Instance Resource</b> using the TLV content format.
      */
     public WriteRequest(final int objectId, final int objectInstanceId, final int resourceId, long value) {
-        this(Mode.REPLACE, ContentFormat.TEXT, new LwM2mPath(objectId, objectInstanceId, resourceId),
+        this(ContentFormat.TLV, objectId, objectInstanceId, resourceId, value);
+    }
+
+    /**
+     * Request to write a <b>Integer Single-Instance Resource</b> using the given content format (TEXT, TLV, JSON).
+     */
+    public WriteRequest(final ContentFormat contentFormat, final int objectId, final int objectInstanceId,
+            final int resourceId, long value) {
+        this(Mode.REPLACE, contentFormat, new LwM2mPath(objectId, objectInstanceId, resourceId),
                 LwM2mSingleResource.newIntegerResource(resourceId, value));
     }
 
     /**
-     * Request to write a <b> Float Single-Instance Resource</b> using the TEXT content format.
+     * Request to write a <b> Float Single-Instance Resource</b> using the TLV content format.
      */
     public WriteRequest(final int objectId, final int objectInstanceId, final int resourceId, double value) {
-        this(Mode.REPLACE, ContentFormat.TEXT, new LwM2mPath(objectId, objectInstanceId, resourceId),
+        this(ContentFormat.TLV, objectId, objectInstanceId, resourceId, value);
+    }
+
+    /**
+     * Request to write a <b> Float Single-Instance Resource</b> using the given content format (TEXT, TLV, JSON).
+     */
+    public WriteRequest(final ContentFormat contentFormat, final int objectId, final int objectInstanceId,
+            final int resourceId, double value) {
+        this(Mode.REPLACE, contentFormat, new LwM2mPath(objectId, objectInstanceId, resourceId),
                 LwM2mSingleResource.newFloatResource(resourceId, value));
     }
 
     /**
-     * Request to write a <b> Date Single-Instance Resource</b> using the TEXT content format.
+     * Request to write a <b> Date Single-Instance Resource</b> using the TLV content format.
      */
     public WriteRequest(final int objectId, final int objectInstanceId, final int resourceId, Date value) {
-        this(Mode.REPLACE, ContentFormat.TEXT, new LwM2mPath(objectId, objectInstanceId, resourceId),
+        this(ContentFormat.TLV, objectId, objectInstanceId, resourceId, value);
+    }
+
+    /**
+     * Request to write a <b> Date Single-Instance Resource</b> using the given content format (TEXT, TLV, JSON).
+     */
+    public WriteRequest(final ContentFormat contentFormat, final int objectId, final int objectInstanceId,
+            final int resourceId, Date value) {
+        this(Mode.REPLACE, contentFormat, new LwM2mPath(objectId, objectInstanceId, resourceId),
                 LwM2mSingleResource.newDateResource(resourceId, value));
     }
 
     /**
-     * Request to write a <b> Binary Single-Instance Resource</b> using the OPAQUE content format.
+     * Request to write a <b> Binary Single-Instance Resource</b> using the TLV content format.
      */
     public WriteRequest(final int objectId, final int objectInstanceId, final int resourceId, byte[] value) {
-        this(Mode.REPLACE, ContentFormat.OPAQUE, new LwM2mPath(objectId, objectInstanceId, resourceId),
+        this(ContentFormat.TLV, objectId, objectInstanceId, resourceId, value);
+    }
+
+    /**
+     * Request to write a <b> Binary Single-Instance Resource</b> using the given content format (OPAQUE, TLV, JSON).
+     */
+    public WriteRequest(final ContentFormat contentFormat, final int objectId, final int objectInstanceId,
+            final int resourceId, byte[] value) {
+        this(Mode.REPLACE, contentFormat, new LwM2mPath(objectId, objectInstanceId, resourceId),
                 LwM2mSingleResource.newBinaryResource(resourceId, value));
     }
 
@@ -241,15 +289,28 @@ public class WriteRequest extends AbstractDownlinkRequest<WriteResponse> {
                 throw new IllegalArgumentException(
                         String.format("%s format must be used only for single resources", format.toString()));
             } else {
-                if (((LwM2mResource) node).isMultiInstances()) {
+                LwM2mResource resource = (LwM2mResource) node;
+                if (resource.isMultiInstances()) {
                     throw new IllegalArgumentException(
                             String.format("%s format must be used only for single resources", format.toString()));
+                } else {
+                    if (resource.getType() == Type.OPAQUE && format == ContentFormat.TEXT) {
+                        throw new IllegalArgumentException(
+                                "TEXT format must not be used for byte array single resources");
+                    } else if (resource.getType() != Type.OPAQUE && format == ContentFormat.OPAQUE) {
+                        throw new IllegalArgumentException(
+                                "OPAQUE format must be used only for byte array single resources");
+                    }
                 }
             }
         }
 
         this.node = node;
-        this.contentFormat = format;
+        if (format == null) {
+            this.contentFormat = ContentFormat.TLV; // use TLV as default content type
+        } else {
+            this.contentFormat = format;
+        }
         this.mode = mode;
     }
 
@@ -282,7 +343,8 @@ public class WriteRequest extends AbstractDownlinkRequest<WriteResponse> {
 
     @Override
     public String toString() {
-        return String.format("WriteRequest [Mode=%s, getPath()=%s]", mode, getPath());
+        return String.format("WriteRequest [mode=%s, path=%s, format=%s, node=%s]", mode, getPath(), contentFormat,
+                node);
     }
 
 }
\ No newline at end of file
diff --git a/leshan-core/src/main/java/org/eclipse/leshan/tlv/TlvEncoder.java b/leshan-core/src/main/java/org/eclipse/leshan/tlv/TlvEncoder.java
index 57de7263..ba7d4741 100644
--- a/leshan-core/src/main/java/org/eclipse/leshan/tlv/TlvEncoder.java
+++ b/leshan-core/src/main/java/org/eclipse/leshan/tlv/TlvEncoder.java
@@ -27,7 +27,7 @@ public class TlvEncoder {
 
     private static final Logger LOG = LoggerFactory.getLogger(TlvEncoder.class);
 
-    private static final int MAX_LENGTH_8BIT  = 256;
+    private static final int MAX_LENGTH_8BIT = 256;
     private static final int MAX_LENGTH_16BIT = 65_536;
     private static final int MAX_LENGTH_24BIT = 16_777_216;
 
@@ -81,13 +81,12 @@ public class TlvEncoder {
      */
     public static byte[] encodeFloat(Number number) {
         ByteBuffer fBuf = null;
-        double dValue = number.doubleValue();
-        if (dValue >= Float.MIN_VALUE && dValue <= Float.MAX_VALUE) {
+        if (number instanceof Float) {
             fBuf = ByteBuffer.allocate(4);
-            fBuf.putFloat((float) dValue);
+            fBuf.putFloat(number.floatValue());
         } else {
             fBuf = ByteBuffer.allocate(8);
-            fBuf.putDouble(dValue);
+            fBuf.putDouble(number.doubleValue());
         }
         return fBuf.array();
     }
diff --git a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/impl/CoapRequestBuilder.java b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/impl/CoapRequestBuilder.java
index 5d3a231b..e2f972fd 100644
--- a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/impl/CoapRequestBuilder.java
+++ b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/impl/CoapRequestBuilder.java
@@ -25,7 +25,6 @@ import org.eclipse.leshan.core.request.BootstrapDeleteRequest;
 import org.eclipse.leshan.core.request.BootstrapFinishRequest;
 import org.eclipse.leshan.core.request.BootstrapWriteRequest;
 import org.eclipse.leshan.core.request.ContentFormat;
-import org.eclipse.leshan.core.request.ContentFormatHelper;
 import org.eclipse.leshan.core.request.CreateRequest;
 import org.eclipse.leshan.core.request.DeleteRequest;
 import org.eclipse.leshan.core.request.DiscoverRequest;
@@ -52,6 +51,8 @@ public class CoapRequestBuilder implements DownlinkRequestVisitor {
     @Override
     public void visit(ReadRequest request) {
         coapRequest = Request.newGet();
+        if (request.getFormat() != null)
+            coapRequest.getOptions().setAccept(request.getFormat().getCode());
         setTarget(coapRequest, destination, request.getPath());
     }
 
@@ -66,9 +67,6 @@ public class CoapRequestBuilder implements DownlinkRequestVisitor {
     public void visit(WriteRequest request) {
         coapRequest = request.isReplaceRequest() ? Request.newPut() : Request.newPost();
         ContentFormat format = request.getContentFormat();
-        if (format == null) {
-            format = ContentFormatHelper.compute(request.getPath(), request.getNode(), model);
-        }
         coapRequest.getOptions().setContentFormat(format.getCode());
         coapRequest.setPayload(LwM2mNodeEncoder.encode(request.getNode(), format, request.getPath(), model));
         setTarget(coapRequest, destination, request.getPath());
@@ -110,6 +108,8 @@ public class CoapRequestBuilder implements DownlinkRequestVisitor {
     @Override
     public void visit(ObserveRequest request) {
         coapRequest = Request.newGet();
+        if (request.getFormat() != null)
+            coapRequest.getOptions().setAccept(request.getFormat().getCode());
         coapRequest.setObserve();
         setTarget(coapRequest, destination, request.getPath());
     }
@@ -118,9 +118,6 @@ public class CoapRequestBuilder implements DownlinkRequestVisitor {
     public void visit(BootstrapWriteRequest request) {
         coapRequest = Request.newPut();
         ContentFormat format = request.getContentFormat();
-        if (format == null) {
-            format = ContentFormatHelper.compute(request.getPath(), request.getNode(), model);
-        }
         coapRequest.getOptions().setContentFormat(format.getCode());
         coapRequest.setPayload(LwM2mNodeEncoder.encode(request.getNode(), format, request.getPath(), model));
         setTarget(coapRequest, destination, request.getPath());
diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/model/StandardModelProvider.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/model/StandardModelProvider.java
index 122d377b..d46fed8f 100644
--- a/leshan-server-core/src/main/java/org/eclipse/leshan/server/model/StandardModelProvider.java
+++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/model/StandardModelProvider.java
@@ -15,21 +15,18 @@
  *******************************************************************************/
 package org.eclipse.leshan.server.model;
 
-import org.eclipse.leshan.core.model.LwM2mModel;
 import org.eclipse.leshan.core.model.ObjectLoader;
-import org.eclipse.leshan.server.client.Client;
 
-public class StandardModelProvider implements LwM2mModelProvider {
-
-    private final LwM2mModel model;
+/**
+ * A static model provider which uses the default model embedded in Leshan. </br>
+ * </br>
+ * The MODELS_FOLDER environment variable can be used to add more model definitions.</br>
+ * The MODELS_FOLDER should be set with the folder path where your custom models are available. Currently the Leshan
+ * JSON format and OMA DDF file format are supported.
+ */
+public class StandardModelProvider extends StaticModelProvider {
 
     public StandardModelProvider() {
-        this.model = new LwM2mModel(ObjectLoader.loadDefault());
-    }
-
-    @Override
-    public LwM2mModel getObjectModel(Client client) {
-        // same model for all clients
-        return model;
+        super(ObjectLoader.loadDefault());
     }
 }
diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/model/StaticModelProvider.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/model/StaticModelProvider.java
new file mode 100644
index 00000000..59f6b341
--- /dev/null
+++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/model/StaticModelProvider.java
@@ -0,0 +1,43 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Sierra Wireless and others.
+ * 
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ * 
+ * The Eclipse Public License is available at
+ *    http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ *    http://www.eclipse.org/org/documents/edl-v10.html.
+ * 
+ * Contributors:
+ *     Sierra Wireless - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.leshan.server.model;
+
+import java.util.Collection;
+
+import org.eclipse.leshan.core.model.LwM2mModel;
+import org.eclipse.leshan.core.model.ObjectModel;
+import org.eclipse.leshan.server.client.Client;
+
+/**
+ * A LwM2mModelProvider which uses only one model for all registered clients.
+ */
+public class StaticModelProvider implements LwM2mModelProvider {
+    private final LwM2mModel model;
+
+    public StaticModelProvider(Collection<ObjectModel> objects) {
+        this(new LwM2mModel(objects));
+    }
+
+    public StaticModelProvider(LwM2mModel model) {
+        this.model = model;
+    }
+
+    @Override
+    public LwM2mModel getObjectModel(Client client) {
+        // same model for all clients
+        return model;
+    }
+}
diff --git a/leshan-server-demo/src/main/java/org/eclipse/leshan/server/demo/servlet/ClientServlet.java b/leshan-server-demo/src/main/java/org/eclipse/leshan/server/demo/servlet/ClientServlet.java
index 8350be5f..931ea30f 100644
--- a/leshan-server-demo/src/main/java/org/eclipse/leshan/server/demo/servlet/ClientServlet.java
+++ b/leshan-server-demo/src/main/java/org/eclipse/leshan/server/demo/servlet/ClientServlet.java
@@ -17,8 +17,6 @@ package org.eclipse.leshan.server.demo.servlet;
 
 import java.io.IOException;
 import java.util.Collection;
-import java.util.HashMap;
-import java.util.Map;
 
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServlet;
@@ -65,6 +63,8 @@ import com.google.gson.JsonSyntaxException;
  */
 public class ClientServlet extends HttpServlet {
 
+    private static final String FORMAT_PARAM = "format";
+
     private static final Logger LOG = LoggerFactory.getLogger(ClientServlet.class);
 
     private static final long TIMEOUT = 5000; // ms
@@ -130,7 +130,13 @@ public class ClientServlet extends HttpServlet {
             String target = StringUtils.removeStart(req.getPathInfo(), "/" + clientEndpoint);
             Client client = server.getClientRegistry().get(clientEndpoint);
             if (client != null) {
-                ReadRequest request = new ReadRequest(target);
+                // get content format
+                String contentFormatParam = req.getParameter(FORMAT_PARAM);
+                ContentFormat contentFormat = contentFormatParam != null
+                        ? ContentFormat.valueOf(contentFormatParam.toUpperCase()) : null;
+
+                // create & process request
+                ReadRequest request = new ReadRequest(contentFormat, target);
                 ReadResponse cResponse = server.send(client, request, TIMEOUT);
                 processDeviceResponse(req, resp, cResponse);
             } else {
@@ -170,7 +176,15 @@ public class ClientServlet extends HttpServlet {
             String target = StringUtils.removeStart(req.getPathInfo(), "/" + clientEndpoint);
             Client client = server.getClientRegistry().get(clientEndpoint);
             if (client != null) {
-                WriteResponse cResponse = this.writeRequest(client, target, req, resp);
+                // get content format
+                String contentFormatParam = req.getParameter(FORMAT_PARAM);
+                ContentFormat contentFormat = contentFormatParam != null
+                        ? ContentFormat.valueOf(contentFormatParam.toUpperCase()) : null;
+
+                // create & process request
+                LwM2mNode node = extractLwM2mNode(target, req);
+                WriteRequest request = new WriteRequest(Mode.REPLACE, contentFormat, target, node);
+                WriteResponse cResponse = server.send(client, request, TIMEOUT);
                 processDeviceResponse(req, resp, cResponse);
             } else {
                 resp.setStatus(HttpServletResponse.SC_BAD_REQUEST);
@@ -205,7 +219,13 @@ public class ClientServlet extends HttpServlet {
                 String target = StringUtils.substringBetween(req.getPathInfo(), clientEndpoint, "/observe");
                 Client client = server.getClientRegistry().get(clientEndpoint);
                 if (client != null) {
-                    ObserveRequest request = new ObserveRequest(target);
+                    // get content format
+                    String contentFormatParam = req.getParameter(FORMAT_PARAM);
+                    ContentFormat contentFormat = contentFormatParam != null
+                            ? ContentFormat.valueOf(contentFormatParam.toUpperCase()) : null;
+
+                    // create & process request
+                    ObserveRequest request = new ObserveRequest(contentFormat, target);
                     ObserveResponse cResponse = server.send(client, request, TIMEOUT);
                     processDeviceResponse(req, resp, cResponse);
                 } else {
@@ -263,8 +283,20 @@ public class ClientServlet extends HttpServlet {
             try {
                 Client client = server.getClientRegistry().get(clientEndpoint);
                 if (client != null) {
-                    CreateResponse cResponse = this.createRequest(client, target, req, resp);
-                    processDeviceResponse(req, resp, cResponse);
+                    // get content format
+                    String contentFormatParam = req.getParameter(FORMAT_PARAM);
+                    ContentFormat contentFormat = contentFormatParam != null
+                            ? ContentFormat.valueOf(contentFormatParam.toUpperCase()) : null;
+
+                    // create & process request
+                    LwM2mNode node = extractLwM2mNode(target, req);
+                    if (node instanceof LwM2mObjectInstance) {
+                        CreateRequest request = new CreateRequest(contentFormat, target, (LwM2mObjectInstance) node);
+                        CreateResponse cResponse = server.send(client, request, TIMEOUT);
+                        processDeviceResponse(req, resp, cResponse);
+                    } else {
+                        throw new IllegalArgumentException("payload must contain an object instance");
+                    }
                 } else {
                     resp.setStatus(HttpServletResponse.SC_BAD_REQUEST);
                     resp.getWriter().format("no registered client with id '%s'", clientEndpoint).flush();
@@ -355,40 +387,9 @@ public class ClientServlet extends HttpServlet {
             resp.getOutputStream().write(response.getBytes());
             resp.setStatus(HttpServletResponse.SC_OK);
         }
-
-    }
-
-    // TODO refactor the code to remove this method.
-    private WriteResponse writeRequest(Client client, String target, HttpServletRequest req, HttpServletResponse resp)
-            throws IOException, InterruptedException {
-        String contentType = StringUtils.substringBefore(req.getContentType(), ";");
-
-        if ("text/plain".equals(contentType)) {
-            String content = IOUtils.toString(req.getInputStream(), req.getCharacterEncoding());
-            int rscId = Integer.valueOf(target.substring(target.lastIndexOf("/") + 1));
-            WriteRequest writeRequest = new WriteRequest(Mode.REPLACE, ContentFormat.TEXT, target,
-                    LwM2mSingleResource.newStringResource(rscId, content));
-            return server.send(client, writeRequest, TIMEOUT);
-
-        } else if ("application/json".equals(contentType)) {
-            String content = IOUtils.toString(req.getInputStream(), req.getCharacterEncoding());
-            LwM2mNode node = null;
-            try {
-                node = gson.fromJson(content, LwM2mNode.class);
-            } catch (JsonSyntaxException e) {
-                throw new IllegalArgumentException("unable to parse json to tlv:" + e.getMessage(), e);
-            }
-            return server.send(client, new WriteRequest(Mode.REPLACE, null, target, node), TIMEOUT);
-
-        } else {
-            throw new IllegalArgumentException("content type " + req.getContentType()
-                    + " not supported for write requests");
-        }
     }
 
-    // TODO refactor the code to remove this method.
-    private CreateResponse createRequest(Client client, String target, HttpServletRequest req, HttpServletResponse resp)
-            throws IOException, InterruptedException {
+    private LwM2mNode extractLwM2mNode(String target, HttpServletRequest req) throws IOException {
         String contentType = StringUtils.substringBefore(req.getContentType(), ";");
         if ("application/json".equals(contentType)) {
             String content = IOUtils.toString(req.getInputStream(), req.getCharacterEncoding());
@@ -398,15 +399,12 @@ public class ClientServlet extends HttpServlet {
             } catch (JsonSyntaxException e) {
                 throw new IllegalArgumentException("unable to parse json to tlv:" + e.getMessage(), e);
             }
-            if (!(node instanceof LwM2mObjectInstance)) {
-                throw new IllegalArgumentException("payload must contain an object instance");
-            }
-            return server.send(client, new CreateRequest(target, ((LwM2mObjectInstance) node).getResources().values()),
-                    TIMEOUT);
-        } else {
-            throw new IllegalArgumentException("content type " + req.getContentType()
-                    + " not supported for write requests");
+            return node;
+        } else if ("text/plain".equals(contentType)) {
+            String content = IOUtils.toString(req.getInputStream(), req.getCharacterEncoding());
+            int rscId = Integer.valueOf(target.substring(target.lastIndexOf("/") + 1));
+            return LwM2mSingleResource.newStringResource(rscId, content);
         }
+        throw new IllegalArgumentException("content type " + req.getContentType() + " not supported");
     }
-
 }
diff --git a/leshan-server-demo/src/main/resources/webapp/css/lw-app.css b/leshan-server-demo/src/main/resources/webapp/css/lw-app.css
index fdc4eab8..2facc58a 100644
--- a/leshan-server-demo/src/main/resources/webapp/css/lw-app.css
+++ b/leshan-server-demo/src/main/resources/webapp/css/lw-app.css
@@ -129,6 +129,16 @@ table.table-href > tbody{
 	cursor: pointer;
 }
 
+/* define minimum size for dropdown menu content format*/
+.dropdown-menu-contentformat{
+	min-width: 75px;
+}
+
+/* remove dotted line for abbreviation */
+abbr[title] {
+	border-bottom: none;
+}
+
 .panel-heading {
 	cursor: pointer;
 }
diff --git a/leshan-server-demo/src/main/resources/webapp/js/client-controllers.js b/leshan-server-demo/src/main/resources/webapp/js/client-controllers.js
index 809453c8..b3a213a0 100644
--- a/leshan-server-demo/src/main/resources/webapp/js/client-controllers.js
+++ b/leshan-server-demo/src/main/resources/webapp/js/client-controllers.js
@@ -145,6 +145,11 @@ lwClientControllers.controller('ClientDetailCtrl', [
             }
         });
 
+        // default format
+        $scope.settings={};
+        $scope.settings.multi = {format:"TLV"};
+        $scope.settings.single = {format:"TLV"};
+
         $scope.clientId = $routeParams.clientId;
 
         // get client details
diff --git a/leshan-server-demo/src/main/resources/webapp/js/instance-directives.js b/leshan-server-demo/src/main/resources/webapp/js/instance-directives.js
index 10f45fc3..5c61932b 100644
--- a/leshan-server-demo/src/main/resources/webapp/js/instance-directives.js
+++ b/leshan-server-demo/src/main/resources/webapp/js/instance-directives.js
@@ -22,7 +22,8 @@ angular.module('instanceDirectives', [])
         replace: true,
         scope: {
             instance: '=',
-            parent: '='
+            parent: '=',
+            settings: '='
         },
         templateUrl: "partials/instance.html",
         link: function (scope, element, attrs) {
@@ -35,8 +36,9 @@ angular.module('instanceDirectives', [])
             scope.instance.observe = {tooltip : "Observe <br/>" + scope.instance.path};
 
             scope.read = function() {
+                var format = scope.settings.multi.format;
                 var uri = "api/clients/" + $routeParams.clientId + scope.instance.path;
-                $http.get(uri)
+                $http.get(uri, {params:{format:format}})
                 .success(function(data, status, headers, config) {
                     // manage request information
                     var read = scope.instance.read;
@@ -122,7 +124,8 @@ angular.module('instanceDirectives', [])
                         }
                     }
                     // Send request
-                    $http({method: 'PUT', url: "api/clients/" + $routeParams.clientId + scope.instance.path, data: payload, headers:{'Content-Type': 'application/json'}})
+                    var format = scope.settings.multi.format;
+                    $http({method: 'PUT', url: "api/clients/" + $routeParams.clientId + scope.instance.path, data: payload, headers:{'Content-Type': 'application/json'}, params:{format:format}})
                     .success(function(data, status, headers, config) {
                         write = scope.instance.write;
                         write.date = new Date();
@@ -149,8 +152,9 @@ angular.module('instanceDirectives', [])
             };
 
             scope.startObserve = function() {
+                var format = scope.settings.multi.format;
                 var uri = "api/clients/" + $routeParams.clientId + scope.instance.path+"/observe";
-                $http.post(uri)
+                $http.post(uri, null, {params:{format:format}})
                 .success(function(data, status, headers, config) {
                     var observe = scope.instance.observe;
                     observe.date = new Date();
diff --git a/leshan-server-demo/src/main/resources/webapp/js/object-directives.js b/leshan-server-demo/src/main/resources/webapp/js/object-directives.js
index 9c075ba4..253a5789 100644
--- a/leshan-server-demo/src/main/resources/webapp/js/object-directives.js
+++ b/leshan-server-demo/src/main/resources/webapp/js/object-directives.js
@@ -22,14 +22,11 @@ angular.module('objectDirectives', [])
         replace: true,
         scope: {
             object: '=',
-            parent: '='
+            settings: '='
         },
         templateUrl: "partials/object.html",
         link: function (scope, element, attrs) {
             var parentPath = "";
-            if(scope.parent) {
-                parentPath = scope.parent.path;
-            }
             scope.status = {};
             scope.status.open = true;
             
@@ -62,8 +59,9 @@ angular.module('objectDirectives', [])
                         } 
                     }
                     // Send request
-                    var instancepath  = scope.object.path
-                    $http({method: 'POST', url: "api/clients/" + $routeParams.clientId + instancepath, data: payload, headers:{'Content-Type': 'application/json'}})
+                    var format = scope.settings.multi.format;
+                    var instancepath  = scope.object.path;
+                    $http({method: 'POST', url: "api/clients/" + $routeParams.clientId + instancepath, data: payload, headers:{'Content-Type': 'application/json'}, params:{format:format}})
                     .success(function(data, status, headers, config) {
                         create = scope.object.create;
                         create.date = new Date();
diff --git a/leshan-server-demo/src/main/resources/webapp/js/resource-directives.js b/leshan-server-demo/src/main/resources/webapp/js/resource-directives.js
index cafc07e1..afb065dd 100644
--- a/leshan-server-demo/src/main/resources/webapp/js/resource-directives.js
+++ b/leshan-server-demo/src/main/resources/webapp/js/resource-directives.js
@@ -22,7 +22,8 @@ angular.module('resourceDirectives', [])
         replace: true,
         scope: {
             resource: '=',
-            parent: '='
+            parent: '=',
+            settings: '='
         },
         templateUrl: "partials/resource.html",
         link: function (scope, element, attrs) {
@@ -59,8 +60,9 @@ angular.module('resourceDirectives', [])
             }
 
             scope.startObserve = function() {
+                var format = scope.settings.single.format;
                 var uri = "api/clients/" + $routeParams.clientId + scope.resource.path+"/observe";
-                $http.post(uri)
+                $http.post(uri, null,{params:{format:format}})
                 .success(function(data, status, headers, config) {
                     var observe = scope.resource.observe;
                     observe.date = new Date();
@@ -107,8 +109,9 @@ angular.module('resourceDirectives', [])
             
             
             scope.read = function() {
+                var format = scope.settings.single.format;
                 var uri = "api/clients/" + $routeParams.clientId + scope.resource.path;
-                $http.get(uri)
+                $http.get(uri, {params:{format:format}})
                 .success(function(data, status, headers, config) {
                     // manage request information
                     var read = scope.resource.read;
@@ -157,7 +160,8 @@ angular.module('resourceDirectives', [])
                         value = lwResources.getTypedValue(value, scope.resource.def.type);
                         rsc["value"] = value;
 
-                        $http({method: 'PUT', url: "api/clients/" + $routeParams.clientId + scope.resource.path, data: rsc, headers:{'Content-Type': 'application/json'}})
+                        var format = scope.settings.single.format;
+                        $http({method: 'PUT', url: "api/clients/" + $routeParams.clientId + scope.resource.path, data: rsc, headers:{'Content-Type': 'application/json'},params:{format:format}})
                         .success(function(data, status, headers, config) {
                             write = scope.resource.write;
                             write.date = new Date();
diff --git a/leshan-server-demo/src/main/resources/webapp/partials/client-detail.html b/leshan-server-demo/src/main/resources/webapp/partials/client-detail.html
index dfcdda0a..bab356c1 100644
--- a/leshan-server-demo/src/main/resources/webapp/partials/client-detail.html
+++ b/leshan-server-demo/src/main/resources/webapp/partials/client-detail.html
@@ -1,10 +1,38 @@
 <div ng-if='client'>
 	<ol class="breadcrumb">
-  		<li><a href="#/clients/">Clients</a></li>
-  		<li class="active">{{ clientId }}</li>
+		<li><a href="#/clients/">Clients</a></li>
+		<li class="active">{{ clientId }}</li>
+
+		<span class="pull-right">
+			<form class="form-inline">
+			<abbr title="Content Format used for object, object instance and multi-resource instance">Multi-value</abbr>
+			<div class="btn-group" dropdown is-open="multi.status.isopen">
+				<button type="button" class="btn btn-xs btn-default dropdown-toggle dropdown-menu-contentformat" dropdown-toggle ng-disabled="disabled">
+					{{settings.multi.format}} <span class="caret"></span>
+				</button>
+				<ul class="dropdown-menu dropdown-menu-contentformat" role="menu">
+					<li><a ng-click="settings.multi.format='TLV'">TLV</a></li>
+					<li><a ng-click="settings.multi.format='JSON'">JSON</a></li>
+				</ul>
+			</div>
+			<abbr title="Content Format used for single resource">Single-value</abbr>
+			<div class="btn-group" dropdown is-open="single.status.isopen">
+				<button type="button" class="btn btn-xs btn-default dropdown-toggle dropdown-menu-contentformat" dropdown-toggle ng-disabled="disabled">
+					{{settings.single.format}} <span class="caret"></span>
+				</button>
+				<ul class="dropdown-menu dropdown-menu-contentformat" role="menu">
+					<li><a ng-click="settings.single.format='TLV'">TLV</a></li>
+					<li><a ng-click="settings.single.format='JSON'">JSON</a></li>
+					<li><a ng-click="settings.single.format='Text'">Text</a></li>
+					<li><a ng-click="settings.single.format='Opaque'">Opaque</a></li>
+				</ul>
+			</div>
+			</form>
+		</span>
 	</ol>
+
 	<accordion close-others=false>
-		<object ng-repeat='object in objects' object='object' parent='parent'></object>
+		<object ng-repeat='object in objects' object='object' settings='settings'></object>
 	</accordion>
 
 	<!-- CoAP logs -->
@@ -18,20 +46,20 @@
 		</div> 
 		<div collapse="coapLogsCollapsed" class="coaplogs-panel">
 			<table class="table coaplogs-header">
-		        <thead>
-		            <tr>
+				<thead>
+					<tr>
 						<th class="col-md-1">Time</th>
 						<th class="col-md-1">CoAP Message</th>
 						<th class="col-md-1">MID</th>
 						<th class="col-md-1">Token</th>
 						<th class="col-md-4">Options</th>
 						<th class="col-md-4">Payload</th>
-		            </tr>
-		        </thead>
-		  	</table>
+					</tr>
+				</thead>
+			</table>
 			<div class="coaplogs-table">
 				<table class="table table-striped">
-				    <tbody>
+					<tbody>
 						<tr ng-repeat="log in coaplogs">
 							<td class="col-md-1">{{log.date}}</td>
 							<td class="col-md-1"><span class="glyphicon" ng-class="{'glyphicon-arrow-left' : !log.incoming, 'glyphicon-arrow-right' : log.incoming}" aria-hidden="true"></span>  {{log.type}}-{{log.code}}</td>
@@ -40,7 +68,7 @@
 							<td class="col-md-4">{{log.options}}</td>
 							<td class="col-md-4">{{log.payload}}</td>
 						</tr>
-				    </tbody>
+					</tbody>
 				</table>
 			</div>
 		</div>
diff --git a/leshan-server-demo/src/main/resources/webapp/partials/instance.html b/leshan-server-demo/src/main/resources/webapp/partials/instance.html
index 2d1e927c..fb875edb 100644
--- a/leshan-server-demo/src/main/resources/webapp/partials/instance.html
+++ b/leshan-server-demo/src/main/resources/webapp/partials/instance.html
@@ -54,5 +54,5 @@
 			</div>
 		</div>
 	</div>
-	<resource ng-repeat='resource in instance.resources' resource='resource' parent='instance'></resource>
+	<resource ng-repeat='resource in instance.resources' resource='resource' parent='instance' settings='settings'></resource>
 </div>
diff --git a/leshan-server-demo/src/main/resources/webapp/partials/object.html b/leshan-server-demo/src/main/resources/webapp/partials/object.html
index 894a7b29..5dd3f24d 100644
--- a/leshan-server-demo/src/main/resources/webapp/partials/object.html
+++ b/leshan-server-demo/src/main/resources/webapp/partials/object.html
@@ -24,6 +24,6 @@
 				</div>
 			</div>
 		</div>
-		<instance ng-repeat='instance in object.instances' instance='instance' parent='object'></instance>
+		<instance ng-repeat='instance in object.instances' instance='instance' parent='object' settings='settings'></instance>
 	</accordion-group>
 </div>
\ No newline at end of file


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 843ac5d4..ea7bcf94 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
@@ -77,8 +77,8 @@ public class CreateTest {
     @Test
     public void can_create_specific_instance_of_object() throws InterruptedException {
         // create ACL instance
-        LwM2mObjectInstance instance = new LwM2mObjectInstance(12, Arrays.<LwM2mResource> asList(LwM2mSingleResource
-                .newIntegerResource(3, 123)));
+        LwM2mObjectInstance instance = new LwM2mObjectInstance(12,
+                Arrays.<LwM2mResource> asList(LwM2mSingleResource.newIntegerResource(3, 123)));
         CreateResponse response = helper.server.send(helper.getClient(), new CreateRequest(2, instance));
 
         // verify result
@@ -86,15 +86,13 @@ public class CreateTest {
         assertEquals("2/12", response.getLocation());
     }
 
-    // TODO JSON encoding: instance id is missing (support of basename tag probably needed)
-    @Ignore
     @Test
     public void can_create_specific_instance_of_object_with_json() throws InterruptedException {
         // create ACL instance
-        LwM2mObjectInstance instance = new LwM2mObjectInstance(12, Arrays.<LwM2mResource> asList(LwM2mSingleResource
-                .newIntegerResource(3, 123)));
-        CreateResponse response = helper.server.send(helper.getClient(), new CreateRequest(ContentFormat.JSON, 2,
-                instance));
+        LwM2mObjectInstance instance = new LwM2mObjectInstance(12,
+                Arrays.<LwM2mResource> asList(LwM2mSingleResource.newIntegerResource(3, 123)));
+        CreateResponse response = helper.server.send(helper.getClient(),
+                new CreateRequest(ContentFormat.JSON, 2, instance));
 
         // verify result
         assertEquals(ResponseCode.CREATED, response.getCode());
@@ -157,8 +155,8 @@ public class CreateTest {
     @Test
     public void cannot_create_mandatory_single_object() throws InterruptedException {
         // try to create another instance of device object
-        CreateResponse response = helper.server.send(helper.getClient(), new CreateRequest(3,
-                new LwM2mResource[] { LwM2mSingleResource.newIntegerResource(3, 123) }));
+        CreateResponse response = helper.server.send(helper.getClient(),
+                new CreateRequest(3, new LwM2mResource[] { LwM2mSingleResource.newIntegerResource(3, 123) }));
 
         // verify result
         assertEquals(ResponseCode.METHOD_NOT_ALLOWED, response.getCode());
@@ -166,8 +164,8 @@ public class CreateTest {
 
     @Test
     public void cannot_create_instance_of_security_object() throws InterruptedException {
-        CreateResponse response = helper.server.send(helper.getClient(), new CreateRequest(0,
-                new LwM2mResource[] { LwM2mSingleResource.newStringResource(0, "new.dest") }));
+        CreateResponse response = helper.server.send(helper.getClient(),
+                new CreateRequest(0, new LwM2mResource[] { LwM2mSingleResource.newStringResource(0, "new.dest") }));
 
         // verify result
         assertEquals(ResponseCode.NOT_FOUND, response.getCode());
diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/IntegrationTestHelper.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/IntegrationTestHelper.java
index 49bdc590..7d351284 100644
--- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/IntegrationTestHelper.java
+++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/IntegrationTestHelper.java
@@ -30,6 +30,12 @@ import org.eclipse.leshan.client.object.Security;
 import org.eclipse.leshan.client.object.Server;
 import org.eclipse.leshan.client.resource.LwM2mObjectEnabler;
 import org.eclipse.leshan.client.resource.ObjectsInitializer;
+import org.eclipse.leshan.core.model.LwM2mModel;
+import org.eclipse.leshan.core.model.ObjectLoader;
+import org.eclipse.leshan.core.model.ObjectModel;
+import org.eclipse.leshan.core.model.ResourceModel;
+import org.eclipse.leshan.core.model.ResourceModel.Operations;
+import org.eclipse.leshan.core.model.ResourceModel.Type;
 import org.eclipse.leshan.core.request.BindingMode;
 import org.eclipse.leshan.core.response.ExecuteResponse;
 import org.eclipse.leshan.server.californium.LeshanServerBuilder;
@@ -38,6 +44,7 @@ import org.eclipse.leshan.server.client.Client;
 import org.eclipse.leshan.server.client.ClientRegistryListener;
 import org.eclipse.leshan.server.client.ClientUpdate;
 import org.eclipse.leshan.server.impl.SecurityRegistryImpl;
+import org.eclipse.leshan.server.model.StaticModelProvider;
 
 /**
  * Helper for running a server and executing a client against it.
@@ -49,6 +56,14 @@ public class IntegrationTestHelper {
     static final String MODEL_NUMBER = "IT-TEST-123";
     public static final long LIFETIME = 2;
 
+    public static final int TEST_OBJECT_ID = 2000;
+    public static final int STRING_RESOURCE_ID = 0;
+    public static final int BOOLEAN_RESOURCE_ID = 1;
+    public static final int INTEGER_RESOURCE_ID = 2;
+    public static final int FLOAT_RESOURCE_ID = 3;
+    public static final int TIME_RESOURCE_ID = 4;
+    public static final int OPAQUE_RESOURCE_ID = 5;
+
     LeshanServer server;
     LwM2mClient client;
     Client last_registration;
@@ -57,13 +72,35 @@ public class IntegrationTestHelper {
     private CountDownLatch deregisterLatch;
     private CountDownLatch updateLatch;
 
+    protected List<ObjectModel> createObjectModels() {
+        // load default object from the spec
+        List<ObjectModel> objectModels = ObjectLoader.loadDefault();
+        // define custom model for testing purpose
+        ResourceModel stringfield = new ResourceModel(STRING_RESOURCE_ID, "stringres", Operations.RW, false, false,
+                Type.STRING, null, null, null);
+        ResourceModel booleanfield = new ResourceModel(BOOLEAN_RESOURCE_ID, "booleanres", Operations.RW, false, false,
+                Type.BOOLEAN, null, null, null);
+        ResourceModel integerfield = new ResourceModel(INTEGER_RESOURCE_ID, "integerres", Operations.RW, false, false,
+                Type.INTEGER, null, null, null);
+        ResourceModel floatfield = new ResourceModel(FLOAT_RESOURCE_ID, "floatres", Operations.RW, false, false,
+                Type.FLOAT, null, null, null);
+        ResourceModel timefield = new ResourceModel(TIME_RESOURCE_ID, "timeres", Operations.RW, false, false, Type.TIME,
+                null, null, null);
+        ResourceModel opaquefield = new ResourceModel(OPAQUE_RESOURCE_ID, "opaque", Operations.RW, false, false,
+                Type.OPAQUE, null, null, null);
+        objectModels.add(new ObjectModel(TEST_OBJECT_ID, "testobject", null, false, false, stringfield, booleanfield,
+                integerfield, floatfield, timefield, opaquefield));
+
+        return objectModels;
+    }
+
     public void createClient() {
+
         // Create objects Enabler
-        ObjectsInitializer initializer = new ObjectsInitializer();
-        initializer.setInstancesForObject(
-                LwM2mId.SECURITY,
-                Security.noSec("coap://" + server.getNonSecureAddress().getHostString() + ":"
-                        + server.getNonSecureAddress().getPort(), 12345));
+        ObjectsInitializer initializer = new ObjectsInitializer(new LwM2mModel(createObjectModels()));
+        initializer.setInstancesForObject(LwM2mId.SECURITY, Security.noSec(
+                "coap://" + server.getNonSecureAddress().getHostString() + ":" + server.getNonSecureAddress().getPort(),
+                12345));
         initializer.setInstancesForObject(LwM2mId.SERVER, new Server(12345, LIFETIME, BindingMode.U, false));
         initializer.setInstancesForObject(LwM2mId.DEVICE, new Device("Eclipse Leshan", MODEL_NUMBER, "12345", "U") {
             @Override
@@ -76,7 +113,7 @@ public class IntegrationTestHelper {
             }
         });
         List<LwM2mObjectEnabler> objects = initializer.createMandatory();
-        objects.add(initializer.create(2));
+        objects.addAll(initializer.create(2, 2000));
 
         // Build Client
         LeshanClientBuilder builder = new LeshanClientBuilder(ENDPOINT_IDENTIFIER);
@@ -86,6 +123,7 @@ public class IntegrationTestHelper {
 
     public void createServer() {
         LeshanServerBuilder builder = new LeshanServerBuilder();
+        builder.setObjectModelProvider(new StaticModelProvider(createObjectModels()));
         builder.setLocalAddress(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0));
         builder.setLocalSecureAddress(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0));
         builder.setSecurityRegistry(new SecurityRegistryImpl() {
@@ -103,7 +141,6 @@ public class IntegrationTestHelper {
             }
         });
         server = builder.build();
-
         // monitor client registration
         resetLatch();
         server.getClientRegistry().addListener(new ClientRegistryListener() {
diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/RegistrationTest.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/RegistrationTest.java
index b932001c..22ac49a0 100644
--- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/RegistrationTest.java
+++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/RegistrationTest.java
@@ -77,7 +77,7 @@ public class RegistrationTest {
         Client client = helper.server.getClientRegistry().get(ENDPOINT_IDENTIFIER);
         assertNotNull(client);
         // TODO </0/0> should not be part of the object links
-        assertArrayEquals(LinkObject.parse("</>;rt=\"oma.lwm2m\",</1/0>,</2>,</3/0>".getBytes()),
+        assertArrayEquals(LinkObject.parse("</>;rt=\"oma.lwm2m\",</1/0>,</2>,</3/0>,</2000/0>".getBytes()),
                 client.getObjectLinks());
 
         // Check for update
@@ -101,7 +101,7 @@ public class RegistrationTest {
         assertEquals(1, helper.server.getClientRegistry().allClients().size());
         Client client = helper.server.getClientRegistry().get(ENDPOINT_IDENTIFIER);
         assertNotNull(client);
-        assertArrayEquals(LinkObject.parse("</>;rt=\"oma.lwm2m\",</1/0>,</2>,</3/0>".getBytes()),
+        assertArrayEquals(LinkObject.parse("</>;rt=\"oma.lwm2m\",</1/0>,</2>,</3/0>,</2000/0>".getBytes()),
                 client.getObjectLinks());
 
         // Stop client with out de-registration
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 36e59fb4..6468890a 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
@@ -18,10 +18,20 @@
 
 package org.eclipse.leshan.integration.tests;
 
+import static org.eclipse.leshan.integration.tests.IntegrationTestHelper.BOOLEAN_RESOURCE_ID;
+import static org.eclipse.leshan.integration.tests.IntegrationTestHelper.FLOAT_RESOURCE_ID;
+import static org.eclipse.leshan.integration.tests.IntegrationTestHelper.INTEGER_RESOURCE_ID;
+import static org.eclipse.leshan.integration.tests.IntegrationTestHelper.OPAQUE_RESOURCE_ID;
+import static org.eclipse.leshan.integration.tests.IntegrationTestHelper.STRING_RESOURCE_ID;
+import static org.eclipse.leshan.integration.tests.IntegrationTestHelper.TEST_OBJECT_ID;
+import static org.eclipse.leshan.integration.tests.IntegrationTestHelper.TIME_RESOURCE_ID;
+import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 
+import java.util.Date;
+
 import org.eclipse.leshan.ResponseCode;
 import org.eclipse.leshan.core.node.LwM2mObjectInstance;
 import org.eclipse.leshan.core.node.LwM2mResource;
@@ -55,33 +65,190 @@ public class WriteTest {
     }
 
     @Test
-    public void can_write_replace_resource() throws InterruptedException {
-        // write device timezone
-        final String timeZone = "Europe/Paris";
-        WriteResponse response = helper.server.send(helper.getClient(), new WriteRequest(3, 0, 15, timeZone));
+    public void can_write_string_resource_in_text() throws InterruptedException {
+        write_string_resource(ContentFormat.TEXT);
+    }
+
+    @Test
+    public void can_write_string_resource_in_tlv() throws InterruptedException {
+        write_string_resource(ContentFormat.TLV);
+    }
+
+    @Test
+    // TODO fix json format
+    public void can_write_string_resource_in_json() throws InterruptedException {
+        write_string_resource(ContentFormat.JSON);
+    }
+
+    private void write_string_resource(ContentFormat format) throws InterruptedException {
+        // write resource
+        final String expectedvalue = "stringvalue";
+        WriteResponse response = helper.server.send(helper.getClient(),
+                new WriteRequest(format, TEST_OBJECT_ID, 0, STRING_RESOURCE_ID, expectedvalue));
 
         // verify result
         assertEquals(ResponseCode.CHANGED, response.getCode());
 
-        // read the timezone to check the value changed
-        ReadResponse readResponse = helper.server.send(helper.getClient(), new ReadRequest(3, 0, 15));
+        // read resource to check the value changed
+        ReadResponse readResponse = helper.server.send(helper.getClient(),
+                new ReadRequest(TEST_OBJECT_ID, 0, STRING_RESOURCE_ID));
         LwM2mResource resource = (LwM2mResource) readResponse.getContent();
-        assertEquals(timeZone, resource.getValue());
+        assertEquals(expectedvalue, resource.getValue());
     }
 
     @Test
-    public void can_write_replace_resource_in_json() throws InterruptedException {
-        // write device timezone
-        final String timeZone = "Europe/Paris";
-        WriteResponse response = helper.server.send(helper.getClient(), new WriteRequest(3, 0, 15, timeZone));
+    public void can_write_boolean_resource_in_text() throws InterruptedException {
+        write_boolean_resource(ContentFormat.TEXT);
+    }
+
+    @Test
+    public void can_write_boolean_resource_in_tlv() throws InterruptedException {
+        write_boolean_resource(ContentFormat.TLV);
+    }
+
+    @Test
+    public void can_write_boolean_resource_in_json() throws InterruptedException {
+        write_boolean_resource(ContentFormat.JSON);
+    }
+
+    private void write_boolean_resource(ContentFormat format) throws InterruptedException {
+        // write resource
+        final boolean expectedvalue = true;
+        WriteResponse response = helper.server.send(helper.getClient(),
+                new WriteRequest(format, TEST_OBJECT_ID, 0, BOOLEAN_RESOURCE_ID, expectedvalue));
 
         // verify result
         assertEquals(ResponseCode.CHANGED, response.getCode());
 
-        // read the timezone to check the value changed
-        ReadResponse readResponse = helper.server.send(helper.getClient(), new ReadRequest(3, 0, 15));
+        // read resource to check the value changed
+        ReadResponse readResponse = helper.server.send(helper.getClient(),
+                new ReadRequest(TEST_OBJECT_ID, 0, BOOLEAN_RESOURCE_ID));
+        LwM2mResource resource = (LwM2mResource) readResponse.getContent();
+        assertEquals(expectedvalue, resource.getValue());
+    }
+
+    @Test
+    public void can_write_integer_resource_in_text() throws InterruptedException {
+        write_integer_resource(ContentFormat.TEXT);
+    }
+
+    @Test
+    public void can_write_integer_resource_in_tlv() throws InterruptedException {
+        write_integer_resource(ContentFormat.TLV);
+    }
+
+    @Test
+    public void can_write_integer_resource_in_json() throws InterruptedException {
+        write_integer_resource(ContentFormat.JSON);
+    }
+
+    private void write_integer_resource(ContentFormat format) throws InterruptedException {
+        // write resource
+        final long expectedvalue = 999l;
+        WriteResponse response = helper.server.send(helper.getClient(),
+                new WriteRequest(format, TEST_OBJECT_ID, 0, INTEGER_RESOURCE_ID, expectedvalue));
+
+        // verify result
+        assertEquals(ResponseCode.CHANGED, response.getCode());
+
+        // read resource to check the value changed
+        ReadResponse readResponse = helper.server.send(helper.getClient(),
+                new ReadRequest(TEST_OBJECT_ID, 0, INTEGER_RESOURCE_ID));
+        LwM2mResource resource = (LwM2mResource) readResponse.getContent();
+        assertEquals(expectedvalue, resource.getValue());
+    }
+
+    @Test
+    public void can_write_float_resource_in_text() throws InterruptedException {
+        write_float_resource(ContentFormat.TEXT);
+    }
+
+    @Test
+    public void can_write_float_resource_in_tlv() throws InterruptedException {
+        write_float_resource(ContentFormat.TLV);
+    }
+
+    @Test
+    public void can_write_float_resource_in_json() throws InterruptedException {
+        write_float_resource(ContentFormat.JSON);
+    }
+
+    private void write_float_resource(ContentFormat format) throws InterruptedException {
+        // write resource
+        final double expectedvalue = 999.99;
+        WriteResponse response = helper.server.send(helper.getClient(),
+                new WriteRequest(format, TEST_OBJECT_ID, 0, FLOAT_RESOURCE_ID, expectedvalue));
+
+        // verify result
+        assertEquals(ResponseCode.CHANGED, response.getCode());
+
+        // read resource to check the value changed
+        ReadResponse readResponse = helper.server.send(helper.getClient(),
+                new ReadRequest(TEST_OBJECT_ID, 0, FLOAT_RESOURCE_ID));
+        LwM2mResource resource = (LwM2mResource) readResponse.getContent();
+        assertEquals(expectedvalue, resource.getValue());
+    }
+
+    @Test
+    public void can_write_time_resource_in_text() throws InterruptedException {
+        write_time_resource(ContentFormat.TEXT);
+    }
+
+    @Test
+    public void can_write_time_resource_in_tlv() throws InterruptedException {
+        write_time_resource(ContentFormat.TLV);
+    }
+
+    @Test
+    public void can_write_time_resource_in_json() throws InterruptedException {
+        write_time_resource(ContentFormat.JSON);
+    }
+
+    private void write_time_resource(ContentFormat format) throws InterruptedException {
+        // write resource
+        final Date expectedvalue = new Date(946681000l); // second accuracy
+        WriteResponse response = helper.server.send(helper.getClient(),
+                new WriteRequest(format, TEST_OBJECT_ID, 0, TIME_RESOURCE_ID, expectedvalue));
+
+        // verify result
+        assertEquals(ResponseCode.CHANGED, response.getCode());
+
+        // read resource to check the value changed
+        ReadResponse readResponse = helper.server.send(helper.getClient(),
+                new ReadRequest(TEST_OBJECT_ID, 0, TIME_RESOURCE_ID));
+        LwM2mResource resource = (LwM2mResource) readResponse.getContent();
+        assertEquals(expectedvalue, resource.getValue());
+    }
+
+    @Test
+    public void can_write_opaque_resource_in_opaque() throws InterruptedException {
+        write_opaque_resource(ContentFormat.OPAQUE);
+    }
+
+    @Test
+    public void can_write_opaque_resource_in_tlv() throws InterruptedException {
+        write_opaque_resource(ContentFormat.TLV);
+    }
+
+    @Test
+    public void can_write_opaque_resource_in_json() throws InterruptedException {
+        write_opaque_resource(ContentFormat.JSON);
+    }
+
+    private void write_opaque_resource(ContentFormat format) throws InterruptedException {
+        // write resource
+        final byte[] expectedvalue = new byte[] { 1, 2, 3 };
+        WriteResponse response = helper.server.send(helper.getClient(),
+                new WriteRequest(format, TEST_OBJECT_ID, 0, OPAQUE_RESOURCE_ID, expectedvalue));
+
+        // verify result
+        assertEquals(ResponseCode.CHANGED, response.getCode());
+
+        // read resource to check the value changed
+        ReadResponse readResponse = helper.server.send(helper.getClient(),
+                new ReadRequest(TEST_OBJECT_ID, 0, OPAQUE_RESOURCE_ID));
         LwM2mResource resource = (LwM2mResource) readResponse.getContent();
-        assertEquals(timeZone, resource.getValue());
+        assertArrayEquals(expectedvalue, (byte[]) resource.getValue());
     }
 
     @Test
diff --git a/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/impl/CaliforniumObservationTest.java b/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/impl/CaliforniumObservationTest.java
index fd939758..1efc63a1 100644
--- a/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/impl/CaliforniumObservationTest.java
+++ b/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/impl/CaliforniumObservationTest.java
@@ -64,7 +64,7 @@ public class CaliforniumObservationTest {
             @Override
             public void newValue(Observation observation, LwM2mNode value) {
                 assertEquals(CaliforniumObservationTest.this.reportedValue, ((LwM2mResource) value).getValue());
-                assertEquals(3, observation.getPath().getObjectId());
+                assertEquals((Integer) 3, observation.getPath().getObjectId());
                 assertEquals((Integer) 0, observation.getPath().getObjectInstanceId());
                 assertEquals((Integer) 15, observation.getPath().getResourceId());
                 assertEquals(CaliforniumObservationTest.this.support.client.getRegistrationId(),
@@ -76,8 +76,8 @@ public class CaliforniumObservationTest {
             }
         };
         givenAnObserveRequest(target);
-        CaliforniumObservation observation = new CaliforniumObservation(coapRequest,
-                support.client.getRegistrationId(), target, model);
+        CaliforniumObservation observation = new CaliforniumObservation(coapRequest, support.client.getRegistrationId(),
+                target, model);
         observation.addListener(listener);
         Response coapResponse = new Response(ResponseCode.CONTENT);
         coapResponse.setPayload(reportedValue);
@@ -89,8 +89,8 @@ public class CaliforniumObservationTest {
 
         givenAnObserveRequest(target);
         assertFalse(coapRequest.isCanceled());
-        CaliforniumObservation observation = new CaliforniumObservation(coapRequest,
-                support.client.getRegistrationId(), target, model);
+        CaliforniumObservation observation = new CaliforniumObservation(coapRequest, support.client.getRegistrationId(),
+                target, model);
         observation.cancel();
         Assert.assertTrue(coapRequest.isCanceled());
     }
@@ -99,8 +99,8 @@ public class CaliforniumObservationTest {
     public void cancelled_Observation_are_removed_from_registry() {
         // create an observation
         givenAnObserveRequest(target);
-        CaliforniumObservation observation = new CaliforniumObservation(coapRequest,
-                support.client.getRegistrationId(), target, model);
+        CaliforniumObservation observation = new CaliforniumObservation(coapRequest, support.client.getRegistrationId(),
+                target, model);
         coapRequest.addMessageObserver(observation);
 
         // add it to registry
@@ -125,8 +125,8 @@ public class CaliforniumObservationTest {
     public void cancelled_from_registry_observation_are_removed_from_registry() {
         // create an observation
         givenAnObserveRequest(target);
-        CaliforniumObservation observation = new CaliforniumObservation(coapRequest,
-                support.client.getRegistrationId(), target, model);
+        CaliforniumObservation observation = new CaliforniumObservation(coapRequest, support.client.getRegistrationId(),
+                target, model);
         coapRequest.addMessageObserver(observation);
 
         // add it to registry