package cucumber.runtime.java.hooks;

public class HttpServerStub {
    private boolean isStarted;
    private Request<String> request;

    public void start() {
        if (isStarted) {
            throw new IllegalStateException("Server already started!");
        }
        try {
            Thread.sleep(2000);
            isStarted = true;
        } catch (InterruptedException e) {}
    }

    public void stop() {
        if (!isStarted) {
            throw new IllegalStateException("Server is not running!");
        }
        try {
            Thread.sleep(2000);
            isStarted = false;
        } catch (InterruptedException e) {}
    }

    public boolean isStarted() {
        return isStarted;
    }

    public int send(Request<String> request) {
        if (!isStarted) {
            throw new IllegalStateException("server is not running");
        }
        this.request = request;
        return 200;
    }

    public String receive() {
        if (!isStarted) {
            throw new IllegalStateException("server is not running");
        }
        return request.getData();
    }

    public void clean() {
        request = new Request<String>(null);
    }

    static class Request<T> {
        private T data;

        Request(T data) {
            this.data = data;
        }

        T getData() {
            return data;
        }
    }
}

package cucumber.runtime.java.hooks;

public class HttpServerStub {
    private boolean isStarted;
    private Request<String> request;

    public void start() {
        if (isStarted) {
            throw new IllegalStateException("Server already started!");
        }
        try {
            Thread.sleep(2000);
            isStarted = true;
        } catch (InterruptedException e) {}
    }

    public void stop() {
        if (!isStarted) {
            throw new IllegalStateException("Server is not running!");
        }
        try {
            Thread.sleep(2000);
            isStarted = false;
        } catch (InterruptedException e) {}
    }

    public boolean isStarted() {
        return isStarted;
    }

    public int send(Request<String> request) {
        if (!isStarted) {
            throw new IllegalStateException("server is not running");
        }
        this.request = request;
        return 200;
    }

    public String retrieve() {
        if (!isStarted) {
            throw new IllegalStateException("server is not running");
        }
        return request.getData();
    }

    public void clean() {
        request = new Request<String>(null);
    }

    static class Request<T> {
        private T data;

        Request(T data) {
            this.data = data;
        }

        T getData() {
            return data;
        }
    }
}

Add new hooks for global level (@BeforeAll and @AfterAll) that run only once before all and after all existing features/scenarios. Unit test and cucumber test to cover the new hooks and the running order between hooks. An execution flow with the new hooks looks like this: 1 - @BeforeAll hook(s) (runs before all features once) 2 - @Before hook(s) (no change, same behavior - before every scenario or tagged scenarios) 3 - @After hook(s) (no change, same behavior - after every scenario or tagged scenarios) 4 - .... 2 and 3 for all scenarios in a feature 5 - @AfterAll hook(s) (runs once after all the features are run)