Repository: netty/netty
Issue: Clean up use of builders in Http2ConnectionHandler
Labels: improvement

Currently Http2ConnectionHandler takes builders for the encoder and decoder, which makes it difficult to decorate them.  We should look at getting rid of this.


Issue comments:

Pull request review comments:
This is the main ugly bit.  A chicken and the egg problem ... handler needs the encoder/decoder and they need the handler.  If you have a better solution, I'm all ears.

@nmittler could we just get rid of the builder altogether? It's only 5 arguments and we are not doing anything special in the builder. Why not pass those arguments to the constructor directly?

@nmittler instead of calling `init()` here, could we do this as the last statement in the constructor? I know it's generally a anti-pattern to let `this` escape from within the constructor, but I think it's fine here? WDYT?

Done.

Done.

Do we need these null checks here?  Won't `DefaultHttp2ConnectionEncoder` and `DefaultHttp2ConnectionDecoder` do these?

I think we should avoid the escaping constructor issue.  We may be OK now but changes in the future, or extensions of our classes may experience half initialized object issues.  Static factory method is the first thing that comes to mind...can anyone think of anything more clever?

Done.

Done ... doing everything when the handler is added now.

Were we at one point checking if this was null before setting it?

We check that the `encoder` and `decoder` are non-null in the constructor.

Ok wasn't sure if we wanted to do the same here...but I guess we want to at least ensure they have a consistent lifecycle manager.

@Scottmitch what exactly is the action you're asking for here?  It seems that this code guarantees they have a consistent lifecycle managers. Or are you suggesting that we ensure the lifecycle managers for both are null in the constructor?

@nmittler sry for being unclear. I'm wondering if we should check if the encoder and decoder already have a lifecycle manager set before we set them (also check to make sure the are the same after). Haven't thought about it too much and I would have to check if the http2connectionhandler has any limitations where it has to be the lifecyclemanager for the encoder/decoder that it uses. Not essential but wdyt?

@Scottmitch I didn't provide a get method for the `lifecycleListener` to this point, since it's not needed for anything.  I'm not convinced it's worth it just to handle this ... WDYT?

@nmittler - sgtm. A user could extend the encoder/decoder and override the setter methods if they really wanted I guess.


Patch that resolved the issue:
diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/CompressorHttp2ConnectionEncoder.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/CompressorHttp2ConnectionEncoder.java
index 5adc5e0af51..04f01b93415 100644
--- a/codec-http2/src/main/java/io/netty/handler/codec/http2/CompressorHttp2ConnectionEncoder.java
+++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/CompressorHttp2ConnectionEncoder.java
@@ -34,10 +34,10 @@
 import io.netty.handler.codec.compression.ZlibWrapper;
 
 /**
- * A HTTP2 encoder that will compress data frames according to the {@code content-encoding} header for each stream.
- * The compression provided by this class will be applied to the data for the entire stream.
+ * A decorating HTTP2 encoder that will compress data frames according to the {@code content-encoding} header for each
+ * stream. The compression provided by this class will be applied to the data for the entire stream.
  */
