Try this:
public static void main(String[] args) {
    try {
        final CompletableFuture<String> retrieveName = CompletableFuture.supplyAsync(() -> {
            System.out.println("running");
            int i = 0;
            if (i == 0) {
                throw new RuntimeException("ding");
            }
            return "test";
        }).exceptionally(it -> {
            if (it.getMessage().contains("ding")) {
                throw (RuntimeException) it;
            }
            System.out.println(it.getMessage());
            return "empty";
        }).thenApply(it -> {
            System.out.println("last block" + it);
            return "dummy";
        });
        retrieveName.join();
    } catch (Exception e) {
        System.out.println("main() exception, cause=" + e.getCause());
    }
}
This is the output:
running
main() exception, cause=java.lang.RuntimeException: ding
I made 3 small changes to your code:
- Wrapped it all in a try-catch
 
- Threw a RuntimeException in exceptionally() for the "ding" exception.
 
- Added a call to retrieveName.join(). From the Javadoc for CompletableFuture.join():
 
public T join()
Returns the result value when complete, or throws an (unchecked) exception if completed exceptionally.
Update based on OP feedback ------->
Lets say if i want to throw runtime exception and want application to
  stop. Basically if i throw Runtime exception , it shouldn't proceed to
  next block in pipeline. How should i do that.
You can achieve what you want with just 2 changes to your code:
[1] Completely remove the exceptionally() callback so the CompletableFuture (CF) terminates with an exception. In exceptionally() in the OP code the exception was being swallowed rather than rethrown, and returning a CF, so the thenApply() method was still performed.
[2] Add a call to retrieveName.join() at the end of main(). This is a blocking call, but since the thread had terminated with an exception that 's not really relevant for the sample code. The join() method will extract the thrown RunTimeException and re-throw it, wrapped in a CompletionException.
Here's your modified code:
 public static void main(String[] args) {
    final CompletableFuture<String> retrieveName = CompletableFuture.supplyAsync(() -> {
        System.out.println("running");
        int i = 0;
        if(i == 0) {
            throw new RuntimeException("ding");
        }
        return "test";
    }).thenApply(it -> {
        System.out.println("last block" + it);
        return "dummy";
    });
    retrieveName.join();
}
Notes:
- This is not how to do things in Production. The blocking call from join() was not a problem here, but could be for a long running CF. But you obviously can't extract the exception from the CF until it is complete, so it makes sense that the join() call blocks. 
 
- Always bear in mind that main() is not running in the same thread(s) as the CF.
 
- An alternative approach (if viable) might be to handle all the necessary post-exception actions (logging, etc,) within exceptionally() and then terminate normally with a suitable return value (e.g. "Exception handled!") rather than propagating the exception.
 
- You can check whether the CF is still running by calling the non-blocking isDone() method. You can also check whether the CF ended with an exception (isCompletedExceptionally()) or was cancelled(isCancelled()).