Is Guava's ListenableFuture still useful/ up to date (after Java 8)?

Viewed 2225

Since Java 8 some features from Guava are already obsolete (for example String joiner, optional values, preconditions check, Futures etc).

In ListenableFuture documentation (currently with no update since 1 year ago) they say:

"We strongly advise that you always use ListenableFuture instead of Future in all of your code, because..."

I'm using guava (and cassandra) in a old project and my question is: does Java 8 standard libraries already have something that makes ListenableFuture obsolete or is it still the best Future alternative? Thanks.

1 Answers

Agree with https://stackoverflow.com/users/3371051/pedro, CompletableFuture is Java Built-in, while ListenableFuture is counting on Guava. It's easy convert ListenableFuture to CompletableFuture.

  1. Create CompletableFuture, complete() or completeExceptionally() in ListenableFuture listenable call back.
  2. use thenCompose to chain CompletableFuture with ListenableFuture
  3. use allOf to replace Futures.allAsList

Like the following tested change I did for com.datastax driver core 3.11.0

CompletableFuture initAsync() {

    CompletableFuture ret = new CompletableFuture();
    if (factory.isShutdown) {
      ret.completeExceptionally(
          new ConnectionException(endPoint, "Connection factory is shut down"));
      return ret;
    }

    ProtocolVersion protocolVersion =
        factory.protocolVersion == null
            ? ProtocolVersion.NEWEST_SUPPORTED
            : factory.protocolVersion;

    try {
      Bootstrap bootstrap = factory.newBootstrap();
      ProtocolOptions protocolOptions = factory.configuration.getProtocolOptions();
      bootstrap.handler(
          new Initializer(
              this,
              protocolVersion,
              protocolOptions.getCompression().compressor(),
              protocolOptions.getSSLOptions(),
              factory.configuration.getPoolingOptions().getHeartbeatIntervalSeconds(),
              factory.configuration.getNettyOptions(),
              factory.configuration.getCodecRegistry(),
              factory.configuration.getMetricsOptions().isEnabled()
                  ? factory.manager.metrics
                  : null));

      ChannelFuture future = bootstrap.connect(endPoint.resolve());

      writer.incrementAndGet();
      future.addListener(
          new ChannelFutureListener() {
            @Override
            public void operationComplete(ChannelFuture future) throws Exception {
              writer.decrementAndGet();
              // Note: future.channel() can be null in some error cases, so we need to guard against
              // it in the rest of the code below.
              channel = future.channel();
              if (isClosed() && channel != null) {
                channel
                    .close()
                    .addListener(
                        new ChannelFutureListener() {
                          @Override
                          public void operationComplete(ChannelFuture future) throws Exception {

                            ret.completeExceptionally(
                                new TransportException(
                                    Connection.this.endPoint,
                                    "Connection closed during initialization."));
                          }
                        });
              } else {
                if (channel != null) {
                  Connection.this.factory.allChannels.add(channel);
                }
                if (!future.isSuccess()) {
                  if (logger.isDebugEnabled())
                    logger.debug(
                        String.format(
                            "%s Error connecting to %s%s",
                            Connection.this,
                            Connection.this.endPoint,
                            extractMessage(future.cause())));
                  ret.completeExceptionally(
                      new TransportException(
                          Connection.this.endPoint, "Cannot connect", future.cause()));
                } else {
                  assert channel != null;
                  logger.debug(
                      "{} Connection established, initializing transport", Connection.this);
                  channel.closeFuture().addListener(new ChannelCloseListener());
                  ret.complete(null);
                }
              }
            }
          });
    } catch (RuntimeException e) {
      closeAsync().force();
      throw e;
    }

    Executor initExecutor =
        factory.manager.configuration.getPoolingOptions().getInitializationExecutor();

    return ret.thenCompose(
            Void -> {
              CompletableFuture ret2 = new CompletableFuture();
              ProtocolOptions protocolOptions = factory.configuration.getProtocolOptions();
              Future startupResponseFuture =
                  write(
                      new Requests.Startup(
                          protocolOptions.getCompression(), protocolOptions.isNoCompact()));

              ListenableFuture channelReadyFuture =
                  GuavaCompatibility.INSTANCE.transformAsync(
                      startupResponseFuture,
                      onStartupResponse(protocolVersion, initExecutor),
                      initExecutor);

              GuavaCompatibility.INSTANCE.addCallback(
                      channelReadyFuture,
                      new FutureCallback() {
                        @Override
                        public void onSuccess(Void result) {
                          ret2.complete(null);
                        }

                        @Override
                        public void onFailure(Throwable t) {

                          // Make sure the connection gets properly closed.
                          if (t instanceof ClusterNameMismatchException
                                  || t instanceof UnsupportedProtocolVersionException) {
                            // Just propagate
                            closeAsync().force();
                            ret2.completeExceptionally(t);
                          } else {
                            // Defunct to ensure that the error will be signaled (marking the host down)
                            Throwable e =
                                    (t instanceof ConnectionException
                                            || t instanceof DriverException
                                            || t instanceof InterruptedException
                                            || t instanceof Error)
                                            ? t
                                            : new ConnectionException(
                                            Connection.this.endPoint,
                                            String.format(
                                                    "Unexpected error during transport initialization (%s)", t),
                                            t);
                            ret2.completeExceptionally(defunct(e));
                          }
                          // Ensure the connection gets closed if the caller cancels the returned future.
                          if (!isClosed()) {
                            closeAsync().force();
                          }


                        }
                      });
              return ret2;
            });
  }

private CompletableFuture createPools(Collection hosts) {
    List> futures = Lists.newArrayListWithCapacity(hosts.size());
    for (Host host : hosts)
      if (host.state != Host.State.DOWN) futures.add(maybeAddPool(host, null));
    return CompletableFuture.allOf(futures.toArray(new CompletableFuture[futures.size()]));
  }
Related