You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by ti...@apache.org on 2020/02/25 23:49:18 UTC

[maven-surefire] branch maven2surefire-jvm-communication updated: SPI using Event instead of String

This is an automated email from the ASF dual-hosted git repository.

tibordigana pushed a commit to branch maven2surefire-jvm-communication
in repository https://gitbox.apache.org/repos/asf/maven-surefire.git


The following commit(s) were added to refs/heads/maven2surefire-jvm-communication by this push:
     new 6486699  SPI using Event instead of String
6486699 is described below

commit 64866993dd1c166be212c1a5af28e42fcd9d354e
Author: tibordigana <ti...@apache.org>
AuthorDate: Wed Feb 26 00:49:06 2020 +0100

    SPI using Event instead of String
---
 .../plugin/surefire/booterclient/ForkStarter.java  |  34 +-
 .../surefire/booterclient/output/ForkClient.java   | 122 +-----
 .../booterclient/output/ForkedChannelDecoder.java  | 352 -----------------
 .../output/ForkedProcessEventObserver.java         | 244 ++++++++++++
 .../output/ForkedProcessExitErrorListener.java     |   4 +-
 .../ForkedProcessStackTraceEventListener.java      |   6 +-
 .../output/NativeStdErrStreamConsumer.java         |   2 +-
 .../output/ThreadedStreamConsumer.java             |  75 +++-
 .../surefire/extensions/EventConsumerThread.java   | 430 +++++++++++++++++++++
 .../surefire/extensions/LegacyForkChannel.java     |  15 +-
 .../surefire/extensions/LegacyForkNodeFactory.java |   5 +-
 .../surefire/extensions/SurefireForkChannel.java   |  14 +-
 .../extensions/SurefireForkNodeFactory.java        |   5 +-
 .../surefire/booterclient/ForkStarterTest.java     |   5 +-
 .../booterclient/ForkingRunListenerTest.java       |  29 +-
 .../booterclient/output/ForkClientTest.java        | 150 ++-----
 ...st.java => ForkedProcessEventObserverTest.java} | 225 ++++-------
 .../org/apache/maven/surefire/JUnit4SuiteTest.java |   4 +-
 ...ocessEvent.java => ForkedProcessEventType.java} |  20 +-
 .../surefire/eventapi/AbstractConsoleEvent.java    |  85 ++++
 .../eventapi/AbstractStandardStreamEvent.java      |  93 +++++
 .../eventapi/AbstractTestControlEvent.java         |  94 +++++
 .../maven/surefire/eventapi/ConsoleDebugEvent.java |  15 +-
 .../maven/surefire/eventapi/ConsoleErrorEvent.java |  87 +++++
 .../maven/surefire/eventapi/ConsoleInfoEvent.java  |  16 +-
 .../surefire/eventapi/ConsoleWarningEvent.java     |  15 +-
 .../maven/surefire/eventapi/ControlByeEvent.java   |  77 ++++
 .../surefire/eventapi/ControlNextTestEvent.java    |  77 ++++
 .../eventapi/ControlStopOnNextTestEvent.java       |  77 ++++
 .../org/apache/maven/surefire/eventapi/Event.java  |  36 +-
 .../maven/surefire/eventapi/JvmExitErrorEvent.java |  87 +++++
 .../surefire/eventapi/StandardStreamErrEvent.java  |  17 +-
 .../StandardStreamErrWithNewLineEvent.java         |  20 +-
 .../surefire/eventapi/StandardStreamOutEvent.java  |  17 +-
 .../StandardStreamOutWithNewLineEvent.java         |  20 +-
 .../surefire/eventapi/SystemPropertyEvent.java     | 101 +++++
 .../eventapi/TestAssumptionFailureEvent.java       |  21 +-
 .../maven/surefire/eventapi/TestErrorEvent.java    |  21 +-
 .../maven/surefire/eventapi/TestFailedEvent.java   |  21 +-
 .../maven/surefire/eventapi/TestSkippedEvent.java  |  21 +-
 .../maven/surefire/eventapi/TestStartingEvent.java |  21 +-
 .../surefire/eventapi/TestSucceededEvent.java      |  21 +-
 .../surefire/eventapi/TestsetCompletedEvent.java   |  21 +-
 .../surefire/eventapi/TestsetStartingEvent.java    |  21 +-
 .../maven/surefire/report/SimpleReportEntry.java   |   2 +
 .../spi/LegacyMasterProcessChannelDecoder.java     |  26 +-
 .../spi/LegacyMasterProcessChannelEncoder.java     |  98 ++---
 ...LegacyMasterProcessChannelProcessorFactory.java |   6 +-
 ...refireMasterProcessChannelProcessorFactory.java |   5 +-
 .../maven/surefire/booter/CommandReaderTest.java   |   8 +-
 .../spi/LegacyMasterProcessChannelDecoderTest.java |  31 +-
 .../spi/LegacyMasterProcessChannelEncoderTest.java |  66 ++--
 .../maven/surefire/extensions/EventHandler.java    |   8 +-
 .../maven/surefire/extensions/ForkChannel.java     |   3 +-
 .../maven/surefire/extensions/ForkNodeFactory.java |   6 +-
 .../extensions/util/LineConsumerThread.java        |   6 +-
 .../extensions/util/CommandlineExecutorTest.java   |   3 +-
 57 files changed, 2029 insertions(+), 1082 deletions(-)

diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ForkStarter.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ForkStarter.java
index 9e57d74..a41384a 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ForkStarter.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ForkStarter.java
@@ -33,11 +33,6 @@ import org.apache.maven.plugin.surefire.booterclient.output.NativeStdErrStreamCo
 import org.apache.maven.plugin.surefire.booterclient.output.ThreadedStreamConsumer;
 import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
 import org.apache.maven.plugin.surefire.report.DefaultReporterFactory;
-import org.apache.maven.surefire.extensions.CloseableDaemonThread;
-import org.apache.maven.surefire.extensions.EventHandler;
-import org.apache.maven.surefire.extensions.util.CommandlineExecutor;
-import org.apache.maven.surefire.extensions.util.CommandlineStreams;
-import org.apache.maven.surefire.extensions.util.CountdownCloseable;
 import org.apache.maven.surefire.booter.AbstractPathConfiguration;
 import org.apache.maven.surefire.booter.PropertiesWrapper;
 import org.apache.maven.surefire.booter.ProviderConfiguration;
@@ -46,8 +41,13 @@ import org.apache.maven.surefire.booter.Shutdown;
 import org.apache.maven.surefire.booter.StartupConfiguration;
 import org.apache.maven.surefire.booter.SurefireBooterForkException;
 import org.apache.maven.surefire.booter.SurefireExecutionException;
+import org.apache.maven.surefire.extensions.CloseableDaemonThread;
+import org.apache.maven.surefire.extensions.EventHandler;
 import org.apache.maven.surefire.extensions.ForkChannel;
 import org.apache.maven.surefire.extensions.ForkNodeFactory;
+import org.apache.maven.surefire.extensions.util.CommandlineExecutor;
+import org.apache.maven.surefire.extensions.util.CommandlineStreams;
+import org.apache.maven.surefire.extensions.util.CountdownCloseable;
 import org.apache.maven.surefire.extensions.util.LineConsumerThread;
 import org.apache.maven.surefire.providerapi.SurefireProvider;
 import org.apache.maven.surefire.report.StackTraceWriter;
@@ -74,7 +74,6 @@ import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.ScheduledFuture;
 import java.util.concurrent.ThreadFactory;
 import java.util.concurrent.ThreadPoolExecutor;
-import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
 
 import static java.lang.StrictMath.min;
@@ -89,12 +88,11 @@ import static org.apache.maven.plugin.surefire.SurefireHelper.DUMP_FILE_PREFIX;
 import static org.apache.maven.plugin.surefire.SurefireHelper.replaceForkThreadsInPath;
 import static org.apache.maven.plugin.surefire.booterclient.ForkNumberBucket.drawNumber;
 import static org.apache.maven.plugin.surefire.booterclient.ForkNumberBucket.returnNumber;
-import static org.apache.maven.plugin.surefire.booterclient.lazytestprovider.TestLessInputStream
-                      .TestLessInputStreamBuilder;
-import static org.apache.maven.surefire.shared.utils.cli.ShutdownHookUtils.addShutDownHook;
-import static org.apache.maven.surefire.shared.utils.cli.ShutdownHookUtils.removeShutdownHook;
+import static org.apache.maven.plugin.surefire.booterclient.lazytestprovider.TestLessInputStream.TestLessInputStreamBuilder;
 import static org.apache.maven.surefire.booter.SystemPropertyManager.writePropertiesFile;
 import static org.apache.maven.surefire.cli.CommandLineOption.SHOW_ERRORS;
+import static org.apache.maven.surefire.shared.utils.cli.ShutdownHookUtils.addShutDownHook;
+import static org.apache.maven.surefire.shared.utils.cli.ShutdownHookUtils.removeShutdownHook;
 import static org.apache.maven.surefire.suite.RunResult.SUCCESS;
 import static org.apache.maven.surefire.suite.RunResult.failure;
 import static org.apache.maven.surefire.suite.RunResult.timeout;
@@ -294,8 +292,7 @@ public class ForkStarter
             DefaultReporterFactory forkedReporterFactory =
                     new DefaultReporterFactory( startupReportConfiguration, log, forkNumber );
             defaultReporterFactories.add( forkedReporterFactory );
-            ForkClient forkClient =
-                    new ForkClient( forkedReporterFactory, stream, log, new AtomicBoolean(), forkNumber );
+            ForkClient forkClient = new ForkClient( forkedReporterFactory, stream, forkNumber );
             return fork( null, props, forkClient, effectiveSystemProperties, forkNumber, stream,
                     forkConfiguration.getForkNodeFactory(), false );
         }
@@ -358,7 +355,6 @@ public class ForkStarter
             int failFastCount = providerConfiguration.getSkipAfterFailureCount();
             final AtomicInteger notifyStreamsToSkipTestsJustNow = new AtomicInteger( failFastCount );
             final Collection<Future<RunResult>> results = new ArrayList<>( forkCount );