-public class CompressorHttp2ConnectionEncoder extends DefaultHttp2ConnectionEncoder {
+public class CompressorHttp2ConnectionEncoder extends DecoratingHttp2ConnectionEncoder {
     private static final Http2ConnectionAdapter CLEAN_UP_LISTENER = new Http2ConnectionAdapter() {
         @Override
         public void streamRemoved(Http2Stream stream) {
@@ -48,53 +48,33 @@ public void streamRemoved(Http2Stream stream) {
         }
     };
 
+    public static final int DEFAULT_COMPRESSION_LEVEL = 6;
+    public static final int DEFAULT_WINDOW_BITS = 15;
+    public static final int DEFAULT_MEM_LEVEL = 8;
+
     private final int compressionLevel;
     private final int windowBits;
     private final int memLevel;
 
-    /**
-     * Builder for new instances of {@link CompressorHttp2ConnectionEncoder}
-     */
-    public static class Builder extends DefaultHttp2ConnectionEncoder.Builder {
-        protected int compressionLevel = 6;
-        protected int windowBits = 15;
-        protected int memLevel = 8;
-
-        public Builder compressionLevel(int compressionLevel) {
-            this.compressionLevel = compressionLevel;
-            return this;
-        }
-
-        public Builder windowBits(int windowBits) {
-            this.windowBits = windowBits;
-            return this;
-        }
-
-        public Builder memLevel(int memLevel) {
-            this.memLevel = memLevel;
-            return this;
-        }
-
-        @Override
-        public CompressorHttp2ConnectionEncoder build() {
-            return new CompressorHttp2ConnectionEncoder(this);
-        }
+    public CompressorHttp2ConnectionEncoder(Http2ConnectionEncoder delegate) {
+        this(delegate, DEFAULT_COMPRESSION_LEVEL, DEFAULT_WINDOW_BITS, DEFAULT_MEM_LEVEL);
     }
 
-    protected CompressorHttp2ConnectionEncoder(Builder builder) {
-        super(builder);
-        if (builder.compressionLevel < 0 || builder.compressionLevel > 9) {
-            throw new IllegalArgumentException("compressionLevel: " + builder.compressionLevel + " (expected: 0-9)");
+    public CompressorHttp2ConnectionEncoder(Http2ConnectionEncoder delegate, int compressionLevel, int windowBits,
+                                            int memLevel) {
+        super(delegate);
+        if (compressionLevel < 0 || compressionLevel > 9) {
+            throw new IllegalArgumentException("compressionLevel: " + compressionLevel + " (expected: 0-9)");
         }
-        if (builder.windowBits < 9 || builder.windowBits > 15) {
-            throw new IllegalArgumentException("windowBits: " + builder.windowBits + " (expected: 9-15)");
+        if (windowBits < 9 || windowBits > 15) {
+            throw new IllegalArgumentException("windowBits: " + windowBits + " (expected: 9-15)");
         }
-        if (builder.memLevel < 1 || builder.memLevel > 9) {
-            throw new IllegalArgumentException("memLevel: " + builder.memLevel + " (expected: 1-9)");
+        if (memLevel < 1 || memLevel > 9) {
+            throw new IllegalArgumentException("memLevel: " + memLevel + " (expected: 1-9)");
         }
-        compressionLevel = builder.compressionLevel;
-        windowBits = builder.windowBits;
-        memLevel = builder.memLevel;
+        this.compressionLevel = compressionLevel;
+        this.windowBits = windowBits;
+        this.memLevel = memLevel;
 
         connection().addListener(CLEAN_UP_LISTENER);
     }
diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/DecoratingHttp2ConnectionDecoder.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/DecoratingHttp2ConnectionDecoder.java
new file mode 100644
index 00000000000..4e056343cb9
--- /dev/null
+++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/DecoratingHttp2ConnectionDecoder.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2015 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:
+ *
+ * http://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.netty.handler.codec.http2;
+
+import static io.netty.util.internal.ObjectUtil.checkNotNull;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.channel.ChannelHandlerContext;
+
+import java.util.List;
+
+/**
+ * Decorator around another {@link Http2ConnectionDecoder} instance.
+ */
+public class DecoratingHttp2ConnectionDecoder implements Http2ConnectionDecoder {
+    private final Http2ConnectionDecoder delegate;
+
+    public DecoratingHttp2ConnectionDecoder(Http2ConnectionDecoder delegate) {
+        this.delegate = checkNotNull(delegate, "delegate");
+    }
+
+    @Override
+    public void lifecycleManager(Http2LifecycleManager lifecycleManager) {
+        delegate.lifecycleManager(lifecycleManager);
+    }
+
+    @Override
+    public Http2Connection connection() {
+        return delegate.connection();
+    }
+
+    @Override
+    public Http2LocalFlowController flowController() {
+        return delegate.flowController();
+    }
+
+    @Override
+    public Http2FrameListener listener() {
+        return delegate.listener();
+    }
+
+    @Override
+    public void decodeFrame(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Http2Exception {
+        delegate.decodeFrame(ctx, in, out);
+    }
+
+    @Override
+    public Http2Settings localSettings() {
+        return delegate.localSettings();
+    }
+
+    @Override
+    public void localSettings(Http2Settings settings) throws Http2Exception {
+        delegate.localSettings(settings);
+    }
+
+    @Override
+    public boolean prefaceReceived() {
+        return delegate.prefaceReceived();
+    }
+
+    @Override
+    public void close() {
+        delegate.close();
+    }
+}
diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/DecoratingHttp2ConnectionEncoder.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/DecoratingHttp2ConnectionEncoder.java
new file mode 100644
index 00000000000..5c4e95a5637
--- /dev/null
+++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/DecoratingHttp2ConnectionEncoder.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2015 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:
+ *
+ * http://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.netty.handler.codec.http2;
+
+import static io.netty.util.internal.ObjectUtil.checkNotNull;
+
+/**
+ * A decorator around another {@link Http2ConnectionEncoder} instance.
+ */
+public class DecoratingHttp2ConnectionEncoder extends DecoratingHttp2FrameWriter implements Http2ConnectionEncoder {
+    private final Http2ConnectionEncoder delegate;
+
+    public DecoratingHttp2ConnectionEncoder(Http2ConnectionEncoder delegate) {
+        super(delegate);
+        this.delegate = checkNotNull(delegate, "delegate");
+    }
+
+    @Override
+    public void lifecycleManager(Http2LifecycleManager lifecycleManager) {
+        delegate.lifecycleManager(lifecycleManager);
+    }
+
+    @Override
+    public Http2Connection connection() {
+        return delegate.connection();
+    }
+
+    @Override
+    public Http2RemoteFlowController flowController() {
+        return delegate.flowController();
+    }
+
+    @Override
+    public Http2FrameWriter frameWriter() {
+        return delegate.frameWriter();
+    }
+
+    @Override
+    public Http2Settings pollSentSettings() {
+        return delegate.pollSentSettings();
+    }
+
+    @Override
+    public void remoteSettings(Http2Settings settings) throws Http2Exception {
+        delegate.remoteSettings(settings);
+    }
+}
diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/DecoratingHttp2FrameWriter.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/DecoratingHttp2FrameWriter.java
new file mode 100644
index 00000000000..2d2f6505b19
--- /dev/null
+++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/DecoratingHttp2FrameWriter.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2015 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:
+ *
+ * http://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.netty.handler.codec.http2;
+
+import static io.netty.util.internal.ObjectUtil.checkNotNull;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelPromise;
+
+/**
+ * Decorator around another {@link Http2FrameWriter} instance.
+ */
+public class DecoratingHttp2FrameWriter implements Http2FrameWriter {
+    private final Http2FrameWriter delegate;
+
+    public DecoratingHttp2FrameWriter(Http2FrameWriter delegate) {
+        this.delegate = checkNotNull(delegate, "delegate");
+    }
+
+    @Override
+    public ChannelFuture writeData(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding,
+                                   boolean endStream, ChannelPromise promise) {
+        return delegate.writeData(ctx, streamId, data, padding, endStream, promise);
+    }
+
+    @Override
+    public ChannelFuture writeHeaders(ChannelHandlerContext ctx, int streamId, Http2Headers headers, int padding,
+                                      boolean endStream, ChannelPromise promise) {
+        return delegate.writeHeaders(ctx, streamId, headers, padding, endStream, promise);
+    }
+
+    @Override
+    public ChannelFuture writeHeaders(ChannelHandlerContext ctx, int streamId, Http2Headers headers,
+                                      int streamDependency, short weight, boolean exclusive, int padding,
+                                      boolean endStream, ChannelPromise promise) {
+        return delegate
+                .writeHeaders(ctx, streamId, headers, streamDependency, weight, exclusive, padding, endStream, promise);
+    }
+
+    @Override
+    public ChannelFuture writePriority(ChannelHandlerContext ctx, int streamId, int streamDependency, short weight,
+                                       boolean exclusive, ChannelPromise promise) {
+        return delegate.writePriority(ctx, streamId, streamDependency, weight, exclusive, promise);
+    }
+
+    @Override
+    public ChannelFuture writeRstStream(ChannelHandlerContext ctx, int streamId, long errorCode,
+                                        ChannelPromise promise) {
+        return delegate.writeRstStream(ctx, streamId, errorCode, promise);
+    }
+
+    @Override
+    public ChannelFuture writeSettings(ChannelHandlerContext ctx, Http2Settings settings, ChannelPromise promise) {
+        return delegate.writeSettings(ctx, settings, promise);
+    }
+
+    @Override
+    public ChannelFuture writeSettingsAck(ChannelHandlerContext ctx, ChannelPromise promise) {
+        return delegate.writeSettingsAck(ctx, promise);
+    }
+
+    @Override
+    public ChannelFuture writePing(ChannelHandlerContext ctx, boolean ack, ByteBuf data, ChannelPromise promise) {
+        return delegate.writePing(ctx, ack, data, promise);
+    }
+
+    @Override
+    public ChannelFuture writePushPromise(ChannelHandlerContext ctx, int streamId, int promisedStreamId,
+                                          Http2Headers headers, int padding, ChannelPromise promise) {
+        return delegate.writePushPromise(ctx, streamId, promisedStreamId, headers, padding, promise);
+    }
+
+    @Override
+    public ChannelFuture writeGoAway(ChannelHandlerContext ctx, int lastStreamId, long errorCode, ByteBuf debugData,
+                                     ChannelPromise promise) {
+        return delegate.writeGoAway(ctx, lastStreamId, errorCode, debugData, promise);
+    }
+
+    @Override
+    public ChannelFuture writeWindowUpdate(ChannelHandlerContext ctx, int streamId, int windowSizeIncrement,
+                                           ChannelPromise promise) {
+        return delegate.writeWindowUpdate(ctx, streamId, windowSizeIncrement, promise);
+    }
+
+    @Override
+    public ChannelFuture writeFrame(ChannelHandlerContext ctx, byte frameType, int streamId, Http2Flags flags,
+                                    ByteBuf payload, ChannelPromise promise) {
+        return delegate.writeFrame(ctx, frameType, streamId, flags, payload, promise);
+    }
+
+    @Override
+    public Configuration configuration() {
+        return delegate.configuration();
+    }
+
+    @Override
+    public void close() {
+        delegate.close();
+    }
+}
diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2ConnectionDecoder.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2ConnectionDecoder.java
index 33fbc1cf707..f71f5dcd926 100644
--- a/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2ConnectionDecoder.java
+++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2ConnectionDecoder.java
@@ -40,87 +40,40 @@
 public class DefaultHttp2ConnectionDecoder implements Http2ConnectionDecoder {
     private Http2FrameListener internalFrameListener = new PrefaceFrameListener();
     private final Http2Connection connection;
-    private final Http2LifecycleManager lifecycleManager;
+    private Http2LifecycleManager lifecycleManager;
     private final Http2ConnectionEncoder encoder;
     private final Http2FrameReader frameReader;
     private final Http2FrameListener listener;
     private final Http2PromisedRequestVerifier requestVerifier;
 
-    /**
-     * Builder for instances of {@link DefaultHttp2ConnectionDecoder}.
-     */
-    public static class Builder implements Http2ConnectionDecoder.Builder {
-        private Http2Connection connection;
-        private Http2LifecycleManager lifecycleManager;
-        private Http2ConnectionEncoder encoder;
-        private Http2FrameReader frameReader;
-        private Http2FrameListener listener;
-        private Http2PromisedRequestVerifier requestVerifier = ALWAYS_VERIFY;
-
-        @Override
-        public Builder connection(Http2Connection connection) {
-            this.connection = connection;
-            return this;
-        }
-
-        @Override
-        public Builder lifecycleManager(Http2LifecycleManager lifecycleManager) {
-            this.lifecycleManager = lifecycleManager;
-            return this;
-        }
-
-        @Override
-        public Http2LifecycleManager lifecycleManager() {
-            return lifecycleManager;
-        }
-
-        @Override
-        public Builder frameReader(Http2FrameReader frameReader) {
-            this.frameReader = frameReader;
-            return this;
-        }
-
-        @Override
-        public Builder listener(Http2FrameListener listener) {
-            this.listener = listener;
-            return this;
-        }
-
-        @Override
-        public Builder encoder(Http2ConnectionEncoder encoder) {
-            this.encoder = encoder;
-            return this;
-        }
-
-        @Override
-        public Http2ConnectionDecoder.Builder requestVerifier(Http2PromisedRequestVerifier requestVerifier) {
-            this.requestVerifier = requestVerifier;
-            return this;
-        }
-
-        @Override
-        public Http2ConnectionDecoder build() {
-            return new DefaultHttp2ConnectionDecoder(this);
-        }
-    }
-
-    public static Builder newBuilder() {
-        return new Builder();
+    public DefaultHttp2ConnectionDecoder(Http2Connection connection,
+                                         Http2ConnectionEncoder encoder,
+                                         Http2FrameReader frameReader,
+                                         Http2FrameListener listener) {
+        this(connection, encoder, frameReader, listener, ALWAYS_VERIFY);
     }
 
-    protected DefaultHttp2ConnectionDecoder(Builder builder) {
-        connection = checkNotNull(builder.connection, "connection");
-        frameReader = checkNotNull(builder.frameReader, "frameReader");
-        lifecycleManager = checkNotNull(builder.lifecycleManager, "lifecycleManager");
-        encoder = checkNotNull(builder.encoder, "encoder");
-        listener = checkNotNull(builder.listener, "listener");
-        requestVerifier = checkNotNull(builder.requestVerifier, "requestVerifier");
+    public DefaultHttp2ConnectionDecoder(Http2Connection connection,
+                                         Http2ConnectionEncoder encoder,
+                                         Http2FrameReader frameReader,
+                                         Http2FrameListener listener,
+                                         Http2PromisedRequestVerifier requestVerifier) {
+        this.connection = checkNotNull(connection, "connection");
+        this.frameReader = checkNotNull(frameReader, "frameReader");
+        this.encoder = checkNotNull(encoder, "encoder");
+        this.listener = checkNotNull(listener, "listener");
+        this.requestVerifier = checkNotNull(requestVerifier, "requestVerifier");
         if (connection.local().flowController() == null) {
             connection.local().flowController(
                     new DefaultHttp2LocalFlowController(connection, encoder.frameWriter()));
         }
     }
 
+    @Override
+    public void lifecycleManager(Http2LifecycleManager lifecycleManager) {
+        this.lifecycleManager = checkNotNull(lifecycleManager, "lifecycleManager");
+    }
+
     @Override
     public Http2Connection connection() {
         return connection;
diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2ConnectionEncoder.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2ConnectionEncoder.java
index 0e0bca4d808..3f908bcb2f7 100644
--- a/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2ConnectionEncoder.java
+++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2ConnectionEncoder.java
@@ -35,63 +35,24 @@
 public class DefaultHttp2ConnectionEncoder implements Http2ConnectionEncoder {
     private final Http2FrameWriter frameWriter;
     private final Http2Connection connection;
-    private final Http2LifecycleManager lifecycleManager;
+    private Http2LifecycleManager lifecycleManager;
     // We prefer ArrayDeque to LinkedList because later will produce more GC.
     // This initial capacity is plenty for SETTINGS traffic.
     private final ArrayDeque<Http2Settings> outstandingLocalSettingsQueue = new ArrayDeque<Http2Settings>(4);
 
-    /**
-     * Builder for new instances of {@link DefaultHttp2ConnectionEncoder}.
-     */
-    public static class Builder implements Http2ConnectionEncoder.Builder {
-        protected Http2FrameWriter frameWriter;
-        protected Http2Connection connection;
-        protected Http2LifecycleManager lifecycleManager;
-
-        @Override
-        public Builder connection(
-                Http2Connection connection) {
-            this.connection = connection;
-            return this;
-        }
-
-        @Override
-        public Builder lifecycleManager(
-                Http2LifecycleManager lifecycleManager) {
-            this.lifecycleManager = lifecycleManager;
-            return this;
-        }
-
-        @Override
-        public Http2LifecycleManager lifecycleManager() {
-            return lifecycleManager;
-        }
-
-        @Override
-        public Builder frameWriter(Http2FrameWriter frameWriter) {
-            this.frameWriter = frameWriter;
-            return this;
-        }
-
-        @Override
-        public Http2ConnectionEncoder build() {
-            return new DefaultHttp2ConnectionEncoder(this);
-        }
-    }
-
-    public static Builder newBuilder() {
-        return new Builder();
-    }
-
-    protected DefaultHttp2ConnectionEncoder(Builder builder) {
-        connection = checkNotNull(builder.connection, "connection");
-        frameWriter = checkNotNull(builder.frameWriter, "frameWriter");
-        lifecycleManager = checkNotNull(builder.lifecycleManager, "lifecycleManager");
+    public DefaultHttp2ConnectionEncoder(Http2Connection connection, Http2FrameWriter frameWriter) {
+        this.connection = checkNotNull(connection, "connection");
+        this.frameWriter = checkNotNull(frameWriter, "frameWriter");
         if (connection.remote().flowController() == null) {
             connection.remote().flowController(new DefaultHttp2RemoteFlowController(connection));
         }
     }
 
+    @Override
+    public void lifecycleManager(Http2LifecycleManager lifecycleManager) {
+        this.lifecycleManager = checkNotNull(lifecycleManager, "lifecycleManager");
+    }
+
     @Override
     public Http2FrameWriter frameWriter() {
         return frameWriter;
diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2ConnectionDecoder.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2ConnectionDecoder.java
index ee6604ae7d3..b2c68e79b03 100644
--- a/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2ConnectionDecoder.java
+++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2ConnectionDecoder.java
@@ -29,49 +29,9 @@
 public interface Http2ConnectionDecoder extends Closeable {
 
     /**
-     * Builder for new instances of {@link Http2ConnectionDecoder}.
+     * Sets the lifecycle manager. Must be called as part of initialization before the decoder is used.
      */
-    interface Builder {
-        /**
-         * Sets the {@link Http2Connection} to be used when building the decoder.
-         */
-        Builder connection(Http2Connection connection);
-
-        /**
-         * Sets the {@link Http2LifecycleManager} to be used when building the decoder.
-         */
-        Builder lifecycleManager(Http2LifecycleManager lifecycleManager);
-
-        /**
-         * Gets the {@link Http2LifecycleManager} to be used when building the decoder.
-         */
-        Http2LifecycleManager lifecycleManager();
-
-        /**
-         * Sets the {@link Http2FrameReader} to be used when building the decoder.
-         */
-        Builder frameReader(Http2FrameReader frameReader);
-
-        /**
-         * Sets the {@link Http2FrameListener} to be used when building the decoder.
-         */
-        Builder listener(Http2FrameListener listener);
-
-        /**
-         * Sets the {@link Http2ConnectionEncoder} used when building the decoder.
-         */
-        Builder encoder(Http2ConnectionEncoder encoder);
-
-        /**
-         * Sets the {@link Http2PromisedRequestVerifier} used when building the decoder.
-         */
-        Builder requestVerifier(Http2PromisedRequestVerifier requestVerifier);
-
-        /**
-         * Creates a new decoder instance.
-         */
-        Http2ConnectionDecoder build();
-    }
+    void lifecycleManager(Http2LifecycleManager lifecycleManager);
 
     /**
      * Provides direct access to the underlying connection.
diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2ConnectionEncoder.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2ConnectionEncoder.java
index 6403fd1fa6b..edb1a1d1752 100644
--- a/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2ConnectionEncoder.java
+++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2ConnectionEncoder.java
@@ -26,35 +26,9 @@
 public interface Http2ConnectionEncoder extends Http2FrameWriter {
 
     /**
-     * Builder for new instances of {@link Http2ConnectionEncoder}.
+     * Sets the lifecycle manager. Must be called as part of initialization before the encoder is used.
      */
-    interface Builder {
-
-        /**
-         * Sets the {@link Http2Connection} to be used when building the encoder.
-         */
-        Builder connection(Http2Connection connection);
-
-        /**
-         * Sets the {@link Http2LifecycleManager} to be used when building the encoder.
-         */
-        Builder lifecycleManager(Http2LifecycleManager lifecycleManager);
-
-        /**
-         * Gets the {@link Http2LifecycleManager} to be used when building the encoder.
-         */
-        Http2LifecycleManager lifecycleManager();
-
-        /**
-         * Sets the {@link Http2FrameWriter} to be used when building the encoder.
-         */
-        Builder frameWriter(Http2FrameWriter frameWriter);
-
-        /**
-         * Creates a new encoder instance.
-         */
-        Http2ConnectionEncoder build();
-    }
+    void lifecycleManager(Http2LifecycleManager lifecycleManager);
 
     /**
      * Provides direct access to the underlying connection.
diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2ConnectionHandler.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2ConnectionHandler.java
index 0af27f0d410..46e6e01a303 100644
--- a/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2ConnectionHandler.java
+++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2ConnectionHandler.java
@@ -60,38 +60,19 @@ public Http2ConnectionHandler(Http2Connection connection, Http2FrameListener lis
     }
 
     public Http2ConnectionHandler(Http2Connection connection, Http2FrameReader frameReader,
-            Http2FrameWriter frameWriter, Http2FrameListener listener) {
-        this(DefaultHttp2ConnectionDecoder.newBuilder().connection(connection)
-                .frameReader(frameReader).listener(listener),
-             DefaultHttp2ConnectionEncoder.newBuilder().connection(connection)
-                .frameWriter(frameWriter));
+                                  Http2FrameWriter frameWriter, Http2FrameListener listener) {
+        encoder = new DefaultHttp2ConnectionEncoder(connection, frameWriter);
+        decoder = new DefaultHttp2ConnectionDecoder(connection, encoder, frameReader, listener);
     }
 
     /**
-     * Constructor for pre-configured encoder and decoder builders. Just sets the {@code this} as the
+     * Constructor for pre-configured encoder and decoder. Just sets the {@code this} as the
      * {@link Http2LifecycleManager} and builds them.
      */
-    public Http2ConnectionHandler(Http2ConnectionDecoder.Builder decoderBuilder,
-            Http2ConnectionEncoder.Builder encoderBuilder) {
-        checkNotNull(decoderBuilder, "decoderBuilder");
-        checkNotNull(encoderBuilder, "encoderBuilder");
-
-        if (encoderBuilder.lifecycleManager() != decoderBuilder.lifecycleManager()) {
-            throw new IllegalArgumentException("Encoder and Decoder must share a lifecycle manager");
-        } else if (encoderBuilder.lifecycleManager() == null) {
-            encoderBuilder.lifecycleManager(this);
-            decoderBuilder.lifecycleManager(this);
-        }
-
-        // Build the encoder.
-        encoder = checkNotNull(encoderBuilder.build(), "encoder");
-
-        // Build the decoder.
-        decoderBuilder.encoder(encoder);
-        decoder = checkNotNull(decoderBuilder.build(), "decoder");
-
-        // Verify that the encoder and decoder use the same connection.
-        checkNotNull(encoder.connection(), "encoder.connection");
+    public Http2ConnectionHandler(Http2ConnectionDecoder decoder,
+                                  Http2ConnectionEncoder encoder) {
+        this.decoder = checkNotNull(decoder, "decoder");
+        this.encoder = checkNotNull(encoder, "encoder");
         if (encoder.connection() != decoder.connection()) {
             throw new IllegalArgumentException("Encoder and Decoder do not share the same connection object");
         }
@@ -301,6 +282,9 @@ public void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) thro
 
     @Override
     public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
+        // Initialize the encoder and decoder.
+        encoder.lifecycleManager(this);
+        decoder.lifecycleManager(this);
         byteDecoder = new PrefaceDecoder(ctx);
     }
 
diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/HttpToHttp2ConnectionHandler.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/HttpToHttp2ConnectionHandler.java
index 1633f170b4a..ca3676617d8 100644
--- a/codec-http2/src/main/java/io/netty/handler/codec/http2/HttpToHttp2ConnectionHandler.java
+++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/HttpToHttp2ConnectionHandler.java
@@ -40,9 +40,9 @@ public HttpToHttp2ConnectionHandler(Http2Connection connection, Http2FrameReader
         super(connection, frameReader, frameWriter, listener);
     }
 
-    public HttpToHttp2ConnectionHandler(Http2ConnectionDecoder.Builder decoderBuilder,
-            Http2ConnectionEncoder.Builder encoderBuilder) {
-        super(decoderBuilder, encoderBuilder);
+    public HttpToHttp2ConnectionHandler(Http2ConnectionDecoder decoder,
+                                        Http2ConnectionEncoder encoder) {
+        super(decoder, encoder);
     }
 
     /**


Test patch:
diff --git a/codec-http2/src/test/java/io/netty/handler/codec/http2/DataCompressionHttp2Test.java b/codec-http2/src/test/java/io/netty/handler/codec/http2/DataCompressionHttp2Test.java
index 66effe1cc3a..633ad61d1e5 100644
--- a/codec-http2/src/test/java/io/netty/handler/codec/http2/DataCompressionHttp2Test.java
+++ b/codec-http2/src/test/java/io/netty/handler/codec/http2/DataCompressionHttp2Test.java
@@ -216,7 +216,7 @@ public void run() {
             });
             awaitServer();
             assertEquals(0, serverConnection.local().flowController().unconsumedBytes(stream));
-            assertEquals(new StringBuilder(text1).append(text2).toString(),
+            assertEquals(text1 + text2,
                     serverOut.toString(CharsetUtil.UTF_8.name()));
         } finally {
             data1.release();
@@ -296,16 +296,13 @@ public Integer answer(InvocationOnMock in) throws Throwable {
             @Override
             protected void initChannel(Channel ch) throws Exception {
                 ChannelPipeline p = ch.pipeline();
-                Http2FrameWriter writer = new DefaultHttp2FrameWriter();
-                Http2ConnectionHandler connectionHandler =
-                        new Http2ConnectionHandler(new DefaultHttp2ConnectionDecoder.Builder()
-                                .connection(serverConnection)
-                                .frameReader(new DefaultHttp2FrameReader())
-                                .listener(
-                                        new DelegatingDecompressorFrameListener(serverConnection,
-                                                serverListener)),
-                                new CompressorHttp2ConnectionEncoder.Builder().connection(
-                                        serverConnection).frameWriter(writer));
+                Http2ConnectionEncoder encoder = new CompressorHttp2ConnectionEncoder(
+                        new DefaultHttp2ConnectionEncoder(serverConnection, new DefaultHttp2FrameWriter()));
+                Http2ConnectionDecoder decoder =
+                        new DefaultHttp2ConnectionDecoder(serverConnection, encoder, new DefaultHttp2FrameReader(),
+                                new DelegatingDecompressorFrameListener(serverConnection,
+                                        serverListener));
+                Http2ConnectionHandler connectionHandler = new Http2ConnectionHandler(decoder, encoder);
                 p.addLast(connectionHandler);
                 serverChannelLatch.countDown();
             }
@@ -319,17 +316,14 @@ protected void initChannel(Channel ch) throws Exception {
                 ChannelPipeline p = ch.pipeline();
                 FrameCountDown clientFrameCountDown = new FrameCountDown(clientListener,
                         clientSettingsAckLatch, clientLatch);
-                Http2FrameWriter writer = new DefaultHttp2FrameWriter();
-                Http2ConnectionHandler connectionHandler =
-                        new Http2ConnectionHandler(new DefaultHttp2ConnectionDecoder.Builder()
-                                .connection(clientConnection)
-                                .frameReader(new DefaultHttp2FrameReader())
-                                .listener(
-                                        new DelegatingDecompressorFrameListener(clientConnection,
-                                                clientFrameCountDown)),
-                                new CompressorHttp2ConnectionEncoder.Builder().connection(
-                                        clientConnection).frameWriter(writer));
-                clientEncoder = connectionHandler.encoder();
+                clientEncoder = new CompressorHttp2ConnectionEncoder(
+                        new DefaultHttp2ConnectionEncoder(clientConnection, new DefaultHttp2FrameWriter()));
+                Http2ConnectionDecoder decoder =
+                        new DefaultHttp2ConnectionDecoder(clientConnection, clientEncoder,
+                                new DefaultHttp2FrameReader(),
+                                new DelegatingDecompressorFrameListener(clientConnection,
+                                        clientFrameCountDown));
+                Http2ConnectionHandler connectionHandler = new Http2ConnectionHandler(decoder, clientEncoder);
                 p.addLast(connectionHandler);
             }
         });
diff --git a/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2ConnectionDecoderTest.java b/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2ConnectionDecoderTest.java
index 53a03ed7648..5056c31bac5 100644
--- a/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2ConnectionDecoderTest.java
+++ b/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2ConnectionDecoderTest.java
@@ -139,9 +139,8 @@ public void setup() throws Exception {
         when(ctx.newPromise()).thenReturn(promise);
         when(ctx.write(any())).thenReturn(future);
 
-        decoder = DefaultHttp2ConnectionDecoder.newBuilder().connection(connection)
-                        .frameReader(reader).encoder(encoder)
-                        .listener(listener).lifecycleManager(lifecycleManager).build();
+        decoder = new DefaultHttp2ConnectionDecoder(connection, encoder, reader, listener);
+        decoder.lifecycleManager(lifecycleManager);
 
         // Simulate receiving the initial settings from the remote endpoint.
         decode().onSettingsRead(ctx, new Http2Settings());
diff --git a/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2ConnectionEncoderTest.java b/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2ConnectionEncoderTest.java
index 5a212696052..3bcd70e0b93 100644
--- a/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2ConnectionEncoderTest.java
+++ b/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2ConnectionEncoderTest.java
@@ -195,8 +195,8 @@ public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
         when(ctx.newPromise()).thenReturn(promise);
         when(ctx.write(any())).thenReturn(future);
 
-        encoder = DefaultHttp2ConnectionEncoder.newBuilder().connection(connection)
-                        .frameWriter(writer).lifecycleManager(lifecycleManager).build();
+        encoder = new DefaultHttp2ConnectionEncoder(connection, writer);
+        encoder.lifecycleManager(lifecycleManager);
     }
 
     @Test
diff --git a/codec-http2/src/test/java/io/netty/handler/codec/http2/Http2ConnectionHandlerTest.java b/codec-http2/src/test/java/io/netty/handler/codec/http2/Http2ConnectionHandlerTest.java
index f216c7b89e1..a92d95dd34c 100644
--- a/codec-http2/src/test/java/io/netty/handler/codec/http2/Http2ConnectionHandlerTest.java
+++ b/codec-http2/src/test/java/io/netty/handler/codec/http2/Http2ConnectionHandlerTest.java
@@ -89,12 +89,6 @@ public class Http2ConnectionHandlerTest {
     @Mock
     private Http2Stream stream;
 
-    @Mock
-    private Http2ConnectionDecoder.Builder decoderBuilder;
-
-    @Mock
-    private Http2ConnectionEncoder.Builder encoderBuilder;
-
     @Mock
     private Http2ConnectionDecoder decoder;
 
@@ -110,8 +104,6 @@ public void setup() throws Exception {
 
         promise = new DefaultChannelPromise(channel);
 
-        when(encoderBuilder.build()).thenReturn(encoder);
-        when(decoderBuilder.build()).thenReturn(decoder);
         when(encoder.connection()).thenReturn(connection);
         when(decoder.connection()).thenReturn(connection);
         when(encoder.frameWriter()).thenReturn(frameWriter);
@@ -132,7 +124,7 @@ public void setup() throws Exception {
     }
 
     private Http2ConnectionHandler newHandler() throws Exception {
-        Http2ConnectionHandler handler = new Http2ConnectionHandler(decoderBuilder, encoderBuilder);
+        Http2ConnectionHandler handler = new Http2ConnectionHandler(decoder, encoder);
         handler.handlerAdded(ctx);
         return handler;
     }
diff --git a/microbench/src/test/java/io/netty/microbench/http2/Http2FrameWriterBenchmark.java b/microbench/src/test/java/io/netty/microbench/http2/Http2FrameWriterBenchmark.java
index 91c513346b0..9d8faa16f50 100644
--- a/microbench/src/test/java/io/netty/microbench/http2/Http2FrameWriterBenchmark.java
+++ b/microbench/src/test/java/io/netty/microbench/http2/Http2FrameWriterBenchmark.java
@@ -52,6 +52,8 @@
 import io.netty.handler.codec.http2.DefaultHttp2FrameWriter;
 import io.netty.handler.codec.http2.DefaultHttp2Headers;
 import io.netty.handler.codec.http2.Http2Connection;
+import io.netty.handler.codec.http2.Http2ConnectionDecoder;
+import io.netty.handler.codec.http2.Http2ConnectionEncoder;
 import io.netty.handler.codec.http2.Http2ConnectionHandler;
 import io.netty.handler.codec.http2.Http2FrameAdapter;
 import io.netty.handler.codec.http2.Http2FrameWriter;
@@ -264,11 +266,11 @@ protected void initChannel(Channel ch) throws Exception {
                     connection.local().flowController(localFlowController);
                 }
                 environment.writer(new DefaultHttp2FrameWriter());
-                Http2ConnectionHandler connectionHandler = new Http2ConnectionHandler(
-                        new DefaultHttp2ConnectionDecoder.Builder().connection(connection)
-                                .frameReader(new DefaultHttp2FrameReader()).listener(new Http2FrameAdapter()),
-                        new DefaultHttp2ConnectionEncoder.Builder().connection(connection).frameWriter(
-                                environment.writer()));
+                Http2ConnectionEncoder encoder = new DefaultHttp2ConnectionEncoder(connection, environment.writer());
+                Http2ConnectionDecoder decoder =
+                        new DefaultHttp2ConnectionDecoder(connection, encoder, new DefaultHttp2FrameReader(),
+                                new Http2FrameAdapter());
+                Http2ConnectionHandler connectionHandler = new Http2ConnectionHandler(decoder, encoder);
                 p.addLast(connectionHandler);
                 environment.context(p.lastContext());
             }
@@ -283,10 +285,11 @@ protected void initChannel(Channel ch) throws Exception {
     private static Environment boostrapEmbeddedEnv(final ByteBufAllocator alloc) {
         final EmbeddedEnvironment env = new EmbeddedEnvironment(new DefaultHttp2FrameWriter());
         final Http2Connection connection = new DefaultHttp2Connection(false);
-        final Http2ConnectionHandler connectionHandler = new Http2ConnectionHandler(
-                new DefaultHttp2ConnectionDecoder.Builder().connection(connection)
-                        .frameReader(new DefaultHttp2FrameReader()).listener(new Http2FrameAdapter()),
-                new DefaultHttp2ConnectionEncoder.Builder().connection(connection).frameWriter(env.writer()));
+        Http2ConnectionEncoder encoder = new DefaultHttp2ConnectionEncoder(connection, env.writer());
+        Http2ConnectionDecoder decoder =
+                new DefaultHttp2ConnectionDecoder(connection, encoder, new DefaultHttp2FrameReader(),
+                        new Http2FrameAdapter());
+        Http2ConnectionHandler connectionHandler = new Http2ConnectionHandler(decoder, encoder);
         env.context(new EmbeddedChannelWriteReleaseHandlerContext(alloc, connectionHandler) {
             @Override
             protected void handleException(Throwable t) {