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/12 00:32:42 UTC

[maven-surefire] 01/01: [SUREFIRE-1658] TCP/IP Channel for forked Surefire JVM. Extensions API and SPI. Polymorphism for remote and local process communication.

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

commit 7545eabee83824159bb8286d701c7fced21e2fe7
Author: tibordigana <ti...@apache.org>
AuthorDate: Sat Jul 6 17:51:13 2019 +0200

    [SUREFIRE-1658] TCP/IP Channel for forked Surefire JVM. Extensions API and SPI. Polymorphism for remote and local process communication.
---
 .github/workflows/maven.yml                        |   2 +-
 Jenkinsfile                                        |   6 +-
 .../maven/plugin/failsafe/IntegrationTestMojo.java |  10 +
 maven-surefire-common/pom.xml                      |  16 -
 .../plugin/surefire/AbstractSurefireMojo.java      |  72 ++--
 .../maven/plugin/surefire/CommonReflector.java     |  18 +-
 .../surefire/SurefireDependencyResolver.java       |   1 +
 .../AbstractClasspathForkConfiguration.java        |   7 +-
 .../surefire/booterclient/BooterSerializer.java    |   5 +-
 .../booterclient/ClasspathForkConfiguration.java   |   7 +-
 .../booterclient/DefaultForkConfiguration.java     |  13 +-
 .../surefire/booterclient/ForkConfiguration.java   |   2 +
 .../plugin/surefire/booterclient/ForkStarter.java  |  96 +++--
 .../booterclient/JarManifestForkConfiguration.java |   7 +-
 .../ModularClasspathForkConfiguration.java         |   7 +-
 ...InputStream.java => AbstractCommandReader.java} |  16 +-
 ...ommandStream.java => DefaultCommandReader.java} |  60 +--
 .../DefferedChannelCommandSender.java              |  14 +-
 .../lazytestprovider/TestLessInputStream.java      |   7 +-
 .../lazytestprovider/TestProvidingInputStream.java |  14 +-
 .../surefire/booterclient/output/ForkClient.java   |   3 +-
 .../booterclient/output/ForkedChannelDecoder.java  |   6 +-
 .../output/NativeStdErrStreamConsumer.java         |   4 +-
 .../output/NativeStdOutStreamConsumer.java         |  28 +-
 .../output/ThreadedStreamConsumer.java             |  13 +-
 .../surefire/extensions/LegacyForkChannel.java     |  98 +++++
 .../surefire/extensions/LegacyForkNodeFactory.java |  19 +-
 .../surefire/extensions/SurefireForkChannel.java   | 140 +++++++
 .../extensions/SurefireForkNodeFactory.java        |  21 +-
 .../AbstractSurefireMojoJava7PlusTest.java         |  27 +-
 .../plugin/surefire/AbstractSurefireMojoTest.java  |  44 ++-
 .../maven/plugin/surefire/CommonReflectorTest.java |  50 +++
 .../maven/plugin/surefire/MojoMocklessTest.java    |   7 +
 .../plugin/surefire/SurefireReflectorTest.java     |  71 ----
 ...ooterDeserializerProviderConfigurationTest.java |   8 +-
 ...BooterDeserializerStartupConfigurationTest.java |  23 +-
 .../booterclient/DefaultForkConfigurationTest.java |  48 ++-
 .../booterclient/ForkConfigurationTest.java        |  17 +-
 .../surefire/booterclient/ForkStarterTest.java     |  12 +-
 .../booterclient/ForkingRunListenerTest.java       |  12 +-
 .../plugin/surefire/booterclient/MainClass.java    |  14 +-
 .../ModularClasspathForkConfigurationTest.java     |  10 +-
 .../TestLessInputStreamBuilderTest.java            |  59 ++-
 .../TestProvidingInputStreamTest.java              | 150 ++++++--
 .../booterclient/output/ForkClientTest.java        |  54 +--
 .../output/ForkedChannelDecoderTest.java           |  90 ++---
 .../org/apache/maven/surefire/JUnit4SuiteTest.java |   4 +-
 .../maven/surefire/extensions/ForkChannelTest.java | 146 ++++++++
 .../maven/plugin/surefire/SurefirePlugin.java      |  10 +
 pom.xml                                            |   5 +-
 .../maven/surefire/booter/BaseProviderFactory.java |  79 ++--
 .../org/apache/maven/surefire/booter/Command.java  |  21 +-
 .../maven/surefire/booter/ForkedProcessEvent.java  |   4 +-
 .../surefire/booter/ForkingReporterFactory.java    |   5 +-
 .../maven/surefire/booter/ForkingRunListener.java  |   5 +-
 .../surefire/booter/MasterProcessCommand.java      | 159 +++-----
 .../surefire/booter/RunOrderParametersAware.java   |  30 --
 .../surefire/booter/TestArtifactInfoAware.java     |  30 --
 .../CommandChainReader.java}                       |  19 +-
 .../{booter => providerapi}/CommandListener.java   |   4 +-
 .../providerapi/MasterProcessChannelDecoder.java   |  47 +++
 .../providerapi/MasterProcessChannelEncoder.java   |  84 +++++
 .../surefire/providerapi/ProviderParameters.java   |   8 +-
 .../org/apache/maven/surefire/suite/RunResult.java |   2 +-
 .../maven/surefire/testset/TestListResolver.java   |   2 +-
 .../maven/surefire/util/ReflectionUtils.java       |  31 +-
 .../util/internal/DaemonThreadFactory.java         |  35 +-
 .../java/org/apache/maven/JUnit4SuiteTest.java     |  10 +-
 .../surefire/booter/ForkingRunListenerTest.java    |  25 +-
 .../surefire/booter/MasterProcessCommandTest.java  | 164 ---------
 .../surefire/booter/SurefireReflectorTest.java     | 198 ----------
 surefire-booter/pom.xml                            |  37 +-
 .../maven/surefire/booter/BooterConstants.java     |   1 +
 .../maven/surefire/booter/BooterDeserializer.java  |  14 +
 .../maven/surefire/booter/CommandReader.java       | 127 +++----
 .../apache/maven/surefire/booter/ForkedBooter.java |  78 +++-
 .../maven/surefire/booter/LazyTestsToRun.java      |  13 +-
 .../surefire/booter/ProviderConfiguration.java     |   2 -
 .../maven/surefire/booter/ProviderFactory.java     |   4 +-
 .../surefire/booter/StartupConfiguration.java      |  22 +-
 .../maven/surefire/booter/SurefireReflector.java   | 120 ++----
 .../spi/LegacyMasterProcessChannelDecoder.java     | 166 +++++++++
 .../spi/LegacyMasterProcessChannelEncoder.java     |  50 ++-
 ...LegacyMasterProcessChannelProcessorFactory.java |  70 ++++
 ...MasterProcessCommandNoMagicNumberException.java |  17 +-
 .../spi/MasterProcessUnknownCommandException.java  |  18 +-
 ...refireMasterProcessChannelProcessorFactory.java |  91 +++++
 ...refire.spi.MasterProcessChannelProcessorFactory |  32 +-
 .../surefire/booter/BooterDeserializerTest.java    |   2 +-
 .../maven/surefire/booter/CommandReaderTest.java   |  49 +--
 .../java/org/apache/maven/surefire/booter/Foo.java |  48 +--
 .../surefire/booter/ForkedBooterMockTest.java      |  55 ++-
 .../surefire/booter/IsolatedClassLoaderTest.java   |  66 ++++
 .../maven/surefire/booter/JUnit4SuiteTest.java     |   7 +
 .../surefire/booter/NewClassLoaderRunner.java      |   0
 .../surefire/booter/SurefireReflectorTest.java     | 408 +++++++++++++++++++++
 .../spi/LegacyMasterProcessChannelDecoderTest.java | 150 ++++++++
 .../spi/LegacyMasterProcessChannelEncoderTest.java | 195 +++++-----
 surefire-extensions-api/pom.xml                    |  38 +-
 .../surefire/extensions/CloseableDaemonThread.java |  17 +-
 .../maven/surefire/extensions/CommandReader.java   |  25 +-
 .../maven/surefire/extensions/EventHandler.java    |  15 +-
 .../maven/surefire/extensions/ForkChannel.java     | 138 +++++++
 .../maven/surefire/extensions/ForkNodeFactory.java |  22 +-
 .../surefire/extensions/StdErrStreamLine.java      |   9 +-
 .../surefire/extensions/StdOutStreamLine.java      |   8 +-
 .../extensions/util/CountdownCloseable.java        |   7 +-
 .../extensions/util/LineConsumerThread.java        |   7 +-
 .../surefire/extensions/util/StreamFeeder.java     |  27 +-
 .../extensions/util/CommandlineExecutorTest.java   |  29 +-
 surefire-extensions-spi/pom.xml                    |  42 +++
 .../spi/MasterProcessChannelProcessorFactory.java  |  62 ++++
 surefire-logger-api/pom.xml                        |   2 +-
 .../maven/surefire/junit4/JUnit4Provider.java      |   9 +-
 .../maven/surefire/junit4/JUnit4ProviderTest.java  |   2 +-
 .../surefire/junitcore/JUnitCoreProvider.java      |   9 +-
 .../maven/surefire/junitcore/Surefire746Test.java  |   3 +-
 .../maven/surefire/testng/TestNGProvider.java      |   9 +-
 surefire-shadefire/pom.xml                         |   2 +
 119 files changed, 3224 insertions(+), 1635 deletions(-)

diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml
index 0d56bb9..9bfd417 100644
--- a/.github/workflows/maven.yml
+++ b/.github/workflows/maven.yml
@@ -39,4 +39,4 @@ jobs:
           java-version: 1.8
 
       - name: Build with Maven
-        run: mvn install -e -B -V -nsu --no-transfer-progress -P run-its
+        run: mvn install -e -B -V -nsu --no-transfer-progress -P run-its
\ No newline at end of file
diff --git a/Jenkinsfile b/Jenkinsfile
index a27c0f9..3cf852e 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -21,10 +21,10 @@
 
 properties(
     [
-        buildDiscarder(logRotator(artifactDaysToKeepStr: env.BRANCH_NAME == 'master' ? '1' : '2',
+        buildDiscarder(logRotator(artifactDaysToKeepStr: env.BRANCH_NAME == 'master' ? '14' : '7',
                                   artifactNumToKeepStr: '50',
-                                  daysToKeepStr: env.BRANCH_NAME == 'master' ? '10' : '5',
-                                  numToKeepStr: env.BRANCH_NAME == 'master' ? '5' : '3')
+                                  daysToKeepStr: env.BRANCH_NAME == 'master' ? '30' : '14',
+                                  numToKeepStr: env.BRANCH_NAME == 'master' ? '20' : '10')
         ),
         disableConcurrentBuilds()
     ]
diff --git a/maven-failsafe-plugin/src/main/java/org/apache/maven/plugin/failsafe/IntegrationTestMojo.java b/maven-failsafe-plugin/src/main/java/org/apache/maven/plugin/failsafe/IntegrationTestMojo.java
index e465bb2..a6771bc 100644
--- a/maven-failsafe-plugin/src/main/java/org/apache/maven/plugin/failsafe/IntegrationTestMojo.java
+++ b/maven-failsafe-plugin/src/main/java/org/apache/maven/plugin/failsafe/IntegrationTestMojo.java
@@ -27,6 +27,7 @@ import org.apache.maven.plugins.annotations.LifecyclePhase;
 import org.apache.maven.plugins.annotations.Mojo;
 import org.apache.maven.plugins.annotations.Parameter;
 import org.apache.maven.plugins.annotations.ResolutionScope;
+import org.apache.maven.surefire.extensions.ForkNodeFactory;
 import org.apache.maven.surefire.suite.RunResult;
 
 import java.io.File;
@@ -384,6 +385,9 @@ public class IntegrationTestMojo
     @Parameter( property = "failsafe.useModulePath", defaultValue = "true" )
     private boolean useModulePath;
 
+    @Parameter( property = "failsafe.forkNode" )
+    private ForkNodeFactory forkNode;
+
     /**
      * You can selectively exclude individual environment variables by enumerating their keys.
      * <br>
@@ -912,6 +916,12 @@ public class IntegrationTestMojo
     }
 
     @Override
+    protected final ForkNodeFactory getForkNode()
+    {
+        return forkNode;
+    }
+
+    @Override
     protected final String[] getExcludedEnvironmentVariables()
     {
         return excludedEnvironmentVariables == null ? new String[0] : excludedEnvironmentVariables;
diff --git a/maven-surefire-common/pom.xml b/maven-surefire-common/pom.xml
index fefe831..8ab9e98 100644
--- a/maven-surefire-common/pom.xml
+++ b/maven-surefire-common/pom.xml
@@ -120,22 +120,6 @@
     <build>
         <plugins>
             <plugin>
-                <artifactId>maven-dependency-plugin</artifactId>
-                <executions>
-                    <execution>
-                        <id>build-test-classpath</id>
-                        <phase>generate-sources</phase>
-                        <goals>
-                            <goal>build-classpath</goal>
-                        </goals>
-                        <configuration>
-                            <includeScope>test</includeScope>
-                            <outputFile>target/test-classpath/cp.txt</outputFile>
-                        </configuration>
-                    </execution>
-                </executions>
-            </plugin>
-            <plugin>
                 <groupId>org.jacoco</groupId>
                 <artifactId>jacoco-maven-plugin</artifactId>
                 <executions>
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/AbstractSurefireMojo.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/AbstractSurefireMojo.java
index ea1e3f4..6bab194 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/AbstractSurefireMojo.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/AbstractSurefireMojo.java
@@ -25,6 +25,7 @@ import org.apache.maven.artifact.DefaultArtifact;
 import org.apache.maven.artifact.handler.ArtifactHandler;
 import org.apache.maven.artifact.repository.ArtifactRepository;
 import org.apache.maven.model.Plugin;
+import org.apache.maven.plugin.surefire.extensions.LegacyForkNodeFactory;
 import org.apache.maven.plugin.surefire.extensions.SurefireConsoleOutputReporter;
 import org.apache.maven.plugin.surefire.extensions.SurefireStatelessReporter;
 import org.apache.maven.plugin.surefire.extensions.SurefireStatelessTestsetInfoReporter;
@@ -73,6 +74,7 @@ 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.cli.CommandLineOption;
+import org.apache.maven.surefire.extensions.ForkNodeFactory;
 import org.apache.maven.surefire.providerapi.SurefireProvider;
 import org.apache.maven.surefire.report.ReporterConfiguration;
 import org.apache.maven.surefire.suite.RunResult;
@@ -773,8 +775,6 @@ public abstract class AbstractSurefireMojo
     @Component
     private DependencyResolver dependencyResolver;
 
-    private Artifact surefireBooterArtifact;
-
     private Toolchain toolchain;
 
     private int effectiveForkCount = -1;
@@ -831,6 +831,8 @@ public abstract class AbstractSurefireMojo
 
     protected abstract String getEnableProcessChecker();
 
+    protected abstract ForkNodeFactory getForkNode();
+
     /**
      * This plugin MOJO artifact.
      *
@@ -912,8 +914,7 @@ public abstract class AbstractSurefireMojo
                 getPluginName(), getDependencyResolver(),
                 getSession().isOffline() );
 
-        surefireBooterArtifact = getBooterArtifact();
-        if ( surefireBooterArtifact == null )
+        if ( getBooterArtifact() == null )
         {
             throw new RuntimeException( "Unable to locate surefire-booter in the list of plugin artifacts" );
         }
@@ -1746,7 +1747,7 @@ public abstract class AbstractSurefireMojo
         return new File( getBasedir(), ".surefire-" + configurationHash );
     }
 
-    private StartupConfiguration createStartupConfiguration( @Nonnull ProviderInfo provider, boolean isInprocess,
+    private StartupConfiguration createStartupConfiguration( @Nonnull ProviderInfo provider, boolean isForking,
                                                              @Nonnull ClassLoaderConfiguration classLoaderConfiguration,
                                                              @Nonnull DefaultScanResult scanResult,
                                                              @Nonnull Platform platform,
@@ -1757,7 +1758,7 @@ public abstract class AbstractSurefireMojo
         {
             Set<Artifact> providerArtifacts = provider.getProviderClasspath();
             String providerName = provider.getProviderName();
-            if ( canExecuteProviderWithModularPath( platform ) && !isInprocess )
+            if ( isForking && canExecuteProviderWithModularPath( platform ) )
             {
                 String jvmExecutable = platform.getJdkExecAttributesForTests().getJvmExecutable();
                 String javaHome = Paths.get( jvmExecutable )
@@ -1799,8 +1800,8 @@ public abstract class AbstractSurefireMojo
         getConsoleLogger().debug( testClasspath.getCompactLogMessage( "test(compact) classpath:" ) );
         getConsoleLogger().debug( providerClasspath.getCompactLogMessage( "provider(compact) classpath:" ) );
 
-        Artifact[] additionalInProcArtifacts =
-                { getCommonArtifact(), getExtensionsArtifact(), getApiArtifact(), getLoggerApiArtifact() };
+        Artifact[] additionalInProcArtifacts = { getCommonArtifact(), getBooterArtifact(), getExtensionsArtifact(),
+            getApiArtifact(), getSpiArtifact(), getLoggerApiArtifact(), getSurefireSharedUtilsArtifact() };
         Set<Artifact> inProcArtifacts = retainInProcArtifactsUnique( providerArtifacts, additionalInProcArtifacts );
         Classpath inProcClasspath = createInProcClasspath( providerClasspath, inProcArtifacts );
         getConsoleLogger().debug( inProcClasspath.getLogMessage( "in-process classpath:" ) );
@@ -1809,8 +1810,8 @@ public abstract class AbstractSurefireMojo
         ClasspathConfiguration classpathConfiguration = new ClasspathConfiguration( testClasspath, providerClasspath,
                 inProcClasspath, effectiveIsEnableAssertions(), isChildDelegation() );
 
-        return new StartupConfiguration( providerName, classpathConfiguration, classLoaderConfiguration, isForking(),
-                false, ProcessCheckerType.toEnum( getEnableProcessChecker() ) );
+        return new StartupConfiguration( providerName, classpathConfiguration, classLoaderConfiguration,
+            ProcessCheckerType.toEnum( getEnableProcessChecker() ) );
     }
 
     private static Set<Artifact> retainInProcArtifactsUnique( Set<Artifact> providerArtifacts,
@@ -1902,8 +1903,8 @@ public abstract class AbstractSurefireMojo
         ModularClasspath modularClasspath = new ModularClasspath( result.getMainModuleDescriptor().name(),
                 testModulepath.getClassPath(), packages, getTestClassesDirectory() );
 
-        Artifact[] additionalInProcArtifacts =
-                { getCommonArtifact(), getExtensionsArtifact(), getApiArtifact(), getLoggerApiArtifact() };
+        Artifact[] additionalInProcArtifacts = { getCommonArtifact(), getBooterArtifact(), getExtensionsArtifact(),
+            getApiArtifact(), getSpiArtifact(), getLoggerApiArtifact(), getSurefireSharedUtilsArtifact() };
         Set<Artifact> inProcArtifacts = retainInProcArtifactsUnique( providerArtifacts, additionalInProcArtifacts );
         Classpath inProcClasspath = createInProcClasspath( providerClasspath, inProcArtifacts );
 
@@ -1919,8 +1920,8 @@ public abstract class AbstractSurefireMojo
         getConsoleLogger().debug( inProcClasspath.getLogMessage( "in-process classpath:" ) );
         getConsoleLogger().debug( inProcClasspath.getCompactLogMessage( "in-process(compact) classpath:" ) );
 
-        return new StartupConfiguration( providerName, classpathConfiguration, classLoaderConfiguration, isForking(),
-                false, ProcessCheckerType.toEnum( getEnableProcessChecker() ) );
+        return new StartupConfiguration( providerName, classpathConfiguration, classLoaderConfiguration,
+            ProcessCheckerType.toEnum( getEnableProcessChecker() ) );
     }
 
     private Artifact getCommonArtifact()
@@ -1933,11 +1934,21 @@ public abstract class AbstractSurefireMojo
         return getPluginArtifactMap().get( "org.apache.maven.surefire:surefire-extensions-api" );
     }
 
+    private Artifact getSpiArtifact()
+    {
+        return getPluginArtifactMap().get( "org.apache.maven.surefire:surefire-extensions-spi" );
+    }
+
     private Artifact getApiArtifact()
     {
         return getPluginArtifactMap().get( "org.apache.maven.surefire:surefire-api" );
     }
 
+    private Artifact getSurefireSharedUtilsArtifact()
+    {
+        return getPluginArtifactMap().get( "org.apache.maven.surefire:surefire-shared-utils" );
+    }
+
     private Artifact getLoggerApiArtifact()
     {
         return getPluginArtifactMap().get( "org.apache.maven.surefire:surefire-logger-api" );
@@ -2227,7 +2238,7 @@ public abstract class AbstractSurefireMojo
                                            @Nonnull TestClassPath testClasspathWrapper )
         throws MojoExecutionException, MojoFailureException
     {
-        StartupConfiguration startupConfiguration = createStartupConfiguration( provider, false,
+        StartupConfiguration startupConfiguration = createStartupConfiguration( provider, true,
                 classLoaderConfiguration, scanResult, platform, testClasspathWrapper );
         String configChecksum = getConfigChecksum();
         StartupReportConfiguration startupReportConfiguration = getStartupReportConfiguration( configChecksum, true );
@@ -2244,7 +2255,7 @@ public abstract class AbstractSurefireMojo
                                                               @Nonnull TestClassPath testClasspathWrapper )
         throws MojoExecutionException, MojoFailureException
     {
-        StartupConfiguration startupConfiguration = createStartupConfiguration( provider, true, classLoaderConfig,
+        StartupConfiguration startupConfiguration = createStartupConfiguration( provider, false, classLoaderConfig,
                 scanResult, platform, testClasspathWrapper );
         String configChecksum = getConfigChecksum();
         StartupReportConfiguration startupReportConfiguration = getStartupReportConfiguration( configChecksum, false );
@@ -2253,6 +2264,14 @@ public abstract class AbstractSurefireMojo
                                               getConsoleLogger() );
     }
 
+    // todo this is in separate method and can be better tested than whole method createForkConfiguration()
+    @Nonnull
+    private ForkNodeFactory getForkNodeFactory()
+    {
+        ForkNodeFactory forkNode = getForkNode();
+        return forkNode == null ? new LegacyForkNodeFactory() : forkNode;
+    }
+
     @Nonnull
     private ForkConfiguration createForkConfiguration( Platform platform )
     {
@@ -2260,7 +2279,9 @@ public abstract class AbstractSurefireMojo
 
         Artifact shadeFire = getShadefireArtifact();
 
-        Classpath bootClasspath = getArtifactClasspath( shadeFire != null ? shadeFire : surefireBooterArtifact );
+        Classpath bootClasspath = getArtifactClasspath( shadeFire != null ? shadeFire : getBooterArtifact() );
+
+        ForkNodeFactory forkNode = getForkNodeFactory();
 
         if ( canExecuteProviderWithModularPath( platform ) )
         {
@@ -2276,7 +2297,8 @@ public abstract class AbstractSurefireMojo
                     getEffectiveForkCount(),
                     reuseForks,
                     platform,
-                    getConsoleLogger() );
+                    getConsoleLogger(),
+                    forkNode );
         }
         else if ( getClassLoaderConfiguration().isManifestOnlyJarRequestedAndUsable() )
         {
@@ -2292,7 +2314,8 @@ public abstract class AbstractSurefireMojo
                     getEffectiveForkCount(),
                     reuseForks,
                     platform,
-                    getConsoleLogger() );
+                    getConsoleLogger(),
+                    forkNode );
         }
         else
         {
@@ -2308,7 +2331,8 @@ public abstract class AbstractSurefireMojo
                     getEffectiveForkCount(),
                     reuseForks,
                     platform,
-                    getConsoleLogger() );
+                    getConsoleLogger(),
+                    forkNode );
         }
     }
 
@@ -2919,7 +2943,7 @@ public abstract class AbstractSurefireMojo
         {
             // add the JUnit provider as default - it doesn't require JUnit to be present,
             // since it supports POJO tests.
-            String version = surefireBooterArtifact.getBaseVersion();
+            String version = getBooterArtifact().getBaseVersion();
             return surefireDependencyResolver.getProviderClasspath( "surefire-junit3", version );
         }
     }
@@ -2958,7 +2982,7 @@ public abstract class AbstractSurefireMojo
         @Nonnull
         public Set<Artifact> getProviderClasspath()
         {
-            String version = surefireBooterArtifact.getBaseVersion();
+            String version = getBooterArtifact().getBaseVersion();
             return surefireDependencyResolver.getProviderClasspath( "surefire-junit4", version );
         }
     }
@@ -3001,7 +3025,7 @@ public abstract class AbstractSurefireMojo
         @Nonnull
         public Set<Artifact> getProviderClasspath() throws MojoExecutionException
         {
-            String surefireVersion = surefireBooterArtifact.getBaseVersion();
+            String surefireVersion = getBooterArtifact().getBaseVersion();
             Map<String, Artifact> providerArtifacts =
                     surefireDependencyResolver.getProviderClasspathAsMap( "surefire-junit-platform", surefireVersion );
             Map<String, Artifact> testDependencies = testClasspath.getTestDependencies();
@@ -3172,7 +3196,7 @@ public abstract class AbstractSurefireMojo
         @Nonnull
         public Set<Artifact> getProviderClasspath()
         {
-            String version = surefireBooterArtifact.getBaseVersion();
+            String version = getBooterArtifact().getBaseVersion();
             return surefireDependencyResolver.getProviderClasspath( "surefire-junit47", version );
         }
     }
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/CommonReflector.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/CommonReflector.java
index 835fb89..466148f 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/CommonReflector.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/CommonReflector.java
@@ -23,8 +23,8 @@ import org.apache.maven.plugin.surefire.extensions.SurefireConsoleOutputReporter
 import org.apache.maven.plugin.surefire.extensions.SurefireStatelessReporter;
 import org.apache.maven.plugin.surefire.extensions.SurefireStatelessTestsetInfoReporter;
 import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
+import org.apache.maven.plugin.surefire.log.api.ConsoleLoggerDecorator;
 import org.apache.maven.plugin.surefire.report.DefaultReporterFactory;
-import org.apache.maven.surefire.booter.SurefireReflector;
 import org.apache.maven.surefire.util.SurefireReflectionException;
 
 import javax.annotation.Nonnull;
@@ -71,7 +71,7 @@ public class CommonReflector
     {
         Class<?>[] args = { this.startupReportConfiguration, this.consoleLogger };
         Object src = createStartupReportConfiguration( startupReportConfiguration );
-        Object logger = SurefireReflector.createConsoleLogger( consoleLogger, surefireClassLoader );
+        Object logger = createConsoleLogger( consoleLogger, surefireClassLoader );
         Object[] params = { src, logger };
         return instantiateObject( DefaultReporterFactory.class.getName(), args, params, surefireClassLoader );
     }
@@ -84,7 +84,6 @@ public class CommonReflector
                                                      int.class, String.class, String.class, boolean.class,
                                                      statelessTestsetReporter, consoleOutputReporter,
                                                      statelessTestsetInfoReporter );
-        //noinspection BooleanConstructorCall
         Object[] params = { reporterConfiguration.isUseFile(), reporterConfiguration.isPrintSummary(),
             reporterConfiguration.getReportFormat(), reporterConfiguration.isRedirectTestOutputToFile(),
             reporterConfiguration.getReportsDirectory(),
@@ -98,4 +97,17 @@ public class CommonReflector
         };
         return newInstance( constructor, params );
     }
+
+    static Object createConsoleLogger( ConsoleLogger consoleLogger, ClassLoader cl )
+    {
+        try
+        {
+            Class<?> decoratorClass = cl.loadClass( ConsoleLoggerDecorator.class.getName() );
+            return getConstructor( decoratorClass, Object.class ).newInstance( consoleLogger );
+        }
+        catch ( Exception e )
+        {
+            throw new SurefireReflectionException( e );
+        }
+    }
 }
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/SurefireDependencyResolver.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/SurefireDependencyResolver.java
index 4684563..6da4a1a 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/SurefireDependencyResolver.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/SurefireDependencyResolver.java
@@ -74,6 +74,7 @@ final class SurefireDependencyResolver
             "surefire-junit-platform",
             "surefire-api",
             "surefire-logger-api",
+            "surefire-shared-utils",
             "common-java5",
             "common-junit3",
             "common-junit4",
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/AbstractClasspathForkConfiguration.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/AbstractClasspathForkConfiguration.java
index 692f486..f8e08ea 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/AbstractClasspathForkConfiguration.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/AbstractClasspathForkConfiguration.java
@@ -21,6 +21,7 @@ package org.apache.maven.plugin.surefire.booterclient;
 
 import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
 import org.apache.maven.surefire.booter.Classpath;
+import org.apache.maven.surefire.extensions.ForkNodeFactory;
 
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
@@ -49,10 +50,12 @@ abstract class AbstractClasspathForkConfiguration
                                         int forkCount,
                                         boolean reuseForks,
                                         @Nonnull Platform pluginPlatform,
-                                        @Nonnull ConsoleLogger log )
+                                        @Nonnull ConsoleLogger log,
+                                        @Nonnull ForkNodeFactory forkNodeFactory )
     {
         super( bootClasspath, tempDirectory, debugLine, workingDirectory, modelProperties, argLine,
-                environmentVariables, excludedEnvironmentVariables, debug, forkCount, reuseForks, pluginPlatform, log );
+            environmentVariables, excludedEnvironmentVariables, debug, forkCount, reuseForks, pluginPlatform, log,
+            forkNodeFactory );
     }
 
     @Override
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/BooterSerializer.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/BooterSerializer.java
index 7eacb74..9a04326 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/BooterSerializer.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/BooterSerializer.java
@@ -71,6 +71,7 @@ import static org.apache.maven.surefire.booter.BooterConstants.TESTARTIFACT_CLAS
 import static org.apache.maven.surefire.booter.BooterConstants.TESTARTIFACT_VERSION;
 import static org.apache.maven.surefire.booter.BooterConstants.USEMANIFESTONLYJAR;
 import static org.apache.maven.surefire.booter.BooterConstants.USESYSTEMCLASSLOADER;
+import static org.apache.maven.surefire.booter.BooterConstants.FORK_NODE_CONNECTION_STRING;
 import static org.apache.maven.surefire.booter.SystemPropertyManager.writePropertiesFile;
 
 /**
@@ -102,11 +103,11 @@ class BooterSerializer
      */
     File serialize( KeyValueSource sourceProperties, ProviderConfiguration providerConfiguration,
                     StartupConfiguration startupConfiguration, Object testSet, boolean readTestsFromInStream,
-                    Long pid, int forkNumber )
+                    Long pid, int forkNumber, String forkNodeConnectionString )
         throws IOException
     {
         SurefireProperties properties = new SurefireProperties( sourceProperties );
-
+        properties.setNullableProperty( FORK_NODE_CONNECTION_STRING, forkNodeConnectionString );
         properties.setProperty( PLUGIN_PID, pid );
 
         AbstractPathConfiguration cp = startupConfiguration.getClasspathConfiguration();
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ClasspathForkConfiguration.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ClasspathForkConfiguration.java
index 1ca3932..9c906c4 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ClasspathForkConfiguration.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ClasspathForkConfiguration.java
@@ -24,6 +24,7 @@ import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
 import org.apache.maven.surefire.booter.Classpath;
 import org.apache.maven.surefire.booter.StartupConfiguration;
 import org.apache.maven.surefire.booter.SurefireBooterForkException;
+import org.apache.maven.surefire.extensions.ForkNodeFactory;
 
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
@@ -48,10 +49,12 @@ public final class ClasspathForkConfiguration
                                        @Nonnull String[] excludedEnvironmentVariables,
                                        boolean debug, int forkCount,
                                        boolean reuseForks, @Nonnull Platform pluginPlatform,
-                                       @Nonnull ConsoleLogger log )
+                                       @Nonnull ConsoleLogger log,
+                                       @Nonnull ForkNodeFactory forkNodeFactory )
     {
         super( bootClasspath, tempDirectory, debugLine, workingDirectory, modelProperties, argLine,
-                environmentVariables, excludedEnvironmentVariables, debug, forkCount, reuseForks, pluginPlatform, log );
+            environmentVariables, excludedEnvironmentVariables, debug, forkCount, reuseForks, pluginPlatform, log,
+            forkNodeFactory );
     }
 
     @Override
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/DefaultForkConfiguration.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/DefaultForkConfiguration.java
index 4ab4435..443bf45 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/DefaultForkConfiguration.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/DefaultForkConfiguration.java
@@ -26,6 +26,7 @@ import org.apache.maven.surefire.booter.AbstractPathConfiguration;
 import org.apache.maven.surefire.booter.Classpath;
 import org.apache.maven.surefire.booter.StartupConfiguration;
 import org.apache.maven.surefire.booter.SurefireBooterForkException;
+import org.apache.maven.surefire.extensions.ForkNodeFactory;
 import org.apache.maven.surefire.util.internal.ImmutableMap;
 
 import javax.annotation.Nonnull;
@@ -65,6 +66,7 @@ public abstract class DefaultForkConfiguration
     private final boolean reuseForks;
     @Nonnull private final Platform pluginPlatform;
     @Nonnull private final ConsoleLogger log;
+    @Nonnull private final ForkNodeFactory forkNodeFactory;
 
     @SuppressWarnings( "checkstyle:parameternumber" )
     protected DefaultForkConfiguration( @Nonnull Classpath booterClasspath,
@@ -79,7 +81,8 @@ public abstract class DefaultForkConfiguration
                                      int forkCount,
                                      boolean reuseForks,
                                      @Nonnull Platform pluginPlatform,
-                                     @Nonnull ConsoleLogger log )
+                                     @Nonnull ConsoleLogger log,
+                                     @Nonnull ForkNodeFactory forkNodeFactory )
     {
         this.booterClasspath = booterClasspath;
         this.tempDirectory = tempDirectory;
@@ -94,6 +97,7 @@ public abstract class DefaultForkConfiguration
         this.reuseForks = reuseForks;
         this.pluginPlatform = pluginPlatform;
         this.log = log;
+        this.forkNodeFactory = forkNodeFactory;
     }
 
     protected abstract void resolveClasspath( @Nonnull OutputStreamFlushableCommandline cli,
@@ -108,6 +112,13 @@ public abstract class DefaultForkConfiguration
         return jvmArgLine;
     }
 
+    @Nonnull
+    @Override
+    public final ForkNodeFactory getForkNodeFactory()
+    {
+        return forkNodeFactory;
+    }
+
     /**
      * @param config       The startup configuration
      * @param forkNumber   index of forked JVM, to be the replacement in the argLine
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ForkConfiguration.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ForkConfiguration.java
index 92bebd0..9fddf96 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ForkConfiguration.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ForkConfiguration.java
@@ -25,6 +25,7 @@ import org.apache.maven.surefire.booter.Classpath;
 import org.apache.maven.surefire.booter.ForkedBooter;
 import org.apache.maven.surefire.booter.StartupConfiguration;
 import org.apache.maven.surefire.booter.SurefireBooterForkException;
+import org.apache.maven.surefire.extensions.ForkNodeFactory;
 
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
@@ -39,6 +40,7 @@ public abstract class ForkConfiguration
 {
     static final String DEFAULT_PROVIDER_CLASS = ForkedBooter.class.getName();
 
+    @Nonnull public abstract ForkNodeFactory getForkNodeFactory();
     @Nonnull public abstract File getTempDirectory();
     @Nullable protected abstract String getDebugLine();
     @Nonnull protected abstract File getWorkingDirectory();
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 0ba2f46..a23d656 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
@@ -22,7 +22,7 @@ package org.apache.maven.plugin.surefire.booterclient;
 import org.apache.maven.plugin.surefire.CommonReflector;
 import org.apache.maven.plugin.surefire.StartupReportConfiguration;
 import org.apache.maven.plugin.surefire.SurefireProperties;
-import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.AbstractForkInputStream;
+import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.AbstractCommandReader;
 import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.NotifiableTestStream;
 import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.OutputStreamFlushableCommandline;
 import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.TestLessInputStream;
@@ -33,12 +33,10 @@ 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.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.extensions.util.StreamFeeder;
-import org.apache.maven.surefire.shared.utils.cli.CommandLineException;
 import org.apache.maven.surefire.booter.AbstractPathConfiguration;
 import org.apache.maven.surefire.booter.PropertiesWrapper;
 import org.apache.maven.surefire.booter.ProviderConfiguration;
@@ -47,8 +45,12 @@ 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.ForkChannel;
+import org.apache.maven.surefire.extensions.ForkNodeFactory;
+import org.apache.maven.surefire.extensions.util.LineConsumerThread;
 import org.apache.maven.surefire.providerapi.SurefireProvider;
 import org.apache.maven.surefire.report.StackTraceWriter;
+import org.apache.maven.surefire.shared.utils.cli.StreamConsumer;
 import org.apache.maven.surefire.suite.RunResult;
 import org.apache.maven.surefire.testset.TestRequest;
 import org.apache.maven.surefire.util.DefaultScanResult;
@@ -79,7 +81,6 @@ import static java.lang.StrictMath.min;
 import static java.lang.System.currentTimeMillis;
 import static java.lang.Thread.currentThread;
 import static java.util.Collections.addAll;
-import static java.util.Objects.requireNonNull;
 import static java.util.concurrent.Executors.newScheduledThreadPool;
 import static java.util.concurrent.TimeUnit.MILLISECONDS;
 import static java.util.concurrent.TimeUnit.SECONDS;
@@ -192,7 +193,7 @@ public class ForkStarter
                 {
                     closeable.close();
                 }
-                catch ( IOException e )
+                catch ( IOException | RuntimeException e )
                 {
                     // This error does not fail a test and does not necessarily mean that the forked JVM std/out stream
                     // was not closed, see ThreadedStreamConsumer. This error means that JVM wrote messages to a native
@@ -212,11 +213,17 @@ public class ForkStarter
         @Override
         public void close()
         {
-            run();
-            testProvidingInputStream.clear();
-            if ( inputStreamCloserHook != null )
+            try
             {
-                removeShutdownHook( inputStreamCloserHook );
+                run();
+            }
+            finally
+            {
+                testProvidingInputStream.clear();
+                if ( inputStreamCloserHook != null )
+                {
+                    removeShutdownHook( inputStreamCloserHook );
+                }
             }
         }
 
@@ -289,7 +296,8 @@ public class ForkStarter
             defaultReporterFactories.add( forkedReporterFactory );
             ForkClient forkClient =
                     new ForkClient( forkedReporterFactory, stream, log, new AtomicBoolean(), forkNumber );
-            return fork( null, props, forkClient, effectiveSystemProperties, forkNumber, stream, false );
+            return fork( null, props, forkClient, effectiveSystemProperties, forkNumber, stream,
+                    forkConfiguration.getForkNodeFactory(), false );
         }
         finally
         {
@@ -379,7 +387,8 @@ public class ForkStarter
                         try
                         {
                             return fork( null, new PropertiesWrapper( providerProperties ), forkClient,
-                                    effectiveSystemProperties, forkNumber, testProvidingInputStream, true );
+                                    effectiveSystemProperties, forkNumber, testProvidingInputStream,
+                                    forkConfiguration.getForkNodeFactory(), true );
                         }
                         finally
                         {
@@ -453,7 +462,8 @@ public class ForkStarter
                         {
                             return fork( testSet,
                                          new PropertiesWrapper( providerConfiguration.getProviderProperties() ),
-                                         forkClient, effectiveSystemProperties, forkNumber, stream, false );
+                                         forkClient, effectiveSystemProperties, forkNumber, stream,
+                                         forkConfiguration.getForkNodeFactory(), false );
                         }
                         finally
                         {
@@ -549,21 +559,25 @@ public class ForkStarter
 
     private RunResult fork( Object testSet, PropertiesWrapper providerProperties, ForkClient forkClient,
                             SurefireProperties effectiveSystemProperties, int forkNumber,
-                            AbstractForkInputStream commandInputStream, boolean readTestsFromInStream )
+                            AbstractCommandReader commandReader, ForkNodeFactory forkNodeFactory,
+                            boolean readTestsFromInStream )
         throws SurefireBooterForkException
     {
         final String tempDir;
         final File surefireProperties;
         final File systPropsFile;
+        final ForkChannel forkChannel;
         try
         {
+            forkChannel = forkNodeFactory.createForkChannel( forkNumber );
             tempDir = forkConfiguration.getTempDirectory().getCanonicalPath();
             BooterSerializer booterSerializer = new BooterSerializer( forkConfiguration );
             Long pluginPid = forkConfiguration.getPluginPlatform().getPluginPid();
-            surefireProperties = booterSerializer.serialize( providerProperties, providerConfiguration,
-                    startupConfiguration, testSet, readTestsFromInStream, pluginPid, forkNumber );
-
             log.debug( "Determined Maven Process ID " + pluginPid );
+            String connectionString = forkChannel.getForkNodeConnectionString();
+            log.debug( "Fork Channel [" + forkNumber + "] connection string " + connectionString );
+            surefireProperties = booterSerializer.serialize( providerProperties, providerConfiguration,
+                    startupConfiguration, testSet, readTestsFromInStream, pluginPid, forkNumber, connectionString );
 
             if ( effectiveSystemProperties != null )
             {
@@ -588,10 +602,7 @@ public class ForkStarter
         OutputStreamFlushableCommandline cli =
                 forkConfiguration.createCommandLine( startupConfiguration, forkNumber, dumpLogDir );
 
-        if ( commandInputStream != null )
-        {
-            commandInputStream.setFlushReceiverProvider( cli );
-        }
+        commandReader.setFlushReceiverProvider( cli );
 
         cli.createArg().setValue( tempDir );
         cli.createArg().setValue( DUMP_FILE_PREFIX + forkNumber );
@@ -602,38 +613,44 @@ public class ForkStarter
         }
 
         ThreadedStreamConsumer eventConsumer = new ThreadedStreamConsumer( forkClient );
-        CloseableCloser closer = new CloseableCloser( forkNumber, eventConsumer, requireNonNull( commandInputStream ) );
+        CloseableCloser closer = new CloseableCloser( forkNumber, eventConsumer, commandReader );
 
         log.debug( "Forking command line: " + cli );
 
         Integer result = null;
         RunResult runResult = null;
         SurefireBooterForkException booterForkException = null;
-        StreamFeeder in = null;
-        LineConsumerThread out = null;
-        LineConsumerThread err = null;
+        CloseableDaemonThread in = null;
+        CloseableDaemonThread out = null;
+        CloseableDaemonThread err = null;
         DefaultReporterFactory reporter = forkClient.getDefaultReporterFactory();
         currentForkClients.add( forkClient );
-        CountdownCloseable countdownCloseable = new CountdownCloseable( eventConsumer, 2 );
+        CountdownCloseable countdownCloseable =
+            new CountdownCloseable( eventConsumer, 1 + ( forkChannel.useStdOut() ? 1 : 0 ) );
         try ( CommandlineExecutor exec = new CommandlineExecutor( cli, countdownCloseable ) )
         {
-            // default impl of the extension - solves everything including the encoder/decoder, Process starter,
-            // adaptation of the streams to pipes and sockets
-            // non-default impl may use another classes and not the LineConsumerThread, StreamFeeder - freedom
-            // BEGIN: beginning of the call of the extension
             CommandlineStreams streams = exec.execute();
             closer.addCloseable( streams );
-            in = new StreamFeeder( "std-in-fork-" + forkNumber, streams.getStdInChannel(), commandInputStream );
+
+            forkChannel.openChannel();
+
+            in = forkChannel.useStdIn()
+                ? forkChannel.bindCommandReader( commandReader, streams.getStdInChannel() )
+                : forkChannel.bindCommandReader( commandReader );
             in.start();
-            out = new LineConsumerThread( "std-out-fork-" + forkNumber, streams.getStdOutChannel(),
-                                          eventConsumer, countdownCloseable );
+
+            StreamConsumer stdErrConsumer = new NativeStdErrStreamConsumer( reporter );
+
+            out = forkChannel.useStdOut()
+                ? forkChannel.bindEventHandler( eventConsumer, streams.getStdOutChannel(), countdownCloseable )
+                : forkChannel.bindEventHandler( stdErrConsumer );
             out.start();
-            NativeStdErrStreamConsumer stdErrConsumer = new NativeStdErrStreamConsumer( reporter );
+
             err = new LineConsumerThread( "std-err-fork-" + forkNumber, streams.getStdErrChannel(),
-                                          stdErrConsumer, countdownCloseable );
+                stdErrConsumer, countdownCloseable );
             err.start();
+
             result = exec.awaitExit();
-            // END: end of the call of the extension
 
             if ( forkClient.hadTimeout() )
             {
@@ -652,8 +669,9 @@ public class ForkStarter
             out.disable();
             err.disable();
         }
-        catch ( CommandLineException e )
+        catch ( Exception e )
         {
+            // CommandLineException from pipes and IOException from sockets
             runResult = failure( reporter.getGlobalRunStatistics().getRunResult(), e );
             String cliErr = e.getLocalizedMessage();
             Throwable cause = e.getCause();
@@ -665,7 +683,7 @@ public class ForkStarter
             currentForkClients.remove( forkClient );
             try
             {
-                Closeable c = forkClient.isSaidGoodBye() ? closer : commandInputStream;
+                Closeable c = forkClient.isSaidGoodBye() ? closer : commandReader;
                 c.close();
             }
             catch ( IOException e )
@@ -710,7 +728,7 @@ public class ForkStarter
                     //noinspection ThrowFromFinallyBlock
                     throw new SurefireBooterForkException( "There was an error in the forked process"
                                                         + detail
-                                                        + ( stackTrace == null ? "" : stackTrace ), cause );
+                                                        + ( stackTrace == null ? "" : "\n" + stackTrace ), cause );
                 }
                 if ( !forkClient.isSaidGoodBye() )
                 {
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/JarManifestForkConfiguration.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/JarManifestForkConfiguration.java
index 02c275c..382bec6 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/JarManifestForkConfiguration.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/JarManifestForkConfiguration.java
@@ -28,6 +28,7 @@ import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
 import org.apache.maven.surefire.booter.Classpath;
 import org.apache.maven.surefire.booter.StartupConfiguration;
 import org.apache.maven.surefire.booter.SurefireBooterForkException;
+import org.apache.maven.surefire.extensions.ForkNodeFactory;
 
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
@@ -70,10 +71,12 @@ public final class JarManifestForkConfiguration
                                          @Nonnull String[] excludedEnvironmentVariables,
                                          boolean debug,
                                          int forkCount, boolean reuseForks, @Nonnull Platform pluginPlatform,
-                                         @Nonnull ConsoleLogger log )
+                                         @Nonnull ConsoleLogger log,
+                                         @Nonnull ForkNodeFactory forkNodeFactory )
     {
         super( bootClasspath, tempDirectory, debugLine, workingDirectory, modelProperties, argLine,
-                environmentVariables, excludedEnvironmentVariables, debug, forkCount, reuseForks, pluginPlatform, log );
+            environmentVariables, excludedEnvironmentVariables, debug, forkCount, reuseForks, pluginPlatform, log,
+            forkNodeFactory );
     }
 
     @Override
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ModularClasspathForkConfiguration.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ModularClasspathForkConfiguration.java
index 8f5030b..d659a6c 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ModularClasspathForkConfiguration.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ModularClasspathForkConfiguration.java
@@ -28,6 +28,7 @@ import org.apache.maven.surefire.booter.ModularClasspath;
 import org.apache.maven.surefire.booter.ModularClasspathConfiguration;
 import org.apache.maven.surefire.booter.StartupConfiguration;
 import org.apache.maven.surefire.booter.SurefireBooterForkException;
+import org.apache.maven.surefire.extensions.ForkNodeFactory;
 
 import javax.annotation.Nonnegative;
 import javax.annotation.Nonnull;
@@ -67,10 +68,12 @@ public class ModularClasspathForkConfiguration
                                               @Nonnegative int forkCount,
                                               boolean reuseForks,
                                               @Nonnull Platform pluginPlatform,
-                                              @Nonnull ConsoleLogger log )
+                                              @Nonnull ConsoleLogger log,
+                                              @Nonnull ForkNodeFactory forkNodeFactory )
     {
         super( bootClasspath, tempDirectory, debugLine, workingDirectory, modelProperties, argLine,
-                environmentVariables, excludedEnvironmentVariables, debug, forkCount, reuseForks, pluginPlatform, log );
+            environmentVariables, excludedEnvironmentVariables, debug, forkCount, reuseForks, pluginPlatform, log,
+            forkNodeFactory );
     }
 
     @Override
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/AbstractForkInputStream.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/AbstractCommandReader.java
similarity index 84%
rename from maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/AbstractForkInputStream.java
rename to maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/AbstractCommandReader.java
index a884c15..a31e9f7 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/AbstractForkInputStream.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/AbstractCommandReader.java
@@ -19,32 +19,34 @@ package org.apache.maven.plugin.surefire.booterclient.lazytestprovider;
  * under the License.
  */
 
+import org.apache.maven.surefire.extensions.CommandReader;
+
 import java.io.IOException;
-import java.io.InputStream;
 
 import static java.util.Objects.requireNonNull;
 
 /**
- * Reader stream sends bytes to forked jvm std-{@link InputStream input-stream}.
+ * Stream reader returns bytes which ar finally sent to the forked jvm std-input-stream.
  *
  * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
  * @since 2.19
  */
-public abstract class AbstractForkInputStream
-    extends InputStream
-    implements NotifiableTestStream
+public abstract class AbstractCommandReader
+        implements CommandReader, DefferedChannelCommandSender
 {
     private volatile FlushReceiverProvider flushReceiverProvider;
 
     /**
      * @param flushReceiverProvider the provider for a flush receiver.
      */
+    @Override
     public void setFlushReceiverProvider( FlushReceiverProvider flushReceiverProvider )
     {
         this.flushReceiverProvider = requireNonNull( flushReceiverProvider );
     }
 
-    protected boolean tryFlush()
+    @Override
+    public void tryFlush()
         throws IOException
     {
         if ( flushReceiverProvider != null )
@@ -53,9 +55,7 @@ public abstract class AbstractForkInputStream
             if ( flushReceiver != null )
             {
                 flushReceiver.flush();
-                return true;
             }
         }
-        return false;
     }
 }
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/AbstractCommandStream.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/DefaultCommandReader.java
similarity index 63%
rename from maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/AbstractCommandStream.java
rename to maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/DefaultCommandReader.java
index 31b56c4..9aa19c3 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/AbstractCommandStream.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/DefaultCommandReader.java
@@ -20,7 +20,6 @@ package org.apache.maven.plugin.surefire.booterclient.lazytestprovider;
  */
 
 import org.apache.maven.surefire.booter.Command;
-import org.apache.maven.surefire.booter.MasterProcessCommand;
 
 import java.io.IOException;
 
@@ -31,14 +30,9 @@ import java.io.IOException;
  * @since 2.19
  * @see org.apache.maven.surefire.booter.Command
  */
-public abstract class AbstractCommandStream
-    extends AbstractForkInputStream
+public abstract class DefaultCommandReader
+        extends AbstractCommandReader
 {
-    private byte[] currentBuffer;
-    private int currentPos;
-
-    protected abstract boolean isClosed();
-
     /**
      * Opposite to {@link #isClosed()}.
      * @return {@code true} if not closed
@@ -62,59 +56,35 @@ public abstract class AbstractCommandStream
     protected abstract Command nextCommand();
 
     /**
-     * Returns quietly and immediately.
-     */
-    protected final void invalidateInternalBuffer()
-    {
-        currentBuffer = null;
-        currentPos = 0;
-    }
-
-    /**
      * Used by single thread in StreamFeeder class.
      *
      * @return {@inheritDoc}
      * @throws IOException {@inheritDoc}
      */
-    @SuppressWarnings( "checkstyle:magicnumber" )
     @Override
-    public int read()
+    public Command readNextCommand()
         throws IOException
     {
+        tryFlush();
+
         if ( isClosed() )
         {
-            tryFlush();
-            return -1;
+            return null;
         }
 
-        if ( currentBuffer == null )
+        if ( !canContinue() )
         {
-            tryFlush();
-
-            if ( !canContinue() )
-            {
-                close();
-                return -1;
-            }
-
-            beforeNextCommand();
-
-            if ( isClosed() )
-            {
-                return -1;
-            }
-
-            Command cmd = nextCommand();
-            MasterProcessCommand cmdType = cmd.getCommandType();
-            currentBuffer = cmdType.hasDataType() ? cmdType.encode( cmd.getData() ) : cmdType.encode();
+            close();
+            return null;
         }
 
-        int b =  currentBuffer[currentPos++] & 0xff;
-        if ( currentPos == currentBuffer.length )
+        beforeNextCommand();
+
+        if ( isClosed() )
         {
-            currentBuffer = null;
-            currentPos = 0;
+            return null;
         }
-        return b;
+
+        return nextCommand();
     }
 }
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/ShutdownAware.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/DefferedChannelCommandSender.java
similarity index 66%
rename from surefire-api/src/main/java/org/apache/maven/surefire/booter/ShutdownAware.java
rename to maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/DefferedChannelCommandSender.java
index 0bfcdb8..e489caa 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/ShutdownAware.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/DefferedChannelCommandSender.java
@@ -1,4 +1,4 @@
-package org.apache.maven.surefire.booter;
+package org.apache.maven.plugin.surefire.booterclient.lazytestprovider;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -19,13 +19,17 @@ package org.apache.maven.surefire.booter;
  * under the License.
  */
 
+import java.io.Closeable;
+
 /**
- * See the plugin configuration parameter {@code shutdown}.
+ * Physical implementation of command sender.<br>
+ * Instance of {@link AbstractCommandReader} (namely {@link TestLessInputStream} or {@link TestProvidingInputStream}).
  *
  * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
- * @since 2.19
+ * @since 3.0.0-M4
  */
-public interface ShutdownAware
+public interface DefferedChannelCommandSender
+    extends NotifiableTestStream, Closeable
 {
-    void setShutdown( Shutdown shutdown );
+    void setFlushReceiverProvider( FlushReceiverProvider flushReceiverProvider );
 }
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestLessInputStream.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestLessInputStream.java
index 372ce00..a1060a7 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestLessInputStream.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestLessInputStream.java
@@ -45,7 +45,7 @@ import static org.apache.maven.surefire.booter.Command.toShutdown;
  * @since 2.19
  */
 public final class TestLessInputStream
-    extends AbstractCommandStream
+        extends DefaultCommandReader
 {
     private final Semaphore barrier = new Semaphore( 0 );
 
@@ -108,7 +108,7 @@ public final class TestLessInputStream
     }
 
     @Override
-    protected boolean isClosed()
+    public boolean isClosed()
     {
         return closed.get();
     }
@@ -141,7 +141,6 @@ public final class TestLessInputStream
     {
         if ( closed.compareAndSet( false, true ) )
         {
-            invalidateInternalBuffer();
             barrier.drainPermits();
             barrier.release();
         }
@@ -166,8 +165,6 @@ public final class TestLessInputStream
         }
         catch ( InterruptedException e )
         {
-            // help GC to free this object because StreamFeeder Thread cannot read it anyway after IOE
-            invalidateInternalBuffer();
             throw new IOException( e.getLocalizedMessage() );
         }
     }
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestProvidingInputStream.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestProvidingInputStream.java
index a4255cc..6f3a4de 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestProvidingInputStream.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestProvidingInputStream.java
@@ -31,6 +31,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
 import static org.apache.maven.surefire.booter.Command.BYE_ACK;
 import static org.apache.maven.surefire.booter.Command.NOOP;
 import static org.apache.maven.surefire.booter.Command.SKIP_SINCE_NEXT_TEST;
+import static org.apache.maven.surefire.booter.Command.TEST_SET_FINISHED;
 import static org.apache.maven.surefire.booter.Command.toRunClass;
 import static org.apache.maven.surefire.booter.Command.toShutdown;
 
@@ -50,7 +51,7 @@ import static org.apache.maven.surefire.booter.Command.toShutdown;
  * @author Tibor Digana (tibor17)
  */
 public final class TestProvidingInputStream
-    extends AbstractCommandStream
+        extends DefaultCommandReader
 {
     private final Semaphore barrier = new Semaphore( 0 );
 
@@ -77,7 +78,7 @@ public final class TestProvidingInputStream
     {
         if ( canContinue() )
         {
-            commands.add( Command.TEST_SET_FINISHED );
+            commands.add( TEST_SET_FINISHED );
             barrier.release();
         }
     }
@@ -129,7 +130,7 @@ public final class TestProvidingInputStream
         if ( cmd == null )
         {
             String cmdData = testClassNames.poll();
-            return cmdData == null ? Command.TEST_SET_FINISHED : toRunClass( cmdData );
+            return cmdData == null ? TEST_SET_FINISHED : toRunClass( cmdData );
         }
         else
         {
@@ -145,7 +146,7 @@ public final class TestProvidingInputStream
     }
 
     @Override
-    protected boolean isClosed()
+    public boolean isClosed()
     {
         return closed.get();
     }
@@ -167,7 +168,6 @@ public final class TestProvidingInputStream
     {
         if ( closed.compareAndSet( false, true ) )
         {
-            invalidateInternalBuffer();
             barrier.drainPermits();
             barrier.release();
         }
@@ -182,9 +182,7 @@ public final class TestProvidingInputStream
         }
         catch ( InterruptedException e )
         {
-            // help GC to free this object because StreamFeeder Thread cannot read it anyway after IOE
-            invalidateInternalBuffer();
-            throw new IOException( e.getLocalizedMessage() );
+            throw new IOException( e.getLocalizedMessage(), e );
         }
     }
 }
\ No newline at end of file
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 18cfe28..a02db78 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.providerapi.MasterProcessChannelEncoder;
 import org.apache.maven.surefire.shared.utils.cli.StreamConsumer;
 import org.apache.maven.surefire.report.ConsoleOutputReceiver;
 import org.apache.maven.surefire.report.ReportEntry;
@@ -74,7 +75,7 @@ public class ForkClient
 
     /**
      * <em>testSetStartedAt</em> is set to non-zero after received
-     * {@link org.apache.maven.surefire.booter.ForkedChannelEncoder#testSetStarting(ReportEntry, boolean)}.
+     * {@link MasterProcessChannelEncoder#testSetStarting(ReportEntry, boolean)}.
      */
     private final AtomicLong testSetStartedAt = new AtomicLong( START_TIME_ZERO );
 
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
index f0c96e3..5d49b0e 100644
--- 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
@@ -32,7 +32,7 @@ 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;
+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;
@@ -183,13 +183,13 @@ public final class ForkedChannelDecoder
 
     public void handleEvent( String line, ForkedChannelDecoderErrorHandler errorHandler )
     {
-        if ( line == null || !line.startsWith( MAGIC_NUMBER ) )
+        if ( line == null || !line.startsWith( MAGIC_NUMBER_DELIMITED ) )
         {
             errorHandler.handledError( line, null );
             return;
         }
 
-        StringTokenizer tokenizer = new StringTokenizer( line.substring( MAGIC_NUMBER.length() ), ":" );
+        StringTokenizer tokenizer = new StringTokenizer( line.substring( MAGIC_NUMBER_DELIMITED.length() ), ":" );
         String opcode = tokenizer.hasMoreTokens() ? tokenizer.nextToken() : null;
         ForkedProcessEvent event = opcode == null ? null : EVENTS.get( opcode );
         if ( event == null )
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 b17bfe4..8e4f6f0 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
@@ -20,7 +20,7 @@ package org.apache.maven.plugin.surefire.booterclient.output;
  */
 
 import org.apache.maven.plugin.surefire.report.DefaultReporterFactory;
-import org.apache.maven.surefire.shared.utils.cli.StreamConsumer;
+import org.apache.maven.surefire.extensions.StdErrStreamLine;
 
 /**
  * Used by forked JMV, see {@link org.apache.maven.plugin.surefire.booterclient.ForkStarter}.
@@ -30,7 +30,7 @@ import org.apache.maven.surefire.shared.utils.cli.StreamConsumer;
  * @see org.apache.maven.plugin.surefire.booterclient.ForkStarter
  */
 public final class NativeStdErrStreamConsumer
-    implements StreamConsumer
+    implements StdErrStreamLine
 {
     private final DefaultReporterFactory defaultReporterFactory;
 
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/MainCliOptionsAware.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/NativeStdOutStreamConsumer.java
similarity index 58%
copy from surefire-api/src/main/java/org/apache/maven/surefire/booter/MainCliOptionsAware.java
copy to maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/NativeStdOutStreamConsumer.java
index eddebed..1f915ae 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/MainCliOptionsAware.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/NativeStdOutStreamConsumer.java
@@ -1,4 +1,4 @@
-package org.apache.maven.surefire.booter;
+package org.apache.maven.plugin.surefire.booterclient.output;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -9,7 +9,7 @@ package org.apache.maven.surefire.booter;
  * "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
+ *   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
@@ -19,17 +19,25 @@ package org.apache.maven.surefire.booter;
  * under the License.
  */
 
-import org.apache.maven.surefire.cli.CommandLineOption;
-
-import java.util.List;
+import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
+import org.apache.maven.surefire.extensions.StdOutStreamLine;
 
 /**
- * CLI options in plugin (main) JVM process.
  *
- * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
- * @since 2.19
  */
-interface MainCliOptionsAware
+public class NativeStdOutStreamConsumer
+        implements StdOutStreamLine
 {
-    void setMainCliOptions( List<CommandLineOption> mainCliOptions );
+    private final ConsoleLogger logger;
+
+    public NativeStdOutStreamConsumer( ConsoleLogger logger )
+    {
+        this.logger = logger;
+    }
+
+    @Override
+    public void handleLine( String line )
+    {
+        logger.info( line );
+    }
 }
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 853d35c..316167c 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
@@ -20,8 +20,10 @@ package org.apache.maven.plugin.surefire.booterclient.output;
  */
 
 import org.apache.maven.surefire.shared.utils.cli.StreamConsumer;
+import org.apache.maven.surefire.extensions.EventHandler;
 import org.apache.maven.surefire.util.internal.DaemonThreadFactory;
 
+import javax.annotation.Nonnull;
 import java.io.Closeable;
 import java.io.IOException;
 import java.util.concurrent.ArrayBlockingQueue;
@@ -36,7 +38,7 @@ import static java.lang.Thread.currentThread;
  * @author Kristian Rosenvold
  */
 public final class ThreadedStreamConsumer
-        implements StreamConsumer, Closeable
+        implements EventHandler, StreamConsumer, Closeable
 {
     private static final String END_ITEM = "";
 
@@ -113,7 +115,14 @@ public final class ThreadedStreamConsumer
     }
 
     @Override
-    public void consumeLine( String s )
+    // todo remove this method and use object instead of string
+    public void handleEvent( @Nonnull String event )
+    {
+        consumeLine( event );
+    }
+
+    @Override
+    public void consumeLine( @Nonnull String s )
     {
         if ( stop.get() )
         {
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
new file mode 100644
index 0000000..31ad325
--- /dev/null
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/LegacyForkChannel.java
@@ -0,0 +1,98 @@
+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.maven.surefire.extensions.CloseableDaemonThread;
+import org.apache.maven.surefire.extensions.CommandReader;
+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 org.apache.maven.surefire.shared.utils.cli.StreamConsumer;
+
+import javax.annotation.Nonnull;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.channels.WritableByteChannel;
+
+/**
+ *
+ */
+final class LegacyForkChannel extends ForkChannel
+{
+    protected LegacyForkChannel( int forkChannelId )
+    {
+        super( forkChannelId );
+    }
+
+    @Override
+    public void openChannel()
+    {
+    }
+
+    @Override
+    public String getForkNodeConnectionString()
+    {
+        return "pipe://" + getForkChannelId();
+    }
+
+    @Override
+    public boolean useStdIn()
+    {
+        return true;
+    }
+
+    @Override
+    public boolean useStdOut()
+    {
+        return true;
+    }
+
+    @Override
+    public CloseableDaemonThread bindCommandReader( @Nonnull CommandReader commands,
+                                                    @Nonnull WritableByteChannel stdIn )
+    {
+        return new StreamFeeder( "std-in-fork-" + getForkChannelId(), stdIn, commands );
+    }
+
+    @Override
+    public CloseableDaemonThread bindCommandReader( @Nonnull CommandReader commands )
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public CloseableDaemonThread bindEventHandler( @Nonnull StreamConsumer consumer,
+                                                   @Nonnull ReadableByteChannel stdOut,
+                                                   @Nonnull CountdownCloseable countdownCloseable )
+    {
+        return new LineConsumerThread( "std-out-fork-" + getForkChannelId(), stdOut, consumer, countdownCloseable );
+    }
+
+    @Override
+    public CloseableDaemonThread bindEventHandler( @Nonnull StreamConsumer consumer )
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void close()
+    {
+    }
+}
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/DirectoryScannerParametersAware.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/LegacyForkNodeFactory.java
similarity index 63%
rename from surefire-api/src/main/java/org/apache/maven/surefire/booter/DirectoryScannerParametersAware.java
rename to maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/LegacyForkNodeFactory.java
index cefeb33..836464e 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/DirectoryScannerParametersAware.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/LegacyForkNodeFactory.java
@@ -1,4 +1,4 @@
-package org.apache.maven.surefire.booter;
+package org.apache.maven.plugin.surefire.extensions;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -19,12 +19,21 @@ package org.apache.maven.surefire.booter;
  * under the License.
  */
 
-import org.apache.maven.surefire.testset.DirectoryScannerParameters;
+import org.apache.maven.surefire.extensions.ForkChannel;
+import org.apache.maven.surefire.extensions.ForkNodeFactory;
+
+import javax.annotation.Nonnegative;
+import javax.annotation.Nonnull;
 
 /**
- * @author Kristian Rosenvold
+ *
  */
-interface DirectoryScannerParametersAware
+public class LegacyForkNodeFactory implements ForkNodeFactory
 {
-    void setDirectoryScannerParameters( DirectoryScannerParameters directoryScanner );
+    @Nonnull
+    @Override
+    public ForkChannel createForkChannel( @Nonnegative int forkChannelId )
+    {
+        return new LegacyForkChannel( forkChannelId );
+    }
 }
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
new file mode 100644
index 0000000..214c9dc
--- /dev/null
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/SurefireForkChannel.java
@@ -0,0 +1,140 @@
+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.maven.surefire.extensions.CloseableDaemonThread;
+import org.apache.maven.surefire.extensions.CommandReader;
+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 org.apache.maven.surefire.shared.utils.cli.StreamConsumer;
+
+import javax.annotation.Nonnull;
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.SocketOption;
+import java.nio.channels.Channel;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.channels.ServerSocketChannel;
+import java.nio.channels.SocketChannel;
+import java.nio.channels.WritableByteChannel;
+
+import static java.net.StandardSocketOptions.SO_KEEPALIVE;
+import static java.net.StandardSocketOptions.SO_REUSEADDR;
+import static java.net.StandardSocketOptions.TCP_NODELAY;
+import static java.nio.channels.ServerSocketChannel.open;
+
+/**
+ *
+ */
+final class SurefireForkChannel extends ForkChannel
+{
+    private final ServerSocketChannel server;
+    private final int serverPort;
+    private SocketChannel channel;
+
+    SurefireForkChannel( int forkChannelId ) throws IOException
+    {
+        super( forkChannelId );
+        server = open();
+        setTrueOptions( SO_REUSEADDR, TCP_NODELAY, SO_KEEPALIVE );
+        server.bind( new InetSocketAddress( 0 ) );
+        serverPort = ( (InetSocketAddress) server.getLocalAddress() ).getPort();
+    }
+
+    @Override
+    public void openChannel() throws IOException
+    {
+        if ( channel != null )
+        {
+            throw new IllegalStateException( "already accepted TCP client connection" );
+        }
+        channel = server.accept();
+    }
+
+    @SafeVarargs
+    private final void setTrueOptions( SocketOption<Boolean>... options ) throws IOException
+    {
+        for ( SocketOption<Boolean> option : options )
+        {
+            if ( server.supportedOptions().contains( option ) )
+            {
+                server.setOption( option, true );
+            }
+        }
+    }
+
+    @Override
+    public String getForkNodeConnectionString()
+    {
+        return "tcp://127.0.0.1:" + serverPort;
+    }
+
+    @Override
+    public boolean useStdIn()
+    {
+        return false;
+    }
+
+    @Override
+    public boolean useStdOut()
+    {
+        return false;
+    }
+
+    @Override
+    public CloseableDaemonThread bindCommandReader( @Nonnull CommandReader commands,
+                                                    @Nonnull WritableByteChannel stdIn )
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public CloseableDaemonThread bindCommandReader( @Nonnull CommandReader commands )
+    {
+        return new StreamFeeder( "commands-fork-" + getForkChannelId(), channel, commands );
+    }
+
+    @Override
+    public CloseableDaemonThread bindEventHandler( @Nonnull StreamConsumer consumer,
+                                                   @Nonnull ReadableByteChannel stdOut,
+                                                   @Nonnull CountdownCloseable countdownCloseable )
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public CloseableDaemonThread bindEventHandler( @Nonnull StreamConsumer consumer )
+    {
+        CountdownCloseable countdownCloseable = new CountdownCloseable( null, 0 );
+        return new LineConsumerThread( "events-fork-" + getForkChannelId(), channel, consumer, countdownCloseable );
+    }
+
+    @Override
+    public void close() throws IOException
+    {
+        //noinspection EmptyTryBlock
+        try ( Channel c1 = channel; Channel c2 = server )
+        {
+            // only close all channels
+        }
+    }
+}
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/MainCliOptionsAware.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/SurefireForkNodeFactory.java
similarity index 61%
copy from surefire-api/src/main/java/org/apache/maven/surefire/booter/MainCliOptionsAware.java
copy to maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/SurefireForkNodeFactory.java
index eddebed..c076ba2 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/MainCliOptionsAware.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/SurefireForkNodeFactory.java
@@ -1,4 +1,4 @@
-package org.apache.maven.surefire.booter;
+package org.apache.maven.plugin.surefire.extensions;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -19,17 +19,22 @@ package org.apache.maven.surefire.booter;
  * under the License.
  */
 
-import org.apache.maven.surefire.cli.CommandLineOption;
+import org.apache.maven.surefire.extensions.ForkChannel;
+import org.apache.maven.surefire.extensions.ForkNodeFactory;
 
-import java.util.List;
+import javax.annotation.Nonnegative;
+import javax.annotation.Nonnull;
+import java.io.IOException;
 
 /**
- * CLI options in plugin (main) JVM process.
  *
- * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
- * @since 2.19
  */
-interface MainCliOptionsAware
+public class SurefireForkNodeFactory implements ForkNodeFactory
 {
-    void setMainCliOptions( List<CommandLineOption> mainCliOptions );
+    @Nonnull
+    @Override
+    public ForkChannel createForkChannel( @Nonnegative int forkChannelId ) throws IOException
+    {
+        return new SurefireForkChannel( forkChannelId );
+    }
 }
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/AbstractSurefireMojoJava7PlusTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/AbstractSurefireMojoJava7PlusTest.java
index 9faa4eb..abd896e 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/AbstractSurefireMojoJava7PlusTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/AbstractSurefireMojoJava7PlusTest.java
@@ -28,6 +28,7 @@ import org.apache.maven.surefire.booter.ClassLoaderConfiguration;
 import org.apache.maven.surefire.booter.Classpath;
 import org.apache.maven.surefire.booter.ModularClasspathConfiguration;
 import org.apache.maven.surefire.booter.StartupConfiguration;
+import org.apache.maven.surefire.extensions.ForkNodeFactory;
 import org.apache.maven.surefire.suite.RunResult;
 import org.apache.maven.surefire.util.DefaultScanResult;
 import org.codehaus.plexus.languages.java.jpms.JavaModuleDescriptor;
@@ -180,11 +181,26 @@ public class AbstractSurefireMojoJava7PlusTest
                 "jar", "", handler );
         loggerApi.setFile( mockFile( "surefire-logger-api.jar" ) );
 
+        Artifact spi = new DefaultArtifact( "org.apache.maven.surefire", "surefire-extensions-spi",
+            createFromVersion( "1" ), "runtime", "jar", "", handler );
+        spi.setFile( mockFile( "surefire-extensions-spi.jar" ) );
+
+        Artifact booter = new DefaultArtifact( "org.apache.maven.surefire", "surefire-booter",
+            createFromVersion( "1" ), "runtime", "jar", "", handler );
+        booter.setFile( mockFile( "surefire-booter.jar" ) );
+
+        Artifact utils = new DefaultArtifact( "org.apache.maven.surefire", "surefire-shared-utils",
+            createFromVersion( "1" ), "runtime", "jar", "", handler );
+        utils.setFile( mockFile( "surefire-shared-utils.jar" ) );
+
         Map<String, Artifact> artifacts = new HashMap<>();
         artifacts.put( "org.apache.maven.surefire:maven-surefire-common", common );
         artifacts.put( "org.apache.maven.surefire:surefire-extensions-api", ext );
         artifacts.put( "org.apache.maven.surefire:surefire-api", api );
         artifacts.put( "org.apache.maven.surefire:surefire-logger-api", loggerApi );
+        artifacts.put( "org.apache.maven.surefire:surefire-extensions-spi", spi );
+        artifacts.put( "org.apache.maven.surefire:surefire-booter", booter );
+        artifacts.put( "org.apache.maven.surefire:surefire-shared-utils", utils );
         when( mojo.getPluginArtifactMap() ).thenReturn( artifacts );
 
         StartupConfiguration conf = invokeMethod( mojo, "newStartupConfigWithModularPath",
@@ -193,7 +209,6 @@ public class AbstractSurefireMojoJava7PlusTest
 
         verify( mojo, times( 1 ) ).effectiveIsEnableAssertions();
         verify( mojo, times( 1 ) ).isChildDelegation();
-        verify( mojo, times( 1 ) ).getEffectiveForkCount();
         verify( mojo, times( 1 ) ).getTestClassesDirectory();
         verify( scanResult, times( 1 ) ).getClasses();
         verifyStatic( ResolvePathsRequest.class, times( 1 ) );
@@ -219,8 +234,8 @@ public class AbstractSurefireMojoJava7PlusTest
                         "test(compact) classpath:  non-modular.jar  junit.jar  hamcrest.jar",
                         "test(compact) modulepath:  modular.jar  classes",
                         "provider(compact) classpath:  surefire-provider.jar",
-                        "in-process classpath:  surefire-provider.jar  maven-surefire-common.jar  surefire-extensions-api.jar  surefire-api.jar  surefire-logger-api.jar",
-                        "in-process(compact) classpath:  surefire-provider.jar  maven-surefire-common.jar  surefire-extensions-api.jar  surefire-api.jar  surefire-logger-api.jar"
+                        "in-process classpath:  surefire-provider.jar  maven-surefire-common.jar  surefire-booter.jar  surefire-extensions-api.jar  surefire-api.jar  surefire-extensions-spi.jar  surefire-logger-api.jar  surefire-shared-utils.jar",
+                        "in-process(compact) classpath:  surefire-provider.jar  maven-surefire-common.jar  surefire-booter.jar  surefire-extensions-api.jar  surefire-api.jar  surefire-extensions-spi.jar  surefire-logger-api.jar  surefire-shared-utils.jar"
                 );
 
         assertThat( conf ).isNotNull();
@@ -632,6 +647,12 @@ public class AbstractSurefireMojoJava7PlusTest
         }
 
         @Override
+        protected ForkNodeFactory getForkNode()
+        {
+            return null;
+        }
+
+        @Override
         protected Artifact getMojoArtifact()
         {
             return null;
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/AbstractSurefireMojoTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/AbstractSurefireMojoTest.java
index b1e7c1e..0553de7 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/AbstractSurefireMojoTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/AbstractSurefireMojoTest.java
@@ -42,6 +42,7 @@ import org.apache.maven.shared.transfer.dependencies.resolve.DependencyResolver;
 import org.apache.maven.surefire.booter.ClassLoaderConfiguration;
 import org.apache.maven.surefire.booter.Classpath;
 import org.apache.maven.surefire.booter.StartupConfiguration;
+import org.apache.maven.surefire.extensions.ForkNodeFactory;
 import org.apache.maven.surefire.suite.RunResult;
 import org.codehaus.plexus.logging.Logger;
 import org.junit.Before;
@@ -312,11 +313,26 @@ public class AbstractSurefireMojoTest
                 createFromVersion( "1" ), "runtime", "jar", "", handler );
         loggerApi.setFile( mockFile( "surefire-logger-api.jar" ) );
 
+        Artifact spi = new DefaultArtifact( "org.apache.maven.surefire", "surefire-extensions-spi",
+            createFromVersion( "1" ), "runtime", "jar", "", handler );
+        spi.setFile( mockFile( "surefire-extensions-spi.jar" ) );
+
+        Artifact booter = new DefaultArtifact( "org.apache.maven.surefire", "surefire-booter",
+            createFromVersion( "1" ), "runtime", "jar", "", handler );
+        booter.setFile( mockFile( "surefire-booter.jar" ) );
+
+        Artifact utils = new DefaultArtifact( "org.apache.maven.surefire", "surefire-shared-utils",
+            createFromVersion( "1" ), "runtime", "jar", "", handler );
+        utils.setFile( mockFile( "surefire-shared-utils.jar" ) );
+
         Map<String, Artifact> providerArtifactsMap = new HashMap<>();
         providerArtifactsMap.put( "org.apache.maven.surefire:maven-surefire-common", common );
         providerArtifactsMap.put( "org.apache.maven.surefire:surefire-extensions-api", ext );
         providerArtifactsMap.put( "org.apache.maven.surefire:surefire-api", api );
         providerArtifactsMap.put( "org.apache.maven.surefire:surefire-logger-api", loggerApi );
+        providerArtifactsMap.put( "org.apache.maven.surefire:surefire-extensions-spi", spi );
+        providerArtifactsMap.put( "org.apache.maven.surefire:surefire-booter", booter );
+        providerArtifactsMap.put( "org.apache.maven.surefire:surefire-shared-utils", utils );
 
         when( mojo.getPluginArtifactMap() )
                 .thenReturn( providerArtifactsMap );
@@ -358,7 +374,6 @@ public class AbstractSurefireMojoTest
 
         verify( mojo, times( 1 ) ).effectiveIsEnableAssertions();
         verify( mojo, times( 1 ) ).isChildDelegation();
-        verify( mojo, times( 1 ) ).getEffectiveForkCount();
         ArgumentCaptor<String> argument = ArgumentCaptor.forClass( String.class );
         verify( logger, times( 6 ) ).debug( argument.capture() );
         assertThat( argument.getAllValues() )
@@ -366,8 +381,8 @@ public class AbstractSurefireMojoTest
                 "provider classpath:  surefire-provider.jar",
                 "test(compact) classpath:  test-classes  classes  junit.jar  hamcrest.jar",
                 "provider(compact) classpath:  surefire-provider.jar",
-                "in-process classpath:  surefire-provider.jar  maven-surefire-common.jar  surefire-extensions-api.jar  surefire-api.jar  surefire-logger-api.jar",
-                "in-process(compact) classpath:  surefire-provider.jar  maven-surefire-common.jar  surefire-extensions-api.jar  surefire-api.jar  surefire-logger-api.jar"
+                "in-process classpath:  surefire-provider.jar  maven-surefire-common.jar  surefire-booter.jar  surefire-extensions-api.jar  surefire-api.jar  surefire-extensions-spi.jar  surefire-logger-api.jar  surefire-shared-utils.jar",
+                "in-process(compact) classpath:  surefire-provider.jar  maven-surefire-common.jar  surefire-booter.jar  surefire-extensions-api.jar  surefire-api.jar  surefire-extensions-spi.jar  surefire-logger-api.jar  surefire-shared-utils.jar"
                 );
 
         assertThat( conf.getClassLoaderConfiguration() )
@@ -405,7 +420,7 @@ public class AbstractSurefireMojoTest
         Artifact provider = new DefaultArtifact( "com.example", "provider", createFromVersion( "1" ), "runtime",
                 "jar", "", handler );
         provider.setFile( mockFile( "original-test-provider.jar" ) );
-        HashSet<Artifact> providerClasspath = new HashSet<>( asList( provider ) );
+        Set<Artifact> providerClasspath = singleton( provider );
         when( providerInfo.getProviderClasspath() ).thenReturn( providerClasspath );
 
         StartupConfiguration startupConfiguration = startupConfigurationForProvider( providerInfo );
@@ -448,11 +463,26 @@ public class AbstractSurefireMojoTest
                 createFromVersion( "1" ), "runtime", "jar", "", handler );
         loggerApi.setFile( mockFile( "surefire-logger-api.jar" ) );
 
+        Artifact spi = new DefaultArtifact( "org.apache.maven.surefire", "surefire-extensions-spi",
+            createFromVersion( "1" ), "runtime", "jar", "", handler );
+        spi.setFile( mockFile( "surefire-extensions-spi.jar" ) );
+
+        Artifact booter = new DefaultArtifact( "org.apache.maven.surefire", "surefire-booter",
+            createFromVersion( "1" ), "runtime", "jar", "", handler );
+        booter.setFile( mockFile( "surefire-booter.jar" ) );
+
+        Artifact utils = new DefaultArtifact( "org.apache.maven.surefire", "surefire-shared-utils",
+            createFromVersion( "1" ), "runtime", "jar", "", handler );
+        utils.setFile( mockFile( "surefire-shared-utils.jar" ) );
+
         Map<String, Artifact> providerArtifactsMap = new HashMap<>();
         providerArtifactsMap.put( "org.apache.maven.surefire:maven-surefire-common", common );
         providerArtifactsMap.put( "org.apache.maven.surefire:surefire-extensions-api", ext );
         providerArtifactsMap.put( "org.apache.maven.surefire:surefire-api", api );
         providerArtifactsMap.put( "org.apache.maven.surefire:surefire-logger-api", loggerApi );
+        providerArtifactsMap.put( "org.apache.maven.surefire:surefire-extensions-spi", spi );
+        providerArtifactsMap.put( "org.apache.maven.surefire:surefire-booter", booter );
+        providerArtifactsMap.put( "org.apache.maven.surefire:surefire-shared-utils", utils );
 
         when( mojo.getPluginArtifactMap() ).thenReturn( providerArtifactsMap );
 
@@ -2121,6 +2151,12 @@ public class AbstractSurefireMojoTest
         }
 
         @Override
+        protected ForkNodeFactory getForkNode()
+        {
+            return null;
+        }
+
+        @Override
         protected Artifact getMojoArtifact()
         {
             return new DefaultArtifact( "org.apache.maven.surefire", "maven-surefire-plugin", createFromVersion( "1" ),
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/CommonReflectorTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/CommonReflectorTest.java
index 1f94a75..4f954d4 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/CommonReflectorTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/CommonReflectorTest.java
@@ -23,15 +23,30 @@ import org.apache.maven.plugin.surefire.extensions.SurefireConsoleOutputReporter
 import org.apache.maven.plugin.surefire.extensions.SurefireStatelessReporter;
 import org.apache.maven.plugin.surefire.extensions.SurefireStatelessTestsetInfoReporter;
 import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
+import org.apache.maven.plugin.surefire.log.api.ConsoleLoggerDecorator;
+import org.apache.maven.plugin.surefire.log.api.PrintStreamLogger;
 import org.apache.maven.plugin.surefire.report.DefaultReporterFactory;
+import org.hamcrest.MatcherAssert;
 import org.junit.Before;
 import org.junit.Test;
+import org.mockito.ArgumentCaptor;
 
 import java.io.File;
 
 import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.apache.maven.surefire.util.ReflectionUtils.getMethod;
+import static org.apache.maven.surefire.util.ReflectionUtils.invokeMethodWithArray;
 import static org.fest.assertions.Assertions.assertThat;
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.CoreMatchers.sameInstance;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 import static org.powermock.reflect.Whitebox.getInternalState;
 
 /**
@@ -96,4 +111,39 @@ public class CommonReflectorTest
         assertThat( reportConfiguration.getConsoleOutputReporter().toString() )
                 .isEqualTo( consoleOutputReporter.toString() );
     }
+
+    @Test
+    public void shouldProxyConsoleLogger()
+    {
+        ClassLoader cl = Thread.currentThread().getContextClassLoader();
+        ConsoleLogger logger = spy( new PrintStreamLogger( System.out ) );
+        Object mirror = CommonReflector.createConsoleLogger( logger, cl );
+        MatcherAssert.assertThat( mirror, is( notNullValue() ) );
+        MatcherAssert.assertThat( mirror.getClass().getInterfaces()[0].getName(), is( ConsoleLogger.class.getName() ) );
+        MatcherAssert.assertThat( mirror, is( not( sameInstance( (Object) logger ) ) ) );
+        MatcherAssert.assertThat( mirror, is( instanceOf( ConsoleLoggerDecorator.class ) ) );
+        invokeMethodWithArray( mirror, getMethod( mirror, "info", String.class ), "Hi There!" );
+        verify( logger, times( 1 ) ).info( "Hi There!" );
+    }
+
+    @Test
+    public void testCreateConsoleLogger()
+    {
+        ClassLoader cl = Thread.currentThread().getContextClassLoader();
+        ConsoleLogger consoleLogger = mock( ConsoleLogger.class );
+        ConsoleLogger decorator = (ConsoleLogger) CommonReflector.createConsoleLogger( consoleLogger, cl );
+        assertThat( decorator )
+                .isNotSameAs( consoleLogger );
+
+        assertThat( decorator.isDebugEnabled() ).isFalse();
+        when( consoleLogger.isDebugEnabled() ).thenReturn( true );
+        assertThat( decorator.isDebugEnabled() ).isTrue();
+        verify( consoleLogger, times( 2 ) ).isDebugEnabled();
+
+        decorator.info( "msg" );
+        ArgumentCaptor<String> argumentMsg = ArgumentCaptor.forClass( String.class );
+        verify( consoleLogger, times( 1 ) ).info( argumentMsg.capture() );
+        assertThat( argumentMsg.getAllValues() ).hasSize( 1 );
+        assertThat( argumentMsg.getAllValues().get( 0 ) ).isEqualTo( "msg" );
+    }
 }
\ No newline at end of file
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/MojoMocklessTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/MojoMocklessTest.java
index ccf63f7..835ce8e 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/MojoMocklessTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/MojoMocklessTest.java
@@ -28,6 +28,7 @@ import org.apache.maven.plugin.MojoFailureException;
 import org.apache.maven.plugin.surefire.extensions.SurefireConsoleOutputReporter;
 import org.apache.maven.plugin.surefire.extensions.SurefireStatelessReporter;
 import org.apache.maven.plugin.surefire.extensions.SurefireStatelessTestsetInfoReporter;
+import org.apache.maven.surefire.extensions.ForkNodeFactory;
 import org.apache.maven.surefire.suite.RunResult;
 import org.apache.maven.surefire.util.DefaultScanResult;
 import org.apache.maven.toolchain.Toolchain;
@@ -751,6 +752,12 @@ public class MojoMocklessTest
         }
 
         @Override
+        protected ForkNodeFactory getForkNode()
+        {
+            return null;
+        }
+
+        @Override
         protected String getEnableProcessChecker()
         {
             return null;
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/SurefireReflectorTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/SurefireReflectorTest.java
deleted file mode 100644
index 2553617..0000000
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/SurefireReflectorTest.java
+++ /dev/null
@@ -1,71 +0,0 @@
-package org.apache.maven.plugin.surefire;
-
-/*
- * 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.plugin.surefire.log.api.ConsoleLogger;
-import org.apache.maven.plugin.surefire.log.api.ConsoleLoggerDecorator;
-import org.apache.maven.plugin.surefire.log.api.PrintStreamLogger;
-import org.apache.maven.surefire.booter.IsolatedClassLoader;
-import org.apache.maven.surefire.booter.SurefireReflector;
-import org.junit.Before;
-import org.junit.Test;
-
-import static org.apache.maven.surefire.util.ReflectionUtils.getMethod;
-import static org.apache.maven.surefire.util.ReflectionUtils.invokeMethodWithArray;
-import static org.hamcrest.CoreMatchers.instanceOf;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.CoreMatchers.not;
-import static org.hamcrest.CoreMatchers.notNullValue;
-import static org.hamcrest.CoreMatchers.sameInstance;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
-/**
- * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
- * @see ConsoleLogger
- * @see SurefireReflector
- * @since 2.20
- */
-public class SurefireReflectorTest
-{
-    private ConsoleLogger logger;
-    private ClassLoader cl;
-
-    @Before
-    public void prepareData()
-    {
-        logger = spy( new PrintStreamLogger( System.out ) );
-        cl = new IsolatedClassLoader( Thread.currentThread().getContextClassLoader(), false, "role" );
-    }
-
-    @Test
-    public void shouldProxyConsoleLogger()
-    {
-        Object mirror = SurefireReflector.createConsoleLogger( logger, cl );
-        assertThat( mirror, is( notNullValue() ) );
-        assertThat( mirror.getClass().getInterfaces()[0].getName(), is( ConsoleLogger.class.getName() ) );
-        assertThat( mirror, is( not( sameInstance( (Object) logger ) ) ) );
-        assertThat( mirror, is( instanceOf( ConsoleLoggerDecorator.class ) ) );
-        invokeMethodWithArray( mirror, getMethod( mirror, "info", String.class ), "Hi There!" );
-        verify( logger, times( 1 ) ).info( "Hi There!" );
-    }
-}
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/BooterDeserializerProviderConfigurationTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/BooterDeserializerProviderConfigurationTest.java
index ca42c44..10563a8 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/BooterDeserializerProviderConfigurationTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/BooterDeserializerProviderConfigurationTest.java
@@ -25,7 +25,6 @@ import org.apache.maven.surefire.shared.io.FileUtils;
 import org.apache.maven.surefire.booter.BooterDeserializer;
 import org.apache.maven.surefire.booter.ClassLoaderConfiguration;
 import org.apache.maven.surefire.booter.ClasspathConfiguration;
-import org.apache.maven.surefire.booter.ProcessCheckerType;
 import org.apache.maven.surefire.booter.PropertiesWrapper;
 import org.apache.maven.surefire.booter.ProviderConfiguration;
 import org.apache.maven.surefire.booter.Shutdown;
@@ -52,6 +51,7 @@ import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 
+import static org.apache.maven.surefire.booter.ProcessCheckerType.ALL;
 import static org.apache.maven.surefire.cli.CommandLineOption.LOGGING_LEVEL_DEBUG;
 import static org.apache.maven.surefire.cli.CommandLineOption.REACTOR_FAIL_FAST;
 import static org.apache.maven.surefire.cli.CommandLineOption.SHOW_ERRORS;
@@ -260,9 +260,10 @@ public class BooterDeserializerProviderConfigurationTest
             test = "aTest";
         }
         final File propsTest = booterSerializer.serialize( props, booterConfiguration, testProviderConfiguration, test,
-                                                           readTestsFromInStream, 51L, 1 );
+                                                           readTestsFromInStream, 51L, 1, "pipe://1" );
         BooterDeserializer booterDeserializer = new BooterDeserializer( new FileInputStream( propsTest ) );
         assertEquals( "51", (Object) booterDeserializer.getPluginPid() );
+        assertEquals( "pipe://1", booterDeserializer.getConnectionString() );
         return booterDeserializer.deserialize();
     }
 
@@ -285,8 +286,7 @@ public class BooterDeserializerProviderConfigurationTest
     {
         ClasspathConfiguration classpathConfiguration = new ClasspathConfiguration( true, true );
 
-        return new StartupConfiguration( "com.provider", classpathConfiguration, classLoaderConfiguration, false,
-                                         false, ProcessCheckerType.ALL );
+        return new StartupConfiguration( "com.provider", classpathConfiguration, classLoaderConfiguration, ALL );
     }
 
     private File getTestSourceDirectory()
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/BooterDeserializerStartupConfigurationTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/BooterDeserializerStartupConfigurationTest.java
index 4e9bbc9..63f6162 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/BooterDeserializerStartupConfigurationTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/BooterDeserializerStartupConfigurationTest.java
@@ -20,24 +20,23 @@ package org.apache.maven.plugin.surefire.booterclient;
  */
 
 import junit.framework.TestCase;
-import org.apache.maven.surefire.shared.io.FileUtils;
 import org.apache.maven.surefire.booter.AbstractPathConfiguration;
 import org.apache.maven.surefire.booter.BooterDeserializer;
+import org.apache.maven.surefire.booter.ClassLoaderConfiguration;
 import org.apache.maven.surefire.booter.Classpath;
 import org.apache.maven.surefire.booter.ClasspathConfiguration;
-import org.apache.maven.surefire.booter.ClassLoaderConfiguration;
-import org.apache.maven.surefire.booter.ProcessCheckerType;
 import org.apache.maven.surefire.booter.PropertiesWrapper;
 import org.apache.maven.surefire.booter.ProviderConfiguration;
-import org.apache.maven.surefire.booter.StartupConfiguration;
 import org.apache.maven.surefire.booter.Shutdown;
+import org.apache.maven.surefire.booter.StartupConfiguration;
 import org.apache.maven.surefire.cli.CommandLineOption;
 import org.apache.maven.surefire.report.ReporterConfiguration;
+import org.apache.maven.surefire.shared.io.FileUtils;
 import org.apache.maven.surefire.testset.DirectoryScannerParameters;
 import org.apache.maven.surefire.testset.RunOrderParameters;
 import org.apache.maven.surefire.testset.TestArtifactInfo;
-import org.apache.maven.surefire.testset.TestRequest;
 import org.apache.maven.surefire.testset.TestListResolver;
+import org.apache.maven.surefire.testset.TestRequest;
 import org.apache.maven.surefire.util.RunOrder;
 import org.junit.After;
 import org.junit.Before;
@@ -50,6 +49,7 @@ import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
 
+import static org.apache.maven.surefire.booter.ProcessCheckerType.ALL;
 import static org.apache.maven.surefire.cli.CommandLineOption.LOGGING_LEVEL_DEBUG;
 import static org.apache.maven.surefire.cli.CommandLineOption.REACTOR_FAIL_FAST;
 import static org.apache.maven.surefire.cli.CommandLineOption.SHOW_ERRORS;
@@ -105,7 +105,7 @@ public class BooterDeserializerStartupConfigurationTest
 
     public void testProcessChecker() throws IOException
     {
-        assertEquals( ProcessCheckerType.ALL, getReloadedStartupConfiguration().getProcessChecker() );
+        assertEquals( ALL, getReloadedStartupConfiguration().getProcessChecker() );
     }
 
     private void assertCpConfigEquals( ClasspathConfiguration expectedConfiguration,
@@ -136,13 +136,13 @@ public class BooterDeserializerStartupConfigurationTest
 
     public void testProcessCheckerAll() throws IOException
     {
-        assertEquals( ProcessCheckerType.ALL, getReloadedStartupConfiguration().getProcessChecker() );
+        assertEquals( ALL, getReloadedStartupConfiguration().getProcessChecker() );
     }
 
     public void testProcessCheckerNull() throws IOException
     {
         StartupConfiguration startupConfiguration = new StartupConfiguration( "com.provider", classpathConfiguration,
-                getManifestOnlyJarForkConfiguration(), false, false, null );
+                getManifestOnlyJarForkConfiguration(), null );
         assertNull( saveAndReload( startupConfiguration ).getProcessChecker() );
     }
 
@@ -178,15 +178,15 @@ public class BooterDeserializerStartupConfigurationTest
         BooterSerializer booterSerializer = new BooterSerializer( forkConfiguration );
         String aTest = "aTest";
         File propsTest = booterSerializer.serialize( props, getProviderConfiguration(), startupConfiguration, aTest,
-                false, null, 1 );
+                false, null, 1, "tcp://127.0.0.1:63003" );
         BooterDeserializer booterDeserializer = new BooterDeserializer( new FileInputStream( propsTest ) );
         assertNull( booterDeserializer.getPluginPid() );
+        assertEquals( "tcp://127.0.0.1:63003", booterDeserializer.getConnectionString() );
         return booterDeserializer.getStartupConfiguration();
     }
 
     private ProviderConfiguration getProviderConfiguration()
     {
-
         File cwd = new File( "." );
         DirectoryScannerParameters directoryScannerParameters =
             new DirectoryScannerParameters( cwd, new ArrayList<String>(), new ArrayList<String>(),
@@ -204,8 +204,7 @@ public class BooterDeserializerStartupConfigurationTest
 
     private StartupConfiguration getTestStartupConfiguration( ClassLoaderConfiguration classLoaderConfiguration )
     {
-        return new StartupConfiguration( "com.provider", classpathConfiguration, classLoaderConfiguration, false,
-                                         false, ProcessCheckerType.ALL );
+        return new StartupConfiguration( "com.provider", classpathConfiguration, classLoaderConfiguration, ALL );
     }
 
     private File getTestSourceDirectory()
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/DefaultForkConfigurationTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/DefaultForkConfigurationTest.java
index 06fe754..45a6b4a 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/DefaultForkConfigurationTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/DefaultForkConfigurationTest.java
@@ -27,7 +27,7 @@ import org.apache.maven.surefire.booter.Classpath;
 import org.apache.maven.surefire.booter.ClasspathConfiguration;
 import org.apache.maven.surefire.booter.ForkedBooter;
 import org.apache.maven.surefire.booter.StartupConfiguration;
-import org.apache.maven.surefire.booter.SurefireBooterForkException;
+import org.apache.maven.surefire.extensions.ForkNodeFactory;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -80,6 +80,7 @@ public class DefaultForkConfigurationTest
     private boolean reuseForks;
     private Platform pluginPlatform;
     private ConsoleLogger log;
+    private ForkNodeFactory forkNodeFactory;
 
     @Before
     public void setup()
@@ -97,6 +98,7 @@ public class DefaultForkConfigurationTest
         reuseForks = true;
         pluginPlatform = new Platform();
         log = mock( ConsoleLogger.class );
+        forkNodeFactory = mock( ForkNodeFactory.class );
     }
 
     @Test
@@ -104,16 +106,15 @@ public class DefaultForkConfigurationTest
     {
         DefaultForkConfiguration config = new DefaultForkConfiguration( booterClasspath, tempDirectory, debugLine,
                 workingDirectory, modelProperties, argLine, environmentVariables, excludedEnvironmentVariables,
-                debug, forkCount, reuseForks, pluginPlatform, log )
+                debug, forkCount, reuseForks, pluginPlatform, log, forkNodeFactory )
         {
 
             @Override
             protected void resolveClasspath( @Nonnull OutputStreamFlushableCommandline cli,
                                              @Nonnull String booterThatHasMainMethod,
                                              @Nonnull StartupConfiguration config,
-                                             @Nonnull File dumpLogDirectory ) throws SurefireBooterForkException
+                                             @Nonnull File dumpLogDirectory )
             {
-
             }
         };
 
@@ -130,16 +131,15 @@ public class DefaultForkConfigurationTest
         argLine = "";
         DefaultForkConfiguration config = new DefaultForkConfiguration( booterClasspath, tempDirectory, debugLine,
                 workingDirectory, modelProperties, argLine, environmentVariables, excludedEnvironmentVariables,
-                debug, forkCount, reuseForks, pluginPlatform, log )
+                debug, forkCount, reuseForks, pluginPlatform, log, forkNodeFactory )
         {
 
             @Override
             protected void resolveClasspath( @Nonnull OutputStreamFlushableCommandline cli,
                                              @Nonnull String booterThatHasMainMethod,
                                              @Nonnull StartupConfiguration config,
-                                             @Nonnull File dumpLogDirectory ) throws SurefireBooterForkException
+                                             @Nonnull File dumpLogDirectory )
             {
-
             }
         };
 
@@ -156,16 +156,15 @@ public class DefaultForkConfigurationTest
         argLine = "\n\r";
         DefaultForkConfiguration config = new DefaultForkConfiguration( booterClasspath, tempDirectory, debugLine,
                 workingDirectory, modelProperties, argLine, environmentVariables, excludedEnvironmentVariables,
-                debug, forkCount, reuseForks, pluginPlatform, log )
+                debug, forkCount, reuseForks, pluginPlatform, log, forkNodeFactory )
         {
 
             @Override
             protected void resolveClasspath( @Nonnull OutputStreamFlushableCommandline cli,
                                              @Nonnull String booterThatHasMainMethod,
                                              @Nonnull StartupConfiguration config,
-                                             @Nonnull File dumpLogDirectory ) throws SurefireBooterForkException
+                                             @Nonnull File dumpLogDirectory )
             {
-
             }
         };
 
@@ -182,16 +181,15 @@ public class DefaultForkConfigurationTest
         argLine = "-Dfile.encoding=UTF-8";
         DefaultForkConfiguration config = new DefaultForkConfiguration( booterClasspath, tempDirectory, debugLine,
                 workingDirectory, modelProperties, argLine, environmentVariables, excludedEnvironmentVariables,
-                debug, forkCount, reuseForks, pluginPlatform, log )
+                debug, forkCount, reuseForks, pluginPlatform, log, forkNodeFactory )
         {
 
             @Override
             protected void resolveClasspath( @Nonnull OutputStreamFlushableCommandline cli,
                                              @Nonnull String booterThatHasMainMethod,
                                              @Nonnull StartupConfiguration config,
-                                             @Nonnull File dumpLogDirectory ) throws SurefireBooterForkException
+                                             @Nonnull File dumpLogDirectory )
             {
-
             }
         };
 
@@ -209,16 +207,15 @@ public class DefaultForkConfigurationTest
         argLine = "-Dfile.encoding=@{encoding}";
         DefaultForkConfiguration config = new DefaultForkConfiguration( booterClasspath, tempDirectory, debugLine,
                 workingDirectory, modelProperties, argLine, environmentVariables, excludedEnvironmentVariables,
-                debug, forkCount, reuseForks, pluginPlatform, log )
+                debug, forkCount, reuseForks, pluginPlatform, log, forkNodeFactory )
         {
 
             @Override
             protected void resolveClasspath( @Nonnull OutputStreamFlushableCommandline cli,
                                              @Nonnull String booterThatHasMainMethod,
                                              @Nonnull StartupConfiguration config,
-                                             @Nonnull File dumpLogDirectory ) throws SurefireBooterForkException
+                                             @Nonnull File dumpLogDirectory )
             {
-
             }
         };
 
@@ -235,16 +232,15 @@ public class DefaultForkConfigurationTest
         argLine = "a\n\rb";
         DefaultForkConfiguration config = new DefaultForkConfiguration( booterClasspath, tempDirectory, debugLine,
                 workingDirectory, modelProperties, argLine, environmentVariables, excludedEnvironmentVariables,
-                debug, forkCount, reuseForks, pluginPlatform, log )
+                debug, forkCount, reuseForks, pluginPlatform, log, forkNodeFactory )
         {
 
             @Override
             protected void resolveClasspath( @Nonnull OutputStreamFlushableCommandline cli,
                                              @Nonnull String booterThatHasMainMethod,
                                              @Nonnull StartupConfiguration config,
-                                             @Nonnull File dumpLogDirectory ) throws SurefireBooterForkException
+                                             @Nonnull File dumpLogDirectory )
             {
-
             }
         };
 
@@ -261,16 +257,15 @@ public class DefaultForkConfigurationTest
         argLine = "-Dthread=${surefire.threadNumber}";
         DefaultForkConfiguration config = new DefaultForkConfiguration( booterClasspath, tempDirectory, debugLine,
                 workingDirectory, modelProperties, argLine, environmentVariables, excludedEnvironmentVariables,
-                debug, forkCount, reuseForks, pluginPlatform, log )
+                debug, forkCount, reuseForks, pluginPlatform, log, forkNodeFactory )
         {
 
             @Override
             protected void resolveClasspath( @Nonnull OutputStreamFlushableCommandline cli,
                                              @Nonnull String booterThatHasMainMethod,
                                              @Nonnull StartupConfiguration config,
-                                             @Nonnull File dumpLogDirectory ) throws SurefireBooterForkException
+                                             @Nonnull File dumpLogDirectory )
             {
-
             }
         };
 
@@ -287,16 +282,15 @@ public class DefaultForkConfigurationTest
         argLine = "-Dthread=${surefire.forkNumber}";
         DefaultForkConfiguration config = new DefaultForkConfiguration( booterClasspath, tempDirectory, debugLine,
                 workingDirectory, modelProperties, argLine, environmentVariables, excludedEnvironmentVariables,
-                debug, forkCount, reuseForks, pluginPlatform, log )
+                debug, forkCount, reuseForks, pluginPlatform, log, forkNodeFactory )
         {
 
             @Override
             protected void resolveClasspath( @Nonnull OutputStreamFlushableCommandline cli,
                                              @Nonnull String booterThatHasMainMethod,
                                              @Nonnull StartupConfiguration config,
-                                             @Nonnull File dumpLogDirectory ) throws SurefireBooterForkException
+                                             @Nonnull File dumpLogDirectory )
             {
-
             }
         };
 
@@ -313,7 +307,7 @@ public class DefaultForkConfigurationTest
         ClassLoaderConfiguration clc = new ClassLoaderConfiguration( true, true );
         ClasspathConfiguration cc = new ClasspathConfiguration( true, true );
         StartupConfiguration conf = new StartupConfiguration( "org.apache.maven.shadefire.surefire.MyProvider",
-                cc, clc, false, false, null );
+                cc, clc, null );
         StartupConfiguration confMock = spy( conf );
         mockStatic( Relocator.class );
         when( Relocator.relocate( anyString() ) ).thenCallRealMethod();
@@ -334,7 +328,7 @@ public class DefaultForkConfigurationTest
         ClassLoaderConfiguration clc = new ClassLoaderConfiguration( true, true );
         ClasspathConfiguration cc = new ClasspathConfiguration( true, true );
         StartupConfiguration conf =
-                new StartupConfiguration( "org.apache.maven.surefire.MyProvider", cc, clc, false, false, null );
+                new StartupConfiguration( "org.apache.maven.surefire.MyProvider", cc, clc, null );
         StartupConfiguration confMock = spy( conf );
         mockStatic( Relocator.class );
         when( Relocator.relocate( anyString() ) ).thenCallRealMethod();
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ForkConfigurationTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ForkConfigurationTest.java
index 72e5372..bc01ee8 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ForkConfigurationTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ForkConfigurationTest.java
@@ -30,6 +30,7 @@ import org.apache.maven.surefire.booter.Classpath;
 import org.apache.maven.surefire.booter.ClasspathConfiguration;
 import org.apache.maven.surefire.booter.StartupConfiguration;
 import org.apache.maven.surefire.booter.SurefireBooterForkException;
+import org.apache.maven.surefire.extensions.ForkNodeFactory;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -47,6 +48,7 @@ import static org.fest.util.Files.temporaryFolder;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
 
 /**
  *
@@ -55,10 +57,7 @@ public class ForkConfigurationTest
 {
     private static final StartupConfiguration STARTUP_CONFIG = new StartupConfiguration( "",
             new ClasspathConfiguration( true, true ),
-            new ClassLoaderConfiguration( true, true ),
-            false,
-            false,
-            ALL );
+            new ClassLoaderConfiguration( true, true ), ALL );
 
     private static int idx = 0;
 
@@ -91,7 +90,7 @@ public class ForkConfigurationTest
         ClasspathConfiguration cpConfig = new ClasspathConfiguration( new Classpath( cp ), emptyClasspath(),
                 emptyClasspath(), true, true );
         ClassLoaderConfiguration clc = new ClassLoaderConfiguration( true, true );
-        StartupConfiguration startup = new StartupConfiguration( "", cpConfig, clc, false, false, ALL );
+        StartupConfiguration startup = new StartupConfiguration( "", cpConfig, clc, ALL );
 
         Commandline cli = config.createCommandLine( startup, 1, temporaryFolder() );
 
@@ -111,7 +110,7 @@ public class ForkConfigurationTest
         ClasspathConfiguration cpConfig = new ClasspathConfiguration( new Classpath( cp ), emptyClasspath(),
                 emptyClasspath(), true, true );
         ClassLoaderConfiguration clc = new ClassLoaderConfiguration( true, true );
-        StartupConfiguration startup = new StartupConfiguration( "", cpConfig, clc, false, false, ALL );
+        StartupConfiguration startup = new StartupConfiguration( "", cpConfig, clc, ALL );
 
         Commandline commandLine = config.createCommandLine( startup, 1, temporaryFolder() );
         assertTrue( commandLine.toString().contains( "abc def" ) );
@@ -126,7 +125,7 @@ public class ForkConfigurationTest
         ClasspathConfiguration cpConfig = new ClasspathConfiguration( emptyClasspath(), emptyClasspath(),
                 emptyClasspath(), true, true );
         ClassLoaderConfiguration clc = new ClassLoaderConfiguration( true, true );
-        StartupConfiguration startup = new StartupConfiguration( "", cpConfig, clc, false, false, ALL );
+        StartupConfiguration startup = new StartupConfiguration( "", cpConfig, clc, ALL );
         ForkConfiguration config = getForkConfiguration( cwd.getCanonicalFile() );
         Commandline commandLine = config.createCommandLine( startup, 1, temporaryFolder() );
 
@@ -225,7 +224,7 @@ public class ForkConfigurationTest
         return new JarManifestForkConfiguration( emptyClasspath(), tmpDir, null,
                 cwd, new Properties(), argLine,
                 Collections.<String, String>emptyMap(), new String[0], false, 1, false,
-                platform, new NullConsoleLogger() );
+                platform, new NullConsoleLogger(), mock( ForkNodeFactory.class ) );
     }
 
     // based on http://stackoverflow.com/questions/2591083/getting-version-of-java-in-runtime
@@ -233,6 +232,6 @@ public class ForkConfigurationTest
     private static boolean isJavaVersionAtLeast7u60()
     {
         String[] javaVersionElements = System.getProperty( "java.runtime.version" ).split( "\\.|_|-b" );
-        return Integer.valueOf( javaVersionElements[1] ) >= 7 && Integer.valueOf( javaVersionElements[3] ) >= 60;
+        return Integer.parseInt( javaVersionElements[1] ) >= 7 && Integer.parseInt( javaVersionElements[3] ) >= 60;
     }
 }
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 e0874f0..a7ab4d7 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
@@ -21,12 +21,13 @@ package org.apache.maven.plugin.surefire.booterclient;
 
 import org.apache.maven.plugin.surefire.StartupReportConfiguration;
 import org.apache.maven.plugin.surefire.SurefireProperties;
-import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.AbstractForkInputStream;
+import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.AbstractCommandReader;
 import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.OutputStreamFlushableCommandline;
 import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.TestLessInputStream;
 import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.TestLessInputStream.TestLessInputStreamBuilder;
 import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.TestProvidingInputStream;
 import org.apache.maven.plugin.surefire.booterclient.output.ForkClient;
+import org.apache.maven.plugin.surefire.extensions.LegacyForkNodeFactory;
 import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
 import org.apache.maven.plugin.surefire.report.DefaultReporterFactory;
 import org.apache.maven.surefire.booter.AbstractPathConfiguration;
@@ -37,6 +38,7 @@ import org.apache.maven.surefire.booter.ProviderConfiguration;
 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.extensions.ForkNodeFactory;
 import org.apache.maven.surefire.report.ReporterConfiguration;
 import org.apache.maven.surefire.shared.compress.archivers.zip.Zip64Mode;
 import org.apache.maven.surefire.shared.compress.archivers.zip.ZipArchiveEntry;
@@ -170,12 +172,12 @@ public class ForkStarterTest
         e.expectMessage( containsString( "VM crash or System.exit called?" ) );
 
         Class<?>[] types = {Object.class, PropertiesWrapper.class, ForkClient.class, SurefireProperties.class,
-            int.class, AbstractForkInputStream.class, boolean.class};
+            int.class, AbstractCommandReader.class, ForkNodeFactory.class, boolean.class};
         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 SurefireProperties(), 1, testProvidingInputStream, true );
+            new SurefireProperties(), 1, testProvidingInputStream, new LegacyForkNodeFactory(), true );
         testProvidingInputStream.close();
     }
 
@@ -224,12 +226,12 @@ public class ForkStarterTest
         DefaultReporterFactory reporterFactory = new DefaultReporterFactory( startupReportConfiguration, logger, 1 );
 
         Class<?>[] types = {Object.class, PropertiesWrapper.class, ForkClient.class, SurefireProperties.class,
-            int.class, AbstractForkInputStream.class, boolean.class};
+            int.class, AbstractCommandReader.class, ForkNodeFactory.class, boolean.class};
         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 SurefireProperties(), 1, testLessInputStream, true );
+            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 79c6f0e..2e44e75 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
@@ -25,7 +25,7 @@ import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.Notifiable
 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.ForkedChannelEncoder;
+import org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelEncoder;
 import org.apache.maven.surefire.booter.ForkingRunListener;
 import org.apache.maven.surefire.report.CategorizedReportEntry;
 import org.apache.maven.surefire.report.ConsoleOutputReceiver;
@@ -196,8 +196,8 @@ public class ForkingRunListenerTest
         ForkClient forkStreamClient =
                 new ForkClient( providerReporterFactory, new MockNotifiableTestStream(), log, new AtomicBoolean(), 1 );
 
-        forkStreamClient.consumeMultiLineContent( ":maven:surefire:std:out:sys-prop:normal-run:UTF-8:azE=:djE="
-                + "\n:maven:surefire:std:out:sys-prop:normal-run:UTF-8:azI=:djI=" );
+        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=" );
 
         MatcherAssert.assertThat( forkStreamClient.getTestVmSystemProperties().size(), is( 2 ) );
     }
@@ -239,10 +239,10 @@ public class ForkingRunListenerTest
         ReportEntry expected = createDefaultReportEntry();
         final SimpleReportEntry secondExpected = createAnotherDefaultReportEntry();
 
-        new ForkingRunListener( new ForkedChannelEncoder( printStream ), false )
+        new ForkingRunListener( new LegacyMasterProcessChannelEncoder( printStream ), false )
                 .testStarting( expected );
 
-        new ForkingRunListener( new ForkedChannelEncoder( anotherPrintStream ), false )
+        new ForkingRunListener( new LegacyMasterProcessChannelEncoder( anotherPrintStream ), false )
                 .testSkipped( secondExpected );
 
         TestSetMockReporterFactory providerReporterFactory = new TestSetMockReporterFactory();
@@ -312,7 +312,7 @@ public class ForkingRunListenerTest
 
     private RunListener createForkingRunListener()
     {
-        return new ForkingRunListener( new ForkedChannelEncoder( printStream ), false );
+        return new ForkingRunListener( new LegacyMasterProcessChannelEncoder( printStream ), false );
     }
 
     private class StandardTestRun
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/MainClass.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/MainClass.java
index d90a128..63fdd4e 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/MainClass.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/MainClass.java
@@ -34,15 +34,11 @@ public class MainClass
         }
         else
         {
-            System.out.println( ":maven:surefire:std:out:bye" );
-            if ( System.in.read() == 0
-                && System.in.read() == 0
-                && System.in.read() == 0
-                && System.in.read() == 5
-                && System.in.read() == 0
-                && System.in.read() == 0
-                && System.in.read() == 0
-                && System.in.read() == 0 )
+            System.out.println( ":maven-surefire-event:bye\n" );
+            String byeAck = ":maven-surefire-command:bye-ack:";
+            byte[] cmd = new byte[byeAck.length()];
+            int len = System.in.read( cmd );
+            if ( len != -1 && new String( cmd, 0, len ).equals( byeAck ) )
             {
                 System.exit( 0 );
             }
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ModularClasspathForkConfigurationTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ModularClasspathForkConfigurationTest.java
index 492c5c0..cfa7dce 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ModularClasspathForkConfigurationTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ModularClasspathForkConfigurationTest.java
@@ -27,6 +27,7 @@ import org.apache.maven.surefire.booter.ForkedBooter;
 import org.apache.maven.surefire.booter.ModularClasspath;
 import org.apache.maven.surefire.booter.ModularClasspathConfiguration;
 import org.apache.maven.surefire.booter.StartupConfiguration;
+import org.apache.maven.surefire.extensions.ForkNodeFactory;
 import org.junit.Test;
 
 import java.io.File;
@@ -43,9 +44,10 @@ import static java.nio.charset.StandardCharsets.UTF_8;
 import static java.nio.file.Files.readAllLines;
 import static java.util.Arrays.asList;
 import static java.util.Collections.singleton;
-import static org.apache.maven.surefire.shared.utils.StringUtils.replace;
 import static org.apache.maven.surefire.booter.Classpath.emptyClasspath;
+import static org.apache.maven.surefire.shared.utils.StringUtils.replace;
 import static org.fest.assertions.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
 
 /**
  * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
@@ -66,7 +68,7 @@ public class ModularClasspathForkConfigurationTest
         ModularClasspathForkConfiguration config = new ModularClasspathForkConfiguration( booter, tmp, "", pwd,
                 new Properties(), "",
                 Collections.<String, String>emptyMap(), new String[0], true, 1, true,
-                new Platform(), new NullConsoleLogger() );
+                new Platform(), new NullConsoleLogger(), mock( ForkNodeFactory.class ) );
 
         File patchFile = new File( "target" + separatorChar + "test-classes" );
         File descriptor = new File( tmp, "module-info.class" );
@@ -141,8 +143,8 @@ public class ModularClasspathForkConfigurationTest
                 new ModularClasspathConfiguration( modularClasspath, testClasspathUrls, surefireClasspathUrls,
                         emptyClasspath(), true, true );
         ClassLoaderConfiguration clc = new ClassLoaderConfiguration( true, true );
-        StartupConfiguration startupConfiguration =
-                new StartupConfiguration( "JUnitCoreProvider", modularClasspathConfiguration, clc, true, true, null );
+        StartupConfiguration startupConfiguration = new StartupConfiguration( "JUnitCoreProvider",
+            modularClasspathConfiguration, clc, null );
         OutputStreamFlushableCommandline cli = new OutputStreamFlushableCommandline();
         config.resolveClasspath( cli, ForkedBooter.class.getName(), startupConfiguration,
                 createTempFile( "surefire", "surefire-reports" ) );
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestLessInputStreamBuilderTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestLessInputStreamBuilderTest.java
index bbc85d4..6496bb8 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestLessInputStreamBuilderTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestLessInputStreamBuilderTest.java
@@ -21,20 +21,21 @@ package org.apache.maven.plugin.surefire.booterclient.lazytestprovider;
 
 import org.apache.maven.surefire.booter.Command;
 import org.apache.maven.surefire.booter.MasterProcessCommand;
+import org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelDecoder;
+import org.apache.maven.surefire.providerapi.MasterProcessChannelDecoder;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
 
-import java.io.DataInputStream;
 import java.io.IOException;
+import java.io.InputStream;
 import java.util.Iterator;
 import java.util.NoSuchElementException;
 
-import static org.apache.maven.plugin.surefire.booterclient.lazytestprovider.TestLessInputStream.TestLessInputStreamBuilder;
-import static org.apache.maven.surefire.booter.Command.NOOP;
+import static org.apache.maven.plugin.surefire.booterclient.lazytestprovider
+    .TestLessInputStream.TestLessInputStreamBuilder;
 import static org.apache.maven.surefire.booter.Command.SKIP_SINCE_NEXT_TEST;
 import static org.apache.maven.surefire.booter.MasterProcessCommand.SHUTDOWN;
-import static org.apache.maven.surefire.booter.MasterProcessCommand.decode;
 import static org.apache.maven.surefire.booter.Shutdown.EXIT;
 import static org.apache.maven.surefire.booter.Shutdown.KILL;
 import static org.hamcrest.MatcherAssert.assertThat;
@@ -87,7 +88,7 @@ public class TestLessInputStreamBuilderTest
         assertThat( is.availablePermits(), is( 1 ) );
         is.beforeNextCommand();
         assertThat( is.availablePermits(), is( 0 ) );
-        assertThat( is.nextCommand(), is( NOOP ) );
+        assertThat( is.nextCommand(), is( Command.NOOP ) );
         assertThat( is.availablePermits(), is( 0 ) );
         e.expect( NoSuchElementException.class );
         is.nextCommand();
@@ -105,7 +106,7 @@ public class TestLessInputStreamBuilderTest
         assertThat( is.availablePermits(), is( 2 ) );
         is.beforeNextCommand();
         assertThat( is.availablePermits(), is( 1 ) );
-        assertThat( is.nextCommand(), is( NOOP ) );
+        assertThat( is.nextCommand(), is( Command.NOOP ) );
         assertThat( is.availablePermits(), is( 1 ) );
         builder.getCachableCommands().skipSinceNextTest();
         assertThat( is.availablePermits(), is( 1 ) );
@@ -123,7 +124,7 @@ public class TestLessInputStreamBuilderTest
         builder.getCachableCommands().shutdown( EXIT );
         assertThat( is.availablePermits(), is( 2 ) );
         is.beforeNextCommand();
-        assertThat( is.nextCommand(), is( NOOP ) );
+        assertThat( is.nextCommand(), is( Command.NOOP ) );
         assertThat( is.availablePermits(), is( 1 ) );
         is.beforeNextCommand();
         assertThat( is.nextCommand().getCommandType(), is( SHUTDOWN ) );
@@ -137,15 +138,51 @@ public class TestLessInputStreamBuilderTest
             throws IOException
     {
         TestLessInputStreamBuilder builder = new TestLessInputStreamBuilder();
-        TestLessInputStream pluginIs = builder.build();
+        final TestLessInputStream pluginIs = builder.build();
+        InputStream is = new InputStream()
+        {
+            private byte[] buffer;
+            private int idx;
+
+            @Override
+            public int read() throws IOException
+            {
+                if ( buffer == null )
+                {
+                    idx = 0;
+                    Command cmd = pluginIs.readNextCommand();
+                    if ( cmd == null )
+                    {
+                        buffer = null;
+                    }
+                    else
+                    {
+                        MasterProcessCommand cmdType = cmd.getCommandType();
+                        buffer = cmdType.hasDataType() ? cmdType.encode( cmd.getData() ) : cmdType.encode();
+                    }
+                }
+
+                if ( buffer != null )
+                {
+                    byte b = buffer[idx++];
+                    if ( idx == buffer.length )
+                    {
+                        buffer = null;
+                        idx = 0;
+                    }
+                    return b;
+                }
+                throw new IOException();
+            }
+        };
+        MasterProcessChannelDecoder decoder = new LegacyMasterProcessChannelDecoder( is );
         builder.getImmediateCommands().shutdown( KILL );
         builder.getImmediateCommands().noop();
-        DataInputStream is = new DataInputStream( pluginIs );
-        Command bye = decode( is );
+        Command bye = decoder.decode();
         assertThat( bye, is( notNullValue() ) );
         assertThat( bye.getCommandType(), is( SHUTDOWN ) );
         assertThat( bye.getData(), is( KILL.name() ) );
-        Command noop = decode( is );
+        Command noop = decoder.decode();
         assertThat( noop, is( notNullValue() ) );
         assertThat( noop.getCommandType(), is( MasterProcessCommand.NOOP ) );
     }
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestProvidingInputStreamTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestProvidingInputStreamTest.java
index 21bc663..731a1ff 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestProvidingInputStreamTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestProvidingInputStreamTest.java
@@ -20,11 +20,13 @@ package org.apache.maven.plugin.surefire.booterclient.lazytestprovider;
  */
 
 import org.apache.maven.surefire.booter.Command;
-import org.apache.maven.surefire.booter.MasterProcessCommand;
+import org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelDecoder;
+import org.apache.maven.surefire.providerapi.MasterProcessChannelDecoder;
 import org.junit.Test;
 
-import java.io.DataInputStream;
 import java.io.IOException;
+import java.io.InputStream;
+import java.lang.Thread.State;
 import java.util.ArrayDeque;
 import java.util.Queue;
 import java.util.concurrent.Callable;
@@ -32,11 +34,14 @@ import java.util.concurrent.ConcurrentLinkedQueue;
 import java.util.concurrent.FutureTask;
 import java.util.concurrent.TimeUnit;
 
+import static java.nio.charset.StandardCharsets.US_ASCII;
 import static org.apache.maven.surefire.booter.MasterProcessCommand.BYE_ACK;
-import static org.apache.maven.surefire.booter.MasterProcessCommand.decode;
+import static org.apache.maven.surefire.booter.MasterProcessCommand.NOOP;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.is;
 import static org.hamcrest.Matchers.notNullValue;
+import static org.hamcrest.Matchers.nullValue;
+import static org.junit.Assert.assertTrue;
 
 /**
  * Asserts that this stream properly reads bytes from queue.
@@ -46,14 +51,15 @@ import static org.hamcrest.Matchers.notNullValue;
  */
 public class TestProvidingInputStreamTest
 {
+    private static final int WAIT_LOOPS = 100;
     @Test
-    public void closedStreamShouldReturnEndOfStream()
+    public void closedStreamShouldReturnNullAsEndOfStream()
         throws IOException
     {
         Queue<String> commands = new ArrayDeque<>();
         TestProvidingInputStream is = new TestProvidingInputStream( commands );
         is.close();
-        assertThat( is.read(), is( -1 ) );
+        assertThat( is.readNextCommand(), is( nullValue() ) );
     }
 
     @Test
@@ -63,22 +69,22 @@ public class TestProvidingInputStreamTest
         Queue<String> commands = new ArrayDeque<>();
         final TestProvidingInputStream is = new TestProvidingInputStream( commands );
         final Thread streamThread = Thread.currentThread();
-        FutureTask<Thread.State> futureTask = new FutureTask<>( new Callable<Thread.State>()
+        FutureTask<State> futureTask = new FutureTask<>( new Callable<State>()
         {
             @Override
-            public Thread.State call()
+            public State call()
             {
-                sleep( 1000 );
-                Thread.State state = streamThread.getState();
+                sleep( 1000L );
+                State state = streamThread.getState();
                 is.close();
                 return state;
             }
         } );
         Thread assertionThread = new Thread( futureTask );
         assertionThread.start();
-        assertThat( is.read(), is( -1 ) );
-        Thread.State state = futureTask.get();
-        assertThat( state, is( Thread.State.WAITING ) );
+        assertThat( is.readNextCommand(), is( nullValue() ) );
+        State state = futureTask.get();
+        assertThat( state, is( State.WAITING ) );
     }
 
     @Test
@@ -96,16 +102,23 @@ public class TestProvidingInputStreamTest
                 is.provideNewTest();
             }
         } ).start();
-        assertThat( is.read(), is( 0 ) );
-        assertThat( is.read(), is( 0 ) );
-        assertThat( is.read(), is( 0 ) );
-        assertThat( is.read(), is( 1 ) );
-        assertThat( is.read(), is( 0 ) );
-        assertThat( is.read(), is( 0 ) );
-        assertThat( is.read(), is( 0 ) );
-        assertThat( is.read(), is( 0 ) );
+
+        Command cmd = is.readNextCommand();
+        assertThat( cmd.getData(), is( nullValue() ) );
+        String stream = new String( cmd.getCommandType().encode(), US_ASCII );
+
+        cmd = is.readNextCommand();
+        assertThat( cmd.getData(), is( nullValue() ) );
+        stream += new String( cmd.getCommandType().encode(), US_ASCII );
+
+        assertThat( stream,
+            is( ":maven-surefire-command:testset-finished::maven-surefire-command:testset-finished:" ) );
+
+        boolean emptyStream = isInputStreamEmpty( is );
+
         is.close();
-        assertThat( is.read(), is( -1 ) );
+        assertTrue( emptyStream );
+        assertThat( is.readNextCommand(), is( nullValue() ) );
     }
 
     @Test
@@ -123,34 +136,55 @@ public class TestProvidingInputStreamTest
                 is.provideNewTest();
             }
         } ).start();
-        assertThat( is.read(), is( 0 ) );
-        assertThat( is.read(), is( 0 ) );
-        assertThat( is.read(), is( 0 ) );
-        assertThat( is.read(), is( 0 ) );
-        assertThat( is.read(), is( 0 ) );
-        assertThat( is.read(), is( 0 ) );
-        assertThat( is.read(), is( 0 ) );
-        assertThat( is.read(), is( 4 ) );
-        assertThat( is.read(), is( (int) 'T' ) );
-        assertThat( is.read(), is( (int) 'e' ) );
-        assertThat( is.read(), is( (int) 's' ) );
-        assertThat( is.read(), is( (int) 't' ) );
+
+        Command cmd = is.readNextCommand();
+        assertThat( cmd.getData(), is( "Test" ) );
+
+        is.close();
     }
 
     @Test
     public void shouldDecodeTwoCommands()
             throws IOException
     {
-        TestProvidingInputStream pluginIs = new TestProvidingInputStream( new ConcurrentLinkedQueue<String>() );
+        final TestProvidingInputStream pluginIs = new TestProvidingInputStream( new ConcurrentLinkedQueue<String>() );
+        InputStream is = new InputStream()
+        {
+            private byte[] buffer;
+            private int idx;
+
+            @Override
+            public int read() throws IOException
+            {
+                if ( buffer == null )
+                {
+                    idx = 0;
+                    Command cmd = pluginIs.readNextCommand();
+                    buffer = cmd == null ? null : cmd.getCommandType().encode();
+                }
+
+                if ( buffer != null )
+                {
+                    byte b = buffer[idx++];
+                    if ( idx == buffer.length )
+                    {
+                        buffer = null;
+                        idx = 0;
+                    }
+                    return b;
+                }
+                throw new IOException();
+            }
+        };
+        MasterProcessChannelDecoder decoder = new LegacyMasterProcessChannelDecoder( is );
         pluginIs.acknowledgeByeEventReceived();
         pluginIs.noop();
-        DataInputStream is = new DataInputStream( pluginIs );
-        Command bye = decode( is );
+        Command bye = decoder.decode();
         assertThat( bye, is( notNullValue() ) );
         assertThat( bye.getCommandType(), is( BYE_ACK ) );
-        Command noop = decode( is );
+        Command noop = decoder.decode();
         assertThat( noop, is( notNullValue() ) );
-        assertThat( noop.getCommandType(), is( MasterProcessCommand.NOOP ) );
+        assertThat( noop.getCommandType(), is( NOOP ) );
     }
 
     private static void sleep( long millis )
@@ -164,4 +198,44 @@ public class TestProvidingInputStreamTest
             // do nothing
         }
     }
+
+    /**
+     * Waiting (max of 20 seconds)
+     * @param is examined stream
+     * @return {@code true} if the {@link InputStream#read()} is waiting for a new byte.
+     */
+    private static boolean isInputStreamEmpty( final TestProvidingInputStream is )
+    {
+        Thread t = new Thread( new Runnable()
+        {
+            @Override
+            public void run()
+            {
+                try
+                {
+                    is.readNextCommand();
+                }
+                catch ( IOException e )
+                {
+                    Throwable cause = e.getCause();
+                    Throwable err = cause == null ? e : cause;
+                    if ( !( err instanceof InterruptedException ) )
+                    {
+                        System.err.println( err.toString() );
+                    }
+                }
+            }
+        } );
+        t.start();
+        State state;
+        int loops = 0;
+        do
+        {
+            sleep( 100L );
+            state = t.getState();
+        }
+        while ( state == State.NEW && loops++ < WAIT_LOOPS );
+        t.interrupt();
+        return state == State.WAITING || state == State.TIMED_WAITING;
+    }
 }
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 09230bb..8a5db41 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
@@ -299,7 +299,7 @@ public class ForkClientTest
         AtomicBoolean printedErrorStream = new AtomicBoolean();
         ConsoleLogger logger = mock( ConsoleLogger.class );
         ForkClient client = new ForkClient( factory, notifiableTestStream, logger, printedErrorStream, 0 );
-        client.consumeMultiLineContent( ":maven:surefire:std:out:next-test\n" );
+        client.consumeMultiLineContent( ":maven-surefire-event:next-test\n" );
         verify( notifiableTestStream, times( 1 ) )
                 .provideNewTest();
         verifyNoMoreInteractions( notifiableTestStream );
@@ -334,7 +334,7 @@ public class ForkClientTest
         ConsoleLogger logger = mock( ConsoleLogger.class );
 
         ForkClient client = new ForkClient( factory, notifiableTestStream, logger, printedErrorStream, 0 );
-        client.consumeMultiLineContent( ":maven:surefire:std:out:bye\n" );
+        client.consumeMultiLineContent( ":maven-surefire-event:bye\n" );
         client.kill();
 
         verify( notifiableTestStream, times( 1 ) )
@@ -381,7 +381,7 @@ public class ForkClientTest
                 verified[0] = true;
             }
         };
-        client.consumeMultiLineContent( ":maven:surefire:std:out:stop-on-next-test\n" );
+        client.consumeMultiLineContent( ":maven-surefire-event:stop-on-next-test\n" );
         verifyZeroInteractions( notifiableTestStream );
         verifyZeroInteractions( factory );
         assertThat( verified[0] )
@@ -418,7 +418,7 @@ public class ForkClientTest
         AtomicBoolean printedErrorStream = new AtomicBoolean();
         ConsoleLogger logger = mock( ConsoleLogger.class );
         ForkClient client = new ForkClient( factory, notifiableTestStream, logger, printedErrorStream, 0 );
-        client.consumeMultiLineContent( ":maven:surefire:std:out:std-out-stream:normal-run:UTF-8:bXNn\n" );
+        client.consumeMultiLineContent( ":maven-surefire-event:std-out-stream:normal-run:UTF-8:bXNn\n" );
         verifyZeroInteractions( notifiableTestStream );
         verify( factory, times( 1 ) )
                 .createReporter();
@@ -463,7 +463,7 @@ public class ForkClientTest
         AtomicBoolean printedErrorStream = new AtomicBoolean();
         ConsoleLogger logger = mock( ConsoleLogger.class );
         ForkClient client = new ForkClient( factory, notifiableTestStream, logger, printedErrorStream, 0 );
-        client.consumeMultiLineContent( ":maven:surefire:std:out:std-out-stream-new-line:normal-run:UTF-8:bXNn\n" );
+        client.consumeMultiLineContent( ":maven-surefire-event:std-out-stream-new-line:normal-run:UTF-8:bXNn\n" );
         verifyZeroInteractions( notifiableTestStream );
         verify( factory, times( 1 ) )
                 .createReporter();
@@ -508,7 +508,7 @@ public class ForkClientTest
         AtomicBoolean printedErrorStream = new AtomicBoolean();
         ConsoleLogger logger = mock( ConsoleLogger.class );
         ForkClient client = new ForkClient( factory, notifiableTestStream, logger, printedErrorStream, 0 );
-        client.consumeMultiLineContent( ":maven:surefire:std:out:std-err-stream:normal-run:UTF-8:bXNn\n" );
+        client.consumeMultiLineContent( ":maven-surefire-event:std-err-stream:normal-run:UTF-8:bXNn\n" );
         verifyZeroInteractions( notifiableTestStream );
         verify( factory, times( 1 ) )
                 .createReporter();
@@ -553,7 +553,7 @@ public class ForkClientTest
         AtomicBoolean printedErrorStream = new AtomicBoolean();
         ConsoleLogger logger = mock( ConsoleLogger.class );
         ForkClient client = new ForkClient( factory, notifiableTestStream, logger, printedErrorStream, 0 );
-        client.consumeMultiLineContent( ":maven:surefire:std:out:std-err-stream-new-line:normal-run:UTF-8:bXNn\n" );
+        client.consumeMultiLineContent( ":maven-surefire-event:std-err-stream-new-line:normal-run:UTF-8:bXNn\n" );
         verifyZeroInteractions( notifiableTestStream );
         verify( factory, times( 1 ) )
                 .createReporter();
@@ -598,7 +598,7 @@ public class ForkClientTest
         AtomicBoolean printedErrorStream = new AtomicBoolean();
         ConsoleLogger logger = mock( ConsoleLogger.class );
         ForkClient client = new ForkClient( factory, notifiableTestStream, logger, printedErrorStream, 0 );
-        client.consumeMultiLineContent( ":maven:surefire:std:out:console-error-log:UTF-8:"
+        client.consumeMultiLineContent( ":maven-surefire-event:console-error-log:UTF-8:"
                 + encodeBase64String( "Listening for transport dt_socket at address:".getBytes( UTF_8 ) )
                 + ":-:-:-" );
         verifyZeroInteractions( notifiableTestStream );
@@ -649,7 +649,7 @@ public class ForkClientTest
         AtomicBoolean printedErrorStream = new AtomicBoolean();
         ConsoleLogger logger = mock( ConsoleLogger.class );
         ForkClient client = new ForkClient( factory, notifiableTestStream, logger, printedErrorStream, 0 );
-        client.consumeMultiLineContent( ":maven:surefire:std:out:console-error-log:UTF-8"
+        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 ) )
                 + ":" + encodeBase64String( "s2".getBytes( UTF_8 ) ) );
@@ -709,7 +709,7 @@ public class ForkClientTest
         when( logger.isWarnEnabled() )
                 .thenReturn( true );
         ForkClient client = new ForkClient( factory, notifiableTestStream, logger, printedErrorStream, 0 );
-        client.consumeMultiLineContent( ":maven:surefire:std:out:console-warning-log:UTF-8:"
+        client.consumeMultiLineContent( ":maven-surefire-event:console-warning-log:UTF-8:"
                 + encodeBase64String( "s1".getBytes( UTF_8 ) ) );
         verifyZeroInteractions( notifiableTestStream );
         verify( factory, times( 1 ) )
@@ -757,7 +757,7 @@ public class ForkClientTest
         when( logger.isDebugEnabled() )
                 .thenReturn( true );
         ForkClient client = new ForkClient( factory, notifiableTestStream, logger, printedErrorStream, 0 );
-        client.consumeMultiLineContent( ":maven:surefire:std:out:console-debug-log:UTF-8:"
+        client.consumeMultiLineContent( ":maven-surefire-event:console-debug-log:UTF-8:"
                 + encodeBase64String( "s1".getBytes( UTF_8 ) ) );
         verifyZeroInteractions( notifiableTestStream );
         verify( factory, times( 1 ) )
@@ -803,7 +803,7 @@ public class ForkClientTest
         AtomicBoolean printedErrorStream = new AtomicBoolean();
         ConsoleLogger logger = mock( ConsoleLogger.class );
         ForkClient client = new ForkClient( factory, notifiableTestStream, logger, printedErrorStream, 0 );
-        client.consumeMultiLineContent( ":maven:surefire:std:out:console-info-log:UTF-8:"
+        client.consumeMultiLineContent( ":maven-surefire-event:console-info-log:UTF-8:"
                 + encodeBase64String( "s1".getBytes( UTF_8 ) ) );
         verifyZeroInteractions( notifiableTestStream );
         verify( factory, times( 1 ) )
@@ -849,7 +849,7 @@ public class ForkClientTest
         AtomicBoolean printedErrorStream = new AtomicBoolean();
         ConsoleLogger logger = mock( ConsoleLogger.class );
         ForkClient client = new ForkClient( factory, notifiableTestStream, logger, printedErrorStream, 0 );
-        client.consumeMultiLineContent( ":maven:surefire:std:out:sys-prop:normal-run:UTF-8:azE=:djE="
+        client.consumeMultiLineContent( ":maven-surefire-event:sys-prop:normal-run:UTF-8:azE=:djE="
                 + encodeBase64String( "s1".getBytes( UTF_8 ) ) );
         verifyZeroInteractions( notifiableTestStream );
         verifyZeroInteractions( factory );
@@ -928,7 +928,7 @@ public class ForkClientTest
         String encodedMessage = encodeBase64String( toArray( UTF_8.encode( reportEntry.getMessage() ) ) );
 
         ForkClient client = new ForkClient( factory, notifiableTestStream, logger, printedErrorStream, 0 );
-        client.consumeMultiLineContent( ":maven:surefire:std:out:testset-starting:normal-run:UTF-8:"
+        client.consumeMultiLineContent( ":maven-surefire-event:testset-starting:normal-run:UTF-8:"
                 + encodedSourceName
                 + ":"
                 + "-"
@@ -1063,7 +1063,7 @@ public class ForkClientTest
         String encodedMessage = encodeBase64String( toArray( UTF_8.encode( reportEntry.getMessage() ) ) );
 
         ForkClient client = new ForkClient( factory, notifiableTestStream, logger, printedErrorStream, 0 );
-        client.consumeMultiLineContent( ":maven:surefire:std:out:testset-starting:normal-run:UTF-8:"
+        client.consumeMultiLineContent( ":maven-surefire-event:testset-starting:normal-run:UTF-8:"
                 + encodedSourceName
                 + ":"
                 + encodedSourceText
@@ -1194,7 +1194,7 @@ public class ForkClientTest
         String encodedMessage = encodeBase64String( toArray( UTF_8.encode( reportEntry.getMessage() ) ) );
 
         ForkClient client = new ForkClient( factory, notifiableTestStream, logger, printedErrorStream, 0 );
-        client.consumeMultiLineContent( ":maven:surefire:std:out:testset-completed:normal-run:UTF-8:"
+        client.consumeMultiLineContent( ":maven-surefire-event:testset-completed:normal-run:UTF-8:"
                 + encodedSourceName
                 + ":"
                 + "-"
@@ -1323,7 +1323,7 @@ public class ForkClientTest
         String encodedMessage = encodeBase64String( toArray( UTF_8.encode( reportEntry.getMessage() ) ) );
 
         ForkClient client = new ForkClient( factory, notifiableTestStream, logger, printedErrorStream, 0 );
-        client.consumeMultiLineContent( ":maven:surefire:std:out:test-starting:normal-run:UTF-8:"
+        client.consumeMultiLineContent( ":maven-surefire-event:test-starting:normal-run:UTF-8:"
                 + encodedSourceName
                 + ":"
                 + "-"
@@ -1454,7 +1454,7 @@ public class ForkClientTest
 
         ForkClient client = new ForkClient( factory, notifiableTestStream, logger, printedErrorStream, 0 );
 
-        client.consumeMultiLineContent( ":maven:surefire:std:out:test-starting:normal-run:UTF-8:"
+        client.consumeMultiLineContent( ":maven-surefire-event:test-starting:normal-run:UTF-8:"
                 + encodedSourceName
                 + ":-:-:-:-:-:-:-:-:-" );
 
@@ -1462,7 +1462,7 @@ public class ForkClientTest
                 .hasSize( 1 )
                 .contains( "pkg.MyTest" );
 
-        client.consumeMultiLineContent( ":maven:surefire:std:out:test-succeeded:normal-run:UTF-8:"
+        client.consumeMultiLineContent( ":maven-surefire-event:test-succeeded:normal-run:UTF-8:"
                 + encodedSourceName
                 + ":"
                 + "-"
@@ -1596,7 +1596,7 @@ public class ForkClientTest
 
         ForkClient client = new ForkClient( factory, notifiableTestStream, logger, printedErrorStream, 0 );
 
-        client.consumeMultiLineContent( ":maven:surefire:std:out:test-starting:normal-run:UTF-8:"
+        client.consumeMultiLineContent( ":maven-surefire-event:test-starting:normal-run:UTF-8:"
                 + encodedSourceName
                 + ":-:-:-:-:-:-:-:-:-" );
 
@@ -1604,7 +1604,7 @@ public class ForkClientTest
                 .hasSize( 1 )
                 .contains( "pkg.MyTest" );
 
-        client.consumeMultiLineContent( ":maven:surefire:std:out:test-failed:normal-run:UTF-8:"
+        client.consumeMultiLineContent( ":maven-surefire-event:test-failed:normal-run:UTF-8:"
                 + encodedSourceName
                 + ":"
                 + "-"
@@ -1742,7 +1742,7 @@ public class ForkClientTest
 
         ForkClient client = new ForkClient( factory, notifiableTestStream, logger, printedErrorStream, 0 );
 
-        client.consumeMultiLineContent( ":maven:surefire:std:out:test-starting:normal-run:UTF-8:"
+        client.consumeMultiLineContent( ":maven-surefire-event:test-starting:normal-run:UTF-8:"
                 + encodedSourceName
                 + ":-:-:-:-:-:-:-:-:-" );
 
@@ -1750,7 +1750,7 @@ public class ForkClientTest
                 .hasSize( 1 )
                 .contains( "pkg.MyTest" );
 
-        client.consumeMultiLineContent( ":maven:surefire:std:out:test-skipped:normal-run:UTF-8:"
+        client.consumeMultiLineContent( ":maven-surefire-event:test-skipped:normal-run:UTF-8:"
                 + encodedSourceName
                 + ":"
                 + "-"
@@ -1890,7 +1890,7 @@ public class ForkClientTest
 
         ForkClient client = new ForkClient( factory, notifiableTestStream, logger, printedErrorStream, 0 );
 
-        client.consumeMultiLineContent( ":maven:surefire:std:out:test-starting:normal-run:UTF-8:"
+        client.consumeMultiLineContent( ":maven-surefire-event:test-starting:normal-run:UTF-8:"
                 + encodedSourceName
                 + ":"
                 + encodedSourceText
@@ -1900,7 +1900,7 @@ public class ForkClientTest
                 .hasSize( 1 )
                 .contains( "pkg.MyTest" );
 
-        client.consumeMultiLineContent( ":maven:surefire:std:out:test-error:normal-run:UTF-8:"
+        client.consumeMultiLineContent( ":maven-surefire-event:test-error:normal-run:UTF-8:"
                 + encodedSourceName
                 + ":"
                 + encodedSourceText
@@ -2036,7 +2036,7 @@ public class ForkClientTest
 
         ForkClient client = new ForkClient( factory, notifiableTestStream, logger, printedErrorStream, 0 );
 
-        client.consumeMultiLineContent( ":maven:surefire:std:out:test-starting:normal-run:UTF-8:"
+        client.consumeMultiLineContent( ":maven-surefire-event:test-starting:normal-run:UTF-8:"
                 + encodedSourceName
                 + ":-:-:-:-:-:-:-:-:-" );
 
@@ -2044,7 +2044,7 @@ public class ForkClientTest
                 .hasSize( 1 )
                 .contains( "pkg.MyTest" );
 
-        client.consumeMultiLineContent( ":maven:surefire:std:out:test-assumption-failure:normal-run:UTF-8:"
+        client.consumeMultiLineContent( ":maven-surefire-event:test-assumption-failure: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/ForkedChannelDecoderTest.java
index 42dd636..5265f09 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/ForkedChannelDecoderTest.java
@@ -20,7 +20,7 @@ package org.apache.maven.plugin.surefire.booterclient.output;
  */
 
 import org.apache.maven.plugin.surefire.log.api.ConsoleLoggerUtils;
-import org.apache.maven.surefire.booter.ForkedChannelEncoder;
+import org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelEncoder;
 import org.apache.maven.surefire.report.ReportEntry;
 import org.apache.maven.surefire.report.RunMode;
 import org.apache.maven.surefire.report.SafeThrowable;
@@ -93,8 +93,8 @@ public class ForkedChannelDecoderTest
         public void shouldHaveSystemProperty() throws IOException
         {
             Stream out = Stream.newStream();
-            ForkedChannelEncoder forkedChannelEncoder = new ForkedChannelEncoder( out );
-            forkedChannelEncoder.sendSystemProperties( ObjectUtils.systemProps() );
+            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( out );
+            encoder.sendSystemProperties( ObjectUtils.systemProps() );
 
             ForkedChannelDecoder decoder = new ForkedChannelDecoder();
             decoder.setSystemPropertiesListener( new PropertyEventAssertionListener() );
@@ -259,11 +259,11 @@ public class ForkedChannelDecoderTest
         public void shouldSendByeEvent() throws IOException
         {
             Stream out = Stream.newStream();
-            ForkedChannelEncoder forkedChannelEncoder = new ForkedChannelEncoder( out );
-            forkedChannelEncoder.bye();
+            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( out );
+            encoder.bye();
             String read = new String( out.toByteArray(), UTF_8 );
             assertThat( read )
-                    .isEqualTo( ":maven:surefire:std:out:bye\n" );
+                    .isEqualTo( ":maven-surefire-event:bye\n" );
             LineNumberReader lines = out.newReader( UTF_8 );
             ForkedChannelDecoder decoder = new ForkedChannelDecoder();
             decoder.setByeListener( new EventAssertionListener() );
@@ -279,11 +279,11 @@ public class ForkedChannelDecoderTest
         {
 
             Stream out = Stream.newStream();
-            ForkedChannelEncoder forkedChannelEncoder = new ForkedChannelEncoder( out );
-            forkedChannelEncoder.stopOnNextTest();
+            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( out );
+            encoder.stopOnNextTest();
             String read = new String( out.toByteArray(), UTF_8 );
             assertThat( read )
-                    .isEqualTo( ":maven:surefire:std:out:stop-on-next-test\n" );
+                    .isEqualTo( ":maven-surefire-event:stop-on-next-test\n" );
             LineNumberReader lines = out.newReader( UTF_8 );
             ForkedChannelDecoder decoder = new ForkedChannelDecoder();
             decoder.setStopOnNextTestListener( new EventAssertionListener() );
@@ -299,11 +299,11 @@ public class ForkedChannelDecoderTest
         {
 
             Stream out = Stream.newStream();
-            ForkedChannelEncoder forkedChannelEncoder = new ForkedChannelEncoder( out );
-            forkedChannelEncoder.acquireNextTest();
+            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( out );
+            encoder.acquireNextTest();
             String read = new String( out.toByteArray(), UTF_8 );
             assertThat( read )
-                    .isEqualTo( ":maven:surefire:std:out:next-test\n" );
+                    .isEqualTo( ":maven-surefire-event:next-test\n" );
             LineNumberReader lines = out.newReader( UTF_8 );
             ForkedChannelDecoder decoder = new ForkedChannelDecoder();
             decoder.setAcquireNextTestListener( new EventAssertionListener() );
@@ -319,8 +319,8 @@ public class ForkedChannelDecoderTest
         {
             Stream out = Stream.newStream();
 
-            ForkedChannelEncoder forkedChannelEncoder = new ForkedChannelEncoder( out );
-            forkedChannelEncoder.consoleInfoLog( "msg" );
+            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( out );
+            encoder.consoleInfoLog( "msg" );
 
             LineNumberReader lines = out.newReader( UTF_8 );
             ForkedChannelDecoder decoder = new ForkedChannelDecoder();
@@ -337,8 +337,8 @@ public class ForkedChannelDecoderTest
         {
             Stream out = Stream.newStream();
 
-            ForkedChannelEncoder forkedChannelEncoder = new ForkedChannelEncoder( out );
-            forkedChannelEncoder.consoleErrorLog( "msg" );
+            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( out );
+            encoder.consoleErrorLog( "msg" );
 
             LineNumberReader lines = out.newReader( UTF_8 );
             ForkedChannelDecoder decoder = new ForkedChannelDecoder();
@@ -357,8 +357,8 @@ public class ForkedChannelDecoderTest
 
             Stream out = Stream.newStream();
 
-            ForkedChannelEncoder forkedChannelEncoder = new ForkedChannelEncoder( out );
-            forkedChannelEncoder.consoleErrorLog( t );
+            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( out );
+            encoder.consoleErrorLog( t );
 
             LineNumberReader lines = out.newReader( UTF_8 );
             ForkedChannelDecoder decoder = new ForkedChannelDecoder();
@@ -376,9 +376,9 @@ public class ForkedChannelDecoderTest
         {
             Stream out = Stream.newStream();
 
-            ForkedChannelEncoder forkedChannelEncoder = new ForkedChannelEncoder( out );
+            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( out );
             StackTraceWriter stackTraceWriter = new DeserializedStacktraceWriter( "1", "2", "3" );
-            forkedChannelEncoder.consoleErrorLog( stackTraceWriter, false );
+            encoder.consoleErrorLog( stackTraceWriter, false );
 
             LineNumberReader lines = out.newReader( UTF_8 );
             ForkedChannelDecoder decoder = new ForkedChannelDecoder();
@@ -395,8 +395,8 @@ public class ForkedChannelDecoderTest
         {
             Stream out = Stream.newStream();
 
-            ForkedChannelEncoder forkedChannelEncoder = new ForkedChannelEncoder( out );
-            forkedChannelEncoder.consoleDebugLog( "msg" );
+            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( out );
+            encoder.consoleDebugLog( "msg" );
 
             LineNumberReader lines = out.newReader( UTF_8 );
             ForkedChannelDecoder decoder = new ForkedChannelDecoder();
@@ -413,8 +413,8 @@ public class ForkedChannelDecoderTest
         {
             Stream out = Stream.newStream();
 
-            ForkedChannelEncoder forkedChannelEncoder = new ForkedChannelEncoder( out );
-            forkedChannelEncoder.consoleWarningLog( "msg" );
+            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( out );
+            encoder.consoleWarningLog( "msg" );
 
             LineNumberReader lines = out.newReader( UTF_8 );
             ForkedChannelDecoder decoder = new ForkedChannelDecoder();
@@ -430,8 +430,8 @@ public class ForkedChannelDecoderTest
         public void testStdOutStream() throws IOException
         {
             Stream out = Stream.newStream();
-            ForkedChannelEncoder forkedChannelEncoder = new ForkedChannelEncoder( out );
-            forkedChannelEncoder.stdOut( "msg", false );
+            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( out );
+            encoder.stdOut( "msg", false );
 
             LineNumberReader lines = out.newReader( UTF_8 );
             ForkedChannelDecoder decoder = new ForkedChannelDecoder();
@@ -447,8 +447,8 @@ public class ForkedChannelDecoderTest
         public void testStdOutStreamPrint() throws IOException
         {
             Stream out = Stream.newStream();
-            ForkedChannelEncoder forkedChannelEncoder = new ForkedChannelEncoder( out );
-            forkedChannelEncoder.stdOut( "", false );
+            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( out );
+            encoder.stdOut( "", false );
 
             LineNumberReader lines = out.newReader( UTF_8 );
             ForkedChannelDecoder decoder = new ForkedChannelDecoder();
@@ -464,8 +464,8 @@ public class ForkedChannelDecoderTest
         public void testStdOutStreamPrintWithNull() throws IOException
         {
             Stream out = Stream.newStream();
-            ForkedChannelEncoder forkedChannelEncoder = new ForkedChannelEncoder( out );
-            forkedChannelEncoder.stdOut( null, false );
+            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( out );
+            encoder.stdOut( null, false );
 
             LineNumberReader lines = out.newReader( UTF_8 );
             ForkedChannelDecoder decoder = new ForkedChannelDecoder();
@@ -481,8 +481,8 @@ public class ForkedChannelDecoderTest
         public void testStdOutStreamPrintln() throws IOException
         {
             Stream out = Stream.newStream();
-            ForkedChannelEncoder forkedChannelEncoder = new ForkedChannelEncoder( out );
-            forkedChannelEncoder.stdOut( "", true );
+            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( out );
+            encoder.stdOut( "", true );
 
             LineNumberReader lines = out.newReader( UTF_8 );
             ForkedChannelDecoder decoder = new ForkedChannelDecoder();
@@ -498,8 +498,8 @@ public class ForkedChannelDecoderTest
         public void testStdOutStreamPrintlnWithNull() throws IOException
         {
             Stream out = Stream.newStream();
-            ForkedChannelEncoder forkedChannelEncoder = new ForkedChannelEncoder( out );
-            forkedChannelEncoder.stdOut( null, true );
+            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( out );
+            encoder.stdOut( null, true );
 
             LineNumberReader lines = out.newReader( UTF_8 );
             ForkedChannelDecoder decoder = new ForkedChannelDecoder();
@@ -515,8 +515,8 @@ public class ForkedChannelDecoderTest
         public void testStdErrStream() throws IOException
         {
             Stream out = Stream.newStream();
-            ForkedChannelEncoder forkedChannelEncoder = new ForkedChannelEncoder( out );
-            forkedChannelEncoder.stdErr( "msg", false );
+            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( out );
+            encoder.stdErr( "msg", false );
 
             LineNumberReader lines = out.newReader( UTF_8 );
             ForkedChannelDecoder decoder = new ForkedChannelDecoder();
@@ -532,8 +532,8 @@ public class ForkedChannelDecoderTest
         public void shouldCountSameNumberOfSystemProperties() throws IOException
         {
             Stream out = Stream.newStream();
-            ForkedChannelEncoder forkedChannelEncoder = new ForkedChannelEncoder( out );
-            forkedChannelEncoder.sendSystemProperties( ObjectUtils.systemProps() );
+            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( out );
+            encoder.sendSystemProperties( ObjectUtils.systemProps() );
 
             LineNumberReader lines = out.newReader( UTF_8 );
             ForkedChannelDecoder decoder = new ForkedChannelDecoder();
@@ -560,22 +560,22 @@ public class ForkedChannelDecoderTest
             ForkedChannelDecoder decoder = new ForkedChannelDecoder();
             decoder.setSystemPropertiesListener( new PropertyEventAssertionListener() );
             AssertionErrorHandler errorHandler = mock( AssertionErrorHandler.class );
-            decoder.handleEvent( ":maven:surefire:std:out:abnormal-run:-", errorHandler );
+            decoder.handleEvent( ":maven-surefire-event:abnormal-run:-", errorHandler );
             verify( errorHandler, times( 1 ) )
-                    .handledError( eq( ":maven:surefire:std:out:abnormal-run:-" ), nullable( Throwable.class ) );
+                    .handledError( eq( ":maven-surefire-event:abnormal-run:-" ), nullable( Throwable.class ) );
         }
 
         @Test
         public void shouldHandleExit() throws IOException
         {
             Stream out = Stream.newStream();
-            ForkedChannelEncoder forkedChannelEncoder = new ForkedChannelEncoder( out );
+            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( out );
             StackTraceWriter stackTraceWriter = mock( StackTraceWriter.class );
             when( stackTraceWriter.getThrowable() ).thenReturn( new SafeThrowable( "1" ) );
             when( stackTraceWriter.smartTrimmedStackTrace() ).thenReturn( "2" );
             when( stackTraceWriter.writeTraceToString() ).thenReturn( "3" );
             when( stackTraceWriter.writeTrimmedTraceToString() ).thenReturn( "4" );
-            forkedChannelEncoder.sendExitEvent( stackTraceWriter, false );
+            encoder.sendExitEvent( stackTraceWriter, false );
 
             LineNumberReader lines = out.newReader( UTF_8 );
             ForkedChannelDecoder decoder = new ForkedChannelDecoder();
@@ -677,10 +677,10 @@ public class ForkedChannelDecoderTest
 
             Stream out = Stream.newStream();
 
-            ForkedChannelEncoder forkedChannelEncoder = new ForkedChannelEncoder( out );
+            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( out );
 
-            ForkedChannelEncoder.class.getMethod( operation[0], ReportEntry.class, boolean.class )
-                    .invoke( forkedChannelEncoder, reportEntry, trim );
+            LegacyMasterProcessChannelEncoder.class.getMethod( operation[0], ReportEntry.class, boolean.class )
+                    .invoke( encoder, reportEntry, trim );
 
             ForkedChannelDecoder decoder = new ForkedChannelDecoder();
 
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 b1e3b22..14a28fe 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
@@ -28,7 +28,6 @@ import org.apache.maven.plugin.surefire.AbstractSurefireMojoTest;
 import org.apache.maven.plugin.surefire.CommonReflectorTest;
 import org.apache.maven.plugin.surefire.MojoMocklessTest;
 import org.apache.maven.plugin.surefire.SurefireHelperTest;
-import org.apache.maven.plugin.surefire.SurefireReflectorTest;
 import org.apache.maven.plugin.surefire.SurefirePropertiesTest;
 import org.apache.maven.plugin.surefire.booterclient.BooterDeserializerProviderConfigurationTest;
 import org.apache.maven.plugin.surefire.booterclient.BooterDeserializerStartupConfigurationTest;
@@ -52,6 +51,7 @@ import org.apache.maven.plugin.surefire.util.DirectoryScannerTest;
 import org.apache.maven.plugin.surefire.util.ScannerUtilTest;
 import org.apache.maven.plugin.surefire.util.SpecificFileFilterTest;
 import org.apache.maven.surefire.extensions.ConsoleOutputReporterTest;
+import org.apache.maven.surefire.extensions.ForkChannelTest;
 import org.apache.maven.surefire.extensions.StatelessReporterTest;
 import org.apache.maven.surefire.extensions.StatelessTestsetInfoReporterTest;
 import org.apache.maven.surefire.report.FileReporterTest;
@@ -89,7 +89,6 @@ public class JUnit4SuiteTest extends TestCase
         suite.addTest( new JUnit4TestAdapter( TestProvidingInputStreamTest.class ) );
         suite.addTest( new JUnit4TestAdapter( TestLessInputStreamBuilderTest.class ) );
         suite.addTest( new JUnit4TestAdapter( SPITest.class ) );
-        suite.addTest( new JUnit4TestAdapter( SurefireReflectorTest.class ) );
         suite.addTest( new JUnit4TestAdapter( SurefireHelperTest.class ) );
         suite.addTest( new JUnit4TestAdapter( AbstractSurefireMojoTest.class ) );
         suite.addTest( new JUnit4TestAdapter( DefaultForkConfigurationTest.class ) );
@@ -106,6 +105,7 @@ public class JUnit4SuiteTest extends TestCase
         suite.addTest( new JUnit4TestAdapter( StatelessTestsetInfoReporterTest.class ) );
         suite.addTest( new JUnit4TestAdapter( CommonReflectorTest.class ) );
         suite.addTest( new JUnit4TestAdapter( ForkStarterTest.class ) );
+        suite.addTest( new JUnit4TestAdapter( ForkChannelTest.class ) );
         return suite;
     }
 }
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/surefire/extensions/ForkChannelTest.java b/maven-surefire-common/src/test/java/org/apache/maven/surefire/extensions/ForkChannelTest.java
new file mode 100644
index 0000000..63db316
--- /dev/null
+++ b/maven-surefire-common/src/test/java/org/apache/maven/surefire/extensions/ForkChannelTest.java
@@ -0,0 +1,146 @@
+package org.apache.maven.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.maven.plugin.surefire.booterclient.lazytestprovider.TestLessInputStream;
+import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.TestLessInputStream.TestLessInputStreamBuilder;
+import org.apache.maven.plugin.surefire.extensions.SurefireForkNodeFactory;
+import org.apache.maven.surefire.shared.utils.cli.StreamConsumer;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.powermock.core.classloader.annotations.PowerMockIgnore;
+import org.powermock.modules.junit4.PowerMockRunner;
+
+import java.io.IOException;
+import java.lang.Thread.UncaughtExceptionHandler;
+import java.net.Socket;
+import java.net.URI;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import static java.nio.charset.StandardCharsets.US_ASCII;
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.fest.assertions.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+/**
+ *
+ */
+@RunWith( PowerMockRunner.class )
+@PowerMockIgnore( { "org.jacoco.agent.rt.*", "com.vladium.emma.rt.*" } )
+public class ForkChannelTest
+{
+    private static final long TESTCASE_TIMEOUT = 30_000L;
+
+    @Mock
+    private StreamConsumer consumer;
+
+    @Test( timeout = TESTCASE_TIMEOUT )
+    public void shouldRequestReplyMessagesViaTCP() throws Exception
+    {
+        ForkNodeFactory factory = new SurefireForkNodeFactory();
+        ForkChannel channel = factory.createForkChannel( 1 );
+
+        assertThat( channel.getForkChannelId() )
+            .isEqualTo( 1 );
+
+        assertThat( channel.useStdIn() )
+            .isFalse();
+
+        assertThat( channel.useStdOut() )
+            .isFalse();
+
+        assertThat( channel.getForkNodeConnectionString() )
+            .startsWith( "tcp://127.0.0.1:" )
+            .isNotEqualTo( "tcp://127.0.0.1:" );
+
+        URI uri = new URI( channel.getForkNodeConnectionString() );
+
+        assertThat( uri.getPort() )
+            .isPositive();
+
+        ArgumentCaptor<String> line = ArgumentCaptor.forClass( String.class );
+        doNothing().when( consumer ).consumeLine( anyString() );
+
+        Client client = new Client( uri.getPort() );
+        final AtomicBoolean hasError = new AtomicBoolean();
+        client.setUncaughtExceptionHandler( new UncaughtExceptionHandler()
+        {
+            @Override
+            public void uncaughtException( Thread t, Throwable e )
+            {
+                hasError.set( true );
+                e.printStackTrace( System.err );
+            }
+        } );
+        client.start();
+
+        channel.openChannel();
+        SECONDS.sleep( 3L );
+
+        TestLessInputStreamBuilder builder = new TestLessInputStreamBuilder();
+        TestLessInputStream commandReader = builder.build();
+        commandReader.noop();
+        channel.bindCommandReader( commandReader ).start();
+        channel.bindEventHandler( consumer ).start();
+
+        client.join( TESTCASE_TIMEOUT );
+
+        assertThat( hasError.get() )
+            .isFalse();
+
+        verify( consumer, times( 1 ) )
+            .consumeLine( line.capture() );
+
+        assertThat( line.getValue() )
+            .isEqualTo( "Hi There!" );
+    }
+
+    private static class Client extends Thread
+    {
+        private final int port;
+
+        private Client( int port )
+        {
+            this.port = port;
+        }
+
+        @Override
+        public void run()
+        {
+            try ( Socket socket = new Socket( "127.0.0.1", port ) )
+            {
+                byte[] data = new byte[128];
+                int readLength = socket.getInputStream().read( data );
+                String token = new String( data, 0, readLength, US_ASCII );
+                assertThat( token ).isEqualTo( ":maven-surefire-command:noop:" );
+                socket.getOutputStream().write( "Hi There!".getBytes( US_ASCII ) );
+            }
+            catch ( IOException e )
+            {
+                throw new IllegalStateException( e );
+            }
+        }
+    }
+}
diff --git a/maven-surefire-plugin/src/main/java/org/apache/maven/plugin/surefire/SurefirePlugin.java b/maven-surefire-plugin/src/main/java/org/apache/maven/plugin/surefire/SurefirePlugin.java
index b39181a..5f6edea 100644
--- a/maven-surefire-plugin/src/main/java/org/apache/maven/plugin/surefire/SurefirePlugin.java
+++ b/maven-surefire-plugin/src/main/java/org/apache/maven/plugin/surefire/SurefirePlugin.java
@@ -29,6 +29,7 @@ import org.apache.maven.plugins.annotations.LifecyclePhase;
 import org.apache.maven.plugins.annotations.Mojo;
 import org.apache.maven.plugins.annotations.Parameter;
 import org.apache.maven.plugins.annotations.ResolutionScope;
+import org.apache.maven.surefire.extensions.ForkNodeFactory;
 import org.apache.maven.surefire.suite.RunResult;
 
 import static org.apache.maven.plugin.surefire.SurefireHelper.reportExecution;
@@ -365,6 +366,9 @@ public class SurefirePlugin
     @Parameter( property = "surefire.useModulePath", defaultValue = "true" )
     private boolean useModulePath;
 
+    @Parameter( property = "surefire.forkNode" )
+    private ForkNodeFactory forkNode;
+
     /**
      * You can selectively exclude individual environment variables by enumerating their keys.
      * <br>
@@ -831,4 +835,10 @@ public class SurefirePlugin
     {
         return enableProcessChecker;
     }
+
+    @Override
+    protected final ForkNodeFactory getForkNode()
+    {
+        return forkNode;
+    }
 }
diff --git a/pom.xml b/pom.xml
index d005dd4..3b0e4bb 100644
--- a/pom.xml
+++ b/pom.xml
@@ -51,6 +51,7 @@
     <module>surefire-logger-api</module>
     <module>surefire-api</module>
     <module>surefire-extensions-api</module>
+    <module>surefire-extensions-spi</module>
     <module>surefire-booter</module>
     <module>surefire-grouper</module>
     <module>surefire-providers</module>
@@ -474,10 +475,8 @@
               </goals>
               <configuration>
                 <includes>
-                  <include>org/apache/maven/shared/utils/logging/*.java</include>
                   <include>HelpMojo.java</include>
                   <include>**/HelpMojo.java</include>
-                  <include>org/apache/maven/plugin/failsafe/xmlsummary/*.java</include>
                 </includes>
                 <compilerArgs>
                   <!-- FIXME: maven-plugin-plugin therefore used -syntax or none due to HelpMojo -->
@@ -493,10 +492,8 @@
               </goals>
               <configuration>
                 <excludes>
-                  <exclude>org/apache/maven/shared/utils/logging/*.java</exclude>
                   <exclude>HelpMojo.java</exclude>
                   <exclude>**/HelpMojo.java</exclude>
-                  <exclude>org/apache/maven/plugin/failsafe/xmlsummary/*.java</exclude>
                 </excludes>
                 <compilerArgs>
                   <arg>-Xdoclint:all</arg>
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/BaseProviderFactory.java b/surefire-api/src/main/java/org/apache/maven/surefire/booter/BaseProviderFactory.java
index ec05580..6ac0ce2 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/BaseProviderFactory.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/booter/BaseProviderFactory.java
@@ -20,6 +20,8 @@ package org.apache.maven.surefire.booter;
  */
 
 import org.apache.maven.surefire.cli.CommandLineOption;
+import org.apache.maven.surefire.providerapi.CommandChainReader;
+import org.apache.maven.surefire.providerapi.MasterProcessChannelEncoder;
 import org.apache.maven.surefire.providerapi.ProviderParameters;
 import org.apache.maven.surefire.report.ConsoleStream;
 import org.apache.maven.surefire.report.DefaultDirectConsoleReporter;
@@ -46,15 +48,13 @@ import static java.util.Collections.emptyList;
  * @author Kristian Rosenvold
  */
 public class BaseProviderFactory
-    implements DirectoryScannerParametersAware, ReporterConfigurationAware, SurefireClassLoadersAware, TestRequestAware,
-    ProviderPropertiesAware, ProviderParameters, TestArtifactInfoAware, RunOrderParametersAware, MainCliOptionsAware,
-    FailFastAware, ShutdownAware
+    implements ProviderParameters
 {
-    private final ReporterFactory reporterFactory;
-
     private final boolean insideFork;
 
-    private ForkedChannelEncoder forkedChannelEncoder;
+    private ReporterFactory reporterFactory;
+
+    private MasterProcessChannelEncoder masterProcessChannelEncoder;
 
     private List<CommandLineOption> mainCliOptions = emptyList();
 
@@ -74,17 +74,27 @@ public class BaseProviderFactory
 
     private int skipAfterFailureCount;
 
-    private Shutdown shutdown;
-
     private Integer systemExitTimeout;
 
-    public BaseProviderFactory( ReporterFactory reporterFactory, boolean insideFork )
+    private CommandChainReader commandReader;
+
+    public BaseProviderFactory( boolean insideFork )
     {
-        this.reporterFactory = reporterFactory;
         this.insideFork = insideFork;
     }
 
     @Override
+    public CommandChainReader getCommandReader()
+    {
+        return commandReader;
+    }
+
+    public void setCommandReader( CommandChainReader commandReader )
+    {
+        this.commandReader = commandReader;
+    }
+
+    @Override
     @Deprecated
     public DirectoryScanner getDirectoryScanner()
     {
@@ -114,25 +124,27 @@ public class BaseProviderFactory
                 ? null : new DefaultRunOrderCalculator( runOrderParameters, getThreadCount() );
     }
 
+    public void setReporterFactory( ReporterFactory reporterFactory )
+    {
+        this.reporterFactory = reporterFactory;
+    }
+
     @Override
     public ReporterFactory getReporterFactory()
     {
         return reporterFactory;
     }
 
-    @Override
     public void setDirectoryScannerParameters( DirectoryScannerParameters directoryScannerParameters )
     {
         this.directoryScannerParameters = directoryScannerParameters;
     }
 
-    @Override
     public void setReporterConfiguration( ReporterConfiguration reporterConfiguration )
     {
         this.reporterConfiguration = reporterConfiguration;
     }
 
-    @Override
     public void setClassLoaders( ClassLoader testClassLoader )
     {
         this.testClassLoader = testClassLoader;
@@ -141,11 +153,11 @@ public class BaseProviderFactory
     @Override
     public ConsoleStream getConsoleLogger()
     {
-        return insideFork ? new ForkingRunListener( forkedChannelEncoder, reporterConfiguration.isTrimStackTrace() )
-                       : new DefaultDirectConsoleReporter( reporterConfiguration.getOriginalSystemOut() );
+        return insideFork
+            ? new ForkingRunListener( masterProcessChannelEncoder, reporterConfiguration.isTrimStackTrace() )
+            : new DefaultDirectConsoleReporter( reporterConfiguration.getOriginalSystemOut() );
     }
 
-    @Override
     public void setTestRequest( TestRequest testRequest )
     {
         this.testRequest = testRequest;
@@ -175,7 +187,6 @@ public class BaseProviderFactory
         return testClassLoader;
     }
 
-    @Override
     public void setProviderProperties( Map<String, String> providerProperties )
     {
         this.providerProperties = providerProperties;
@@ -193,13 +204,11 @@ public class BaseProviderFactory
         return testArtifactInfo;
     }
 
-    @Override
     public void setTestArtifactInfo( TestArtifactInfo testArtifactInfo )
     {
         this.testArtifactInfo = testArtifactInfo;
     }
 
-    @Override
     public void setRunOrderParameters( RunOrderParameters runOrderParameters )
     {
         this.runOrderParameters = runOrderParameters;
@@ -211,7 +220,11 @@ public class BaseProviderFactory
         return mainCliOptions;
     }
 
-    @Override
+    /**
+     * CLI options in plugin (main) JVM process.
+     *
+     * @param mainCliOptions options
+     */
     public void setMainCliOptions( List<CommandLineOption> mainCliOptions )
     {
         this.mainCliOptions = mainCliOptions == null ? Collections.<CommandLineOption>emptyList() : mainCliOptions;
@@ -223,7 +236,11 @@ public class BaseProviderFactory
         return skipAfterFailureCount;
     }
 
-    @Override
+    /**
+     * See the plugin configuration parameter "skipAfterFailureCount".
+     *
+     * @param skipAfterFailureCount the value in config parameter "skipAfterFailureCount"
+     */
     public void setSkipAfterFailureCount( int skipAfterFailureCount )
     {
         this.skipAfterFailureCount = skipAfterFailureCount;
@@ -236,18 +253,6 @@ public class BaseProviderFactory
     }
 
     @Override
-    public Shutdown getShutdown()
-    {
-        return shutdown;
-    }
-
-    @Override
-    public void setShutdown( Shutdown shutdown )
-    {
-        this.shutdown = shutdown;
-    }
-
-    @Override
     public Integer getSystemExitTimeout()
     {
         return systemExitTimeout;
@@ -259,13 +264,13 @@ public class BaseProviderFactory
     }
 
     @Override
-    public ForkedChannelEncoder getForkedChannelEncoder()
+    public MasterProcessChannelEncoder getForkedChannelEncoder()
     {
-        return forkedChannelEncoder;
+        return masterProcessChannelEncoder;
     }
 
-    public void setForkedChannelEncoder( ForkedChannelEncoder forkedChannelEncoder )
+    public void setForkedChannelEncoder( MasterProcessChannelEncoder masterProcessChannelEncoder )
     {
-        this.forkedChannelEncoder = forkedChannelEncoder;
+        this.masterProcessChannelEncoder = masterProcessChannelEncoder;
     }
 }
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/Command.java b/surefire-api/src/main/java/org/apache/maven/surefire/booter/Command.java
index f05c0f6..834317b 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/Command.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/booter/Command.java
@@ -19,6 +19,8 @@ package org.apache.maven.surefire.booter;
  * under the License.
  */
 
+import java.util.Objects;
+
 import static java.util.Objects.requireNonNull;
 import static org.apache.maven.surefire.shared.utils.StringUtils.isBlank;
 import static org.apache.maven.surefire.booter.MasterProcessCommand.RUN_CLASS;
@@ -47,6 +49,11 @@ public final class Command
         this.data = data;
     }
 
+    public Command( MasterProcessCommand command )
+    {
+        this( command, null );
+    }
+
     public static Command toShutdown( Shutdown shutdownType )
     {
         return new Command( SHUTDOWN, shutdownType.name() );
@@ -57,11 +64,6 @@ public final class Command
         return new Command( RUN_CLASS, runClass );
     }
 
-    public Command( MasterProcessCommand command )
-    {
-        this( command, null );
-    }
-
     public MasterProcessCommand getCommandType()
     {
         return command;
@@ -78,18 +80,13 @@ public final class Command
      */
     public Shutdown toShutdownData()
     {
-        if ( !isType( SHUTDOWN ) )
+        if ( command != SHUTDOWN )
         {
             throw new IllegalStateException( "expected MasterProcessCommand.SHUTDOWN" );
         }
         return isBlank( data ) ? DEFAULT : Shutdown.valueOf( data );
     }
 
-    public boolean isType( MasterProcessCommand command )
-    {
-        return command == this.command;
-    }
-
     @Override
     public boolean equals( Object o )
     {
@@ -105,7 +102,7 @@ public final class Command
 
         Command arg = (Command) o;
 
-        return command == arg.command && ( data == null ? arg.data == null : data.equals( arg.data ) );
+        return command == arg.command && Objects.equals( data, arg.data );
     }
 
     @Override
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/ForkedProcessEvent.java
index 74b9eb9..617eadc 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/ForkedProcessEvent.java
@@ -59,7 +59,9 @@ public enum ForkedProcessEvent
 
     BOOTERCODE_JVM_EXIT_ERROR( "jvm-exit-error" );
 
-    public static final String MAGIC_NUMBER = ":maven:surefire:std:out:";
+    public static final String MAGIC_NUMBER = "maven-surefire-event";
+
+    public static final String MAGIC_NUMBER_DELIMITED = ':' + MAGIC_NUMBER + ':';
 
     public static final Map<String, ForkedProcessEvent> EVENTS = events();
 
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/ForkingReporterFactory.java b/surefire-api/src/main/java/org/apache/maven/surefire/booter/ForkingReporterFactory.java
index 5bb16ee..1c6db50 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/ForkingReporterFactory.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/booter/ForkingReporterFactory.java
@@ -19,6 +19,7 @@ package org.apache.maven.surefire.booter;
  * under the License.
  */
 
+import org.apache.maven.surefire.providerapi.MasterProcessChannelEncoder;
 import org.apache.maven.surefire.report.ReporterFactory;
 import org.apache.maven.surefire.report.RunListener;
 import org.apache.maven.surefire.suite.RunResult;
@@ -34,9 +35,9 @@ public class ForkingReporterFactory
 {
     private final boolean trimstackTrace;
 
-    private final ForkedChannelEncoder eventChannel;
+    private final MasterProcessChannelEncoder eventChannel;
 
-    public ForkingReporterFactory( boolean trimstackTrace, ForkedChannelEncoder eventChannel )
+    public ForkingReporterFactory( boolean trimstackTrace, MasterProcessChannelEncoder eventChannel )
     {
         this.trimstackTrace = trimstackTrace;
         this.eventChannel = eventChannel;
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/ForkingRunListener.java b/surefire-api/src/main/java/org/apache/maven/surefire/booter/ForkingRunListener.java
index 528b607..6148149 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/ForkingRunListener.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/booter/ForkingRunListener.java
@@ -20,6 +20,7 @@ package org.apache.maven.surefire.booter;
  */
 
 import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
+import org.apache.maven.surefire.providerapi.MasterProcessChannelEncoder;
 import org.apache.maven.surefire.report.ConsoleOutputReceiver;
 import org.apache.maven.surefire.report.ConsoleStream;
 import org.apache.maven.surefire.report.ReportEntry;
@@ -50,13 +51,13 @@ import static java.util.Objects.requireNonNull;
 public class ForkingRunListener
     implements RunListener, ConsoleLogger, ConsoleOutputReceiver, ConsoleStream
 {
-    private final ForkedChannelEncoder target;
+    private final MasterProcessChannelEncoder target;
 
     private final boolean trim;
 
     private volatile RunMode runMode = NORMAL_RUN;
 
-    public ForkingRunListener( ForkedChannelEncoder target, boolean trim )
+    public ForkingRunListener( MasterProcessChannelEncoder target, boolean trim )
     {
         this.target = target;
         this.trim = trim;
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/MasterProcessCommand.java b/surefire-api/src/main/java/org/apache/maven/surefire/booter/MasterProcessCommand.java
index 7c4520f..6fa85a1 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/MasterProcessCommand.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/booter/MasterProcessCommand.java
@@ -19,44 +19,49 @@ package org.apache.maven.surefire.booter;
  * under the License.
  */
 
-import java.io.DataInputStream;
-import java.io.IOException;
-
 import static java.nio.charset.StandardCharsets.US_ASCII;
 import static java.util.Objects.requireNonNull;
-import static java.lang.String.format;
 
 /**
  * Commands which are sent from plugin to the forked jvm.
  * Support and methods related to the commands.
+ * <br>
+ *     <br>
+ * magic number : opcode [: opcode specific data]*
+ * <br>
+ *     or data encoded with Base64
+ * <br>
+ * magic number : opcode [: Base64(opcode specific data)]*
  *
  * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
  * @since 2.19
  */
 public enum MasterProcessCommand
 {
-    RUN_CLASS( 0, String.class ),
-    TEST_SET_FINISHED( 1, Void.class ),
-    SKIP_SINCE_NEXT_TEST( 2, Void.class ),
-    SHUTDOWN( 3, String.class ),
+    RUN_CLASS( "run-testclass", String.class ),
+    TEST_SET_FINISHED( "testset-finished", Void.class ),
+    SKIP_SINCE_NEXT_TEST( "skip-since-next-test", Void.class ),
+    SHUTDOWN( "shutdown", String.class ),
 
     /** To tell a forked process that the master process is still alive. Repeated after 10 seconds. */
-    NOOP( 4, Void.class ),
-    BYE_ACK( 5, Void.class );
+    NOOP( "noop", Void.class ),
+    BYE_ACK( "bye-ack", Void.class );
+
+    public static final String MAGIC_NUMBER = "maven-surefire-command";
 
-    private final int id;
+    private final String opcodeName;
 
     private final Class<?> dataType;
 
-    MasterProcessCommand( int id, Class<?> dataType )
+    MasterProcessCommand( String opcodeName, Class<?> dataType )
     {
-        this.id = id;
+        this.opcodeName = opcodeName;
         this.dataType = requireNonNull( dataType, "dataType cannot be null" );
     }
 
-    public int getId()
+    public String getOpcode()
     {
-        return id;
+        return opcodeName;
     }
 
     public Class<?> getDataType()
@@ -69,7 +74,18 @@ public enum MasterProcessCommand
         return dataType != Void.class;
     }
 
-    @SuppressWarnings( "checkstyle:magicnumber" )
+    public static MasterProcessCommand byOpcode( String opcode )
+    {
+        for ( MasterProcessCommand cmd : values() )
+        {
+            if ( cmd.opcodeName.equals( opcode ) )
+            {
+                return cmd;
+            }
+        }
+        return null;
+    }
+
     public byte[] encode( String data )
     {
         if ( !hasDataType() )
@@ -82,109 +98,44 @@ public enum MasterProcessCommand
             throw new IllegalArgumentException( "Data type can be only " + String.class );
         }
 
-        final byte[] dataBytes = fromDataType( data );
-        final int len = dataBytes.length;
-
-        final byte[] encoded = new byte[8 + len];
-
-        final int command = getId();
-        setCommandAndDataLength( command, len, encoded );
-        System.arraycopy( dataBytes, 0, encoded, 8, len );
-
-        return encoded;
+        return encode( opcodeName, data )
+                .toString()
+                .getBytes( US_ASCII );
     }
 
-    @SuppressWarnings( "checkstyle:magicnumber" )
     public byte[] encode()
     {
         if ( getDataType() != Void.class )
         {
             throw new IllegalArgumentException( "Data type can be only " + getDataType() );
         }
-        byte[] encoded = new byte[8];
-        int command = getId();
-        setCommandAndDataLength( command, 0, encoded );
-        return encoded;
-    }
-
-    public static Command decode( DataInputStream is )
-        throws IOException
-    {
-        MasterProcessCommand command = resolve( is.readInt() );
-        if ( command == null )
-        {
-            return null;
-        }
-        else
-        {
-            int dataLength = is.readInt();
-            if ( dataLength > 0 )
-            {
-                byte[] buffer = new byte[ dataLength ];
-                is.readFully( buffer );
 
-                if ( command.getDataType() == Void.class )
-                {
-                    throw new IOException( format( "Command %s unexpectedly read Void data with length %d.",
-                                                   command, dataLength ) );
-                }
-
-                String data = command.toDataTypeAsString( buffer );
-                return new Command( command, data );
-            }
-            else
-            {
-                return new Command( command );
-            }
-        }
+        return encode( opcodeName, null )
+                .toString()
+                .getBytes( US_ASCII );
     }
 
-    String toDataTypeAsString( byte... data )
+    /**
+     * Encodes opcode and data.
+     *
+     * @param operation opcode
+     * @param data   data
+     * @return encoded command
+     */
+    private static StringBuilder encode( String operation, String data )
     {
-        switch ( this )
-        {
-            case RUN_CLASS:
-            case SHUTDOWN:
-                return new String( data, US_ASCII );
-            default:
-                return null;
-        }
-    }
+        StringBuilder s = new StringBuilder( 128 )
+            .append( ':' )
+            .append( MAGIC_NUMBER )
+            .append( ':' )
+            .append( operation );
 
-    byte[] fromDataType( String data )
-    {
-        switch ( this )
+        if ( data != null )
         {
-            case RUN_CLASS:
-            case SHUTDOWN:
-                return data.getBytes( US_ASCII );
-            default:
-                return new byte[0];
+            s.append( ':' )
+                    .append( data );
         }
-    }
 
-    static MasterProcessCommand resolve( int id )
-    {
-        for ( MasterProcessCommand command : values() )
-        {
-            if ( id == command.id )
-            {
-                return command;
-            }
-        }
-        return null;
-    }
-
-    @SuppressWarnings( "checkstyle:magicnumber" )
-    static void setCommandAndDataLength( int command, int dataLength, byte... encoded )
-    {
-        encoded[0] = (byte) ( command >>> 24 );
-        encoded[1] = (byte) ( command >>> 16 );
-        encoded[2] = (byte) ( command >>> 8 );
-        encoded[3] = (byte) command;
-        encoded[4] = (byte) ( dataLength >>> 24 );
-        encoded[5] = (byte) ( dataLength >>> 16 );
-        encoded[6] = (byte) ( dataLength >>> 8 );
-        encoded[7] = (byte) dataLength;
+        return s.append( ':' );
     }
 }
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/RunOrderParametersAware.java b/surefire-api/src/main/java/org/apache/maven/surefire/booter/RunOrderParametersAware.java
deleted file mode 100644
index 3bee07d..0000000
--- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/RunOrderParametersAware.java
+++ /dev/null
@@ -1,30 +0,0 @@
-package org.apache.maven.surefire.booter;
-
-/*
- * 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.testset.RunOrderParameters;
-
-/**
- * @author Kristian Rosenvold
- */
-interface RunOrderParametersAware
-{
-    void setRunOrderParameters( RunOrderParameters runOrderParameters );
-}
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/TestArtifactInfoAware.java b/surefire-api/src/main/java/org/apache/maven/surefire/booter/TestArtifactInfoAware.java
deleted file mode 100644
index 9898061..0000000
--- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/TestArtifactInfoAware.java
+++ /dev/null
@@ -1,30 +0,0 @@
-package org.apache.maven.surefire.booter;
-
-/*
- * 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.testset.TestArtifactInfo;
-
-/**
- * @author Kristian Rosenvold
- */
-interface TestArtifactInfoAware
-{
-    void setTestArtifactInfo( TestArtifactInfo testArtifactInfo );
-}
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/ReporterConfigurationAware.java b/surefire-api/src/main/java/org/apache/maven/surefire/providerapi/CommandChainReader.java
similarity index 62%
rename from surefire-api/src/main/java/org/apache/maven/surefire/booter/ReporterConfigurationAware.java
rename to surefire-api/src/main/java/org/apache/maven/surefire/providerapi/CommandChainReader.java
index 8c65be3..2c94e9d 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/ReporterConfigurationAware.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/providerapi/CommandChainReader.java
@@ -1,4 +1,4 @@
-package org.apache.maven.surefire.booter;
+package org.apache.maven.surefire.providerapi;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -19,12 +19,21 @@ package org.apache.maven.surefire.booter;
  * under the License.
  */
 
-import org.apache.maven.surefire.report.ReporterConfiguration;
+import org.apache.maven.surefire.testset.TestSetFailedException;
 
 /**
- * @author Kristian Rosenvold
+ * Hiding CommandReader instance in provider.
  */
-interface ReporterConfigurationAware
+public interface CommandChainReader
 {
-    void setReporterConfiguration( ReporterConfiguration reporterConfiguration );
+    boolean awaitStarted()
+        throws TestSetFailedException;
+
+    void addTestsFinishedListener( CommandListener listener );
+
+    void addSkipNextTestsListener( CommandListener listener );
+
+    void addShutdownListener( CommandListener listener );
+
+    void removeListener( CommandListener listener );
 }
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/CommandListener.java b/surefire-api/src/main/java/org/apache/maven/surefire/providerapi/CommandListener.java
similarity index 90%
rename from surefire-api/src/main/java/org/apache/maven/surefire/booter/CommandListener.java
rename to surefire-api/src/main/java/org/apache/maven/surefire/providerapi/CommandListener.java
index 523ca76..b0d8870 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/CommandListener.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/providerapi/CommandListener.java
@@ -1,4 +1,4 @@
-package org.apache.maven.surefire.booter;
+package org.apache.maven.surefire.providerapi;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -19,6 +19,8 @@ package org.apache.maven.surefire.booter;
  * under the License.
  */
 
+import org.apache.maven.surefire.booter.Command;
+
 /**
  * Command listener interface.
  */
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/providerapi/MasterProcessChannelDecoder.java b/surefire-api/src/main/java/org/apache/maven/surefire/providerapi/MasterProcessChannelDecoder.java
new file mode 100644
index 0000000..6c64b25
--- /dev/null
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/providerapi/MasterProcessChannelDecoder.java
@@ -0,0 +1,47 @@
+package org.apache.maven.surefire.providerapi;
+
+/*
+ * 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.Command;
+
+import java.io.IOException;
+
+/**
+ * An abstraction for physical decoder of commands. The commands are sent from master Maven process and
+ * received by the child forked Surefire process. The session must be open after the MasterProcessChannelDecoderFactory
+ * has created the decoder instance. The session can be closed on the decoder instance.
+ */
+public interface MasterProcessChannelDecoder
+    extends AutoCloseable
+{
+    /**
+     * Reads the bytes from a channel, waiting until the command is read completely or
+     * the channel throws {@link java.io.EOFException}.
+     * <br>
+     * This method is called in a single Thread. The constructor can be called within another thread.
+     *
+     * @return decoded command
+     * @throws IOException exception in channel
+     */
+    Command decode() throws IOException;
+
+    @Override
+    void close() throws IOException;
+}
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/providerapi/MasterProcessChannelEncoder.java b/surefire-api/src/main/java/org/apache/maven/surefire/providerapi/MasterProcessChannelEncoder.java
new file mode 100644
index 0000000..b734b61
--- /dev/null
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/providerapi/MasterProcessChannelEncoder.java
@@ -0,0 +1,84 @@
+package org.apache.maven.surefire.providerapi;
+
+/*
+ * 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.ReportEntry;
+import org.apache.maven.surefire.report.StackTraceWriter;
+
+import java.util.Map;
+
+/**
+ * An abstraction for physical encoder of events.
+ *
+ * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
+ * @since 3.0.0-M5
+ */
+public interface MasterProcessChannelEncoder
+{
+    MasterProcessChannelEncoder asRerunMode();
+
+    MasterProcessChannelEncoder asNormalMode();
+
+    boolean checkError();
+
+    void sendSystemProperties( Map<String, String> sysProps );
+
+    void testSetStarting( ReportEntry reportEntry, boolean trimStackTraces );
+
+    void testSetCompleted( ReportEntry reportEntry, boolean trimStackTraces );
+
+    void testStarting( ReportEntry reportEntry, boolean trimStackTraces );
+
+    void testSucceeded( ReportEntry reportEntry, boolean trimStackTraces );
+
+    void testFailed( ReportEntry reportEntry, boolean trimStackTraces );
+
+    void testSkipped( ReportEntry reportEntry, boolean trimStackTraces );
+
+    void testError( ReportEntry reportEntry, boolean trimStackTraces );
+
+    void testAssumptionFailure( ReportEntry reportEntry, boolean trimStackTraces );
+
+    void stdOut( String msg, boolean newLine );
+
+    void stdErr( String msg, boolean newLine );
+
+    void consoleInfoLog( String msg );
+
+    void consoleErrorLog( String msg );
+
+    void consoleErrorLog( Throwable t );
+
+    void consoleErrorLog( String msg, Throwable t );
+
+    void consoleErrorLog( StackTraceWriter stackTraceWriter, boolean trimStackTraces );
+
+    void consoleDebugLog( String msg );
+
+    void consoleWarningLog( String msg );
+
+    void bye();
+
+    void stopOnNextTest();
+
+    void acquireNextTest();
+
+    void sendExitEvent( StackTraceWriter stackTraceWriter, boolean trimStackTraces );
+}
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/providerapi/ProviderParameters.java b/surefire-api/src/main/java/org/apache/maven/surefire/providerapi/ProviderParameters.java
index 0fea537..47a6a7e 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/providerapi/ProviderParameters.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/providerapi/ProviderParameters.java
@@ -19,8 +19,6 @@ package org.apache.maven.surefire.providerapi;
  * under the License.
  */
 
-import org.apache.maven.surefire.booter.ForkedChannelEncoder;
-import org.apache.maven.surefire.booter.Shutdown;
 import org.apache.maven.surefire.cli.CommandLineOption;
 import org.apache.maven.surefire.report.ConsoleStream;
 import org.apache.maven.surefire.report.ReporterConfiguration;
@@ -147,9 +145,9 @@ public interface ProviderParameters
      */
     boolean isInsideFork();
 
-    Shutdown getShutdown();
-
     Integer getSystemExitTimeout();
 
-    ForkedChannelEncoder getForkedChannelEncoder();
+    MasterProcessChannelEncoder getForkedChannelEncoder();
+
+    CommandChainReader getCommandReader();
 }
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/suite/RunResult.java b/surefire-api/src/main/java/org/apache/maven/surefire/suite/RunResult.java
index 8145e51..cb89c31 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/suite/RunResult.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/suite/RunResult.java
@@ -48,7 +48,7 @@ public class RunResult
 
     public static final int SUCCESS = 0;
 
-    private static final int FAILURE = 255;
+    public static final int FAILURE = 255;
 
     private static final int NO_TESTS = 254;
 
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/testset/TestListResolver.java b/surefire-api/src/main/java/org/apache/maven/surefire/testset/TestListResolver.java
index c7c123a..e7ed763 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/testset/TestListResolver.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/testset/TestListResolver.java
@@ -41,7 +41,7 @@ import static org.apache.maven.surefire.testset.ResolvedTest.Type.METHOD;
  * composed of included and excluded tests.<br>
  * The methods {@link #shouldRun(String, String)} are filters easily used in JUnit filter or TestNG.
  * This class is independent of JUnit and TestNG API.<br>
- * It is accessed by Java Reflection API in {@link org.apache.maven.surefire.booter.SurefireReflector}
+ * It is accessed by Java Reflection API in {@code org.apache.maven.surefire.booter.SurefireReflector}
  * using specific ClassLoader.
  */
 public class TestListResolver
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/util/ReflectionUtils.java b/surefire-api/src/main/java/org/apache/maven/surefire/util/ReflectionUtils.java
index 57e9ea7..2e4eba6 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/util/ReflectionUtils.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/util/ReflectionUtils.java
@@ -76,7 +76,7 @@ public final class ReflectionUtils
         return invokeMethodWithArray( instance, method );
     }
 
-    public static Constructor getConstructor( Class<?> clazz, Class<?>... arguments )
+    public static Constructor<?> getConstructor( Class<?> clazz, Class<?>... arguments )
     {
         try
         {
@@ -88,7 +88,7 @@ public final class ReflectionUtils
         }
     }
 
-    public static Object newInstance( Constructor constructor, Object... params )
+    public static Object newInstance( Constructor<?> constructor, Object... params )
     {
         try
         {
@@ -119,7 +119,7 @@ public final class ReflectionUtils
         try
         {
             Class<?> aClass = loadClass( classLoader, className );
-            Constructor constructor = getConstructor( aClass, param1Class );
+            Constructor<?> constructor = getConstructor( aClass, param1Class );
             return constructor.newInstance( param1 );
         }
         catch ( InvocationTargetException e )
@@ -132,25 +132,6 @@ public final class ReflectionUtils
         }
     }
 
-    public static Object instantiateTwoArgs( ClassLoader classLoader, String className, Class<?> param1Class,
-                                             Object param1, Class param2Class, Object param2 )
-    {
-        try
-        {
-            Class<?> aClass = loadClass( classLoader, className );
-            Constructor constructor = getConstructor( aClass, param1Class, param2Class );
-            return constructor.newInstance( param1, param2 );
-        }
-        catch ( InvocationTargetException e )
-        {
-            throw new SurefireReflectionException( e.getTargetException() );
-        }
-        catch ( ReflectiveOperationException e )
-        {
-            throw new SurefireReflectionException( e );
-        }
-    }
-
     public static void invokeSetter( Object o, String name, Class<?> value1clazz, Object value )
     {
         Method setter = getMethod( o, name, value1clazz );
@@ -191,10 +172,10 @@ public final class ReflectionUtils
         }
     }
 
-    public static Object instantiateObject( String className, Class[] types, Object[] params, ClassLoader classLoader )
+    public static Object instantiateObject( String className, Class<?>[] types, Object[] params, ClassLoader cl )
     {
-        Class<?> clazz = loadClass( classLoader, className );
-        final Constructor constructor = getConstructor( clazz, types );
+        Class<?> clazz = loadClass( cl, className );
+        final Constructor<?> constructor = getConstructor( clazz, types );
         return newInstance( constructor, params );
     }
 
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/DaemonThreadFactory.java b/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/DaemonThreadFactory.java
index 3610a4b..32b597a 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/DaemonThreadFactory.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/DaemonThreadFactory.java
@@ -19,6 +19,7 @@ package org.apache.maven.surefire.util.internal;
  * under the License.
  */
 
+import java.util.concurrent.Executors;
 import java.util.concurrent.ThreadFactory;
 import java.util.concurrent.atomic.AtomicInteger;
 
@@ -28,29 +29,24 @@ import java.util.concurrent.atomic.AtomicInteger;
 public final class DaemonThreadFactory
     implements ThreadFactory
 {
+    private static final ThreadFactory DEFAULT_THREAD_FACTORY = Executors.defaultThreadFactory();
+
     private static final AtomicInteger POOL_NUMBER = new AtomicInteger( 1 );
 
     private final AtomicInteger threadNumber = new AtomicInteger( 1 );
 
-    private final ThreadGroup group;
-
     private final String namePrefix;
 
     private DaemonThreadFactory()
     {
-        SecurityManager s = System.getSecurityManager();
-        group = s != null ? s.getThreadGroup() : Thread.currentThread().getThreadGroup();
         namePrefix = "pool-" + POOL_NUMBER.getAndIncrement() + "-thread-";
     }
 
     @Override
     public Thread newThread( Runnable r )
     {
-        Thread t = new Thread( group, r, namePrefix + threadNumber.getAndIncrement() );
-        if ( t.getPriority() != Thread.NORM_PRIORITY )
-        {
-            t.setPriority( Thread.NORM_PRIORITY );
-        }
+        Thread t = DEFAULT_THREAD_FACTORY.newThread( r );
+        t.setName( namePrefix + threadNumber.getAndIncrement() );
         t.setDaemon( true );
         return t;
     }
@@ -71,34 +67,19 @@ public final class DaemonThreadFactory
 
     public static Thread newDaemonThread( Runnable r )
     {
-        SecurityManager s = System.getSecurityManager();
-        ThreadGroup group = s == null ? Thread.currentThread().getThreadGroup() : s.getThreadGroup();
-        Thread t = new Thread( group, r );
-        if ( t.getPriority() != Thread.NORM_PRIORITY )
-        {
-            t.setPriority( Thread.NORM_PRIORITY );
-        }
-        t.setDaemon( true );
-        return t;
+        return new DaemonThreadFactory().newThread( r );
     }
 
     public static Thread newDaemonThread( Runnable r, String name )
     {
-        SecurityManager s = System.getSecurityManager();
-        ThreadGroup group = s == null ? Thread.currentThread().getThreadGroup() : s.getThreadGroup();
-        Thread t = new Thread( group, r, name );
-        if ( t.getPriority() != Thread.NORM_PRIORITY )
-        {
-            t.setPriority( Thread.NORM_PRIORITY );
-        }
-        t.setDaemon( true );
+        Thread t = new DaemonThreadFactory().newThread( r );
+        t.setName( name );
         return t;
     }
 
     private static class NamedThreadFactory
         implements ThreadFactory
     {
-
         private final String name;
 
         private NamedThreadFactory( String name )
diff --git a/surefire-api/src/test/java/org/apache/maven/JUnit4SuiteTest.java b/surefire-api/src/test/java/org/apache/maven/JUnit4SuiteTest.java
index 38f0c48..66a95a6 100644
--- a/surefire-api/src/test/java/org/apache/maven/JUnit4SuiteTest.java
+++ b/surefire-api/src/test/java/org/apache/maven/JUnit4SuiteTest.java
@@ -23,11 +23,7 @@ import junit.framework.JUnit4TestAdapter;
 import junit.framework.Test;
 import org.apache.maven.plugin.surefire.runorder.ThreadedExecutionSchedulerTest;
 import org.apache.maven.surefire.SpecificTestClassFilterTest;
-import org.apache.maven.surefire.booter.CommandReaderTest;
-import org.apache.maven.surefire.booter.ForkedChannelEncoderTest;
 import org.apache.maven.surefire.booter.ForkingRunListenerTest;
-import org.apache.maven.surefire.booter.MasterProcessCommandTest;
-import org.apache.maven.surefire.booter.SurefireReflectorTest;
 import org.apache.maven.surefire.report.LegacyPojoStackTraceWriterTest;
 import org.apache.maven.surefire.suite.RunResultTest;
 import org.apache.maven.surefire.testset.FundamentalFilterTest;
@@ -51,11 +47,8 @@ import org.junit.runners.Suite;
  * @since 2.19
  */
 @Suite.SuiteClasses( {
-    CommandReaderTest.class,
     ThreadedExecutionSchedulerTest.class,
     ForkingRunListenerTest.class,
-    MasterProcessCommandTest.class,
-    SurefireReflectorTest.class,
     LegacyPojoStackTraceWriterTest.class,
     RunResultTest.class,
     ResolvedTestTest.class,
@@ -69,8 +62,7 @@ import org.junit.runners.Suite;
     SpecificTestClassFilterTest.class,
     FundamentalFilterTest.class,
     ImmutableMapTest.class,
-    ReflectionUtilsTest.class,
-    ForkedChannelEncoderTest.class
+    ReflectionUtilsTest.class
 } )
 @RunWith( Suite.class )
 public class JUnit4SuiteTest
diff --git a/surefire-api/src/test/java/org/apache/maven/surefire/booter/ForkingRunListenerTest.java b/surefire-api/src/test/java/org/apache/maven/surefire/booter/ForkingRunListenerTest.java
index 549f3aa..b125b0c 100644
--- a/surefire-api/src/test/java/org/apache/maven/surefire/booter/ForkingRunListenerTest.java
+++ b/surefire-api/src/test/java/org/apache/maven/surefire/booter/ForkingRunListenerTest.java
@@ -19,10 +19,17 @@ package org.apache.maven.surefire.booter;
  * under the License.
  */
 
-import java.io.ByteArrayOutputStream;
-import java.io.PrintStream;
-
 import junit.framework.TestCase;
+import org.apache.maven.surefire.providerapi.MasterProcessChannelEncoder;
+import org.mockito.ArgumentCaptor;
+
+import static org.fest.assertions.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 
 /**
  * @author <a href="mailto:kristian.rosenvold@gmail.com">Kristian Rosenvold</a>
@@ -32,10 +39,16 @@ public class ForkingRunListenerTest
 {
     public void testInfo()
     {
-        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
-        PrintStream target = new PrintStream( byteArrayOutputStream );
-        ForkingRunListener forkingRunListener = new ForkingRunListener( new ForkedChannelEncoder( target ), true );
+        MasterProcessChannelEncoder encoder = mock( MasterProcessChannelEncoder.class );
+        ArgumentCaptor<String> argument1 = ArgumentCaptor.forClass( String.class );
+        doNothing().when( encoder ).consoleInfoLog( anyString() );
+        ForkingRunListener forkingRunListener = new ForkingRunListener( encoder, true );
         forkingRunListener.info( new String( new byte[]{ (byte) 'A' } ) );
         forkingRunListener.info( new String( new byte[]{ } ) );
+        verify( encoder, times( 2 ) ).consoleInfoLog( argument1.capture() );
+        assertThat( argument1.getAllValues() )
+            .hasSize( 2 )
+            .containsSequence( "A", "" );
+        verifyNoMoreInteractions( encoder );
     }
 }
diff --git a/surefire-api/src/test/java/org/apache/maven/surefire/booter/MasterProcessCommandTest.java b/surefire-api/src/test/java/org/apache/maven/surefire/booter/MasterProcessCommandTest.java
deleted file mode 100644
index cfd4d5f..0000000
--- a/surefire-api/src/test/java/org/apache/maven/surefire/booter/MasterProcessCommandTest.java
+++ /dev/null
@@ -1,164 +0,0 @@
-package org.apache.maven.surefire.booter;
-
-/*
- * 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 junit.framework.TestCase;
-import org.junit.Rule;
-import org.junit.rules.ExpectedException;
-
-import java.io.ByteArrayInputStream;
-import java.io.DataInputStream;
-import java.io.IOException;
-
-import static org.apache.maven.surefire.booter.MasterProcessCommand.decode;
-import static org.apache.maven.surefire.booter.MasterProcessCommand.resolve;
-import static org.apache.maven.surefire.booter.MasterProcessCommand.setCommandAndDataLength;
-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;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.is;
-
-/**
- * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
- * @since 2.19
- */
-public class MasterProcessCommandTest
-    extends TestCase
-{
-    @Rule
-    public final ExpectedException exception = ExpectedException.none();
-
-    public void testEncodedStreamSequence()
-    {
-        byte[] streamSequence = new byte[10];
-        streamSequence[8] = (byte) 'T';
-        streamSequence[9] = (byte) 'e';
-        setCommandAndDataLength( 256, 2, streamSequence );
-        assertEquals( streamSequence[0], (byte) 0 );
-        assertEquals( streamSequence[1], (byte) 0 );
-        assertEquals( streamSequence[2], (byte) 1 );
-        assertEquals( streamSequence[3], (byte) 0 );
-        assertEquals( streamSequence[4], (byte) 0 );
-        assertEquals( streamSequence[5], (byte) 0 );
-        assertEquals( streamSequence[6], (byte) 0 );
-        assertEquals( streamSequence[7], (byte) 2 );
-        // remain unchanged
-        assertEquals( streamSequence[8], (byte) 'T' );
-        assertEquals( streamSequence[9], (byte) 'e' );
-    }
-
-    public void testResolved()
-    {
-        for ( MasterProcessCommand command : MasterProcessCommand.values() )
-        {
-            assertThat( command, is( resolve( command.getId() ) ) );
-        }
-    }
-
-    public void testDataToByteArrayAndBack()
-    {
-        String dummyData = "pkg.Test";
-        for ( MasterProcessCommand command : MasterProcessCommand.values() )
-        {
-            switch ( command )
-            {
-                case RUN_CLASS:
-                    assertEquals( String.class, command.getDataType() );
-                    byte[] encoded = command.fromDataType( dummyData );
-                    assertThat( encoded.length, is( 8 ) );
-                    assertThat( encoded[0], is( (byte) 'p' ) );
-                    assertThat( encoded[1], is( (byte) 'k' ) );
-                    assertThat( encoded[2], is( (byte) 'g' ) );
-                    assertThat( encoded[3], is( (byte) '.' ) );
-                    assertThat( encoded[4], is( (byte) 'T' ) );
-                    assertThat( encoded[5], is( (byte) 'e' ) );
-                    assertThat( encoded[6], is( (byte) 's' ) );
-                    assertThat( encoded[7], is( (byte) 't' ) );
-                    String decoded = command.toDataTypeAsString( encoded );
-                    assertThat( decoded, is( dummyData ) );
-                    break;
-                case TEST_SET_FINISHED:
-                case SKIP_SINCE_NEXT_TEST:
-                case NOOP:
-                case  BYE_ACK:
-                    assertEquals( Void.class, command.getDataType() );
-                    encoded = command.fromDataType( dummyData );
-                    assertThat( encoded.length, is( 0 ) );
-                    decoded = command.toDataTypeAsString( encoded );
-                    assertNull( decoded );
-                    break;
-                case SHUTDOWN:
-                    assertEquals( String.class, command.getDataType() );
-                    encoded = command.fromDataType( Shutdown.EXIT.name() );
-                    assertThat( encoded.length, is( 4 ) );
-                    decoded = command.toDataTypeAsString( encoded );
-                    assertThat( decoded, is( Shutdown.EXIT.name() ) );
-                    break;
-                default:
-                    fail();
-            }
-            assertThat( command, is( resolve( command.getId() ) ) );
-        }
-    }
-
-    public void testEncodedDecodedIsSameForRunClass()
-        throws IOException
-    {
-        byte[] encoded = RUN_CLASS.encode( "pkg.Test" );
-        assertThat( encoded.length, is( 16 ) );
-        assertThat( encoded[0], is( (byte) 0 ) );
-        assertThat( encoded[1], is( (byte) 0 ) );
-        assertThat( encoded[2], is( (byte) 0 ) );
-        assertThat( encoded[3], is( (byte) 0 ) );
-        assertThat( encoded[4], is( (byte) 0 ) );
-        assertThat( encoded[5], is( (byte) 0 ) );
-        assertThat( encoded[6], is( (byte) 0 ) );
-        assertThat( encoded[7], is( (byte) 8 ) );
-        assertThat( encoded[8], is( (byte) 'p' ) );
-        assertThat( encoded[9], is( (byte) 'k' ) );
-        assertThat( encoded[10], is( (byte) 'g' ) );
-        assertThat( encoded[11], is( (byte) '.' ) );
-        assertThat( encoded[12], is( (byte) 'T' ) );
-        assertThat( encoded[13], is( (byte) 'e' ) );
-        assertThat( encoded[14], is( (byte) 's' ) );
-        assertThat( encoded[15], is( (byte) 't' ) );
-        Command command = decode( new DataInputStream( new ByteArrayInputStream( encoded ) ) );
-        assertNotNull( command );
-        assertThat( command.getCommandType(), is( RUN_CLASS ) );
-        assertThat( command.getData(), is( "pkg.Test" ) );
-    }
-
-    public void testShouldDecodeTwoCommands() throws IOException
-    {
-        byte[] cmd1 = BYE_ACK.encode();
-        byte[] cmd2 = NOOP.encode();
-        byte[] stream = new byte[cmd1.length + cmd2.length];
-        System.arraycopy( cmd1, 0, stream, 0, cmd1.length );
-        System.arraycopy( cmd2, 0, stream, cmd1.length, cmd2.length );
-        DataInputStream is = new DataInputStream( new ByteArrayInputStream( stream ) );
-        Command bye = decode( is );
-        assertNotNull( bye );
-        assertThat( bye.getCommandType(), is( BYE_ACK ) );
-        Command noop = decode( is );
-        assertNotNull( noop );
-        assertThat( noop.getCommandType(), is( NOOP ) );
-    }
-}
diff --git a/surefire-api/src/test/java/org/apache/maven/surefire/booter/SurefireReflectorTest.java b/surefire-api/src/test/java/org/apache/maven/surefire/booter/SurefireReflectorTest.java
deleted file mode 100644
index 0a006bf..0000000
--- a/surefire-api/src/test/java/org/apache/maven/surefire/booter/SurefireReflectorTest.java
+++ /dev/null
@@ -1,198 +0,0 @@
-package org.apache.maven.surefire.booter;
-
-/*
- * 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 junit.framework.TestCase;
-import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
-import org.apache.maven.surefire.report.ReporterConfiguration;
-import org.apache.maven.surefire.report.ReporterFactory;
-import org.apache.maven.surefire.report.RunListener;
-import org.apache.maven.surefire.suite.RunResult;
-import org.apache.maven.surefire.testset.DirectoryScannerParameters;
-import org.apache.maven.surefire.testset.RunOrderParameters;
-import org.apache.maven.surefire.testset.TestArtifactInfo;
-import org.apache.maven.surefire.testset.TestListResolver;
-import org.apache.maven.surefire.testset.TestRequest;
-import org.apache.maven.surefire.util.RunOrder;
-import org.mockito.ArgumentCaptor;
-
-import java.io.File;
-import java.lang.reflect.Method;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-
-import static org.fest.assertions.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-/**
- *
- */
-public class SurefireReflectorTest
-        extends TestCase
-{
-    public void testCreateConsoleLogger()
-    {
-        ClassLoader cl = Thread.currentThread().getContextClassLoader();
-        ConsoleLogger consoleLogger = mock( ConsoleLogger.class );
-        ConsoleLogger decorator = (ConsoleLogger) SurefireReflector.createConsoleLogger( consoleLogger, cl );
-        assertThat( decorator )
-        .isNotSameAs( consoleLogger );
-
-        assertThat( decorator.isDebugEnabled() ).isFalse();
-        when( consoleLogger.isDebugEnabled() ).thenReturn( true );
-        assertThat( decorator.isDebugEnabled() ).isTrue();
-        verify( consoleLogger, times( 2 ) ).isDebugEnabled();
-
-        decorator.info( "msg" );
-        ArgumentCaptor<String> argumentMsg = ArgumentCaptor.forClass( String.class );
-        verify( consoleLogger, times( 1 ) ).info( argumentMsg.capture() );
-        assertThat( argumentMsg.getAllValues() ).hasSize( 1 );
-        assertThat( argumentMsg.getAllValues().get( 0 ) ).isEqualTo( "msg" );
-    }
-
-    public void testShouldCreateFactoryWithoutException()
-    {
-        ReporterFactory factory = new ReporterFactory()
-        {
-            @Override
-            public RunListener createReporter()
-            {
-                return null;
-            }
-
-            @Override
-            public RunResult close()
-            {
-                return null;
-            }
-        };
-        ClassLoader cl = Thread.currentThread().getContextClassLoader();
-        SurefireReflector reflector = new SurefireReflector( cl );
-        BaseProviderFactory baseProviderFactory =
-                (BaseProviderFactory) reflector.createBooterConfiguration( cl, factory, true );
-        assertNotNull( baseProviderFactory.getReporterFactory() );
-        assertSame( factory, baseProviderFactory.getReporterFactory() );
-    }
-
-    public void testSetDirectoryScannerParameters()
-    {
-        SurefireReflector surefireReflector = getReflector();
-        Object foo = getFoo();
-
-        DirectoryScannerParameters directoryScannerParameters =
-                new DirectoryScannerParameters( new File( "ABC" ), new ArrayList<String>(), new ArrayList<String>(),
-                        new ArrayList<String>(), false, "hourly" );
-        surefireReflector.setDirectoryScannerParameters( foo, directoryScannerParameters );
-        assertTrue( isCalled( foo ) );
-    }
-
-    public void testRunOrderParameters()
-    {
-        SurefireReflector surefireReflector = getReflector();
-        Object foo = getFoo();
-
-        RunOrderParameters runOrderParameters = new RunOrderParameters( RunOrder.DEFAULT, new File( "." ) );
-        surefireReflector.setRunOrderParameters( foo, runOrderParameters );
-        assertTrue( isCalled( foo ) );
-    }
-
-    public void testTestSuiteDefinition()
-    {
-        SurefireReflector surefireReflector = getReflector();
-        Object foo = getFoo();
-
-        TestRequest testSuiteDefinition =
-                new TestRequest( Arrays.asList( new File( "file1" ), new File( "file2" ) ),
-                        new File( "TestSOurce" ), new TestListResolver( "aUserRequestedTest#aMethodRequested" ) );
-        surefireReflector.setTestSuiteDefinition( foo, testSuiteDefinition );
-        assertTrue( isCalled( foo ) );
-    }
-
-    public void testProviderProperties()
-    {
-        SurefireReflector surefireReflector = getReflector();
-        Object foo = getFoo();
-
-        surefireReflector.setProviderProperties( foo, new HashMap<String, String>() );
-        assertTrue( isCalled( foo ) );
-    }
-
-    public void testReporterConfiguration()
-    {
-        SurefireReflector surefireReflector = getReflector();
-        Object foo = getFoo();
-
-        ReporterConfiguration reporterConfiguration = getReporterConfiguration();
-        surefireReflector.setReporterConfigurationAware( foo, reporterConfiguration );
-        assertTrue( isCalled( foo ) );
-    }
-
-    private ReporterConfiguration getReporterConfiguration()
-    {
-        return new ReporterConfiguration( new File( "CDE" ), true );
-    }
-
-    public void testTestClassLoaderAware()
-    {
-        SurefireReflector surefireReflector = getReflector();
-        Object foo = getFoo();
-
-        surefireReflector.setTestClassLoader( foo, getClass().getClassLoader() );
-        assertTrue( isCalled( foo ) );
-    }
-
-    public void testArtifactInfoAware()
-    {
-        SurefireReflector surefireReflector = getReflector();
-        Object foo = getFoo();
-
-        TestArtifactInfo testArtifactInfo = new TestArtifactInfo( "12.3", "test" );
-        surefireReflector.setTestArtifactInfo( foo, testArtifactInfo );
-        assertTrue( isCalled( foo ) );
-    }
-
-    private SurefireReflector getReflector()
-    {
-        return new SurefireReflector( this.getClass().getClassLoader() );
-    }
-
-    private Object getFoo()
-    { // Todo: Setup a different classloader so we can really test crossing
-        return new Foo();
-    }
-
-    private Boolean isCalled( Object foo )
-    {
-        final Method isCalled;
-        try
-        {
-            isCalled = foo.getClass().getMethod( "isCalled" );
-            return (Boolean) isCalled.invoke( foo );
-        }
-        catch ( ReflectiveOperationException e )
-        {
-            throw new RuntimeException( e );
-        }
-    }
-}
diff --git a/surefire-booter/pom.xml b/surefire-booter/pom.xml
index 1fa5045..a0dbd9a 100644
--- a/surefire-booter/pom.xml
+++ b/surefire-booter/pom.xml
@@ -38,6 +38,16 @@
       <version>${project.version}</version>
     </dependency>
     <dependency>
+      <groupId>org.apache.maven.surefire</groupId>
+      <artifactId>surefire-extensions-spi</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.maven.shared</groupId>
+      <artifactId>maven-shared-utils</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
       <groupId>com.google.code.findbugs</groupId>
       <artifactId>jsr305</artifactId>
       <scope>provided</scope>
@@ -73,22 +83,35 @@
   <build>
     <plugins>
       <plugin>
-        <groupId>org.jacoco</groupId>
-        <artifactId>jacoco-maven-plugin</artifactId>
+        <artifactId>maven-dependency-plugin</artifactId>
         <executions>
           <execution>
-            <id>jacoco-instrumentation</id>
+            <id>build-test-classpath</id>
+            <phase>generate-sources</phase>
             <goals>
-              <goal>instrument</goal>
+              <goal>build-classpath</goal>
             </goals>
+            <configuration>
+              <includeScope>test</includeScope>
+              <outputFile>target/test-classpath/cp.txt</outputFile>
+            </configuration>
           </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <groupId>org.jacoco</groupId>
+        <artifactId>jacoco-maven-plugin</artifactId>
+        <executions>
           <execution>
-            <id>restore-classes</id>
+            <id>jacoco-agent</id>
             <goals>
-              <goal>restore-instrumented-classes</goal>
+              <goal>prepare-agent</goal>
             </goals>
           </execution>
         </executions>
+        <configuration>
+          <propertyName>jacoco.agent</propertyName>
+        </configuration>
       </plugin>
       <plugin>
         <artifactId>maven-surefire-plugin</artifactId>
@@ -100,7 +123,7 @@
           </dependency>
         </dependencies>
         <configuration>
-          <argLine>${jvm.args.tests}</argLine>
+          <argLine>${jvm.args.tests} ${jacoco.agent}</argLine>
           <includes>
             <include>**/JUnit4SuiteTest.java</include>
           </includes>
diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/BooterConstants.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/BooterConstants.java
index fc570f0..fa664be 100644
--- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/BooterConstants.java
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/BooterConstants.java
@@ -58,4 +58,5 @@ public final class BooterConstants
     public static final String SYSTEM_EXIT_TIMEOUT = "systemExitTimeout";
     public static final String PLUGIN_PID = "pluginPid";
     public static final String PROCESS_CHECKER = "processChecker";
+    public static final String FORK_NODE_CONNECTION_STRING = "forkNodeConnectionString";
 }
diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/BooterDeserializer.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/BooterDeserializer.java
index b091679..9c8a9ed 100644
--- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/BooterDeserializer.java
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/BooterDeserializer.java
@@ -32,6 +32,8 @@ import org.apache.maven.surefire.testset.TestListResolver;
 import org.apache.maven.surefire.testset.TestRequest;
 
 // CHECKSTYLE_OFF: imports
+import javax.annotation.Nonnull;
+
 import static org.apache.maven.surefire.booter.BooterConstants.*;
 import static org.apache.maven.surefire.cli.CommandLineOption.*;
 
@@ -59,6 +61,18 @@ public class BooterDeserializer
     }
 
     /**
+     * Describes the current connection channel used by the client in the forked JVM
+     * in order to connect to the plugin process.
+     *
+     * @return connection string (must not be null)
+     */
+    @Nonnull
+    public String getConnectionString()
+    {
+        return properties.getProperty( FORK_NODE_CONNECTION_STRING );
+    }
+
+    /**
      * @return PID of Maven process where plugin is executed; or null if PID could not be determined.
      */
     public String getPluginPid()
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/CommandReader.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/CommandReader.java
similarity index 81%
rename from surefire-api/src/main/java/org/apache/maven/surefire/booter/CommandReader.java
rename to surefire-booter/src/main/java/org/apache/maven/surefire/booter/CommandReader.java
index b51f713..4b43fb2 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/CommandReader.java
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/CommandReader.java
@@ -20,10 +20,14 @@ package org.apache.maven.surefire.booter;
  */
 
 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.MasterProcessCommandNoMagicNumberException;
+import org.apache.maven.surefire.booter.spi.MasterProcessUnknownCommandException;
+import org.apache.maven.surefire.providerapi.CommandChainReader;
+import org.apache.maven.surefire.providerapi.CommandListener;
+import org.apache.maven.surefire.providerapi.MasterProcessChannelDecoder;
+import org.apache.maven.surefire.providerapi.MasterProcessChannelEncoder;
 import org.apache.maven.surefire.testset.TestSetFailedException;
 
-import java.io.DataInputStream;
 import java.io.EOFException;
 import java.io.IOException;
 import java.util.Iterator;
@@ -47,7 +51,6 @@ import static org.apache.maven.surefire.booter.MasterProcessCommand.RUN_CLASS;
 import static org.apache.maven.surefire.booter.MasterProcessCommand.SHUTDOWN;
 import static org.apache.maven.surefire.booter.MasterProcessCommand.SKIP_SINCE_NEXT_TEST;
 import static org.apache.maven.surefire.booter.MasterProcessCommand.TEST_SET_FINISHED;
-import static org.apache.maven.surefire.booter.MasterProcessCommand.decode;
 import static org.apache.maven.surefire.util.internal.DaemonThreadFactory.newDaemonThread;
 import static org.apache.maven.surefire.shared.utils.StringUtils.isBlank;
 import static org.apache.maven.surefire.shared.utils.StringUtils.isNotBlank;
@@ -58,12 +61,10 @@ import static org.apache.maven.surefire.shared.utils.StringUtils.isNotBlank;
  * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
  * @since 2.19
  */
-public final class CommandReader
+public final class CommandReader implements CommandChainReader
 {
     private static final String LAST_TEST_SYMBOL = "";
 
-    private static final CommandReader READER = new CommandReader();
-
     private final Queue<BiProperty<MasterProcessCommand, CommandListener>> listeners = new ConcurrentLinkedQueue<>();
 
     private final Thread commandThread = newDaemonThread( new CommandRunnable(), "surefire-forkedjvm-command-thread" );
@@ -76,38 +77,24 @@ public final class CommandReader
 
     private final CopyOnWriteArrayList<String> testClasses = new CopyOnWriteArrayList<>();
 
-    private volatile Shutdown shutdown;
-
-    private int iteratedCount;
-
-    private volatile ConsoleLogger logger = new NullConsoleLogger();
+    private final MasterProcessChannelDecoder decoder;
 
-    private CommandReader()
-    {
-    }
+    private final Shutdown shutdown;
 
-    public static CommandReader getReader()
-    {
-        final CommandReader reader = READER;
-        if ( reader.state.compareAndSet( NEW, RUNNABLE ) )
-        {
-            reader.commandThread.start();
-        }
-        return reader;
-    }
+    private final ConsoleLogger logger;
 
-    public CommandReader setShutdown( Shutdown shutdown )
-    {
-        this.shutdown = shutdown;
-        return this;
-    }
+    private int iteratedCount;
 
-    public CommandReader setLogger( ConsoleLogger logger )
+    public CommandReader( MasterProcessChannelDecoder decoder, Shutdown shutdown, ConsoleLogger logger )
     {
+        this.decoder = requireNonNull( decoder, "null decoder" );
+        this.shutdown = requireNonNull( shutdown, "null Shutdown config" );
         this.logger = requireNonNull( logger, "null logger" );
-        return this;
+        state.set( RUNNABLE );
+        commandThread.start();
     }
 
+    @Override
     public boolean awaitStarted()
         throws TestSetFailedException
     {
@@ -143,11 +130,13 @@ public final class CommandReader
         addListener( RUN_CLASS, listener );
     }
 
+    @Override
     public void addTestsFinishedListener( CommandListener listener )
     {
         addListener( TEST_SET_FINISHED, listener );
     }
 
+    @Override
     public void addSkipNextTestsListener( CommandListener listener )
     {
         addListener( SKIP_SINCE_NEXT_TEST, listener );
@@ -173,6 +162,7 @@ public final class CommandReader
         listeners.add( new BiProperty<>( cmd, listener ) );
     }
 
+    @Override
     public void removeListener( CommandListener listener )
     {
         for ( Iterator<BiProperty<MasterProcessCommand, CommandListener>> it = listeners.iterator(); it.hasNext(); )
@@ -186,7 +176,8 @@ public final class CommandReader
     }
 
     /**
-     * @return test classes which have been retrieved by {@link CommandReader#getIterableClasses(ForkedChannelEncoder)}.
+     * @return test classes which have been retrieved by
+     * {@link CommandReader#getIterableClasses(MasterProcessChannelEncoder)}.
      */
     Iterator<String> iterated()
     {
@@ -200,7 +191,7 @@ public final class CommandReader
      * @param eventChannel original stream in current JVM process
      * @return Iterator with test classes lazily loaded as commands from the main process
      */
-    Iterable<String> getIterableClasses( ForkedChannelEncoder eventChannel )
+    Iterable<String> getIterableClasses( MasterProcessChannelEncoder eventChannel )
     {
         return new ClassesIterable( eventChannel );
     }
@@ -251,9 +242,9 @@ public final class CommandReader
     private final class ClassesIterable
         implements Iterable<String>
     {
-        private final ForkedChannelEncoder eventChannel;
+        private final MasterProcessChannelEncoder eventChannel;
 
-        ClassesIterable( ForkedChannelEncoder eventChannel )
+        ClassesIterable( MasterProcessChannelEncoder eventChannel )
         {
             this.eventChannel = eventChannel;
         }
@@ -268,13 +259,13 @@ public final class CommandReader
     private final class ClassesIterator
         implements Iterator<String>
     {
-        private final ForkedChannelEncoder eventChannel;
+        private final MasterProcessChannelEncoder eventChannel;
 
         private String clazz;
 
         private int nextQueueIndex;
 
-        private ClassesIterator( ForkedChannelEncoder eventChannel )
+        private ClassesIterator( MasterProcessChannelEncoder eventChannel )
         {
             this.eventChannel = eventChannel;
         }
@@ -374,54 +365,43 @@ public final class CommandReader
         public void run()
         {
             CommandReader.this.startMonitor.countDown();
-            DataInputStream stdIn = new DataInputStream( System.in );
             boolean isTestSetFinished = false;
             try
             {
                 while ( CommandReader.this.state.get() == RUNNABLE )
                 {
-                    Command command = decode( stdIn );
-                    if ( command == null )
-                    {
-                        String errorMessage = "[SUREFIRE] std/in stream corrupted: first sequence not recognized";
-                        DumpErrorSingleton.getSingleton().dumpStreamText( errorMessage );
-                        logger.error( errorMessage );
-                        break;
-                    }
-                    else
+                    Command command = CommandReader.this.decoder.decode();
+                    switch ( command.getCommandType() )
                     {
-                        switch ( command.getCommandType() )
-                        {
-                            case RUN_CLASS:
-                                String test = command.getData();
-                                boolean inserted = CommandReader.this.insertToQueue( test );
-                                if ( inserted )
-                                {
-                                    CommandReader.this.wakeupIterator();
-                                    callListeners( command );
-                                }
-                                break;
-                            case TEST_SET_FINISHED:
-                                CommandReader.this.makeQueueFull();
-                                isTestSetFinished = true;
-                                CommandReader.this.wakeupIterator();
-                                callListeners( command );
-                                break;
-                            case SHUTDOWN:
-                                CommandReader.this.makeQueueFull();
+                        case RUN_CLASS:
+                            String test = command.getData();
+                            boolean inserted = CommandReader.this.insertToQueue( test );
+                            if ( inserted )
+                            {
                                 CommandReader.this.wakeupIterator();
                                 callListeners( command );
+                            }
+                            break;
+                        case TEST_SET_FINISHED:
+                            CommandReader.this.makeQueueFull();
+                            isTestSetFinished = true;
+                            CommandReader.this.wakeupIterator();
+                            callListeners( command );
+                            break;
+                        case SHUTDOWN:
+                            CommandReader.this.makeQueueFull();
+                            CommandReader.this.wakeupIterator();
+                            callListeners( command );
                                 break;
                             case BYE_ACK:
                                 callListeners( command );
-                                // After SHUTDOWN no more commands can come.
+                            // After SHUTDOWN no more commands can come.
                                 // Hence, do NOT go back to blocking in I/O.
                                 CommandReader.this.state.set( TERMINATED );
                                 break;
-                            default:
-                                callListeners( command );
-                                break;
-                        }
+                        default:
+                            callListeners( command );
+                            break;
                     }
                 }
             }
@@ -439,6 +419,11 @@ public final class CommandReader
                     // does not go to finally for non-default config: Shutdown.EXIT or Shutdown.KILL
                 }
             }
+            catch ( MasterProcessCommandNoMagicNumberException | MasterProcessUnknownCommandException e )
+            {
+                DumpErrorSingleton.getSingleton().dumpStreamException( e );
+                CommandReader.this.logger.error( e );
+            }
             catch ( IOException e )
             {
                 CommandReader.this.state.set( TERMINATED );
@@ -447,7 +432,7 @@ public final class CommandReader
                 {
                     String msg = "[SUREFIRE] std/in stream corrupted";
                     DumpErrorSingleton.getSingleton().dumpStreamException( e, msg );
-                    logger.error( msg, e );
+                    CommandReader.this.logger.error( msg, e );
                 }
             }
             finally
diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ForkedBooter.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ForkedBooter.java
index 2701221..34b752e 100644
--- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ForkedBooter.java
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ForkedBooter.java
@@ -20,9 +20,16 @@ package org.apache.maven.surefire.booter;
  */
 
 import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
+import org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelProcessorFactory;
+import org.apache.maven.surefire.booter.spi.SurefireMasterProcessChannelProcessorFactory;
+import org.apache.maven.surefire.providerapi.CommandListener;
+import org.apache.maven.surefire.providerapi.MasterProcessChannelDecoder;
+import org.apache.maven.surefire.providerapi.MasterProcessChannelEncoder;
 import org.apache.maven.surefire.providerapi.ProviderParameters;
 import org.apache.maven.surefire.providerapi.SurefireProvider;
 import org.apache.maven.surefire.report.LegacyPojoStackTraceWriter;
+import org.apache.maven.surefire.report.StackTraceWriter;
+import org.apache.maven.surefire.spi.MasterProcessChannelProcessorFactory;
 import org.apache.maven.surefire.testset.TestSetFailedException;
 
 import java.io.File;
@@ -45,6 +52,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
 
 import static java.lang.Math.max;
 import static java.lang.Thread.currentThread;
+import static java.util.ServiceLoader.load;
 import static java.util.concurrent.TimeUnit.MILLISECONDS;
 import static java.util.concurrent.TimeUnit.SECONDS;
 import static org.apache.maven.surefire.booter.ProcessCheckerType.ALL;
@@ -73,10 +81,11 @@ public final class ForkedBooter
     private static final String LAST_DITCH_SHUTDOWN_THREAD = "surefire-forkedjvm-last-ditch-daemon-shutdown-thread-";
     private static final String PING_THREAD = "surefire-forkedjvm-ping-";
 
-    private final CommandReader commandReader = CommandReader.getReader();
-    private final ForkedChannelEncoder eventChannel = new ForkedChannelEncoder( System.out );
     private final Semaphore exitBarrier = new Semaphore( 0 );
 
+    private volatile MasterProcessChannelEncoder eventChannel;
+    private volatile MasterProcessChannelProcessorFactory channelProcessorFactory;
+    private volatile CommandReader commandReader;
     private volatile long systemExitTimeoutInSeconds = DEFAULT_SYSTEM_EXIT_TIMEOUT_IN_SECONDS;
     private volatile PingScheduler pingScheduler;
 
@@ -106,9 +115,16 @@ public final class ForkedBooter
 
         startupConfiguration = booterDeserializer.getStartupConfiguration();
 
-        forkingReporterFactory = createForkingReporterFactory();
+        String channelConfig = booterDeserializer.getConnectionString();
+        channelProcessorFactory = lookupDecoderFactory( channelConfig );
+        channelProcessorFactory.connect( channelConfig );
+        eventChannel = channelProcessorFactory.createEncoder();
+        MasterProcessChannelDecoder decoder = channelProcessorFactory.createDecoder();
 
+        forkingReporterFactory = createForkingReporterFactory();
         ConsoleLogger logger = (ConsoleLogger) forkingReporterFactory.createReporter();
+        commandReader = new CommandReader( decoder, providerConfiguration.getShutdown(), logger );
+
         pingScheduler = isDebugging() ? null : listenToShutdownCommands( booterDeserializer.getPluginPid(), logger );
 
         systemExitTimeoutInSeconds = providerConfiguration.systemExitTimeout( DEFAULT_SYSTEM_EXIT_TIMEOUT_IN_SECONDS );
@@ -162,7 +178,7 @@ public final class ForkedBooter
         }
         else if ( readTestsFromCommandReader )
         {
-            return new LazyTestsToRun( eventChannel );
+            return new LazyTestsToRun( eventChannel, commandReader );
         }
         return null;
     }
@@ -191,6 +207,21 @@ public final class ForkedBooter
         }
     }
 
+    private void closeForkChannel()
+    {
+        if ( channelProcessorFactory != null )
+        {
+            try
+            {
+                channelProcessorFactory.close();
+            }
+            catch ( IOException e )
+            {
+                e.printStackTrace();
+            }
+        }
+    }
+
     private PingScheduler listenToShutdownCommands( String ppid, ConsoleLogger logger )
     {
         PpidChecker ppidChecker = ppid == null ? null : new PpidChecker( ppid );
@@ -339,6 +370,7 @@ public final class ForkedBooter
     private void kill( int returnCode )
     {
         commandReader.stop();
+        closeForkChannel();
         Runtime.getRuntime().halt( returnCode );
     }
 
@@ -365,6 +397,7 @@ public final class ForkedBooter
         acquireOnePermit( exitBarrier, timeoutMillis );
         cancelPingScheduler();
         commandReader.stop();
+        closeForkChannel();
         System.exit( 0 );
     }
 
@@ -418,7 +451,9 @@ public final class ForkedBooter
 
     private SurefireProvider createProviderInCurrentClassloader( ForkingReporterFactory reporterManagerFactory )
     {
-        BaseProviderFactory bpf = new BaseProviderFactory( reporterManagerFactory, true );
+        BaseProviderFactory bpf = new BaseProviderFactory( true );
+        bpf.setReporterFactory( reporterManagerFactory );
+        bpf.setCommandReader( commandReader );
         bpf.setTestRequest( providerConfiguration.getTestSuiteDefinition() );
         bpf.setReporterConfiguration( providerConfiguration.getReporterConfiguration() );
         bpf.setForkedChannelEncoder( eventChannel );
@@ -430,12 +465,38 @@ public final class ForkedBooter
         bpf.setDirectoryScannerParameters( providerConfiguration.getDirScannerParams() );
         bpf.setMainCliOptions( providerConfiguration.getMainCliOptions() );
         bpf.setSkipAfterFailureCount( providerConfiguration.getSkipAfterFailureCount() );
-        bpf.setShutdown( providerConfiguration.getShutdown() );
         bpf.setSystemExitTimeout( providerConfiguration.getSystemExitTimeout() );
         String providerClass = startupConfiguration.getActualClassName();
         return (SurefireProvider) instantiateOneArg( classLoader, providerClass, ProviderParameters.class, bpf );
     }
 
+    private static MasterProcessChannelProcessorFactory lookupDecoderFactory( String channelConfig )
+    {
+        MasterProcessChannelProcessorFactory defaultFactory = null;
+        MasterProcessChannelProcessorFactory customFactory = null;
+        for ( MasterProcessChannelProcessorFactory factory : load( MasterProcessChannelProcessorFactory.class ) )
+        {
+            Class<?> cls = factory.getClass();
+
+            boolean isSurefireFactory =
+                cls == LegacyMasterProcessChannelProcessorFactory.class
+                    || cls == SurefireMasterProcessChannelProcessorFactory.class;
+
+            if ( isSurefireFactory )
+            {
+                if ( factory.canUse( channelConfig ) )
+                {
+                    defaultFactory = factory;
+                }
+            }
+            else
+            {
+                customFactory = factory;
+            }
+        }
+        return customFactory != null ? customFactory : defaultFactory;
+    }
+
     /**
      * This method is invoked when Surefire is forked - this method parses and organizes the arguments passed to it and
      * then calls the Surefire class' run method. <br> The system exit code will be 1 if an exception is thrown.
@@ -465,6 +526,11 @@ public final class ForkedBooter
         {
             DumpErrorSingleton.getSingleton().dumpException( t );
             t.printStackTrace();
+            if ( booter.eventChannel != null )
+            {
+                StackTraceWriter stack = new LegacyPojoStackTraceWriter( "test subsystem", "no method", t );
+                booter.eventChannel.consoleErrorLog( stack, false );
+            }
             booter.cancelPingScheduler();
             booter.exit1();
         }
diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/LazyTestsToRun.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/LazyTestsToRun.java
index 9d0b2e0..568a2c5 100644
--- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/LazyTestsToRun.java
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/LazyTestsToRun.java
@@ -22,10 +22,10 @@ package org.apache.maven.surefire.booter;
 import java.util.Collections;
 import java.util.Iterator;
 
+import org.apache.maven.surefire.providerapi.MasterProcessChannelEncoder;
 import org.apache.maven.surefire.util.CloseableIterator;
 import org.apache.maven.surefire.util.TestsToRun;
 
-import static org.apache.maven.surefire.booter.CommandReader.getReader;
 import static org.apache.maven.surefire.util.ReflectionUtils.loadClass;
 
 /**
@@ -43,24 +43,25 @@ import static org.apache.maven.surefire.util.ReflectionUtils.loadClass;
 final class LazyTestsToRun
     extends TestsToRun
 {
-    private final ForkedChannelEncoder eventChannel;
+    private final MasterProcessChannelEncoder eventChannel;
+    private final CommandReader commandReader;
 
     /**
      * C'tor
      *
      * @param eventChannel the output stream to use when requesting new new tests
      */
-    LazyTestsToRun( ForkedChannelEncoder eventChannel )
+    LazyTestsToRun( MasterProcessChannelEncoder eventChannel, CommandReader commandReader )
     {
         super( Collections.<Class<?>>emptySet() );
-
         this.eventChannel = eventChannel;
+        this.commandReader = commandReader;
     }
 
     private final class BlockingIterator
         implements Iterator<Class<?>>
     {
-        private final Iterator<String> it = getReader().getIterableClasses( eventChannel ).iterator();
+        private final Iterator<String> it = commandReader.getIterableClasses( eventChannel ).iterator();
 
         @Override
         public boolean hasNext()
@@ -132,7 +133,7 @@ final class LazyTestsToRun
      */
     private Iterator<Class<?>> newWeakIterator()
     {
-        final Iterator<String> it = getReader().iterated();
+        final Iterator<String> it = commandReader.iterated();
         return new CloseableIterator<Class<?>>()
         {
             @Override
diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ProviderConfiguration.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ProviderConfiguration.java
index c7379e4..caa26d2 100644
--- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ProviderConfiguration.java
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ProviderConfiguration.java
@@ -96,7 +96,6 @@ public class ProviderConfiguration
         return reporterConfiguration;
     }
 
-
     public boolean isFailIfNoTests()
     {
         return failIfNoTests;
@@ -107,7 +106,6 @@ public class ProviderConfiguration
         return dirScannerParams.getTestClassesDirectory();
     }
 
-
     public DirectoryScannerParameters getDirScannerParams()
     {
         return dirScannerParams;
diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ProviderFactory.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ProviderFactory.java
index 41badfd..614ff92 100644
--- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ProviderFactory.java
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ProviderFactory.java
@@ -99,7 +99,8 @@ public class ProviderFactory
         final ClassLoader systemClassLoader = currentThread.getContextClassLoader();
         currentThread.setContextClassLoader( classLoader );
         // Note: Duplicated in ForkedBooter#createProviderInCurrentClassloader
-        Object o = surefireReflector.createBooterConfiguration( classLoader, reporterManagerFactory, isInsideFork );
+        Object o = surefireReflector.createBooterConfiguration( classLoader, isInsideFork );
+        surefireReflector.setReporterFactoryAware( o, reporterManagerFactory );
         surefireReflector.setTestSuiteDefinitionAware( o, providerConfiguration.getTestSuiteDefinition() );
         surefireReflector.setProviderPropertiesAware( o, providerConfiguration.getProviderProperties() );
         surefireReflector.setReporterConfigurationAware( o, providerConfiguration.getReporterConfiguration() );
@@ -109,7 +110,6 @@ public class ProviderFactory
         surefireReflector.setIfDirScannerAware( o, providerConfiguration.getDirScannerParams() );
         surefireReflector.setMainCliOptions( o, providerConfiguration.getMainCliOptions() );
         surefireReflector.setSkipAfterFailureCount( o, providerConfiguration.getSkipAfterFailureCount() );
-        surefireReflector.setShutdown( o, providerConfiguration.getShutdown() );
         if ( isInsideFork )
         {
             surefireReflector.setSystemExitTimeout( o, providerConfiguration.getSystemExitTimeout() );
diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/StartupConfiguration.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/StartupConfiguration.java
index 0bbe988..d6a3dd0 100644
--- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/StartupConfiguration.java
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/StartupConfiguration.java
@@ -33,20 +33,16 @@ public class StartupConfiguration
     private final String providerClassName;
     private final AbstractPathConfiguration classpathConfiguration;
     private final ClassLoaderConfiguration classLoaderConfiguration;
-    private final boolean isForkRequested;
-    private final boolean isInForkedVm;
     private final ProcessCheckerType processChecker;
 
     public StartupConfiguration( @Nonnull String providerClassName,
                                  @Nonnull AbstractPathConfiguration classpathConfiguration,
-                                 @Nonnull ClassLoaderConfiguration classLoaderConfiguration, boolean isForkRequested,
-                                 boolean inForkedVm, ProcessCheckerType processChecker )
+                                 @Nonnull ClassLoaderConfiguration classLoaderConfiguration,
+                                 ProcessCheckerType processChecker )
     {
         this.classpathConfiguration = classpathConfiguration;
         this.classLoaderConfiguration = classLoaderConfiguration;
-        this.isForkRequested = isForkRequested;
         this.providerClassName = providerClassName;
-        isInForkedVm = inForkedVm;
         this.processChecker = processChecker;
     }
 
@@ -56,12 +52,11 @@ public class StartupConfiguration
     }
 
     public static StartupConfiguration inForkedVm( String providerClassName,
-                                                   ClasspathConfiguration classpathConfiguration,
-                                                   ClassLoaderConfiguration classLoaderConfiguration,
+                                                   ClasspathConfiguration classpathConfig,
+                                                   ClassLoaderConfiguration classLoaderConfig,
                                                    ProcessCheckerType processChecker )
     {
-        return new StartupConfiguration( providerClassName, classpathConfiguration, classLoaderConfiguration, true,
-                                         true, processChecker );
+        return new StartupConfiguration( providerClassName, classpathConfig, classLoaderConfig, processChecker );
     }
 
     public AbstractPathConfiguration getClasspathConfiguration()
@@ -69,13 +64,6 @@ public class StartupConfiguration
         return classpathConfiguration;
     }
 
-    @Deprecated
-    public boolean useSystemClassLoader()
-    {
-        // todo; I am not totally convinced this logic is as simple as it could be
-        return classLoaderConfiguration.isUseSystemClassLoader() && ( isInForkedVm || isForkRequested );
-    }
-
     public boolean isManifestOnlyJarRequestedAndUsable()
     {
         return classLoaderConfiguration.isManifestOnlyJarRequestedAndUsable();
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/SurefireReflector.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/SurefireReflector.java
similarity index 74%
rename from surefire-api/src/main/java/org/apache/maven/surefire/booter/SurefireReflector.java
rename to surefire-booter/src/main/java/org/apache/maven/surefire/booter/SurefireReflector.java
index 5cc1415..140cf0e 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/SurefireReflector.java
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/SurefireReflector.java
@@ -19,8 +19,6 @@ package org.apache.maven.surefire.booter;
  * under the License.
  */
 
-import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
-import org.apache.maven.plugin.surefire.log.api.ConsoleLoggerDecorator;
 import org.apache.maven.surefire.cli.CommandLineOption;
 import org.apache.maven.surefire.providerapi.ProviderParameters;
 import org.apache.maven.surefire.report.ReporterConfiguration;
@@ -46,7 +44,6 @@ import static java.util.Collections.checkedList;
 import static org.apache.maven.surefire.util.ReflectionUtils.getConstructor;
 import static org.apache.maven.surefire.util.ReflectionUtils.getMethod;
 import static org.apache.maven.surefire.util.ReflectionUtils.instantiateOneArg;
-import static org.apache.maven.surefire.util.ReflectionUtils.instantiateTwoArgs;
 import static org.apache.maven.surefire.util.ReflectionUtils.invokeGetter;
 import static org.apache.maven.surefire.util.ReflectionUtils.invokeMethodWithArray;
 import static org.apache.maven.surefire.util.ReflectionUtils.invokeSetter;
@@ -59,7 +56,7 @@ import static org.apache.maven.surefire.util.ReflectionUtils.newInstance;
  *
  * @author Kristian Rosenvold
  */
-public class SurefireReflector
+public final class SurefireReflector
 {
     private final ClassLoader surefireClassLoader;
 
@@ -69,21 +66,11 @@ public class SurefireReflector
 
     private final Class<?> testArtifactInfo;
 
-    private final Class<?> testArtifactInfoAware;
-
     private final Class<?> directoryScannerParameters;
 
     private final Class<?> runOrderParameters;
 
-    private final Class<?> directoryScannerParametersAware;
-
-    private final Class<?> testSuiteDefinitionAware;
-
-    private final Class<?> testClassLoaderAware;
-
-    private final Class<?> reporterConfigurationAware;
-
-    private final Class<?> providerPropertiesAware;
+    private final Class<?> baseProviderFactory;
 
     private final Class<?> runResult;
 
@@ -93,13 +80,7 @@ public class SurefireReflector
 
     private final Class<?> testListResolver;
 
-    private final Class<?> mainCliOptions;
-
-    private final Class<Enum> commandLineOptionsClass;
-
-    private final Class<?> shutdownAwareClass;
-
-    private final Class<Enum> shutdownClass;
+    private final Class<Enum<?>> commandLineOptionsClass;
 
     @SuppressWarnings( "unchecked" )
     public SurefireReflector( ClassLoader surefireClassLoader )
@@ -110,23 +91,15 @@ public class SurefireReflector
             reporterConfiguration = surefireClassLoader.loadClass( ReporterConfiguration.class.getName() );
             testRequest = surefireClassLoader.loadClass( TestRequest.class.getName() );
             testArtifactInfo = surefireClassLoader.loadClass( TestArtifactInfo.class.getName() );
-            testArtifactInfoAware = surefireClassLoader.loadClass( TestArtifactInfoAware.class.getName() );
             directoryScannerParameters = surefireClassLoader.loadClass( DirectoryScannerParameters.class.getName() );
             runOrderParameters = surefireClassLoader.loadClass( RunOrderParameters.class.getName() );
-            directoryScannerParametersAware =
-                surefireClassLoader.loadClass( DirectoryScannerParametersAware.class.getName() );
-            testSuiteDefinitionAware = surefireClassLoader.loadClass( TestRequestAware.class.getName() );
-            testClassLoaderAware = surefireClassLoader.loadClass( SurefireClassLoadersAware.class.getName() );
-            reporterConfigurationAware = surefireClassLoader.loadClass( ReporterConfigurationAware.class.getName() );
-            providerPropertiesAware = surefireClassLoader.loadClass( ProviderPropertiesAware.class.getName() );
+            baseProviderFactory = surefireClassLoader.loadClass( BaseProviderFactory.class.getName() );
             reporterFactory = surefireClassLoader.loadClass( ReporterFactory.class.getName() );
             runResult = surefireClassLoader.loadClass( RunResult.class.getName() );
             booterParameters = surefireClassLoader.loadClass( ProviderParameters.class.getName() );
             testListResolver = surefireClassLoader.loadClass( TestListResolver.class.getName() );
-            mainCliOptions = surefireClassLoader.loadClass( MainCliOptionsAware.class.getName() );
-            commandLineOptionsClass = (Class<Enum>) surefireClassLoader.loadClass( CommandLineOption.class.getName() );
-            shutdownAwareClass = surefireClassLoader.loadClass( ShutdownAware.class.getName() );
-            shutdownClass = (Class<Enum>) surefireClassLoader.loadClass( Shutdown.class.getName() );
+            commandLineOptionsClass =
+                (Class<Enum<?>>) surefireClassLoader.loadClass( CommandLineOption.class.getName() );
         }
         catch ( ClassNotFoundException e )
         {
@@ -157,7 +130,7 @@ public class SurefireReflector
         {
             Object resolver = createTestListResolver( suiteDefinition.getTestListResolver() );
             Class<?>[] arguments = { List.class, File.class, testListResolver, int.class };
-            Constructor constructor = getConstructor( testRequest, arguments );
+            Constructor<?> constructor = getConstructor( testRequest, arguments );
             return newInstance( constructor,
                                 suiteDefinition.getSuiteXmlFiles(),
                                 suiteDefinition.getTestSourceDirectory(),
@@ -174,7 +147,7 @@ public class SurefireReflector
         }
         else
         {
-            Constructor constructor = getConstructor( testListResolver, String.class );
+            Constructor<?> constructor = getConstructor( testListResolver, String.class );
             return newInstance( constructor, resolver.getPluginParameterTest() );
         }
     }
@@ -187,7 +160,7 @@ public class SurefireReflector
         }
         //Can't use the constructor with the RunOrder parameter. Using it causes some integration tests to fail.
         Class<?>[] arguments = { File.class, List.class, List.class, List.class, boolean.class, String.class };
-        Constructor constructor = getConstructor( this.directoryScannerParameters, arguments );
+        Constructor<?> constructor = getConstructor( this.directoryScannerParameters, arguments );
         return newInstance( constructor,
                             directoryScannerParameters.getTestClassesDirectory(),
                             directoryScannerParameters.getIncludes(),
@@ -205,7 +178,7 @@ public class SurefireReflector
         }
         //Can't use the constructor with the RunOrder parameter. Using it causes some integration tests to fail.
         Class<?>[] arguments = { String.class, File.class };
-        Constructor constructor = getConstructor( this.runOrderParameters, arguments );
+        Constructor<?> constructor = getConstructor( this.runOrderParameters, arguments );
         File runStatisticsFile = runOrderParameters.getRunStatisticsFile();
         return newInstance( constructor, RunOrder.asString( runOrderParameters.getRunOrder() ), runStatisticsFile );
     }
@@ -217,21 +190,19 @@ public class SurefireReflector
             return null;
         }
         Class<?>[] arguments = { String.class, String.class };
-        Constructor constructor = getConstructor( this.testArtifactInfo, arguments );
+        Constructor<?> constructor = getConstructor( this.testArtifactInfo, arguments );
         return newInstance( constructor, testArtifactInfo.getVersion(), testArtifactInfo.getClassifier() );
     }
 
     private Object createReporterConfiguration( ReporterConfiguration reporterConfig )
     {
-        Constructor constructor = getConstructor( reporterConfiguration, File.class, boolean.class );
+        Constructor<?> constructor = getConstructor( reporterConfiguration, File.class, boolean.class );
         return newInstance( constructor, reporterConfig.getReportsDirectory(), reporterConfig.isTrimStackTrace() );
     }
 
-    public Object createBooterConfiguration( ClassLoader surefireClassLoader, Object factoryInstance,
-                                             boolean insideFork )
+    public Object createBooterConfiguration( ClassLoader surefireClassLoader, boolean insideFork )
     {
-        return instantiateTwoArgs( surefireClassLoader, BaseProviderFactory.class.getName(),
-                                   reporterFactory, factoryInstance, boolean.class, insideFork );
+        return instantiateOneArg( surefireClassLoader, BaseProviderFactory.class.getName(), boolean.class, insideFork );
     }
 
     public Object instantiateProvider( String providerClassName, Object booterParameters )
@@ -241,7 +212,7 @@ public class SurefireReflector
 
     public void setIfDirScannerAware( Object o, DirectoryScannerParameters dirScannerParams )
     {
-        if ( directoryScannerParametersAware.isAssignableFrom( o.getClass() ) )
+        if ( baseProviderFactory.isAssignableFrom( o.getClass() ) )
         {
             setDirectoryScannerParameters( o, dirScannerParams );
         }
@@ -249,11 +220,11 @@ public class SurefireReflector
 
     public void setMainCliOptions( Object o, List<CommandLineOption> options )
     {
-        if ( mainCliOptions.isAssignableFrom( o.getClass() ) )
+        if ( baseProviderFactory.isAssignableFrom( o.getClass() ) )
         {
-            List<Enum> newOptions = checkedList( new ArrayList<Enum>( options.size() ), commandLineOptionsClass );
+            List<Enum<?>> newOptions = checkedList( new ArrayList<Enum<?>>( options.size() ), commandLineOptionsClass );
             Collection<Integer> ordinals = toOrdinals( options );
-            for ( Enum e : commandLineOptionsClass.getEnumConstants() )
+            for ( Enum<?> e : commandLineOptionsClass.getEnumConstants() )
             {
                 if ( ordinals.contains( e.ordinal() ) )
                 {
@@ -269,21 +240,6 @@ public class SurefireReflector
         invokeSetter( o, "setSkipAfterFailureCount", int.class, skipAfterFailureCount );
     }
 
-    public void setShutdown( Object o, Shutdown shutdown )
-    {
-        if ( shutdownAwareClass.isAssignableFrom( o.getClass() ) )
-        {
-            for ( Enum e : shutdownClass.getEnumConstants() )
-            {
-                if ( shutdown.ordinal() == e.ordinal() )
-                {
-                    invokeSetter( o, "setShutdown", shutdownClass, e );
-                    break;
-                }
-            }
-        }
-    }
-
     public void setSystemExitTimeout( Object o, Integer systemExitTimeout )
     {
         invokeSetter( o, "setSystemExitTimeout", Integer.class, systemExitTimeout );
@@ -303,7 +259,7 @@ public class SurefireReflector
 
     public void setTestSuiteDefinitionAware( Object o, TestRequest testSuiteDefinition2 )
     {
-        if ( testSuiteDefinitionAware.isAssignableFrom( o.getClass() ) )
+        if ( baseProviderFactory.isAssignableFrom( o.getClass() ) )
         {
             setTestSuiteDefinition( o, testSuiteDefinition2 );
         }
@@ -317,7 +273,7 @@ public class SurefireReflector
 
     public void setProviderPropertiesAware( Object o, Map<String, String> properties )
     {
-        if ( providerPropertiesAware.isAssignableFrom( o.getClass() ) )
+        if ( baseProviderFactory.isAssignableFrom( o.getClass() ) )
         {
             setProviderProperties( o, properties );
         }
@@ -330,7 +286,7 @@ public class SurefireReflector
 
     public void setReporterConfigurationAware( Object o, ReporterConfiguration reporterConfiguration1 )
     {
-        if ( reporterConfigurationAware.isAssignableFrom( o.getClass() ) )
+        if ( baseProviderFactory.isAssignableFrom( o.getClass() ) )
         {
             setReporterConfiguration( o, reporterConfiguration1 );
         }
@@ -344,7 +300,7 @@ public class SurefireReflector
 
     public void setTestClassLoaderAware( Object o, ClassLoader testClassLoader )
     {
-        if ( testClassLoaderAware.isAssignableFrom( o.getClass() ) )
+        if ( baseProviderFactory.isAssignableFrom( o.getClass() ) )
         {
             setTestClassLoader( o, testClassLoader );
         }
@@ -358,7 +314,7 @@ public class SurefireReflector
 
     public void setTestArtifactInfoAware( Object o, TestArtifactInfo testArtifactInfo1 )
     {
-        if ( testArtifactInfoAware.isAssignableFrom( o.getClass() ) )
+        if ( baseProviderFactory.isAssignableFrom( o.getClass() ) )
         {
             setTestArtifactInfo( o, testArtifactInfo1 );
         }
@@ -370,31 +326,31 @@ public class SurefireReflector
         invokeSetter( o, "setTestArtifactInfo", this.testArtifactInfo, param );
     }
 
+    public void setReporterFactoryAware( Object o, Object reporterFactory )
+    {
+        if ( baseProviderFactory.isAssignableFrom( o.getClass() ) )
+        {
+            setReporterFactory( o, reporterFactory );
+        }
+    }
+
+    void setReporterFactory( Object o, Object reporterFactory )
+    {
+        invokeSetter( o, "setReporterFactory", this.reporterFactory, reporterFactory );
+    }
+
     private boolean isRunResult( Object o )
     {
         return runResult.isAssignableFrom( o.getClass() );
     }
 
-    private static Collection<Integer> toOrdinals( Collection<? extends Enum> enums )
+    private static Collection<Integer> toOrdinals( Collection<? extends Enum<?>> enums )
     {
         Collection<Integer> ordinals = new ArrayList<>( enums.size() );
-        for ( Enum e : enums )
+        for ( Enum<?> e : enums )
         {
             ordinals.add( e.ordinal() );
         }
         return ordinals;
     }
-
-    public static Object createConsoleLogger( ConsoleLogger consoleLogger, ClassLoader cl )
-    {
-        try
-        {
-            Class<?> decoratorClass = cl.loadClass( ConsoleLoggerDecorator.class.getName() );
-            return getConstructor( decoratorClass, Object.class ).newInstance( consoleLogger );
-        }
-        catch ( Exception e )
-        {
-            throw new SurefireReflectionException( e );
-        }
-    }
 }
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
new file mode 100644
index 0000000..c22ee11
--- /dev/null
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelDecoder.java
@@ -0,0 +1,166 @@
+package org.apache.maven.surefire.booter.spi;
+
+/*
+ * 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.Command;
+import org.apache.maven.surefire.booter.MasterProcessCommand;
+import org.apache.maven.surefire.providerapi.MasterProcessChannelDecoder;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * magic number : opcode [: opcode specific data]*
+ * <br>
+ *
+ * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
+ * @since 3.0.0-M5
+ */
+public class LegacyMasterProcessChannelDecoder implements MasterProcessChannelDecoder
+{
+    private final InputStream is;
+
+    public LegacyMasterProcessChannelDecoder( InputStream is )
+    {
+        this.is = is;
+    }
+
+    protected boolean hasData( String opcode )
+    {
+        MasterProcessCommand cmd = MasterProcessCommand.byOpcode( opcode );
+        return cmd == null || cmd.hasDataType();
+    }
+
+    @SuppressWarnings( "checkstyle:innerassignment" )
+    @Override
+    public Command decode() throws IOException
+    {
+        List<String> tokens = new ArrayList<>();
+        StringBuilder frame = new StringBuilder();
+        boolean frameStarted = false;
+        boolean frameFinished = false;
+        boolean notEndOfStream;
+        for ( int r; notEndOfStream = ( r = is.read() ) != -1 ; )
+        {
+            char c = (char) r;
+            if ( frameFinished && c == '\n' )
+            {
+                continue;
+            }
+
+            if ( !frameStarted )
+            {
+                if ( c == ':' )
+                {
+                    frameStarted = true;
+                    frameFinished = false;
+                    frame.setLength( 0 );
+                    tokens.clear();
+                    continue;
+                }
+            }
+            else if ( !frameFinished )
+            {
+                boolean isColon = c == ':';
+                if ( isColon || c == '\n' || c == '\r' )
+                {
+                    tokens.add( frame.toString() );
+                    frame.setLength( 0 );
+                }
+                else
+                {
+                    frame.append( c );
+                }
+                boolean isFinishedFrame = isTokenComplete( tokens );
+                if ( isFinishedFrame )
+                {
+                    frameFinished = true;
+                    frameStarted = false;
+                    break;
+                }
+            }
+
+            boolean removed = removeUnsynchronizedTokens( tokens );
+            if ( removed && tokens.isEmpty() )
+            {
+                frameStarted = false;
+                frameFinished = true;
+            }
+        }
+
+        if ( !notEndOfStream )
+        {
+            throw new EOFException();
+        }
+
+        if ( tokens.size() <= 1 ) // todo
+        {
+            throw new MasterProcessCommandNoMagicNumberException( frame.toString() );
+        }
+        if ( tokens.size() == 2 )
+        {
+            return new Command( MasterProcessCommand.byOpcode( tokens.get( 1 ) ) );
+        }
+        else if ( tokens.size() == 3 )
+        {
+            return new Command( MasterProcessCommand.byOpcode( tokens.get( 1 ) ), tokens.get( 2 ) );
+        }
+        else
+        {
+            throw new MasterProcessUnknownCommandException( frame.toString() );
+        }
+    }
+
+    private boolean isTokenComplete( List<String> tokens )
+    {
+        if ( tokens.size() >= 2 )
+        {
+            return hasData( tokens.get( 1 ) ) == ( tokens.size() == 3 );
+        }
+        return false;
+    }
+
+    private boolean removeUnsynchronizedTokens( Collection<String> tokens )
+    {
+        boolean removed = false;
+        for ( Iterator<String> it = tokens.iterator(); it.hasNext(); )
+        {
+            String token = it.next();
+            if ( token.equals( MasterProcessCommand.MAGIC_NUMBER ) )
+            {
+                break;
+            }
+            removed = true;
+            it.remove();
+            System.err.println( "Forked JVM could not synchronize the '" + token + "' token with preamble sequence." );
+        }
+        return removed;
+    }
+
+    @Override
+    public void close()
+    {
+    }
+}
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/ForkedChannelEncoder.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelEncoder.java
similarity index 92%
rename from surefire-api/src/main/java/org/apache/maven/surefire/booter/ForkedChannelEncoder.java
rename to surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelEncoder.java
index 94f620a..d4b32bd 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/ForkedChannelEncoder.java
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelEncoder.java
@@ -1,4 +1,4 @@
-package org.apache.maven.surefire.booter;
+package org.apache.maven.surefire.booter.spi;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -19,6 +19,9 @@ package org.apache.maven.surefire.booter;
  * under the License.
  */
 
+import org.apache.maven.surefire.booter.DumpErrorSingleton;
+import org.apache.maven.surefire.booter.ForkedProcessEvent;
+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;
@@ -34,7 +37,7 @@ 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;
+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;
@@ -67,42 +70,46 @@ import static java.util.Objects.requireNonNull;
  * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
  * @since 3.0.0-M4
  */
-public final class ForkedChannelEncoder
+public class LegacyMasterProcessChannelEncoder implements MasterProcessChannelEncoder
 {
     private static final Base64 BASE64 = new Base64();
     private static final Charset STREAM_ENCODING = US_ASCII;
     private static final Charset STRING_ENCODING = UTF_8;
 
-    private final OutputStream out;
+    protected final OutputStream out;
     private final RunMode runMode;
     private volatile boolean trouble;
 
-    public ForkedChannelEncoder( OutputStream out )
+    public LegacyMasterProcessChannelEncoder( OutputStream out )
     {
         this( out, NORMAL_RUN );
     }
 
-    private ForkedChannelEncoder( OutputStream out, RunMode runMode )
+    protected LegacyMasterProcessChannelEncoder( OutputStream out, RunMode runMode )
     {
         this.out = requireNonNull( out );
         this.runMode = requireNonNull( runMode );
     }
 
-    public ForkedChannelEncoder asRerunMode() // todo apply this and rework providers
+    @Override
+    public MasterProcessChannelEncoder asRerunMode() // todo apply this and rework providers
     {
-        return new ForkedChannelEncoder( out, RERUN_TEST_AFTER_FAILURE );
+        return new LegacyMasterProcessChannelEncoder( out, RERUN_TEST_AFTER_FAILURE );
     }
 
-    public ForkedChannelEncoder asNormalMode()
+    @Override
+    public MasterProcessChannelEncoder asNormalMode()
     {
-        return new ForkedChannelEncoder( out, NORMAL_RUN );
+        return new LegacyMasterProcessChannelEncoder( out, NORMAL_RUN );
     }
 
+    @Override
     public boolean checkError()
     {
         return trouble;
     }
 
+    @Override
     public void sendSystemProperties( Map<String, String> sysProps )
     {
         for ( Entry<String, String> entry : sysProps.entrySet() )
@@ -114,52 +121,62 @@ public final class ForkedChannelEncoder
         }
     }
 
+    @Override
     public void testSetStarting( ReportEntry reportEntry, boolean trimStackTraces )
     {
         encode( BOOTERCODE_TESTSET_STARTING, runMode, reportEntry, trimStackTraces );
     }
 
+    @Override
     public void testSetCompleted( ReportEntry reportEntry, boolean trimStackTraces )
     {
         encode( BOOTERCODE_TESTSET_COMPLETED, runMode, reportEntry, trimStackTraces );
     }
 
+    @Override
     public void testStarting( ReportEntry reportEntry, boolean trimStackTraces )
     {
         encode( BOOTERCODE_TEST_STARTING, runMode, reportEntry, trimStackTraces );
     }
 
+    @Override
     public void testSucceeded( ReportEntry reportEntry, boolean trimStackTraces )
     {
         encode( BOOTERCODE_TEST_SUCCEEDED, runMode, reportEntry, trimStackTraces );
     }
 
+    @Override
     public void testFailed( ReportEntry reportEntry, boolean trimStackTraces )
     {
         encode( BOOTERCODE_TEST_FAILED, runMode, reportEntry, trimStackTraces );
     }
 
+    @Override
     public void testSkipped( ReportEntry reportEntry, boolean trimStackTraces )
     {
         encode( BOOTERCODE_TEST_SKIPPED, runMode, reportEntry, trimStackTraces );
     }
 
+    @Override
     public void testError( ReportEntry reportEntry, boolean trimStackTraces )
     {
         encode( BOOTERCODE_TEST_ERROR, runMode, reportEntry, trimStackTraces );
     }
 
+    @Override
     public void testAssumptionFailure( ReportEntry reportEntry, boolean trimStackTraces )
     {
         encode( BOOTERCODE_TEST_ASSUMPTIONFAILURE, runMode, reportEntry, trimStackTraces );
     }
 
+    @Override
     public void stdOut( String msg, boolean newLine )
     {
         ForkedProcessEvent 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;
@@ -173,23 +190,27 @@ public final class ForkedChannelEncoder
         encodeAndPrintEvent( event );
     }
 
+    @Override
     public void consoleInfoLog( String msg )
     {
         StringBuilder event = print( BOOTERCODE_CONSOLE_INFO.getOpcode(), msg );
         encodeAndPrintEvent( event );
     }
 
+    @Override
     public void consoleErrorLog( String msg )
     {
         StringBuilder event = print( BOOTERCODE_CONSOLE_ERROR.getOpcode(), msg );
         encodeAndPrintEvent( event );
     }
 
+    @Override
     public void consoleErrorLog( Throwable t )
     {
         consoleErrorLog( t.getLocalizedMessage(), t );
     }
 
+    @Override
     public void consoleErrorLog( String msg, Throwable t )
     {
         StringBuilder encoded = encodeHeader( BOOTERCODE_CONSOLE_ERROR.getOpcode(), null );
@@ -197,38 +218,45 @@ public final class ForkedChannelEncoder
         encodeAndPrintEvent( encoded );
     }
 
+    @Override
     public void consoleErrorLog( StackTraceWriter stackTraceWriter, boolean trimStackTraces )
     {
         error( stackTraceWriter, trimStackTraces, BOOTERCODE_CONSOLE_ERROR );
     }
 
+    @Override
     public void consoleDebugLog( String msg )
     {
         StringBuilder event = print( BOOTERCODE_CONSOLE_DEBUG.getOpcode(), msg );
         encodeAndPrintEvent( event );
     }
 
+    @Override
     public void consoleWarningLog( String msg )
     {
         StringBuilder event = print( BOOTERCODE_CONSOLE_WARNING.getOpcode(), msg );
         encodeAndPrintEvent( event );
     }
 
+    @Override
     public void bye()
     {
         encodeOpcode( BOOTERCODE_BYE );
     }
 
+    @Override
     public void stopOnNextTest()
     {
         encodeOpcode( BOOTERCODE_STOP_ON_NEXT_TEST );
     }
 
+    @Override
     public void acquireNextTest()
     {
         encodeOpcode( BOOTERCODE_NEXT_TEST );
     }
 
+    @Override
     public void sendExitEvent( StackTraceWriter stackTraceWriter, boolean trimStackTraces )
     {
         error( stackTraceWriter, trimStackTraces, BOOTERCODE_JVM_EXIT_ERROR );
@@ -390,7 +418,7 @@ public final class ForkedChannelEncoder
     static StringBuilder encodeOpcode( String operation, String runMode )
     {
         StringBuilder s = new StringBuilder( 128 )
-                .append( MAGIC_NUMBER )
+                .append( MAGIC_NUMBER_DELIMITED )
                 .append( operation );
 
         return runMode == null ? s : s.append( ':' ).append( runMode );
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
new file mode 100644
index 0000000..b5d4dd5
--- /dev/null
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelProcessorFactory.java
@@ -0,0 +1,70 @@
+package org.apache.maven.surefire.booter.spi;
+
+/*
+ * 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.providerapi.MasterProcessChannelDecoder;
+import org.apache.maven.surefire.providerapi.MasterProcessChannelEncoder;
+import org.apache.maven.surefire.spi.MasterProcessChannelProcessorFactory;
+
+import java.io.IOException;
+import java.net.MalformedURLException;
+
+/**
+ * Producer of encoder and decoder for process pipes.
+ * <br>
+ *
+ * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
+ * @since 3.0.0-M5
+ */
+public class LegacyMasterProcessChannelProcessorFactory
+    implements MasterProcessChannelProcessorFactory
+{
+    @Override
+    public boolean canUse( String channelConfig )
+    {
+        return channelConfig.startsWith( "pipe://" );
+    }
+
+    @Override
+    public void connect( String channelConfig ) throws IOException
+    {
+        if ( !canUse( channelConfig ) )
+        {
+            throw new MalformedURLException( "Unknown chanel string " + channelConfig );
+        }
+    }
+
+    @Override
+    public MasterProcessChannelDecoder createDecoder()
+    {
+        return new LegacyMasterProcessChannelDecoder( System.in );
+    }
+
+    @Override
+    public MasterProcessChannelEncoder createEncoder()
+    {
+        return new LegacyMasterProcessChannelEncoder( System.out );
+    }
+
+    @Override
+    public void close()
+    {
+    }
+}
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/FailFastAware.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/MasterProcessCommandNoMagicNumberException.java
similarity index 65%
rename from surefire-api/src/main/java/org/apache/maven/surefire/booter/FailFastAware.java
rename to surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/MasterProcessCommandNoMagicNumberException.java
index 994b60d..261969e 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/FailFastAware.java
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/MasterProcessCommandNoMagicNumberException.java
@@ -1,4 +1,4 @@
-package org.apache.maven.surefire.booter;
+package org.apache.maven.surefire.booter.spi;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -19,13 +19,20 @@ package org.apache.maven.surefire.booter;
  * under the License.
  */
 
+import org.apache.maven.surefire.booter.MasterProcessCommand;
+
+import java.io.IOException;
+
 /**
- * See the plugin configuration parameter {@code skipAfterFailureCount}.
+ * No magic number recognized in the command line, see the JavaDoc in {@link MasterProcessCommand}.
  *
  * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
- * @since 2.19
+ * @since 3.0.0-M4
  */
-interface FailFastAware
+public class MasterProcessCommandNoMagicNumberException extends IOException
 {
-    void setSkipAfterFailureCount( int skipAfterFailureCount );
+    MasterProcessCommandNoMagicNumberException( String line )
+    {
+        super( "No magic # recognized in the line '" + line + "'" );
+    }
 }
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/MainCliOptionsAware.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/MasterProcessUnknownCommandException.java
similarity index 63%
copy from surefire-api/src/main/java/org/apache/maven/surefire/booter/MainCliOptionsAware.java
copy to surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/MasterProcessUnknownCommandException.java
index eddebed..11cab97 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/MainCliOptionsAware.java
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/MasterProcessUnknownCommandException.java
@@ -1,4 +1,4 @@
-package org.apache.maven.surefire.booter;
+package org.apache.maven.surefire.booter.spi;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -19,17 +19,21 @@ package org.apache.maven.surefire.booter;
  * under the License.
  */
 
-import org.apache.maven.surefire.cli.CommandLineOption;
+import org.apache.maven.surefire.booter.MasterProcessCommand;
 
-import java.util.List;
+import java.io.IOException;
 
 /**
- * CLI options in plugin (main) JVM process.
+ * No {@link MasterProcessCommand command} recognized according to the opcode
+ * encapsulated in the command line, see the JavaDoc in {@link MasterProcessCommand}.
  *
  * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
- * @since 2.19
+ * @since 3.0.0-M4
  */
-interface MainCliOptionsAware
+public class MasterProcessUnknownCommandException extends IOException
 {
-    void setMainCliOptions( List<CommandLineOption> mainCliOptions );
+    MasterProcessUnknownCommandException( String line )
+    {
+        super( "Unrecognized command found '" + line + "'" );
+    }
 }
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
new file mode 100644
index 0000000..497e748
--- /dev/null
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/SurefireMasterProcessChannelProcessorFactory.java
@@ -0,0 +1,91 @@
+package org.apache.maven.surefire.booter.spi;
+
+/*
+ * 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.providerapi.MasterProcessChannelDecoder;
+import org.apache.maven.surefire.providerapi.MasterProcessChannelEncoder;
+import org.apache.maven.surefire.spi.MasterProcessChannelProcessorFactory;
+
+import java.io.IOException;
+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;
+
+/**
+ * Producer of TCP/IP encoder and decoder.
+ * <br>
+ *
+ * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
+ * @since 3.0.0-M5
+ */
+public class SurefireMasterProcessChannelProcessorFactory
+    implements MasterProcessChannelProcessorFactory
+{
+    private volatile SocketChannel channel;
+
+    @Override
+    public boolean canUse( String channelConfig )
+    {
+        return channelConfig.startsWith( "tcp://" );
+    }
+
+    @Override
+    public void connect( String channelConfig ) throws IOException
+    {
+        if ( !canUse( channelConfig ) )
+        {
+            throw new MalformedURLException( "Unknown chanel string " + channelConfig );
+        }
+
+        try
+        {
+            URI uri = new URI( channelConfig );
+            channel = SocketChannel.open( new InetSocketAddress( uri.getHost(), uri.getPort() ) );
+        }
+        catch ( URISyntaxException e )
+        {
+            throw new IOException( e.getLocalizedMessage(), e );
+        }
+    }
+
+    @Override
+    public MasterProcessChannelDecoder createDecoder()
+    {
+        return new LegacyMasterProcessChannelDecoder( Channels.newInputStream( channel ) );
+    }
+
+    @Override
+    public MasterProcessChannelEncoder createEncoder()
+    {
+        return new LegacyMasterProcessChannelEncoder( Channels.newOutputStream( channel ) );
+    }
+
+    @Override
+    public void close() throws IOException
+    {
+        if ( channel != null )
+        {
+            channel.close();
+        }
+    }
+}
diff --git a/.github/workflows/maven.yml b/surefire-booter/src/main/resources/META-INF/services/org.apache.maven.surefire.spi.MasterProcessChannelProcessorFactory
similarity index 58%
copy from .github/workflows/maven.yml
copy to surefire-booter/src/main/resources/META-INF/services/org.apache.maven.surefire.spi.MasterProcessChannelProcessorFactory
index 0d56bb9..22a3f43 100644
--- a/.github/workflows/maven.yml
+++ b/surefire-booter/src/main/resources/META-INF/services/org.apache.maven.surefire.spi.MasterProcessChannelProcessorFactory
@@ -1,3 +1,4 @@
+#
 # 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
@@ -6,7 +7,7 @@
 # "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
+#     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
@@ -14,29 +15,6 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-
-name: GitHub CI
-
-on: [push]
-
-jobs:
-  build:
-
-    strategy:
-      matrix:
-        os: [ubuntu-latest, windows-latest, macOS-latest]
-      fail-fast: false
-
-    runs-on: ${{ matrix.os }}
-
-    steps:
-      - name: Checkout
-        uses: actions/checkout@v1
-
-      - name: Set up JDK 1.8
-        uses: actions/setup-java@v1
-        with:
-          java-version: 1.8
-
-      - name: Build with Maven
-        run: mvn install -e -B -V -nsu --no-transfer-progress -P run-its
+#
+org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelProcessorFactory
+org.apache.maven.surefire.booter.spi.SurefireMasterProcessChannelProcessorFactory
diff --git a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/BooterDeserializerTest.java b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/BooterDeserializerTest.java
index 302f7ad..b1c0284 100644
--- a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/BooterDeserializerTest.java
+++ b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/BooterDeserializerTest.java
@@ -48,7 +48,7 @@ public class BooterDeserializerTest
         assertThat( deserializer.getStartupConfiguration().getProcessChecker() )
                 .isEqualTo( ALL );
 
-        assertThat( deserializer.getStartupConfiguration().useSystemClassLoader() )
+        assertThat( deserializer.getStartupConfiguration().getClassLoaderConfiguration().isUseSystemClassLoader() )
                 .isTrue();
 
         assertThat( deserializer.getStartupConfiguration().getProviderClassName() )
diff --git a/surefire-api/src/test/java/org/apache/maven/surefire/booter/CommandReaderTest.java b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/CommandReaderTest.java
similarity index 78%
rename from surefire-api/src/test/java/org/apache/maven/surefire/booter/CommandReaderTest.java
rename to surefire-booter/src/test/java/org/apache/maven/surefire/booter/CommandReaderTest.java
index 40c0243..74d7b16 100644
--- a/surefire-api/src/test/java/org/apache/maven/surefire/booter/CommandReaderTest.java
+++ b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/CommandReaderTest.java
@@ -19,6 +19,11 @@ package org.apache.maven.surefire.booter;
  * under the License.
  */
 
+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.LegacyMasterProcessChannelDecoder;
+import org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelEncoder;
+import org.apache.maven.surefire.providerapi.MasterProcessChannelDecoder;
 import org.apache.maven.surefire.testset.TestSetFailedException;
 import org.junit.After;
 import org.junit.Before;
@@ -29,7 +34,6 @@ import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.PrintStream;
-import java.nio.ByteBuffer;
 import java.util.Iterator;
 import java.util.NoSuchElementException;
 import java.util.concurrent.BlockingQueue;
@@ -39,8 +43,7 @@ import java.util.concurrent.FutureTask;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
 
-import static java.nio.charset.StandardCharsets.ISO_8859_1;
-
+import static java.nio.charset.StandardCharsets.US_ASCII;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
@@ -61,7 +64,6 @@ public class CommandReaderTest
     private static final long TEST_TIMEOUT = 15_000L;
 
     private final BlockingQueue<Byte> blockingStream = new LinkedBlockingQueue<>();
-    private InputStream realInputStream;
     private CommandReader reader;
 
     static class A
@@ -83,24 +85,25 @@ public class CommandReaderTest
     @Before
     public void init()
     {
+        //noinspection ResultOfMethodCallIgnored
         Thread.interrupted();
-        realInputStream = System.in;
+        InputStream realInputStream = new SystemInputStream();
         addTestToPipeline( getClass().getName() );
-        System.setIn( new SystemInputStream() );
-        reader = CommandReader.getReader();
+        ConsoleLogger logger = new NullConsoleLogger();
+        MasterProcessChannelDecoder decoder = new LegacyMasterProcessChannelDecoder( realInputStream );
+        reader = new CommandReader( decoder, Shutdown.DEFAULT, logger );
     }
 
     @After
     public void deinit()
     {
         reader.stop();
-        System.setIn( realInputStream );
     }
 
     @Test
     public void readJustOneClass()
     {
-        Iterator<String> it = reader.getIterableClasses( new ForkedChannelEncoder( nul() ) ).iterator();
+        Iterator<String> it = reader.getIterableClasses( new LegacyMasterProcessChannelEncoder( nul() ) ).iterator();
         assertTrue( it.hasNext() );
         assertThat( it.next(), is( getClass().getName() ) );
         reader.stop();
@@ -119,7 +122,7 @@ public class CommandReaderTest
     @Test
     public void manyClasses()
     {
-        Iterator<String> it1 = reader.getIterableClasses( new ForkedChannelEncoder( nul() ) ).iterator();
+        Iterator<String> it1 = reader.getIterableClasses( new LegacyMasterProcessChannelEncoder( nul() ) ).iterator();
         assertThat( it1.next(), is( getClass().getName() ) );
         addTestToPipeline( A.class.getName() );
         assertThat( it1.next(), is( A.class.getName() ) );
@@ -135,7 +138,7 @@ public class CommandReaderTest
     @Test
     public void twoIterators() throws Exception
     {
-        Iterator<String> it1 = reader.getIterableClasses( new ForkedChannelEncoder( nul() ) ).iterator();
+        Iterator<String> it1 = reader.getIterableClasses( new LegacyMasterProcessChannelEncoder( nul() ) ).iterator();
 
         assertThat( it1.next(), is( getClass().getName() ) );
         addTestToPipeline( A.class.getName() );
@@ -168,7 +171,8 @@ public class CommandReaderTest
             @Override
             public void run()
             {
-                Iterator<String> it = reader.getIterableClasses( new ForkedChannelEncoder( nul() ) ).iterator();
+                Iterator<String> it =
+                    reader.getIterableClasses( new LegacyMasterProcessChannelEncoder( nul() ) ).iterator();
                 assertThat( it.next(), is( CommandReaderTest.class.getName() ) );
             }
         };
@@ -195,7 +199,8 @@ public class CommandReaderTest
             @Override
             public void run()
             {
-                Iterator<String> it = reader.getIterableClasses( new ForkedChannelEncoder( nul() ) ).iterator();
+                Iterator<String> it =
+                    reader.getIterableClasses( new LegacyMasterProcessChannelEncoder( nul() ) ).iterator();
                 assertThat( it.next(), is( CommandReaderTest.class.getName() ) );
                 counter.countDown();
                 assertThat( it.next(), is( Foo.class.getName() ) );
@@ -242,24 +247,20 @@ public class CommandReaderTest
 
     private void addTestToPipeline( String cls )
     {
-        byte[] clazz = cls.getBytes( ISO_8859_1 );
-        ByteBuffer buffer = ByteBuffer.allocate( 8 + clazz.length ).putInt(
-                MasterProcessCommand.RUN_CLASS.getId() ).putInt( clazz.length ).put( clazz );
-        buffer.rewind();
-        for ( ; buffer.hasRemaining(); )
+        String cmd = ":maven-surefire-command:"
+            + MasterProcessCommand.RUN_CLASS.getOpcode() + ':' + cls + '\n';
+        for ( byte cmdByte : cmd.getBytes( US_ASCII ) )
         {
-            blockingStream.add( buffer.get() );
+            blockingStream.add( cmdByte );
         }
     }
 
     private void addEndOfPipeline()
     {
-        ByteBuffer buffer = ByteBuffer.allocate( 8 ).putInt( MasterProcessCommand.TEST_SET_FINISHED.getId() ).putInt(
-                0 );
-        buffer.rewind();
-        for ( ; buffer.hasRemaining(); )
+        String cmd = ":maven-surefire-command:" + MasterProcessCommand.TEST_SET_FINISHED.getOpcode() + '\n';
+        for ( byte cmdByte : cmd.getBytes( US_ASCII ) )
         {
-            blockingStream.add( buffer.get() );
+            blockingStream.add( cmdByte );
         }
     }
 
diff --git a/surefire-api/src/test/java/org/apache/maven/surefire/booter/Foo.java b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/Foo.java
similarity index 71%
rename from surefire-api/src/test/java/org/apache/maven/surefire/booter/Foo.java
rename to surefire-booter/src/test/java/org/apache/maven/surefire/booter/Foo.java
index ccb01e3..1f96fbf 100644
--- a/surefire-api/src/test/java/org/apache/maven/surefire/booter/Foo.java
+++ b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/Foo.java
@@ -19,9 +19,9 @@ package org.apache.maven.surefire.booter;
  * under the License.
  */
 
-
 import java.util.Map;
 import org.apache.maven.surefire.report.ReporterConfiguration;
+import org.apache.maven.surefire.report.ReporterFactory;
 import org.apache.maven.surefire.testset.DirectoryScannerParameters;
 import org.apache.maven.surefire.testset.RunOrderParameters;
 import org.apache.maven.surefire.testset.TestArtifactInfo;
@@ -30,36 +30,32 @@ import org.apache.maven.surefire.testset.TestRequest;
 /**
  * @author Kristian Rosenvold
  */
-public class Foo
-    implements DirectoryScannerParametersAware, TestRequestAware, ProviderPropertiesAware, ReporterConfigurationAware,
-    SurefireClassLoadersAware, TestArtifactInfoAware, RunOrderParametersAware
+public class Foo extends BaseProviderFactory
 {
-    DirectoryScannerParameters directoryScannerParameters;
-
-    Map<String, String> providerProperties;
-
-    ReporterConfiguration reporterConfiguration;
+    private Map<String, String> providerProperties;
 
-    ClassLoader surefireClassLoader;
+    private ReporterConfiguration reporterConfiguration;
 
-    ClassLoader testClassLoader;
+    private ClassLoader testClassLoader;
 
-    TestRequest testRequest;
+    private TestArtifactInfo testArtifactInfo;
 
-    TestArtifactInfo testArtifactInfo;
+    private RunOrderParameters runOrderParameters;
 
-    RunOrderParameters runOrderParameters;
+    private boolean called;
 
-    boolean called = false;
+    Foo()
+    {
+        super( false );
+    }
 
     @Override
     public void setDirectoryScannerParameters( DirectoryScannerParameters directoryScanner )
     {
-        this.directoryScannerParameters = directoryScanner;
+        super.setDirectoryScannerParameters( directoryScanner );
         this.called = true;
     }
 
-
     /**
      * @return true if it has been called
      */
@@ -71,8 +67,8 @@ public class Foo
     @Override
     public void setProviderProperties( Map<String, String> providerProperties )
     {
-        this.providerProperties = providerProperties;
-        this.called = true;
+        super.setProviderProperties( providerProperties );
+        called = true;
     }
 
     @Override
@@ -86,14 +82,13 @@ public class Foo
     public void setClassLoaders( ClassLoader testClassLoader )
     {
         this.testClassLoader = testClassLoader;
-        this.surefireClassLoader = surefireClassLoader;
         this.called = true;
     }
 
     @Override
-    public void setTestRequest( TestRequest testRequest1 )
+    public void setTestRequest( TestRequest testRequest )
     {
-        this.testRequest = testRequest1;
+        super.setTestRequest( testRequest );
         this.called = true;
     }
 
@@ -107,7 +102,14 @@ public class Foo
     @Override
     public void setRunOrderParameters( RunOrderParameters runOrderParameters )
     {
-        this.runOrderParameters = runOrderParameters;
+        super.setRunOrderParameters( runOrderParameters );
         this.called = true;
     }
+
+    @Override
+    public void setReporterFactory( ReporterFactory reporterFactory )
+    {
+        super.setReporterFactory( reporterFactory );
+        called = true;
+    }
 }
diff --git a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/ForkedBooterMockTest.java b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/ForkedBooterMockTest.java
index e65beb1..2cbb3f2 100644
--- a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/ForkedBooterMockTest.java
+++ b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/ForkedBooterMockTest.java
@@ -19,11 +19,15 @@ package org.apache.maven.surefire.booter;
  * under the License.
  */
 
+import org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelEncoder;
+import org.apache.maven.surefire.report.StackTraceWriter;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
 import org.mockito.Mock;
 import org.powermock.api.mockito.PowerMockito;
+import org.powermock.core.classloader.annotations.PowerMockIgnore;
 import org.powermock.core.classloader.annotations.PrepareForTest;
 import org.powermock.modules.junit4.PowerMockRunner;
 
@@ -31,18 +35,24 @@ import static org.fest.assertions.Assertions.assertThat;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.same;
 import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
 import static org.powermock.api.mockito.PowerMockito.doCallRealMethod;
+import static org.powermock.api.mockito.PowerMockito.doNothing;
 import static org.powermock.api.mockito.PowerMockito.doThrow;
 import static org.powermock.api.mockito.PowerMockito.verifyNoMoreInteractions;
 import static org.powermock.api.mockito.PowerMockito.verifyPrivate;
 import static org.powermock.api.mockito.PowerMockito.when;
 import static org.powermock.reflect.Whitebox.invokeMethod;
+import static org.powermock.reflect.Whitebox.setInternalState;
+import static org.powermock.utils.JavaVersion.JAVA_RECENT;
+import static org.powermock.utils.JavaVersion.JAVA_12;
 
 /**
  * PowerMock tests for {@link ForkedBooter}.
  */
 @RunWith( PowerMockRunner.class )
-@PrepareForTest( { PpidChecker.class, ForkedBooter.class } )
+@PrepareForTest( { PpidChecker.class, ForkedBooter.class, LegacyMasterProcessChannelEncoder.class } )
+@PowerMockIgnore( { "org.jacoco.agent.rt.*", "com.vladium.emma.rt.*" } )
 public class ForkedBooterMockTest
 {
     @Mock
@@ -51,6 +61,21 @@ public class ForkedBooterMockTest
     @Mock
     private ForkedBooter booter;
 
+    @Mock
+    private LegacyMasterProcessChannelEncoder eventChannel;
+
+    @Captor
+    private ArgumentCaptor<StackTraceWriter> capturedStackTraceWriter;
+
+    @Captor
+    private ArgumentCaptor<Boolean> capturedBoolean;
+
+    @Captor
+    private ArgumentCaptor<String[]> capturedArgs;
+
+    @Captor
+    private ArgumentCaptor<ForkedBooter> capturedBooter;
+
     @Test
     public void shouldCheckNewPingMechanism() throws Exception
     {
@@ -71,8 +96,6 @@ public class ForkedBooterMockTest
     {
         PowerMockito.mockStatic( ForkedBooter.class );
 
-        ArgumentCaptor<String[]> capturedArgs = ArgumentCaptor.forClass( String[].class );
-        ArgumentCaptor<ForkedBooter> capturedBooter = ArgumentCaptor.forClass( ForkedBooter.class );
         doCallRealMethod()
                 .when( ForkedBooter.class, "run", capturedBooter.capture(), capturedArgs.capture() );
 
@@ -106,6 +129,13 @@ public class ForkedBooterMockTest
     @Test
     public void testMainWithError() throws Exception
     {
+        //does not work in PowerMock
+        //assumeFalse( JAVA_RECENT.atLeast( JAVA_12 ) );
+        if ( JAVA_RECENT.atLeast( JAVA_12 ) )
+        {
+            return;
+        }
+
         PowerMockito.mockStatic( ForkedBooter.class );
 
         doCallRealMethod()
@@ -114,6 +144,13 @@ public class ForkedBooterMockTest
         doThrow( new RuntimeException( "dummy exception" ) )
                 .when( booter, "execute" );
 
+        doNothing()
+                .when( booter, "setupBooter",
+                        any( String.class ), any( String.class ), any( String.class ), any( String.class ) );
+
+        // broken in PowerMock JDK 12
+        setInternalState( booter, "eventChannel", eventChannel );
+
         String[] args = new String[]{ "/", "dump", "surefire.properties", "surefire-effective.properties" };
         invokeMethod( ForkedBooter.class, "run", booter, args );
 
@@ -123,6 +160,18 @@ public class ForkedBooterMockTest
         verifyPrivate( booter, times( 1 ) )
                 .invoke( "execute" );
 
+        verify( eventChannel, times( 1 ) )
+                .consoleErrorLog( capturedStackTraceWriter.capture(), capturedBoolean.capture() );
+        assertThat( capturedStackTraceWriter.getValue() )
+                .isNotNull();
+        assertThat( capturedStackTraceWriter.getValue().smartTrimmedStackTrace() )
+                .isEqualTo( "test subsystem#no method RuntimeException dummy exception" );
+        assertThat( capturedStackTraceWriter.getValue().getThrowable().getTarget() )
+                .isNotNull()
+                .isInstanceOf( RuntimeException.class );
+        assertThat( capturedStackTraceWriter.getValue().getThrowable().getTarget().getMessage() )
+                .isEqualTo( "dummy exception" );
+
         verifyPrivate( booter, times( 1 ) )
                 .invoke( "cancelPingScheduler" );
 
diff --git a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/IsolatedClassLoaderTest.java b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/IsolatedClassLoaderTest.java
new file mode 100644
index 0000000..b424526
--- /dev/null
+++ b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/IsolatedClassLoaderTest.java
@@ -0,0 +1,66 @@
+package org.apache.maven.surefire.booter;
+
+/*
+ * 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.shared.utils.io.FileUtils;
+import org.apache.maven.surefire.providerapi.AbstractProvider;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.File;
+import java.net.URL;
+
+import static java.io.File.pathSeparator;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+/**
+ * Tests isolated CL.
+ */
+public class IsolatedClassLoaderTest
+{
+    private IsolatedClassLoader classLoader;
+
+    @Before
+    public void prepareClassLoader() throws Exception
+    {
+        classLoader = new IsolatedClassLoader( null, false, "role" );
+
+        String[] files = FileUtils.fileRead( new File( "target/test-classpath/cp.txt" ), "UTF-8" )
+                .split( pathSeparator );
+
+        for ( String file : files )
+        {
+            URL fileUrl = new File( file ).toURL();
+            classLoader.addURL( fileUrl );
+        }
+    }
+
+    @Test
+    public void shouldLoadIsolatedClass() throws Exception
+    {
+        Class<?> isolatedClass = classLoader.loadClass( AbstractProvider.class.getName() );
+        assertThat( isolatedClass, is( notNullValue() ) );
+        assertThat( isolatedClass.getName(), is( AbstractProvider.class.getName() ) );
+        assertThat( isolatedClass, is( not( (Class) AbstractProvider.class ) ) );
+    }
+}
\ No newline at end of file
diff --git a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/JUnit4SuiteTest.java b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/JUnit4SuiteTest.java
index 75b2ef0..a7c6298 100644
--- a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/JUnit4SuiteTest.java
+++ b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/JUnit4SuiteTest.java
@@ -23,6 +23,8 @@ import junit.framework.JUnit4TestAdapter;
 import junit.framework.Test;
 import junit.framework.TestCase;
 import junit.framework.TestSuite;
+import org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelDecoderTest;
+import org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelEncoderTest;
 
 /**
  * Adapt the JUnit4 tests which use only annotations to the JUnit3 test suite.
@@ -35,13 +37,18 @@ public class JUnit4SuiteTest extends TestCase
     public static Test suite()
     {
         TestSuite suite = new TestSuite();
+        suite.addTest( new JUnit4TestAdapter( CommandReaderTest.class ) );
         suite.addTest( new JUnit4TestAdapter( PpidCheckerTest.class ) );
         suite.addTest( new JUnit4TestAdapter( SystemUtilsTest.class ) );
+        suite.addTest( new JUnit4TestAdapter( IsolatedClassLoaderTest.class ) );
         suite.addTest( new JUnit4TestAdapter( ForkedBooterTest.class ) );
         suite.addTest( new JUnit4TestAdapter( ForkedBooterMockTest.class ) );
         suite.addTest( new JUnit4TestAdapter( BooterDeserializerTest.class ) );
         suite.addTestSuite( ClasspathTest.class );
         suite.addTestSuite( PropertiesWrapperTest.class );
+        suite.addTestSuite( LegacyMasterProcessChannelDecoderTest.class );
+        suite.addTest( new JUnit4TestAdapter( LegacyMasterProcessChannelEncoderTest.class ) );
+        suite.addTestSuite( SurefireReflectorTest.class );
         return suite;
     }
 }
diff --git a/surefire-api/src/test/java/org/apache/maven/surefire/booter/NewClassLoaderRunner.java b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/NewClassLoaderRunner.java
similarity index 100%
rename from surefire-api/src/test/java/org/apache/maven/surefire/booter/NewClassLoaderRunner.java
rename to surefire-booter/src/test/java/org/apache/maven/surefire/booter/NewClassLoaderRunner.java
diff --git a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/SurefireReflectorTest.java b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/SurefireReflectorTest.java
new file mode 100644
index 0000000..9f7372d
--- /dev/null
+++ b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/SurefireReflectorTest.java
@@ -0,0 +1,408 @@
+package org.apache.maven.surefire.booter;
+
+/*
+ * 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 junit.framework.TestCase;
+import org.apache.maven.surefire.providerapi.ProviderParameters;
+import org.apache.maven.surefire.providerapi.SurefireProvider;
+import org.apache.maven.surefire.report.ReporterConfiguration;
+import org.apache.maven.surefire.report.ReporterFactory;
+import org.apache.maven.surefire.report.RunListener;
+import org.apache.maven.surefire.suite.RunResult;
+import org.apache.maven.surefire.testset.DirectoryScannerParameters;
+import org.apache.maven.surefire.testset.RunOrderParameters;
+import org.apache.maven.surefire.testset.TestArtifactInfo;
+import org.apache.maven.surefire.testset.TestListResolver;
+import org.apache.maven.surefire.testset.TestRequest;
+import org.apache.maven.surefire.util.RunOrder;
+
+import java.io.File;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+
+import static java.util.Arrays.asList;
+import static org.apache.maven.surefire.cli.CommandLineOption.LOGGING_LEVEL_DEBUG;
+import static org.apache.maven.surefire.cli.CommandLineOption.SHOW_ERRORS;
+
+/**
+ *
+ */
+public class SurefireReflectorTest
+        extends TestCase
+{
+    public void testShouldCreateFactoryWithoutException()
+    {
+        ReporterFactory factory = new ReporterFactory()
+        {
+            @Override
+            public RunListener createReporter()
+            {
+                return null;
+            }
+
+            @Override
+            public RunResult close()
+            {
+                return null;
+            }
+        };
+        ClassLoader cl = Thread.currentThread().getContextClassLoader();
+        SurefireReflector reflector = new SurefireReflector( cl );
+        BaseProviderFactory bpf = (BaseProviderFactory) reflector.createBooterConfiguration( cl, true );
+        bpf.setReporterFactory( factory );
+        assertNotNull( bpf.getReporterFactory() );
+        assertSame( factory, bpf.getReporterFactory() );
+    }
+
+    public void testSetDirectoryScannerParameters()
+    {
+        SurefireReflector surefireReflector = getReflector();
+        Object foo = getFoo();
+
+        DirectoryScannerParameters directoryScannerParameters =
+                new DirectoryScannerParameters( new File( "ABC" ), new ArrayList<String>(), new ArrayList<String>(),
+                        new ArrayList<String>(), false, "hourly" );
+        surefireReflector.setDirectoryScannerParameters( foo, directoryScannerParameters );
+        assertTrue( isCalled( foo ) );
+        assertNotNull( ( (Foo) foo ).getDirectoryScannerParameters() );
+    }
+
+    public void testNullSetDirectoryScannerParameters()
+    {
+        SurefireReflector surefireReflector = getReflector();
+        Object foo = getFoo();
+
+        surefireReflector.setDirectoryScannerParameters( foo, null );
+        assertTrue( isCalled( foo ) );
+        assertNull( ( (Foo) foo ).getDirectoryScannerParameters() );
+    }
+
+    public void testSetIfDirScannerAware()
+    {
+        SurefireReflector surefireReflector = getReflector();
+        Object foo = getFoo();
+
+        DirectoryScannerParameters directoryScannerParameters =
+            new DirectoryScannerParameters( new File( "ABC" ), new ArrayList<String>(), new ArrayList<String>(),
+                new ArrayList<String>(), false, "hourly" );
+        surefireReflector.setIfDirScannerAware( foo, directoryScannerParameters );
+        assertTrue( isCalled( foo ) );
+    }
+
+    public void testRunOrderParameters()
+    {
+        SurefireReflector surefireReflector = getReflector();
+        Object foo = getFoo();
+
+        RunOrderParameters runOrderParameters = new RunOrderParameters( RunOrder.DEFAULT, new File( "." ) );
+        surefireReflector.setRunOrderParameters( foo, runOrderParameters );
+        assertTrue( isCalled( foo ) );
+    }
+
+    public void testNullRunOrderParameters()
+    {
+        SurefireReflector surefireReflector = getReflector();
+        Object foo = getFoo();
+
+        surefireReflector.setRunOrderParameters( foo, null );
+        assertTrue( isCalled( foo ) );
+        assertNull( ( (Foo) foo ).getRunOrderCalculator() );
+    }
+
+    public void testTestSuiteDefinition()
+    {
+        SurefireReflector surefireReflector = getReflector();
+        Object foo = getFoo();
+
+        TestRequest testSuiteDefinition =
+                new TestRequest( asList( new File( "file1" ), new File( "file2" ) ),
+                        new File( "TestSOurce" ), new TestListResolver( "aUserRequestedTest#aMethodRequested" ) );
+        surefireReflector.setTestSuiteDefinition( foo, testSuiteDefinition );
+        assertTrue( isCalled( foo ) );
+        assertNotNull( ( (Foo) foo ).getTestRequest() );
+    }
+
+    public void testNullTestSuiteDefinition()
+    {
+        SurefireReflector surefireReflector = getReflector();
+        Object foo = getFoo();
+        surefireReflector.setTestSuiteDefinition( foo, null );
+        assertTrue( isCalled( foo ) );
+        assertNull( ( (Foo) foo ).getTestRequest() );
+    }
+
+    public void testProviderProperties()
+    {
+        SurefireReflector surefireReflector = getReflector();
+        Object foo = getFoo();
+
+        surefireReflector.setProviderProperties( foo, new HashMap<String, String>() );
+        assertTrue( isCalled( foo ) );
+    }
+
+    public void testReporterConfiguration()
+    {
+        SurefireReflector surefireReflector = getReflector();
+        Object foo = getFoo();
+
+        ReporterConfiguration reporterConfiguration = getReporterConfiguration();
+        surefireReflector.setReporterConfigurationAware( foo, reporterConfiguration );
+        assertTrue( isCalled( foo ) );
+    }
+
+    private ReporterConfiguration getReporterConfiguration()
+    {
+        return new ReporterConfiguration( new File( "CDE" ), true );
+    }
+
+    public void testTestClassLoader()
+    {
+        SurefireReflector surefireReflector = getReflector();
+        Object foo = getFoo();
+
+        surefireReflector.setTestClassLoader( foo, getClass().getClassLoader() );
+        assertTrue( isCalled( foo ) );
+    }
+
+    public void testTestClassLoaderAware()
+    {
+        SurefireReflector surefireReflector = getReflector();
+        Object foo = getFoo();
+
+        surefireReflector.setTestClassLoaderAware( foo, getClass().getClassLoader() );
+        assertTrue( isCalled( foo ) );
+    }
+
+    public void testArtifactInfo()
+    {
+        SurefireReflector surefireReflector = getReflector();
+        Object foo = getFoo();
+
+        TestArtifactInfo testArtifactInfo = new TestArtifactInfo( "12.3", "test" );
+        surefireReflector.setTestArtifactInfo( foo, testArtifactInfo );
+        assertTrue( isCalled( foo ) );
+    }
+
+    public void testNullArtifactInfo()
+    {
+        SurefireReflector surefireReflector = getReflector();
+        Object foo = getFoo();
+
+        surefireReflector.setTestArtifactInfo( foo, null );
+        assertTrue( isCalled( foo ) );
+        assertNull( ( (Foo) foo ).getTestArtifactInfo() );
+    }
+
+    public void testArtifactInfoAware()
+    {
+        SurefireReflector surefireReflector = getReflector();
+        Object foo = getFoo();
+
+        TestArtifactInfo testArtifactInfo = new TestArtifactInfo( "12.3", "test" );
+        surefireReflector.setTestArtifactInfoAware( foo, testArtifactInfo );
+        assertTrue( isCalled( foo ) );
+        assertEquals( testArtifactInfo.getClassifier(), "test" );
+        assertEquals( testArtifactInfo.getVersion(), "12.3" );
+    }
+
+    public void testReporterFactory()
+    {
+        SurefireReflector surefireReflector = getReflector();
+        Object foo = getFoo();
+
+        ReporterFactory reporterFactory = new ReporterFactory()
+        {
+            @Override
+            public RunListener createReporter()
+            {
+                return null;
+            }
+
+            @Override
+            public RunResult close()
+            {
+                return null;
+            }
+        };
+
+        surefireReflector.setReporterFactory( foo, reporterFactory );
+        assertTrue( isCalled( foo ) );
+    }
+
+    public void testReporterFactoryAware()
+    {
+        SurefireReflector surefireReflector = getReflector();
+        Object foo = getFoo();
+
+        ReporterFactory reporterFactory = new ReporterFactory()
+        {
+            @Override
+            public RunListener createReporter()
+            {
+                return null;
+            }
+
+            @Override
+            public RunResult close()
+            {
+                return null;
+            }
+        };
+
+        surefireReflector.setReporterFactoryAware( foo, reporterFactory );
+        assertTrue( isCalled( foo ) );
+        assertSame( ( (Foo) foo ).getReporterFactory(), reporterFactory );
+    }
+
+    @SuppressWarnings( "checkstyle:magicnumber" )
+    public void testConvertIfRunResult()
+    {
+        RunResult runResult = new RunResult( 20, 1, 2, 3, 4, "IOException", true );
+        SurefireReflector reflector = new SurefireReflector( Thread.currentThread().getContextClassLoader() );
+        RunResult obj = (RunResult) reflector.convertIfRunResult( runResult );
+        assertEquals( obj.getCompletedCount(), 20 );
+        assertEquals( obj.getErrors(), 1 );
+        assertEquals( obj.getFailures(), 2 );
+        assertEquals( obj.getSkipped(), 3 );
+        assertFalse( obj.isErrorFree() );
+        assertFalse( obj.isInternalError() );
+        assertEquals( obj.getFailsafeCode(), (Integer) RunResult.FAILURE );
+
+        assertNull( reflector.convertIfRunResult( null ) );
+        assertEquals( reflector.convertIfRunResult( "" ), "" );
+    }
+
+    public void testInstantiateProvider()
+    {
+        SurefireReflector reflector = new SurefireReflector( Thread.currentThread().getContextClassLoader() );
+        Object booterParams = getFoo();
+        Object provider = reflector.instantiateProvider( DummyProvider.class.getName(), booterParams );
+        assertNotNull( provider );
+        assertEquals( provider.getClass(), DummyProvider.class );
+    }
+
+    public void testSetMainCliOptions()
+    {
+        SurefireReflector reflector = new SurefireReflector( Thread.currentThread().getContextClassLoader() );
+        Object booterParams = getFoo();
+        reflector.setMainCliOptions( booterParams, asList( SHOW_ERRORS, LOGGING_LEVEL_DEBUG ) );
+        assertEquals( ( (BaseProviderFactory) booterParams ).getMainCliOptions().size(), 2 );
+        assertEquals( ( (BaseProviderFactory) booterParams ).getMainCliOptions().get( 0 ), SHOW_ERRORS );
+        assertEquals( ( (BaseProviderFactory) booterParams ).getMainCliOptions().get( 1 ), LOGGING_LEVEL_DEBUG );
+    }
+
+    public void testSetSkipAfterFailureCount()
+    {
+        SurefireReflector reflector = new SurefireReflector( Thread.currentThread().getContextClassLoader() );
+        Foo booterParams = (Foo) getFoo();
+        assertEquals( booterParams.getSkipAfterFailureCount(), 0 );
+        reflector.setSkipAfterFailureCount( booterParams, 5 );
+        assertEquals( booterParams.getSkipAfterFailureCount(), 5 );
+    }
+
+    @SuppressWarnings( "checkstyle:magicnumber" )
+    public void testSetSystemExitTimeout()
+    {
+        SurefireReflector reflector = new SurefireReflector( Thread.currentThread().getContextClassLoader() );
+        Foo booterParams = (Foo) getFoo();
+        assertNull( booterParams.getSystemExitTimeout() );
+        reflector.setSystemExitTimeout( booterParams, 60 );
+        assertEquals( booterParams.getSystemExitTimeout(), (Integer) 60 );
+    }
+
+    public void testSetTestSuiteDefinitionAware()
+    {
+        SurefireReflector reflector = new SurefireReflector( Thread.currentThread().getContextClassLoader() );
+        Foo booterParams = (Foo) getFoo();
+        TestRequest request = new TestRequest( Collections.emptyList(), null, null );
+        reflector.setTestSuiteDefinitionAware( booterParams, request );
+        assertTrue( booterParams.isCalled() );
+        assertNotNull( booterParams.getTestRequest() );
+        assertTrue( booterParams.getTestRequest().getSuiteXmlFiles().isEmpty() );
+        assertNull( booterParams.getTestRequest().getTestSourceDirectory() );
+        assertNull( booterParams.getTestRequest().getTestListResolver() );
+        assertEquals( booterParams.getTestRequest().getRerunFailingTestsCount(), 0 );
+    }
+
+    public void testSetProviderPropertiesAware()
+    {
+        SurefireReflector reflector = new SurefireReflector( Thread.currentThread().getContextClassLoader() );
+        Foo booterParams = (Foo) getFoo();
+        reflector.setProviderPropertiesAware( booterParams, Collections.singletonMap( "k", "v" ) );
+        assertTrue( booterParams.isCalled() );
+        assertNotNull( booterParams.getProviderProperties() );
+        assertEquals( booterParams.getProviderProperties().size(), 1 );
+        assertEquals( booterParams.getProviderProperties().get( "k" ), "v" );
+    }
+
+    private SurefireReflector getReflector()
+    {
+        return new SurefireReflector( getClass().getClassLoader() );
+    }
+
+    private Object getFoo()
+    { // Todo: Setup a different classloader so we can really test crossing
+        return new Foo();
+    }
+
+    private Boolean isCalled( Object foo )
+    {
+        final Method isCalled;
+        try
+        {
+            isCalled = foo.getClass().getMethod( "isCalled" );
+            return (Boolean) isCalled.invoke( foo );
+        }
+        catch ( ReflectiveOperationException e )
+        {
+            throw new RuntimeException( e );
+        }
+    }
+
+    /**
+     *
+     */
+    public static final class DummyProvider implements SurefireProvider
+    {
+        public DummyProvider( ProviderParameters providerParameters )
+        {
+        }
+
+        @Override
+        public Iterable<Class<?>> getSuites()
+        {
+            return null;
+        }
+
+        @Override
+        public RunResult invoke( Object forkTestSet )
+        {
+            return null;
+        }
+
+        @Override
+        public void cancel()
+        {
+
+        }
+    }
+}
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
new file mode 100644
index 0000000..13efee2
--- /dev/null
+++ b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelDecoderTest.java
@@ -0,0 +1,150 @@
+package org.apache.maven.surefire.booter.spi;
+
+/*
+ * 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 junit.framework.TestCase;
+import org.apache.maven.surefire.booter.Command;
+import org.apache.maven.surefire.booter.MasterProcessCommand;
+import org.apache.maven.surefire.booter.Shutdown;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import static org.apache.maven.surefire.booter.MasterProcessCommand.BYE_ACK;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.fest.assertions.Assertions.assertThat;
+
+/**
+ * Tests for {@link LegacyMasterProcessChannelDecoder}.
+ */
+public class LegacyMasterProcessChannelDecoderTest
+    extends TestCase
+{
+    public void testDataToByteArrayAndBack() throws IOException
+    {
+        for ( MasterProcessCommand commandType : MasterProcessCommand.values() )
+        {
+            switch ( commandType )
+            {
+                case RUN_CLASS:
+                    assertEquals( String.class, commandType.getDataType() );
+                    byte[] encoded = commandType.encode( "pkg.Test" );
+                    assertThat( new String( encoded ) )
+                            .isEqualTo( ":maven-surefire-command:run-testclass:pkg.Test:" );
+                    byte[] line = addNL( encoded, '\n' );
+                    InputStream is = new ByteArrayInputStream( line );
+                    LegacyMasterProcessChannelDecoder decoder = new LegacyMasterProcessChannelDecoder( is );
+                    Command command = decoder.decode();
+                    assertThat( command.getCommandType(), is( commandType ) );
+                    assertThat( command.getData(), is( "pkg.Test" ) );
+                    break;
+                case TEST_SET_FINISHED:
+                    assertThat( commandType ).isSameAs( Command.TEST_SET_FINISHED.getCommandType() );
+                    assertEquals( Void.class, commandType.getDataType() );
+                    encoded = commandType.encode();
+                    assertThat( new String( encoded ) )
+                            .isEqualTo( ":maven-surefire-command:testset-finished:" );
+                    is = new ByteArrayInputStream( encoded );
+                    decoder = new LegacyMasterProcessChannelDecoder( is );
+                    command = decoder.decode();
+                    assertThat( command.getCommandType(), is( commandType ) );
+                    assertNull( command.getData() );
+                    break;
+                case SKIP_SINCE_NEXT_TEST:
+                    assertThat( commandType ).isSameAs( Command.SKIP_SINCE_NEXT_TEST.getCommandType() );
+                    assertEquals( Void.class, commandType.getDataType() );
+                    encoded = commandType.encode();
+                    assertThat( new String( encoded ) )
+                            .isEqualTo( ":maven-surefire-command:skip-since-next-test:" );
+                    is = new ByteArrayInputStream( encoded );
+                    decoder = new LegacyMasterProcessChannelDecoder( is );
+                    command = decoder.decode();
+                    assertThat( command.getCommandType(), is( commandType ) );
+                    assertNull( command.getData() );
+                    break;
+                case SHUTDOWN:
+                    assertEquals( String.class, commandType.getDataType() );
+                    encoded = commandType.encode( Shutdown.EXIT.name() );
+                    assertThat( new String( encoded ) )
+                            .isEqualTo( ":maven-surefire-command:shutdown:EXIT:" );
+                    is = new ByteArrayInputStream( encoded );
+                    decoder = new LegacyMasterProcessChannelDecoder( is );
+                    command = decoder.decode();
+                    assertThat( command.getCommandType(), is( commandType ) );
+                    assertThat( command.getData(), is( "EXIT" ) );
+                    break;
+                case NOOP:
+                    assertThat( commandType ).isSameAs( Command.NOOP.getCommandType() );
+                    assertEquals( Void.class, commandType.getDataType() );
+                    encoded = commandType.encode();
+                    assertThat( new String( encoded ) )
+                            .isEqualTo( ":maven-surefire-command:noop:" );
+                    is = new ByteArrayInputStream( encoded );
+                    decoder = new LegacyMasterProcessChannelDecoder( is );
+                    command = decoder.decode();
+                    assertThat( command.getCommandType(), is( commandType ) );
+                    assertNull( command.getData() );
+                    break;
+                case BYE_ACK:
+                    assertThat( commandType ).isSameAs( Command.BYE_ACK.getCommandType() );
+                    assertEquals( Void.class, commandType.getDataType() );
+                    encoded = commandType.encode();
+                    assertThat( new String( encoded ) )
+                            .isEqualTo( ":maven-surefire-command:bye-ack:" );
+                    is = new ByteArrayInputStream( encoded );
+                    decoder = new LegacyMasterProcessChannelDecoder( is );
+                    command = decoder.decode();
+                    assertThat( command.getCommandType(), is( commandType ) );
+                    assertNull( command.getData() );
+                    break;
+                default:
+                    fail();
+            }
+        }
+    }
+
+    public void testShouldDecodeTwoCommands() throws IOException
+    {
+        String cmd = ":maven-surefire-command:bye-ack\n:maven-surefire-command:bye-ack:";
+        InputStream is = new ByteArrayInputStream( cmd.getBytes() );
+        LegacyMasterProcessChannelDecoder decoder = new LegacyMasterProcessChannelDecoder( is );
+
+        Command command = decoder.decode();
+        assertThat( command.getCommandType() ).isEqualTo( BYE_ACK );
+        assertThat( command.getData() ).isNull();
+
+        command = decoder.decode();
+        assertThat( command.getCommandType() ).isEqualTo( BYE_ACK );
+        assertThat( command.getData() ).isNull();
+    }
+
+    private static byte[] addNL( byte[] encoded, char... newLines )
+    {
+        byte[] line = new byte[encoded.length + newLines.length];
+        System.arraycopy( encoded, 0, line, 0, encoded.length );
+        for ( int i = encoded.length, j = 0; i < line.length; i++, j++ )
+        {
+            line[i] = (byte) newLines[j];
+        }
+        return line;
+    }
+}
diff --git a/surefire-api/src/test/java/org/apache/maven/surefire/booter/ForkedChannelEncoderTest.java b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelEncoderTest.java
similarity index 84%
rename from surefire-api/src/test/java/org/apache/maven/surefire/booter/ForkedChannelEncoderTest.java
rename to surefire-booter/src/test/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelEncoderTest.java
index 1dcf4d9..eaafd6e 100644
--- a/surefire-api/src/test/java/org/apache/maven/surefire/booter/ForkedChannelEncoderTest.java
+++ b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelEncoderTest.java
@@ -1,4 +1,4 @@
-package org.apache.maven.surefire.booter;
+package org.apache.maven.surefire.booter.spi;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -36,40 +36,41 @@ import java.util.Map;
 
 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.shared.codec.binary.Base64.encodeBase64String;
-import static org.apache.maven.surefire.booter.ForkedChannelEncoder.encode;
-import static org.apache.maven.surefire.booter.ForkedChannelEncoder.encodeHeader;
-import static org.apache.maven.surefire.booter.ForkedChannelEncoder.encodeMessage;
-import static org.apache.maven.surefire.booter.ForkedChannelEncoder.encodeOpcode;
+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;
+import static org.apache.maven.surefire.booter.ForkedProcessEvent.MAGIC_NUMBER_DELIMITED;
 import static org.apache.maven.surefire.report.RunMode.NORMAL_RUN;
 import static org.fest.assertions.Assertions.assertThat;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
 /**
- * Test for {@link ForkedChannelEncoder}.
+ * Test for {@link LegacyMasterProcessChannelEncoder}.
  *
  * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
  * @since 3.0.0-M4
  */
-public class ForkedChannelEncoderTest
+public class LegacyMasterProcessChannelEncoderTest
 {
     private static final int ELAPSED_TIME = 102;
 
     @Test
     public void shouldBeFailSafe()
     {
-        assertThat( ForkedChannelEncoder.toBase64( null ) ).isEqualTo( "-" );
-        assertThat( ForkedChannelEncoder.toBase64( "" ) ).isEqualTo( "" );
+        assertThat( LegacyMasterProcessChannelEncoder.toBase64( null ) ).isEqualTo( "-" );
+        assertThat( LegacyMasterProcessChannelEncoder.toBase64( "" ) ).isEqualTo( "" );
     }
 
     @Test
     public void shouldHaveSystemProperty()
     {
         StringBuilder actualEncoded = encode( BOOTERCODE_SYSPROPS, NORMAL_RUN, "arg1", "arg2" );
-        String expected = MAGIC_NUMBER + BOOTERCODE_SYSPROPS.getOpcode() + ":normal-run" + ":UTF-8:YXJnMQ==:YXJnMg==";
+        String expected = MAGIC_NUMBER_DELIMITED + BOOTERCODE_SYSPROPS.getOpcode()
+            + ":normal-run:UTF-8:YXJnMQ==:YXJnMg==";
 
         assertThat( actualEncoded.toString() )
                 .isEqualTo( expected );
@@ -179,7 +180,7 @@ public class ForkedChannelEncoderTest
 
         StringBuilder encode = encode( "X", "normal-run", reportEntry, false );
         assertThat( encode.toString() )
-                .isEqualTo( ":maven:surefire:std:out:X:normal-run:UTF-8:"
+                .isEqualTo( ":maven-surefire-event:X:normal-run:UTF-8:"
                                 + encodedSourceName
                                 + ":"
                                 + "-"
@@ -204,7 +205,7 @@ public class ForkedChannelEncoderTest
 
         encode = encode( "X", "normal-run", reportEntry, true );
         assertThat( encode.toString() )
-                .isEqualTo( ":maven:surefire:std:out:X:normal-run:UTF-8:"
+                .isEqualTo( ":maven-surefire-event:X:normal-run:UTF-8:"
                                 + encodedSourceName
                                 + ":"
                                 + "-"
@@ -228,12 +229,12 @@ public class ForkedChannelEncoderTest
                 );
 
         Stream out = Stream.newStream();
-        ForkedChannelEncoder forkedChannelEncoder = new ForkedChannelEncoder( out );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( out );
 
-        forkedChannelEncoder.testSetStarting( reportEntry, true );
+        encoder.testSetStarting( reportEntry, true );
         LineNumberReader printedLines = out.newReader( UTF_8 );
         assertThat( printedLines.readLine() )
-                .isEqualTo( ":maven:surefire:std:out:testset-starting:normal-run:UTF-8:"
+                .isEqualTo( ":maven-surefire-event:testset-starting:normal-run:UTF-8:"
                                     + encodedSourceName
                                     + ":"
                                     + "-"
@@ -258,12 +259,12 @@ public class ForkedChannelEncoderTest
         assertThat( printedLines.readLine() ).isNull();
 
         out = Stream.newStream();
-        forkedChannelEncoder = new ForkedChannelEncoder( out );
+        encoder = new LegacyMasterProcessChannelEncoder( out );
 
-        forkedChannelEncoder.testSetStarting( reportEntry, false );
+        encoder.testSetStarting( reportEntry, false );
         printedLines = out.newReader( UTF_8 );
         assertThat( printedLines.readLine() )
-                .isEqualTo( ":maven:surefire:std:out:testset-starting:normal-run:UTF-8:"
+                .isEqualTo( ":maven-surefire-event:testset-starting:normal-run:UTF-8:"
                                     + encodedSourceName
                                     + ":"
                                     + "-"
@@ -325,12 +326,12 @@ public class ForkedChannelEncoderTest
         String encodedMessage = encodeBase64String( toArray( UTF_8.encode( reportEntry.getMessage() ) ) );
 
         Stream out = Stream.newStream();
-        ForkedChannelEncoder forkedChannelEncoder = new ForkedChannelEncoder( out );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( out );
 
-        forkedChannelEncoder.testSetCompleted( reportEntry, false );
+        encoder.testSetCompleted( reportEntry, false );
         LineNumberReader printedLines = out.newReader( UTF_8 );
         assertThat( printedLines.readLine() )
-                .isEqualTo( ":maven:surefire:std:out:testset-completed:normal-run:UTF-8:"
+                .isEqualTo( ":maven-surefire-event:testset-completed:normal-run:UTF-8:"
                         + encodedSourceName
                         + ":"
                         + "-"
@@ -392,12 +393,12 @@ public class ForkedChannelEncoderTest
         String encodedMessage = encodeBase64String( toArray( UTF_8.encode( reportEntry.getMessage() ) ) );
 
         Stream out = Stream.newStream();
-        ForkedChannelEncoder forkedChannelEncoder = new ForkedChannelEncoder( out );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( out );
 
-        forkedChannelEncoder.testStarting( reportEntry, true );
+        encoder.testStarting( reportEntry, true );
         LineNumberReader printedLines = out.newReader( UTF_8 );
         assertThat( printedLines.readLine() )
-                .isEqualTo( ":maven:surefire:std:out:test-starting:normal-run:UTF-8:"
+                .isEqualTo( ":maven-surefire-event:test-starting:normal-run:UTF-8:"
                         + encodedSourceName
                         + ":"
                         + "-"
@@ -459,12 +460,12 @@ public class ForkedChannelEncoderTest
         String encodedMessage = encodeBase64String( toArray( UTF_8.encode( reportEntry.getMessage() ) ) );
 
         Stream out = Stream.newStream();
-        ForkedChannelEncoder forkedChannelEncoder = new ForkedChannelEncoder( out );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( out );
 
-        forkedChannelEncoder.testSucceeded( reportEntry, true );
+        encoder.testSucceeded( reportEntry, true );
         LineNumberReader printedLines = out.newReader( UTF_8 );
         assertThat( printedLines.readLine() )
-                .isEqualTo( ":maven:surefire:std:out:test-succeeded:normal-run:UTF-8:"
+                .isEqualTo( ":maven-surefire-event:test-succeeded:normal-run:UTF-8:"
                         + encodedSourceName
                         + ":"
                         + "-"
@@ -526,12 +527,12 @@ public class ForkedChannelEncoderTest
         String encodedMessage = encodeBase64String( toArray( UTF_8.encode( reportEntry.getMessage() ) ) );
 
         Stream out = Stream.newStream();
-        ForkedChannelEncoder forkedChannelEncoder = new ForkedChannelEncoder( out );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( out );
 
-        forkedChannelEncoder.testFailed( reportEntry, false );
+        encoder.testFailed( reportEntry, false );
         LineNumberReader printedLines = out.newReader( UTF_8 );
         assertThat( printedLines.readLine() )
-                .isEqualTo( ":maven:surefire:std:out:test-failed:normal-run:UTF-8:"
+                .isEqualTo( ":maven-surefire-event:test-failed:normal-run:UTF-8:"
                         + encodedSourceName
                         + ":"
                         + "-"
@@ -592,12 +593,12 @@ public class ForkedChannelEncoderTest
         String encodedMessage = encodeBase64String( toArray( UTF_8.encode( reportEntry.getMessage() ) ) );
 
         Stream out = Stream.newStream();
-        ForkedChannelEncoder forkedChannelEncoder = new ForkedChannelEncoder( out );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( out );
 
-        forkedChannelEncoder.testSkipped( reportEntry, false );
+        encoder.testSkipped( reportEntry, false );
         LineNumberReader printedLines = out.newReader( UTF_8 );
         assertThat( printedLines.readLine() )
-                .isEqualTo( ":maven:surefire:std:out:test-skipped:normal-run:UTF-8:"
+                .isEqualTo( ":maven-surefire-event:test-skipped:normal-run:UTF-8:"
                         + encodedSourceName
                         + ":"
                         + "-"
@@ -657,12 +658,12 @@ public class ForkedChannelEncoderTest
         String encodedMessage = encodeBase64String( toArray( UTF_8.encode( reportEntry.getMessage() ) ) );
 
         Stream out = Stream.newStream();
-        ForkedChannelEncoder forkedChannelEncoder = new ForkedChannelEncoder( out );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( out );
 
-        forkedChannelEncoder.testError( reportEntry, false );
+        encoder.testError( reportEntry, false );
         LineNumberReader printedLines = out.newReader( UTF_8 );
         assertThat( printedLines.readLine() )
-                .isEqualTo( ":maven:surefire:std:out:test-error:normal-run:UTF-8:"
+                .isEqualTo( ":maven-surefire-event:test-error:normal-run:UTF-8:"
                         + encodedSourceName
                         + ":"
                         + "-"
@@ -720,12 +721,12 @@ public class ForkedChannelEncoderTest
         String encodedMessage = encodeBase64String( toArray( UTF_8.encode( reportEntry.getMessage() ) ) );
 
         Stream out = Stream.newStream();
-        ForkedChannelEncoder forkedChannelEncoder = new ForkedChannelEncoder( out );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( out );
 
-        forkedChannelEncoder.testAssumptionFailure( reportEntry, false );
+        encoder.testAssumptionFailure( reportEntry, false );
         LineNumberReader printedLines = out.newReader( UTF_8 );
         assertThat( printedLines.readLine() )
-                .isEqualTo( ":maven:surefire:std:out:test-assumption-failure:normal-run:UTF-8:"
+                .isEqualTo( ":maven-surefire-event:test-assumption-failure:normal-run:UTF-8:"
                         + encodedSourceName
                         + ":"
                         + "-"
@@ -754,12 +755,12 @@ public class ForkedChannelEncoderTest
     public void testBye() throws IOException
     {
         Stream out = Stream.newStream();
-        ForkedChannelEncoder forkedChannelEncoder = new ForkedChannelEncoder( out );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( out );
 
-        forkedChannelEncoder.bye();
+        encoder.bye();
         LineNumberReader printedLines = out.newReader( UTF_8 );
         assertThat( printedLines.readLine() )
-                .isEqualTo( ":maven:surefire:std:out:bye" );
+                .isEqualTo( ":maven-surefire-event:bye" );
         assertThat( printedLines.readLine() ).isNull();
     }
 
@@ -767,12 +768,12 @@ public class ForkedChannelEncoderTest
     public void testStopOnNextTest() throws IOException
     {
         Stream out = Stream.newStream();
-        ForkedChannelEncoder forkedChannelEncoder = new ForkedChannelEncoder( out );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( out );
 
-        forkedChannelEncoder.stopOnNextTest();
+        encoder.stopOnNextTest();
         LineNumberReader printedLines = out.newReader( UTF_8 );
         assertThat( printedLines.readLine() )
-                .isEqualTo( ":maven:surefire:std:out:stop-on-next-test" );
+                .isEqualTo( ":maven-surefire-event:stop-on-next-test" );
         assertThat( printedLines.readLine() ).isNull();
     }
 
@@ -780,12 +781,12 @@ public class ForkedChannelEncoderTest
     public void testAcquireNextTest() throws IOException
     {
         Stream out = Stream.newStream();
-        ForkedChannelEncoder forkedChannelEncoder = new ForkedChannelEncoder( out );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( out );
 
-        forkedChannelEncoder.acquireNextTest();
+        encoder.acquireNextTest();
         LineNumberReader printedLines = out.newReader( UTF_8 );
         assertThat( printedLines.readLine() )
-                .isEqualTo( ":maven:surefire:std:out:next-test" );
+                .isEqualTo( ":maven-surefire-event:next-test" );
         assertThat( printedLines.readLine() ).isNull();
     }
 
@@ -794,38 +795,38 @@ public class ForkedChannelEncoderTest
     {
         StringBuilder encoded = encodeOpcode( "some-opcode", "normal-run" );
         assertThat( encoded.toString() )
-                .isEqualTo( ":maven:surefire:std:out:some-opcode:normal-run" );
+                .isEqualTo( ":maven-surefire-event:some-opcode:normal-run" );
 
         encoded = encodeHeader( "some-opcode", "normal-run" );
         assertThat( encoded.toString() )
-                .isEqualTo( ":maven:surefire:std:out:some-opcode:normal-run:UTF-8" );
+                .isEqualTo( ":maven-surefire-event:some-opcode:normal-run:UTF-8" );
 
         encoded = encodeMessage( "some-opcode", "normal-run", "msg" );
         assertThat( encoded.toString() )
-                .isEqualTo( ":maven:surefire:std:out:some-opcode:normal-run:UTF-8:msg" );
+                .isEqualTo( ":maven-surefire-event:some-opcode:normal-run:UTF-8:msg" );
 
         Stream out = Stream.newStream();
-        ForkedChannelEncoder encoder = new ForkedChannelEncoder( out );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( out );
         encoded = encoder.print( "some-opcode", "msg" );
         assertThat( encoded.toString() )
-                .isEqualTo( ":maven:surefire:std:out:some-opcode:UTF-8:bXNn" );
+                .isEqualTo( ":maven-surefire-event:some-opcode:UTF-8:bXNn" );
 
         encoded = encoder.print( "some-opcode", new String[] { null } );
         assertThat( encoded.toString() )
-                .isEqualTo( ":maven:surefire:std:out:some-opcode:UTF-8:-" );
+                .isEqualTo( ":maven-surefire-event:some-opcode:UTF-8:-" );
     }
 
     @Test
     public void testConsoleInfo()
     {
         Stream out = Stream.newStream();
-        ForkedChannelEncoder forkedChannelEncoder = new ForkedChannelEncoder( out );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( out );
 
-        forkedChannelEncoder.consoleInfoLog( "msg" );
+        encoder.consoleInfoLog( "msg" );
 
         String encoded = new String( out.toByteArray(), UTF_8 );
 
-        String expected = ":maven:surefire:std:out:console-info-log:UTF-8:"
+        String expected = ":maven-surefire-event:console-info-log:UTF-8:"
                                   + encodeBase64String( toArray( UTF_8.encode( "msg" ) ) )
                                   + "\n";
 
@@ -837,13 +838,13 @@ public class ForkedChannelEncoderTest
     public void testConsoleError()
     {
         Stream out = Stream.newStream();
-        ForkedChannelEncoder forkedChannelEncoder = new ForkedChannelEncoder( out );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( out );
 
-        forkedChannelEncoder.consoleErrorLog( "msg" );
+        encoder.consoleErrorLog( "msg" );
 
         String encoded = new String( out.toByteArray(), UTF_8 );
 
-        String expected = ":maven:surefire:std:out:console-error-log:UTF-8:"
+        String expected = ":maven-surefire-event:console-error-log:UTF-8:"
                 + encodeBase64String( toArray( UTF_8.encode( "msg" ) ) )
                 + "\n";
 
@@ -855,12 +856,12 @@ public class ForkedChannelEncoderTest
     public void testConsoleErrorLog1() throws IOException
     {
         Stream out = Stream.newStream();
-        ForkedChannelEncoder forkedChannelEncoder = new ForkedChannelEncoder( out );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( out );
 
-        forkedChannelEncoder.consoleErrorLog( new Exception( "msg" ) );
+        encoder.consoleErrorLog( new Exception( "msg" ) );
         LineNumberReader printedLines = out.newReader( UTF_8 );
         assertThat( printedLines.readLine() )
-                .startsWith( ":maven:surefire:std:out:console-error-log:UTF-8:bXNn:-:" );
+                .startsWith( ":maven-surefire-event:console-error-log:UTF-8:bXNn:-:" );
         assertThat( printedLines.readLine() ).isNull();
     }
 
@@ -868,12 +869,12 @@ public class ForkedChannelEncoderTest
     public void testConsoleErrorLog2() throws IOException
     {
         Stream out = Stream.newStream();
-        ForkedChannelEncoder forkedChannelEncoder = new ForkedChannelEncoder( out );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( out );
 
-        forkedChannelEncoder.consoleErrorLog( "msg2", new Exception( "msg" ) );
+        encoder.consoleErrorLog( "msg2", new Exception( "msg" ) );
         LineNumberReader printedLines = out.newReader( UTF_8 );
         assertThat( printedLines.readLine() )
-                .startsWith( ":maven:surefire:std:out:console-error-log:UTF-8:bXNnMg==:-:" );
+                .startsWith( ":maven-surefire-event:console-error-log:UTF-8:bXNnMg==:-:" );
         assertThat( printedLines.readLine() ).isNull();
     }
 
@@ -881,7 +882,7 @@ public class ForkedChannelEncoderTest
     public void testConsoleErrorLog3() throws IOException
     {
         Stream out = Stream.newStream();
-        ForkedChannelEncoder forkedChannelEncoder = new ForkedChannelEncoder( out );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( out );
 
         StackTraceWriter stackTraceWriter = mock( StackTraceWriter.class );
         when( stackTraceWriter.getThrowable() ).thenReturn( new SafeThrowable( "1" ) );
@@ -889,10 +890,10 @@ public class ForkedChannelEncoderTest
         when( stackTraceWriter.writeTraceToString() ).thenReturn( "3" );
         when( stackTraceWriter.writeTrimmedTraceToString() ).thenReturn( "4" );
 
-        forkedChannelEncoder.consoleErrorLog( stackTraceWriter, true );
+        encoder.consoleErrorLog( stackTraceWriter, true );
         LineNumberReader printedLines = out.newReader( UTF_8 );
         assertThat( printedLines.readLine() )
-                .startsWith( ":maven:surefire:std:out:console-error-log:UTF-8:MQ==:Mg==:NA==" );
+                .startsWith( ":maven-surefire-event:console-error-log:UTF-8:MQ==:Mg==:NA==" );
         assertThat( printedLines.readLine() ).isNull();
     }
 
@@ -900,13 +901,13 @@ public class ForkedChannelEncoderTest
     public void testConsoleDebug()
     {
         Stream out = Stream.newStream();
-        ForkedChannelEncoder forkedChannelEncoder = new ForkedChannelEncoder( out );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( out );
 
-        forkedChannelEncoder.consoleDebugLog( "msg" );
+        encoder.consoleDebugLog( "msg" );
 
         String encoded = new String( out.toByteArray(), UTF_8 );
 
-        String expected = ":maven:surefire:std:out:console-debug-log:UTF-8:"
+        String expected = ":maven-surefire-event:console-debug-log:UTF-8:"
                                   + encodeBase64String( toArray( UTF_8.encode( "msg" ) ) )
                                   + "\n";
 
@@ -918,13 +919,13 @@ public class ForkedChannelEncoderTest
     public void testConsoleWarning()
     {
         Stream out = Stream.newStream();
-        ForkedChannelEncoder forkedChannelEncoder = new ForkedChannelEncoder( out );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( out );
 
-        forkedChannelEncoder.consoleWarningLog( "msg" );
+        encoder.consoleWarningLog( "msg" );
 
         String encoded = new String( out.toByteArray(), UTF_8 );
 
-        String expected = ":maven:surefire:std:out:console-warning-log:UTF-8:"
+        String expected = ":maven-surefire-event:console-warning-log:UTF-8:"
                                   + encodeBase64String( toArray( UTF_8.encode( "msg" ) ) )
                                   + "\n";
 
@@ -936,11 +937,11 @@ public class ForkedChannelEncoderTest
     public void testStdOutStream() throws IOException
     {
         Stream out = Stream.newStream();
-        ForkedChannelEncoder forkedChannelEncoder = new ForkedChannelEncoder( out );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( out );
 
-        forkedChannelEncoder.stdOut( "msg", false );
+        encoder.stdOut( "msg", false );
 
-        String expected = ":maven:surefire:std:out:std-out-stream:normal-run:UTF-8:bXNn";
+        String expected = ":maven-surefire-event:std-out-stream:normal-run:UTF-8:bXNn";
 
         LineNumberReader printedLines = out.newReader( UTF_8 );
         assertThat( printedLines.readLine() )
@@ -953,11 +954,11 @@ public class ForkedChannelEncoderTest
     public void testStdOutStreamLn() throws IOException
     {
         Stream out = Stream.newStream();
-        ForkedChannelEncoder forkedChannelEncoder = new ForkedChannelEncoder( out );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( out );
 
-        forkedChannelEncoder.stdOut( "msg", true );
+        encoder.stdOut( "msg", true );
 
-        String expected = ":maven:surefire:std:out:std-out-stream-new-line:normal-run:UTF-8:bXNn";
+        String expected = ":maven-surefire-event:std-out-stream-new-line:normal-run:UTF-8:bXNn";
 
         LineNumberReader printedLines = out.newReader( UTF_8 );
         assertThat( printedLines.readLine() )
@@ -970,11 +971,11 @@ public class ForkedChannelEncoderTest
     public void testStdErrStream() throws IOException
     {
         Stream out = Stream.newStream();
-        ForkedChannelEncoder forkedChannelEncoder = new ForkedChannelEncoder( out );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( out );
 
-        forkedChannelEncoder.stdErr( "msg", false );
+        encoder.stdErr( "msg", false );
 
-        String expected = ":maven:surefire:std:out:std-err-stream:normal-run:UTF-8:bXNn";
+        String expected = ":maven-surefire-event:std-err-stream:normal-run:UTF-8:bXNn";
 
         LineNumberReader printedLines = out.newReader( UTF_8 );
         assertThat( printedLines.readLine() )
@@ -987,11 +988,11 @@ public class ForkedChannelEncoderTest
     public void testStdErrStreamLn() throws IOException
     {
         Stream out = Stream.newStream();
-        ForkedChannelEncoder forkedChannelEncoder = new ForkedChannelEncoder( out );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( out );
 
-        forkedChannelEncoder.stdErr( "msg", true );
+        encoder.stdErr( "msg", true );
 
-        String expected = ":maven:surefire:std:out:std-err-stream-new-line:normal-run:UTF-8:bXNn";
+        String expected = ":maven-surefire-event:std-err-stream-new-line:normal-run:UTF-8:bXNn";
 
         LineNumberReader printedLines = out.newReader( UTF_8 );
         assertThat( printedLines.readLine() )
@@ -1005,11 +1006,11 @@ public class ForkedChannelEncoderTest
     public void shouldCountSameNumberOfSystemProperties() throws IOException
     {
         Stream out = Stream.newStream();
-        ForkedChannelEncoder forkedChannelEncoder = new ForkedChannelEncoder( out );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( out );
 
         Map<String, String> sysProps = ObjectUtils.systemProps();
         int expectedSize = sysProps.size();
-        forkedChannelEncoder.sendSystemProperties( sysProps );
+        encoder.sendSystemProperties( sysProps );
 
         LineNumberReader printedLines = out.newReader( UTF_8 );
 
@@ -1017,7 +1018,7 @@ public class ForkedChannelEncoderTest
         for ( String line; ( line = printedLines.readLine() ) != null; size++ )
         {
             assertThat( line )
-                    .startsWith( ":maven:surefire:std:out:sys-prop:normal-run:UTF-8:" );
+                    .startsWith( ":maven-surefire-event:sys-prop:normal-run:UTF-8:" );
         }
 
         assertThat( size )
@@ -1029,17 +1030,17 @@ public class ForkedChannelEncoderTest
     {
         Stream out = Stream.newStream();
 
-        ForkedChannelEncoder forkedChannelEncoder = new ForkedChannelEncoder( out );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( out );
         StackTraceWriter stackTraceWriter = mock( StackTraceWriter.class );
         when( stackTraceWriter.getThrowable() ).thenReturn( new SafeThrowable( "1" ) );
         when( stackTraceWriter.smartTrimmedStackTrace() ).thenReturn( "2" );
         when( stackTraceWriter.writeTraceToString() ).thenReturn( "3" );
         when( stackTraceWriter.writeTrimmedTraceToString() ).thenReturn( "4" );
-        forkedChannelEncoder.sendExitEvent( stackTraceWriter, false );
+        encoder.sendExitEvent( stackTraceWriter, false );
 
         LineNumberReader printedLines = out.newReader( UTF_8 );
         assertThat( printedLines.readLine() )
-                .startsWith( ":maven:surefire:std:out:jvm-exit-error:UTF-8:MQ==:Mg==:Mw==" );
+                .startsWith( ":maven-surefire-event:jvm-exit-error:UTF-8:MQ==:Mg==:Mw==" );
     }
 
     @Test
@@ -1047,17 +1048,17 @@ public class ForkedChannelEncoderTest
     {
         Stream out = Stream.newStream();
 
-        ForkedChannelEncoder forkedChannelEncoder = new ForkedChannelEncoder( out );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( out );
         StackTraceWriter stackTraceWriter = mock( StackTraceWriter.class );
         when( stackTraceWriter.getThrowable() ).thenReturn( new SafeThrowable( "1" ) );
         when( stackTraceWriter.smartTrimmedStackTrace() ).thenReturn( "2" );
         when( stackTraceWriter.writeTraceToString() ).thenReturn( "3" );
         when( stackTraceWriter.writeTrimmedTraceToString() ).thenReturn( "4" );
-        forkedChannelEncoder.sendExitEvent( stackTraceWriter, true );
+        encoder.sendExitEvent( stackTraceWriter, true );
 
         LineNumberReader printedLines = out.newReader( UTF_8 );
         assertThat( printedLines.readLine() )
-                .startsWith( ":maven:surefire:std:out:jvm-exit-error:UTF-8:MQ==:Mg==:NA==" );
+                .startsWith( ":maven-surefire-event:jvm-exit-error:UTF-8:MQ==:Mg==:NA==" );
     }
 
     private static class Stream extends PrintStream
diff --git a/surefire-extensions-api/pom.xml b/surefire-extensions-api/pom.xml
index 1e88403..87fcff4 100644
--- a/surefire-extensions-api/pom.xml
+++ b/surefire-extensions-api/pom.xml
@@ -38,11 +38,6 @@
             <scope>provided</scope>
         </dependency>
         <dependency>
-            <groupId>org.codehaus.plexus</groupId>
-            <artifactId>plexus-component-annotations</artifactId>
-            <scope>provided</scope>
-        </dependency>
-        <dependency>
             <groupId>com.google.code.findbugs</groupId>
             <artifactId>jsr305</artifactId>
             <scope>provided</scope>
@@ -72,33 +67,20 @@
                 <artifactId>jacoco-maven-plugin</artifactId>
                 <executions>
                     <execution>
-                        <id>jacoco-instrumentation</id>
-                        <!--
-                        phase: the order of the plugins matters and maven-plugin-plugin must be before jacoco plugin
-                        -->
-                        <goals>
-                            <goal>instrument</goal>
-                        </goals>
-                    </execution>
-                    <execution>
-                        <id>restore-classes</id>
+                        <id>jacoco-agent</id>
                         <goals>
-                            <goal>restore-instrumented-classes</goal>
+                            <goal>prepare-agent</goal>
                         </goals>
                     </execution>
                 </executions>
+                <configuration>
+                    <propertyName>jacoco.agent</propertyName>
+                </configuration>
             </plugin>
             <plugin>
                 <artifactId>maven-surefire-plugin</artifactId>
-                <dependencies>
-                    <dependency>
-                        <groupId>org.apache.maven.surefire</groupId>
-                        <artifactId>surefire-shadefire</artifactId>
-                        <version>3.0.0-M4</version> <!-- ${shadedVersion}, but resolved due to https://issues.apache.org/jira/browse/MRELEASE-799 -->
-                    </dependency>
-                </dependencies>
                 <configuration>
-                    <argLine>${jvm.args.tests}</argLine>
+                    <argLine>${jvm.args.tests} ${jacoco.agent}</argLine>
                     <includes>
                         <include>**/JUnit4SuiteTest.java</include>
                     </includes>
@@ -106,8 +88,14 @@
                         <jacoco-agent.destfile>${project.build.directory}/jacoco.exec</jacoco-agent.destfile>
                     </systemPropertyVariables>
                 </configuration>
+                <dependencies>
+                    <dependency>
+                        <groupId>org.apache.maven.surefire</groupId>
+                        <artifactId>surefire-shadefire</artifactId>
+                        <version>3.0.0-M4</version> <!-- ${shadedVersion}, but resolved due to https://issues.apache.org/jira/browse/MRELEASE-799 -->
+                    </dependency>
+                </dependencies>
             </plugin>
         </plugins>
     </build>
-
 </project>
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/ProviderPropertiesAware.java b/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/CloseableDaemonThread.java
similarity index 65%
rename from surefire-api/src/main/java/org/apache/maven/surefire/booter/ProviderPropertiesAware.java
rename to surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/CloseableDaemonThread.java
index caedb98..a15ed8a 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/ProviderPropertiesAware.java
+++ b/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/CloseableDaemonThread.java
@@ -1,4 +1,4 @@
-package org.apache.maven.surefire.booter;
+package org.apache.maven.surefire.extensions;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -19,12 +19,19 @@ package org.apache.maven.surefire.booter;
  * under the License.
  */
 
-import java.util.Map;
+import javax.annotation.Nonnull;
+import java.io.Closeable;
 
 /**
- * @author Kristian Rosenvold
+ * The base thread class used to handle a stream and transform it to an object.
  */
-interface ProviderPropertiesAware
+public abstract class CloseableDaemonThread extends Thread implements Closeable
 {
-    void setProviderProperties( Map<String, String> providerProperties );
+    protected CloseableDaemonThread( @Nonnull String threadName )
+    {
+        setName( threadName );
+        setDaemon( true );
+    }
+
+    public abstract void disable();
 }
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/MainCliOptionsAware.java b/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/CommandReader.java
similarity index 62%
copy from surefire-api/src/main/java/org/apache/maven/surefire/booter/MainCliOptionsAware.java
copy to surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/CommandReader.java
index eddebed..08fe5ed 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/MainCliOptionsAware.java
+++ b/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/CommandReader.java
@@ -1,4 +1,4 @@
-package org.apache.maven.surefire.booter;
+package org.apache.maven.surefire.extensions;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -19,17 +19,28 @@ package org.apache.maven.surefire.booter;
  * under the License.
  */
 
-import org.apache.maven.surefire.cli.CommandLineOption;
+import org.apache.maven.surefire.booter.Command;
 
-import java.util.List;
+import java.io.Closeable;
+import java.io.IOException;
 
 /**
- * CLI options in plugin (main) JVM process.
+ * Stream reader returns bytes which ar finally sent to the forked jvm.
  *
  * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
- * @since 2.19
+ * @since 3.0.0-M4
  */
-interface MainCliOptionsAware
+public interface CommandReader extends Closeable
 {
-    void setMainCliOptions( List<CommandLineOption> mainCliOptions );
+
+    /**
+     * Waits for the next command and returns it.
+     *
+     * @return the command, or null if closed
+     */
+    Command readNextCommand() throws IOException;
+    @Override
+    void close();
+    boolean isClosed();
+    void tryFlush() throws IOException;
 }
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/MainCliOptionsAware.java b/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/EventHandler.java
similarity index 69%
copy from surefire-api/src/main/java/org/apache/maven/surefire/booter/MainCliOptionsAware.java
copy to surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/EventHandler.java
index eddebed..bcb6c81 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/MainCliOptionsAware.java
+++ b/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/EventHandler.java
@@ -1,4 +1,4 @@
-package org.apache.maven.surefire.booter;
+package org.apache.maven.surefire.extensions;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -19,17 +19,14 @@ package org.apache.maven.surefire.booter;
  * under the License.
  */
 
-import org.apache.maven.surefire.cli.CommandLineOption;
-
-import java.util.List;
+import javax.annotation.Nonnull;
 
 /**
- * CLI options in plugin (main) JVM process.
  *
- * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
- * @since 2.19
  */
-interface MainCliOptionsAware
+public interface EventHandler
 {
-    void setMainCliOptions( List<CommandLineOption> mainCliOptions );
+    // 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 );
 }
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
new file mode 100644
index 0000000..baeae00
--- /dev/null
+++ b/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/ForkChannel.java
@@ -0,0 +1,138 @@
+package org.apache.maven.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.maven.surefire.extensions.util.CountdownCloseable;
+import org.apache.maven.surefire.shared.utils.cli.StreamConsumer;
+
+import javax.annotation.Nonnull;
+import java.io.Closeable;
+import java.io.IOException;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.channels.WritableByteChannel;
+
+/**
+ * It's a session object used only by a particular Thread in ForkStarter
+ * and dedicated forked JVM {@link #getForkChannelId()}. It represents a server.
+ * <br>
+ * <br>
+ * It opens the channel via {@link #openChannel()}, provides a connection string {@link #getForkNodeConnectionString()}
+ * used by the client in the JVM, binds event and command handlers.
+ *
+ * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
+ * @since 3.0.0-M5
+ */
+public abstract class ForkChannel implements Closeable
+{
+    private final int forkChannelId;
+
+    /**
+     * @param forkChannelId the index of the forked JVM, from 1 to N.
+     */
+    protected ForkChannel( int forkChannelId )
+    {
+        this.forkChannelId = forkChannelId;
+    }
+
+    public abstract void openChannel() throws IOException;
+
+    /**
+     * This is server related class, which if binds to a TCP port, determines the connection string for the client.
+     *
+     * @return a connection string utilized by the client in the fork JVM
+     */
+    public abstract String getForkNodeConnectionString();
+
+    /**
+     * Determines which one of the two <em>bindCommandReader-s</em> to call in <em>ForkStarter</em>.
+     * Can be called anytime.
+     *
+     * @return If {@code true}, calling {@link #bindCommandReader(CommandReader, WritableByteChannel)} by
+     * <em>ForkStarter</em> and {@link #bindCommandReader(CommandReader)} throws {@link UnsupportedOperationException}.
+     * If {@code false}, then opposite.
+     */
+    public abstract boolean useStdIn();
+
+    /**
+     * Determines which one of the two <em>bindEventHandler-s</em> to call in <em>ForkStarter</em>.
+     * Can be called anytime.
+     *
+     * @return If {@code true}, the {@link #bindEventHandler(StreamConsumer, ReadableByteChannel, CountdownCloseable)}
+     * is called in <em>ForkStarter</em> and {@link #bindEventHandler(StreamConsumer)} throws
+     * {@link UnsupportedOperationException}.
+     * If {@code false}, then opposite.
+     */
+    public abstract boolean useStdOut();
+
+    /**
+     * Binds command handler to the channel.
+     *
+     * @param commands command reader, see {@link CommandReader#readNextCommand()}
+     * @param stdIn    the standard input stream of the JVM to write the encoded commands into it
+     * @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 bindCommandReader( @Nonnull CommandReader commands,
+                                                             @Nonnull WritableByteChannel stdIn )
+        throws IOException;
+
+    /**
+     * Binds command handler to the channel.
+     *
+     * @param commands command reader, see {@link CommandReader#readNextCommand()}
+     * @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 bindCommandReader( @Nonnull CommandReader commands )
+        throws IOException;
+
+    /**
+     *
+     * @param consumer           event consumer
+     * @param stdOut             the standard output stream of the JVM
+     * @param countdownCloseable count down of the final call of {@link Closeable#close()}
+     * @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 StreamConsumer consumer,
+                                                            @Nonnull ReadableByteChannel stdOut,
+                                                            @Nonnull CountdownCloseable countdownCloseable )
+        throws IOException;
+
+    /**
+     * Binds event handler to the channel.
+     *
+     * @param consumer event consumer
+     * @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 StreamConsumer consumer )
+        throws IOException;
+
+    /**
+     * The index of the fork.
+     *
+     * @return the index of the forked JVM, from 1 to N.
+     */
+    protected final int getForkChannelId()
+    {
+        return forkChannelId;
+    }
+}
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/MainCliOptionsAware.java b/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/ForkNodeFactory.java
similarity index 65%
rename from surefire-api/src/main/java/org/apache/maven/surefire/booter/MainCliOptionsAware.java
rename to surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/ForkNodeFactory.java
index eddebed..db0e7b8 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/MainCliOptionsAware.java
+++ b/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/ForkNodeFactory.java
@@ -1,4 +1,4 @@
-package org.apache.maven.surefire.booter;
+package org.apache.maven.surefire.extensions;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -19,17 +19,21 @@ package org.apache.maven.surefire.booter;
  * under the License.
  */
 
-import org.apache.maven.surefire.cli.CommandLineOption;
-
-import java.util.List;
+import javax.annotation.Nonnegative;
+import javax.annotation.Nonnull;
+import java.io.IOException;
 
 /**
- * CLI options in plugin (main) JVM process.
- *
  * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
- * @since 2.19
+ * @since 3.0.0-M5
  */
-interface MainCliOptionsAware
+public interface ForkNodeFactory
 {
-    void setMainCliOptions( List<CommandLineOption> mainCliOptions );
+    /**
+     * 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;
 }
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/TestRequestAware.java b/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/StdErrStreamLine.java
similarity index 78%
rename from surefire-api/src/main/java/org/apache/maven/surefire/booter/TestRequestAware.java
rename to surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/StdErrStreamLine.java
index 3e98b92..f0ae082 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/TestRequestAware.java
+++ b/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/StdErrStreamLine.java
@@ -1,4 +1,4 @@
-package org.apache.maven.surefire.booter;
+package org.apache.maven.surefire.extensions;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -19,12 +19,11 @@ package org.apache.maven.surefire.booter;
  * under the License.
  */
 
-import org.apache.maven.surefire.testset.TestRequest;
+import org.apache.maven.surefire.shared.utils.cli.StreamConsumer;
 
 /**
- * @author Kristian Rosenvold
+ * The line handler of forked process standard-error.
  */
-interface TestRequestAware
+public interface StdErrStreamLine extends StreamConsumer
 {
-    void setTestRequest( TestRequest testSuiteDefinition );
 }
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/SurefireClassLoadersAware.java b/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/StdOutStreamLine.java
similarity index 82%
rename from surefire-api/src/main/java/org/apache/maven/surefire/booter/SurefireClassLoadersAware.java
rename to surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/StdOutStreamLine.java
index c2f5d99..f615764 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/SurefireClassLoadersAware.java
+++ b/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/StdOutStreamLine.java
@@ -1,4 +1,4 @@
-package org.apache.maven.surefire.booter;
+package org.apache.maven.surefire.extensions;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -20,9 +20,9 @@ package org.apache.maven.surefire.booter;
  */
 
 /**
- * @author Kristian Rosenvold
+ * The line handler of forked process standard-output.
  */
-interface SurefireClassLoadersAware
+public interface StdOutStreamLine
 {
-    void setClassLoaders( ClassLoader testClassLoader );
+    void handleLine( String line );
 }
diff --git a/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/util/CountdownCloseable.java b/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/util/CountdownCloseable.java
index 9818ec9..4bb5272 100644
--- a/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/util/CountdownCloseable.java
+++ b/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/util/CountdownCloseable.java
@@ -20,7 +20,6 @@ package org.apache.maven.surefire.extensions.util;
  */
 
 import javax.annotation.Nonnegative;
-import javax.annotation.Nonnull;
 import java.io.Closeable;
 import java.io.IOException;
 
@@ -33,8 +32,12 @@ public final class CountdownCloseable
     private final Closeable closeable;
     private volatile int countdown;
 
-    public CountdownCloseable( @Nonnull Closeable closeable, @Nonnegative int countdown )
+    public CountdownCloseable( Closeable closeable, @Nonnegative int countdown )
     {
+        if ( closeable == null && countdown > 0 )
+        {
+            throw new IllegalStateException( "closeable is null and countdown is " + countdown );
+        }
         this.closeable = closeable;
         this.countdown = countdown;
     }
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 109e541..162af4b 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
@@ -19,10 +19,10 @@ package org.apache.maven.surefire.extensions.util;
  * under the License.
  */
 
+import org.apache.maven.surefire.extensions.CloseableDaemonThread;
 import org.apache.maven.surefire.shared.utils.cli.StreamConsumer;
 
 import javax.annotation.Nonnull;
-import java.io.Closeable;
 import java.io.IOException;
 import java.nio.channels.ReadableByteChannel;
 import java.nio.charset.Charset;
@@ -31,7 +31,7 @@ import java.util.Scanner;
 /**
  *
  */
-public final class LineConsumerThread extends Thread implements Closeable
+public final class LineConsumerThread extends CloseableDaemonThread
 {
     private final Charset encoding;
     private final ReadableByteChannel channel;
@@ -50,8 +50,7 @@ public final class LineConsumerThread extends Thread implements Closeable
                                @Nonnull ReadableByteChannel channel, @Nonnull StreamConsumer consumer,
                                @Nonnull CountdownCloseable countdownCloseable, @Nonnull Charset encoding )
     {
-        setName( threadName );
-        setDaemon( true );
+        super( threadName );
         this.channel = channel;
         this.consumer = consumer;
         this.countdownCloseable = countdownCloseable;
diff --git a/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/util/StreamFeeder.java b/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/util/StreamFeeder.java
index ca8eb8e..78e5ce0 100644
--- a/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/util/StreamFeeder.java
+++ b/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/util/StreamFeeder.java
@@ -19,10 +19,13 @@ package org.apache.maven.surefire.extensions.util;
  * under the License.
  */
 
+import org.apache.maven.surefire.booter.Command;
+import org.apache.maven.surefire.booter.MasterProcessCommand;
+import org.apache.maven.surefire.extensions.CloseableDaemonThread;
+import org.apache.maven.surefire.extensions.CommandReader;
+
 import javax.annotation.Nonnull;
-import java.io.Closeable;
 import java.io.IOException;
-import java.io.InputStream;
 import java.nio.ByteBuffer;
 import java.nio.channels.ClosedChannelException;
 import java.nio.channels.NonWritableChannelException;
@@ -31,33 +34,35 @@ import java.nio.channels.WritableByteChannel;
 /**
  *
  */
-public class StreamFeeder extends Thread implements Closeable
+public class StreamFeeder extends CloseableDaemonThread
 {
     private final WritableByteChannel channel;
-    private final InputStream is;
+    private final CommandReader commandReader;
 
     private volatile boolean disabled;
     private volatile Throwable exception;
 
-    public StreamFeeder( @Nonnull String threadName, @Nonnull WritableByteChannel channel, @Nonnull InputStream is )
+    public StreamFeeder( @Nonnull String threadName, @Nonnull WritableByteChannel channel,
+                         @Nonnull CommandReader commandReader )
     {
-        setName( threadName );
-        setDaemon( true );
+        super( threadName );
         this.channel = channel;
-        this.is = is;
+        this.commandReader = commandReader;
     }
 
     @Override
+    @SuppressWarnings( "checkstyle:innerassignment" )
     public void run()
     {
         try ( WritableByteChannel c = channel )
         {
-            for ( int data = is.read(); data != -1; data = is.read()  )
+            for ( Command cmd; ( cmd = commandReader.readNextCommand() ) != null; )
             {
                 if ( !disabled )
                 {
-                    // todo use CommandReader interface instead of InputStream. Then we would write ByteBuffer.
-                    c.write( ByteBuffer.wrap( new byte[] {(byte) data} ) );
+                    MasterProcessCommand cmdType = cmd.getCommandType();
+                    byte[] data = cmdType.hasDataType() ? cmdType.encode( cmd.getData() ) : cmdType.encode();
+                    c.write( ByteBuffer.wrap( data ) );
                 }
             }
         }
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 fd99059..fc919d2 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
@@ -19,6 +19,8 @@ package org.apache.maven.surefire.extensions.util;
  * under the License.
  */
 
+import org.apache.maven.surefire.booter.Command;
+import org.apache.maven.surefire.extensions.CommandReader;
 import org.apache.maven.surefire.shared.utils.cli.Commandline;
 import org.apache.maven.surefire.shared.utils.cli.StreamConsumer;
 import org.junit.After;
@@ -26,7 +28,6 @@ import org.junit.Before;
 import org.junit.Test;
 
 import java.io.Closeable;
-import java.io.InputStream;
 import java.nio.file.Paths;
 
 import static org.apache.maven.surefire.shared.lang3.SystemUtils.IS_OS_WINDOWS;
@@ -90,15 +91,33 @@ public class CommandlineExecutorTest
         exec = new CommandlineExecutor( cli, countdownCloseable );
         streams = exec.execute();
         StreamConsumer consumer = mock( StreamConsumer.class );
-        InputStream is = new InputStream()
+        CommandReader commandReader = new CommandReader()
         {
             @Override
-            public int read()
+            public Command readNextCommand()
             {
-                return -1;
+                return null;
+            }
+
+            @Override
+            public void close()
+            {
+
+            }
+
+            @Override
+            public boolean isClosed()
+            {
+                return false;
+            }
+
+            @Override
+            public void tryFlush()
+            {
+
             }
         };
-        StreamFeeder in = new StreamFeeder( "std-in-fork-1", streams.getStdInChannel(), is );
+        StreamFeeder in = new StreamFeeder( "std-in-fork-1", streams.getStdInChannel(), commandReader );
         in.start();
         out = new LineConsumerThread( "std-out-fork-1", streams.getStdOutChannel(), consumer, countdownCloseable );
         out.start();
diff --git a/surefire-extensions-spi/pom.xml b/surefire-extensions-spi/pom.xml
new file mode 100644
index 0000000..95cf3d9
--- /dev/null
+++ b/surefire-extensions-spi/pom.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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.
+  -->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.apache.maven.surefire</groupId>
+        <artifactId>surefire</artifactId>
+        <version>3.0.0-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>surefire-extensions-spi</artifactId>
+    <name>Surefire Extensions SPI</name>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.maven.surefire</groupId>
+            <artifactId>surefire-api</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+    </dependencies>
+
+</project>
diff --git a/surefire-extensions-spi/src/main/java/org/apache/maven/surefire/spi/MasterProcessChannelProcessorFactory.java b/surefire-extensions-spi/src/main/java/org/apache/maven/surefire/spi/MasterProcessChannelProcessorFactory.java
new file mode 100644
index 0000000..989a52d
--- /dev/null
+++ b/surefire-extensions-spi/src/main/java/org/apache/maven/surefire/spi/MasterProcessChannelProcessorFactory.java
@@ -0,0 +1,62 @@
+package org.apache.maven.surefire.spi;
+
+/*
+ * 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.providerapi.MasterProcessChannelDecoder;
+import org.apache.maven.surefire.providerapi.MasterProcessChannelEncoder;
+
+import java.io.Closeable;
+import java.io.IOException;
+
+/**
+ * The SPI interface, a factory of an encoder and a decoder.
+ */
+public interface MasterProcessChannelProcessorFactory extends Closeable
+{
+    /**
+     * Evaluates the {@code channelConfig}.
+     *
+     * @param channelConfig a connection string used by the fork JVM
+     * @return {@code true} if {@code channelConfig} is applicable and thus this SPI is eligible in the fork
+     */
+    boolean canUse( String channelConfig );
+
+    /**
+     * Open a new connection.
+     *
+     * @param channelConfig e.g. "pipe://3" or "tcp://127.0.0.1:65035"
+     * @throws IOException if cannot connect
+     */
+    void connect( String channelConfig ) throws IOException;
+
+    /**
+     * Decoder factory method.
+     *
+     * @return a new instance of decoder
+     */
+    MasterProcessChannelDecoder createDecoder() throws IOException;
+
+    /**
+     * Encoder factory method.
+     *
+     * @return a new instance of encoder
+     */
+    MasterProcessChannelEncoder createEncoder() throws IOException;
+}
diff --git a/surefire-logger-api/pom.xml b/surefire-logger-api/pom.xml
index bcac4ad..559e829 100644
--- a/surefire-logger-api/pom.xml
+++ b/surefire-logger-api/pom.xml
@@ -87,7 +87,7 @@
                     <dependency>
                         <groupId>org.apache.maven.surefire</groupId>
                         <artifactId>surefire-shadefire</artifactId>
-<!--                        <version>3.0.0-M4</version>-->
+<!--                        <version>3.0.0-M3</version>-->
                         <version>3.0.0-M3</version> <!-- ${shadedVersion}, but resolved due to https://issues.apache.org/jira/browse/MRELEASE-799 -->
                     </dependency>
                 </dependencies>
diff --git a/surefire-providers/surefire-junit4/src/main/java/org/apache/maven/surefire/junit4/JUnit4Provider.java b/surefire-providers/surefire-junit4/src/main/java/org/apache/maven/surefire/junit4/JUnit4Provider.java
index b964933..9e5efad 100644
--- a/surefire-providers/surefire-junit4/src/main/java/org/apache/maven/surefire/junit4/JUnit4Provider.java
+++ b/surefire-providers/surefire-junit4/src/main/java/org/apache/maven/surefire/junit4/JUnit4Provider.java
@@ -20,8 +20,8 @@ package org.apache.maven.surefire.junit4;
  */
 
 import org.apache.maven.surefire.booter.Command;
-import org.apache.maven.surefire.booter.CommandListener;
-import org.apache.maven.surefire.booter.CommandReader;
+import org.apache.maven.surefire.providerapi.CommandChainReader;
+import org.apache.maven.surefire.providerapi.CommandListener;
 import org.apache.maven.surefire.common.junit4.JUnit4RunListener;
 import org.apache.maven.surefire.common.junit4.JUnit4TestChecker;
 import org.apache.maven.surefire.common.junit4.JUnitTestFailureListener;
@@ -52,7 +52,6 @@ import java.util.Set;
 
 import static java.lang.reflect.Modifier.isAbstract;
 import static java.lang.reflect.Modifier.isInterface;
-import static org.apache.maven.surefire.booter.CommandReader.getReader;
 import static org.apache.maven.surefire.common.junit4.JUnit4ProviderUtil.createMatchAnyDescriptionFilter;
 import static org.apache.maven.surefire.common.junit4.JUnit4ProviderUtil.generateFailingTestDescriptions;
 import static org.apache.maven.surefire.common.junit4.JUnit4ProviderUtil.isFailureInsideJUnitItself;
@@ -92,14 +91,14 @@ public class JUnit4Provider
 
     private final int rerunFailingTestsCount;
 
-    private final CommandReader commandsReader;
+    private final CommandChainReader commandsReader;
 
     private TestsToRun testsToRun;
 
     public JUnit4Provider( ProviderParameters bootParams )
     {
         // don't start a thread in CommandReader while we are in in-plugin process
-        commandsReader = bootParams.isInsideFork() ? getReader().setShutdown( bootParams.getShutdown() ) : null;
+        commandsReader = bootParams.isInsideFork() ? bootParams.getCommandReader() : null;
         providerParameters = bootParams;
         testClassLoader = bootParams.getTestClassLoader();
         scanResult = bootParams.getScanResult();
diff --git a/surefire-providers/surefire-junit4/src/test/java/org/apache/maven/surefire/junit4/JUnit4ProviderTest.java b/surefire-providers/surefire-junit4/src/test/java/org/apache/maven/surefire/junit4/JUnit4ProviderTest.java
index b949baa..c03ea7e 100644
--- a/surefire-providers/surefire-junit4/src/test/java/org/apache/maven/surefire/junit4/JUnit4ProviderTest.java
+++ b/surefire-providers/surefire-junit4/src/test/java/org/apache/maven/surefire/junit4/JUnit4ProviderTest.java
@@ -50,7 +50,7 @@ public class JUnit4ProviderTest
 
     private JUnit4Provider getJUnit4Provider()
     {
-        BaseProviderFactory providerParameters = new BaseProviderFactory( null, true );
+        BaseProviderFactory providerParameters = new BaseProviderFactory( true );
         providerParameters.setProviderProperties( new HashMap<String, String>() );
         providerParameters.setClassLoaders( getClass().getClassLoader() );
         providerParameters.setTestRequest( new TestRequest( null, null, null ) );
diff --git a/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/JUnitCoreProvider.java b/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/JUnitCoreProvider.java
index 7c74e8b..daabb60 100644
--- a/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/JUnitCoreProvider.java
+++ b/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/JUnitCoreProvider.java
@@ -20,8 +20,8 @@ package org.apache.maven.surefire.junitcore;
  */
 
 import org.apache.maven.surefire.booter.Command;
-import org.apache.maven.surefire.booter.CommandListener;
-import org.apache.maven.surefire.booter.CommandReader;
+import org.apache.maven.surefire.providerapi.CommandChainReader;
+import org.apache.maven.surefire.providerapi.CommandListener;
 import org.apache.maven.surefire.common.junit4.JUnit4RunListener;
 import org.apache.maven.surefire.common.junit4.JUnitTestFailureListener;
 import org.apache.maven.surefire.common.junit4.Notifier;
@@ -46,7 +46,6 @@ import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 
-import static org.apache.maven.surefire.booter.CommandReader.getReader;
 import static org.apache.maven.surefire.common.junit4.JUnit4ProviderUtil.generateFailingTestDescriptions;
 import static org.apache.maven.surefire.common.junit4.JUnit4RunListenerFactory.createCustomListeners;
 import static org.apache.maven.surefire.common.junit4.Notifier.pureNotifier;
@@ -82,14 +81,14 @@ public class JUnitCoreProvider
 
     private final TestListResolver testResolver;
 
-    private final CommandReader commandsReader;
+    private final CommandChainReader commandsReader;
 
     private TestsToRun testsToRun;
 
     public JUnitCoreProvider( ProviderParameters bootParams )
     {
         // don't start a thread in CommandReader while we are in in-plugin process
-        commandsReader = bootParams.isInsideFork() ? getReader().setShutdown( bootParams.getShutdown() ) : null;
+        commandsReader = bootParams.isInsideFork() ? bootParams.getCommandReader() : null;
         providerParameters = bootParams;
         testClassLoader = bootParams.getTestClassLoader();
         scanResult = bootParams.getScanResult();
diff --git a/surefire-providers/surefire-junit47/src/test/java/org/apache/maven/surefire/junitcore/Surefire746Test.java b/surefire-providers/surefire-junit47/src/test/java/org/apache/maven/surefire/junitcore/Surefire746Test.java
index 8859c19..66f0b85 100644
--- a/surefire-providers/surefire-junit47/src/test/java/org/apache/maven/surefire/junitcore/Surefire746Test.java
+++ b/surefire-providers/surefire-junit47/src/test/java/org/apache/maven/surefire/junitcore/Surefire746Test.java
@@ -110,7 +110,8 @@ public class Surefire746Test
     public void surefireIsConfused_ByMultipleIgnore_OnClassLevel() throws Exception
     {
         ReporterFactory reporterFactory = JUnitCoreTester.defaultNoXml();
-        BaseProviderFactory providerParameters = new BaseProviderFactory( reporterFactory, true );
+        BaseProviderFactory providerParameters = new BaseProviderFactory( true );
+        providerParameters.setReporterFactory( reporterFactory );
 
         providerParameters.setReporterConfiguration( new ReporterConfiguration( new File( "" ), false ) );
         Map<String, String> junitProps = new HashMap<>();
diff --git a/surefire-providers/surefire-testng/src/main/java/org/apache/maven/surefire/testng/TestNGProvider.java b/surefire-providers/surefire-testng/src/main/java/org/apache/maven/surefire/testng/TestNGProvider.java
index 14e103a..a8a5fb7 100644
--- a/surefire-providers/surefire-testng/src/main/java/org/apache/maven/surefire/testng/TestNGProvider.java
+++ b/surefire-providers/surefire-testng/src/main/java/org/apache/maven/surefire/testng/TestNGProvider.java
@@ -20,8 +20,8 @@ package org.apache.maven.surefire.testng;
  */
 
 import org.apache.maven.surefire.booter.Command;
-import org.apache.maven.surefire.booter.CommandListener;
-import org.apache.maven.surefire.booter.CommandReader;
+import org.apache.maven.surefire.providerapi.CommandChainReader;
+import org.apache.maven.surefire.providerapi.CommandListener;
 import org.apache.maven.surefire.cli.CommandLineOption;
 import org.apache.maven.surefire.providerapi.AbstractProvider;
 import org.apache.maven.surefire.providerapi.ProviderParameters;
@@ -43,7 +43,6 @@ import java.util.Collection;
 import java.util.List;
 import java.util.Map;
 
-import static org.apache.maven.surefire.booter.CommandReader.getReader;
 import static org.apache.maven.surefire.report.ConsoleOutputCapture.startCapture;
 import static org.apache.maven.surefire.testset.TestListResolver.getEmptyTestListResolver;
 import static org.apache.maven.surefire.testset.TestListResolver.optionallyWildcardFilter;
@@ -71,14 +70,14 @@ public class TestNGProvider
 
     private final List<CommandLineOption> mainCliOptions;
 
-    private final CommandReader commandsReader;
+    private final CommandChainReader commandsReader;
 
     private TestsToRun testsToRun;
 
     public TestNGProvider( ProviderParameters bootParams )
     {
         // don't start a thread in CommandReader while we are in in-plugin process
-        commandsReader = bootParams.isInsideFork() ? getReader().setShutdown( bootParams.getShutdown() ) : null;
+        commandsReader = bootParams.isInsideFork() ? bootParams.getCommandReader() : null;
         providerParameters = bootParams;
         testClassLoader = bootParams.getTestClassLoader();
         runOrderCalculator = bootParams.getRunOrderCalculator();
diff --git a/surefire-shadefire/pom.xml b/surefire-shadefire/pom.xml
index 6ade032..1d2c468 100644
--- a/surefire-shadefire/pom.xml
+++ b/surefire-shadefire/pom.xml
@@ -73,8 +73,10 @@
             <configuration>
               <artifactSet>
                 <includes>
+                  <include>org.apache.maven.surefire:surefire-shared-utils</include>
                   <include>org.apache.maven.surefire:surefire-logger-api</include>
                   <include>org.apache.maven.surefire:surefire-api</include>
+                  <include>org.apache.maven.surefire:surefire-extensions-spi</include>
                   <include>org.apache.maven.surefire:surefire-booter</include>
                   <include>org.apache.maven.surefire:common-junit3</include>
                   <include>org.apache.maven.surefire:surefire-junit3</include>