-            final AtomicBoolean printedErrorStream = new AtomicBoolean();
             for ( final TestProvidingInputStream testProvidingInputStream : testStreams )
             {
                 Callable<RunResult> pf = new Callable<RunResult>()
@@ -371,8 +367,7 @@ public class ForkStarter
                         DefaultReporterFactory reporter =
                                 new DefaultReporterFactory( startupReportConfiguration, log, forkNumber );
                         defaultReporterFactories.add( reporter );
-                        ForkClient forkClient = new ForkClient( reporter, testProvidingInputStream, log,
-                                printedErrorStream, forkNumber )
+                        ForkClient forkClient = new ForkClient( reporter, testProvidingInputStream, forkNumber )
                         {
                             @Override
                             protected void stopOnNextTest()
@@ -432,7 +427,6 @@ public class ForkStarter
             addShutDownHook( shutdown );
             int failFastCount = providerConfiguration.getSkipAfterFailureCount();
             final AtomicInteger notifyStreamsToSkipTestsJustNow = new AtomicInteger( failFastCount );
-            final AtomicBoolean printedErrorStream = new AtomicBoolean();
             for ( final Object testSet : getSuitesIterator() )
             {
                 Callable<RunResult> pf = new Callable<RunResult>()
@@ -446,8 +440,7 @@ public class ForkStarter
                             new DefaultReporterFactory( startupReportConfiguration, log, forkNumber );
                         defaultReporterFactories.add( forkedReporterFactory );
                         TestLessInputStream stream = builder.build();
-                        ForkClient forkClient = new ForkClient( forkedReporterFactory, stream,
-                                log, printedErrorStream, forkNumber )
+                        ForkClient forkClient = new ForkClient( forkedReporterFactory, stream, forkNumber )
                         {
                             @Override
                             protected void stopOnNextTest()
@@ -570,7 +563,7 @@ public class ForkStarter
         final ForkChannel forkChannel;
         try
         {
-            forkChannel = forkNodeFactory.createForkChannel( forkNumber );
+            forkChannel = forkNodeFactory.createForkChannel( forkNumber, log );
             closer.addCloseable( forkChannel );
             tempDir = forkConfiguration.getTempDirectory().getCanonicalPath();
             BooterSerializer booterSerializer = new BooterSerializer( forkConfiguration );
@@ -644,7 +637,7 @@ public class ForkStarter
             out = forkChannel.bindEventHandler( eventConsumer, countdownCloseable, streams.getStdOutChannel() );
             out.start();
 
-            EventHandler stdErrConsumer = new NativeStdErrStreamConsumer( reporter );
+            EventHandler<String> stdErrConsumer = new NativeStdErrStreamConsumer( reporter );
             err = new LineConsumerThread( "fork-" + forkNumber + "-err-thread-", streams.getStdErrChannel(),
                 stdErrConsumer, countdownCloseable );
             err.start();
@@ -749,7 +742,6 @@ public class ForkStarter
 
             if ( booterForkException != null )
             {
-                // noinspection ThrowFromFinallyBlock
                 throw booterForkException;
             }
         }
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkClient.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkClient.java
index 703e92b..199f656 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkClient.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkClient.java
@@ -22,6 +22,7 @@ package org.apache.maven.plugin.surefire.booterclient.output;
 import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.NotifiableTestStream;
 import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
 import org.apache.maven.plugin.surefire.report.DefaultReporterFactory;
+import org.apache.maven.surefire.eventapi.Event;
 import org.apache.maven.surefire.extensions.EventHandler;
 import org.apache.maven.surefire.providerapi.MasterProcessChannelEncoder;
 import org.apache.maven.surefire.report.ConsoleOutputReceiver;
@@ -32,25 +33,19 @@ import org.apache.maven.surefire.report.StackTraceWriter;
 import org.apache.maven.surefire.report.TestSetReportEntry;
 
 import javax.annotation.Nonnull;
-import java.io.BufferedReader;
 import java.io.File;
-import java.io.IOException;
-import java.io.StringReader;
 import java.util.Map;
 import java.util.Queue;
 import java.util.Set;
 import java.util.TreeSet;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentLinkedQueue;
-import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicLong;
 
 import static java.lang.System.currentTimeMillis;
 import static java.util.Collections.unmodifiableMap;
 import static org.apache.maven.surefire.booter.Shutdown.KILL;
 import static org.apache.maven.surefire.report.CategorizedReportEntry.reportEntry;
-import static org.apache.maven.surefire.shared.utils.StringUtils.isBlank;
-import static org.apache.maven.surefire.shared.utils.StringUtils.isNotBlank;
 
 // todo move to the same package with ForkStarter
 
@@ -60,9 +55,8 @@ import static org.apache.maven.surefire.shared.utils.StringUtils.isNotBlank;
  * @author Kristian Rosenvold
  */
 public class ForkClient
-    implements EventHandler
+    implements EventHandler<Event>
 {
-    private static final String PRINTABLE_JVM_NATIVE_STREAM = "Listening for transport dt_socket at address:";
     private static final long START_TIME_ZERO = 0L;
     private static final long START_TIME_NEGATIVE_TIMEOUT = -1L;
 
@@ -80,22 +74,10 @@ public class ForkClient
      */
     private final AtomicLong testSetStartedAt = new AtomicLong( START_TIME_ZERO );
 
-    private final ForkedChannelDecoder decoder = new ForkedChannelDecoder();
-
-    private final ConsoleLogger log;
-
-    /**
-     * prevents from printing same warning
-     */
-    private final AtomicBoolean printedErrorStream;
+    private final ForkedProcessEventObserver decoder = new ForkedProcessEventObserver();
 
     private final int forkNumber;
 
-    /**
-     * Used by single Thread started by {@link ThreadedStreamConsumer} and therefore does not need to be volatile.
-     */
-    private final ForkedChannelDecoderErrorHandler errorHandler;
-
     private RunListener testSetReporter;
 
     /**
@@ -106,12 +88,10 @@ public class ForkClient
     private volatile StackTraceWriter errorInFork;
 
     public ForkClient( DefaultReporterFactory defaultReporterFactory, NotifiableTestStream notifiableTestStream,
-                       ConsoleLogger log, AtomicBoolean printedErrorStream, int forkNumber )
+                       int forkNumber )
     {
         this.defaultReporterFactory = defaultReporterFactory;
         this.notifiableTestStream = notifiableTestStream;
-        this.log = log;
-        this.printedErrorStream = printedErrorStream;
         this.forkNumber = forkNumber;
         decoder.setTestSetStartingListener( new TestSetStartingListener() );
         decoder.setTestSetCompletedListener( new TestSetCompletedListener() );
@@ -131,16 +111,6 @@ public class ForkClient
         decoder.setStopOnNextTestListener( new StopOnNextTestListener() );
         decoder.setConsoleDebugListener( new DebugListener() );
         decoder.setConsoleWarningListener( new WarningListener() );
-        errorHandler = new ErrorHandler();
-    }
-
-    private final class ErrorHandler implements ForkedChannelDecoderErrorHandler
-    {
-        @Override
-        public void handledError( String line, Throwable e )
-        {
-            logStreamWarning( line, e );
-        }
     }
 
     private final class TestSetStartingListener
@@ -278,18 +248,19 @@ public class ForkClient
     private class ErrorListener implements ForkedProcessStackTraceEventListener
     {
         @Override
-        public void handle( String msg, String smartStackTrace, String stackTrace )
+        public void handle( @Nonnull StackTraceWriter stackTrace )
         {
+            String msg = stackTrace.getThrowable().getMessage();
             if ( errorInFork == null )
             {
-                errorInFork = deserializeStackTraceWriter( msg, smartStackTrace, stackTrace );
+                errorInFork = stackTrace;
                 if ( msg != null )
                 {
                     getOrCreateConsoleLogger()
                             .error( msg );
                 }
             }
-            dumpToLoFile( msg, null );
+            dumpToLoFile( msg );
         }
     }
 
@@ -374,12 +345,9 @@ public class ForkClient
     }
 
     @Override
-    public final void handleEvent( @Nonnull String event )
+    public final void handleEvent( @Nonnull Event event )
     {
-        if ( isNotBlank( event ) )
-        {
-            processLine( event );
-        }
+        decoder.handleEvent( event );
     }
 
     private void setCurrentStartTime()
@@ -406,53 +374,11 @@ public class ForkClient
         return testSetReporter;
     }
 
-    private void processLine( String event )
-    {
-        decoder.handleEvent( event, errorHandler );
-    }
-
-    private File dumpToLoFile( String msg, Throwable e )
+    private void dumpToLoFile( String msg )
     {
         File reportsDir = defaultReporterFactory.getReportsDirectory();
         InPluginProcessDumpSingleton util = InPluginProcessDumpSingleton.getSingleton();
-        return e == null
-                ? util.dumpStreamText( msg, reportsDir, forkNumber )
-                : util.dumpStreamException( e, msg, reportsDir, forkNumber );
-    }
-
-    private void logStreamWarning( String event, Throwable e )
-    {
-        if ( event == null || !event.contains( PRINTABLE_JVM_NATIVE_STREAM ) )
-        {
-            String msg = "Corrupted STDOUT by directly writing to native stream in forked JVM " + forkNumber + ".";
-            File dump = dumpToLoFile( msg + " Stream '" + event + "'.", e );
-
-            if ( printedErrorStream.compareAndSet( false, true ) )
-            {
-                log.warning( msg + " See FAQ web page and the dump file " + dump.getAbsolutePath() );
-            }
-
-            if ( log.isDebugEnabled() && event != null )
-            {
-                log.debug( event );
-            }
-        }
-        else
-        {
-            if ( log.isDebugEnabled() )
-            {
-                log.debug( event );
-            }
-            else if ( log.isInfoEnabled() )
-            {
-                log.info( event );
-            }
-            else
-            {
-                // In case of debugging forked JVM, see PRINTABLE_JVM_NATIVE_STREAM.
-                System.out.println( event );
-            }
-        }
+        util.dumpStreamText( msg, reportsDir, forkNumber );
     }
 
     private void writeTestOutput( String output, boolean newLine, boolean isStdout )
@@ -461,23 +387,6 @@ public class ForkClient
                 .writeTestOutput( output, newLine, isStdout );
     }
 
-    public final void consumeMultiLineContent( String s )
-            throws IOException
-    {
-        if ( isBlank( s ) )
-        {
-            logStreamWarning( s, null );
-        }
-        else
-        {
-            BufferedReader stringReader = new BufferedReader( new StringReader( s ) );
-            for ( String s1 = stringReader.readLine(); s1 != null; s1 = stringReader.readLine() )
-            {
-                handleEvent( s1 );
-            }
-        }
-    }
-
     public final Map<String, String> getTestVmSystemProperties()
     {
         return unmodifiableMap( testVmSystemProperties );
@@ -533,11 +442,4 @@ public class ForkClient
     {
         return !testsInProgress.isEmpty();
     }
-
-    private StackTraceWriter deserializeStackTraceWriter( String stackTraceMessage,
-                                                          String smartStackTrace, String stackTrace )
-    {
-        boolean hasTrace = stackTrace != null;
-        return hasTrace ? new DeserializedStacktraceWriter( stackTraceMessage, smartStackTrace, stackTrace ) : null;
-    }
 }
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedChannelDecoder.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedChannelDecoder.java
deleted file mode 100644
index ade1494..0000000
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedChannelDecoder.java
+++ /dev/null
@@ -1,352 +0,0 @@
-package org.apache.maven.plugin.surefire.booterclient.output;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF 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.
- */
-
-import org.apache.commons.codec.binary.Base64;
-import org.apache.maven.surefire.booter.ForkedProcessEvent;
-import org.apache.maven.surefire.report.ReportEntry;
-import org.apache.maven.surefire.report.RunMode;
-import org.apache.maven.surefire.report.StackTraceWriter;
-
-import java.nio.charset.Charset;
-import java.util.Collections;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-
-import static java.nio.charset.StandardCharsets.US_ASCII;
-import static org.apache.maven.surefire.booter.ForkedProcessEvent.MAGIC_NUMBER_DELIMITED;
-import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_STDERR;
-import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_STDERR_NEW_LINE;
-import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_STDOUT;
-import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_STDOUT_NEW_LINE;
-import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_BYE;
-import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_CONSOLE_DEBUG;
-import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_CONSOLE_INFO;
-import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_CONSOLE_WARNING;
-import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_NEXT_TEST;
-import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_STOP_ON_NEXT_TEST;
-import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_TEST_ASSUMPTIONFAILURE;
-import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_TEST_ERROR;
-import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_TEST_FAILED;
-import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_TEST_SKIPPED;
-import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_TEST_STARTING;
-import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_TEST_SUCCEEDED;
-import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_TESTSET_COMPLETED;
-import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_TESTSET_STARTING;
-import static org.apache.maven.surefire.booter.ForkedProcessEvent.EVENTS;
-import static org.apache.maven.surefire.report.CategorizedReportEntry.reportEntry;
-import static org.apache.maven.surefire.report.RunMode.MODES;
-import static org.apache.maven.surefire.shared.utils.StringUtils.isBlank;
-import static org.apache.maven.surefire.shared.utils.StringUtils.isNotBlank;
-import static java.util.Objects.requireNonNull;
-
-/**
- * magic number : run mode : opcode [: opcode specific data]*
- *
- * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
- * @since 3.0.0-M4
- */
-public final class ForkedChannelDecoder
-{
-    private static final Base64 BASE64 = new Base64();
-
-    private volatile ForkedProcessPropertyEventListener propertyEventListener;
-    private volatile ForkedProcessStackTraceEventListener consoleErrorEventListener;
-    private volatile ForkedProcessExitErrorListener exitErrorEventListener;
-
-    private final ConcurrentMap<ForkedProcessEvent, ForkedProcessReportEventListener<?>> reportEventListeners =
-            new ConcurrentHashMap<>();
-
-    private final ConcurrentMap<ForkedProcessEvent, ForkedProcessStandardOutErrEventListener> stdOutErrEventListeners =
-            new ConcurrentHashMap<>();
-
-    private final ConcurrentMap<ForkedProcessEvent, ForkedProcessStringEventListener> consoleEventListeners =
-            new ConcurrentHashMap<>();
-
-    private final ConcurrentMap<ForkedProcessEvent, ForkedProcessEventListener> controlEventListeners =
-            new ConcurrentHashMap<>();
-
-    public void setSystemPropertiesListener( ForkedProcessPropertyEventListener listener )
-    {
-        propertyEventListener = requireNonNull( listener );
-    }
-
-    public <T extends ReportEntry> void setTestSetStartingListener( ForkedProcessReportEventListener<T> listener )
-    {
-        reportEventListeners.put( BOOTERCODE_TESTSET_STARTING, requireNonNull( listener ) );
-    }
-
-    public void setTestSetCompletedListener( ForkedProcessReportEventListener<?> listener )
-    {
-        reportEventListeners.put( BOOTERCODE_TESTSET_COMPLETED, requireNonNull( listener ) );
-    }
-
-    public void setTestStartingListener( ForkedProcessReportEventListener<?> listener )
-    {
-        reportEventListeners.put( BOOTERCODE_TEST_STARTING, requireNonNull( listener ) );
-    }
-
-    public void setTestSucceededListener( ForkedProcessReportEventListener<?> listener )
-    {
-        reportEventListeners.put( BOOTERCODE_TEST_SUCCEEDED, requireNonNull( listener ) );
-    }
-
-    public void setTestFailedListener( ForkedProcessReportEventListener<?> listener )
-    {
-        reportEventListeners.put( BOOTERCODE_TEST_FAILED, requireNonNull( listener ) );
-    }
-
-    public void setTestSkippedListener( ForkedProcessReportEventListener<?> listener )
-    {
-        reportEventListeners.put( BOOTERCODE_TEST_SKIPPED, requireNonNull( listener ) );
-    }
-
-    public void setTestErrorListener( ForkedProcessReportEventListener<?> listener )
-    {
-        reportEventListeners.put( BOOTERCODE_TEST_ERROR, requireNonNull( listener ) );
-    }
-
-    public void setTestAssumptionFailureListener( ForkedProcessReportEventListener<?> listener )
-    {
-        reportEventListeners.put( BOOTERCODE_TEST_ASSUMPTIONFAILURE, requireNonNull( listener ) );
-    }
-
-    public void setStdOutListener( ForkedProcessStandardOutErrEventListener listener )
-    {
-        stdOutErrEventListeners.put( BOOTERCODE_STDOUT, requireNonNull( listener ) );
-        stdOutErrEventListeners.put( BOOTERCODE_STDOUT_NEW_LINE, requireNonNull( listener ) );
-    }
-
-    public void setStdErrListener( ForkedProcessStandardOutErrEventListener listener )
-    {
-        stdOutErrEventListeners.put( BOOTERCODE_STDERR, requireNonNull( listener ) );
-        stdOutErrEventListeners.put( BOOTERCODE_STDERR_NEW_LINE, requireNonNull( listener ) );
-    }
-
-    public void setConsoleInfoListener( ForkedProcessStringEventListener listener )
-    {
-        consoleEventListeners.put( BOOTERCODE_CONSOLE_INFO, requireNonNull( listener ) );
-    }
-
-    public void setConsoleErrorListener( ForkedProcessStackTraceEventListener listener )
-    {
-        consoleErrorEventListener = requireNonNull( listener );
-    }
-
-    public void setConsoleDebugListener( ForkedProcessStringEventListener listener )
-    {
-        consoleEventListeners.put( BOOTERCODE_CONSOLE_DEBUG, requireNonNull( listener ) );
-    }
-
-    public void setConsoleWarningListener( ForkedProcessStringEventListener listener )
-    {
-        consoleEventListeners.put( BOOTERCODE_CONSOLE_WARNING, requireNonNull( listener ) );
-    }
-
-    public void setByeListener( ForkedProcessEventListener listener )
-    {
-        controlEventListeners.put( BOOTERCODE_BYE, requireNonNull( listener ) );
-    }
-
-    public void setStopOnNextTestListener( ForkedProcessEventListener listener )
-    {
-        controlEventListeners.put( BOOTERCODE_STOP_ON_NEXT_TEST, requireNonNull( listener ) );
-    }
-
-    public void setAcquireNextTestListener( ForkedProcessEventListener listener )
-    {
-        controlEventListeners.put( BOOTERCODE_NEXT_TEST, requireNonNull( listener ) );
-    }
-
-    public void setExitErrorEventListener( ForkedProcessExitErrorListener listener )
-    {
-        exitErrorEventListener = requireNonNull( listener );
-    }
-
-    public void handleEvent( String line, ForkedChannelDecoderErrorHandler errorHandler )
-    {
-        if ( line == null || !line.startsWith( MAGIC_NUMBER_DELIMITED ) )
-        {
-            errorHandler.handledError( line, null );
-            return;
-        }
-
-        String[] tokens = line.substring( MAGIC_NUMBER_DELIMITED.length() ).split( ":" );
-        int index = -1;
-        String opcode = tokens.length > ++index ? tokens[index] : null;
-        ForkedProcessEvent event = opcode == null ? null : EVENTS.get( opcode );
-        if ( event == null )
-        {
-            errorHandler.handledError( line, null );
-            return;
-        }
-
-        try
-        {
-            if ( event.isControlCategory() )
-            {
-                ForkedProcessEventListener listener = controlEventListeners.get( event );
-                if ( listener != null )
-                {
-                    listener.handle();
-                }
-            }
-            else if ( event.isConsoleCategory() )
-            {
-                ForkedProcessStringEventListener listener = consoleEventListeners.get( event );
-                Charset encoding = tokens.length > ++index ? Charset.forName( tokens[index] ) : null;
-                if ( listener != null && encoding != null )
-                {
-                    String msg = tokens.length > ++index ? decode( tokens[index], encoding ) : "";
-                    listener.handle( msg );
-                }
-            }
-            else if ( event.isConsoleErrorCategory() )
-            {
-                Charset encoding = tokens.length > ++index ? Charset.forName( tokens[index] ) : null;
-                if ( consoleErrorEventListener != null && encoding != null )
-                {
-                    String msg = tokens.length > ++index ? decode( tokens[index], encoding ) : null;
-                    String smartStackTrace =
-                            tokens.length > ++index ? decode( tokens[index], encoding ) : null;
-                    String stackTrace = tokens.length > ++index ? decode( tokens[index], encoding ) : null;
-                    consoleErrorEventListener.handle( msg, smartStackTrace, stackTrace );
-                }
-            }
-            else if ( event.isStandardStreamCategory() )
-            {
-                ForkedProcessStandardOutErrEventListener listener = stdOutErrEventListeners.get( event );
-                RunMode mode = tokens.length > ++index ? MODES.get( tokens[index] ) : null;
-                Charset encoding = tokens.length > ++index ? Charset.forName( tokens[index] ) : null;
-                if ( listener != null && encoding != null && mode != null )
-                {
-                    boolean newLine = event == BOOTERCODE_STDOUT_NEW_LINE || event == BOOTERCODE_STDERR_NEW_LINE;
-                    String output = tokens.length > ++index ? decode( tokens[index], encoding ) : "";
-                    listener.handle( mode, output, newLine );
-                }
-            }
-            else if ( event.isSysPropCategory() )
-            {
-                RunMode mode = tokens.length > ++index ? MODES.get( tokens[index] ) : null;
-                Charset encoding = tokens.length > ++index ? Charset.forName( tokens[index] ) : null;
-                String key = tokens.length > ++index ? decode( tokens[index], encoding ) : "";
-                if ( propertyEventListener != null && isNotBlank( key ) )
-                {
-                    String value = tokens.length > ++index ? decode( tokens[index], encoding ) : "";
-                    propertyEventListener.handle( mode, key, value );
-                }
-            }
-            else if ( event.isTestCategory() )
-            {
-                ForkedProcessReportEventListener listener = reportEventListeners.get( event );
-                RunMode mode = tokens.length > ++index ? MODES.get( tokens[index] ) : null;
-                Charset encoding = tokens.length > ++index ? Charset.forName( tokens[index] ) : null;
-                if ( listener != null && encoding != null && mode != null )
-                {
-                    String sourceName = tokens.length > ++index ? tokens[index] : null;
-                    String sourceText = tokens.length > ++index ? tokens[index] : null;
-                    String name = tokens.length > ++index ? tokens[index] : null;
-                    String nameText = tokens.length > ++index ? tokens[index] : null;
-                    String group = tokens.length > ++index ? tokens[index] : null;
-                    String message = tokens.length > ++index ? tokens[index] : null;
-                    String elapsed = tokens.length > ++index ? tokens[index] : null;
-                    String traceMessage = tokens.length > ++index ? tokens[index] : null;
-                    String smartTrimmedStackTrace = tokens.length > ++index ? tokens[index] : null;
-                    String stackTrace = tokens.length > ++index ? tokens[index] : null;
-                    ReportEntry reportEntry = toReportEntry( encoding, sourceName, sourceText, name, nameText,
-                            group, message, elapsed, traceMessage, smartTrimmedStackTrace, stackTrace );
-                    listener.handle( mode, reportEntry );
-                }
-            }
-            else if ( event.isJvmExitError() )
-            {
-                if ( exitErrorEventListener != null )
-                {
-                    Charset encoding = tokens.length > ++index ? Charset.forName( tokens[index] ) : null;
-                    String message = tokens.length > ++index ? decode( tokens[index], encoding ) : "";
-                    String smartTrimmedStackTrace =
-                            tokens.length > ++index ? decode( tokens[index], encoding ) : "";
-                    String stackTrace = tokens.length > ++index ? decode( tokens[index], encoding ) : "";
-                    exitErrorEventListener.handle( message, smartTrimmedStackTrace, stackTrace );
-                }
-            }
-        }
-        catch ( IllegalArgumentException e )
-        {
-            errorHandler.handledError( line, e );
-        }
-    }
-
-    static ReportEntry toReportEntry( Charset encoding,
-                   // ReportEntry:
-                   String encSource, String encSourceText, String encName, String encNameText,
-                                      String encGroup, String encMessage, String encTimeElapsed,
-                   // StackTraceWriter:
-                   String encTraceMessage, String encSmartTrimmedStackTrace, String encStackTrace )
-            throws NumberFormatException
-    {
-        if ( encoding == null )
-        {
-            // corrupted or incomplete stream
-            return null;
-        }
-
-        String source = decode( encSource, encoding );
-        String sourceText = decode( encSourceText, encoding );
-        String name = decode( encName, encoding );
-        String nameText = decode( encNameText, encoding );
-        String group = decode( encGroup, encoding );
-        StackTraceWriter stackTraceWriter =
-                decodeTrace( encoding, encTraceMessage, encSmartTrimmedStackTrace, encStackTrace );
-        Integer elapsed = decodeToInteger( encTimeElapsed );
-        String message = decode( encMessage, encoding );
-        return reportEntry( source, sourceText, name, nameText,
-                group, stackTraceWriter, elapsed, message, Collections.<String, String>emptyMap() );
-    }
-
-    static String decode( String line, Charset encoding )
-    {
-        // ForkedChannelEncoder is encoding the stream with US_ASCII
-        return line == null || "-".equals( line )
-                ? null
-                : new String( BASE64.decode( line.getBytes( US_ASCII ) ), encoding );
-    }
-
-    static Integer decodeToInteger( String line )
-    {
-        return line == null || "-".equals( line ) ? null : Integer.decode( line );
-    }
-
-    private static StackTraceWriter decodeTrace( Charset encoding, String encTraceMessage,
-                                                 String encSmartTrimmedStackTrace, String encStackTrace )
-    {
-        if ( isBlank( encStackTrace ) || "-".equals( encStackTrace ) )
-        {
-            return null;
-        }
-        else
-        {
-            String traceMessage = decode( encTraceMessage, encoding );
-            String stackTrace = decode( encStackTrace, encoding );
-            String smartTrimmedStackTrace = decode( encSmartTrimmedStackTrace, encoding );
-            return new DeserializedStacktraceWriter( traceMessage, smartTrimmedStackTrace, stackTrace );
-        }
-    }
-}
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedProcessEventObserver.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedProcessEventObserver.java
new file mode 100644
index 0000000..843aaee
--- /dev/null
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedProcessEventObserver.java
@@ -0,0 +1,244 @@
+package org.apache.maven.plugin.surefire.booterclient.output;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF 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.
+ */
+
+import org.apache.maven.surefire.booter.ForkedProcessEventType;
+import org.apache.maven.surefire.eventapi.AbstractConsoleEvent;
+import org.apache.maven.surefire.eventapi.AbstractStandardStreamEvent;
+import org.apache.maven.surefire.eventapi.AbstractTestControlEvent;
+import org.apache.maven.surefire.eventapi.ConsoleErrorEvent;
+import org.apache.maven.surefire.eventapi.Event;
+import org.apache.maven.surefire.eventapi.JvmExitErrorEvent;
+import org.apache.maven.surefire.eventapi.SystemPropertyEvent;
+import org.apache.maven.surefire.report.ReportEntry;
+import org.apache.maven.surefire.report.RunMode;
+
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import static java.util.Objects.requireNonNull;
+import static org.apache.maven.surefire.booter.ForkedProcessEventType.BOOTERCODE_BYE;
+import static org.apache.maven.surefire.booter.ForkedProcessEventType.BOOTERCODE_CONSOLE_DEBUG;
+import static org.apache.maven.surefire.booter.ForkedProcessEventType.BOOTERCODE_CONSOLE_INFO;
+import static org.apache.maven.surefire.booter.ForkedProcessEventType.BOOTERCODE_CONSOLE_WARNING;
+import static org.apache.maven.surefire.booter.ForkedProcessEventType.BOOTERCODE_NEXT_TEST;
+import static org.apache.maven.surefire.booter.ForkedProcessEventType.BOOTERCODE_STDERR;
+import static org.apache.maven.surefire.booter.ForkedProcessEventType.BOOTERCODE_STDERR_NEW_LINE;
+import static org.apache.maven.surefire.booter.ForkedProcessEventType.BOOTERCODE_STDOUT;
+import static org.apache.maven.surefire.booter.ForkedProcessEventType.BOOTERCODE_STDOUT_NEW_LINE;
+import static org.apache.maven.surefire.booter.ForkedProcessEventType.BOOTERCODE_STOP_ON_NEXT_TEST;
+import static org.apache.maven.surefire.booter.ForkedProcessEventType.BOOTERCODE_TESTSET_COMPLETED;
+import static org.apache.maven.surefire.booter.ForkedProcessEventType.BOOTERCODE_TESTSET_STARTING;
+import static org.apache.maven.surefire.booter.ForkedProcessEventType.BOOTERCODE_TEST_ASSUMPTIONFAILURE;
+import static org.apache.maven.surefire.booter.ForkedProcessEventType.BOOTERCODE_TEST_ERROR;
+import static org.apache.maven.surefire.booter.ForkedProcessEventType.BOOTERCODE_TEST_FAILED;
+import static org.apache.maven.surefire.booter.ForkedProcessEventType.BOOTERCODE_TEST_SKIPPED;
+import static org.apache.maven.surefire.booter.ForkedProcessEventType.BOOTERCODE_TEST_STARTING;
+import static org.apache.maven.surefire.booter.ForkedProcessEventType.BOOTERCODE_TEST_SUCCEEDED;
+
+/**
+ * magic number : run mode : opcode [: opcode specific data]*
+ *
+ * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
+ * @since 3.0.0-M4
+ */
+public final class ForkedProcessEventObserver
+{
+    private volatile ForkedProcessPropertyEventListener propertyEventListener;
+    private volatile ForkedProcessStackTraceEventListener consoleErrorEventListener;
+    private volatile ForkedProcessExitErrorListener exitErrorEventListener;
+
+    private final ConcurrentMap<ForkedProcessEventType, ForkedProcessReportEventListener<?>> reportEventListeners =
+            new ConcurrentHashMap<>();
+
+    private final ConcurrentMap<ForkedProcessEventType, ForkedProcessStandardOutErrEventListener> stdOutErrEventListeners =
+            new ConcurrentHashMap<>();
+
+    private final ConcurrentMap<ForkedProcessEventType, ForkedProcessStringEventListener> consoleEventListeners =
+            new ConcurrentHashMap<>();
+
+    private final ConcurrentMap<ForkedProcessEventType, ForkedProcessEventListener> controlEventListeners =
+            new ConcurrentHashMap<>();
+
+    public void setSystemPropertiesListener( ForkedProcessPropertyEventListener listener )
+    {
+        propertyEventListener = requireNonNull( listener );
+    }
+
+    public <T extends ReportEntry> void setTestSetStartingListener( ForkedProcessReportEventListener<T> listener )
+    {
+        reportEventListeners.put( BOOTERCODE_TESTSET_STARTING, requireNonNull( listener ) );
+    }
+
+    public void setTestSetCompletedListener( ForkedProcessReportEventListener<?> listener )
+    {
+        reportEventListeners.put( BOOTERCODE_TESTSET_COMPLETED, requireNonNull( listener ) );
+    }
+
+    public void setTestStartingListener( ForkedProcessReportEventListener<?> listener )
+    {
+        reportEventListeners.put( BOOTERCODE_TEST_STARTING, requireNonNull( listener ) );
+    }
+
+    public void setTestSucceededListener( ForkedProcessReportEventListener<?> listener )
+    {
+        reportEventListeners.put( BOOTERCODE_TEST_SUCCEEDED, requireNonNull( listener ) );
+    }
+
+    public void setTestFailedListener( ForkedProcessReportEventListener<?> listener )
+    {
+        reportEventListeners.put( BOOTERCODE_TEST_FAILED, requireNonNull( listener ) );
+    }
+
+    public void setTestSkippedListener( ForkedProcessReportEventListener<?> listener )
+    {
+        reportEventListeners.put( BOOTERCODE_TEST_SKIPPED, requireNonNull( listener ) );
+    }
+
+    public void setTestErrorListener( ForkedProcessReportEventListener<?> listener )
+    {
+        reportEventListeners.put( BOOTERCODE_TEST_ERROR, requireNonNull( listener ) );
+    }
+
+    public void setTestAssumptionFailureListener( ForkedProcessReportEventListener<?> listener )
+    {
+        reportEventListeners.put( BOOTERCODE_TEST_ASSUMPTIONFAILURE, requireNonNull( listener ) );
+    }
+
+    public void setStdOutListener( ForkedProcessStandardOutErrEventListener listener )
+    {
+        stdOutErrEventListeners.put( BOOTERCODE_STDOUT, requireNonNull( listener ) );
+        stdOutErrEventListeners.put( BOOTERCODE_STDOUT_NEW_LINE, requireNonNull( listener ) );
+    }
+
+    public void setStdErrListener( ForkedProcessStandardOutErrEventListener listener )
+    {
+        stdOutErrEventListeners.put( BOOTERCODE_STDERR, requireNonNull( listener ) );
+        stdOutErrEventListeners.put( BOOTERCODE_STDERR_NEW_LINE, requireNonNull( listener ) );
+    }
+
+    public void setConsoleInfoListener( ForkedProcessStringEventListener listener )
+    {
+        consoleEventListeners.put( BOOTERCODE_CONSOLE_INFO, requireNonNull( listener ) );
+    }
+
+    public void setConsoleErrorListener( ForkedProcessStackTraceEventListener listener )
+    {
+        consoleErrorEventListener = requireNonNull( listener );
+    }
+
+    public void setConsoleDebugListener( ForkedProcessStringEventListener listener )
+    {
+        consoleEventListeners.put( BOOTERCODE_CONSOLE_DEBUG, requireNonNull( listener ) );
+    }
+
+    public void setConsoleWarningListener( ForkedProcessStringEventListener listener )
+    {
+        consoleEventListeners.put( BOOTERCODE_CONSOLE_WARNING, requireNonNull( listener ) );
+    }
+
+    public void setByeListener( ForkedProcessEventListener listener )
+    {
+        controlEventListeners.put( BOOTERCODE_BYE, requireNonNull( listener ) );
+    }
+
+    public void setStopOnNextTestListener( ForkedProcessEventListener listener )
+    {
+        controlEventListeners.put( BOOTERCODE_STOP_ON_NEXT_TEST, requireNonNull( listener ) );
+    }
+
+    public void setAcquireNextTestListener( ForkedProcessEventListener listener )
+    {
+        controlEventListeners.put( BOOTERCODE_NEXT_TEST, requireNonNull( listener ) );
+    }
+
+    public void setExitErrorEventListener( ForkedProcessExitErrorListener listener )
+    {
+        exitErrorEventListener = requireNonNull( listener );
+    }
+
+    public void handleEvent( Event event )
+    {
+        ForkedProcessEventType eventType = event.getEventType();
+        if ( event.isControlCategory() )
+        {
+            ForkedProcessEventListener listener = controlEventListeners.get( eventType );
+            if ( listener != null )
+            {
+                listener.handle();
+            }
+        }
+        else if ( event.isConsoleCategory() )
+        {
+            ForkedProcessStringEventListener listener = consoleEventListeners.get( eventType );
+            if ( listener != null )
+            {
+                listener.handle( ( (AbstractConsoleEvent) event ).getMessage() );
+            }
+        }
+        else if ( event.isConsoleErrorCategory() )
+        {
+            if ( consoleErrorEventListener != null )
+            {
+                consoleErrorEventListener.handle( ( ( ConsoleErrorEvent ) event ).getStackTraceWriter() );
+            }
+        }
+        else if ( event.isStandardStreamCategory() )
+        {
+            boolean newLine = eventType == BOOTERCODE_STDOUT_NEW_LINE || eventType == BOOTERCODE_STDERR_NEW_LINE;
+            AbstractStandardStreamEvent standardStreamEvent = (AbstractStandardStreamEvent) event;
+            ForkedProcessStandardOutErrEventListener listener = stdOutErrEventListeners.get( eventType );
+            if ( listener != null )
+            {
+                listener.handle( standardStreamEvent.getRunMode(), standardStreamEvent.getMessage(), newLine );
+            }
+        }
+        else if ( event.isSysPropCategory() )
+        {
+            SystemPropertyEvent systemPropertyEvent = (SystemPropertyEvent) event;
+            RunMode runMode = systemPropertyEvent.getRunMode();
+            String key = systemPropertyEvent.getKey();
+            String value = systemPropertyEvent.getValue();
+            if ( propertyEventListener != null )
+            {
+                propertyEventListener.handle( runMode, key, value );
+            }
+        }
+        else if ( event.isTestCategory() )
+        {
+            ForkedProcessReportEventListener listener = reportEventListeners.get( eventType );
+            AbstractTestControlEvent testControlEvent = (AbstractTestControlEvent) event;
+            RunMode mode = testControlEvent.getRunMode();
+            ReportEntry reportEntry = testControlEvent.getReportEntry();
+            if ( listener != null )
+            {
+                listener.handle( mode, reportEntry );
+            }
+        }
+        else if ( event.isJvmExitError() )
+        {
+            JvmExitErrorEvent jvmExitErrorEvent = (JvmExitErrorEvent) event;
+            if ( exitErrorEventListener != null )
+            {
+                exitErrorEventListener.handle( jvmExitErrorEvent.getStackTraceWriter() );
+            }
+        }
+    }
+}
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedProcessExitErrorListener.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedProcessExitErrorListener.java
index b14c38c..a8b4b0b 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedProcessExitErrorListener.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedProcessExitErrorListener.java
@@ -19,11 +19,13 @@ package org.apache.maven.plugin.surefire.booterclient.output;
  * under the License.
  */
 
+import org.apache.maven.surefire.report.StackTraceWriter;
+
 /**
  * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
  * @since 3.0.0-M4
  */
 public interface ForkedProcessExitErrorListener
 {
-    void handle( String exceptionMessage, String smartTrimmedStackTrace, String stackTrace );
+    void handle( StackTraceWriter stackTrace );
 }
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedProcessStackTraceEventListener.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedProcessStackTraceEventListener.java
index f54cc40..81bf53f 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedProcessStackTraceEventListener.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedProcessStackTraceEventListener.java
@@ -19,11 +19,15 @@ package org.apache.maven.plugin.surefire.booterclient.output;
  * under the License.
  */
 
+import org.apache.maven.surefire.report.StackTraceWriter;
+
+import javax.annotation.Nonnull;
+
 /**
  * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
  * @since 3.0.0-M4
  */
 public interface ForkedProcessStackTraceEventListener
 {
-    void handle( String msg, String smartStackTrace, String stackTrace );
+    void handle( @Nonnull StackTraceWriter stackTrace );
 }
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/NativeStdErrStreamConsumer.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/NativeStdErrStreamConsumer.java
index 06ecc93..bc0bc7c 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/NativeStdErrStreamConsumer.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/NativeStdErrStreamConsumer.java
@@ -32,7 +32,7 @@ import javax.annotation.Nonnull;
  * @see org.apache.maven.plugin.surefire.booterclient.ForkStarter
  */
 public final class NativeStdErrStreamConsumer
-    implements EventHandler
+    implements EventHandler<String>
 {
     private final DefaultReporterFactory defaultReporterFactory;
 
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ThreadedStreamConsumer.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ThreadedStreamConsumer.java
index a6ae68f..9c72a04 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ThreadedStreamConsumer.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ThreadedStreamConsumer.java
@@ -19,6 +19,7 @@ package org.apache.maven.plugin.surefire.booterclient.output;
  * under the License.
  */
 
+import org.apache.maven.surefire.eventapi.Event;
 import org.apache.maven.surefire.shared.utils.cli.StreamConsumer;
 import org.apache.maven.surefire.extensions.EventHandler;
 import org.apache.maven.surefire.util.internal.DaemonThreadFactory;
@@ -38,13 +39,13 @@ import static java.lang.Thread.currentThread;
  * @author Kristian Rosenvold
  */
 public final class ThreadedStreamConsumer
-        implements EventHandler, Closeable
+        implements EventHandler<Event>, Closeable
 {
-    private static final String END_ITEM = "";
+    private static final Event END_ITEM = new FinalEvent();
 
     private static final int ITEM_LIMIT_BEFORE_SLEEP = 10_000;
 
-    private final BlockingQueue<String> items = new ArrayBlockingQueue<>( ITEM_LIMIT_BEFORE_SLEEP );
+    private final BlockingQueue<Event> items = new ArrayBlockingQueue<>( ITEM_LIMIT_BEFORE_SLEEP );
 
     private final AtomicBoolean stop = new AtomicBoolean();
 
@@ -55,17 +56,17 @@ public final class ThreadedStreamConsumer
     final class Pumper
             implements Runnable
     {
-        private final EventHandler target;
+        private final EventHandler<Event> target;
 
         private final MultipleFailureException errors = new MultipleFailureException();
 
-        Pumper( EventHandler target )
+        Pumper( EventHandler<Event> target )
         {
             this.target = target;
         }
 
         /**
-         * Calls {@link ForkClient#handleEvent(String)} which may throw any {@link RuntimeException}.<br>
+         * Calls {@link ForkClient#handleEvent(Event)} which may throw any {@link RuntimeException}.<br>
          * Even if {@link ForkClient} is not fault-tolerant, this method MUST be fault-tolerant and thus the
          * try-catch block must be inside of the loop which prevents from loosing events from {@link StreamConsumer}.
          * <br>
@@ -82,7 +83,7 @@ public final class ThreadedStreamConsumer
             {
                 try
                 {
-                    String item = ThreadedStreamConsumer.this.items.take();
+                    Event item = ThreadedStreamConsumer.this.items.take();
                     if ( shouldStopQueueing( item ) )
                     {
                         return;
@@ -107,7 +108,7 @@ public final class ThreadedStreamConsumer
         }
     }
 
-    public ThreadedStreamConsumer( EventHandler target )
+    public ThreadedStreamConsumer( EventHandler<Event> target )
     {
         pumper = new Pumper( target );
         thread = DaemonThreadFactory.newDaemonThread( pumper, "ThreadedStreamConsumer" );
@@ -115,8 +116,7 @@ public final class ThreadedStreamConsumer
     }
 
     @Override
-    // todo use object instead of string
-    public void handleEvent( @Nonnull String event )
+    public void handleEvent( @Nonnull Event event )
     {
         if ( stop.get() )
         {
@@ -167,8 +167,61 @@ public final class ThreadedStreamConsumer
      * @param item    element from <code>items</code>
      * @return {@code true} if tail of the queue
      */
-    private boolean shouldStopQueueing( String item )
+    private boolean shouldStopQueueing( Event item )
     {
         return item == END_ITEM;
     }
+
+    /**
+     *
+     */
+    private static class FinalEvent extends Event
+    {
+        FinalEvent()
+        {
+            super( null );
+        }
+
+        @Override
+        public boolean isControlCategory()
+        {
+            return false;
+        }
+
+        @Override
+        public boolean isConsoleCategory()
+        {
+            return false;
+        }
+
+        @Override
+        public boolean isConsoleErrorCategory()
+        {
+            return false;
+        }
+
+        @Override
+        public boolean isStandardStreamCategory()
+        {
+            return false;
+        }
+
+        @Override
+        public boolean isSysPropCategory()
+        {
+            return false;
+        }
+
+        @Override
+        public boolean isTestCategory()
+        {
+            return false;
+        }
+
+        @Override
+        public boolean isJvmExitError()
+        {
+            return false;
+        }
+    }
 }
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/EventConsumerThread.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/EventConsumerThread.java
new file mode 100644
index 0000000..d8a276d
--- /dev/null
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/EventConsumerThread.java
@@ -0,0 +1,430 @@
+package org.apache.maven.plugin.surefire.extensions;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF 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.
+ */
+
+import org.apache.commons.codec.binary.Base64;
+import org.apache.maven.plugin.surefire.booterclient.output.DeserializedStacktraceWriter;
+import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
+import org.apache.maven.surefire.booter.ForkedProcessEventType;
+import org.apache.maven.surefire.eventapi.ConsoleDebugEvent;
+import org.apache.maven.surefire.eventapi.ConsoleErrorEvent;
+import org.apache.maven.surefire.eventapi.ConsoleInfoEvent;
+import org.apache.maven.surefire.eventapi.ConsoleWarningEvent;
+import org.apache.maven.surefire.eventapi.ControlByeEvent;
+import org.apache.maven.surefire.eventapi.ControlNextTestEvent;
+import org.apache.maven.surefire.eventapi.ControlStopOnNextTestEvent;
+import org.apache.maven.surefire.eventapi.Event;
+import org.apache.maven.surefire.eventapi.JvmExitErrorEvent;
+import org.apache.maven.surefire.eventapi.StandardStreamErrEvent;
+import org.apache.maven.surefire.eventapi.StandardStreamErrWithNewLineEvent;
+import org.apache.maven.surefire.eventapi.StandardStreamOutEvent;
+import org.apache.maven.surefire.eventapi.StandardStreamOutWithNewLineEvent;
+import org.apache.maven.surefire.eventapi.SystemPropertyEvent;
+import org.apache.maven.surefire.eventapi.TestAssumptionFailureEvent;
+import org.apache.maven.surefire.eventapi.TestErrorEvent;
+import org.apache.maven.surefire.eventapi.TestFailedEvent;
+import org.apache.maven.surefire.eventapi.TestSkippedEvent;
+import org.apache.maven.surefire.eventapi.TestStartingEvent;
+import org.apache.maven.surefire.eventapi.TestSucceededEvent;
+import org.apache.maven.surefire.eventapi.TestsetCompletedEvent;
+import org.apache.maven.surefire.eventapi.TestsetStartingEvent;
+import org.apache.maven.surefire.extensions.CloseableDaemonThread;
+import org.apache.maven.surefire.extensions.EventHandler;
+import org.apache.maven.surefire.extensions.util.CountdownCloseable;
+import org.apache.maven.surefire.report.ReportEntry;
+import org.apache.maven.surefire.report.RunMode;
+import org.apache.maven.surefire.report.StackTraceWriter;
+
+import javax.annotation.Nonnull;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import static java.nio.charset.StandardCharsets.US_ASCII;
+import static org.apache.maven.surefire.booter.ForkedProcessEventType.MAGIC_NUMBER;
+import static org.apache.maven.surefire.report.CategorizedReportEntry.reportEntry;
+import static org.apache.maven.surefire.report.RunMode.MODES;
+import static org.apache.maven.surefire.shared.utils.StringUtils.isBlank;
+
+/**
+ *
+ */
+public class EventConsumerThread extends CloseableDaemonThread
+{
+    private static final Base64 BASE64 = new Base64();
+
+    private final ReadableByteChannel channel;
+    private final EventHandler<Event> eventHandler;
+    private final CountdownCloseable countdownCloseable;
+    private final ConsoleLogger logger;
+    private volatile boolean disabled;
+
+    protected EventConsumerThread( @Nonnull String threadName,
+                                   @Nonnull ReadableByteChannel channel,
+                                   @Nonnull EventHandler<Event> eventHandler,
+                                   @Nonnull CountdownCloseable countdownCloseable,
+                                   @Nonnull ConsoleLogger logger )
+    {
+        super( threadName );
+        this.channel = channel;
+        this.eventHandler = eventHandler;
+        this.countdownCloseable = countdownCloseable;
+        this.logger = logger;
+    }
+
+    @Override
+    public void run()
+    {
+        try ( ReadableByteChannel stream = channel;
+              CountdownCloseable c = countdownCloseable; )
+        {
+            decode();
+        }
+        catch ( IOException e )
+        {
+            // not needed
+        }
+    }
+
+    @Override
+    public void disable()
+    {
+        disabled = true;
+    }
+
+    @Override
+    public void close() throws IOException
+    {
+        channel.close();
+    }
+
+    @SuppressWarnings( "checkstyle:innerassignment" )
+    private void decode() throws IOException
+    {
+        List<String> tokens = new ArrayList<>();
+        StringBuilder token = new StringBuilder( MAGIC_NUMBER.length() );
+        ByteBuffer buffer = ByteBuffer.allocate( 1 );
+        boolean endOfStream;
+
+        start:
+        do
+        {
+            tokens.clear();
+            token.setLength( 0 );
+            FrameCompletion completion = null;
+            for ( boolean frameStarted = false; !( endOfStream = channel.read( buffer ) == -1 ) ; completion = null )
+            {
+                buffer.flip();
+                char c = (char) buffer.get();
+                buffer.clear();
+
+                if ( !frameStarted )
+                {
+                    if ( c == ':' )
+                    {
+                        frameStarted = true;
+                        token.setLength( 0 );
+                        tokens.clear();
+                    }
+                }
+                else
+                {
+                    if ( c == ':' )
+                    {
+                        tokens.add( token.toString() );
+                        token.setLength( 0 );
+                        completion = frameCompleteness( tokens );
+                        if ( completion == FrameCompletion.COMPLETE )
+                        {
+                            break;
+                        }
+                        else if ( completion == FrameCompletion.MALFORMED )
+                        {
+                            logger.error( "Malformed frame with tokens " + tokens );
+                            continue start;
+                        }
+                    }
+                    else
+                    {
+                        token.append( c );
+                    }
+                }
+            }
+
+            if ( completion == FrameCompletion.COMPLETE )
+            {
+                Event event = toEvent( tokens );
+                if ( !disabled && event != null )
+                {
+                    eventHandler.handleEvent( event );
+                }
+            }
+
+            if ( endOfStream )
+            {
+                return;
+            }
+        }
+        while ( true );
+    }
+
+    private Event toEvent( List<String> tokensInFrame )
+    {
+        Iterator<String> tokens = tokensInFrame.iterator();
+        assert tokens.next() != null;
+
+        ForkedProcessEventType event = ForkedProcessEventType.byOpcode( tokens.next() );
+
+        if ( event == null )
+        {
+            return null;
+        }
+
+        if ( event.isControlCategory() )
+        {
+            switch ( event )
+            {
+                case BOOTERCODE_BYE:
+                    return new ControlByeEvent();
+                case BOOTERCODE_STOP_ON_NEXT_TEST:
+                    return new ControlStopOnNextTestEvent();
+                case BOOTERCODE_NEXT_TEST:
+                    return new ControlNextTestEvent();
+                default:
+                    throw new IllegalStateException( "Unknown enum " + event );
+            }
+        }
+        else if ( event.isConsoleCategory() )
+        {
+            Charset encoding = Charset.forName( tokens.next() );
+            String msg = decode( tokens.next(), encoding );
+            switch ( event )
+            {
+                case BOOTERCODE_CONSOLE_INFO:
+                    return new ConsoleInfoEvent( msg );
+                case BOOTERCODE_CONSOLE_DEBUG:
+                    return new ConsoleDebugEvent( msg );
+                case BOOTERCODE_CONSOLE_WARNING:
+                    return new ConsoleWarningEvent( msg );
+                case BOOTERCODE_CONSOLE_ERROR:
+                    String smartStackTrace = decode( tokens.next(), encoding );
+                    String stackTrace = decode( tokens.next(), encoding );
+                    StackTraceWriter stackTraceWriter = decodeTrace( encoding, msg, smartStackTrace, stackTrace );
+                    return new ConsoleErrorEvent( stackTraceWriter );
+                default:
+                    throw new IllegalStateException( "Unknown enum " + event );
+            }
+        }
+        else if ( event.isStandardStreamCategory() )
+        {
+            RunMode mode = MODES.get( tokens.next() );
+            Charset encoding = Charset.forName( tokens.next() );
+            String output = decode( tokens.next(), encoding );
+            switch ( event )
+            {
+                case BOOTERCODE_STDOUT:
+                    return new StandardStreamOutEvent( mode, output );
+                case BOOTERCODE_STDOUT_NEW_LINE:
+                    return new StandardStreamOutWithNewLineEvent( mode, output );
+                case BOOTERCODE_STDERR:
+                    return new StandardStreamErrEvent( mode, output );
+                case BOOTERCODE_STDERR_NEW_LINE:
+                    return new StandardStreamErrWithNewLineEvent( mode, output );
+                default:
+                    throw new IllegalStateException( "Unknown enum " + event );
+            }
+        }
+        else if ( event.isSysPropCategory() )
+        {
+            RunMode mode = MODES.get( tokens.next() );
+            Charset encoding = Charset.forName( tokens.next() );
+            String key = decode( tokens.next(), encoding );
+            String value = decode( tokens.next(), encoding );
+            return new SystemPropertyEvent( mode, key, value );
+        }
+        else if ( event.isTestCategory() )
+        {
+            RunMode mode = MODES.get( tokens.next() );
+            Charset encoding = Charset.forName( tokens.next() );
+            String sourceName = tokens.next();
+            String sourceText = tokens.next();
+            String name = tokens.next();
+            String nameText = tokens.next();
+            String group = tokens.next();
+            String message = tokens.next();
+            String elapsed = tokens.next();
+            String traceMessage = tokens.next();
+            String smartTrimmedStackTrace = tokens.next();
+            String stackTrace = tokens.next();
+            ReportEntry reportEntry = toReportEntry( encoding, sourceName, sourceText, name, nameText,
+                group, message, elapsed, traceMessage, smartTrimmedStackTrace, stackTrace );
+            switch ( event )
+            {
+                case BOOTERCODE_TESTSET_STARTING:
+                    return new TestsetStartingEvent( mode, reportEntry );
+                case BOOTERCODE_TESTSET_COMPLETED:
+                    return new TestsetCompletedEvent( mode, reportEntry );
+                case BOOTERCODE_TEST_STARTING:
+                    return new TestStartingEvent( mode, reportEntry );
+                case BOOTERCODE_TEST_SUCCEEDED:
+                    return new TestSucceededEvent( mode, reportEntry );
+                case BOOTERCODE_TEST_FAILED:
+                    return new TestFailedEvent( mode, reportEntry );
+                case BOOTERCODE_TEST_SKIPPED:
+                    return new TestSkippedEvent( mode, reportEntry );
+                case BOOTERCODE_TEST_ERROR:
+                    return new TestErrorEvent( mode, reportEntry );
+                case BOOTERCODE_TEST_ASSUMPTIONFAILURE:
+                    return new TestAssumptionFailureEvent( mode, reportEntry );
+                default:
+                    throw new IllegalStateException( "Unknown enum " + event );
+            }
+        }
+        else if ( event.isJvmExitError() )
+        {
+            Charset encoding = Charset.forName( tokens.next() );
+            String message = decode( tokens.next(), encoding );
+            String smartTrimmedStackTrace = decode( tokens.next(), encoding );
+            String stackTrace = decode( tokens.next(), encoding );
+            StackTraceWriter stackTraceWriter = decodeTrace( encoding, message, smartTrimmedStackTrace, stackTrace );
+            return new JvmExitErrorEvent( stackTraceWriter );
+        }
+
+        throw new IllegalStateException( "Missing a branch for the event type " + event );
+    }
+
+    private static FrameCompletion frameCompleteness( List<String> tokens )
+    {
+        if ( !tokens.isEmpty() && !MAGIC_NUMBER.equals( tokens.get( 0 ) ) )
+        {
+            return FrameCompletion.MALFORMED;
+        }
+
+        if ( tokens.size() >= 2 )
+        {
+            String opcode = tokens.get( 1 );
+            ForkedProcessEventType event = ForkedProcessEventType.byOpcode( opcode );
+            if ( event == null )
+            {
+                return FrameCompletion.MALFORMED;
+            }
+            else if ( event.isControlCategory() )
+            {
+                return FrameCompletion.COMPLETE;
+            }
+            else if ( event.isConsoleErrorCategory() )
+            {
+                return tokens.size() == 6 ? FrameCompletion.COMPLETE : FrameCompletion.NOT_COMPLETE;
+            }
+            else if ( event.isConsoleCategory() )
+            {
+                return tokens.size() == 4 ? FrameCompletion.COMPLETE : FrameCompletion.NOT_COMPLETE;
+            }
+            else if ( event.isStandardStreamCategory() )
+            {
+                return tokens.size() == 5 ? FrameCompletion.COMPLETE : FrameCompletion.NOT_COMPLETE;
+            }
+            else if ( event.isSysPropCategory() )
+            {
+                return tokens.size() == 6 ? FrameCompletion.COMPLETE : FrameCompletion.NOT_COMPLETE;
+            }
+            else if ( event.isTestCategory() )
+            {
+                return tokens.size() == 14 ? FrameCompletion.COMPLETE : FrameCompletion.NOT_COMPLETE;
+            }
+            else if ( event.isJvmExitError() )
+            {
+                return tokens.size() == 6 ? FrameCompletion.COMPLETE : FrameCompletion.NOT_COMPLETE;
+            }
+        }
+        return FrameCompletion.NOT_COMPLETE;
+    }
+
+    private static String decode( String line, Charset encoding )
+    {
+        // ForkedChannelEncoder is encoding the stream with US_ASCII
+        return line == null || "-".equals( line )
+            ? null
+            : new String( BASE64.decode( line.getBytes( US_ASCII ) ), encoding );
+    }
+
+    private static StackTraceWriter decodeTrace( Charset encoding, String encTraceMessage,
+                                                 String encSmartTrimmedStackTrace, String encStackTrace )
+    {
+        if ( isBlank( encStackTrace ) || "-".equals( encStackTrace ) )
+        {
+            return null;
+        }
+        else
+        {
+            String traceMessage = decode( encTraceMessage, encoding );
+            String stackTrace = decode( encStackTrace, encoding );
+            String smartTrimmedStackTrace = decode( encSmartTrimmedStackTrace, encoding );
+            return new DeserializedStacktraceWriter( traceMessage, smartTrimmedStackTrace, stackTrace );
+        }
+    }
+
+    private static ReportEntry toReportEntry( Charset encoding,
+                                      // ReportEntry:
+                                      String encSource, String encSourceText, String encName, String encNameText,
+                                      String encGroup, String encMessage, String encTimeElapsed,
+                                      // StackTraceWriter:
+                                      String encTraceMessage, String encSmartTrimmedStackTrace, String encStackTrace )
+        throws NumberFormatException
+    {
+        if ( encoding == null )
+        {
+            // corrupted or incomplete stream
+            return null;
+        }
+
+        String source = decode( encSource, encoding );
+        String sourceText = decode( encSourceText, encoding );
+        String name = decode( encName, encoding );
+        String nameText = decode( encNameText, encoding );
+        String group = decode( encGroup, encoding );
+        StackTraceWriter stackTraceWriter =
+            decodeTrace( encoding, encTraceMessage, encSmartTrimmedStackTrace, encStackTrace );
+        Integer elapsed = decodeToInteger( encTimeElapsed );
+        String message = decode( encMessage, encoding );
+        return reportEntry( source, sourceText, name, nameText,
+            group, stackTraceWriter, elapsed, message, Collections.<String, String>emptyMap() );
+    }
+
+    private static Integer decodeToInteger( String line )
+    {
+        return line == null || "-".equals( line ) ? null : Integer.decode( line );
+    }
+
+    /**
+     * Determines whether the frame is complete or malformed.
+     */
+    private enum FrameCompletion
+    {
+        NOT_COMPLETE,
+        COMPLETE,
+        MALFORMED
+    }
+}
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/LegacyForkChannel.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/LegacyForkChannel.java
index 3dc4425..89cda94 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/LegacyForkChannel.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/LegacyForkChannel.java
@@ -19,12 +19,13 @@ package org.apache.maven.plugin.surefire.extensions;
  * under the License.
  */
 
+import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
+import org.apache.maven.surefire.eventapi.Event;
 import org.apache.maven.surefire.extensions.CloseableDaemonThread;
 import org.apache.maven.surefire.extensions.CommandReader;
 import org.apache.maven.surefire.extensions.EventHandler;
 import org.apache.maven.surefire.extensions.ForkChannel;
 import org.apache.maven.surefire.extensions.util.CountdownCloseable;
-import org.apache.maven.surefire.extensions.util.LineConsumerThread;
 import org.apache.maven.surefire.extensions.util.StreamFeeder;
 
 import javax.annotation.Nonnull;
@@ -41,9 +42,12 @@ import java.nio.channels.WritableByteChannel;
  */
 final class LegacyForkChannel extends ForkChannel
 {
-    protected LegacyForkChannel( int forkChannelId )
+    private final ConsoleLogger logger;
+
+    protected LegacyForkChannel( int forkChannelId, ConsoleLogger logger )
     {
         super( forkChannelId );
+        this.logger = logger;
     }
 
     @Override
@@ -71,13 +75,12 @@ final class LegacyForkChannel extends ForkChannel
     }
 
     @Override
-    public CloseableDaemonThread bindEventHandler( @Nonnull EventHandler eventHandler,
+    public CloseableDaemonThread bindEventHandler( @Nonnull EventHandler<Event> eventHandler,
                                                    @Nonnull CountdownCloseable countdownCloseable,
                                                    ReadableByteChannel stdOut )
     {
-        // todo develop Event and EventConsumerThread, see the algorithm in ForkedChannelDecoder#handleEvent()
-        return new LineConsumerThread( "fork-" + getForkChannelId() + "-event-thread-", stdOut,
-            eventHandler, countdownCloseable );
+        return new EventConsumerThread( "fork-" + getForkChannelId() + "-event-thread-", stdOut,
+            eventHandler, countdownCloseable, logger );
     }
 
     @Override
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/LegacyForkNodeFactory.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/LegacyForkNodeFactory.java
index 3667407..ca2010b 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/LegacyForkNodeFactory.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/LegacyForkNodeFactory.java
@@ -19,6 +19,7 @@ package org.apache.maven.plugin.surefire.extensions;
  * under the License.
  */
 
+import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
 import org.apache.maven.surefire.extensions.ForkChannel;
 import org.apache.maven.surefire.extensions.ForkNodeFactory;
 
@@ -32,8 +33,8 @@ public class LegacyForkNodeFactory implements ForkNodeFactory
 {
     @Nonnull
     @Override
-    public ForkChannel createForkChannel( @Nonnegative int forkChannelId )
+    public ForkChannel createForkChannel( @Nonnegative int forkChannelId, ConsoleLogger logger )
     {
-        return new LegacyForkChannel( forkChannelId );
+        return new LegacyForkChannel( forkChannelId, logger );
     }
 }
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/SurefireForkChannel.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/SurefireForkChannel.java
index fb2a1b1..65a38f0 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/SurefireForkChannel.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/SurefireForkChannel.java
@@ -19,12 +19,13 @@ package org.apache.maven.plugin.surefire.extensions;
  * under the License.
  */
 
+import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
+import org.apache.maven.surefire.eventapi.Event;
 import org.apache.maven.surefire.extensions.CloseableDaemonThread;
 import org.apache.maven.surefire.extensions.CommandReader;
 import org.apache.maven.surefire.extensions.EventHandler;
 import org.apache.maven.surefire.extensions.ForkChannel;
 import org.apache.maven.surefire.extensions.util.CountdownCloseable;
-import org.apache.maven.surefire.extensions.util.LineConsumerThread;
 import org.apache.maven.surefire.extensions.util.StreamFeeder;
 
 import javax.annotation.Nonnull;
@@ -62,13 +63,15 @@ final class SurefireForkChannel extends ForkChannel
 {
     private static final byte[] LOCAL_LOOPBACK_IP_ADDRESS = new byte[]{127, 0, 0, 1};
 
+    private final ConsoleLogger logger;
     private final ServerSocketChannel server;
     private final int localPort;
     private volatile SocketChannel channel;
 
-    SurefireForkChannel( int forkChannelId ) throws IOException
+    SurefireForkChannel( int forkChannelId, ConsoleLogger logger ) throws IOException
     {
         super( forkChannelId );
+        this.logger = logger;
         server = open();
         setTrueOptions( SO_REUSEADDR, TCP_NODELAY, SO_KEEPALIVE );
         InetAddress ip = Inet4Address.getByAddress( LOCAL_LOOPBACK_IP_ADDRESS );
@@ -118,13 +121,12 @@ final class SurefireForkChannel extends ForkChannel
     }
 
     @Override
-    public CloseableDaemonThread bindEventHandler( @Nonnull EventHandler eventHandler,
+    public CloseableDaemonThread bindEventHandler( @Nonnull EventHandler<Event> eventHandler,
                                                    @Nonnull CountdownCloseable countdownCloseable,
                                                    ReadableByteChannel stdOut )
     {
-        // todo develop Event and EventConsumerThread, see the algorithm in ForkedChannelDecoder#handleEvent()
-        return new LineConsumerThread( "fork-" + getForkChannelId() + "-event-thread-", channel,
-            eventHandler, countdownCloseable );
+        return new EventConsumerThread( "fork-" + getForkChannelId() + "-event-thread-", channel,
+            eventHandler, countdownCloseable, logger );
     }
 
     @Override
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/SurefireForkNodeFactory.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/SurefireForkNodeFactory.java
index d870819..1c4aabc 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/SurefireForkNodeFactory.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/SurefireForkNodeFactory.java
@@ -19,6 +19,7 @@ package org.apache.maven.plugin.surefire.extensions;
  * under the License.
  */
 
+import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
 import org.apache.maven.surefire.extensions.ForkChannel;
 import org.apache.maven.surefire.extensions.ForkNodeFactory;
 
@@ -33,8 +34,8 @@ public class SurefireForkNodeFactory implements ForkNodeFactory
 {
     @Nonnull
     @Override
-    public ForkChannel createForkChannel( @Nonnegative int forkChannelId ) throws IOException
+    public ForkChannel createForkChannel( @Nonnegative int forkChannelId, ConsoleLogger logger ) throws IOException
     {
-        return new SurefireForkChannel( forkChannelId );
+        return new SurefireForkChannel( forkChannelId, logger );
     }
 }
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ForkStarterTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ForkStarterTest.java
index a7ab4d7..55f789f 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ForkStarterTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ForkStarterTest.java
@@ -57,7 +57,6 @@ import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.ArrayDeque;
 import java.util.Collections;
-import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.jar.Manifest;
 import java.util.zip.Deflater;
 
@@ -176,7 +175,7 @@ public class ForkStarterTest
         TestProvidingInputStream testProvidingInputStream = new TestProvidingInputStream( new ArrayDeque<String>() );
         invokeMethod( forkStarter, "fork", types, null,
             new PropertiesWrapper( Collections.<String, String>emptyMap() ),
-            new ForkClient( reporterFactory, null, logger, new AtomicBoolean(), 1 ),
+            new ForkClient( reporterFactory, null, 1 ),
             new SurefireProperties(), 1, testProvidingInputStream, new LegacyForkNodeFactory(), true );
         testProvidingInputStream.close();
     }
@@ -230,7 +229,7 @@ public class ForkStarterTest
         TestLessInputStream testLessInputStream = new TestLessInputStreamBuilder().build();
         invokeMethod( forkStarter, "fork", types, null,
             new PropertiesWrapper( Collections.<String, String>emptyMap() ),
-            new ForkClient( reporterFactory, testLessInputStream, logger, new AtomicBoolean(), 1 ),
+            new ForkClient( reporterFactory, testLessInputStream, 1 ),
             new SurefireProperties(), 1, testLessInputStream, new LegacyForkNodeFactory(), true );
         testLessInputStream.close();
     }
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ForkingRunListenerTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ForkingRunListenerTest.java
index 2e44e75..3504b0c 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ForkingRunListenerTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ForkingRunListenerTest.java
@@ -24,9 +24,8 @@ import junit.framework.TestCase;
 import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.NotifiableTestStream;
 import org.apache.maven.plugin.surefire.booterclient.output.ForkClient;
 import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
-import org.apache.maven.plugin.surefire.log.api.NullConsoleLogger;
-import org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelEncoder;
 import org.apache.maven.surefire.booter.ForkingRunListener;
+import org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelEncoder;
 import org.apache.maven.surefire.report.CategorizedReportEntry;
 import org.apache.maven.surefire.report.ConsoleOutputReceiver;
 import org.apache.maven.surefire.report.LegacyPojoStackTraceWriter;
@@ -44,8 +43,8 @@ import java.io.PrintStream;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
-import java.util.concurrent.atomic.AtomicBoolean;
 
+import static java.nio.channels.Channels.newChannel;
 import static org.hamcrest.Matchers.is;
 
 /**
@@ -183,7 +182,7 @@ public class ForkingRunListenerTest
     }
 
     public void testSystemProperties()
-        throws ReporterException, IOException
+        throws ReporterException
     {
         final StandardTestRun standardTestRun = new StandardTestRun();
         standardTestRun.run();
@@ -192,9 +191,8 @@ public class ForkingRunListenerTest
         createForkingRunListener();
 
         TestSetMockReporterFactory providerReporterFactory = new TestSetMockReporterFactory();
-        NullConsoleLogger log = new NullConsoleLogger();
         ForkClient forkStreamClient =
-                new ForkClient( providerReporterFactory, new MockNotifiableTestStream(), log, new AtomicBoolean(), 1 );
+                new ForkClient( providerReporterFactory, new MockNotifiableTestStream(), 1 );
 
         forkStreamClient.consumeMultiLineContent( ":maven-surefire-event:sys-prop:normal-run:UTF-8:azE=:djE="
                 + "\n:maven-surefire-event:sys-prop:normal-run:UTF-8:azI=:djI=" );
@@ -218,9 +216,8 @@ public class ForkingRunListenerTest
         forkingReporter.testSetCompleted( reportEntry );
 
         TestSetMockReporterFactory providerReporterFactory = new TestSetMockReporterFactory();
-        NullConsoleLogger log = new NullConsoleLogger();
         ForkClient forkStreamClient =
-                new ForkClient( providerReporterFactory, new MockNotifiableTestStream(), log, null, 1 );
+                new ForkClient( providerReporterFactory, new MockNotifiableTestStream(), 1 );
 
         forkStreamClient.consumeMultiLineContent( content.toString( "UTF-8" ) );
 
@@ -239,17 +236,16 @@ public class ForkingRunListenerTest
         ReportEntry expected = createDefaultReportEntry();
         final SimpleReportEntry secondExpected = createAnotherDefaultReportEntry();
 
-        new ForkingRunListener( new LegacyMasterProcessChannelEncoder( printStream ), false )
+        new ForkingRunListener( new LegacyMasterProcessChannelEncoder( newChannel( printStream ) ), false )
                 .testStarting( expected );
 
-        new ForkingRunListener( new LegacyMasterProcessChannelEncoder( anotherPrintStream ), false )
+        new ForkingRunListener( new LegacyMasterProcessChannelEncoder( newChannel( anotherPrintStream ) ), false )
                 .testSkipped( secondExpected );
 
         TestSetMockReporterFactory providerReporterFactory = new TestSetMockReporterFactory();
         NotifiableTestStream notifiableTestStream = new MockNotifiableTestStream();
-        NullConsoleLogger log = new NullConsoleLogger();
 
-        ForkClient forkStreamClient = new ForkClient( providerReporterFactory, notifiableTestStream, log, null, 1 );
+        ForkClient forkStreamClient = new ForkClient( providerReporterFactory, notifiableTestStream, 1 );
         forkStreamClient.consumeMultiLineContent( content.toString( "UTF-8" ) );
 
         MockReporter reporter = (MockReporter) forkStreamClient.getReporter();
@@ -257,7 +253,7 @@ public class ForkingRunListenerTest
         Assert.assertEquals( expected, reporter.getFirstData() );
         Assert.assertEquals( 1, reporter.getEvents().size() );
 
-        forkStreamClient = new ForkClient( providerReporterFactory, notifiableTestStream, log, null, 2 );
+        forkStreamClient = new ForkClient( providerReporterFactory, notifiableTestStream, 2 );
         forkStreamClient.consumeMultiLineContent( anotherContent.toString( "UTF-8" ) );
         MockReporter reporter2 = (MockReporter) forkStreamClient.getReporter();
         Assert.assertEquals( MockReporter.TEST_SKIPPED, reporter2.getFirstEvent() );
@@ -312,7 +308,7 @@ public class ForkingRunListenerTest
 
     private RunListener createForkingRunListener()
     {
-        return new ForkingRunListener( new LegacyMasterProcessChannelEncoder( printStream ), false );
+        return new ForkingRunListener( new LegacyMasterProcessChannelEncoder( newChannel( printStream ) ), false );
     }
 
     private class StandardTestRun
@@ -327,12 +323,11 @@ public class ForkingRunListenerTest
         }
 
         public void clientReceiveContent()
-            throws ReporterException, IOException
+            throws ReporterException
         {
             TestSetMockReporterFactory providerReporterFactory = new TestSetMockReporterFactory();
-            NullConsoleLogger log = new NullConsoleLogger();
             final ForkClient forkStreamClient =
-                    new ForkClient( providerReporterFactory, new MockNotifiableTestStream(), log, null, 1 );
+                    new ForkClient( providerReporterFactory, new MockNotifiableTestStream(), 1 );
             forkStreamClient.consumeMultiLineContent( content.toString( ) );
             reporter = (MockReporter) forkStreamClient.getReporter();
         }
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/output/ForkClientTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/output/ForkClientTest.java
index 1aedbb4..e6ef887 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/output/ForkClientTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/output/ForkClientTest.java
@@ -32,7 +32,6 @@ import org.junit.Test;
 import java.io.File;
 import java.io.IOException;
 import java.nio.ByteBuffer;
-import java.util.concurrent.atomic.AtomicBoolean;
 
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static java.util.Arrays.copyOfRange;
@@ -82,9 +81,7 @@ public class ForkClientTest
         DefaultReporterFactory factory = mock( DefaultReporterFactory.class );
         when( factory.getReportsDirectory() )
                 .thenReturn( new File( target, "surefire-reports" ) );
-        AtomicBoolean printedErrorStream = new AtomicBoolean();
-        ConsoleLogger logger = mock( ConsoleLogger.class );
-        ForkClient client = new ForkClient( factory, null, logger, printedErrorStream, 0 );
+        ForkClient client = new ForkClient( factory, null, 0 );
         client.handleEvent( null );
         assertThat( client.isSaidGoodBye() )
                 .isFalse();
@@ -110,9 +107,7 @@ public class ForkClientTest
         DefaultReporterFactory factory = mock( DefaultReporterFactory.class );
         when( factory.getReportsDirectory() )
                 .thenReturn( new File( target, "surefire-reports" ) );
-        AtomicBoolean printedErrorStream = new AtomicBoolean();
-        ConsoleLogger logger = mock( ConsoleLogger.class );
-        ForkClient client = new ForkClient( factory, null, logger, printedErrorStream, 0 );
+        ForkClient client = new ForkClient( factory, null, 0 );
         client.handleEvent( "   " );
         assertThat( client.isSaidGoodBye() )
                 .isFalse();
@@ -132,16 +127,13 @@ public class ForkClientTest
 
     @Test
     public void shouldNotFailOnEmptyInput3()
-            throws IOException
     {
         String cwd = System.getProperty( "user.dir" );
         File target = new File( cwd, "target" );
         DefaultReporterFactory factory = mock( DefaultReporterFactory.class );
         when( factory.getReportsDirectory() )
                 .thenReturn( new File( target, "surefire-reports" ) );
-        AtomicBoolean printedErrorStream = new AtomicBoolean();
-        ConsoleLogger logger = mock( ConsoleLogger.class );
-        ForkClient client = new ForkClient( factory, null, logger, printedErrorStream, 0 );
+        ForkClient client = new ForkClient( factory, null, 0 );
         client.consumeMultiLineContent( null );
         assertThat( client.isSaidGoodBye() )
                 .isFalse();
@@ -161,18 +153,16 @@ public class ForkClientTest
 
     @Test
     public void shouldNotFailOnEmptyInput4()
-            throws IOException
     {
         String cwd = System.getProperty( "user.dir" );
         File target = new File( cwd, "target" );
         DefaultReporterFactory factory = mock( DefaultReporterFactory.class );
         when( factory.getReportsDirectory() )
                 .thenReturn( new File( target, "surefire-reports" ) );
-        AtomicBoolean printedErrorStream = new AtomicBoolean();
         ConsoleLogger logger = mock( ConsoleLogger.class );
         when( logger.isDebugEnabled() )
                 .thenReturn( true );
-        ForkClient client = new ForkClient( factory, null, logger, printedErrorStream, 0 );
+        ForkClient client = new ForkClient( factory, null, 0 );
         client.consumeMultiLineContent( "   " );
         verify( logger )
                 .isDebugEnabled();
@@ -200,18 +190,16 @@ public class ForkClientTest
 
     @Test
     public void shouldNotFailOnEmptyInput5()
-            throws IOException
     {
         String cwd = System.getProperty( "user.dir" );
         File target = new File( cwd, "target" );
         DefaultReporterFactory factory = mock( DefaultReporterFactory.class );
         when( factory.getReportsDirectory() )
                 .thenReturn( new File( target, "surefire-reports" ) );
-        AtomicBoolean printedErrorStream = new AtomicBoolean();
         ConsoleLogger logger = mock( ConsoleLogger.class );
         when( logger.isDebugEnabled() )
                 .thenReturn( true );
-        ForkClient client = new ForkClient( factory, null, logger, printedErrorStream, 0 );
+        ForkClient client = new ForkClient( factory, null, 0 );
         client.consumeMultiLineContent( "Listening for transport dt_socket at address: bla" );
         verify( logger )
                 .isDebugEnabled();
@@ -236,20 +224,18 @@ public class ForkClientTest
 
     @Test
     public void shouldNotFailOnEmptyInput6()
-            throws IOException
     {
         String cwd = System.getProperty( "user.dir" );
         File target = new File( cwd, "target" );
         DefaultReporterFactory factory = mock( DefaultReporterFactory.class );
         when( factory.getReportsDirectory() )
                 .thenReturn( new File( target, "surefire-reports" ) );
-        AtomicBoolean printedErrorStream = new AtomicBoolean();
         ConsoleLogger logger = mock( ConsoleLogger.class );
         when( logger.isDebugEnabled() )
                 .thenReturn( false );
         when( logger.isInfoEnabled() )
                 .thenReturn( true );
-        ForkClient client = new ForkClient( factory, null, logger, printedErrorStream, 0 );
+        ForkClient client = new ForkClient( factory, null, 0 );
         client.consumeMultiLineContent( "Listening for transport dt_socket at address: bla" );
         verify( logger )
                 .isDebugEnabled();
@@ -279,7 +265,7 @@ public class ForkClientTest
     {
         NotifiableTestStream notifiableTestStream = mock( NotifiableTestStream.class );
 
-        ForkClient client = new ForkClient( null, notifiableTestStream, null, null, 0 );
+        ForkClient client = new ForkClient( null, notifiableTestStream, 0 );
         client.kill();
 
         verify( notifiableTestStream, times( 1 ) )
@@ -288,7 +274,6 @@ public class ForkClientTest
 
     @Test
     public void shouldAcquireNextTest()
-            throws IOException
     {
         String cwd = System.getProperty( "user.dir" );
         File target = new File( cwd, "target" );
@@ -296,9 +281,7 @@ public class ForkClientTest
         when( factory.getReportsDirectory() )
                 .thenReturn( new File( target, "surefire-reports" ) );
         NotifiableTestStream notifiableTestStream = mock( NotifiableTestStream.class );
-        AtomicBoolean printedErrorStream = new AtomicBoolean();
-        ConsoleLogger logger = mock( ConsoleLogger.class );
-        ForkClient client = new ForkClient( factory, notifiableTestStream, logger, printedErrorStream, 0 );
+        ForkClient client = new ForkClient( factory, notifiableTestStream, 0 );
         client.consumeMultiLineContent( ":maven-surefire-event:next-test\n" );
         verify( notifiableTestStream, times( 1 ) )
                 .provideNewTest();
@@ -322,7 +305,6 @@ public class ForkClientTest
 
     @Test
     public void shouldNotifyWithBye()
-            throws IOException
     {
         String cwd = System.getProperty( "user.dir" );
         File target = new File( cwd, "target" );
@@ -330,10 +312,8 @@ public class ForkClientTest
         when( factory.getReportsDirectory() )
                 .thenReturn( new File( target, "surefire-reports" ) );
         NotifiableTestStream notifiableTestStream = mock( NotifiableTestStream.class );
-        AtomicBoolean printedErrorStream = new AtomicBoolean();
-        ConsoleLogger logger = mock( ConsoleLogger.class );
 
-        ForkClient client = new ForkClient( factory, notifiableTestStream, logger, printedErrorStream, 0 );
+        ForkClient client = new ForkClient( factory, notifiableTestStream, 0 );
         client.consumeMultiLineContent( ":maven-surefire-event:bye\n" );
         client.kill();
 
@@ -361,7 +341,6 @@ public class ForkClientTest
 
     @Test
     public void shouldStopOnNextTest()
-            throws IOException
     {
         String cwd = System.getProperty( "user.dir" );
         File target = new File( cwd, "target" );
@@ -369,10 +348,8 @@ public class ForkClientTest
         when( factory.getReportsDirectory() )
                 .thenReturn( new File( target, "surefire-reports" ) );
         NotifiableTestStream notifiableTestStream = mock( NotifiableTestStream.class );
-        AtomicBoolean printedErrorStream = new AtomicBoolean();
-        ConsoleLogger logger = mock( ConsoleLogger.class );
         final boolean[] verified = {false};
-        ForkClient client = new ForkClient( factory, notifiableTestStream, logger, printedErrorStream, 0 )
+        ForkClient client = new ForkClient( factory, notifiableTestStream, 0 )
         {
             @Override
             protected void stopOnNextTest()
@@ -404,7 +381,6 @@ public class ForkClientTest
 
     @Test
     public void shouldReceiveStdOut()
-            throws IOException
     {
         String cwd = System.getProperty( "user.dir" );
         File target = new File( cwd, "target" );
@@ -415,9 +391,7 @@ public class ForkClientTest
         when( factory.createReporter() )
                 .thenReturn( receiver );
         NotifiableTestStream notifiableTestStream = mock( NotifiableTestStream.class );
-        AtomicBoolean printedErrorStream = new AtomicBoolean();
-        ConsoleLogger logger = mock( ConsoleLogger.class );
-        ForkClient client = new ForkClient( factory, notifiableTestStream, logger, printedErrorStream, 0 );
+        ForkClient client = new ForkClient( factory, notifiableTestStream, 0 );
         client.consumeMultiLineContent( ":maven-surefire-event:std-out-stream:normal-run:UTF-8:bXNn\n" );
         verifyZeroInteractions( notifiableTestStream );
         verify( factory, times( 1 ) )
@@ -449,7 +423,6 @@ public class ForkClientTest
 
     @Test
     public void shouldReceiveStdOutNewLine()
-            throws IOException
     {
         String cwd = System.getProperty( "user.dir" );
         File target = new File( cwd, "target" );
@@ -460,9 +433,7 @@ public class ForkClientTest
         when( factory.createReporter() )
                 .thenReturn( receiver );
         NotifiableTestStream notifiableTestStream = mock( NotifiableTestStream.class );
-        AtomicBoolean printedErrorStream = new AtomicBoolean();
-        ConsoleLogger logger = mock( ConsoleLogger.class );
-        ForkClient client = new ForkClient( factory, notifiableTestStream, logger, printedErrorStream, 0 );
+        ForkClient client = new ForkClient( factory, notifiableTestStream, 0 );
         client.consumeMultiLineContent( ":maven-surefire-event:std-out-stream-new-line:normal-run:UTF-8:bXNn\n" );
         verifyZeroInteractions( notifiableTestStream );
         verify( factory, times( 1 ) )
@@ -494,7 +465,6 @@ public class ForkClientTest
 
     @Test
     public void shouldReceiveStdErr()
-            throws IOException
     {
         String cwd = System.getProperty( "user.dir" );
         File target = new File( cwd, "target" );
@@ -505,9 +475,7 @@ public class ForkClientTest
         when( factory.createReporter() )
                 .thenReturn( receiver );
         NotifiableTestStream notifiableTestStream = mock( NotifiableTestStream.class );
-        AtomicBoolean printedErrorStream = new AtomicBoolean();
-        ConsoleLogger logger = mock( ConsoleLogger.class );
-        ForkClient client = new ForkClient( factory, notifiableTestStream, logger, printedErrorStream, 0 );
+        ForkClient client = new ForkClient( factory, notifiableTestStream, 0 );
         client.consumeMultiLineContent( ":maven-surefire-event:std-err-stream:normal-run:UTF-8:bXNn\n" );
         verifyZeroInteractions( notifiableTestStream );
         verify( factory, times( 1 ) )
@@ -539,7 +507,6 @@ public class ForkClientTest
 
     @Test
     public void shouldReceiveStdErrNewLine()
-            throws IOException
     {
         String cwd = System.getProperty( "user.dir" );
         File target = new File( cwd, "target" );
@@ -550,9 +517,7 @@ public class ForkClientTest
         when( factory.createReporter() )
                 .thenReturn( receiver );
         NotifiableTestStream notifiableTestStream = mock( NotifiableTestStream.class );
-        AtomicBoolean printedErrorStream = new AtomicBoolean();
-        ConsoleLogger logger = mock( ConsoleLogger.class );
-        ForkClient client = new ForkClient( factory, notifiableTestStream, logger, printedErrorStream, 0 );
+        ForkClient client = new ForkClient( factory, notifiableTestStream, 0 );
         client.consumeMultiLineContent( ":maven-surefire-event:std-err-stream-new-line:normal-run:UTF-8:bXNn\n" );
         verifyZeroInteractions( notifiableTestStream );
         verify( factory, times( 1 ) )
@@ -584,7 +549,6 @@ public class ForkClientTest
 
     @Test
     public void shouldLogConsoleError()
-            throws IOException
     {
         String cwd = System.getProperty( "user.dir" );
         File target = new File( cwd, "target" );
@@ -595,9 +559,7 @@ public class ForkClientTest
         when( factory.createReporter() )
                 .thenReturn( receiver );
         NotifiableTestStream notifiableTestStream = mock( NotifiableTestStream.class );
-        AtomicBoolean printedErrorStream = new AtomicBoolean();
-        ConsoleLogger logger = mock( ConsoleLogger.class );
-        ForkClient client = new ForkClient( factory, notifiableTestStream, logger, printedErrorStream, 0 );
+        ForkClient client = new ForkClient( factory, notifiableTestStream, 0 );
         client.consumeMultiLineContent( ":maven-surefire-event:console-error-log:UTF-8:"
                 + encodeBase64String( "Listening for transport dt_socket at address:".getBytes( UTF_8 ) )
                 + ":-:-:-" );
@@ -635,7 +597,6 @@ public class ForkClientTest
 
     @Test
     public void shouldLogConsoleErrorWithStackTrace()
-            throws IOException
     {
         String cwd = System.getProperty( "user.dir" );
         File target = new File( cwd, "target" );
@@ -646,9 +607,7 @@ public class ForkClientTest
         when( factory.createReporter() )
                 .thenReturn( receiver );
         NotifiableTestStream notifiableTestStream = mock( NotifiableTestStream.class );
-        AtomicBoolean printedErrorStream = new AtomicBoolean();
-        ConsoleLogger logger = mock( ConsoleLogger.class );
-        ForkClient client = new ForkClient( factory, notifiableTestStream, logger, printedErrorStream, 0 );
+        ForkClient client = new ForkClient( factory, notifiableTestStream, 0 );
         client.consumeMultiLineContent( ":maven-surefire-event:console-error-log:UTF-8"
                 + ":" + encodeBase64String( "Listening for transport dt_socket at address:".getBytes( UTF_8 ) )
                 + ":" + encodeBase64String( "s1".getBytes( UTF_8 ) )
@@ -693,7 +652,6 @@ public class ForkClientTest
 
     @Test
     public void shouldLogConsoleWarning()
-            throws IOException
     {
         String cwd = System.getProperty( "user.dir" );
         File target = new File( cwd, "target" );
@@ -704,11 +662,7 @@ public class ForkClientTest
         when( factory.createReporter() )
                 .thenReturn( receiver );
         NotifiableTestStream notifiableTestStream = mock( NotifiableTestStream.class );
-        AtomicBoolean printedErrorStream = new AtomicBoolean();
-        ConsoleLogger logger = mock( ConsoleLogger.class );
-        when( logger.isWarnEnabled() )
-                .thenReturn( true );
-        ForkClient client = new ForkClient( factory, notifiableTestStream, logger, printedErrorStream, 0 );
+        ForkClient client = new ForkClient( factory, notifiableTestStream, 0 );
         client.consumeMultiLineContent( ":maven-surefire-event:console-warning-log:UTF-8:"
                 + encodeBase64String( "s1".getBytes( UTF_8 ) ) );
         verifyZeroInteractions( notifiableTestStream );
@@ -741,7 +695,6 @@ public class ForkClientTest
 
     @Test
     public void shouldLogConsoleDebug()
-            throws IOException
     {
         String cwd = System.getProperty( "user.dir" );
         File target = new File( cwd, "target" );
@@ -752,11 +705,7 @@ public class ForkClientTest
         when( factory.createReporter() )
                 .thenReturn( receiver );
         NotifiableTestStream notifiableTestStream = mock( NotifiableTestStream.class );
-        AtomicBoolean printedErrorStream = new AtomicBoolean();
-        ConsoleLogger logger = mock( ConsoleLogger.class );
-        when( logger.isDebugEnabled() )
-                .thenReturn( true );
-        ForkClient client = new ForkClient( factory, notifiableTestStream, logger, printedErrorStream, 0 );
+        ForkClient client = new ForkClient( factory, notifiableTestStream, 0 );
         client.consumeMultiLineContent( ":maven-surefire-event:console-debug-log:UTF-8:"
                 + encodeBase64String( "s1".getBytes( UTF_8 ) ) );
         verifyZeroInteractions( notifiableTestStream );
@@ -789,7 +738,6 @@ public class ForkClientTest
 
     @Test
     public void shouldLogConsoleInfo()
-            throws IOException
     {
         String cwd = System.getProperty( "user.dir" );
         File target = new File( cwd, "target" );
@@ -800,9 +748,7 @@ public class ForkClientTest
         when( factory.createReporter() )
                 .thenReturn( receiver );
         NotifiableTestStream notifiableTestStream = mock( NotifiableTestStream.class );
-        AtomicBoolean printedErrorStream = new AtomicBoolean();
-        ConsoleLogger logger = mock( ConsoleLogger.class );
-        ForkClient client = new ForkClient( factory, notifiableTestStream, logger, printedErrorStream, 0 );
+        ForkClient client = new ForkClient( factory, notifiableTestStream, 0 );
         client.consumeMultiLineContent( ":maven-surefire-event:console-info-log:UTF-8:"
                 + encodeBase64String( "s1".getBytes( UTF_8 ) ) );
         verifyZeroInteractions( notifiableTestStream );
@@ -835,7 +781,6 @@ public class ForkClientTest
 
     @Test
     public void shouldSendSystemProperty()
-            throws IOException
     {
         String cwd = System.getProperty( "user.dir" );
         File target = new File( cwd, "target" );
@@ -846,9 +791,7 @@ public class ForkClientTest
         when( factory.createReporter() )
                 .thenReturn( receiver );
         NotifiableTestStream notifiableTestStream = mock( NotifiableTestStream.class );
-        AtomicBoolean printedErrorStream = new AtomicBoolean();
-        ConsoleLogger logger = mock( ConsoleLogger.class );
-        ForkClient client = new ForkClient( factory, notifiableTestStream, logger, printedErrorStream, 0 );
+        ForkClient client = new ForkClient( factory, notifiableTestStream, 0 );
         client.consumeMultiLineContent( ":maven-surefire-event:sys-prop:normal-run:UTF-8:azE=:djE="
                 + encodeBase64String( "s1".getBytes( UTF_8 ) ) );
         verifyZeroInteractions( notifiableTestStream );
@@ -879,7 +822,6 @@ public class ForkClientTest
 
     @Test
     public void shouldSendTestsetStartingKilled()
-            throws IOException
     {
         String cwd = System.getProperty( "user.dir" );
         File target = new File( cwd, "target" );
@@ -890,9 +832,6 @@ public class ForkClientTest
         when( factory.createReporter() )
                 .thenReturn( receiver );
         NotifiableTestStream notifiableTestStream = mock( NotifiableTestStream.class );
-        AtomicBoolean printedErrorStream = new AtomicBoolean();
-        ConsoleLogger logger = mock( ConsoleLogger.class );
-
 
         final String exceptionMessage = "msg";
         final String encodedExceptionMsg = encodeBase64String( toArray( UTF_8.encode( exceptionMessage ) ) );
@@ -927,7 +866,7 @@ public class ForkClientTest
         String encodedGroup = encodeBase64String( toArray( UTF_8.encode( reportEntry.getGroup() ) ) );
         String encodedMessage = encodeBase64String( toArray( UTF_8.encode( reportEntry.getMessage() ) ) );
 
-        ForkClient client = new ForkClient( factory, notifiableTestStream, logger, printedErrorStream, 0 );
+        ForkClient client = new ForkClient( factory, notifiableTestStream, 0 );
         client.consumeMultiLineContent( ":maven-surefire-event:testset-starting:normal-run:UTF-8:"
                 + encodedSourceName
                 + ":"
@@ -1021,9 +960,6 @@ public class ForkClientTest
         when( factory.createReporter() )
                 .thenReturn( receiver );
         NotifiableTestStream notifiableTestStream = mock( NotifiableTestStream.class );
-        AtomicBoolean printedErrorStream = new AtomicBoolean();
-        ConsoleLogger logger = mock( ConsoleLogger.class );
-
 
         final String exceptionMessage = "msg";
         final String encodedExceptionMsg = encodeBase64String( toArray( UTF_8.encode( exceptionMessage ) ) );
@@ -1062,7 +998,7 @@ public class ForkClientTest
         String encodedGroup = encodeBase64String( toArray( UTF_8.encode( reportEntry.getGroup() ) ) );
         String encodedMessage = encodeBase64String( toArray( UTF_8.encode( reportEntry.getMessage() ) ) );
 
-        ForkClient client = new ForkClient( factory, notifiableTestStream, logger, printedErrorStream, 0 );
+        ForkClient client = new ForkClient( factory, notifiableTestStream, 0 );
         client.consumeMultiLineContent( ":maven-surefire-event:testset-starting:normal-run:UTF-8:"
                 + encodedSourceName
                 + ":"
@@ -1145,7 +1081,6 @@ public class ForkClientTest
 
     @Test
     public void shouldSendTestsetCompleted()
-            throws IOException
     {
         String cwd = System.getProperty( "user.dir" );
         File target = new File( cwd, "target" );
@@ -1156,9 +1091,6 @@ public class ForkClientTest
         when( factory.createReporter() )
                 .thenReturn( receiver );
         NotifiableTestStream notifiableTestStream = mock( NotifiableTestStream.class );
-        AtomicBoolean printedErrorStream = new AtomicBoolean();
-        ConsoleLogger logger = mock( ConsoleLogger.class );
-
 
         final String exceptionMessage = "msg";
         final String encodedExceptionMsg = encodeBase64String( toArray( UTF_8.encode( exceptionMessage ) ) );
@@ -1193,7 +1125,7 @@ public class ForkClientTest
         String encodedGroup = encodeBase64String( toArray( UTF_8.encode( reportEntry.getGroup() ) ) );
         String encodedMessage = encodeBase64String( toArray( UTF_8.encode( reportEntry.getMessage() ) ) );
 
-        ForkClient client = new ForkClient( factory, notifiableTestStream, logger, printedErrorStream, 0 );
+        ForkClient client = new ForkClient( factory, notifiableTestStream, 0 );
         client.consumeMultiLineContent( ":maven-surefire-event:testset-completed:normal-run:UTF-8:"
                 + encodedSourceName
                 + ":"
@@ -1274,7 +1206,6 @@ public class ForkClientTest
 
     @Test
     public void shouldSendTestStarting()
-            throws IOException
     {
         String cwd = System.getProperty( "user.dir" );
         File target = new File( cwd, "target" );
@@ -1285,9 +1216,6 @@ public class ForkClientTest
         when( factory.createReporter() )
                 .thenReturn( receiver );
         NotifiableTestStream notifiableTestStream = mock( NotifiableTestStream.class );
-        AtomicBoolean printedErrorStream = new AtomicBoolean();
-        ConsoleLogger logger = mock( ConsoleLogger.class );
-
 
         final String exceptionMessage = "msg";
         final String encodedExceptionMsg = encodeBase64String( toArray( UTF_8.encode( exceptionMessage ) ) );
@@ -1322,7 +1250,7 @@ public class ForkClientTest
         String encodedGroup = encodeBase64String( toArray( UTF_8.encode( reportEntry.getGroup() ) ) );
         String encodedMessage = encodeBase64String( toArray( UTF_8.encode( reportEntry.getMessage() ) ) );
 
-        ForkClient client = new ForkClient( factory, notifiableTestStream, logger, printedErrorStream, 0 );
+        ForkClient client = new ForkClient( factory, notifiableTestStream, 0 );
         client.consumeMultiLineContent( ":maven-surefire-event:test-starting:normal-run:UTF-8:"
                 + encodedSourceName
                 + ":"
@@ -1404,7 +1332,6 @@ public class ForkClientTest
 
     @Test
     public void shouldSendTestSucceeded()
-            throws IOException
     {
         String cwd = System.getProperty( "user.dir" );
         File target = new File( cwd, "target" );
@@ -1415,9 +1342,6 @@ public class ForkClientTest
         when( factory.createReporter() )
                 .thenReturn( receiver );
         NotifiableTestStream notifiableTestStream = mock( NotifiableTestStream.class );
-        AtomicBoolean printedErrorStream = new AtomicBoolean();
-        ConsoleLogger logger = mock( ConsoleLogger.class );
-
 
         final String exceptionMessage = "msg";
         final String encodedExceptionMsg = encodeBase64String( toArray( UTF_8.encode( exceptionMessage ) ) );
@@ -1452,7 +1376,7 @@ public class ForkClientTest
         String encodedGroup = encodeBase64String( toArray( UTF_8.encode( reportEntry.getGroup() ) ) );
         String encodedMessage = encodeBase64String( toArray( UTF_8.encode( reportEntry.getMessage() ) ) );
 
-        ForkClient client = new ForkClient( factory, notifiableTestStream, logger, printedErrorStream, 0 );
+        ForkClient client = new ForkClient( factory, notifiableTestStream,  0 );
 
         client.consumeMultiLineContent( ":maven-surefire-event:test-starting:normal-run:UTF-8:"
                 + encodedSourceName
@@ -1546,7 +1470,6 @@ public class ForkClientTest
 
     @Test
     public void shouldSendTestFailed()
-            throws IOException
     {
         String cwd = System.getProperty( "user.dir" );
         File target = new File( cwd, "target" );
@@ -1557,9 +1480,6 @@ public class ForkClientTest
         when( factory.createReporter() )
                 .thenReturn( receiver );
         NotifiableTestStream notifiableTestStream = mock( NotifiableTestStream.class );
-        AtomicBoolean printedErrorStream = new AtomicBoolean();
-        ConsoleLogger logger = mock( ConsoleLogger.class );
-
 
         final String exceptionMessage = "msg";
         final String encodedExceptionMsg = encodeBase64String( toArray( UTF_8.encode( exceptionMessage ) ) );
@@ -1594,7 +1514,7 @@ public class ForkClientTest
         String encodedGroup = encodeBase64String( toArray( UTF_8.encode( reportEntry.getGroup() ) ) );
         String encodedMessage = encodeBase64String( toArray( UTF_8.encode( reportEntry.getMessage() ) ) );
 
-        ForkClient client = new ForkClient( factory, notifiableTestStream, logger, printedErrorStream, 0 );
+        ForkClient client = new ForkClient( factory, notifiableTestStream, 0 );
 
         client.consumeMultiLineContent( ":maven-surefire-event:test-starting:normal-run:UTF-8:"
                 + encodedSourceName
@@ -1692,7 +1612,6 @@ public class ForkClientTest
 
     @Test
     public void shouldSendTestSkipped()
-            throws IOException
     {
         String cwd = System.getProperty( "user.dir" );
         File target = new File( cwd, "target" );
@@ -1703,9 +1622,6 @@ public class ForkClientTest
         when( factory.createReporter() )
                 .thenReturn( receiver );
         NotifiableTestStream notifiableTestStream = mock( NotifiableTestStream.class );
-        AtomicBoolean printedErrorStream = new AtomicBoolean();
-        ConsoleLogger logger = mock( ConsoleLogger.class );
-
 
         final String exceptionMessage = "msg";
         final String encodedExceptionMsg = encodeBase64String( toArray( UTF_8.encode( exceptionMessage ) ) );
@@ -1740,7 +1656,7 @@ public class ForkClientTest
         String encodedGroup = encodeBase64String( toArray( UTF_8.encode( reportEntry.getGroup() ) ) );
         String encodedMessage = encodeBase64String( toArray( UTF_8.encode( reportEntry.getMessage() ) ) );
 
-        ForkClient client = new ForkClient( factory, notifiableTestStream, logger, printedErrorStream, 0 );
+        ForkClient client = new ForkClient( factory, notifiableTestStream, 0 );
 
         client.consumeMultiLineContent( ":maven-surefire-event:test-starting:normal-run:UTF-8:"
                 + encodedSourceName
@@ -1838,7 +1754,6 @@ public class ForkClientTest
 
     @Test
     public void shouldSendTestError()
-            throws IOException
     {
         String cwd = System.getProperty( "user.dir" );
         File target = new File( cwd, "target" );
@@ -1849,9 +1764,6 @@ public class ForkClientTest
         when( factory.createReporter() )
                 .thenReturn( receiver );
         NotifiableTestStream notifiableTestStream = mock( NotifiableTestStream.class );
-        AtomicBoolean printedErrorStream = new AtomicBoolean();
-        ConsoleLogger logger = mock( ConsoleLogger.class );
-
 
         final String exceptionMessage = "msg";
         final String encodedExceptionMsg = encodeBase64String( toArray( UTF_8.encode( exceptionMessage ) ) );
@@ -1888,7 +1800,7 @@ public class ForkClientTest
         String encodedGroup = encodeBase64String( toArray( UTF_8.encode( reportEntry.getGroup() ) ) );
         String encodedMessage = encodeBase64String( toArray( UTF_8.encode( reportEntry.getMessage() ) ) );
 
-        ForkClient client = new ForkClient( factory, notifiableTestStream, logger, printedErrorStream, 0 );
+        ForkClient client = new ForkClient( factory, notifiableTestStream, 0 );
 
         client.consumeMultiLineContent( ":maven-surefire-event:test-starting:normal-run:UTF-8:"
                 + encodedSourceName
@@ -1984,7 +1896,6 @@ public class ForkClientTest
 
     @Test
     public void shouldSendTestAssumptionFailure()
-            throws IOException
     {
         String cwd = System.getProperty( "user.dir" );
         File target = new File( cwd, "target" );
@@ -1995,9 +1906,6 @@ public class ForkClientTest
         when( factory.createReporter() )
                 .thenReturn( receiver );
         NotifiableTestStream notifiableTestStream = mock( NotifiableTestStream.class );
-        AtomicBoolean printedErrorStream = new AtomicBoolean();
-        ConsoleLogger logger = mock( ConsoleLogger.class );
-
 
         final String exceptionMessage = "msg";
         final String encodedExceptionMsg = encodeBase64String( toArray( UTF_8.encode( exceptionMessage ) ) );
@@ -2034,7 +1942,7 @@ public class ForkClientTest
         String encodedGroup = encodeBase64String( toArray( UTF_8.encode( reportEntry.getGroup() ) ) );
         String encodedMessage = encodeBase64String( toArray( UTF_8.encode( reportEntry.getMessage() ) ) );
 
-        ForkClient client = new ForkClient( factory, notifiableTestStream, logger, printedErrorStream, 0 );
+        ForkClient client = new ForkClient( factory, notifiableTestStream, 0 );
 
         client.consumeMultiLineContent( ":maven-surefire-event:test-starting:normal-run:UTF-8:"
                 + encodedSourceName
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedChannelDecoderTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedProcessEventObserverTest.java
similarity index 80%
rename from maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedChannelDecoderTest.java
rename to maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedProcessEventObserverTest.java
index cd1473d..760df6b 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedChannelDecoderTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedProcessEventObserverTest.java
@@ -36,6 +36,7 @@ import org.junit.experimental.theories.Theory;
 import org.junit.rules.ExpectedException;
 import org.junit.runner.RunWith;
 
+import javax.annotation.Nonnull;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.LineNumberReader;
@@ -46,30 +47,26 @@ import java.nio.charset.Charset;
 import java.util.Arrays;
 import java.util.Map;
 
+import static java.nio.channels.Channels.newChannel;
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.apache.commons.codec.binary.Base64.encodeBase64String;
-import static org.apache.maven.plugin.surefire.booterclient.output.ForkedChannelDecoder.toReportEntry;
+import static org.apache.maven.plugin.surefire.booterclient.output.ForkedProcessEventObserver.toReportEntry;
 import static org.apache.maven.surefire.report.RunMode.NORMAL_RUN;
 import static org.fest.assertions.Assertions.assertThat;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.junit.rules.ExpectedException.none;
-import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.nullable;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 
 /**
- * Test for {@link ForkedChannelDecoder}.
+ * Test for {@link ForkedProcessEventObserver}.
  *
  * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
  * @since 3.0.0-M4
  */
 @RunWith( Enclosed.class )
-public class ForkedChannelDecoderTest
+public class ForkedProcessEventObserverTest
 {
     /**
      *
@@ -82,10 +79,10 @@ public class ForkedChannelDecoderTest
         @Test
         public void shouldBeFailSafe()
         {
-            assertThat( ForkedChannelDecoder.decode( null, UTF_8 ) ).isNull();
-            assertThat( ForkedChannelDecoder.decode( "-", UTF_8 ) ).isNull();
-            assertThat( ForkedChannelDecoder.decodeToInteger( null ) ).isNull();
-            assertThat( ForkedChannelDecoder.decodeToInteger( "-" ) ).isNull();
+            assertThat( ForkedProcessEventObserver.decode( null, UTF_8 ) ).isNull();
+            assertThat( ForkedProcessEventObserver.decode( "-", UTF_8 ) ).isNull();
+            assertThat( ForkedProcessEventObserver.decodeToInteger( null ) ).isNull();
+            assertThat( ForkedProcessEventObserver.decodeToInteger( "-" ) ).isNull();
         }
 
         @Test
@@ -93,18 +90,16 @@ public class ForkedChannelDecoderTest
         public void shouldHaveSystemProperty() throws IOException
         {
             Stream out = Stream.newStream();
-            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( out );
+            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
             encoder.sendSystemProperties( ObjectUtils.systemProps() );
 
-            ForkedChannelDecoder decoder = new ForkedChannelDecoder();
+            ForkedProcessEventObserver decoder = new ForkedProcessEventObserver();
             decoder.setSystemPropertiesListener( new PropertyEventAssertionListener() );
             LineNumberReader reader = out.newReader( UTF_8 );
-            AssertionErrorHandler errorHandler = mock( AssertionErrorHandler.class );
             for ( String line; ( line = reader.readLine() ) != null; )
             {
-                decoder.handleEvent( line, errorHandler );
+                decoder.handleEvent( line );
             }
-            verifyZeroInteractions( errorHandler );
             assertThat( reader.getLineNumber() ).isPositive();
         }
 
@@ -259,17 +254,15 @@ public class ForkedChannelDecoderTest
         public void shouldSendByeEvent() throws IOException
         {
             Stream out = Stream.newStream();
-            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( out );
+            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
             encoder.bye();
             String read = new String( out.toByteArray(), UTF_8 );
             assertThat( read )
                     .isEqualTo( ":maven-surefire-event:bye\n" );
             LineNumberReader lines = out.newReader( UTF_8 );
-            ForkedChannelDecoder decoder = new ForkedChannelDecoder();
+            ForkedProcessEventObserver decoder = new ForkedProcessEventObserver();
             decoder.setByeListener( new EventAssertionListener() );
-            AssertionErrorHandler errorHandler = mock( AssertionErrorHandler.class );
-            decoder.handleEvent( lines.readLine(), errorHandler );
-            verifyZeroInteractions( errorHandler );
+            decoder.handleEvent( lines.readLine() );
             assertThat( lines.readLine() )
                     .isNull();
         }
@@ -277,19 +270,16 @@ public class ForkedChannelDecoderTest
         @Test
         public void shouldSendStopOnNextTestEvent() throws IOException
         {
-
             Stream out = Stream.newStream();
-            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( out );
+            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
             encoder.stopOnNextTest();
             String read = new String( out.toByteArray(), UTF_8 );
             assertThat( read )
                     .isEqualTo( ":maven-surefire-event:stop-on-next-test\n" );
             LineNumberReader lines = out.newReader( UTF_8 );
-            ForkedChannelDecoder decoder = new ForkedChannelDecoder();
+            ForkedProcessEventObserver decoder = new ForkedProcessEventObserver();
             decoder.setStopOnNextTestListener( new EventAssertionListener() );
-            AssertionErrorHandler errorHandler = mock( AssertionErrorHandler.class );
-            decoder.handleEvent( lines.readLine(), errorHandler );
-            verifyZeroInteractions( errorHandler );
+            decoder.handleEvent( lines.readLine() );
             assertThat( lines.readLine() )
                     .isNull();
         }
@@ -320,15 +310,14 @@ public class ForkedChannelDecoderTest
             when( reportEntry.getStackTraceWriter() ).thenReturn( stackTraceWriter );
 
             Stream out = Stream.newStream();
-            LegacyMasterProcessChannelEncoder forkedChannelEncoder = new LegacyMasterProcessChannelEncoder( out );
+            LegacyMasterProcessChannelEncoder forkedChannelEncoder =
+                new LegacyMasterProcessChannelEncoder( newChannel( out ) );
             forkedChannelEncoder.testFailed( reportEntry, true );
             String line = new String( out.toByteArray(), UTF_8 );
 
-            ForkedChannelDecoder decoder = new ForkedChannelDecoder();
+            ForkedProcessEventObserver decoder = new ForkedProcessEventObserver();
             decoder.setTestFailedListener( new ReportEventAssertionListener( reportEntry ) );
-            AssertionErrorHandler errorHandler = mock( AssertionErrorHandler.class );
-            decoder.handleEvent( line, errorHandler );
-            verifyZeroInteractions( errorHandler );
+            decoder.handleEvent( line );
         }
 
         @Test
@@ -336,17 +325,15 @@ public class ForkedChannelDecoderTest
         {
 
             Stream out = Stream.newStream();
-            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( out );
+            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
             encoder.acquireNextTest();
             String read = new String( out.toByteArray(), UTF_8 );
             assertThat( read )
                     .isEqualTo( ":maven-surefire-event:next-test\n" );
             LineNumberReader lines = out.newReader( UTF_8 );
-            ForkedChannelDecoder decoder = new ForkedChannelDecoder();
+            ForkedProcessEventObserver decoder = new ForkedProcessEventObserver();
             decoder.setAcquireNextTestListener( new EventAssertionListener() );
-            AssertionErrorHandler errorHandler = mock( AssertionErrorHandler.class );
-            decoder.handleEvent( lines.readLine(), errorHandler );
-            verifyZeroInteractions( errorHandler );
+            decoder.handleEvent( lines.readLine() );
             assertThat( lines.readLine() )
                     .isNull();
         }
@@ -356,15 +343,13 @@ public class ForkedChannelDecoderTest
         {
             Stream out = Stream.newStream();
 
-            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( out );
+            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
             encoder.consoleInfoLog( "msg" );
 
             LineNumberReader lines = out.newReader( UTF_8 );
-            ForkedChannelDecoder decoder = new ForkedChannelDecoder();
+            ForkedProcessEventObserver decoder = new ForkedProcessEventObserver();
             decoder.setConsoleInfoListener( new StringEventAssertionListener( "msg" ) );
-            AssertionErrorHandler errorHandler = mock( AssertionErrorHandler.class );
-            decoder.handleEvent( lines.readLine(), errorHandler );
-            verifyZeroInteractions( errorHandler );
+            decoder.handleEvent( lines.readLine() );
             assertThat( lines.readLine() )
                     .isNull();
         }
@@ -374,15 +359,13 @@ public class ForkedChannelDecoderTest
         {
             Stream out = Stream.newStream();
 
-            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( out );
+            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
             encoder.consoleErrorLog( "msg" );
 
             LineNumberReader lines = out.newReader( UTF_8 );
-            ForkedChannelDecoder decoder = new ForkedChannelDecoder();
+            ForkedProcessEventObserver decoder = new ForkedProcessEventObserver();
             decoder.setConsoleErrorListener( new StackTraceEventListener( "msg", null, null ) );
-            AssertionErrorHandler errorHandler = mock( AssertionErrorHandler.class );
-            decoder.handleEvent( lines.readLine(), errorHandler );
-            verifyZeroInteractions( errorHandler );
+            decoder.handleEvent( lines.readLine() );
             assertThat( lines.readLine() )
                     .isNull();
         }
@@ -394,16 +377,14 @@ public class ForkedChannelDecoderTest
 
             Stream out = Stream.newStream();
 
-            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( out );
+            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
             encoder.consoleErrorLog( t );
 
             LineNumberReader lines = out.newReader( UTF_8 );
-            ForkedChannelDecoder decoder = new ForkedChannelDecoder();
+            ForkedProcessEventObserver decoder = new ForkedProcessEventObserver();
             String stackTrace = ConsoleLoggerUtils.toString( t );
             decoder.setConsoleErrorListener( new StackTraceEventListener( "msg", null, stackTrace ) );
-            AssertionErrorHandler errorHandler = mock( AssertionErrorHandler.class );
-            decoder.handleEvent( lines.readLine(), errorHandler );
-            verifyZeroInteractions( errorHandler );
+            decoder.handleEvent( lines.readLine() );
             assertThat( lines.readLine() )
                     .isNull();
         }
@@ -413,16 +394,14 @@ public class ForkedChannelDecoderTest
         {
             Stream out = Stream.newStream();
 
-            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( out );
+            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
             StackTraceWriter stackTraceWriter = new DeserializedStacktraceWriter( "1", "2", "3" );
             encoder.consoleErrorLog( stackTraceWriter, false );
 
             LineNumberReader lines = out.newReader( UTF_8 );
-            ForkedChannelDecoder decoder = new ForkedChannelDecoder();
+            ForkedProcessEventObserver decoder = new ForkedProcessEventObserver();
             decoder.setConsoleErrorListener( new StackTraceEventListener( "1", "2", "3" ) );
-            AssertionErrorHandler errorHandler = mock( AssertionErrorHandler.class );
-            decoder.handleEvent( lines.readLine(), errorHandler );
-            verifyZeroInteractions( errorHandler );
+            decoder.handleEvent( lines.readLine() );
             assertThat( lines.readLine() )
                     .isNull();
         }
@@ -432,15 +411,13 @@ public class ForkedChannelDecoderTest
         {
             Stream out = Stream.newStream();
 
-            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( out );
+            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
             encoder.consoleDebugLog( "msg" );
 
             LineNumberReader lines = out.newReader( UTF_8 );
-            ForkedChannelDecoder decoder = new ForkedChannelDecoder();
+            ForkedProcessEventObserver decoder = new ForkedProcessEventObserver();
             decoder.setConsoleDebugListener( new StringEventAssertionListener( "msg" ) );
-            AssertionErrorHandler errorHandler = mock( AssertionErrorHandler.class );
-            decoder.handleEvent( lines.readLine(), errorHandler );
-            verifyZeroInteractions( errorHandler );
+            decoder.handleEvent( lines.readLine() );
             assertThat( lines.readLine() )
                     .isNull();
         }
@@ -450,15 +427,13 @@ public class ForkedChannelDecoderTest
         {
             Stream out = Stream.newStream();
 
-            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( out );
+            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
             encoder.consoleWarningLog( "msg" );
 
             LineNumberReader lines = out.newReader( UTF_8 );
-            ForkedChannelDecoder decoder = new ForkedChannelDecoder();
+            ForkedProcessEventObserver decoder = new ForkedProcessEventObserver();
             decoder.setConsoleWarningListener( new StringEventAssertionListener( "msg" ) );
-            AssertionErrorHandler errorHandler = mock( AssertionErrorHandler.class );
-            decoder.handleEvent( lines.readLine(), errorHandler );
-            verifyZeroInteractions( errorHandler );
+            decoder.handleEvent( lines.readLine() );
             assertThat( lines.readLine() )
                     .isNull();
         }
@@ -467,15 +442,13 @@ public class ForkedChannelDecoderTest
         public void testStdOutStream() throws IOException
         {
             Stream out = Stream.newStream();
-            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( out );
+            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
             encoder.stdOut( "msg", false );
 
             LineNumberReader lines = out.newReader( UTF_8 );
-            ForkedChannelDecoder decoder = new ForkedChannelDecoder();
+            ForkedProcessEventObserver decoder = new ForkedProcessEventObserver();
             decoder.setStdOutListener( new StandardOutErrEventAssertionListener( NORMAL_RUN, "msg", false ) );
-            AssertionErrorHandler errorHandler = mock( AssertionErrorHandler.class );
-            decoder.handleEvent( lines.readLine(), errorHandler );
-            verifyZeroInteractions( errorHandler );
+            decoder.handleEvent( lines.readLine() );
             assertThat( lines.readLine() )
                     .isNull();
         }
@@ -484,15 +457,13 @@ public class ForkedChannelDecoderTest
         public void testStdOutStreamPrint() throws IOException
         {
             Stream out = Stream.newStream();
-            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( out );
+            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
             encoder.stdOut( "", false );
 
             LineNumberReader lines = out.newReader( UTF_8 );
-            ForkedChannelDecoder decoder = new ForkedChannelDecoder();
+            ForkedProcessEventObserver decoder = new ForkedProcessEventObserver();
             decoder.setStdOutListener( new StandardOutErrEventAssertionListener( NORMAL_RUN, "", false ) );
-            AssertionErrorHandler errorHandler = mock( AssertionErrorHandler.class );
-            decoder.handleEvent( lines.readLine(), errorHandler );
-            verifyZeroInteractions( errorHandler );
+            decoder.handleEvent( lines.readLine() );
             assertThat( lines.readLine() )
                     .isNull();
         }
@@ -501,15 +472,13 @@ public class ForkedChannelDecoderTest
         public void testStdOutStreamPrintWithNull() throws IOException
         {
             Stream out = Stream.newStream();
-            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( out );
+            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
             encoder.stdOut( null, false );
 
             LineNumberReader lines = out.newReader( UTF_8 );
-            ForkedChannelDecoder decoder = new ForkedChannelDecoder();
+            ForkedProcessEventObserver decoder = new ForkedProcessEventObserver();
             decoder.setStdOutListener( new StandardOutErrEventAssertionListener( NORMAL_RUN, null, false ) );
-            AssertionErrorHandler errorHandler = mock( AssertionErrorHandler.class );
-            decoder.handleEvent( lines.readLine(), errorHandler );
-            verifyZeroInteractions( errorHandler );
+            decoder.handleEvent( lines.readLine() );
             assertThat( lines.readLine() )
                     .isNull();
         }
@@ -518,15 +487,13 @@ public class ForkedChannelDecoderTest
         public void testStdOutStreamPrintln() throws IOException
         {
             Stream out = Stream.newStream();
-            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( out );
+            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
             encoder.stdOut( "", true );
 
             LineNumberReader lines = out.newReader( UTF_8 );
-            ForkedChannelDecoder decoder = new ForkedChannelDecoder();
+            ForkedProcessEventObserver decoder = new ForkedProcessEventObserver();
             decoder.setStdOutListener( new StandardOutErrEventAssertionListener( NORMAL_RUN, "", true ) );
-            AssertionErrorHandler errorHandler = mock( AssertionErrorHandler.class );
-            decoder.handleEvent( lines.readLine(), errorHandler );
-            verifyZeroInteractions( errorHandler );
+            decoder.handleEvent( lines.readLine() );
             assertThat( lines.readLine() )
                     .isNull();
         }
@@ -535,15 +502,13 @@ public class ForkedChannelDecoderTest
         public void testStdOutStreamPrintlnWithNull() throws IOException
         {
             Stream out = Stream.newStream();
-            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( out );
+            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
             encoder.stdOut( null, true );
 
             LineNumberReader lines = out.newReader( UTF_8 );
-            ForkedChannelDecoder decoder = new ForkedChannelDecoder();
+            ForkedProcessEventObserver decoder = new ForkedProcessEventObserver();
             decoder.setStdOutListener( new StandardOutErrEventAssertionListener( NORMAL_RUN, null, true ) );
-            AssertionErrorHandler errorHandler = mock( AssertionErrorHandler.class );
-            decoder.handleEvent( lines.readLine(), errorHandler );
-            verifyZeroInteractions( errorHandler );
+            decoder.handleEvent( lines.readLine() );
             assertThat( lines.readLine() )
                     .isNull();
         }
@@ -552,15 +517,13 @@ public class ForkedChannelDecoderTest
         public void testStdErrStream() throws IOException
         {
             Stream out = Stream.newStream();
-            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( out );
+            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
             encoder.stdErr( "msg", false );
 
             LineNumberReader lines = out.newReader( UTF_8 );
-            ForkedChannelDecoder decoder = new ForkedChannelDecoder();
+            ForkedProcessEventObserver decoder = new ForkedProcessEventObserver();
             decoder.setStdErrListener( new StandardOutErrEventAssertionListener( NORMAL_RUN, "msg", false ) );
-            AssertionErrorHandler errorHandler = mock( AssertionErrorHandler.class );
-            decoder.handleEvent( lines.readLine(), errorHandler );
-            verifyZeroInteractions( errorHandler );
+            decoder.handleEvent( lines.readLine() );
             assertThat( lines.readLine() )
                     .isNull();
         }
@@ -569,44 +532,36 @@ public class ForkedChannelDecoderTest
         public void shouldCountSameNumberOfSystemProperties() throws IOException
         {
             Stream out = Stream.newStream();
-            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( out );
+            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
             encoder.sendSystemProperties( ObjectUtils.systemProps() );
 
             LineNumberReader lines = out.newReader( UTF_8 );
-            ForkedChannelDecoder decoder = new ForkedChannelDecoder();
+            ForkedProcessEventObserver decoder = new ForkedProcessEventObserver();
             decoder.setSystemPropertiesListener( new PropertyEventAssertionListener() );
-            AssertionErrorHandler errorHandler = mock( AssertionErrorHandler.class );
-            decoder.handleEvent( lines.readLine(), errorHandler );
-            verifyZeroInteractions( errorHandler );
+            decoder.handleEvent( lines.readLine() );
         }
 
         @Test
         public void shouldHandleErrorAfterNullLine()
         {
-            ForkedChannelDecoder decoder = new ForkedChannelDecoder();
+            ForkedProcessEventObserver decoder = new ForkedProcessEventObserver();
             decoder.setSystemPropertiesListener( new PropertyEventAssertionListener() );
-            AssertionErrorHandler errorHandler = mock( AssertionErrorHandler.class );
-            decoder.handleEvent( null, errorHandler );
-            verify( errorHandler, times( 1 ) )
-                    .handledError( nullable( String.class ), nullable( Throwable.class ) );
+            decoder.handleEvent( null );
         }
 
         @Test
         public void shouldHandleErrorAfterUnknownOperation()
         {
-            ForkedChannelDecoder decoder = new ForkedChannelDecoder();
+            ForkedProcessEventObserver decoder = new ForkedProcessEventObserver();
             decoder.setSystemPropertiesListener( new PropertyEventAssertionListener() );
-            AssertionErrorHandler errorHandler = mock( AssertionErrorHandler.class );
-            decoder.handleEvent( ":maven-surefire-event:abnormal-run:-", errorHandler );
-            verify( errorHandler, times( 1 ) )
-                    .handledError( eq( ":maven-surefire-event:abnormal-run:-" ), nullable( Throwable.class ) );
+            decoder.handleEvent( ":maven-surefire-event:abnormal-run:-" );
         }
 
         @Test
         public void shouldHandleExit() throws IOException
         {
             Stream out = Stream.newStream();
-            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( out );
+            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
             StackTraceWriter stackTraceWriter = mock( StackTraceWriter.class );
             when( stackTraceWriter.getThrowable() ).thenReturn( new SafeThrowable( "1" ) );
             when( stackTraceWriter.smartTrimmedStackTrace() ).thenReturn( "2" );
@@ -615,20 +570,18 @@ public class ForkedChannelDecoderTest
             encoder.sendExitEvent( stackTraceWriter, false );
 
             LineNumberReader lines = out.newReader( UTF_8 );
-            ForkedChannelDecoder decoder = new ForkedChannelDecoder();
+            ForkedProcessEventObserver decoder = new ForkedProcessEventObserver();
             decoder.setExitErrorEventListener( new ForkedProcessExitErrorListener()
             {
                 @Override
-                public void handle( String exceptionMessage, String smartTrimmedStackTrace, String stackTrace )
+                public void handle( StackTraceWriter stackTrace )
                 {
-                    assertThat( exceptionMessage ).isEqualTo( "1" );
-                    assertThat( smartTrimmedStackTrace ).isEqualTo( "2" );
-                    assertThat( stackTrace ).isEqualTo( "3" );
+                    assertThat( stackTrace.getThrowable().getMessage() ).isEqualTo( "1" );
+                    assertThat( stackTrace.smartTrimmedStackTrace() ).isEqualTo( "2" );
+                    assertThat( stackTrace.writeTraceToString() ).isEqualTo( "3" );
                 }
             } );
-            AssertionErrorHandler errorHandler = mock( AssertionErrorHandler.class );
-            decoder.handleEvent( lines.readLine(), errorHandler );
-            verifyZeroInteractions( errorHandler );
+            decoder.handleEvent( lines.readLine() );
         }
     }
 
@@ -714,37 +667,23 @@ public class ForkedChannelDecoderTest
 
             Stream out = Stream.newStream();
 
-            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( out );
+            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
 
             LegacyMasterProcessChannelEncoder.class.getMethod( operation[0], ReportEntry.class, boolean.class )
                     .invoke( encoder, reportEntry, trim );
 
-            ForkedChannelDecoder decoder = new ForkedChannelDecoder();
+            ForkedProcessEventObserver decoder = new ForkedProcessEventObserver();
 
-            ForkedChannelDecoder.class.getMethod( operation[1], ForkedProcessReportEventListener.class )
+            ForkedProcessEventObserver.class.getMethod( operation[1], ForkedProcessReportEventListener.class )
                     .invoke( decoder, new ReportEventAssertionListener( reportEntry ) );
 
-            AssertionErrorHandler errorHandler = mock( AssertionErrorHandler.class );
-            decoder.handleEvent( out.newReader( UTF_8 ).readLine(), errorHandler );
-            verifyZeroInteractions( errorHandler );
-        }
-    }
-
-    private static class AssertionErrorHandler implements ForkedChannelDecoderErrorHandler
-    {
-        public void handledError( String line, Throwable e )
-        {
-            if ( e != null )
-            {
-                e.printStackTrace();
-            }
-            fail( line + ( e == null ? "" : "\n" + e.getLocalizedMessage() ) );
+            decoder.handleEvent( out.newReader( UTF_8 ).readLine() );
         }
     }
 
     private static class PropertyEventAssertionListener implements ForkedProcessPropertyEventListener
     {
-        private final Map sysProps = System.getProperties();
+        private final Map<?, ?> sysProps = System.getProperties();
 
         public void handle( RunMode runMode, String key, String value )
         {
@@ -791,7 +730,7 @@ public class ForkedChannelDecoderTest
         }
 
         @Override
-        public void handle( String msg, String smartStackTrace, String stackTrace )
+        public void handle( @Nonnull StackTraceWriter stackTrace )
         {
             assertThat( msg )
                     .isEqualTo( this.msg );
@@ -830,7 +769,7 @@ public class ForkedChannelDecoderTest
         }
     }
 
-    private static class ReportEventAssertionListener implements ForkedProcessReportEventListener
+    private static class ReportEventAssertionListener implements ForkedProcessReportEventListener<ReportEntry>
     {
         private final ReportEntry reportEntry;
 
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/surefire/JUnit4SuiteTest.java b/maven-surefire-common/src/test/java/org/apache/maven/surefire/JUnit4SuiteTest.java
index 14a28fe..1e146c0 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/surefire/JUnit4SuiteTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/surefire/JUnit4SuiteTest.java
@@ -40,7 +40,7 @@ import org.apache.maven.plugin.surefire.booterclient.ModularClasspathForkConfigu
 import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.TestLessInputStreamBuilderTest;
 import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.TestProvidingInputStreamTest;
 import org.apache.maven.plugin.surefire.booterclient.output.ForkClientTest;
-import org.apache.maven.plugin.surefire.booterclient.output.ForkedChannelDecoderTest;
+import org.apache.maven.plugin.surefire.booterclient.output.ForkedProcessEventObserverTest;
 import org.apache.maven.plugin.surefire.report.DefaultReporterFactoryTest;
 import org.apache.maven.plugin.surefire.report.StatelessXmlReporterTest;
 import org.apache.maven.plugin.surefire.report.TestSetStatsTest;
@@ -98,7 +98,7 @@ public class JUnit4SuiteTest extends TestCase
         suite.addTest( new JUnit4TestAdapter( ScannerUtilTest.class ) );
         suite.addTest( new JUnit4TestAdapter( MojoMocklessTest.class ) );
         suite.addTest( new JUnit4TestAdapter( ForkClientTest.class ) );
-        suite.addTest( new JUnit4TestAdapter( ForkedChannelDecoderTest.class ) );
+        suite.addTest( new JUnit4TestAdapter( ForkedProcessEventObserverTest.class ) );
         suite.addTest( new JUnit4TestAdapter( ConsoleOutputReporterTest.class ) );
         suite.addTest( new JUnit4TestAdapter( StatelessReporterTest.class ) );
         suite.addTest( new JUnit4TestAdapter( TestSetStatsTest.class ) );
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/ForkedProcessEvent.java b/surefire-api/src/main/java/org/apache/maven/surefire/booter/ForkedProcessEventType.java
similarity index 88%
rename from surefire-api/src/main/java/org/apache/maven/surefire/booter/ForkedProcessEvent.java
rename to surefire-api/src/main/java/org/apache/maven/surefire/booter/ForkedProcessEventType.java
index 617eadc..c1087ac 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/ForkedProcessEvent.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/booter/ForkedProcessEventType.java
@@ -19,6 +19,7 @@ package org.apache.maven.surefire.booter;
  * under the License.
  */
 
+import javax.annotation.Nonnull;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 
@@ -30,7 +31,7 @@ import static java.util.Collections.unmodifiableMap;
  * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
  * @since 3.0.0-M4
  */
-public enum ForkedProcessEvent
+public enum ForkedProcessEventType
 {
     BOOTERCODE_SYSPROPS( "sys-prop" ),
 
@@ -61,14 +62,12 @@ public enum ForkedProcessEvent
 
     public static final String MAGIC_NUMBER = "maven-surefire-event";
 
-    public static final String MAGIC_NUMBER_DELIMITED = ':' + MAGIC_NUMBER + ':';
+    private static final Map<String, ForkedProcessEventType> EVENTS = events();
 
-    public static final Map<String, ForkedProcessEvent> EVENTS = events();
-
-    private static Map<String, ForkedProcessEvent> events()
+    private static Map<String, ForkedProcessEventType> events()
     {
-        Map<String, ForkedProcessEvent> events = new ConcurrentHashMap<>();
-        for ( ForkedProcessEvent event : values() )
+        Map<String, ForkedProcessEventType> events = new ConcurrentHashMap<>();
+        for ( ForkedProcessEventType event : values() )
         {
             events.put( event.getOpcode(), event );
         }
@@ -77,7 +76,7 @@ public enum ForkedProcessEvent
 
     private final String opcode;
 
-    ForkedProcessEvent( String opcode )
+    ForkedProcessEventType( String opcode )
     {
         this.opcode = opcode;
     }
@@ -131,4 +130,9 @@ public enum ForkedProcessEvent
     {
         return this == BOOTERCODE_JVM_EXIT_ERROR;
     }
+
+    public static ForkedProcessEventType byOpcode( @Nonnull String opcode )
+    {
+        return EVENTS.get( opcode );
+    }
 }
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/eventapi/AbstractConsoleEvent.java b/surefire-api/src/main/java/org/apache/maven/surefire/eventapi/AbstractConsoleEvent.java
new file mode 100644
index 0000000..09e1898
--- /dev/null
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/eventapi/AbstractConsoleEvent.java
@@ -0,0 +1,85 @@
+package org.apache.maven.surefire.eventapi;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF 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.
+ */
+
+import org.apache.maven.surefire.booter.ForkedProcessEventType;
+
+/**
+ * The base class of an event.
+ *
+ * @since 3.0.0-M5
+ */
+public abstract class AbstractConsoleEvent extends Event
+{
+    private final String message;
+
+    protected AbstractConsoleEvent( ForkedProcessEventType eventType, String message )
+    {
+        super( eventType );
+        this.message = message;
+    }
+
+    public String getMessage()
+    {
+        return message;
+    }
+
+    @Override
+    public boolean isControlCategory()
+    {
+        return false;
+    }
+
+    @Override
+    public boolean isConsoleCategory()
+    {
+        return true;
+    }
+
+    @Override
+    public boolean isConsoleErrorCategory()
+    {
+        return false;
+    }
+
+    @Override
+    public boolean isStandardStreamCategory()
+    {
+        return false;
+    }
+
+    @Override
+    public boolean isSysPropCategory()
+    {
+        return false;
+    }
+
+    @Override
+    public boolean isTestCategory()
+    {
+        return false;
+    }
+
+    @Override
+    public boolean isJvmExitError()
+    {
+        return false;
+    }
+}
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/eventapi/AbstractStandardStreamEvent.java b/surefire-api/src/main/java/org/apache/maven/surefire/eventapi/AbstractStandardStreamEvent.java
new file mode 100644
index 0000000..329483a
--- /dev/null
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/eventapi/AbstractStandardStreamEvent.java
@@ -0,0 +1,93 @@
+package org.apache.maven.surefire.eventapi;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF 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.
+ */
+
+import org.apache.maven.surefire.booter.ForkedProcessEventType;
+import org.apache.maven.surefire.report.RunMode;
+
+/**
+ * The base class of an event of standard streams.
+ *
+ * @since 3.0.0-M5
+ */
+public abstract class AbstractStandardStreamEvent extends Event
+{
+    private final RunMode runMode;
+    private final String message;
+
+    protected AbstractStandardStreamEvent( ForkedProcessEventType eventType, RunMode runMode, String message )
+    {
+        super( eventType );
+        this.runMode = runMode;
+        this.message = message;
+    }
+
+    public RunMode getRunMode()
+    {
+        return runMode;
+    }
+
+    public String getMessage()
+    {
+        return message;
+    }
+
+    @Override
+    public boolean isControlCategory()
+    {
+        return false;
+    }
+
+    @Override
+    public boolean isConsoleCategory()
+    {
+        return false;
+    }
+
+    @Override
+    public boolean isConsoleErrorCategory()
+    {
+        return false;
+    }
+
+    @Override
+    public boolean isStandardStreamCategory()
+    {
+        return true;
+    }
+
+    @Override
+    public boolean isSysPropCategory()
+    {
+        return false;
+    }
+
+    @Override
+    public boolean isTestCategory()
+    {
+        return false;
+    }
+
+    @Override
+    public boolean isJvmExitError()
+    {
+        return false;
+    }
+}
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/eventapi/AbstractTestControlEvent.java b/surefire-api/src/main/java/org/apache/maven/surefire/eventapi/AbstractTestControlEvent.java
new file mode 100644
index 0000000..03fa93d
--- /dev/null
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/eventapi/AbstractTestControlEvent.java
@@ -0,0 +1,94 @@
+package org.apache.maven.surefire.eventapi;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF 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.
+ */
+
+import org.apache.maven.surefire.booter.ForkedProcessEventType;
+import org.apache.maven.surefire.report.ReportEntry;
+import org.apache.maven.surefire.report.RunMode;
+
+/**
+ * The base class of an event of test control.
+ *
+ * @since 3.0.0-M5
+ */
+public abstract class AbstractTestControlEvent extends Event
+{
+    private final RunMode runMode;
+    private final ReportEntry reportEntry;
+
+    public AbstractTestControlEvent( ForkedProcessEventType eventType, RunMode runMode, ReportEntry reportEntry )
+    {
+        super( eventType );
+        this.runMode = runMode;
+        this.reportEntry = reportEntry;
+    }
+
+    public RunMode getRunMode()
+    {
+        return runMode;
+    }
+
+    public ReportEntry getReportEntry()
+    {
+        return reportEntry;
+    }
+
+    @Override
+    public boolean isControlCategory()
+    {
+        return false;
+    }
+
+    @Override
+    public boolean isConsoleCategory()
+    {
+        return false;
+    }
+
+    @Override
+    public boolean isConsoleErrorCategory()
+    {
+        return false;
+    }
+
+    @Override
+    public boolean isStandardStreamCategory()
+    {
+        return false;
+    }
+
+    @Override
+    public boolean isSysPropCategory()
+    {
+        return false;
+    }
+
+    @Override
+    public boolean isTestCategory()
+    {
+        return true;
+    }
+
+    @Override
+    public boolean isJvmExitError()
+    {
+        return false;
+    }
+}
diff --git a/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/EventHandler.java b/surefire-api/src/main/java/org/apache/maven/surefire/eventapi/ConsoleDebugEvent.java
similarity index 68%
copy from surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/EventHandler.java
copy to surefire-api/src/main/java/org/apache/maven/surefire/eventapi/ConsoleDebugEvent.java
index bcb6c81..a039c55 100644
--- a/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/EventHandler.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/eventapi/ConsoleDebugEvent.java
@@ -1,4 +1,4 @@
-package org.apache.maven.surefire.extensions;
+package org.apache.maven.surefire.eventapi;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -19,14 +19,17 @@ package org.apache.maven.surefire.extensions;
  * under the License.
  */
 
-import javax.annotation.Nonnull;
+import static org.apache.maven.surefire.booter.ForkedProcessEventType.BOOTERCODE_CONSOLE_DEBUG;
 
 /**
+ * The event with a message for console debug level.
  *
+ * @since 3.0.0-M5
  */
-public interface EventHandler
+public final class ConsoleDebugEvent extends AbstractConsoleEvent
 {
-    // todo here should be Event object as POJO which separates physical stream representation from the independent
-    // todo Event parser in ForkClient. So the parser should be part of ForkChannel implementation.
-    void handleEvent( @Nonnull String event );
+    public ConsoleDebugEvent( String message )
+    {
+        super( BOOTERCODE_CONSOLE_DEBUG, message );
+    }
 }
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/eventapi/ConsoleErrorEvent.java b/surefire-api/src/main/java/org/apache/maven/surefire/eventapi/ConsoleErrorEvent.java
new file mode 100644
index 0000000..7c78347
--- /dev/null
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/eventapi/ConsoleErrorEvent.java
@@ -0,0 +1,87 @@
+package org.apache.maven.surefire.eventapi;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF 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.
+ */
+
+import org.apache.maven.surefire.report.StackTraceWriter;
+
+import static org.apache.maven.surefire.booter.ForkedProcessEventType.BOOTERCODE_CONSOLE_ERROR;
+
+/**
+ * The event with a message for console error level.
+ *
+ * @since 3.0.0-M5
+ */
+public final class ConsoleErrorEvent extends Event
+{
+    private final StackTraceWriter stackTraceWriter;
+
+    public ConsoleErrorEvent( StackTraceWriter stackTraceWriter )
+    {
+        super( BOOTERCODE_CONSOLE_ERROR );
+        this.stackTraceWriter = stackTraceWriter;
+    }
+
+    public StackTraceWriter getStackTraceWriter()
+    {
+        return stackTraceWriter;
+    }
+
+    @Override
+    public boolean isControlCategory()
+    {
+        return false;
+    }
+
+    @Override
+    public boolean isConsoleCategory()
+    {
+        return true;
+    }
+
+    @Override
+    public boolean isConsoleErrorCategory()
+    {
+        return true;
+    }
+
+    @Override
+    public boolean isStandardStreamCategory()
+    {
+        return false;
+    }
+
+    @Override
+    public boolean isSysPropCategory()
+    {
+        return false;
+    }
+
+    @Override
+    public boolean isTestCategory()
+    {
+        return false;
+    }
+
+    @Override
+    public boolean isJvmExitError()
+    {
+        return false;
+    }
+}
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedChannelDecoderErrorHandler.java b/surefire-api/src/main/java/org/apache/maven/surefire/eventapi/ConsoleInfoEvent.java
similarity index 67%
rename from maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedChannelDecoderErrorHandler.java
rename to surefire-api/src/main/java/org/apache/maven/surefire/eventapi/ConsoleInfoEvent.java
index 8faa803..452b46a 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedChannelDecoderErrorHandler.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/eventapi/ConsoleInfoEvent.java
@@ -1,4 +1,4 @@
-package org.apache.maven.plugin.surefire.booterclient.output;
+package org.apache.maven.surefire.eventapi;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -19,11 +19,17 @@ package org.apache.maven.plugin.surefire.booterclient.output;
  * under the License.
  */
 
+import static org.apache.maven.surefire.booter.ForkedProcessEventType.BOOTERCODE_CONSOLE_INFO;
+
 /**
- * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
- * @since 3.0.0-M4
+ * The event with a message for console info level.
+ *
+ * @since 3.0.0-M5
  */
-public interface ForkedChannelDecoderErrorHandler
+public final class ConsoleInfoEvent extends AbstractConsoleEvent
 {
-    void handledError( String line, Throwable e );
+    public ConsoleInfoEvent( String message )
+    {
+        super( BOOTERCODE_CONSOLE_INFO, message );
+    }
 }
diff --git a/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/EventHandler.java b/surefire-api/src/main/java/org/apache/maven/surefire/eventapi/ConsoleWarningEvent.java
similarity index 67%
copy from surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/EventHandler.java
copy to surefire-api/src/main/java/org/apache/maven/surefire/eventapi/ConsoleWarningEvent.java
index bcb6c81..3eb9f03 100644
--- a/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/EventHandler.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/eventapi/ConsoleWarningEvent.java
@@ -1,4 +1,4 @@
-package org.apache.maven.surefire.extensions;
+package org.apache.maven.surefire.eventapi;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -19,14 +19,17 @@ package org.apache.maven.surefire.extensions;
  * under the License.
  */
 
-import javax.annotation.Nonnull;
+import static org.apache.maven.surefire.booter.ForkedProcessEventType.BOOTERCODE_CONSOLE_WARNING;
 
 /**
+ * The event with a message for console warning level.
  *
+ * @since 3.0.0-M5
  */
-public interface EventHandler
+public final class ConsoleWarningEvent extends AbstractConsoleEvent
 {
-    // todo here should be Event object as POJO which separates physical stream representation from the independent
-    // todo Event parser in ForkClient. So the parser should be part of ForkChannel implementation.
-    void handleEvent( @Nonnull String event );
+    public ConsoleWarningEvent( String message )
+    {
+        super( BOOTERCODE_CONSOLE_WARNING, message );
+    }
 }
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/eventapi/ControlByeEvent.java b/surefire-api/src/main/java/org/apache/maven/surefire/eventapi/ControlByeEvent.java
new file mode 100644
index 0000000..577cc89
--- /dev/null
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/eventapi/ControlByeEvent.java
@@ -0,0 +1,77 @@
+package org.apache.maven.surefire.eventapi;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF 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.
+ */
+
+import static org.apache.maven.surefire.booter.ForkedProcessEventType.BOOTERCODE_BYE;
+
+/**
+ * The control event of bye.
+ *
+ * @since 3.0.0-M5
+ */
+public final class ControlByeEvent extends Event
+{
+    public ControlByeEvent()
+    {
+        super( BOOTERCODE_BYE );
+    }
+
+    @Override
+    public boolean isControlCategory()
+    {
+        return true;
+    }
+
+    @Override
+    public boolean isConsoleCategory()
+    {
+        return false;
+    }
+
+    @Override
+    public boolean isConsoleErrorCategory()
+    {
+        return false;
+    }
+
+    @Override
+    public boolean isStandardStreamCategory()
+    {
+        return false;
+    }
+
+    @Override
+    public boolean isSysPropCategory()
+    {
+        return false;
+    }
+
+    @Override
+    public boolean isTestCategory()
+    {
+        return false;
+    }
+
+    @Override
+    public boolean isJvmExitError()
+    {
+        return false;
+    }
+}
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/eventapi/ControlNextTestEvent.java b/surefire-api/src/main/java/org/apache/maven/surefire/eventapi/ControlNextTestEvent.java
new file mode 100644
index 0000000..e320a26
--- /dev/null
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/eventapi/ControlNextTestEvent.java
@@ -0,0 +1,77 @@
+package org.apache.maven.surefire.eventapi;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF 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.
+ */
+
+import static org.apache.maven.surefire.booter.ForkedProcessEventType.BOOTERCODE_NEXT_TEST;
+
+/**
+ * The control event of requesting a next test.
+ *
+ * @since 3.0.0-M5
+ */
+public final class ControlNextTestEvent extends Event
+{
+    public ControlNextTestEvent()
+    {
+        super( BOOTERCODE_NEXT_TEST );
+    }
+
+    @Override
+    public boolean isControlCategory()
+    {
+        return true;
+    }
+
+    @Override
+    public boolean isConsoleCategory()
+    {
+        return false;
+    }
+
+    @Override
+    public boolean isConsoleErrorCategory()
+    {
+        return false;
+    }
+
+    @Override
+    public boolean isStandardStreamCategory()
+    {
+        return false;
+    }
+
+    @Override
+    public boolean isSysPropCategory()
+    {
+        return false;
+    }
+
+    @Override
+    public boolean isTestCategory()
+    {
+        return false;
+    }
+
+    @Override
+    public boolean isJvmExitError()
+    {
+        return false;
+    }
+}
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/eventapi/ControlStopOnNextTestEvent.java b/surefire-api/src/main/java/org/apache/maven/surefire/eventapi/ControlStopOnNextTestEvent.java
new file mode 100644
index 0000000..fe65550
--- /dev/null
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/eventapi/ControlStopOnNextTestEvent.java
@@ -0,0 +1,77 @@
+package org.apache.maven.surefire.eventapi;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF 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.
+ */
+
+import static org.apache.maven.surefire.booter.ForkedProcessEventType.BOOTERCODE_STOP_ON_NEXT_TEST;
+
+/**
+ * The control event of stopping on next test.
+ *
+ * @since 3.0.0-M5
+ */
+public final class ControlStopOnNextTestEvent extends Event
+{
+    public ControlStopOnNextTestEvent()
+    {
+        super( BOOTERCODE_STOP_ON_NEXT_TEST );
+    }
+
+    @Override
+    public boolean isControlCategory()
+    {
+        return true;
+    }
+
+    @Override
+    public boolean isConsoleCategory()
+    {
+        return false;
+    }
+
+    @Override
+    public boolean isConsoleErrorCategory()
+    {
+        return false;
+    }
+
+    @Override
+    public boolean isStandardStreamCategory()
+    {
+        return false;
+    }
+
+    @Override
+    public boolean isSysPropCategory()
+    {
+        return false;
+    }
+
+    @Override
+    public boolean isTestCategory()
+    {
+        return false;
+    }
+
+    @Override
+    public boolean isJvmExitError()
+    {
+        return false;
+    }
+}
diff --git a/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/ForkNodeFactory.java b/surefire-api/src/main/java/org/apache/maven/surefire/eventapi/Event.java
similarity index 54%
copy from surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/ForkNodeFactory.java
copy to surefire-api/src/main/java/org/apache/maven/surefire/eventapi/Event.java
index a460482..32893a1 100644
--- a/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/ForkNodeFactory.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/eventapi/Event.java
@@ -1,4 +1,4 @@
-package org.apache.maven.surefire.extensions;
+package org.apache.maven.surefire.eventapi;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -19,23 +19,33 @@ package org.apache.maven.surefire.extensions;
  * under the License.
  */
 
-import javax.annotation.Nonnegative;
-import javax.annotation.Nonnull;
-import java.io.IOException;
+import org.apache.maven.surefire.booter.ForkedProcessEventType;
 
 /**
- * This is the plugin extension as a factory of {@link ForkChannel}.
+ * Super type of events.
  *
  * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
  * @since 3.0.0-M5
  */
-public interface ForkNodeFactory
+public abstract class Event
 {
-    /**
-     * Opens and closes the channel.
-     *
-     * @return specific implementation of the communication channel
-     * @throws IOException if cannot open the channel
-     */
-    @Nonnull ForkChannel createForkChannel( @Nonnegative int forkChannelId ) throws IOException;
+    private final ForkedProcessEventType eventType;
+
+    public Event( ForkedProcessEventType eventType )
+    {
+        this.eventType = eventType;
+    }
+
+    public abstract boolean isControlCategory();
+    public abstract boolean isConsoleCategory();
+    public abstract boolean isConsoleErrorCategory();
+    public abstract boolean isStandardStreamCategory();
+    public abstract boolean isSysPropCategory();
+    public abstract boolean isTestCategory();
+    public abstract boolean isJvmExitError();
+
+    public final ForkedProcessEventType getEventType()
+    {
+        return eventType;
+    }
 }
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/eventapi/JvmExitErrorEvent.java b/surefire-api/src/main/java/org/apache/maven/surefire/eventapi/JvmExitErrorEvent.java
new file mode 100644
index 0000000..43a9a17
--- /dev/null
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/eventapi/JvmExitErrorEvent.java
@@ -0,0 +1,87 @@
+package org.apache.maven.surefire.eventapi;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF 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.
+ */
+
+import org.apache.maven.surefire.report.StackTraceWriter;
+
+import static org.apache.maven.surefire.booter.ForkedProcessEventType.BOOTERCODE_JVM_EXIT_ERROR;
+
+/**
+ * The event for the JVM exit error.
+ *
+ * @since 3.0.0-M5
+ */
+public final class JvmExitErrorEvent extends Event
+{
+    private final StackTraceWriter stackTraceWriter;
+
+    public JvmExitErrorEvent( StackTraceWriter stackTraceWriter )
+    {
+        super( BOOTERCODE_JVM_EXIT_ERROR );
+        this.stackTraceWriter = stackTraceWriter;
+    }
+
+    public StackTraceWriter getStackTraceWriter()
+    {
+        return stackTraceWriter;
+    }
+
+    @Override
+    public boolean isControlCategory()
+    {
+        return false;
+    }
+
+    @Override
+    public boolean isConsoleCategory()
+    {
+        return false;
+    }
+
+    @Override
+    public boolean isConsoleErrorCategory()
+    {
+        return false;
+    }
+
+    @Override
+    public boolean isStandardStreamCategory()
+    {
+        return false;
+    }
+
+    @Override
+    public boolean isSysPropCategory()
+    {
+        return false;
+    }
+
+    @Override
+    public boolean isTestCategory()
+    {
+        return false;
+    }
+
+    @Override
+    public boolean isJvmExitError()
+    {
+        return true;
+    }
+}
diff --git a/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/EventHandler.java b/surefire-api/src/main/java/org/apache/maven/surefire/eventapi/StandardStreamErrEvent.java
similarity index 64%
copy from surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/EventHandler.java
copy to surefire-api/src/main/java/org/apache/maven/surefire/eventapi/StandardStreamErrEvent.java
index bcb6c81..bdf498d 100644
--- a/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/EventHandler.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/eventapi/StandardStreamErrEvent.java
@@ -1,4 +1,4 @@
-package org.apache.maven.surefire.extensions;
+package org.apache.maven.surefire.eventapi;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -19,14 +19,19 @@ package org.apache.maven.surefire.extensions;
  * under the License.
  */
 
-import javax.annotation.Nonnull;
+import org.apache.maven.surefire.report.RunMode;
+
+import static org.apache.maven.surefire.booter.ForkedProcessEventType.BOOTERCODE_STDERR;
 
 /**
+ * The event of standard error stream.
  *
+ * @since 3.0.0-M5
  */
-public interface EventHandler
+public final class StandardStreamErrEvent extends AbstractStandardStreamEvent
 {
-    // todo here should be Event object as POJO which separates physical stream representation from the independent
-    // todo Event parser in ForkClient. So the parser should be part of ForkChannel implementation.
-    void handleEvent( @Nonnull String event );
+    public StandardStreamErrEvent( RunMode runMode, String message )
+    {
+        super( BOOTERCODE_STDERR, runMode, message );
+    }
 }
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/LegacyForkNodeFactory.java b/surefire-api/src/main/java/org/apache/maven/surefire/eventapi/StandardStreamErrWithNewLineEvent.java
similarity index 62%
copy from maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/LegacyForkNodeFactory.java
copy to surefire-api/src/main/java/org/apache/maven/surefire/eventapi/StandardStreamErrWithNewLineEvent.java
index 3667407..1daf1c6 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/LegacyForkNodeFactory.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/eventapi/StandardStreamErrWithNewLineEvent.java
@@ -1,4 +1,4 @@
-package org.apache.maven.plugin.surefire.extensions;
+package org.apache.maven.surefire.eventapi;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -19,21 +19,19 @@ package org.apache.maven.plugin.surefire.extensions;
  * under the License.
  */
 
-import org.apache.maven.surefire.extensions.ForkChannel;
-import org.apache.maven.surefire.extensions.ForkNodeFactory;
+import org.apache.maven.surefire.report.RunMode;
 
-import javax.annotation.Nonnegative;
-import javax.annotation.Nonnull;
+import static org.apache.maven.surefire.booter.ForkedProcessEventType.BOOTERCODE_STDERR_NEW_LINE;
 
 /**
- * The factory of {@link LegacyForkChannel}.
+ * The event of standard error stream with new line.
+ *
+ * @since 3.0.0-M5
  */
-public class LegacyForkNodeFactory implements ForkNodeFactory
+public final class StandardStreamErrWithNewLineEvent extends AbstractStandardStreamEvent
 {
-    @Nonnull
-    @Override
-    public ForkChannel createForkChannel( @Nonnegative int forkChannelId )
+    public StandardStreamErrWithNewLineEvent( RunMode runMode, String message )
     {
-        return new LegacyForkChannel( forkChannelId );
+        super( BOOTERCODE_STDERR_NEW_LINE, runMode, message );
     }
 }
diff --git a/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/EventHandler.java b/surefire-api/src/main/java/org/apache/maven/surefire/eventapi/StandardStreamOutEvent.java
similarity index 64%
copy from surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/EventHandler.java
copy to surefire-api/src/main/java/org/apache/maven/surefire/eventapi/StandardStreamOutEvent.java
index bcb6c81..333755f 100644
--- a/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/EventHandler.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/eventapi/StandardStreamOutEvent.java
@@ -1,4 +1,4 @@
-package org.apache.maven.surefire.extensions;
+package org.apache.maven.surefire.eventapi;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -19,14 +19,19 @@ package org.apache.maven.surefire.extensions;
  * under the License.
  */
 
-import javax.annotation.Nonnull;
+import org.apache.maven.surefire.report.RunMode;
+
+import static org.apache.maven.surefire.booter.ForkedProcessEventType.BOOTERCODE_STDOUT;
 
 /**
+ * The event of standard output stream.
  *
+ * @since 3.0.0-M5
  */
-public interface EventHandler
+public final class StandardStreamOutEvent extends AbstractStandardStreamEvent
 {
-    // todo here should be Event object as POJO which separates physical stream representation from the independent
-    // todo Event parser in ForkClient. So the parser should be part of ForkChannel implementation.
-    void handleEvent( @Nonnull String event );
+    public StandardStreamOutEvent( RunMode runMode, String message )
+    {
+        super( BOOTERCODE_STDOUT, runMode, message );
+    }
 }
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/LegacyForkNodeFactory.java b/surefire-api/src/main/java/org/apache/maven/surefire/eventapi/StandardStreamOutWithNewLineEvent.java
similarity index 62%
copy from maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/LegacyForkNodeFactory.java
copy to surefire-api/src/main/java/org/apache/maven/surefire/eventapi/StandardStreamOutWithNewLineEvent.java
index 3667407..0263b92 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/LegacyForkNodeFactory.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/eventapi/StandardStreamOutWithNewLineEvent.java
@@ -1,4 +1,4 @@
-package org.apache.maven.plugin.surefire.extensions;
+package org.apache.maven.surefire.eventapi;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -19,21 +19,19 @@ package org.apache.maven.plugin.surefire.extensions;
  * under the License.
  */
 
-import org.apache.maven.surefire.extensions.ForkChannel;
-import org.apache.maven.surefire.extensions.ForkNodeFactory;
+import org.apache.maven.surefire.report.RunMode;
 
-import javax.annotation.Nonnegative;
-import javax.annotation.Nonnull;
+import static org.apache.maven.surefire.booter.ForkedProcessEventType.BOOTERCODE_STDOUT_NEW_LINE;
 
 /**
- * The factory of {@link LegacyForkChannel}.
+ * The event of standard output stream with new line.
+ *
+ * @since 3.0.0-M5
  */
-public class LegacyForkNodeFactory implements ForkNodeFactory
+public final class StandardStreamOutWithNewLineEvent extends AbstractStandardStreamEvent
 {
-    @Nonnull
-    @Override
-    public ForkChannel createForkChannel( @Nonnegative int forkChannelId )
+    public StandardStreamOutWithNewLineEvent( RunMode runMode, String message )
     {
-        return new LegacyForkChannel( forkChannelId );
+        super( BOOTERCODE_STDOUT_NEW_LINE, runMode, message );
     }
 }
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/eventapi/SystemPropertyEvent.java b/surefire-api/src/main/java/org/apache/maven/surefire/eventapi/SystemPropertyEvent.java
new file mode 100644
index 0000000..9db4e1b
--- /dev/null
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/eventapi/SystemPropertyEvent.java
@@ -0,0 +1,101 @@
+package org.apache.maven.surefire.eventapi;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF 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.
+ */
+
+import org.apache.maven.surefire.report.RunMode;
+
+import static org.apache.maven.surefire.booter.ForkedProcessEventType.BOOTERCODE_SYSPROPS;
+
+/**
+ * The event of system property.
+ *
+ * @since 3.0.0-M5
+ */
+public final class SystemPropertyEvent extends Event
+{
+    private final RunMode runMode;
+    private final String key;
+    private final String value;
+
+    public SystemPropertyEvent( RunMode runMode, String key, String value )
+    {
+        super( BOOTERCODE_SYSPROPS );
+        this.runMode = runMode;
+        this.key = key;
+        this.value = value;
+    }
+
+    public RunMode getRunMode()
+    {
+        return runMode;
+    }
+
+    public String getKey()
+    {
+        return key;
+    }
+
+    public String getValue()
+    {
+        return value;
+    }
+
+    @Override
+    public boolean isControlCategory()
+    {
+        return false;
+    }
+
+    @Override
+    public boolean isConsoleCategory()
+    {
+        return false;
+    }
+
+    @Override
+    public boolean isConsoleErrorCategory()
+    {
+        return false;
+    }
+
+    @Override
+    public boolean isStandardStreamCategory()
+    {
+        return false;
+    }
+
+    @Override
+    public boolean isSysPropCategory()
+    {
+        return true;
+    }
+
+    @Override
+    public boolean isTestCategory()
+    {
+        return false;
+    }
+
+    @Override
+    public boolean isJvmExitError()
+    {
+        return false;
+    }
+}
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/LegacyForkNodeFactory.java b/surefire-api/src/main/java/org/apache/maven/surefire/eventapi/TestAssumptionFailureEvent.java
similarity index 60%
copy from maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/LegacyForkNodeFactory.java
copy to surefire-api/src/main/java/org/apache/maven/surefire/eventapi/TestAssumptionFailureEvent.java
index 3667407..4c046ec 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/LegacyForkNodeFactory.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/eventapi/TestAssumptionFailureEvent.java
@@ -1,4 +1,4 @@
-package org.apache.maven.plugin.surefire.extensions;
+package org.apache.maven.surefire.eventapi;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -19,21 +19,20 @@ package org.apache.maven.plugin.surefire.extensions;
  * under the License.
  */
 
-import org.apache.maven.surefire.extensions.ForkChannel;
-import org.apache.maven.surefire.extensions.ForkNodeFactory;
+import org.apache.maven.surefire.report.ReportEntry;
+import org.apache.maven.surefire.report.RunMode;
 
-import javax.annotation.Nonnegative;
-import javax.annotation.Nonnull;
+import static org.apache.maven.surefire.booter.ForkedProcessEventType.BOOTERCODE_TEST_ASSUMPTIONFAILURE;
 
 /**
- * The factory of {@link LegacyForkChannel}.
+ * The event for the failed assumption.
+ *
+ * @since 3.0.0-M5
  */
-public class LegacyForkNodeFactory implements ForkNodeFactory
+public final class TestAssumptionFailureEvent extends AbstractTestControlEvent
 {
-    @Nonnull
-    @Override
-    public ForkChannel createForkChannel( @Nonnegative int forkChannelId )
+    public TestAssumptionFailureEvent( RunMode runMode, ReportEntry reportEntry )
     {
-        return new LegacyForkChannel( forkChannelId );
+        super( BOOTERCODE_TEST_ASSUMPTIONFAILURE, runMode, reportEntry );
     }
 }
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/LegacyForkNodeFactory.java b/surefire-api/src/main/java/org/apache/maven/surefire/eventapi/TestErrorEvent.java
similarity index 62%
copy from maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/LegacyForkNodeFactory.java
copy to surefire-api/src/main/java/org/apache/maven/surefire/eventapi/TestErrorEvent.java
index 3667407..bcd32b9 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/LegacyForkNodeFactory.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/eventapi/TestErrorEvent.java
@@ -1,4 +1,4 @@
-package org.apache.maven.plugin.surefire.extensions;
+package org.apache.maven.surefire.eventapi;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -19,21 +19,20 @@ package org.apache.maven.plugin.surefire.extensions;
  * under the License.
  */
 
-import org.apache.maven.surefire.extensions.ForkChannel;
-import org.apache.maven.surefire.extensions.ForkNodeFactory;
+import org.apache.maven.surefire.report.ReportEntry;
+import org.apache.maven.surefire.report.RunMode;
 
-import javax.annotation.Nonnegative;
-import javax.annotation.Nonnull;
+import static org.apache.maven.surefire.booter.ForkedProcessEventType.BOOTERCODE_TEST_ERROR;
 
 /**
- * The factory of {@link LegacyForkChannel}.
+ * The event for the erroneous test.
+ *
+ * @since 3.0.0-M5
  */
-public class LegacyForkNodeFactory implements ForkNodeFactory
+public final class TestErrorEvent extends AbstractTestControlEvent
 {
-    @Nonnull
-    @Override
-    public ForkChannel createForkChannel( @Nonnegative int forkChannelId )
+    public TestErrorEvent( RunMode runMode, ReportEntry reportEntry )
     {
-        return new LegacyForkChannel( forkChannelId );
+        super( BOOTERCODE_TEST_ERROR, runMode, reportEntry );
     }
 }
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/LegacyForkNodeFactory.java b/surefire-api/src/main/java/org/apache/maven/surefire/eventapi/TestFailedEvent.java
similarity index 62%
copy from maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/LegacyForkNodeFactory.java
copy to surefire-api/src/main/java/org/apache/maven/surefire/eventapi/TestFailedEvent.java
index 3667407..f24bfcf 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/LegacyForkNodeFactory.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/eventapi/TestFailedEvent.java
@@ -1,4 +1,4 @@
-package org.apache.maven.plugin.surefire.extensions;
+package org.apache.maven.surefire.eventapi;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -19,21 +19,20 @@ package org.apache.maven.plugin.surefire.extensions;
  * under the License.
  */
 
-import org.apache.maven.surefire.extensions.ForkChannel;
-import org.apache.maven.surefire.extensions.ForkNodeFactory;
+import org.apache.maven.surefire.report.ReportEntry;
+import org.apache.maven.surefire.report.RunMode;
 
-import javax.annotation.Nonnegative;
-import javax.annotation.Nonnull;
+import static org.apache.maven.surefire.booter.ForkedProcessEventType.BOOTERCODE_TEST_FAILED;
 
 /**
- * The factory of {@link LegacyForkChannel}.
+ * The event for the failed test.
+ *
+ * @since 3.0.0-M5
  */
-public class LegacyForkNodeFactory implements ForkNodeFactory
+public final class TestFailedEvent extends AbstractTestControlEvent
 {
-    @Nonnull
-    @Override
-    public ForkChannel createForkChannel( @Nonnegative int forkChannelId )
+    public TestFailedEvent( RunMode runMode, ReportEntry reportEntry )
     {
-        return new LegacyForkChannel( forkChannelId );
+        super( BOOTERCODE_TEST_FAILED, runMode, reportEntry );
     }
 }
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/LegacyForkNodeFactory.java b/surefire-api/src/main/java/org/apache/maven/surefire/eventapi/TestSkippedEvent.java
similarity index 62%
copy from maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/LegacyForkNodeFactory.java
copy to surefire-api/src/main/java/org/apache/maven/surefire/eventapi/TestSkippedEvent.java
index 3667407..6f70471 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/LegacyForkNodeFactory.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/eventapi/TestSkippedEvent.java
@@ -1,4 +1,4 @@
-package org.apache.maven.plugin.surefire.extensions;
+package org.apache.maven.surefire.eventapi;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -19,21 +19,20 @@ package org.apache.maven.plugin.surefire.extensions;
  * under the License.
  */
 
-import org.apache.maven.surefire.extensions.ForkChannel;
-import org.apache.maven.surefire.extensions.ForkNodeFactory;
+import org.apache.maven.surefire.report.ReportEntry;
+import org.apache.maven.surefire.report.RunMode;
 
-import javax.annotation.Nonnegative;
-import javax.annotation.Nonnull;
+import static org.apache.maven.surefire.booter.ForkedProcessEventType.BOOTERCODE_TEST_SKIPPED;
 
 /**
- * The factory of {@link LegacyForkChannel}.
+ * The event for the skipped test.
+ *
+ * @since 3.0.0-M5
  */
-public class LegacyForkNodeFactory implements ForkNodeFactory
+public final class TestSkippedEvent extends AbstractTestControlEvent
 {
-    @Nonnull
-    @Override
-    public ForkChannel createForkChannel( @Nonnegative int forkChannelId )
+    public TestSkippedEvent( RunMode runMode, ReportEntry reportEntry )
     {
-        return new LegacyForkChannel( forkChannelId );
+        super( BOOTERCODE_TEST_SKIPPED, runMode, reportEntry );
     }
 }
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/LegacyForkNodeFactory.java b/surefire-api/src/main/java/org/apache/maven/surefire/eventapi/TestStartingEvent.java
similarity index 62%
copy from maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/LegacyForkNodeFactory.java
copy to surefire-api/src/main/java/org/apache/maven/surefire/eventapi/TestStartingEvent.java
index 3667407..ebfcffd 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/LegacyForkNodeFactory.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/eventapi/TestStartingEvent.java
@@ -1,4 +1,4 @@
-package org.apache.maven.plugin.surefire.extensions;
+package org.apache.maven.surefire.eventapi;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -19,21 +19,20 @@ package org.apache.maven.plugin.surefire.extensions;
  * under the License.
  */
 
-import org.apache.maven.surefire.extensions.ForkChannel;
-import org.apache.maven.surefire.extensions.ForkNodeFactory;
+import org.apache.maven.surefire.report.ReportEntry;
+import org.apache.maven.surefire.report.RunMode;
 
-import javax.annotation.Nonnegative;
-import javax.annotation.Nonnull;
+import static org.apache.maven.surefire.booter.ForkedProcessEventType.BOOTERCODE_TEST_STARTING;
 
 /**
- * The factory of {@link LegacyForkChannel}.
+ * The event for started test.
+ *
+ * @since 3.0.0-M5
  */
-public class LegacyForkNodeFactory implements ForkNodeFactory
+public final class TestStartingEvent extends AbstractTestControlEvent
 {
-    @Nonnull
-    @Override
-    public ForkChannel createForkChannel( @Nonnegative int forkChannelId )
+    public TestStartingEvent( RunMode runMode, ReportEntry reportEntry )
     {
-        return new LegacyForkChannel( forkChannelId );
+        super( BOOTERCODE_TEST_STARTING, runMode, reportEntry );
     }
 }
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/LegacyForkNodeFactory.java b/surefire-api/src/main/java/org/apache/maven/surefire/eventapi/TestSucceededEvent.java
similarity index 62%
copy from maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/LegacyForkNodeFactory.java
copy to surefire-api/src/main/java/org/apache/maven/surefire/eventapi/TestSucceededEvent.java
index 3667407..a382e09 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/LegacyForkNodeFactory.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/eventapi/TestSucceededEvent.java
@@ -1,4 +1,4 @@
-package org.apache.maven.plugin.surefire.extensions;
+package org.apache.maven.surefire.eventapi;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -19,21 +19,20 @@ package org.apache.maven.plugin.surefire.extensions;
  * under the License.
  */
 
-import org.apache.maven.surefire.extensions.ForkChannel;
-import org.apache.maven.surefire.extensions.ForkNodeFactory;
+import org.apache.maven.surefire.report.ReportEntry;
+import org.apache.maven.surefire.report.RunMode;
 
-import javax.annotation.Nonnegative;
-import javax.annotation.Nonnull;
+import static org.apache.maven.surefire.booter.ForkedProcessEventType.BOOTERCODE_TEST_SUCCEEDED;
 
 /**
- * The factory of {@link LegacyForkChannel}.
+ * The event for succeeded test.
+ *
+ * @since 3.0.0-M5
  */
-public class LegacyForkNodeFactory implements ForkNodeFactory
+public final class TestSucceededEvent extends AbstractTestControlEvent
 {
-    @Nonnull
-    @Override
-    public ForkChannel createForkChannel( @Nonnegative int forkChannelId )
+    public TestSucceededEvent( RunMode runMode, ReportEntry reportEntry )
     {
-        return new LegacyForkChannel( forkChannelId );
+        super( BOOTERCODE_TEST_SUCCEEDED, runMode, reportEntry );
     }
 }
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/LegacyForkNodeFactory.java b/surefire-api/src/main/java/org/apache/maven/surefire/eventapi/TestsetCompletedEvent.java
similarity index 61%
copy from maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/LegacyForkNodeFactory.java
copy to surefire-api/src/main/java/org/apache/maven/surefire/eventapi/TestsetCompletedEvent.java
index 3667407..6779565 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/LegacyForkNodeFactory.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/eventapi/TestsetCompletedEvent.java
@@ -1,4 +1,4 @@
-package org.apache.maven.plugin.surefire.extensions;
+package org.apache.maven.surefire.eventapi;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -19,21 +19,20 @@ package org.apache.maven.plugin.surefire.extensions;
  * under the License.
  */
 
-import org.apache.maven.surefire.extensions.ForkChannel;
-import org.apache.maven.surefire.extensions.ForkNodeFactory;
+import org.apache.maven.surefire.report.ReportEntry;
+import org.apache.maven.surefire.report.RunMode;
 
-import javax.annotation.Nonnegative;
-import javax.annotation.Nonnull;
+import static org.apache.maven.surefire.booter.ForkedProcessEventType.BOOTERCODE_TESTSET_COMPLETED;
 
 /**
- * The factory of {@link LegacyForkChannel}.
+ * The event for completed testset.
+ *
+ * @since 3.0.0-M5
  */
-public class LegacyForkNodeFactory implements ForkNodeFactory
+public final class TestsetCompletedEvent extends AbstractTestControlEvent
 {
-    @Nonnull
-    @Override
-    public ForkChannel createForkChannel( @Nonnegative int forkChannelId )
+    public TestsetCompletedEvent( RunMode runMode, ReportEntry reportEntry )
     {
-        return new LegacyForkChannel( forkChannelId );
+        super( BOOTERCODE_TESTSET_COMPLETED, runMode, reportEntry );
     }
 }
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/LegacyForkNodeFactory.java b/surefire-api/src/main/java/org/apache/maven/surefire/eventapi/TestsetStartingEvent.java
similarity index 61%
copy from maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/LegacyForkNodeFactory.java
copy to surefire-api/src/main/java/org/apache/maven/surefire/eventapi/TestsetStartingEvent.java
index 3667407..ae6acd0 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/LegacyForkNodeFactory.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/eventapi/TestsetStartingEvent.java
@@ -1,4 +1,4 @@
-package org.apache.maven.plugin.surefire.extensions;
+package org.apache.maven.surefire.eventapi;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -19,21 +19,20 @@ package org.apache.maven.plugin.surefire.extensions;
  * under the License.
  */
 
-import org.apache.maven.surefire.extensions.ForkChannel;
-import org.apache.maven.surefire.extensions.ForkNodeFactory;
+import org.apache.maven.surefire.report.ReportEntry;
+import org.apache.maven.surefire.report.RunMode;
 
-import javax.annotation.Nonnegative;
-import javax.annotation.Nonnull;
+import static org.apache.maven.surefire.booter.ForkedProcessEventType.BOOTERCODE_TESTSET_STARTING;
 
 /**
- * The factory of {@link LegacyForkChannel}.
+ * The event for started testset.
+ *
+ * @since 3.0.0-M5
  */
-public class LegacyForkNodeFactory implements ForkNodeFactory
+public final class TestsetStartingEvent extends AbstractTestControlEvent
 {
-    @Nonnull
-    @Override
-    public ForkChannel createForkChannel( @Nonnegative int forkChannelId )
+    public TestsetStartingEvent( RunMode runMode, ReportEntry reportEntry )
     {
-        return new LegacyForkChannel( forkChannelId );
+        super( BOOTERCODE_TESTSET_STARTING, runMode, reportEntry );
     }
 }
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/report/SimpleReportEntry.java b/surefire-api/src/main/java/org/apache/maven/surefire/report/SimpleReportEntry.java
index df7b4c4..948a7d6 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/report/SimpleReportEntry.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/report/SimpleReportEntry.java
@@ -26,6 +26,8 @@ import java.util.Map;
 import java.util.Objects;
 
 /**
+ * Basic implementation of {@link TestSetReportEntry} (immutable and thread-safe object).
+ *
  * @author Kristian Rosenvold
  */
 public class SimpleReportEntry
diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelDecoder.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelDecoder.java
index ca5f4f5..9ac1ca5 100644
--- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelDecoder.java
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelDecoder.java
@@ -20,13 +20,15 @@ package org.apache.maven.surefire.booter.spi;
  */
 
 import org.apache.maven.surefire.booter.Command;
+import org.apache.maven.surefire.booter.DumpErrorSingleton;
 import org.apache.maven.surefire.booter.MasterProcessCommand;
 import org.apache.maven.surefire.providerapi.MasterProcessChannelDecoder;
 
 import javax.annotation.Nonnull;
 import java.io.EOFException;
 import java.io.IOException;
-import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.channels.ReadableByteChannel;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -41,11 +43,11 @@ import static org.apache.maven.surefire.booter.MasterProcessCommand.MAGIC_NUMBER
  */
 public class LegacyMasterProcessChannelDecoder implements MasterProcessChannelDecoder
 {
-    private final InputStream is;
+    private final ReadableByteChannel channel;
 
-    public LegacyMasterProcessChannelDecoder( @Nonnull InputStream is )
+    public LegacyMasterProcessChannelDecoder( @Nonnull ReadableByteChannel channel )
     {
-        this.is = is;
+        this.channel = channel;
     }
 
     @Override
@@ -55,6 +57,7 @@ public class LegacyMasterProcessChannelDecoder implements MasterProcessChannelDe
     {
         List<String> tokens = new ArrayList<>( 3 );
         StringBuilder token = new StringBuilder( MAGIC_NUMBER.length() );
+        ByteBuffer buffer = ByteBuffer.allocate( 1 );
 
         start:
         do
@@ -62,10 +65,13 @@ public class LegacyMasterProcessChannelDecoder implements MasterProcessChannelDe
             boolean endOfStream;
             tokens.clear();
             token.setLength( 0 );
-            boolean frameStarted = false;
-            for ( int r; !( endOfStream = ( r = is.read() ) == -1 ) ; )
+            FrameCompletion completion = null;
+            for ( boolean frameStarted = false; !( endOfStream = channel.read( buffer ) == -1 ) ; completion = null )
             {
-                char c = (char) r;
+                buffer.flip();
+                char c = (char) buffer.get();
+                buffer.clear();
+
                 if ( !frameStarted )
                 {
                     if ( c == ':' )
@@ -81,13 +87,15 @@ public class LegacyMasterProcessChannelDecoder implements MasterProcessChannelDe
                     {
                         tokens.add( token.toString() );
                         token.setLength( 0 );
-                        FrameCompletion completion = frameCompleteness( tokens );
+                        completion = frameCompleteness( tokens );
                         if ( completion == FrameCompletion.COMPLETE )
                         {
                             break;
                         }
                         else if ( completion == FrameCompletion.MALFORMED )
                         {
+                            DumpErrorSingleton.getSingleton()
+                                .dumpStreamText( "Malformed frame with tokens " + tokens );
                             continue start;
                         }
                     }
@@ -98,7 +106,7 @@ public class LegacyMasterProcessChannelDecoder implements MasterProcessChannelDe
                 }
             }
 
-            if ( frameCompleteness( tokens ) == FrameCompletion.COMPLETE )
+            if ( completion == FrameCompletion.COMPLETE )
             {
                 MasterProcessCommand cmd = MasterProcessCommand.byOpcode( tokens.get( 1 ) );
                 if ( tokens.size() == 2 )
diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelEncoder.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelEncoder.java
index 6ad771e..a6f870c 100644
--- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelEncoder.java
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelEncoder.java
@@ -19,50 +19,51 @@ package org.apache.maven.surefire.booter.spi;
  * under the License.
  */
 
+import org.apache.maven.plugin.surefire.log.api.ConsoleLoggerUtils;
 import org.apache.maven.surefire.booter.DumpErrorSingleton;
-import org.apache.maven.surefire.booter.ForkedProcessEvent;
+import org.apache.maven.surefire.booter.ForkedProcessEventType;
 import org.apache.maven.surefire.providerapi.MasterProcessChannelEncoder;
-import org.apache.maven.surefire.shared.codec.binary.Base64;
-import org.apache.maven.plugin.surefire.log.api.ConsoleLoggerUtils;
 import org.apache.maven.surefire.report.ReportEntry;
 import org.apache.maven.surefire.report.RunMode;
 import org.apache.maven.surefire.report.SafeThrowable;
 import org.apache.maven.surefire.report.StackTraceWriter;
+import org.apache.maven.surefire.shared.codec.binary.Base64;
 
 import javax.annotation.Nonnull;
 import java.io.IOException;
-import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.nio.channels.WritableByteChannel;
 import java.nio.charset.Charset;
 import java.util.Map;
 import java.util.Map.Entry;
 
 import static java.nio.charset.StandardCharsets.US_ASCII;
 import static java.nio.charset.StandardCharsets.UTF_8;
-import static org.apache.maven.surefire.booter.ForkedProcessEvent.MAGIC_NUMBER_DELIMITED;
-import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_SYSPROPS;
-import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_STDERR;
-import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_STDERR_NEW_LINE;
-import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_STDOUT;
-import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_STDOUT_NEW_LINE;
-import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_BYE;
-import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_CONSOLE_ERROR;
-import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_CONSOLE_DEBUG;
-import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_CONSOLE_INFO;
-import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_CONSOLE_WARNING;
-import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_NEXT_TEST;
-import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_STOP_ON_NEXT_TEST;
-import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_TEST_ASSUMPTIONFAILURE;
-import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_TEST_ERROR;
-import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_TEST_FAILED;
-import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_TEST_SKIPPED;
-import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_TEST_STARTING;
-import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_TEST_SUCCEEDED;
-import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_TESTSET_COMPLETED;
-import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_TESTSET_STARTING;
-import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_JVM_EXIT_ERROR;
+import static java.util.Objects.requireNonNull;
+import static org.apache.maven.surefire.booter.ForkedProcessEventType.BOOTERCODE_BYE;
+import static org.apache.maven.surefire.booter.ForkedProcessEventType.BOOTERCODE_CONSOLE_DEBUG;
+import static org.apache.maven.surefire.booter.ForkedProcessEventType.BOOTERCODE_CONSOLE_ERROR;
+import static org.apache.maven.surefire.booter.ForkedProcessEventType.BOOTERCODE_CONSOLE_INFO;
+import static org.apache.maven.surefire.booter.ForkedProcessEventType.BOOTERCODE_CONSOLE_WARNING;
+import static org.apache.maven.surefire.booter.ForkedProcessEventType.BOOTERCODE_JVM_EXIT_ERROR;
+import static org.apache.maven.surefire.booter.ForkedProcessEventType.BOOTERCODE_NEXT_TEST;
+import static org.apache.maven.surefire.booter.ForkedProcessEventType.BOOTERCODE_STDERR;
+import static org.apache.maven.surefire.booter.ForkedProcessEventType.BOOTERCODE_STDERR_NEW_LINE;
+import static org.apache.maven.surefire.booter.ForkedProcessEventType.BOOTERCODE_STDOUT;
+import static org.apache.maven.surefire.booter.ForkedProcessEventType.BOOTERCODE_STDOUT_NEW_LINE;
+import static org.apache.maven.surefire.booter.ForkedProcessEventType.BOOTERCODE_STOP_ON_NEXT_TEST;
+import static org.apache.maven.surefire.booter.ForkedProcessEventType.BOOTERCODE_SYSPROPS;
+import static org.apache.maven.surefire.booter.ForkedProcessEventType.BOOTERCODE_TESTSET_COMPLETED;
+import static org.apache.maven.surefire.booter.ForkedProcessEventType.BOOTERCODE_TESTSET_STARTING;
+import static org.apache.maven.surefire.booter.ForkedProcessEventType.BOOTERCODE_TEST_ASSUMPTIONFAILURE;
+import static org.apache.maven.surefire.booter.ForkedProcessEventType.BOOTERCODE_TEST_ERROR;
+import static org.apache.maven.surefire.booter.ForkedProcessEventType.BOOTERCODE_TEST_FAILED;
+import static org.apache.maven.surefire.booter.ForkedProcessEventType.BOOTERCODE_TEST_SKIPPED;
+import static org.apache.maven.surefire.booter.ForkedProcessEventType.BOOTERCODE_TEST_STARTING;
+import static org.apache.maven.surefire.booter.ForkedProcessEventType.BOOTERCODE_TEST_SUCCEEDED;
+import static org.apache.maven.surefire.booter.ForkedProcessEventType.MAGIC_NUMBER;
 import static org.apache.maven.surefire.report.RunMode.NORMAL_RUN;
 import static org.apache.maven.surefire.report.RunMode.RERUN_TEST_AFTER_FAILURE;
-import static java.util.Objects.requireNonNull;
 
 /**
  * magic number : opcode : run mode [: opcode specific data]*
@@ -73,20 +74,21 @@ import static java.util.Objects.requireNonNull;
  */
 public class LegacyMasterProcessChannelEncoder implements MasterProcessChannelEncoder
 {
+    static final String MAGIC_NUMBER_DELIMITED = ':' + MAGIC_NUMBER + ':';
     private static final Base64 BASE64 = new Base64();
     private static final Charset STREAM_ENCODING = US_ASCII;
     private static final Charset STRING_ENCODING = UTF_8;
 
-    protected final OutputStream out;
+    protected final WritableByteChannel out;
     private final RunMode runMode;
     private volatile boolean trouble;
 
-    public LegacyMasterProcessChannelEncoder( @Nonnull OutputStream out )
+    public LegacyMasterProcessChannelEncoder( @Nonnull WritableByteChannel out )
     {
         this( out, NORMAL_RUN );
     }
 
-    protected LegacyMasterProcessChannelEncoder( @Nonnull OutputStream out, @Nonnull RunMode runMode )
+    protected LegacyMasterProcessChannelEncoder( @Nonnull WritableByteChannel out, @Nonnull RunMode runMode )
     {
         this.out = requireNonNull( out );
         this.runMode = requireNonNull( runMode );
@@ -173,14 +175,14 @@ public class LegacyMasterProcessChannelEncoder implements MasterProcessChannelEn
     @Override
     public void stdOut( String msg, boolean newLine )
     {
-        ForkedProcessEvent event = newLine ? BOOTERCODE_STDOUT_NEW_LINE : BOOTERCODE_STDOUT;
+        ForkedProcessEventType event = newLine ? BOOTERCODE_STDOUT_NEW_LINE : BOOTERCODE_STDOUT;
         setOutErr( event.getOpcode(), msg );
     }
 
     @Override
     public void stdErr( String msg, boolean newLine )
     {
-        ForkedProcessEvent event = newLine ? BOOTERCODE_STDERR_NEW_LINE : BOOTERCODE_STDERR;
+        ForkedProcessEventType event = newLine ? BOOTERCODE_STDERR_NEW_LINE : BOOTERCODE_STDERR;
         setOutErr( event.getOpcode(), msg );
     }
 
@@ -263,21 +265,21 @@ public class LegacyMasterProcessChannelEncoder implements MasterProcessChannelEn
         error( stackTraceWriter, trimStackTraces, BOOTERCODE_JVM_EXIT_ERROR );
     }
 
-    private void error( StackTraceWriter stackTraceWriter, boolean trimStackTraces, ForkedProcessEvent event )
+    private void error( StackTraceWriter stackTraceWriter, boolean trimStackTraces, ForkedProcessEventType event )
     {
         StringBuilder encoded = encodeHeader( event.getOpcode(), null );
         encode( encoded, stackTraceWriter, trimStackTraces );
         encodeAndPrintEvent( encoded );
     }
 
-    private void encode( ForkedProcessEvent operation, RunMode runMode, ReportEntry reportEntry,
+    private void encode( ForkedProcessEventType operation, RunMode runMode, ReportEntry reportEntry,
                          boolean trimStackTraces )
     {
         StringBuilder event = encode( operation.getOpcode(), runMode.geRunName(), reportEntry, trimStackTraces );
         encodeAndPrintEvent( event );
     }
 
-    private void encodeOpcode( ForkedProcessEvent operation )
+    private void encodeOpcode( ForkedProcessEventType operation )
     {
         StringBuilder event = encodeOpcode( operation.getOpcode(), null );
         encodeAndPrintEvent( event );
@@ -290,8 +292,7 @@ public class LegacyMasterProcessChannelEncoder implements MasterProcessChannelEn
         {
             try
             {
-                out.write( array );
-                out.flush();
+                out.write( ByteBuffer.wrap( array ) );
             }
             catch ( IOException e )
             {
@@ -301,7 +302,7 @@ public class LegacyMasterProcessChannelEncoder implements MasterProcessChannelEn
         }
     }
 
-    static StringBuilder encode( ForkedProcessEvent operation, RunMode runMode, String... args )
+    static StringBuilder encode( ForkedProcessEventType operation, RunMode runMode, String... args )
     {
         StringBuilder encodedTo = encodeHeader( operation.getOpcode(), runMode.geRunName() )
                                           .append( ':' );
@@ -340,18 +341,17 @@ public class LegacyMasterProcessChannelEncoder implements MasterProcessChannelEn
     /**
      * Used operations:<br>
      * <ul>
-     * <li>{@link ForkedProcessEvent#BOOTERCODE_TESTSET_STARTING},</li>
-     * <li>{@link ForkedProcessEvent#BOOTERCODE_TESTSET_COMPLETED},</li>
-     * <li>{@link ForkedProcessEvent#BOOTERCODE_TEST_STARTING},</li>
-     * <li>{@link ForkedProcessEvent#BOOTERCODE_TEST_SUCCEEDED},</li>
-     * <li>{@link ForkedProcessEvent#BOOTERCODE_TEST_FAILED},</li>
-     * <li>{@link ForkedProcessEvent#BOOTERCODE_TEST_ERROR},</li>
-     * <li>{@link ForkedProcessEvent#BOOTERCODE_TEST_SKIPPED},</li>
-     * <li>{@link ForkedProcessEvent#BOOTERCODE_TEST_ASSUMPTIONFAILURE}.</li>
+     * <li>{@link ForkedProcessEventType#BOOTERCODE_TESTSET_STARTING},</li>
+     * <li>{@link ForkedProcessEventType#BOOTERCODE_TESTSET_COMPLETED},</li>
+     * <li>{@link ForkedProcessEventType#BOOTERCODE_TEST_STARTING},</li>
+     * <li>{@link ForkedProcessEventType#BOOTERCODE_TEST_SUCCEEDED},</li>
+     * <li>{@link ForkedProcessEventType#BOOTERCODE_TEST_FAILED},</li>
+     * <li>{@link ForkedProcessEventType#BOOTERCODE_TEST_ERROR},</li>
+     * <li>{@link ForkedProcessEventType#BOOTERCODE_TEST_SKIPPED},</li>
+     * <li>{@link ForkedProcessEventType#BOOTERCODE_TEST_ASSUMPTIONFAILURE}.</li>
      * </ul>
      */
-    static StringBuilder encode( String operation, String runMode, ReportEntry reportEntry,
-                                         boolean trimStackTraces )
+    static StringBuilder encode( String operation, String runMode, ReportEntry reportEntry, boolean trimStackTraces )
     {
         StringBuilder encodedTo = encodeHeader( operation, runMode )
                 .append( ':' )
@@ -409,7 +409,7 @@ public class LegacyMasterProcessChannelEncoder implements MasterProcessChannelEn
     }
 
     /**
-     * Used in {@link #bye()}, {@link #stopOnNextTest()} and {@link #encodeOpcode(ForkedProcessEvent)}
+     * Used in {@link #bye()}, {@link #stopOnNextTest()} and {@link #encodeOpcode(ForkedProcessEventType)}
      * and private methods extending the buffer.
      *
      * @param operation opcode
diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelProcessorFactory.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelProcessorFactory.java
index b5d4dd5..c6e11da 100644
--- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelProcessorFactory.java
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelProcessorFactory.java
@@ -26,6 +26,8 @@ import org.apache.maven.surefire.spi.MasterProcessChannelProcessorFactory;
 import java.io.IOException;
 import java.net.MalformedURLException;
 
+import static java.nio.channels.Channels.newChannel;
+
 /**
  * Producer of encoder and decoder for process pipes.
  * <br>
@@ -54,13 +56,13 @@ public class LegacyMasterProcessChannelProcessorFactory
     @Override
     public MasterProcessChannelDecoder createDecoder()
     {
-        return new LegacyMasterProcessChannelDecoder( System.in );
+        return new LegacyMasterProcessChannelDecoder( newChannel( System.in ) );
     }
 
     @Override
     public MasterProcessChannelEncoder createEncoder()
     {
-        return new LegacyMasterProcessChannelEncoder( System.out );
+        return new LegacyMasterProcessChannelEncoder( newChannel( System.out ) );
     }
 
     @Override
diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/SurefireMasterProcessChannelProcessorFactory.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/SurefireMasterProcessChannelProcessorFactory.java
index 497e748..1893009 100644
--- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/SurefireMasterProcessChannelProcessorFactory.java
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/SurefireMasterProcessChannelProcessorFactory.java
@@ -28,7 +28,6 @@ import java.net.InetSocketAddress;
 import java.net.MalformedURLException;
 import java.net.URI;
 import java.net.URISyntaxException;
-import java.nio.channels.Channels;
 import java.nio.channels.SocketChannel;
 
 /**
@@ -71,13 +70,13 @@ public class SurefireMasterProcessChannelProcessorFactory
     @Override
     public MasterProcessChannelDecoder createDecoder()
     {
-        return new LegacyMasterProcessChannelDecoder( Channels.newInputStream( channel ) );
+        return new LegacyMasterProcessChannelDecoder( channel );
     }
 
     @Override
     public MasterProcessChannelEncoder createEncoder()
     {
-        return new LegacyMasterProcessChannelEncoder( Channels.newOutputStream( channel ) );
+        return new LegacyMasterProcessChannelEncoder( channel );
     }
 
     @Override
diff --git a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/CommandReaderTest.java b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/CommandReaderTest.java
index 452c0d2..30a2c28 100644
--- a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/CommandReaderTest.java
+++ b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/CommandReaderTest.java
@@ -34,6 +34,7 @@ import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.PrintStream;
+import java.nio.channels.WritableByteChannel;
 import java.util.Iterator;
 import java.util.NoSuchElementException;
 import java.util.concurrent.BlockingQueue;
@@ -43,6 +44,7 @@ import java.util.concurrent.FutureTask;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
 
+import static java.nio.channels.Channels.newChannel;
 import static org.fest.assertions.Assertions.assertThat;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.is;
@@ -89,7 +91,7 @@ public class CommandReaderTest
         InputStream realInputStream = new SystemInputStream();
         addTestToPipeline( getClass().getName() );
         ConsoleLogger logger = new NullConsoleLogger();
-        MasterProcessChannelDecoder decoder = new LegacyMasterProcessChannelDecoder( realInputStream );
+        MasterProcessChannelDecoder decoder = new LegacyMasterProcessChannelDecoder( newChannel( realInputStream ) );
         reader = new CommandReader( decoder, Shutdown.DEFAULT, logger );
     }
 
@@ -260,8 +262,8 @@ public class CommandReaderTest
         }
     }
 
-    private static PrintStream nul()
+    private static WritableByteChannel nul()
     {
-        return new PrintStream( new ByteArrayOutputStream() );
+        return newChannel( new PrintStream( new ByteArrayOutputStream() ) );
     }
 }
diff --git a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelDecoderTest.java b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelDecoderTest.java
index 5cf6b65..b17e4c5 100644
--- a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelDecoderTest.java
+++ b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelDecoderTest.java
@@ -28,6 +28,7 @@ import java.io.EOFException;
 import java.io.IOException;
 import java.io.InputStream;
 
+import static java.nio.channels.Channels.newChannel;
 import static org.apache.maven.surefire.booter.MasterProcessCommand.BYE_ACK;
 import static org.apache.maven.surefire.booter.MasterProcessCommand.NOOP;
 import static org.apache.maven.surefire.booter.MasterProcessCommand.RUN_CLASS;
@@ -56,7 +57,7 @@ public class LegacyMasterProcessChannelDecoderTest
             .isEqualTo( ":maven-surefire-command:run-testclass:pkg.Test:" );
         byte[] line = addNL( encoded, '\n' );
         InputStream is = new ByteArrayInputStream( line );
-        LegacyMasterProcessChannelDecoder decoder = new LegacyMasterProcessChannelDecoder( is );
+        LegacyMasterProcessChannelDecoder decoder = new LegacyMasterProcessChannelDecoder( newChannel( is ) );
         Command command = decoder.decode();
         assertThat( command.getCommandType() ).isSameAs( RUN_CLASS );
         assertThat( command.getData() ).isEqualTo( "pkg.Test" );
@@ -72,7 +73,7 @@ public class LegacyMasterProcessChannelDecoderTest
         assertThat( new String( encoded ) )
             .isEqualTo( ":maven-surefire-command:testset-finished:" );
         ByteArrayInputStream is = new ByteArrayInputStream( encoded );
-        LegacyMasterProcessChannelDecoder decoder = new LegacyMasterProcessChannelDecoder( is );
+        LegacyMasterProcessChannelDecoder decoder = new LegacyMasterProcessChannelDecoder( newChannel( is ) );
         command = decoder.decode();
         assertThat( command.getCommandType() ).isSameAs( TEST_SET_FINISHED );
         assertNull( command.getData() );
@@ -88,7 +89,7 @@ public class LegacyMasterProcessChannelDecoderTest
         assertThat( new String( encoded ) )
             .isEqualTo( ":maven-surefire-command:skip-since-next-test:" );
         ByteArrayInputStream is = new ByteArrayInputStream( encoded );
-        LegacyMasterProcessChannelDecoder decoder = new LegacyMasterProcessChannelDecoder( is );
+        LegacyMasterProcessChannelDecoder decoder = new LegacyMasterProcessChannelDecoder( newChannel( is ) );
         command = decoder.decode();
         assertThat( command.getCommandType() ).isSameAs( SKIP_SINCE_NEXT_TEST );
         assertNull( command.getData() );
@@ -103,7 +104,7 @@ public class LegacyMasterProcessChannelDecoderTest
         assertThat( new String( encoded ) )
             .isEqualTo( ":maven-surefire-command:shutdown:" + shutdownType + ":" );
         ByteArrayInputStream is = new ByteArrayInputStream( encoded );
-        LegacyMasterProcessChannelDecoder decoder = new LegacyMasterProcessChannelDecoder( is );
+        LegacyMasterProcessChannelDecoder decoder = new LegacyMasterProcessChannelDecoder( newChannel( is ) );
         Command command = decoder.decode();
         assertThat( command.getCommandType() ).isSameAs( SHUTDOWN );
         assertThat( command.getData() ).isEqualTo( shutdownType.name() );
@@ -118,7 +119,7 @@ public class LegacyMasterProcessChannelDecoderTest
         assertThat( new String( encoded ) )
             .isEqualTo( ":maven-surefire-command:shutdown:" + shutdownType + ":" );
         ByteArrayInputStream is = new ByteArrayInputStream( encoded );
-        LegacyMasterProcessChannelDecoder decoder = new LegacyMasterProcessChannelDecoder( is );
+        LegacyMasterProcessChannelDecoder decoder = new LegacyMasterProcessChannelDecoder( newChannel( is ) );
         Command command = decoder.decode();
         assertThat( command.getCommandType() ).isSameAs( SHUTDOWN );
         assertThat( command.getData() ).isEqualTo( shutdownType.name() );
@@ -133,7 +134,7 @@ public class LegacyMasterProcessChannelDecoderTest
         assertThat( new String( encoded ) )
             .isEqualTo( ":maven-surefire-command:shutdown:" + shutdownType + ":" );
         ByteArrayInputStream is = new ByteArrayInputStream( encoded );
-        LegacyMasterProcessChannelDecoder decoder = new LegacyMasterProcessChannelDecoder( is );
+        LegacyMasterProcessChannelDecoder decoder = new LegacyMasterProcessChannelDecoder( newChannel( is ) );
         Command command = decoder.decode();
         assertThat( command.getCommandType() ).isSameAs( SHUTDOWN );
         assertThat( command.getData() ).isEqualTo( shutdownType.name() );
@@ -148,7 +149,7 @@ public class LegacyMasterProcessChannelDecoderTest
         assertThat( new String( encoded ) )
             .isEqualTo( ":maven-surefire-command:noop:" );
         ByteArrayInputStream is = new ByteArrayInputStream( encoded );
-        LegacyMasterProcessChannelDecoder decoder = new LegacyMasterProcessChannelDecoder( is );
+        LegacyMasterProcessChannelDecoder decoder = new LegacyMasterProcessChannelDecoder( newChannel( is ) );
         Command command = decoder.decode();
         assertThat( command.getCommandType() ).isSameAs( NOOP );
         assertNull( command.getData() );
@@ -164,7 +165,7 @@ public class LegacyMasterProcessChannelDecoderTest
             .isEqualTo( ":maven-surefire-command:bye-ack:" );
         byte[] streamContent = ( "<something>" + new String( encoded ) + "<damaged>" ).getBytes();
         ByteArrayInputStream is = new ByteArrayInputStream( streamContent );
-        LegacyMasterProcessChannelDecoder decoder = new LegacyMasterProcessChannelDecoder( is );
+        LegacyMasterProcessChannelDecoder decoder = new LegacyMasterProcessChannelDecoder( newChannel( is ) );
         Command command = decoder.decode();
         assertThat( command.getCommandType() ).isSameAs( BYE_ACK );
         assertNull( command.getData() );
@@ -180,7 +181,7 @@ public class LegacyMasterProcessChannelDecoderTest
             .isEqualTo( ":maven-surefire-command:bye-ack:" );
         byte[] streamContent = ( ":<damaged>:" + new String( encoded ) ).getBytes();
         ByteArrayInputStream is = new ByteArrayInputStream( streamContent );
-        LegacyMasterProcessChannelDecoder decoder = new LegacyMasterProcessChannelDecoder( is );
+        LegacyMasterProcessChannelDecoder decoder = new LegacyMasterProcessChannelDecoder( newChannel( is ) );
         Command command = decoder.decode();
         assertThat( command.getCommandType() ).isSameAs( BYE_ACK );
         assertNull( command.getData() );
@@ -195,7 +196,7 @@ public class LegacyMasterProcessChannelDecoderTest
         assertThat( new String( encoded ) )
             .isEqualTo( ":maven-surefire-command:bye-ack:" );
         ByteArrayInputStream is = new ByteArrayInputStream( encoded );
-        LegacyMasterProcessChannelDecoder decoder = new LegacyMasterProcessChannelDecoder( is );
+        LegacyMasterProcessChannelDecoder decoder = new LegacyMasterProcessChannelDecoder( newChannel( is ) );
         Command command = decoder.decode();
         assertThat( command.getCommandType() ).isSameAs( BYE_ACK );
         assertNull( command.getData() );
@@ -206,7 +207,7 @@ public class LegacyMasterProcessChannelDecoderTest
     {
         String cmd = ":maven-surefire-command:bye-ack:\r\n:maven-surefire-command:bye-ack:";
         InputStream is = new ByteArrayInputStream( cmd.getBytes() );
-        LegacyMasterProcessChannelDecoder decoder = new LegacyMasterProcessChannelDecoder( is );
+        LegacyMasterProcessChannelDecoder decoder = new LegacyMasterProcessChannelDecoder( newChannel( is ) );
 
         Command command = decoder.decode();
         assertThat( command.getCommandType() ).isEqualTo( BYE_ACK );
@@ -224,7 +225,7 @@ public class LegacyMasterProcessChannelDecoderTest
     {
 
         ByteArrayInputStream is = new ByteArrayInputStream( ":maven-surefire-command:".getBytes() );
-        LegacyMasterProcessChannelDecoder decoder = new LegacyMasterProcessChannelDecoder( is );
+        LegacyMasterProcessChannelDecoder decoder = new LegacyMasterProcessChannelDecoder( newChannel( is ) );
         decoder.decode();
         fail();
     }
@@ -234,7 +235,7 @@ public class LegacyMasterProcessChannelDecoderTest
     {
 
         ByteArrayInputStream is = new ByteArrayInputStream( new byte[] {':', '\r'} );
-        LegacyMasterProcessChannelDecoder decoder = new LegacyMasterProcessChannelDecoder( is );
+        LegacyMasterProcessChannelDecoder decoder = new LegacyMasterProcessChannelDecoder( newChannel( is ) );
         decoder.decode();
         fail();
     }
@@ -244,7 +245,7 @@ public class LegacyMasterProcessChannelDecoderTest
     {
         String cmd = ":maven-surefire-command:bye-ack ::maven-surefire-command:";
         InputStream is = new ByteArrayInputStream( cmd.getBytes() );
-        LegacyMasterProcessChannelDecoder decoder = new LegacyMasterProcessChannelDecoder( is );
+        LegacyMasterProcessChannelDecoder decoder = new LegacyMasterProcessChannelDecoder( newChannel( is ) );
 
         decoder.decode();
     }
@@ -254,7 +255,7 @@ public class LegacyMasterProcessChannelDecoderTest
     {
         String cmd = ":maven-surefire-command:bye-ack\r\n::maven-surefire-command:noop:";
         InputStream is = new ByteArrayInputStream( cmd.getBytes() );
-        LegacyMasterProcessChannelDecoder decoder = new LegacyMasterProcessChannelDecoder( is );
+        LegacyMasterProcessChannelDecoder decoder = new LegacyMasterProcessChannelDecoder( newChannel( is ) );
 
         Command command = decoder.decode();
         assertThat( command.getCommandType() ).isSameAs( NOOP );
diff --git a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelEncoderTest.java b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelEncoderTest.java
index eaafd6e..ff70656 100644
--- a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelEncoderTest.java
+++ b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelEncoderTest.java
@@ -34,15 +34,17 @@ import java.nio.ByteBuffer;
 import java.nio.charset.Charset;
 import java.util.Map;
 
+import static java.nio.channels.Channels.newChannel;
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static java.util.Arrays.copyOfRange;
-import static org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelEncoder.encode;
+import static org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelEncoder.MAGIC_NUMBER_DELIMITED;
+import static org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelEncoder.toBase64;
 import static org.apache.maven.surefire.shared.codec.binary.Base64.encodeBase64String;
+import static org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelEncoder.encode;
 import static org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelEncoder.encodeHeader;
 import static org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelEncoder.encodeMessage;
 import static org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelEncoder.encodeOpcode;
-import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_SYSPROPS;
-import static org.apache.maven.surefire.booter.ForkedProcessEvent.MAGIC_NUMBER_DELIMITED;
+import static org.apache.maven.surefire.booter.ForkedProcessEventType.BOOTERCODE_SYSPROPS;
 import static org.apache.maven.surefire.report.RunMode.NORMAL_RUN;
 import static org.fest.assertions.Assertions.assertThat;
 import static org.mockito.Mockito.mock;
@@ -61,8 +63,8 @@ public class LegacyMasterProcessChannelEncoderTest
     @Test
     public void shouldBeFailSafe()
     {
-        assertThat( LegacyMasterProcessChannelEncoder.toBase64( null ) ).isEqualTo( "-" );
-        assertThat( LegacyMasterProcessChannelEncoder.toBase64( "" ) ).isEqualTo( "" );
+        assertThat( toBase64( null ) ).isEqualTo( "-" );
+        assertThat( toBase64( "" ) ).isEqualTo( "" );
     }
 
     @Test
@@ -229,7 +231,7 @@ public class LegacyMasterProcessChannelEncoderTest
                 );
 
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( out );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
 
         encoder.testSetStarting( reportEntry, true );
         LineNumberReader printedLines = out.newReader( UTF_8 );
@@ -259,7 +261,7 @@ public class LegacyMasterProcessChannelEncoderTest
         assertThat( printedLines.readLine() ).isNull();
 
         out = Stream.newStream();
-        encoder = new LegacyMasterProcessChannelEncoder( out );
+        encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
 
         encoder.testSetStarting( reportEntry, false );
         printedLines = out.newReader( UTF_8 );
@@ -326,7 +328,7 @@ public class LegacyMasterProcessChannelEncoderTest
         String encodedMessage = encodeBase64String( toArray( UTF_8.encode( reportEntry.getMessage() ) ) );
 
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( out );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
 
         encoder.testSetCompleted( reportEntry, false );
         LineNumberReader printedLines = out.newReader( UTF_8 );
@@ -393,7 +395,7 @@ public class LegacyMasterProcessChannelEncoderTest
         String encodedMessage = encodeBase64String( toArray( UTF_8.encode( reportEntry.getMessage() ) ) );
 
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( out );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
 
         encoder.testStarting( reportEntry, true );
         LineNumberReader printedLines = out.newReader( UTF_8 );
@@ -460,7 +462,7 @@ public class LegacyMasterProcessChannelEncoderTest
         String encodedMessage = encodeBase64String( toArray( UTF_8.encode( reportEntry.getMessage() ) ) );
 
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( out );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
 
         encoder.testSucceeded( reportEntry, true );
         LineNumberReader printedLines = out.newReader( UTF_8 );
@@ -527,7 +529,7 @@ public class LegacyMasterProcessChannelEncoderTest
         String encodedMessage = encodeBase64String( toArray( UTF_8.encode( reportEntry.getMessage() ) ) );
 
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( out );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
 
         encoder.testFailed( reportEntry, false );
         LineNumberReader printedLines = out.newReader( UTF_8 );
@@ -593,7 +595,7 @@ public class LegacyMasterProcessChannelEncoderTest
         String encodedMessage = encodeBase64String( toArray( UTF_8.encode( reportEntry.getMessage() ) ) );
 
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( out );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
 
         encoder.testSkipped( reportEntry, false );
         LineNumberReader printedLines = out.newReader( UTF_8 );
@@ -658,7 +660,7 @@ public class LegacyMasterProcessChannelEncoderTest
         String encodedMessage = encodeBase64String( toArray( UTF_8.encode( reportEntry.getMessage() ) ) );
 
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( out );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
 
         encoder.testError( reportEntry, false );
         LineNumberReader printedLines = out.newReader( UTF_8 );
@@ -721,7 +723,7 @@ public class LegacyMasterProcessChannelEncoderTest
         String encodedMessage = encodeBase64String( toArray( UTF_8.encode( reportEntry.getMessage() ) ) );
 
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( out );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
 
         encoder.testAssumptionFailure( reportEntry, false );
         LineNumberReader printedLines = out.newReader( UTF_8 );
@@ -755,7 +757,7 @@ public class LegacyMasterProcessChannelEncoderTest
     public void testBye() throws IOException
     {
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( out );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
 
         encoder.bye();
         LineNumberReader printedLines = out.newReader( UTF_8 );
@@ -768,7 +770,7 @@ public class LegacyMasterProcessChannelEncoderTest
     public void testStopOnNextTest() throws IOException
     {
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( out );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
 
         encoder.stopOnNextTest();
         LineNumberReader printedLines = out.newReader( UTF_8 );
@@ -781,7 +783,7 @@ public class LegacyMasterProcessChannelEncoderTest
     public void testAcquireNextTest() throws IOException
     {
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( out );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
 
         encoder.acquireNextTest();
         LineNumberReader printedLines = out.newReader( UTF_8 );
@@ -806,7 +808,7 @@ public class LegacyMasterProcessChannelEncoderTest
                 .isEqualTo( ":maven-surefire-event:some-opcode:normal-run:UTF-8:msg" );
 
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( out );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
         encoded = encoder.print( "some-opcode", "msg" );
         assertThat( encoded.toString() )
                 .isEqualTo( ":maven-surefire-event:some-opcode:UTF-8:bXNn" );
@@ -820,7 +822,7 @@ public class LegacyMasterProcessChannelEncoderTest
     public void testConsoleInfo()
     {
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( out );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
 
         encoder.consoleInfoLog( "msg" );
 
@@ -838,7 +840,7 @@ public class LegacyMasterProcessChannelEncoderTest
     public void testConsoleError()
     {
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( out );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
 
         encoder.consoleErrorLog( "msg" );
 
@@ -856,7 +858,7 @@ public class LegacyMasterProcessChannelEncoderTest
     public void testConsoleErrorLog1() throws IOException
     {
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( out );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
 
         encoder.consoleErrorLog( new Exception( "msg" ) );
         LineNumberReader printedLines = out.newReader( UTF_8 );
@@ -869,7 +871,7 @@ public class LegacyMasterProcessChannelEncoderTest
     public void testConsoleErrorLog2() throws IOException
     {
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( out );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
 
         encoder.consoleErrorLog( "msg2", new Exception( "msg" ) );
         LineNumberReader printedLines = out.newReader( UTF_8 );
@@ -882,7 +884,7 @@ public class LegacyMasterProcessChannelEncoderTest
     public void testConsoleErrorLog3() throws IOException
     {
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( out );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
 
         StackTraceWriter stackTraceWriter = mock( StackTraceWriter.class );
         when( stackTraceWriter.getThrowable() ).thenReturn( new SafeThrowable( "1" ) );
@@ -901,7 +903,7 @@ public class LegacyMasterProcessChannelEncoderTest
     public void testConsoleDebug()
     {
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( out );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
 
         encoder.consoleDebugLog( "msg" );
 
@@ -919,7 +921,7 @@ public class LegacyMasterProcessChannelEncoderTest
     public void testConsoleWarning()
     {
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( out );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
 
         encoder.consoleWarningLog( "msg" );
 
@@ -937,7 +939,7 @@ public class LegacyMasterProcessChannelEncoderTest
     public void testStdOutStream() throws IOException
     {
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( out );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
 
         encoder.stdOut( "msg", false );
 
@@ -954,7 +956,7 @@ public class LegacyMasterProcessChannelEncoderTest
     public void testStdOutStreamLn() throws IOException
     {
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( out );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
 
         encoder.stdOut( "msg", true );
 
@@ -971,7 +973,7 @@ public class LegacyMasterProcessChannelEncoderTest
     public void testStdErrStream() throws IOException
     {
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( out );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
 
         encoder.stdErr( "msg", false );
 
@@ -988,7 +990,7 @@ public class LegacyMasterProcessChannelEncoderTest
     public void testStdErrStreamLn() throws IOException
     {
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( out );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
 
         encoder.stdErr( "msg", true );
 
@@ -1006,7 +1008,7 @@ public class LegacyMasterProcessChannelEncoderTest
     public void shouldCountSameNumberOfSystemProperties() throws IOException
     {
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( out );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
 
         Map<String, String> sysProps = ObjectUtils.systemProps();
         int expectedSize = sysProps.size();
@@ -1030,7 +1032,7 @@ public class LegacyMasterProcessChannelEncoderTest
     {
         Stream out = Stream.newStream();
 
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( out );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
         StackTraceWriter stackTraceWriter = mock( StackTraceWriter.class );
         when( stackTraceWriter.getThrowable() ).thenReturn( new SafeThrowable( "1" ) );
         when( stackTraceWriter.smartTrimmedStackTrace() ).thenReturn( "2" );
@@ -1048,7 +1050,7 @@ public class LegacyMasterProcessChannelEncoderTest
     {
         Stream out = Stream.newStream();
 
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( out );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
         StackTraceWriter stackTraceWriter = mock( StackTraceWriter.class );
         when( stackTraceWriter.getThrowable() ).thenReturn( new SafeThrowable( "1" ) );
         when( stackTraceWriter.smartTrimmedStackTrace() ).thenReturn( "2" );
diff --git a/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/EventHandler.java b/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/EventHandler.java
index bcb6c81..7ded309 100644
--- a/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/EventHandler.java
+++ b/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/EventHandler.java
@@ -22,11 +22,11 @@ package org.apache.maven.surefire.extensions;
 import javax.annotation.Nonnull;
 
 /**
+ * Handler of events.
  *
+ * @param <T> generic type of the event object
  */
-public interface EventHandler
+public interface EventHandler<T>
 {
-    // todo here should be Event object as POJO which separates physical stream representation from the independent
-    // todo Event parser in ForkClient. So the parser should be part of ForkChannel implementation.
-    void handleEvent( @Nonnull String event );
+    void handleEvent( @Nonnull T event );
 }
diff --git a/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/ForkChannel.java b/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/ForkChannel.java
index f264647..ea6b0b9 100644
--- a/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/ForkChannel.java
+++ b/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/ForkChannel.java
@@ -19,6 +19,7 @@ package org.apache.maven.surefire.extensions;
  * under the License.
  */
 
+import org.apache.maven.surefire.eventapi.Event;
 import org.apache.maven.surefire.extensions.util.CountdownCloseable;
 
 import javax.annotation.Nonnull;
@@ -90,7 +91,7 @@ public abstract class ForkChannel implements Closeable
      * @return the thread instance to start up in order to stream out the data
      * @throws IOException if an error in the fork channel
      */
-    public abstract CloseableDaemonThread bindEventHandler( @Nonnull EventHandler eventHandler,
+    public abstract CloseableDaemonThread bindEventHandler( @Nonnull EventHandler<Event> eventHandler,
                                                             @Nonnull CountdownCloseable countdownCloseable,
                                                             ReadableByteChannel stdOut )
         throws IOException;
diff --git a/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/ForkNodeFactory.java b/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/ForkNodeFactory.java
index a460482..5161261 100644
--- a/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/ForkNodeFactory.java
+++ b/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/ForkNodeFactory.java
@@ -19,6 +19,8 @@ package org.apache.maven.surefire.extensions;
  * under the License.
  */
 
+import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
+
 import javax.annotation.Nonnegative;
 import javax.annotation.Nonnull;
 import java.io.IOException;
@@ -34,8 +36,10 @@ public interface ForkNodeFactory
     /**
      * Opens and closes the channel.
      *
+     * @param forkChannelId the ID of forked JVM
+     * @param logger the console logger
      * @return specific implementation of the communication channel
      * @throws IOException if cannot open the channel
      */
-    @Nonnull ForkChannel createForkChannel( @Nonnegative int forkChannelId ) throws IOException;
+    @Nonnull ForkChannel createForkChannel( @Nonnegative int forkChannelId, ConsoleLogger logger ) throws IOException;
 }
diff --git a/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/util/LineConsumerThread.java b/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/util/LineConsumerThread.java
index 3ed57fa..4cb1e88 100644
--- a/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/util/LineConsumerThread.java
+++ b/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/util/LineConsumerThread.java
@@ -35,19 +35,19 @@ public final class LineConsumerThread extends CloseableDaemonThread
 {
     private final Charset encoding;
     private final ReadableByteChannel channel;
-    private final EventHandler eventHandler;
+    private final EventHandler<String> eventHandler;
     private final CountdownCloseable countdownCloseable;
     private volatile boolean disabled;
 
     public LineConsumerThread( @Nonnull String threadName,
-                               @Nonnull ReadableByteChannel channel, @Nonnull EventHandler eventHandler,
+                               @Nonnull ReadableByteChannel channel, @Nonnull EventHandler<String> eventHandler,
                                @Nonnull CountdownCloseable countdownCloseable )
     {
         this( threadName, channel, eventHandler, countdownCloseable, Charset.defaultCharset() );
     }
 
     public LineConsumerThread( @Nonnull String threadName,
-                               @Nonnull ReadableByteChannel channel, @Nonnull EventHandler eventHandler,
+                               @Nonnull ReadableByteChannel channel, @Nonnull EventHandler<String> eventHandler,
                                @Nonnull CountdownCloseable countdownCloseable, @Nonnull Charset encoding )
     {
         super( threadName );
diff --git a/surefire-extensions-api/src/test/java/org/apache/maven/surefire/extensions/util/CommandlineExecutorTest.java b/surefire-extensions-api/src/test/java/org/apache/maven/surefire/extensions/util/CommandlineExecutorTest.java
index 1a1b93d..15f8ab8 100644
--- a/surefire-extensions-api/src/test/java/org/apache/maven/surefire/extensions/util/CommandlineExecutorTest.java
+++ b/surefire-extensions-api/src/test/java/org/apache/maven/surefire/extensions/util/CommandlineExecutorTest.java
@@ -90,7 +90,8 @@ public class CommandlineExecutorTest
         CountdownCloseable countdownCloseable = new CountdownCloseable( closer, 1 );
         exec = new CommandlineExecutor( cli, countdownCloseable );
         streams = exec.execute();
-        EventHandler consumer = mock( EventHandler.class );
+        @SuppressWarnings( "unchecked" )
+        EventHandler<String> consumer = mock( EventHandler.class );
         CommandReader commandReader = new CommandReader()
         {
             @Override