diff --git a/src/main/java/io/vertx/core/http/impl/Http1xServerRequest.java b/src/main/java/io/vertx/core/http/impl/Http1xServerRequest.java
--- a/src/main/java/io/vertx/core/http/impl/Http1xServerRequest.java
+++ b/src/main/java/io/vertx/core/http/impl/Http1xServerRequest.java
@@ -14,20 +14,19 @@
 import io.netty.handler.codec.http.*;
 import io.netty.handler.codec.http.HttpHeaders;
 import io.netty.handler.codec.http.multipart.Attribute;
-import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder;
 import io.netty.handler.codec.http.multipart.InterfaceHttpData;
 import io.vertx.codegen.annotations.Nullable;
 import io.vertx.core.Context;
 import io.vertx.core.Future;
 import io.vertx.core.Handler;
 import io.vertx.core.MultiMap;
-import io.vertx.core.Promise;
 import io.vertx.core.buffer.Buffer;
 import io.vertx.core.http.*;
 import io.vertx.core.http.Cookie;
 import io.vertx.core.http.HttpMethod;
 import io.vertx.core.http.HttpVersion;
 import io.vertx.core.http.impl.headers.HeadersAdaptor;
+import io.vertx.core.http.impl.netty.HttpPostRequestDecoder;
 import io.vertx.core.impl.ContextInternal;
 import io.vertx.core.impl.VertxInternal;
 import io.vertx.core.impl.future.PromiseInternal;
diff --git a/src/main/java/io/vertx/core/http/impl/Http2ServerRequest.java b/src/main/java/io/vertx/core/http/impl/Http2ServerRequest.java
--- a/src/main/java/io/vertx/core/http/impl/Http2ServerRequest.java
+++ b/src/main/java/io/vertx/core/http/impl/Http2ServerRequest.java
@@ -17,7 +17,6 @@
 import io.netty.handler.codec.http.HttpRequest;
 import io.netty.handler.codec.http.LastHttpContent;
 import io.netty.handler.codec.http.multipart.Attribute;
-import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder;
 import io.netty.handler.codec.http.multipart.InterfaceHttpData;
 import io.netty.handler.codec.http2.Http2Headers;
 import io.vertx.codegen.annotations.Nullable;
@@ -37,6 +36,7 @@
 import io.vertx.core.http.StreamResetException;
 import io.vertx.core.http.HttpFrame;
 import io.vertx.core.http.impl.headers.Http2HeadersAdaptor;
+import io.vertx.core.http.impl.netty.HttpPostRequestDecoder;
 import io.vertx.core.impl.ContextInternal;
 import io.vertx.core.impl.logging.Logger;
 import io.vertx.core.impl.logging.LoggerFactory;
