Java implementing task on another thread with javascript-like promise resolve-reject Background:

I have a TCP online multiplayer game server, which the player requests to log-in. However, the game server does not handle the authentication part of the login process, instead, it uses an outer micro-service which is an HTTP server to see if the user can authenticate with the given credentials. 

Now why is that a problem? - Well I use netty.io networking framework, which is a non-blocking framework, it handles all connections in a sequence on the same thread. Since I don't want to cause any delays, I will not send an HTTP request on the same thread because I don't want to stop it.

Instead I followed a pattern of callbacks like java script promise resolve-reject, and I Would like to hear what you think, and if it could be done better.

Don't mind the generic types, just take the basic idea of it:

First of all this is my ServiceProvider, the point of this class is to provide a singleton of each service that interacts with microservices such as UserService which is an HTTP server to handle everything related to user (authentication, data, etc):

public class ServiceProvider {

    private static ExecutorService executorService = Executors.newFixedThreadPool(ServicesConfig.SERVICES_THREAD_POOL_CAPACITY);

    private static UserService userService;

    /**
     * Provdes an user service singleton instance
     * @return  The user service
     */
    public static UserService getUserService() {
        if (userService == null) {
            userService = new UserService();
            userService.setBaseUrl(ServicesConfig.USER_SERVICE_BASE_URL);
        }
        return userService;
    }

    public static void submitServiceTask(ServicePromise task) {
        executorService.submit(task);
    }
}


Secondly is my UserService for example:

public class UserService extends HeroesHttpService {

    /**
     * Authenticates an user
     * @param username  The username
     * @param password  The password
     * @return  ?
     */
    public ServicePromise&lt;?&gt; authenticateUser(String username, String password) {

        ServicePromise&lt;HttpResponse&gt; promise = new ServicePromise&lt;HttpResponse&gt;() {
            @Override
            public void run() {
                HttpClient client = HttpClients.createDefault();

                HttpGet request = new HttpGet(getBaseUrl() + "/user/authenticate");
                try {
                    HttpResponse response = client.execute(request);
                    resolve(response);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        };

        ServiceProvider.submitServiceTask(promise);
        return promise;
    }
}


Now this is my promise interface: 

public abstract class ServicePromise&lt;T&gt; implements Runnable {

    private ServicePromiseCallback&lt;T&gt; callback;

    public ServicePromise&lt;T&gt; then(ServicePromiseCallback&lt;T&gt; cb) {
        this.callback = cb;
        return this;
    }

    @Override
    public void run() {
    }

    public void resolve(T data) {
        if (this.callback == null) {
            return;
        }
        this.callback.onResolve(data);
    }

    public void reject(T data) {
        if (this.callback == null) {
            return;
        }
        this.callback.onReject(data);
    }
}


And that's the callback interface:

public interface ServicePromiseCallback&lt;T&gt; {
    void onResolve(T response);
    void onReject(T response);
}


And finally, thats how I use it:

    ServiceProvider.getUserService().authenticateUser(player.playerName, player.playerPass).then(new ServicePromiseCallback&lt;?&gt; () {
        @Override
        public void onResolve(Object response) {

        }

        @Override
        public void onReject(Object response) {

        }
    });


Again, don't mind the types in resolve/reject, it's going to definitely be AuthenticateUserResponseDto and so on, but it doesn't matter right now
the ServiceProvider is supposed to provide a singleton instance of UserService but it does not follow all the principles of the Singleton design pattern. specifically, the locking mechanisms that prevent multiple threads from creating multiple instances of UserService (or rather, multiple calling of the constructor)

and while on the subject - why not use Dependency Injection?

also, reject(response) is never called

with regard to the issue of the "javascript-like promise resolve-reject": Java has such a mechanism: CompletableFuture where you supply a Runnable and for the callbacks there are thenApply() thenAccept() and thenRun() methods.  exceptions are handled by exceptionally() method. There are other capabilities like chaining CompletableFuture instances (thenCompose().