Netty - Handle AnnotatedConnectException

Viewed 4947

I am new to networking libraries and Netty, and I am building a client. From my testing, I understand that my code works properly as intended, however I would like to give a more meaningful message when it can't connect to the host properly. So what I've done is to try having it connect to localhost at the port and I have nothing hosted on there, so it would throw an exception. Below is the stack trace:

Exception in thread "main" io.netty.channel.AbstractChannel$AnnotatedConnectException: Connection refused: no further information: /127.0.0.1:8484
    at sun.nio.ch.SocketChannelImpl.checkConnect(Native Method)
    at sun.nio.ch.SocketChannelImpl.finishConnect(Unknown Source)
    at io.netty.channel.socket.nio.NioSocketChannel.doFinishConnect(NioSocketChannel.java:353)
    at io.netty.channel.nio.AbstractNioChannel$AbstractNioUnsafe.finishConnect(AbstractNioChannel.java:340)
    at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:633)
    at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:580)
    at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:497)
    at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:459)
    at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:858)
    at io.netty.util.concurrent.DefaultThreadFactory$DefaultRunnableDecorator.run(DefaultThreadFactory.java:138)
    at java.lang.Thread.run(Unknown Source)
Caused by: java.net.ConnectException: Connection refused: no further information
    ... 11 more

I've attempted to try-catch AnnotatedConnectException, however there doesn't appear to be any imports to that class. I suspect this class is a private exception in AbstractChannel. I checked the implementation and it is:

private static final class AnnotatedConnectException extends ConnectException {

    private static final long serialVersionUID = 3901958112696433556L;

    AnnotatedConnectException(ConnectException exception, SocketAddress remoteAddress) {
        super(exception.getMessage() + ": " + remoteAddress);
        initCause(exception);
        setStackTrace(exception.getStackTrace());
    }

    @Override
    public Throwable fillInStackTrace() {
        return this;
    }
}

I've also found that I could handle this exception by doing a try-catch with Exception. However, I would like to avoid this if possible, since I feel like it is an awful idea. Any ideas on if this can be done and how?

EDIT: Here is my attempt to catch a ConnectException (the same thing was tried for AnnotatedConnectException. This was done where the ConnectException is):

 public void connectToServer(String host, int port) {
Bootstrap bootstrap = new Bootstrap();
/*
 * Creates/configures the handler group which will handle the connection and keep the connection
 * alive.
 */
EventLoopGroup handlerGroup = new NioEventLoopGroup();
bootstrap.group(handlerGroup).channel(NioSocketChannel.class)
    .option(ChannelOption.SO_KEEPALIVE, true).handler(new ChannelInitializer<SocketChannel>() {
      @Override
      protected void initChannel(SocketChannel socketChannel) {
        /*
         * Handles a connection when it is connected (ie. decoding/encoding packets and
         * handling the packets that the server sends back to the client).
         */
        socketChannel.pipeline().addLast("MapleProtocolDecoder", new MapleProtocolDecoder());
        socketChannel.pipeline().addLast("ServerHandler", new ServerHandler());
        socketChannel.pipeline().addLast("MapleProtocolEncoder", new MapleProtocolEncoder());
      }
    });

try {
  // Connects the client
  ChannelFuture channelFuture = bootstrap.connect(host, port).sync();
  // Waits until the connection is closed
  channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
  handlerGroup.shutdownGracefully();
  System.out.println("[Info] The client has disconnected!");
} catch (ConnectException e) {
  System.out.println( e.getClass().getCanonicalName()); 
  System.out.println("[Info] The server is offline."); 
}

}

Thanks!

2 Answers

Remove sync() and handle the success or failure of the connection using a listener.

try {
    ChannelFuture channelFuture = bootstrap.connect(host, port);
    channelFuture.addListener(f -> {
        if (!f.isSuccess() && f.cause() instanceof ConnectException) {
            System.out.println("[Info] The server is offline.");
        }
    });
    channelFuture.await();
} catch (InterruptedException e) {
    e.printStackTrace();
}

That library is likely provided by your container at runtime and might not be available to you during development without specifically adding it to your project's classpath. But you don't necessarily want to do this because it then creates a dependency on your project to a low level container implementation detail. In this case the netty library might only exist during local development if you end up deploying to another app server (tomcat, jboss, weblogic, etc).

I would suggest simply catching the more generic ConnectException, which AnnotatedConnectException extends. ConnectException is part of the standard Java JDK, so no additional classpath modifications will be required and this will be portable across all app servers.

You are correct in feeling that catching Exception is not a good idea. It's best to catch only the specific exceptions that are to be thrown by the code you are trying to execute so that you can present more accurate message to the user, or in your logs. This article on exception handling techniques might be useful.

EDIT:

If the framework you are calling does not indicate that is can throw a ConnectException, then you might be forced to catch Exception:

    try {
        // your code
    } catch (InterruptedException e) {
       // Do something
    } catch (Exception e) {
       // Do something else 
    }

It's not ideal, but the framework API has given you no choice.

Related