diff --git a/src/main/java/io/vertx/core/http/impl/netty/CaseIgnoringComparator.java b/src/main/java/io/vertx/core/http/impl/netty/CaseIgnoringComparator.java
new file mode 100644
--- /dev/null
+++ b/src/main/java/io/vertx/core/http/impl/netty/CaseIgnoringComparator.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package io.vertx.core.http.impl.netty;
+
+import java.io.Serializable;
+import java.util.Comparator;
+
+final class CaseIgnoringComparator implements Comparator<CharSequence>, Serializable {
+
+    private static final long serialVersionUID = 4582133183775373862L;
+
+    static final CaseIgnoringComparator INSTANCE = new CaseIgnoringComparator();
+
+    private CaseIgnoringComparator() {
+    }
+
+    @Override
+    public int compare(CharSequence o1, CharSequence o2) {
+        int o1Length = o1.length();
+        int o2Length = o2.length();
+        int min = Math.min(o1Length, o2Length);
+        for (int i = 0; i < min; i++) {
+            char c1 = o1.charAt(i);
+            char c2 = o2.charAt(i);
+            if (c1 != c2) {
+                c1 = Character.toUpperCase(c1);
+                c2 = Character.toUpperCase(c2);
+                if (c1 != c2) {
+                    c1 = Character.toLowerCase(c1);
+                    c2 = Character.toLowerCase(c2);
+                    if (c1 != c2) {
+                        return c1 - c2;
+                    }
+                }
+            }
+        }
+        return o1Length - o2Length;
+    }
+
+    private Object readResolve() {
+        return INSTANCE;
+    }
+}
diff --git a/src/main/java/io/vertx/core/http/impl/netty/HttpPostBodyUtil.java b/src/main/java/io/vertx/core/http/impl/netty/HttpPostBodyUtil.java
new file mode 100644
--- /dev/null
+++ b/src/main/java/io/vertx/core/http/impl/netty/HttpPostBodyUtil.java
@@ -0,0 +1,267 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package io.vertx.core.http.impl.netty;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.handler.codec.http.HttpConstants;
+
+/**
+ * Shared Static object between HttpMessageDecoder, HttpPostRequestDecoder and HttpPostRequestEncoder
+ */
+final class HttpPostBodyUtil {
+
+    public static final int chunkSize = 8096;
+
+    /**
+     * Default Content-Type in binary form
+     */
+    public static final String DEFAULT_BINARY_CONTENT_TYPE = "application/octet-stream";
+
+    /**
+     * Default Content-Type in Text form
+     */
+    public static final String DEFAULT_TEXT_CONTENT_TYPE = "text/plain";
+
+    /**
+     * Allowed mechanism for multipart
+     * mechanism := "7bit"
+                  / "8bit"
+                  / "binary"
+       Not allowed: "quoted-printable"
+                  / "base64"
+     */
+    public enum TransferEncodingMechanism {
+        /**
+         * Default encoding
+         */
+        BIT7("7bit"),
+        /**
+         * Short lines but not in ASCII - no encoding
+         */
+        BIT8("8bit"),
+        /**
+         * Could be long text not in ASCII - no encoding
+         */
+        BINARY("binary");
+
+        private final String value;
+
+        TransferEncodingMechanism(String value) {
+            this.value = value;
+        }
+
+        public String value() {
+            return value;
+        }
+
+        @Override
+        public String toString() {
+            return value;
+        }
+    }
+
+    private HttpPostBodyUtil() {
+    }
+
+    /**
+    * This class intends to decrease the CPU in seeking ahead some bytes in
+    * HttpPostRequestDecoder
+    */
+    static class SeekAheadOptimize {
+        byte[] bytes;
+        int readerIndex;
+        int pos;
+        int origPos;
+        int limit;
+        ByteBuf buffer;
+
+        /**
+         * @param buffer buffer with a backing byte array
+         */
+        SeekAheadOptimize(ByteBuf buffer) {
+            if (!buffer.hasArray()) {
+                throw new IllegalArgumentException("buffer hasn't backing byte array");
+            }
+            this.buffer = buffer;
+            bytes = buffer.array();
+            readerIndex = buffer.readerIndex();
+            origPos = pos = buffer.arrayOffset() + readerIndex;
+            limit = buffer.arrayOffset() + buffer.writerIndex();
+        }
+
+        /**
+        *
+        * @param minus this value will be used as (currentPos - minus) to set
+        * the current readerIndex in the buffer.
+        */
+        void setReadPosition(int minus) {
+            pos -= minus;
+            readerIndex = getReadPosition(pos);
+            buffer.readerIndex(readerIndex);
+        }
+
+        /**
+        *
+        * @param index raw index of the array (pos in general)
+        * @return the value equivalent of raw index to be used in readerIndex(value)
+        */
+        int getReadPosition(int index) {
+            return index - origPos + readerIndex;
+        }
+    }
+
+    /**
+     * Find the first non whitespace
+     * @return the rank of the first non whitespace
+     */
+    static int findNonWhitespace(String sb, int offset) {
+        int result;
+        for (result = offset; result < sb.length(); result ++) {
+            if (!Character.isWhitespace(sb.charAt(result))) {
+                break;
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Find the end of String
+     * @return the rank of the end of string
+     */
+    static int findEndOfString(String sb) {
+        int result;
+        for (result = sb.length(); result > 0; result --) {
+            if (!Character.isWhitespace(sb.charAt(result - 1))) {
+                break;
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Try to find first LF or CRLF as Line Breaking
+     *
+     * @param buffer the buffer to search in
+     * @param index the index to start from in the buffer
+     * @return a relative position from index > 0 if LF or CRLF is found
+     *         or < 0 if not found
+     */
+    static int findLineBreak(ByteBuf buffer, int index) {
+        int toRead = buffer.readableBytes() - (index - buffer.readerIndex());
+        int posFirstChar = buffer.bytesBefore(index, toRead, HttpConstants.LF);
+        if (posFirstChar == -1) {
+            // No LF, so neither CRLF
+            return -1;
+        }
+        if (posFirstChar > 0 && buffer.getByte(index + posFirstChar - 1) == HttpConstants.CR) {
+            posFirstChar--;
+        }
+        return posFirstChar;
+    }
+
+    /**
+     * Try to find last LF or CRLF as Line Breaking
+     *
+     * @param buffer the buffer to search in
+     * @param index the index to start from in the buffer
+     * @return a relative position from index > 0 if LF or CRLF is found
+     *         or < 0 if not found
+     */
+    static int findLastLineBreak(ByteBuf buffer, int index) {
+        int candidate = findLineBreak(buffer, index);
+        int findCRLF = 0;
+        if (candidate >= 0) {
+            if (buffer.getByte(index + candidate) == HttpConstants.CR) {
+                findCRLF = 2;
+            } else {
+                findCRLF = 1;
+            }
+            candidate += findCRLF;
+        }
+        int next;
+        while (candidate > 0 && (next = findLineBreak(buffer, index + candidate)) >= 0) {
+            candidate += next;
+            if (buffer.getByte(index + candidate) == HttpConstants.CR) {
+                findCRLF = 2;
+            } else {
+                findCRLF = 1;
+            }
+            candidate += findCRLF;
+        }
+        return candidate - findCRLF;
+    }
+
+    /**
+     * Try to find the delimiter, with LF or CRLF in front of it (added as delimiters) if needed
+     *
+     * @param buffer the buffer to search in
+     * @param index the index to start from in the buffer
+     * @param delimiter the delimiter as byte array
+     * @param precededByLineBreak true if it must be preceded by LF or CRLF, else false
+     * @return a relative position from index > 0 if delimiter found designing the start of it
+     *         (including LF or CRLF is asked)
+     *         or a number < 0 if delimiter is not found
+     * @throws IndexOutOfBoundsException
+     *         if {@code offset + delimiter.length} is greater than {@code buffer.capacity}
+     */
+    static int findDelimiter(ByteBuf buffer, int index, byte[] delimiter, boolean precededByLineBreak) {
+        final int delimiterLength = delimiter.length;
+        final int readerIndex = buffer.readerIndex();
+        final int writerIndex = buffer.writerIndex();
+        int toRead = writerIndex - index;
+        int newOffset = index;
+        boolean delimiterNotFound = true;
+        while (delimiterNotFound && delimiterLength <= toRead) {
+            // Find first position: delimiter
+            int posDelimiter = buffer.bytesBefore(newOffset, toRead, delimiter[0]);
+            if (posDelimiter < 0) {
+                return -1;
+            }
+            newOffset += posDelimiter;
+            toRead -= posDelimiter;
+            // Now check for delimiter
+            delimiterNotFound = false;
+            for (int i = 0; i < delimiterLength; i++) {
+                if (buffer.getByte(newOffset + i) != delimiter[i]) {
+                    newOffset++;
+                    toRead--;
+                    delimiterNotFound = true;
+                    break;
+                }
+            }
+            if (!delimiterNotFound) {
+                // Delimiter found, find if necessary: LF or CRLF
+                if (precededByLineBreak && newOffset > readerIndex) {
+                    if (buffer.getByte(newOffset - 1) == HttpConstants.LF) {
+                        newOffset--;
+                        // Check if CR before: not mandatory to be there
+                        if (newOffset > readerIndex && buffer.getByte(newOffset - 1) == HttpConstants.CR) {
+                            newOffset--;
+                        }
+                    } else {
+                        // Delimiter with Line Break could be further: iterate after first char of delimiter
+                        newOffset++;
+                        toRead--;
+                        delimiterNotFound = true;
+                        continue;
+                    }
+                }
+                return newOffset - readerIndex;
+            }
+        }
+        return -1;
+    }
+}
diff --git a/src/main/java/io/vertx/core/http/impl/netty/HttpPostMultipartRequestDecoder.java b/src/main/java/io/vertx/core/http/impl/netty/HttpPostMultipartRequestDecoder.java
new file mode 100644
--- /dev/null
+++ b/src/main/java/io/vertx/core/http/impl/netty/HttpPostMultipartRequestDecoder.java
@@ -0,0 +1,1379 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package io.vertx.core.http.impl.netty;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.handler.codec.http.HttpConstants;
+import io.netty.handler.codec.http.HttpContent;
+import io.netty.handler.codec.http.HttpHeaderNames;
+import io.netty.handler.codec.http.HttpHeaderValues;
+import io.netty.handler.codec.http.HttpRequest;
+import io.netty.handler.codec.http.LastHttpContent;
+import io.netty.handler.codec.http.QueryStringDecoder;
+import io.netty.handler.codec.http.multipart.Attribute;
+import io.netty.handler.codec.http.multipart.DefaultHttpDataFactory;
+import io.netty.handler.codec.http.multipart.FileUpload;
+import io.netty.handler.codec.http.multipart.HttpData;
+import io.netty.handler.codec.http.multipart.HttpDataFactory;
+import io.vertx.core.http.impl.netty.HttpPostBodyUtil.SeekAheadOptimize;
+import io.vertx.core.http.impl.netty.HttpPostBodyUtil.TransferEncodingMechanism;
+import io.vertx.core.http.impl.netty.HttpPostRequestDecoder.EndOfDataDecoderException;
+import io.vertx.core.http.impl.netty.HttpPostRequestDecoder.ErrorDataDecoderException;
+import io.vertx.core.http.impl.netty.HttpPostRequestDecoder.MultiPartStatus;
+import io.vertx.core.http.impl.netty.HttpPostRequestDecoder.NotEnoughDataDecoderException;
+import io.netty.handler.codec.http.multipart.InterfaceHttpData;
+import io.netty.handler.codec.http.multipart.InterfaceHttpPostRequestDecoder;
+import io.netty.util.CharsetUtil;
+import io.netty.util.internal.InternalThreadLocalMap;
+import io.netty.util.internal.PlatformDependent;
+import io.netty.util.internal.StringUtil;
+
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.nio.charset.IllegalCharsetNameException;
+import java.nio.charset.UnsupportedCharsetException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+import static io.netty.util.internal.ObjectUtil.checkNotNull;
+import static io.netty.util.internal.ObjectUtil.checkPositiveOrZero;
+
+/**
+ * This decoder will decode Body and can handle POST BODY.
+ *
+ * You <strong>MUST</strong> call {@link #destroy()} after completion to release all resources.
+ *
+ */
+public class HttpPostMultipartRequestDecoder implements InterfaceHttpPostRequestDecoder {
+
+    /**
+     * Factory used to create InterfaceHttpData
+     */
+    private final HttpDataFactory factory;
+
+    /**
+     * Request to decode
+     */
+    private final HttpRequest request;
+
+    /**
+     * Default charset to use
+     */
+    private Charset charset;
+
+    /**
+     * Does the last chunk already received
+     */
+    private boolean isLastChunk;
+
+    /**
+     * HttpDatas from Body
+     */
+    private final List<InterfaceHttpData> bodyListHttpData = new ArrayList<InterfaceHttpData>();
+
+    /**
+     * HttpDatas as Map from Body
+     */
+    private final Map<String, List<InterfaceHttpData>> bodyMapHttpData = new TreeMap<String, List<InterfaceHttpData>>(
+            CaseIgnoringComparator.INSTANCE);
+
+    /**
+     * The current channelBuffer
+     */
+    private ByteBuf undecodedChunk;
+
+    /**
+     * Body HttpDatas current position
+     */
+    private int bodyListHttpDataRank;
+
+    /**
+     * If multipart, this is the boundary for the global multipart
+     */
+    private String multipartDataBoundary;
+
+    /**
+     * If multipart, there could be internal multiparts (mixed) to the global
+     * multipart. Only one level is allowed.
+     */
+    private String multipartMixedBoundary;
+
+    /**
+     * Current getStatus
+     */
+    private MultiPartStatus currentStatus = MultiPartStatus.NOTSTARTED;
+
+    /**
+     * Used in Multipart
+     */
+    private Map<CharSequence, Attribute> currentFieldAttributes;
+
+    /**
+     * The current FileUpload that is currently in decode process
+     */
+    private FileUpload currentFileUpload;
+
+    /**
+     * The current Attribute that is currently in decode process
+     */
+    private Attribute currentAttribute;
+
+    private boolean destroyed;
+
+    private int discardThreshold = HttpPostRequestDecoder.DEFAULT_DISCARD_THRESHOLD;
+
+    /**
+     *
+     * @param request
+     *            the request to decode
+     * @throws NullPointerException
+     *             for request
+     * @throws ErrorDataDecoderException
+     *             if the default charset was wrong when decoding or other
+     *             errors
+     */
+    public HttpPostMultipartRequestDecoder(HttpRequest request) {
+        this(new DefaultHttpDataFactory(DefaultHttpDataFactory.MINSIZE), request, HttpConstants.DEFAULT_CHARSET);
+    }
+
+    /**
+     *
+     * @param factory
+     *            the factory used to create InterfaceHttpData
+     * @param request
+     *            the request to decode
+     * @throws NullPointerException
+     *             for request or factory
+     * @throws ErrorDataDecoderException
+     *             if the default charset was wrong when decoding or other
+     *             errors
+     */
+    public HttpPostMultipartRequestDecoder(HttpDataFactory factory, HttpRequest request) {
+        this(factory, request, HttpConstants.DEFAULT_CHARSET);
+    }
+
+    /**
+     *
+     * @param factory
+     *            the factory used to create InterfaceHttpData
+     * @param request
+     *            the request to decode
+     * @param charset
+     *            the charset to use as default
+     * @throws NullPointerException
+     *             for request or charset or factory
+     * @throws ErrorDataDecoderException
+     *             if the default charset was wrong when decoding or other
+     *             errors
+     */
+    public HttpPostMultipartRequestDecoder(HttpDataFactory factory, HttpRequest request, Charset charset) {
+        this.request = checkNotNull(request, "request");
+        this.charset = checkNotNull(charset, "charset");
+        this.factory = checkNotNull(factory, "factory");
+        // Fill default values
+
+        String contentTypeValue = this.request.headers().get(HttpHeaderNames.CONTENT_TYPE);
+        if (contentTypeValue == null) {
+            throw new ErrorDataDecoderException("No '" + HttpHeaderNames.CONTENT_TYPE + "' header present.");
+        }
+
+        String[] dataBoundary = HttpPostRequestDecoder.getMultipartDataBoundary(contentTypeValue);
+        if (dataBoundary != null) {
+            multipartDataBoundary = dataBoundary[0];
+            if (dataBoundary.length > 1 && dataBoundary[1] != null) {
+                try {
+                    this.charset = Charset.forName(dataBoundary[1]);
+                } catch (IllegalCharsetNameException e) {
+                    throw new ErrorDataDecoderException(e);
+                }
+            }
+        } else {
+            multipartDataBoundary = null;
+        }
+        currentStatus = MultiPartStatus.HEADERDELIMITER;
+
+        try {
+            if (request instanceof HttpContent) {
+                // Offer automatically if the given request is als type of HttpContent
+                // See #1089
+                offer((HttpContent) request);
+            } else {
+                parseBody();
+            }
+        } catch (Throwable e) {
+            destroy();
+            PlatformDependent.throwException(e);
+        }
+    }
+
+    private void checkDestroyed() {
+        if (destroyed) {
+            throw new IllegalStateException(HttpPostMultipartRequestDecoder.class.getSimpleName()
+                    + " was destroyed already");
+        }
+    }
+
+    /**
+     * True if this request is a Multipart request
+     *
+     * @return True if this request is a Multipart request
+     */
+    @Override
+    public boolean isMultipart() {
+        checkDestroyed();
+        return true;
+    }
+
+    /**
+     * Set the amount of bytes after which read bytes in the buffer should be discarded.
+     * Setting this lower gives lower memory usage but with the overhead of more memory copies.
+     * Use {@code 0} to disable it.
+     */
+    @Override
+    public void setDiscardThreshold(int discardThreshold) {
+        this.discardThreshold = checkPositiveOrZero(discardThreshold, "discardThreshold");
+    }
+
+    /**
+     * Return the threshold in bytes after which read data in the buffer should be discarded.
+     */
+    @Override
+    public int getDiscardThreshold() {
+        return discardThreshold;
+    }
+
+    /**
+     * This getMethod returns a List of all HttpDatas from body.<br>
+     *
+     * If chunked, all chunks must have been offered using offer() getMethod. If
+     * not, NotEnoughDataDecoderException will be raised.
+     *
+     * @return the list of HttpDatas from Body part for POST getMethod
+     * @throws NotEnoughDataDecoderException
+     *             Need more chunks
+     */
+    @Override
+    public List<InterfaceHttpData> getBodyHttpDatas() {
+        checkDestroyed();
+
+        if (!isLastChunk) {
+            throw new NotEnoughDataDecoderException();
+        }
+        return bodyListHttpData;
+    }
+
+    /**
+     * This getMethod returns a List of all HttpDatas with the given name from
+     * body.<br>
+     *
+     * If chunked, all chunks must have been offered using offer() getMethod. If
+     * not, NotEnoughDataDecoderException will be raised.
+     *
+     * @return All Body HttpDatas with the given name (ignore case)
+     * @throws NotEnoughDataDecoderException
+     *             need more chunks
+     */
+    @Override
+    public List<InterfaceHttpData> getBodyHttpDatas(String name) {
+        checkDestroyed();
+
+        if (!isLastChunk) {
+            throw new NotEnoughDataDecoderException();
+        }
+        return bodyMapHttpData.get(name);
+    }
+
+    /**
+     * This getMethod returns the first InterfaceHttpData with the given name from
+     * body.<br>
+     *
+     * If chunked, all chunks must have been offered using offer() getMethod. If
+     * not, NotEnoughDataDecoderException will be raised.
+     *
+     * @return The first Body InterfaceHttpData with the given name (ignore
+     *         case)
+     * @throws NotEnoughDataDecoderException
+     *             need more chunks
+     */
+    @Override
+    public InterfaceHttpData getBodyHttpData(String name) {
+        checkDestroyed();
+
+        if (!isLastChunk) {
+            throw new NotEnoughDataDecoderException();
+        }
+        List<InterfaceHttpData> list = bodyMapHttpData.get(name);
+        if (list != null) {
+            return list.get(0);
+        }
+        return null;
+    }
+
+    /**
+     * Initialized the internals from a new chunk
+     *
+     * @param content
+     *            the new received chunk
+     * @throws ErrorDataDecoderException
+     *             if there is a problem with the charset decoding or other
+     *             errors
+     */
+    @Override
+    public HttpPostMultipartRequestDecoder offer(HttpContent content) {
+        checkDestroyed();
+
+        if (content instanceof LastHttpContent) {
+            isLastChunk = true;
+        }
+
+        ByteBuf buf = content.content();
+        if (undecodedChunk == null) {
+            undecodedChunk =
+                    // Since the Handler will release the incoming later on, we need to copy it
+                    //
+                    // We are explicit allocate a buffer and NOT calling copy() as otherwise it may set a maxCapacity
+                    // which is not really usable for us as we may exceed it once we add more bytes.
+                    buf.alloc().buffer(buf.readableBytes()).writeBytes(buf);
+        } else {
+            undecodedChunk.writeBytes(buf);
+        }
+        parseBody();
+        if (undecodedChunk != null && undecodedChunk.writerIndex() > discardThreshold) {
+            if (undecodedChunk.refCnt() == 1) {
+                // It's safe to call discardBytes() as we are the only owner of the buffer.
+                undecodedChunk.discardReadBytes();
+            } else {
+                // There seems to be multiple references of the buffer. Let's copy the data and release the buffer to
+                // ensure we can give back memory to the system.
+                ByteBuf buffer = undecodedChunk.alloc().buffer(undecodedChunk.readableBytes());
+                buffer.writeBytes(undecodedChunk);
+                undecodedChunk.release();
+                undecodedChunk = buffer;
+            }
+        }
+        return this;
+    }
+
+    /**
+     * True if at current getStatus, there is an available decoded
+     * InterfaceHttpData from the Body.
+     *
+     * This getMethod works for chunked and not chunked request.
+     *
+     * @return True if at current getStatus, there is a decoded InterfaceHttpData
+     * @throws EndOfDataDecoderException
+     *             No more data will be available
+     */
+    @Override
+    public boolean hasNext() {
+        checkDestroyed();
+
+        if (currentStatus == MultiPartStatus.EPILOGUE) {
+            // OK except if end of list
+            if (bodyListHttpDataRank >= bodyListHttpData.size()) {
+                throw new EndOfDataDecoderException();
+            }
+        }
+        return !bodyListHttpData.isEmpty() && bodyListHttpDataRank < bodyListHttpData.size();
+    }
+
+    /**
+     * Returns the next available InterfaceHttpData or null if, at the time it
+     * is called, there is no more available InterfaceHttpData. A subsequent
+     * call to offer(httpChunk) could enable more data.
+     *
+     * Be sure to call {@link InterfaceHttpData#release()} after you are done
+     * with processing to make sure to not leak any resources
+     *
+     * @return the next available InterfaceHttpData or null if none
+     * @throws EndOfDataDecoderException
+     *             No more data will be available
+     */
+    @Override
+    public InterfaceHttpData next() {
+        checkDestroyed();
+
+        if (hasNext()) {
+            return bodyListHttpData.get(bodyListHttpDataRank++);
+        }
+        return null;
+    }
+
+    @Override
+    public InterfaceHttpData currentPartialHttpData() {
+        if (currentFileUpload != null) {
+            return currentFileUpload;
+        } else {
+            return currentAttribute;
+        }
+    }
+
+    /**
+     * This getMethod will parse as much as possible data and fill the list and map
+     *
+     * @throws ErrorDataDecoderException
+     *             if there is a problem with the charset decoding or other
+     *             errors
+     */
+    private void parseBody() {
+        if (currentStatus == MultiPartStatus.PREEPILOGUE || currentStatus == MultiPartStatus.EPILOGUE) {
+            if (isLastChunk) {
+                currentStatus = MultiPartStatus.EPILOGUE;
+            }
+            return;
+        }
+        parseBodyMultipart();
+    }
+
+    /**
+     * Utility function to add a new decoded data
+     */
+    protected void addHttpData(InterfaceHttpData data) {
+        if (data == null) {
+            return;
+        }
+        List<InterfaceHttpData> datas = bodyMapHttpData.get(data.getName());
+        if (datas == null) {
+            datas = new ArrayList<InterfaceHttpData>(1);
+            bodyMapHttpData.put(data.getName(), datas);
+        }
+        datas.add(data);
+        bodyListHttpData.add(data);
+    }
+
+    /**
+     * Parse the Body for multipart
+     *
+     * @throws ErrorDataDecoderException
+     *             if there is a problem with the charset decoding or other
+     *             errors
+     */
+    private void parseBodyMultipart() {
+        if (undecodedChunk == null || undecodedChunk.readableBytes() == 0) {
+            // nothing to decode
+            return;
+        }
+        InterfaceHttpData data = decodeMultipart(currentStatus);
+        while (data != null) {
+            addHttpData(data);
+            if (currentStatus == MultiPartStatus.PREEPILOGUE || currentStatus == MultiPartStatus.EPILOGUE) {
+                break;
+            }
+            data = decodeMultipart(currentStatus);
+        }
+    }
+
+    /**
+     * Decode a multipart request by pieces<br>
+     * <br>
+     * NOTSTARTED PREAMBLE (<br>
+     * (HEADERDELIMITER DISPOSITION (FIELD | FILEUPLOAD))*<br>
+     * (HEADERDELIMITER DISPOSITION MIXEDPREAMBLE<br>
+     * (MIXEDDELIMITER MIXEDDISPOSITION MIXEDFILEUPLOAD)+<br>
+     * MIXEDCLOSEDELIMITER)*<br>
+     * CLOSEDELIMITER)+ EPILOGUE<br>
+     *
+     * Inspired from HttpMessageDecoder
+     *
+     * @return the next decoded InterfaceHttpData or null if none until now.
+     * @throws ErrorDataDecoderException
+     *             if an error occurs
+     */
+    private InterfaceHttpData decodeMultipart(MultiPartStatus state) {
+        switch (state) {
+        case NOTSTARTED:
+            throw new ErrorDataDecoderException("Should not be called with the current getStatus");
+        case PREAMBLE:
+            // Content-type: multipart/form-data, boundary=AaB03x
+            throw new ErrorDataDecoderException("Should not be called with the current getStatus");
+        case HEADERDELIMITER: {
+            // --AaB03x or --AaB03x--
+            return findMultipartDelimiter(multipartDataBoundary, MultiPartStatus.DISPOSITION,
+                    MultiPartStatus.PREEPILOGUE);
+        }
+        case DISPOSITION: {
+            // content-disposition: form-data; name="field1"
+            // content-disposition: form-data; name="pics"; filename="file1.txt"
+            // and other immediate values like
+            // Content-type: image/gif
+            // Content-Type: text/plain
+            // Content-Type: text/plain; charset=ISO-8859-1
+            // Content-Transfer-Encoding: binary
+            // The following line implies a change of mode (mixed mode)
+            // Content-type: multipart/mixed, boundary=BbC04y
+            return findMultipartDisposition();
+        }
+        case FIELD: {
+            // Now get value according to Content-Type and Charset
+            Charset localCharset = null;
+            Attribute charsetAttribute = currentFieldAttributes.get(HttpHeaderValues.CHARSET);
+            if (charsetAttribute != null) {
+                try {
+                    localCharset = Charset.forName(charsetAttribute.getValue());
+                } catch (IOException e) {
+                    throw new ErrorDataDecoderException(e);
+                } catch (UnsupportedCharsetException e) {
+                    throw new ErrorDataDecoderException(e);
+                }
+            }
+            Attribute nameAttribute = currentFieldAttributes.get(HttpHeaderValues.NAME);
+            if (currentAttribute == null) {
+                Attribute lengthAttribute = currentFieldAttributes
+                        .get(HttpHeaderNames.CONTENT_LENGTH);
+                long size;
+                try {
+                    size = lengthAttribute != null? Long.parseLong(lengthAttribute
+                            .getValue()) : 0L;
+                } catch (IOException e) {
+                    throw new ErrorDataDecoderException(e);
+                } catch (NumberFormatException ignored) {
+                    size = 0;
+                }
+                try {
+                    if (size > 0) {
+                        currentAttribute = factory.createAttribute(request,
+                                cleanString(nameAttribute.getValue()), size);
+                    } else {
+                        currentAttribute = factory.createAttribute(request,
+                                cleanString(nameAttribute.getValue()));
+                    }
+                } catch (NullPointerException e) {
+                    throw new ErrorDataDecoderException(e);
+                } catch (IllegalArgumentException e) {
+                    throw new ErrorDataDecoderException(e);
+                } catch (IOException e) {
+                    throw new ErrorDataDecoderException(e);
+                }
+                if (localCharset != null) {
+                    currentAttribute.setCharset(localCharset);
+                }
+            }
+            // load data
+            if (!loadDataMultipartOptimized(undecodedChunk, multipartDataBoundary, currentAttribute)) {
+                // Delimiter is not found. Need more chunks.
+                return null;
+            }
+            Attribute finalAttribute = currentAttribute;
+            currentAttribute = null;
+            currentFieldAttributes = null;
+            // ready to load the next one
+            currentStatus = MultiPartStatus.HEADERDELIMITER;
+            return finalAttribute;
+        }
+        case FILEUPLOAD: {
+            // eventually restart from existing FileUpload
+            return getFileUpload(multipartDataBoundary);
+        }
+        case MIXEDDELIMITER: {
+            // --AaB03x or --AaB03x--
+            // Note that currentFieldAttributes exists
+            return findMultipartDelimiter(multipartMixedBoundary, MultiPartStatus.MIXEDDISPOSITION,
+                    MultiPartStatus.HEADERDELIMITER);
+        }
+        case MIXEDDISPOSITION: {
+            return findMultipartDisposition();
+        }
+        case MIXEDFILEUPLOAD: {
+            // eventually restart from existing FileUpload
+            return getFileUpload(multipartMixedBoundary);
+        }
+        case PREEPILOGUE:
+            return null;
+        case EPILOGUE:
+            return null;
+        default:
+            throw new ErrorDataDecoderException("Shouldn't reach here.");
+        }
+    }
+
+    /**
+     * Skip control Characters
+     *
+     * @throws NotEnoughDataDecoderException
+     */
+    private static void skipControlCharacters(ByteBuf undecodedChunk) {
+        if (!undecodedChunk.hasArray()) {
+            try {
+                skipControlCharactersStandard(undecodedChunk);
+            } catch (IndexOutOfBoundsException e1) {
+                throw new NotEnoughDataDecoderException(e1);
+            }
+            return;
+        }
+        SeekAheadOptimize sao = new SeekAheadOptimize(undecodedChunk);
+        while (sao.pos < sao.limit) {
+            char c = (char) (sao.bytes[sao.pos++] & 0xFF);
+            if (!Character.isISOControl(c) && !Character.isWhitespace(c)) {
+                sao.setReadPosition(1);
+                return;
+            }
+        }
+        throw new NotEnoughDataDecoderException("Access out of bounds");
+    }
+
+    private static void skipControlCharactersStandard(ByteBuf undecodedChunk) {
+        for (;;) {
+            char c = (char) undecodedChunk.readUnsignedByte();
+            if (!Character.isISOControl(c) && !Character.isWhitespace(c)) {
+                undecodedChunk.readerIndex(undecodedChunk.readerIndex() - 1);
+                break;
+            }
+        }
+    }
+
+    /**
+     * Find the next Multipart Delimiter
+     *
+     * @param delimiter
+     *            delimiter to find
+     * @param dispositionStatus
+     *            the next getStatus if the delimiter is a start
+     * @param closeDelimiterStatus
+     *            the next getStatus if the delimiter is a close delimiter
+     * @return the next InterfaceHttpData if any
+     * @throws ErrorDataDecoderException
+     */
+    private InterfaceHttpData findMultipartDelimiter(String delimiter, MultiPartStatus dispositionStatus,
+            MultiPartStatus closeDelimiterStatus) {
+        // --AaB03x or --AaB03x--
+        int readerIndex = undecodedChunk.readerIndex();
+        try {
+            skipControlCharacters(undecodedChunk);
+        } catch (NotEnoughDataDecoderException ignored) {
+            undecodedChunk.readerIndex(readerIndex);
+            return null;
+        }
+        skipOneLine();
+        String newline;
+        try {
+            newline = readDelimiterOptimized(undecodedChunk, delimiter, charset);
+        } catch (NotEnoughDataDecoderException ignored) {
+            undecodedChunk.readerIndex(readerIndex);
+            return null;
+        }
+        if (newline.equals(delimiter)) {
+            currentStatus = dispositionStatus;
+            return decodeMultipart(dispositionStatus);
+        }
+        if (newline.equals(delimiter + "--")) {
+            // CLOSEDELIMITER or MIXED CLOSEDELIMITER found
+            currentStatus = closeDelimiterStatus;
+            if (currentStatus == MultiPartStatus.HEADERDELIMITER) {
+                // MIXEDCLOSEDELIMITER
+                // end of the Mixed part
+                currentFieldAttributes = null;
+                return decodeMultipart(MultiPartStatus.HEADERDELIMITER);
+            }
+            return null;
+        }
+        undecodedChunk.readerIndex(readerIndex);
+        throw new ErrorDataDecoderException("No Multipart delimiter found");
+    }
+
+    /**
+     * Find the next Disposition
+     *
+     * @return the next InterfaceHttpData if any
+     * @throws ErrorDataDecoderException
+     */
+    private InterfaceHttpData findMultipartDisposition() {
+        int readerIndex = undecodedChunk.readerIndex();
+        if (currentStatus == MultiPartStatus.DISPOSITION) {
+            currentFieldAttributes = new TreeMap<CharSequence, Attribute>(CaseIgnoringComparator.INSTANCE);
+        }
+        // read many lines until empty line with newline found! Store all data
+        while (!skipOneLine()) {
+            String newline;
+            try {
+                skipControlCharacters(undecodedChunk);
+                newline = readLineOptimized(undecodedChunk, charset);
+            } catch (NotEnoughDataDecoderException ignored) {
+                undecodedChunk.readerIndex(readerIndex);
+                return null;
+            }
+            String[] contents = splitMultipartHeader(newline);
+            if (HttpHeaderNames.CONTENT_DISPOSITION.contentEqualsIgnoreCase(contents[0])) {
+                boolean checkSecondArg;
+                if (currentStatus == MultiPartStatus.DISPOSITION) {
+                    checkSecondArg = HttpHeaderValues.FORM_DATA.contentEqualsIgnoreCase(contents[1]);
+                } else {
+                    checkSecondArg = HttpHeaderValues.ATTACHMENT.contentEqualsIgnoreCase(contents[1])
+                            || HttpHeaderValues.FILE.contentEqualsIgnoreCase(contents[1]);
+                }
+                if (checkSecondArg) {
+                    // read next values and store them in the map as Attribute
+                    for (int i = 2; i < contents.length; i++) {
+                        String[] values = contents[i].split("=", 2);
+                        Attribute attribute;
+                        try {
+                            attribute = getContentDispositionAttribute(values);
+                        } catch (NullPointerException e) {
+                            throw new ErrorDataDecoderException(e);
+                        } catch (IllegalArgumentException e) {
+                            throw new ErrorDataDecoderException(e);
+                        }
+                        currentFieldAttributes.put(attribute.getName(), attribute);
+                    }
+                }
+            } else if (HttpHeaderNames.CONTENT_TRANSFER_ENCODING.contentEqualsIgnoreCase(contents[0])) {
+                Attribute attribute;
+                try {
+                    attribute = factory.createAttribute(request, HttpHeaderNames.CONTENT_TRANSFER_ENCODING.toString(),
+                            cleanString(contents[1]));
+                } catch (NullPointerException e) {
+                    throw new ErrorDataDecoderException(e);
+                } catch (IllegalArgumentException e) {
+                    throw new ErrorDataDecoderException(e);
+                }
+
+                currentFieldAttributes.put(HttpHeaderNames.CONTENT_TRANSFER_ENCODING, attribute);
+            } else if (HttpHeaderNames.CONTENT_LENGTH.contentEqualsIgnoreCase(contents[0])) {
+                Attribute attribute;
+                try {
+                    attribute = factory.createAttribute(request, HttpHeaderNames.CONTENT_LENGTH.toString(),
+                            cleanString(contents[1]));
+                } catch (NullPointerException e) {
+                    throw new ErrorDataDecoderException(e);
+                } catch (IllegalArgumentException e) {
+                    throw new ErrorDataDecoderException(e);
+                }
+
+                currentFieldAttributes.put(HttpHeaderNames.CONTENT_LENGTH, attribute);
+            } else if (HttpHeaderNames.CONTENT_TYPE.contentEqualsIgnoreCase(contents[0])) {
+                // Take care of possible "multipart/mixed"
+                if (HttpHeaderValues.MULTIPART_MIXED.contentEqualsIgnoreCase(contents[1])) {
+                    if (currentStatus == MultiPartStatus.DISPOSITION) {
+                        String values = StringUtil.substringAfter(contents[2], '=');
+                        multipartMixedBoundary = "--" + values;
+                        currentStatus = MultiPartStatus.MIXEDDELIMITER;
+                        return decodeMultipart(MultiPartStatus.MIXEDDELIMITER);
+                    } else {
+                        throw new ErrorDataDecoderException("Mixed Multipart found in a previous Mixed Multipart");
+                    }
+                } else {
+                    for (int i = 1; i < contents.length; i++) {
+                        final String charsetHeader = HttpHeaderValues.CHARSET.toString();
+                        if (contents[i].regionMatches(true, 0, charsetHeader, 0, charsetHeader.length())) {
+                            String values = StringUtil.substringAfter(contents[i], '=');
+                            Attribute attribute;
+                            try {
+                                attribute = factory.createAttribute(request, charsetHeader, cleanString(values));
+                            } catch (NullPointerException e) {
+                                throw new ErrorDataDecoderException(e);
+                            } catch (IllegalArgumentException e) {
+                                throw new ErrorDataDecoderException(e);
+                            }
+                            currentFieldAttributes.put(HttpHeaderValues.CHARSET, attribute);
+                        } else {
+                            Attribute attribute;
+                            try {
+                                attribute = factory.createAttribute(request,
+                                        cleanString(contents[0]), contents[i]);
+                            } catch (NullPointerException e) {
+                                throw new ErrorDataDecoderException(e);
+                            } catch (IllegalArgumentException e) {
+                                throw new ErrorDataDecoderException(e);
+                            }
+                            currentFieldAttributes.put(attribute.getName(), attribute);
+                        }
+                    }
+                }
+            }
+        }
+        // Is it a FileUpload
+        Attribute filenameAttribute = currentFieldAttributes.get(HttpHeaderValues.FILENAME);
+        if (currentStatus == MultiPartStatus.DISPOSITION) {
+            if (filenameAttribute != null) {
+                // FileUpload
+                currentStatus = MultiPartStatus.FILEUPLOAD;
+                // do not change the buffer position
+                return decodeMultipart(MultiPartStatus.FILEUPLOAD);
+            } else {
+                // Field
+                currentStatus = MultiPartStatus.FIELD;
+                // do not change the buffer position
+                return decodeMultipart(MultiPartStatus.FIELD);
+            }
+        } else {
+            if (filenameAttribute != null) {
+                // FileUpload
+                currentStatus = MultiPartStatus.MIXEDFILEUPLOAD;
+                // do not change the buffer position
+                return decodeMultipart(MultiPartStatus.MIXEDFILEUPLOAD);
+            } else {
+                // Field is not supported in MIXED mode
+                throw new ErrorDataDecoderException("Filename not found");
+            }
+        }
+    }
+
+    private static final String FILENAME_ENCODED = HttpHeaderValues.FILENAME.toString() + '*';
+
+    private Attribute getContentDispositionAttribute(String... values) {
+        String name = cleanString(values[0]);
+        String value = values[1];
+
+        // Filename can be token, quoted or encoded. See https://tools.ietf.org/html/rfc5987
+        if (HttpHeaderValues.FILENAME.contentEquals(name)) {
+            // Value is quoted or token. Strip if quoted:
+            int last = value.length() - 1;
+            if (last > 0 &&
+              value.charAt(0) == HttpConstants.DOUBLE_QUOTE &&
+              value.charAt(last) == HttpConstants.DOUBLE_QUOTE) {
+                value = value.substring(1, last);
+            }
+        } else if (FILENAME_ENCODED.equals(name)) {
+            try {
+                name = HttpHeaderValues.FILENAME.toString();
+                String[] split = cleanString(value).split("'", 3);
+                value = QueryStringDecoder.decodeComponent(split[2], Charset.forName(split[0]));
+            } catch (ArrayIndexOutOfBoundsException e) {
+                 throw new ErrorDataDecoderException(e);
+            } catch (UnsupportedCharsetException e) {
+                throw new ErrorDataDecoderException(e);
+            }
+        } else {
+            // otherwise we need to clean the value
+            value = cleanString(value);
+        }
+        return factory.createAttribute(request, name, value);
+    }
+
+    /**
+     * Get the FileUpload (new one or current one)
+     *
+     * @param delimiter
+     *            the delimiter to use
+     * @return the InterfaceHttpData if any
+     * @throws ErrorDataDecoderException
+     */
+    protected InterfaceHttpData getFileUpload(String delimiter) {
+        // eventually restart from existing FileUpload
+        // Now get value according to Content-Type and Charset
+        Attribute encoding = currentFieldAttributes.get(HttpHeaderNames.CONTENT_TRANSFER_ENCODING);
+        Charset localCharset = charset;
+        // Default
+        TransferEncodingMechanism mechanism = TransferEncodingMechanism.BIT7;
+        if (encoding != null) {
+            String code;
+            try {
+                code = encoding.getValue().toLowerCase();
+            } catch (IOException e) {
+                throw new ErrorDataDecoderException(e);
+            }
+            if (code.equals(TransferEncodingMechanism.BIT7.value())) {
+                localCharset = CharsetUtil.US_ASCII;
+            } else if (code.equals(TransferEncodingMechanism.BIT8.value())) {
+                localCharset = CharsetUtil.ISO_8859_1;
+                mechanism = TransferEncodingMechanism.BIT8;
+            } else if (code.equals(TransferEncodingMechanism.BINARY.value())) {
+                // no real charset, so let the default
+                mechanism = TransferEncodingMechanism.BINARY;
+            } else {
+                throw new ErrorDataDecoderException("TransferEncoding Unknown: " + code);
+            }
+        }
+        Attribute charsetAttribute = currentFieldAttributes.get(HttpHeaderValues.CHARSET);
+        if (charsetAttribute != null) {
+            try {
+                localCharset = Charset.forName(charsetAttribute.getValue());
+            } catch (IOException e) {
+                throw new ErrorDataDecoderException(e);
+            } catch (UnsupportedCharsetException e) {
+                throw new ErrorDataDecoderException(e);
+            }
+        }
+        if (currentFileUpload == null) {
+            Attribute filenameAttribute = currentFieldAttributes.get(HttpHeaderValues.FILENAME);
+            Attribute nameAttribute = currentFieldAttributes.get(HttpHeaderValues.NAME);
+            Attribute contentTypeAttribute = currentFieldAttributes.get(HttpHeaderNames.CONTENT_TYPE);
+            Attribute lengthAttribute = currentFieldAttributes.get(HttpHeaderNames.CONTENT_LENGTH);
+            long size;
+            try {
+                size = lengthAttribute != null ? Long.parseLong(lengthAttribute.getValue()) : 0L;
+            } catch (IOException e) {
+                throw new ErrorDataDecoderException(e);
+            } catch (NumberFormatException ignored) {
+                size = 0;
+            }
+            try {
+                String contentType;
+                if (contentTypeAttribute != null) {
+                    contentType = contentTypeAttribute.getValue();
+                } else {
+                    contentType = HttpPostBodyUtil.DEFAULT_BINARY_CONTENT_TYPE;
+                }
+                currentFileUpload = factory.createFileUpload(request,
+                        cleanString(nameAttribute.getValue()), cleanString(filenameAttribute.getValue()),
+                        contentType, mechanism.value(), localCharset,
+                        size);
+            } catch (NullPointerException e) {
+                throw new ErrorDataDecoderException(e);
+            } catch (IllegalArgumentException e) {
+                throw new ErrorDataDecoderException(e);
+            } catch (IOException e) {
+                throw new ErrorDataDecoderException(e);
+            }
+        }
+        // load data as much as possible
+        if (!loadDataMultipartOptimized(undecodedChunk, delimiter, currentFileUpload)) {
+            // Delimiter is not found. Need more chunks.
+            return null;
+        }
+        if (currentFileUpload.isCompleted()) {
+            // ready to load the next one
+            if (currentStatus == MultiPartStatus.FILEUPLOAD) {
+                currentStatus = MultiPartStatus.HEADERDELIMITER;
+                currentFieldAttributes = null;
+            } else {
+                currentStatus = MultiPartStatus.MIXEDDELIMITER;
+                cleanMixedAttributes();
+            }
+            FileUpload fileUpload = currentFileUpload;
+            currentFileUpload = null;
+            return fileUpload;
+        }
+        // do not change the buffer position
+        // since some can be already saved into FileUpload
+        // So do not change the currentStatus
+        return null;
+    }
+
+    /**
+     * Destroy the {@link HttpPostMultipartRequestDecoder} and release all it resources. After this method
+     * was called it is not possible to operate on it anymore.
+     */
+    @Override
+    public void destroy() {
+        // Release all data items, including those not yet pulled, only file based items
+        cleanFiles();
+        // Clean Memory based data
+        for (InterfaceHttpData httpData : bodyListHttpData) {
+            // Might have been already released by the user
+            if (httpData.refCnt() > 0) {
+                httpData.release();
+            }
+        }
+
+        destroyed = true;
+
+        if (undecodedChunk != null && undecodedChunk.refCnt() > 0) {
+            undecodedChunk.release();
+            undecodedChunk = null;
+        }
+    }
+
+    /**
+     * Clean all HttpDatas (on Disk) for the current request.
+     */
+    @Override
+    public void cleanFiles() {
+        checkDestroyed();
+
+        factory.cleanRequestHttpData(request);
+    }
+
+    /**
+     * Remove the given FileUpload from the list of FileUploads to clean
+     */
+    @Override
+    public void removeHttpDataFromClean(InterfaceHttpData data) {
+        checkDestroyed();
+
+        factory.removeHttpDataFromClean(request, data);
+    }
+
+    /**
+     * Remove all Attributes that should be cleaned between two FileUpload in
+     * Mixed mode
+     */
+    private void cleanMixedAttributes() {
+        currentFieldAttributes.remove(HttpHeaderValues.CHARSET);
+        currentFieldAttributes.remove(HttpHeaderNames.CONTENT_LENGTH);
+        currentFieldAttributes.remove(HttpHeaderNames.CONTENT_TRANSFER_ENCODING);
+        currentFieldAttributes.remove(HttpHeaderNames.CONTENT_TYPE);
+        currentFieldAttributes.remove(HttpHeaderValues.FILENAME);
+    }
+
+    /**
+     * Read one line up to the CRLF or LF
+     *
+     * @return the String from one line
+     * @throws NotEnoughDataDecoderException
+     *             Need more chunks and reset the {@code readerIndex} to the previous
+     *             value
+     */
+    private static String readLineOptimized(ByteBuf undecodedChunk, Charset charset) {
+        int readerIndex = undecodedChunk.readerIndex();
+        ByteBuf line = null;
+        try {
+            if (undecodedChunk.isReadable()) {
+                int posLfOrCrLf = HttpPostBodyUtil.findLineBreak(undecodedChunk, undecodedChunk.readerIndex());
+                if (posLfOrCrLf <= 0) {
+                    throw new NotEnoughDataDecoderException();
+                }
+                try {
+                    line = undecodedChunk.alloc().heapBuffer(posLfOrCrLf);
+                    line.writeBytes(undecodedChunk, posLfOrCrLf);
+
+                    byte nextByte = undecodedChunk.readByte();
+                    if (nextByte == HttpConstants.CR) {
+                        // force read next byte since LF is the following one
+                        undecodedChunk.readByte();
+                    }
+                    return line.toString(charset);
+                } finally {
+                    line.release();
+                }
+            }
+        } catch (IndexOutOfBoundsException e) {
+            undecodedChunk.readerIndex(readerIndex);
+            throw new NotEnoughDataDecoderException(e);
+        }
+        undecodedChunk.readerIndex(readerIndex);
+        throw new NotEnoughDataDecoderException();
+    }
+
+    /**
+     * Read one line up to --delimiter or --delimiter-- and if existing the CRLF
+     * or LF Read one line up to --delimiter or --delimiter-- and if existing
+     * the CRLF or LF. Note that CRLF or LF are mandatory for opening delimiter
+     * (--delimiter) but not for closing delimiter (--delimiter--) since some
+     * clients does not include CRLF in this case.
+     *
+     * @param delimiter
+     *            of the form --string, such that '--' is already included
+     * @return the String from one line as the delimiter searched (opening or
+     *         closing)
+     * @throws NotEnoughDataDecoderException
+     *             Need more chunks and reset the {@code readerIndex} to the previous
+     *             value
+     */
+    private static String readDelimiterOptimized(ByteBuf undecodedChunk, String delimiter, Charset charset) {
+        final int readerIndex = undecodedChunk.readerIndex();
+        final byte[] bdelimiter = delimiter.getBytes(charset);
+        final int delimiterLength = bdelimiter.length;
+        try {
+            int delimiterPos = HttpPostBodyUtil.findDelimiter(undecodedChunk, readerIndex, bdelimiter, false);
+            if (delimiterPos < 0) {
+                // delimiter not found so break here !
+                undecodedChunk.readerIndex(readerIndex);
+                throw new NotEnoughDataDecoderException();
+            }
+            StringBuilder sb = new StringBuilder(delimiter);
+            undecodedChunk.readerIndex(readerIndex + delimiterPos + delimiterLength);
+            // Now check if either opening delimiter or closing delimiter
+            if (undecodedChunk.isReadable()) {
+                byte nextByte = undecodedChunk.readByte();
+                // first check for opening delimiter
+                if (nextByte == HttpConstants.CR) {
+                    nextByte = undecodedChunk.readByte();
+                    if (nextByte == HttpConstants.LF) {
+                        return sb.toString();
+                    } else {
+                        // error since CR must be followed by LF
+                        // delimiter not found so break here !
+                        undecodedChunk.readerIndex(readerIndex);
+                        throw new NotEnoughDataDecoderException();
+                    }
+                } else if (nextByte == HttpConstants.LF) {
+                    return sb.toString();
+                } else if (nextByte == '-') {
+                    sb.append('-');
+                    // second check for closing delimiter
+                    nextByte = undecodedChunk.readByte();
+                    if (nextByte == '-') {
+                        sb.append('-');
+                        // now try to find if CRLF or LF there
+                        if (undecodedChunk.isReadable()) {
+                            nextByte = undecodedChunk.readByte();
+                            if (nextByte == HttpConstants.CR) {
+                                nextByte = undecodedChunk.readByte();
+                                if (nextByte == HttpConstants.LF) {
+                                    return sb.toString();
+                                } else {
+                                    // error CR without LF
+                                    // delimiter not found so break here !
+                                    undecodedChunk.readerIndex(readerIndex);
+                                    throw new NotEnoughDataDecoderException();
+                                }
+                            } else if (nextByte == HttpConstants.LF) {
+                                return sb.toString();
+                            } else {
+                                // No CRLF but ok however (Adobe Flash uploader)
+                                // minus 1 since we read one char ahead but
+                                // should not
+                                undecodedChunk.readerIndex(undecodedChunk.readerIndex() - 1);
+                                return sb.toString();
+                            }
+                        }
+                        // FIXME what do we do here?
+                        // either considering it is fine, either waiting for
+                        // more data to come?
+                        // lets try considering it is fine...
+                        return sb.toString();
+                    }
+                    // only one '-' => not enough
+                    // whatever now => error since incomplete
+                }
+            }
+        } catch (IndexOutOfBoundsException e) {
+            undecodedChunk.readerIndex(readerIndex);
+            throw new NotEnoughDataDecoderException(e);
+        }
+        undecodedChunk.readerIndex(readerIndex);
+        throw new NotEnoughDataDecoderException();
+    }
+
+    /**
+     * Rewrite buffer in order to skip lengthToSkip bytes from current readerIndex,
+     * such that any readable bytes available after readerIndex + lengthToSkip (so before writerIndex)
+     * are moved at readerIndex position,
+     * therefore decreasing writerIndex of lengthToSkip at the end of the process.
+     *
+     * @param buffer the buffer to rewrite from current readerIndex
+     * @param lengthToSkip the size to skip from readerIndex
+     */
+    private static void rewriteCurrentBuffer(ByteBuf buffer, int lengthToSkip) {
+        if (lengthToSkip == 0) {
+            return;
+        }
+        final int readerIndex = buffer.readerIndex();
+        final int readableBytes = buffer.readableBytes();
+        if (readableBytes == lengthToSkip) {
+            buffer.readerIndex(readerIndex);
+            buffer.writerIndex(readerIndex);
+            return;
+        }
+        buffer.setBytes(readerIndex, buffer, readerIndex + lengthToSkip, readableBytes - lengthToSkip);
+        buffer.readerIndex(readerIndex);
+        buffer.writerIndex(readerIndex + readableBytes - lengthToSkip);
+    }
+
+    /**
+     * Load the field value or file data from a Multipart request
+     *
+     * @return {@code true} if the last chunk is loaded (boundary delimiter found), {@code false} if need more chunks
+     * @throws ErrorDataDecoderException
+     */
+    private static boolean loadDataMultipartOptimized(ByteBuf undecodedChunk, String delimiter, HttpData httpData) {
+        if (!undecodedChunk.isReadable()) {
+            return false;
+        }
+        final int startReaderIndex = undecodedChunk.readerIndex();
+        final byte[] bdelimiter = delimiter.getBytes(httpData.getCharset());
+        int posDelimiter = HttpPostBodyUtil.findDelimiter(undecodedChunk, startReaderIndex, bdelimiter, true);
+        if (posDelimiter < 0) {
+            // Not found but however perhaps because incomplete so search LF or CRLF from the end.
+            // Possible last bytes contain partially delimiter
+            // (delimiter is possibly partially there, at least 1 missing byte),
+            // therefore searching last delimiter.length +1 (+1 for CRLF instead of LF)
+            int lastPosition = undecodedChunk.readableBytes() - bdelimiter.length - 1;
+            if (lastPosition < 0) {
+                // Not enough bytes, but at most delimiter.length bytes available so can still try to find CRLF there
+                lastPosition = 0;
+            }
+            posDelimiter = HttpPostBodyUtil.findLastLineBreak(undecodedChunk, startReaderIndex  + lastPosition);
+            if (posDelimiter < 0) {
+                // not found so this chunk can be fully added
+                ByteBuf content = undecodedChunk.copy();
+                try {
+                    httpData.addContent(content, false);
+                } catch (IOException e) {
+                    throw new ErrorDataDecoderException(e);
+                }
+                undecodedChunk.readerIndex(startReaderIndex);
+                undecodedChunk.writerIndex(startReaderIndex);
+                return false;
+            }
+            // posDelimiter is not from startReaderIndex but from startReaderIndex + lastPosition
+            posDelimiter += lastPosition;
+            if (posDelimiter == 0) {
+                // Nothing to add
+                return false;
+            }
+            // Not fully but still some bytes to provide: httpData is not yet finished since delimiter not found
+            ByteBuf content = undecodedChunk.copy(startReaderIndex, posDelimiter);
+            try {
+                httpData.addContent(content, false);
+            } catch (IOException e) {
+                throw new ErrorDataDecoderException(e);
+            }
+            rewriteCurrentBuffer(undecodedChunk, posDelimiter);
+            return false;
+        }
+        // Delimiter found at posDelimiter, including LF or CRLF, so httpData has its last chunk
+        ByteBuf content = undecodedChunk.copy(startReaderIndex, posDelimiter);
+        try {
+            httpData.addContent(content, true);
+        } catch (IOException e) {
+            throw new ErrorDataDecoderException(e);
+        }
+        rewriteCurrentBuffer(undecodedChunk, posDelimiter);
+        return true;
+    }
+
+    /**
+     * Clean the String from any unallowed character
+     *
+     * @return the cleaned String
+     */
+    private static String cleanString(String field) {
+        int size = field.length();
+        StringBuilder sb = new StringBuilder(size);
+        for (int i = 0; i < size; i++) {
+            char nextChar = field.charAt(i);
+            switch (nextChar) {
+            case HttpConstants.COLON:
+            case HttpConstants.COMMA:
+            case HttpConstants.EQUALS:
+            case HttpConstants.SEMICOLON:
+            case HttpConstants.HT:
+                sb.append(HttpConstants.SP_CHAR);
+                break;
+            case HttpConstants.DOUBLE_QUOTE:
+                // nothing added, just removes it
+                break;
+            default:
+                sb.append(nextChar);
+                break;
+            }
+        }
+        return sb.toString().trim();
+    }
+
+    /**
+     * Skip one empty line
+     *
+     * @return True if one empty line was skipped
+     */
+    private boolean skipOneLine() {
+        if (!undecodedChunk.isReadable()) {
+            return false;
+        }
+        byte nextByte = undecodedChunk.readByte();
+        if (nextByte == HttpConstants.CR) {
+            if (!undecodedChunk.isReadable()) {
+                undecodedChunk.readerIndex(undecodedChunk.readerIndex() - 1);
+                return false;
+            }
+            nextByte = undecodedChunk.readByte();
+            if (nextByte == HttpConstants.LF) {
+                return true;
+            }
+            undecodedChunk.readerIndex(undecodedChunk.readerIndex() - 2);
+            return false;
+        }
+        if (nextByte == HttpConstants.LF) {
+            return true;
+        }
+        undecodedChunk.readerIndex(undecodedChunk.readerIndex() - 1);
+        return false;
+    }
+
+    /**
+     * Split one header in Multipart
+     *
+     * @return an array of String where rank 0 is the name of the header,
+     *         follows by several values that were separated by ';' or ','
+     */
+    private static String[] splitMultipartHeader(String sb) {
+        ArrayList<String> headers = new ArrayList<String>(1);
+        int nameStart;
+        int nameEnd;
+        int colonEnd;
+        int valueStart;
+        int valueEnd;
+        nameStart = HttpPostBodyUtil.findNonWhitespace(sb, 0);
+        for (nameEnd = nameStart; nameEnd < sb.length(); nameEnd++) {
+            char ch = sb.charAt(nameEnd);
+            if (ch == ':' || Character.isWhitespace(ch)) {
+                break;
+            }
+        }
+        for (colonEnd = nameEnd; colonEnd < sb.length(); colonEnd++) {
+            if (sb.charAt(colonEnd) == ':') {
+                colonEnd++;
+                break;
+            }
+        }
+        valueStart = HttpPostBodyUtil.findNonWhitespace(sb, colonEnd);
+        valueEnd = HttpPostBodyUtil.findEndOfString(sb);
+        headers.add(sb.substring(nameStart, nameEnd));
+        String svalue = (valueStart >= valueEnd) ? StringUtil.EMPTY_STRING : sb.substring(valueStart, valueEnd);
+        String[] values;
+        if (svalue.indexOf(';') >= 0) {
+            values = splitMultipartHeaderValues(svalue);
+        } else {
+            values = svalue.split(",");
+        }
+        for (String value : values) {
+            headers.add(value.trim());
+        }
+        String[] array = new String[headers.size()];
+        for (int i = 0; i < headers.size(); i++) {
+            array[i] = headers.get(i);
+        }
+        return array;
+    }
+
+    /**
+     * Split one header value in Multipart
+     * @return an array of String where values that were separated by ';' or ','
+     */
+    private static String[] splitMultipartHeaderValues(String svalue) {
+        List<String> values = InternalThreadLocalMap.get().arrayList(1);
+        boolean inQuote = false;
+        boolean escapeNext = false;
+        int start = 0;
+        for (int i = 0; i < svalue.length(); i++) {
+            char c = svalue.charAt(i);
+            if (inQuote) {
+                if (escapeNext) {
+                    escapeNext = false;
+                } else {
+                    if (c == '\\') {
+                        escapeNext = true;
+                    } else if (c == '"') {
+                        inQuote = false;
+                    }
+                }
+            } else {
+                if (c == '"') {
+                    inQuote = true;
+                } else if (c == ';') {
+                    values.add(svalue.substring(start, i));
+                    start = i + 1;
+                }
+            }
+        }
+        values.add(svalue.substring(start));
+        return values.toArray(new String[0]);
+    }
+
+    /**
+     * This method is package private intentionally in order to allow during tests
+     * to access to the amount of memory allocated (capacity) within the private
+     * ByteBuf undecodedChunk
+     *
+     * @return the number of bytes the internal buffer can contain
+     */
+    int getCurrentAllocatedCapacity() {
+        return undecodedChunk.capacity();
+    }
+}
diff --git a/src/main/java/io/vertx/core/http/impl/netty/HttpPostRequestDecoder.java b/src/main/java/io/vertx/core/http/impl/netty/HttpPostRequestDecoder.java
new file mode 100644
--- /dev/null
+++ b/src/main/java/io/vertx/core/http/impl/netty/HttpPostRequestDecoder.java
@@ -0,0 +1,330 @@
+package io.vertx.core.http.impl.netty;
+
+import io.netty.handler.codec.DecoderException;
+import io.netty.handler.codec.http.HttpConstants;
+import io.netty.handler.codec.http.HttpContent;
+import io.netty.handler.codec.http.HttpHeaderNames;
+import io.netty.handler.codec.http.HttpHeaderValues;
+import io.netty.handler.codec.http.HttpRequest;
+import io.netty.handler.codec.http.multipart.DefaultHttpDataFactory;
+import io.netty.handler.codec.http.multipart.HttpDataFactory;
+import io.netty.handler.codec.http.multipart.InterfaceHttpData;
+import io.netty.handler.codec.http.multipart.InterfaceHttpPostRequestDecoder;
+import io.netty.util.internal.ObjectUtil;
+import io.netty.util.internal.StringUtil;
+
+import java.nio.charset.Charset;
+import java.util.List;
+
+/**
+ * This decoder will decode Body and can handle POST BODY.
+ *
+ * You <strong>MUST</strong> call {@link #destroy()} after completion to release all resources.
+ *
+ */
+public class HttpPostRequestDecoder implements InterfaceHttpPostRequestDecoder {
+
+    static final int DEFAULT_DISCARD_THRESHOLD = 10 * 1024 * 1024;
+
+    private final InterfaceHttpPostRequestDecoder decoder;
+
+    /**
+     *
+     * @param request
+     *            the request to decode
+     * @throws NullPointerException
+     *             for request
+     * @throws ErrorDataDecoderException
+     *             if the default charset was wrong when decoding or other
+     *             errors
+     */
+    public HttpPostRequestDecoder(HttpRequest request) {
+        this(new DefaultHttpDataFactory(DefaultHttpDataFactory.MINSIZE), request, HttpConstants.DEFAULT_CHARSET);
+    }
+
+    /**
+     *
+     * @param factory
+     *            the factory used to create InterfaceHttpData
+     * @param request
+     *            the request to decode
+     * @throws NullPointerException
+     *             for request or factory
+     * @throws ErrorDataDecoderException
+     *             if the default charset was wrong when decoding or other
+     *             errors
+     */
+    public HttpPostRequestDecoder(HttpDataFactory factory, HttpRequest request) {
+        this(factory, request, HttpConstants.DEFAULT_CHARSET);
+    }
+
+    /**
+     *
+     * @param factory
+     *            the factory used to create InterfaceHttpData
+     * @param request
+     *            the request to decode
+     * @param charset
+     *            the charset to use as default
+     * @throws NullPointerException
+     *             for request or charset or factory
+     * @throws ErrorDataDecoderException
+     *             if the default charset was wrong when decoding or other
+     *             errors
+     */
+    public HttpPostRequestDecoder(HttpDataFactory factory, HttpRequest request, Charset charset) {
+        ObjectUtil.checkNotNull(factory, "factory");
+        ObjectUtil.checkNotNull(request, "request");
+        ObjectUtil.checkNotNull(charset, "charset");
+
+        // Fill default values
+        if (isMultipart(request)) {
+            decoder = new HttpPostMultipartRequestDecoder(factory, request, charset);
+        } else {
+            decoder = new HttpPostStandardRequestDecoder(factory, request, charset);
+        }
+    }
+
+    /**
+     * states follow NOTSTARTED PREAMBLE ( (HEADERDELIMITER DISPOSITION (FIELD |
+     * FILEUPLOAD))* (HEADERDELIMITER DISPOSITION MIXEDPREAMBLE (MIXEDDELIMITER
+     * MIXEDDISPOSITION MIXEDFILEUPLOAD)+ MIXEDCLOSEDELIMITER)* CLOSEDELIMITER)+
+     * EPILOGUE
+     *
+     * First getStatus is: NOSTARTED
+     *
+     * Content-type: multipart/form-data, boundary=AaB03x => PREAMBLE in Header
+     *
+     * --AaB03x => HEADERDELIMITER content-disposition: form-data; name="field1"
+     * => DISPOSITION
+     *
+     * Joe Blow => FIELD --AaB03x => HEADERDELIMITER content-disposition:
+     * form-data; name="pics" => DISPOSITION Content-type: multipart/mixed,
+     * boundary=BbC04y
+     *
+     * --BbC04y => MIXEDDELIMITER Content-disposition: attachment;
+     * filename="file1.txt" => MIXEDDISPOSITION Content-Type: text/plain
+     *
+     * ... contents of file1.txt ... => MIXEDFILEUPLOAD --BbC04y =>
+     * MIXEDDELIMITER Content-disposition: file; filename="file2.gif" =>
+     * MIXEDDISPOSITION Content-type: image/gif Content-Transfer-Encoding:
+     * binary
+     *
+     * ...contents of file2.gif... => MIXEDFILEUPLOAD --BbC04y-- =>
+     * MIXEDCLOSEDELIMITER --AaB03x-- => CLOSEDELIMITER
+     *
+     * Once CLOSEDELIMITER is found, last getStatus is EPILOGUE
+     */
+    protected enum MultiPartStatus {
+        NOTSTARTED, PREAMBLE, HEADERDELIMITER, DISPOSITION, FIELD, FILEUPLOAD, MIXEDPREAMBLE, MIXEDDELIMITER,
+        MIXEDDISPOSITION, MIXEDFILEUPLOAD, MIXEDCLOSEDELIMITER, CLOSEDELIMITER, PREEPILOGUE, EPILOGUE
+    }
+
+    /**
+     * Check if the given request is a multipart request
+     * @return True if the request is a Multipart request
+     */
+    public static boolean isMultipart(HttpRequest request) {
+        String mimeType = request.headers().get(HttpHeaderNames.CONTENT_TYPE);
+        if (mimeType != null && mimeType.startsWith(HttpHeaderValues.MULTIPART_FORM_DATA.toString())) {
+            return getMultipartDataBoundary(mimeType) != null;
+        }
+        return false;
+    }
+
+    /**
+     * Check from the request ContentType if this request is a Multipart request.
+     * @return an array of String if multipartDataBoundary exists with the multipartDataBoundary
+     * as first element, charset if any as second (missing if not set), else null
+     */
+    protected static String[] getMultipartDataBoundary(String contentType) {
+        // Check if Post using "multipart/form-data; boundary=--89421926422648 [; charset=xxx]"
+        String[] headerContentType = splitHeaderContentType(contentType);
+        final String multiPartHeader = HttpHeaderValues.MULTIPART_FORM_DATA.toString();
+        if (headerContentType[0].regionMatches(true, 0, multiPartHeader, 0 , multiPartHeader.length())) {
+            int mrank;
+            int crank;
+            final String boundaryHeader = HttpHeaderValues.BOUNDARY.toString();
+            if (headerContentType[1].regionMatches(true, 0, boundaryHeader, 0, boundaryHeader.length())) {
+                mrank = 1;
+                crank = 2;
+            } else if (headerContentType[2].regionMatches(true, 0, boundaryHeader, 0, boundaryHeader.length())) {
+                mrank = 2;
+                crank = 1;
+            } else {
+                return null;
+            }
+            String boundary = StringUtil.substringAfter(headerContentType[mrank], '=');
+            if (boundary == null) {
+                throw new ErrorDataDecoderException("Needs a boundary value");
+            }
+            if (boundary.charAt(0) == '"') {
+                String bound = boundary.trim();
+                int index = bound.length() - 1;
+                if (bound.charAt(index) == '"') {
+                    boundary = bound.substring(1, index);
+                }
+            }
+            final String charsetHeader = HttpHeaderValues.CHARSET.toString();
+            if (headerContentType[crank].regionMatches(true, 0, charsetHeader, 0, charsetHeader.length())) {
+                String charset = StringUtil.substringAfter(headerContentType[crank], '=');
+                if (charset != null) {
+                    return new String[] {"--" + boundary, charset};
+                }
+            }
+            return new String[] {"--" + boundary};
+        }
+        return null;
+    }
+
+    @Override
+    public boolean isMultipart() {
+        return decoder.isMultipart();
+    }
+
+    @Override
+    public void setDiscardThreshold(int discardThreshold) {
+        decoder.setDiscardThreshold(discardThreshold);
+    }
+
+    @Override
+    public int getDiscardThreshold() {
+        return decoder.getDiscardThreshold();
+    }
+
+    @Override
+    public List<InterfaceHttpData> getBodyHttpDatas() {
+        return decoder.getBodyHttpDatas();
+    }
+
+    @Override
+    public List<InterfaceHttpData> getBodyHttpDatas(String name) {
+        return decoder.getBodyHttpDatas(name);
+    }
+
+    @Override
+    public InterfaceHttpData getBodyHttpData(String name) {
+        return decoder.getBodyHttpData(name);
+    }
+
+    @Override
+    public InterfaceHttpPostRequestDecoder offer(HttpContent content) {
+        return decoder.offer(content);
+    }
+
+    @Override
+    public boolean hasNext() {
+        return decoder.hasNext();
+    }
+
+    @Override
+    public InterfaceHttpData next() {
+        return decoder.next();
+    }
+
+    @Override
+    public InterfaceHttpData currentPartialHttpData() {
+        return decoder.currentPartialHttpData();
+    }
+
+    @Override
+    public void destroy() {
+        decoder.destroy();
+    }
+
+    @Override
+    public void cleanFiles() {
+        decoder.cleanFiles();
+    }
+
+    @Override
+    public void removeHttpDataFromClean(InterfaceHttpData data) {
+        decoder.removeHttpDataFromClean(data);
+    }
+
+    /**
+     * Split the very first line (Content-Type value) in 3 Strings
+     *
+     * @return the array of 3 Strings
+     */
+    private static String[] splitHeaderContentType(String sb) {
+        int aStart;
+        int aEnd;
+        int bStart;
+        int bEnd;
+        int cStart;
+        int cEnd;
+        aStart = HttpPostBodyUtil.findNonWhitespace(sb, 0);
+        aEnd =  sb.indexOf(';');
+        if (aEnd == -1) {
+            return new String[] { sb, "", "" };
+        }
+        bStart = HttpPostBodyUtil.findNonWhitespace(sb, aEnd + 1);
+        if (sb.charAt(aEnd - 1) == ' ') {
+            aEnd--;
+        }
+        bEnd =  sb.indexOf(';', bStart);
+        if (bEnd == -1) {
+            bEnd = HttpPostBodyUtil.findEndOfString(sb);
+            return new String[] { sb.substring(aStart, aEnd), sb.substring(bStart, bEnd), "" };
+        }
+        cStart = HttpPostBodyUtil.findNonWhitespace(sb, bEnd + 1);
+        if (sb.charAt(bEnd - 1) == ' ') {
+            bEnd--;
+        }
+        cEnd = HttpPostBodyUtil.findEndOfString(sb);
+        return new String[] { sb.substring(aStart, aEnd), sb.substring(bStart, bEnd), sb.substring(cStart, cEnd) };
+    }
+
+    /**
+     * Exception when try reading data from request in chunked format, and not
+     * enough data are available (need more chunks)
+     */
+    public static class NotEnoughDataDecoderException extends DecoderException {
+        private static final long serialVersionUID = -7846841864603865638L;
+
+        public NotEnoughDataDecoderException() {
+        }
+
+        public NotEnoughDataDecoderException(String msg) {
+            super(msg);
+        }
+
+        public NotEnoughDataDecoderException(Throwable cause) {
+            super(cause);
+        }
+
+        public NotEnoughDataDecoderException(String msg, Throwable cause) {
+            super(msg, cause);
+        }
+    }
+
+    /**
+     * Exception when the body is fully decoded, even if there is still data
+     */
+    public static class EndOfDataDecoderException extends DecoderException {
+        private static final long serialVersionUID = 1336267941020800769L;
+    }
+
+    /**
+     * Exception when an error occurs while decoding
+     */
+    public static class ErrorDataDecoderException extends DecoderException {
+        private static final long serialVersionUID = 5020247425493164465L;
+
+        public ErrorDataDecoderException() {
+        }
+
+        public ErrorDataDecoderException(String msg) {
+            super(msg);
+        }
+
+        public ErrorDataDecoderException(Throwable cause) {
+            super(cause);
+        }
+
+        public ErrorDataDecoderException(String msg, Throwable cause) {
+            super(msg, cause);
+        }
+    }
+}
diff --git a/src/main/java/io/vertx/core/http/impl/netty/HttpPostStandardRequestDecoder.java b/src/main/java/io/vertx/core/http/impl/netty/HttpPostStandardRequestDecoder.java
new file mode 100644
--- /dev/null
+++ b/src/main/java/io/vertx/core/http/impl/netty/HttpPostStandardRequestDecoder.java
@@ -0,0 +1,779 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package io.vertx.core.http.impl.netty;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import io.netty.handler.codec.http.HttpConstants;
+import io.netty.handler.codec.http.HttpContent;
+import io.netty.handler.codec.http.HttpRequest;
+import io.netty.handler.codec.http.LastHttpContent;
+import io.netty.handler.codec.http.QueryStringDecoder;
+import io.netty.handler.codec.http.multipart.Attribute;
+import io.netty.handler.codec.http.multipart.DefaultHttpDataFactory;
+import io.netty.handler.codec.http.multipart.HttpData;
+import io.netty.handler.codec.http.multipart.HttpDataFactory;
+import io.vertx.core.http.impl.netty.HttpPostBodyUtil.SeekAheadOptimize;
+import io.vertx.core.http.impl.netty.HttpPostRequestDecoder.EndOfDataDecoderException;
+import io.vertx.core.http.impl.netty.HttpPostRequestDecoder.ErrorDataDecoderException;
+import io.vertx.core.http.impl.netty.HttpPostRequestDecoder.MultiPartStatus;
+import io.vertx.core.http.impl.netty.HttpPostRequestDecoder.NotEnoughDataDecoderException;
+import io.netty.handler.codec.http.multipart.InterfaceHttpData;
+import io.netty.handler.codec.http.multipart.InterfaceHttpPostRequestDecoder;
+import io.netty.util.ByteProcessor;
+import io.netty.util.internal.PlatformDependent;
+import io.netty.util.internal.StringUtil;
+
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+import static io.netty.util.internal.ObjectUtil.checkNotNull;
+import static io.netty.util.internal.ObjectUtil.checkPositiveOrZero;
+
+/**
+ * This decoder will decode Body and can handle POST BODY.
+ *
+ * You <strong>MUST</strong> call {@link #destroy()} after completion to release all resources.
+ *
+ */
+public class HttpPostStandardRequestDecoder implements InterfaceHttpPostRequestDecoder {
+
+    /**
+     * Factory used to create InterfaceHttpData
+     */
+    private final HttpDataFactory factory;
+
+    /**
+     * Request to decode
+     */
+    private final HttpRequest request;
+
+    /**
+     * Default charset to use
+     */
+    private final Charset charset;
+
+    /**
+     * Does the last chunk already received
+     */
+    private boolean isLastChunk;
+
+    /**
+     * HttpDatas from Body
+     */
+    private final List<InterfaceHttpData> bodyListHttpData = new ArrayList<InterfaceHttpData>();
+
+    /**
+     * HttpDatas as Map from Body
+     */
+    private final Map<String, List<InterfaceHttpData>> bodyMapHttpData = new TreeMap<String, List<InterfaceHttpData>>(
+            CaseIgnoringComparator.INSTANCE);
+
+    /**
+     * The current channelBuffer
+     */
+    private ByteBuf undecodedChunk;
+
+    /**
+     * Body HttpDatas current position
+     */
+    private int bodyListHttpDataRank;
+
+    /**
+     * Current getStatus
+     */
+    private MultiPartStatus currentStatus = MultiPartStatus.NOTSTARTED;
+
+    /**
+     * The current Attribute that is currently in decode process
+     */
+    private Attribute currentAttribute;
+
+    private boolean destroyed;
+
+    private int discardThreshold = HttpPostRequestDecoder.DEFAULT_DISCARD_THRESHOLD;
+
+    /**
+     *
+     * @param request
+     *            the request to decode
+     * @throws NullPointerException
+     *             for request
+     * @throws ErrorDataDecoderException
+     *             if the default charset was wrong when decoding or other
+     *             errors
+     */
+    public HttpPostStandardRequestDecoder(HttpRequest request) {
+        this(new DefaultHttpDataFactory(DefaultHttpDataFactory.MINSIZE), request, HttpConstants.DEFAULT_CHARSET);
+    }
+
+    /**
+     *
+     * @param factory
+     *            the factory used to create InterfaceHttpData
+     * @param request
+     *            the request to decode
+     * @throws NullPointerException
+     *             for request or factory
+     * @throws ErrorDataDecoderException
+     *             if the default charset was wrong when decoding or other
+     *             errors
+     */
+    public HttpPostStandardRequestDecoder(HttpDataFactory factory, HttpRequest request) {
+        this(factory, request, HttpConstants.DEFAULT_CHARSET);
+    }
+
+    /**
+     *
+     * @param factory
+     *            the factory used to create InterfaceHttpData
+     * @param request
+     *            the request to decode
+     * @param charset
+     *            the charset to use as default
+     * @throws NullPointerException
+     *             for request or charset or factory
+     * @throws ErrorDataDecoderException
+     *             if the default charset was wrong when decoding or other
+     *             errors
+     */
+    public HttpPostStandardRequestDecoder(HttpDataFactory factory, HttpRequest request, Charset charset) {
+        this.request = checkNotNull(request, "request");
+        this.charset = checkNotNull(charset, "charset");
+        this.factory = checkNotNull(factory, "factory");
+        try {
+            if (request instanceof HttpContent) {
+                // Offer automatically if the given request is as type of HttpContent
+                // See #1089
+                offer((HttpContent) request);
+            } else {
+                parseBody();
+            }
+        } catch (Throwable e) {
+            destroy();
+            PlatformDependent.throwException(e);
+        }
+    }
+
+    private void checkDestroyed() {
+        if (destroyed) {
+            throw new IllegalStateException(HttpPostStandardRequestDecoder.class.getSimpleName()
+                    + " was destroyed already");
+        }
+    }
+
+    /**
+     * True if this request is a Multipart request
+     *
+     * @return True if this request is a Multipart request
+     */
+    @Override
+    public boolean isMultipart() {
+        checkDestroyed();
+        return false;
+    }
+
+    /**
+     * Set the amount of bytes after which read bytes in the buffer should be discarded.
+     * Setting this lower gives lower memory usage but with the overhead of more memory copies.
+     * Use {@code 0} to disable it.
+     */
+    @Override
+    public void setDiscardThreshold(int discardThreshold) {
+        this.discardThreshold = checkPositiveOrZero(discardThreshold, "discardThreshold");
+    }
+
+    /**
+     * Return the threshold in bytes after which read data in the buffer should be discarded.
+     */
+    @Override
+    public int getDiscardThreshold() {
+        return discardThreshold;
+    }
+
+    /**
+     * This getMethod returns a List of all HttpDatas from body.<br>
+     *
+     * If chunked, all chunks must have been offered using offer() getMethod. If
+     * not, NotEnoughDataDecoderException will be raised.
+     *
+     * @return the list of HttpDatas from Body part for POST getMethod
+     * @throws NotEnoughDataDecoderException
+     *             Need more chunks
+     */
+    @Override
+    public List<InterfaceHttpData> getBodyHttpDatas() {
+        checkDestroyed();
+
+        if (!isLastChunk) {
+            throw new NotEnoughDataDecoderException();
+        }
+        return bodyListHttpData;
+    }
+
+    /**
+     * This getMethod returns a List of all HttpDatas with the given name from
+     * body.<br>
+     *
+     * If chunked, all chunks must have been offered using offer() getMethod. If
+     * not, NotEnoughDataDecoderException will be raised.
+     *
+     * @return All Body HttpDatas with the given name (ignore case)
+     * @throws NotEnoughDataDecoderException
+     *             need more chunks
+     */
+    @Override
+    public List<InterfaceHttpData> getBodyHttpDatas(String name) {
+        checkDestroyed();
+
+        if (!isLastChunk) {
+            throw new NotEnoughDataDecoderException();
+        }
+        return bodyMapHttpData.get(name);
+    }
+
+    /**
+     * This getMethod returns the first InterfaceHttpData with the given name from
+     * body.<br>
+     *
+     * If chunked, all chunks must have been offered using offer() getMethod. If
+     * not, NotEnoughDataDecoderException will be raised.
+     *
+     * @return The first Body InterfaceHttpData with the given name (ignore
+     *         case)
+     * @throws NotEnoughDataDecoderException
+     *             need more chunks
+     */
+    @Override
+    public InterfaceHttpData getBodyHttpData(String name) {
+        checkDestroyed();
+
+        if (!isLastChunk) {
+            throw new NotEnoughDataDecoderException();
+        }
+        List<InterfaceHttpData> list = bodyMapHttpData.get(name);
+        if (list != null) {
+            return list.get(0);
+        }
+        return null;
+    }
+
+    /**
+     * Initialized the internals from a new chunk
+     *
+     * @param content
+     *            the new received chunk
+     * @throws ErrorDataDecoderException
+     *             if there is a problem with the charset decoding or other
+     *             errors
+     */
+    @Override
+    public HttpPostStandardRequestDecoder offer(HttpContent content) {
+        checkDestroyed();
+
+        if (content instanceof LastHttpContent) {
+            isLastChunk = true;
+        }
+
+        ByteBuf buf = content.content();
+        if (undecodedChunk == null) {
+            undecodedChunk =
+                    // Since the Handler will release the incoming later on, we need to copy it
+                    //
+                    // We are explicit allocate a buffer and NOT calling copy() as otherwise it may set a maxCapacity
+                    // which is not really usable for us as we may exceed it once we add more bytes.
+                    buf.alloc().buffer(buf.readableBytes()).writeBytes(buf);
+        } else {
+            undecodedChunk.writeBytes(buf);
+        }
+        parseBody();
+        if (undecodedChunk != null && undecodedChunk.writerIndex() > discardThreshold) {
+            if (undecodedChunk.refCnt() == 1) {
+                // It's safe to call discardBytes() as we are the only owner of the buffer.
+                undecodedChunk.discardReadBytes();
+            } else {
+                // There seems to be multiple references of the buffer. Let's copy the data and release the buffer to
+                // ensure we can give back memory to the system.
+                ByteBuf buffer = undecodedChunk.alloc().buffer(undecodedChunk.readableBytes());
+                buffer.writeBytes(undecodedChunk);
+                undecodedChunk.release();
+                undecodedChunk = buffer;
+            }
+        }
+        return this;
+    }
+
+    /**
+     * True if at current getStatus, there is an available decoded
+     * InterfaceHttpData from the Body.
+     *
+     * This getMethod works for chunked and not chunked request.
+     *
+     * @return True if at current getStatus, there is a decoded InterfaceHttpData
+     * @throws EndOfDataDecoderException
+     *             No more data will be available
+     */
+    @Override
+    public boolean hasNext() {
+        checkDestroyed();
+
+        if (currentStatus == MultiPartStatus.EPILOGUE) {
+            // OK except if end of list
+            if (bodyListHttpDataRank >= bodyListHttpData.size()) {
+                throw new EndOfDataDecoderException();
+            }
+        }
+        return !bodyListHttpData.isEmpty() && bodyListHttpDataRank < bodyListHttpData.size();
+    }
+
+    /**
+     * Returns the next available InterfaceHttpData or null if, at the time it
+     * is called, there is no more available InterfaceHttpData. A subsequent
+     * call to offer(httpChunk) could enable more data.
+     *
+     * Be sure to call {@link InterfaceHttpData#release()} after you are done
+     * with processing to make sure to not leak any resources
+     *
+     * @return the next available InterfaceHttpData or null if none
+     * @throws EndOfDataDecoderException
+     *             No more data will be available
+     */
+    @Override
+    public InterfaceHttpData next() {
+        checkDestroyed();
+
+        if (hasNext()) {
+            return bodyListHttpData.get(bodyListHttpDataRank++);
+        }
+        return null;
+    }
+
+    @Override
+    public InterfaceHttpData currentPartialHttpData() {
+        return currentAttribute;
+    }
+
+    /**
+     * This getMethod will parse as much as possible data and fill the list and map
+     *
+     * @throws ErrorDataDecoderException
+     *             if there is a problem with the charset decoding or other
+     *             errors
+     */
+    private void parseBody() {
+        if (currentStatus == MultiPartStatus.PREEPILOGUE || currentStatus == MultiPartStatus.EPILOGUE) {
+            if (isLastChunk) {
+                currentStatus = MultiPartStatus.EPILOGUE;
+            }
+            return;
+        }
+        parseBodyAttributes();
+    }
+
+    /**
+     * Utility function to add a new decoded data
+     */
+    protected void addHttpData(InterfaceHttpData data) {
+        if (data == null) {
+            return;
+        }
+        List<InterfaceHttpData> datas = bodyMapHttpData.get(data.getName());
+        if (datas == null) {
+            datas = new ArrayList<InterfaceHttpData>(1);
+            bodyMapHttpData.put(data.getName(), datas);
+        }
+        datas.add(data);
+        bodyListHttpData.add(data);
+    }
+
+    /**
+     * This getMethod fill the map and list with as much Attribute as possible from
+     * Body in not Multipart mode.
+     *
+     * @throws ErrorDataDecoderException
+     *             if there is a problem with the charset decoding or other
+     *             errors
+     */
+    private void parseBodyAttributesStandard() {
+        int firstpos = undecodedChunk.readerIndex();
+        int currentpos = firstpos;
+        int equalpos;
+        int ampersandpos;
+        if (currentStatus == MultiPartStatus.NOTSTARTED) {
+            currentStatus = MultiPartStatus.DISPOSITION;
+        }
+        boolean contRead = true;
+        try {
+            while (undecodedChunk.isReadable() && contRead) {
+                char read = (char) undecodedChunk.readUnsignedByte();
+                currentpos++;
+                switch (currentStatus) {
+                case DISPOSITION:// search '='
+                    if (read == '=') {
+                        currentStatus = MultiPartStatus.FIELD;
+                        equalpos = currentpos - 1;
+                        String key = decodeAttribute(undecodedChunk.toString(firstpos, equalpos - firstpos, charset),
+                                charset);
+                        currentAttribute = factory.createAttribute(request, key);
+                        firstpos = currentpos;
+                    } else if (read == '&') { // special empty FIELD
+                        currentStatus = MultiPartStatus.DISPOSITION;
+                        ampersandpos = currentpos - 1;
+                        String key = decodeAttribute(
+                                undecodedChunk.toString(firstpos, ampersandpos - firstpos, charset), charset);
+                        currentAttribute = factory.createAttribute(request, key);
+                        currentAttribute.setValue(""); // empty
+                        addHttpData(currentAttribute);
+                        currentAttribute = null;
+                        firstpos = currentpos;
+                        contRead = true;
+                    }
+                    break;
+                case FIELD:// search '&' or end of line
+                    if (read == '&') {
+                        currentStatus = MultiPartStatus.DISPOSITION;
+                        ampersandpos = currentpos - 1;
+                        setFinalBuffer(undecodedChunk.retainedSlice(firstpos, ampersandpos - firstpos));
+                        firstpos = currentpos;
+                        contRead = true;
+                    } else if (read == HttpConstants.CR) {
+                        if (undecodedChunk.isReadable()) {
+                            read = (char) undecodedChunk.readUnsignedByte();
+                            currentpos++;
+                            if (read == HttpConstants.LF) {
+                                currentStatus = MultiPartStatus.PREEPILOGUE;
+                                ampersandpos = currentpos - 2;
+                                setFinalBuffer(undecodedChunk.retainedSlice(firstpos, ampersandpos - firstpos));
+                                firstpos = currentpos;
+                                contRead = false;
+                            } else {
+                                // Error
+                                throw new ErrorDataDecoderException("Bad end of line");
+                            }
+                        } else {
+                            currentpos--;
+                        }
+                    } else if (read == HttpConstants.LF) {
+                        currentStatus = MultiPartStatus.PREEPILOGUE;
+                        ampersandpos = currentpos - 1;
+                        setFinalBuffer(undecodedChunk.retainedSlice(firstpos, ampersandpos - firstpos));
+                        firstpos = currentpos;
+                        contRead = false;
+                    }
+                    break;
+                default:
+                    // just stop
+                    contRead = false;
+                }
+            }
+            if (isLastChunk && currentAttribute != null) {
+                // special case
+                ampersandpos = currentpos;
+                if (ampersandpos > firstpos) {
+                    setFinalBuffer(undecodedChunk.retainedSlice(firstpos, ampersandpos - firstpos));
+                } else if (!currentAttribute.isCompleted()) {
+                    setFinalBuffer(Unpooled.EMPTY_BUFFER);
+                }
+                firstpos = currentpos;
+                currentStatus = MultiPartStatus.EPILOGUE;
+            } else if (contRead && currentAttribute != null && currentStatus == MultiPartStatus.FIELD) {
+                // reset index except if to continue in case of FIELD getStatus
+                currentAttribute.addContent(undecodedChunk.retainedSlice(firstpos, currentpos - firstpos),
+                                            false);
+                firstpos = currentpos;
+            }
+            undecodedChunk.readerIndex(firstpos);
+        } catch (ErrorDataDecoderException e) {
+            // error while decoding
+            undecodedChunk.readerIndex(firstpos);
+            throw e;
+        } catch (IOException e) {
+            // error while decoding
+            undecodedChunk.readerIndex(firstpos);
+            throw new ErrorDataDecoderException(e);
+        } catch (IllegalArgumentException e) {
+            // error while decoding
+            undecodedChunk.readerIndex(firstpos);
+            throw new ErrorDataDecoderException(e);
+        }
+    }
+
+    /**
+     * This getMethod fill the map and list with as much Attribute as possible from
+     * Body in not Multipart mode.
+     *
+     * @throws ErrorDataDecoderException
+     *             if there is a problem with the charset decoding or other
+     *             errors
+     */
+    private void parseBodyAttributes() {
+        if (undecodedChunk == null) {
+            return;
+        }
+        if (!undecodedChunk.hasArray()) {
+            parseBodyAttributesStandard();
+            return;
+        }
+        SeekAheadOptimize sao = new SeekAheadOptimize(undecodedChunk);
+        int firstpos = undecodedChunk.readerIndex();
+        int currentpos = firstpos;
+        int equalpos;
+        int ampersandpos;
+        if (currentStatus == MultiPartStatus.NOTSTARTED) {
+            currentStatus = MultiPartStatus.DISPOSITION;
+        }
+        boolean contRead = true;
+        try {
+            loop: while (sao.pos < sao.limit) {
+                char read = (char) (sao.bytes[sao.pos++] & 0xFF);
+                currentpos++;
+                switch (currentStatus) {
+                case DISPOSITION:// search '='
+                    if (read == '=') {
+                        currentStatus = MultiPartStatus.FIELD;
+                        equalpos = currentpos - 1;
+                        String key = decodeAttribute(undecodedChunk.toString(firstpos, equalpos - firstpos, charset),
+                                charset);
+                        currentAttribute = factory.createAttribute(request, key);
+                        firstpos = currentpos;
+                    } else if (read == '&') { // special empty FIELD
+                        currentStatus = MultiPartStatus.DISPOSITION;
+                        ampersandpos = currentpos - 1;
+                        String key = decodeAttribute(
+                                undecodedChunk.toString(firstpos, ampersandpos - firstpos, charset), charset);
+                        currentAttribute = factory.createAttribute(request, key);
+                        currentAttribute.setValue(""); // empty
+                        addHttpData(currentAttribute);
+                        currentAttribute = null;
+                        firstpos = currentpos;
+                        contRead = true;
+                    }
+                    break;
+                case FIELD:// search '&' or end of line
+                    if (read == '&') {
+                        currentStatus = MultiPartStatus.DISPOSITION;
+                        ampersandpos = currentpos - 1;
+                        setFinalBuffer(undecodedChunk.retainedSlice(firstpos, ampersandpos - firstpos));
+                        firstpos = currentpos;
+                        contRead = true;
+                    } else if (read == HttpConstants.CR) {
+                        if (sao.pos < sao.limit) {
+                            read = (char) (sao.bytes[sao.pos++] & 0xFF);
+                            currentpos++;
+                            if (read == HttpConstants.LF) {
+                                currentStatus = MultiPartStatus.PREEPILOGUE;
+                                ampersandpos = currentpos - 2;
+                                sao.setReadPosition(0);
+                                setFinalBuffer(undecodedChunk.retainedSlice(firstpos, ampersandpos - firstpos));
+                                firstpos = currentpos;
+                                contRead = false;
+                                break loop;
+                            } else {
+                                // Error
+                                sao.setReadPosition(0);
+                                throw new ErrorDataDecoderException("Bad end of line");
+                            }
+                        } else {
+                            if (sao.limit > 0) {
+                                currentpos--;
+                            }
+                        }
+                    } else if (read == HttpConstants.LF) {
+                        currentStatus = MultiPartStatus.PREEPILOGUE;
+                        ampersandpos = currentpos - 1;
+                        sao.setReadPosition(0);
+                        setFinalBuffer(undecodedChunk.retainedSlice(firstpos, ampersandpos - firstpos));
+                        firstpos = currentpos;
+                        contRead = false;
+                        break loop;
+                    }
+                    break;
+                default:
+                    // just stop
+                    sao.setReadPosition(0);
+                    contRead = false;
+                    break loop;
+                }
+            }
+            if (isLastChunk && currentAttribute != null) {
+                // special case
+                ampersandpos = currentpos;
+                if (ampersandpos > firstpos) {
+                    setFinalBuffer(undecodedChunk.retainedSlice(firstpos, ampersandpos - firstpos));
+                } else if (!currentAttribute.isCompleted()) {
+                    setFinalBuffer(Unpooled.EMPTY_BUFFER);
+                }
+                firstpos = currentpos;
+                currentStatus = MultiPartStatus.EPILOGUE;
+            } else if (contRead && currentAttribute != null && currentStatus == MultiPartStatus.FIELD) {
+                // reset index except if to continue in case of FIELD getStatus
+                currentAttribute.addContent(undecodedChunk.retainedSlice(firstpos, currentpos - firstpos),
+                                            false);
+                firstpos = currentpos;
+            }
+            undecodedChunk.readerIndex(firstpos);
+        } catch (ErrorDataDecoderException e) {
+            // error while decoding
+            undecodedChunk.readerIndex(firstpos);
+            throw e;
+        } catch (IOException e) {
+            // error while decoding
+            undecodedChunk.readerIndex(firstpos);
+            throw new ErrorDataDecoderException(e);
+        } catch (IllegalArgumentException e) {
+            // error while decoding
+            undecodedChunk.readerIndex(firstpos);
+            throw new ErrorDataDecoderException(e);
+        }
+    }
+
+    private void setFinalBuffer(ByteBuf buffer) throws IOException {
+        currentAttribute.addContent(buffer, true);
+        ByteBuf decodedBuf = decodeAttribute(currentAttribute.getByteBuf(), charset);
+        if (decodedBuf != null) { // override content only when ByteBuf needed decoding
+            currentAttribute.setContent(decodedBuf);
+        }
+        addHttpData(currentAttribute);
+        currentAttribute = null;
+    }
+
+    /**
+     * Decode component
+     *
+     * @return the decoded component
+     */
+    private static String decodeAttribute(String s, Charset charset) {
+        try {
+            return QueryStringDecoder.decodeComponent(s, charset);
+        } catch (IllegalArgumentException e) {
+            throw new ErrorDataDecoderException("Bad string: '" + s + '\'', e);
+        }
+    }
+
+    private static ByteBuf decodeAttribute(ByteBuf b, Charset charset) {
+        int firstEscaped = b.forEachByte(new UrlEncodedDetector());
+        if (firstEscaped == -1) {
+            return null; // nothing to decode
+        }
+
+        ByteBuf buf = b.alloc().buffer(b.readableBytes());
+        UrlDecoder urlDecode = new UrlDecoder(buf);
+        int idx = b.forEachByte(urlDecode);
+        if (urlDecode.nextEscapedIdx != 0) { // incomplete hex byte
+            if (idx == -1) {
+                idx = b.readableBytes() - 1;
+            }
+            idx -= urlDecode.nextEscapedIdx - 1;
+            buf.release();
+            throw new ErrorDataDecoderException(
+                String.format("Invalid hex byte at index '%d' in string: '%s'", idx, b.toString(charset)));
+        }
+
+        return buf;
+    }
+
+    /**
+     * Destroy the {@link HttpPostStandardRequestDecoder} and release all it resources. After this method
+     * was called it is not possible to operate on it anymore.
+     */
+    @Override
+    public void destroy() {
+        // Release all data items, including those not yet pulled, only file based items
+        cleanFiles();
+        // Clean Memory based data
+        for (InterfaceHttpData httpData : bodyListHttpData) {
+            // Might have been already released by the user
+            if (httpData.refCnt() > 0) {
+                httpData.release();
+            }
+        }
+
+        destroyed = true;
+
+        if (undecodedChunk != null && undecodedChunk.refCnt() > 0) {
+            undecodedChunk.release();
+            undecodedChunk = null;
+        }
+    }
+
+    /**
+     * Clean all {@link HttpData}s for the current request.
+     */
+    @Override
+    public void cleanFiles() {
+        checkDestroyed();
+
+        factory.cleanRequestHttpData(request);
+    }
+
+    /**
+     * Remove the given FileUpload from the list of FileUploads to clean
+     */
+    @Override
+    public void removeHttpDataFromClean(InterfaceHttpData data) {
+        checkDestroyed();
+
+        factory.removeHttpDataFromClean(request, data);
+    }
+
+    private static final class UrlEncodedDetector implements ByteProcessor {
+        @Override
+        public boolean process(byte value) throws Exception {
+            return value != '%' && value != '+';
+        }
+    }
+
+    private static final class UrlDecoder implements ByteProcessor {
+
+        private final ByteBuf output;
+        private int nextEscapedIdx;
+        private byte hiByte;
+
+        UrlDecoder(ByteBuf output) {
+            this.output = output;
+        }
+
+        @Override
+        public boolean process(byte value) {
+            if (nextEscapedIdx != 0) {
+                if (nextEscapedIdx == 1) {
+                    hiByte = value;
+                    ++nextEscapedIdx;
+                } else {
+                    int hi = StringUtil.decodeHexNibble((char) hiByte);
+                    int lo = StringUtil.decodeHexNibble((char) value);
+                    if (hi == -1 || lo == -1) {
+                        ++nextEscapedIdx;
+                        return false;
+                    }
+                    output.writeByte((hi << 4) + lo);
+                    nextEscapedIdx = 0;
+                }
+            } else if (value == '%') {
+                nextEscapedIdx = 1;
+            } else if (value == '+') {
+                output.writeByte(' ');
+            } else {
+                output.writeByte(value);
+            }
+            return true;
+        }
+    }
+}

diff --git a/src/test/java/io/vertx/core/http/Http1xServerFileUploadTest.java b/src/test/java/io/vertx/core/http/Http1xServerFileUploadTest.java
--- a/src/test/java/io/vertx/core/http/Http1xServerFileUploadTest.java
+++ b/src/test/java/io/vertx/core/http/Http1xServerFileUploadTest.java
@@ -17,7 +17,6 @@
  */
 public class Http1xServerFileUploadTest extends HttpServerFileUploadTest {
 
-
   @Ignore
   @Test
   @Override

Request for a new ContextImpl that mimics a Worker Context but allows multiple queues
I have run into the following issue in the past before using Vert.x and now that I am using it as well. I really like the functionality that a Worker Context provides: A separate thread that runs tasks in the order they were submitted to the context to be executed. However this functionality could be even more helpful if the Worker Context would allow the submission of tasks to be executed in order in different queues based on some (defined?arbitrary?) identifier/priority. For example, I have the need to "serialize" the execution of tasks but tasks which have different priorities could be executed in parallel but I still need the "serialization" feature for tasks of the same priority.

Thanks - Juan


hi, do you have an actual use case for this ?
Yes. I need to process several messages on a worker context but those messages can be categorized and there is no need for messages of category A to be processed sequentially with messages of category B, but all messages for category A/B need to be processed in order.. Think about messages for order processing (not my actual use case but close), I have a Verticle which processes messages (creation, activation, provision, etc.) but all messages for an order need to be processed sequentially but messages for different orders can be processed in parallel. I think this new ContextImpl would be some sort hybrid of Worker and Multi-threaded contexts.
why would you need a specific context implementation for this ?

there is this existing issue https://github.com/eclipse/vert.x/issues/1539 that could somehow help for your use case
@vietj I agree that requesting the creation of a new Context Implementation might not be the right approach. My main complain with a possible implementation of #1539 (unless I am wrong the issue has no solution yet, right?) is that there is no way to indicate how to place the Handler in a specific TaskQueue for execution unless the signature of Context.executeBlocking can change to accept a new structure instead of a Handler (so you could determine what TaskQueue to use). I was hoping that there could be something of an `executeBlocking(blockingCodeHandler, "Category/Queue", resultCodeHandler)` that would allow the blockingCodeHandler to be executed on a particular TaskQueue. Anyway, it seems you have not warmed up to the idea, so please feel free to close the issue. IMHO I still think there is value in supporting something like this.
After some thought I was able to come up with a simple(r?) solution to what I think the improvement should be:

https://gist.github.com/juanavelez/bf47ae84f842896350ca046a1d88ac5f.

Let me know what you think. 

Thanks - Juan
it seems you can do it without requiring a modification in Vert.x itself with an helper
I am sorry I don't understand what you meant since my modifications where to Vert.x core code themselves: src/main/java/io/vertx/core/Context.java, src/main/java/io/vertx/core/Vertx.java, src/main/java/io/vertx/core/impl/ContextImpl.java, src/main/java/io/vertx/core/impl/VertxImpl.java, src/test/java/io/vertx/test/core/ContextTest.java
I mean that we cannot have this in Vert.x core and you should make your own helper based on Vert.x core to implement this particular use case you need
May I ask why you cannot have this in Vert.x core? 
why should we have ?
no offense to your proposal but this code does not seem generic purpose enough to be in Vert.x Core and we prefer to keep the Vert.x Core API minimal (less is more) and not overload it with a different meaning.

In your case you seem to need to add the notion of priority to task execution and it seems better to handle this with your concurrency util build on top of Vert.x Context concurrency. This will also allow you to improve it and tune it to your needs.

If you think this can interest the community, I encourage you to create a library for it and publish it then on the vertx-awesome page https://github.com/vert-x3/vertx-awesome . That's what many people in the community do.