Se usar o join no final, não terá muita diferença, pois ambos serão bloqueantes. Então você poderia usar qualquer um. A vantagem seria usar sem o join no final, ai teriamos uma chamada de fato assincrona.
Basicamente, da maneira como foi feito, usando apenas o método send, será uma chamada bloqueante, ou seja, o seu código não irá continuar até que todo esse fluxo do client seja finalizado.
Já o sendAsync(sem o join), ele acontece de maneira não bloqueante/assíncrona, ou seja, vai enviar a requisição para o servidor, mas o resto do seu código continua executando, até que finalmente o servidor devolva uma resposta. Quando o servidor devolver uma resposta, ele cai no fluxo de thenApply.
Ex (send):print(1)
print(2)
send()
print(3)
print(4)
Fluxo de execução(send)
1
2
resposta do servidor
3
4
Nesse caso, após a chamada pro servidor, o programa só printou 3 e 4 após o servidor responder.
Exemplo(async)
print(1)
print(2)
sendAsync()
print(3)
print(4)
Fluxo de execução (async)
1
2
3
resposta do servidor
4
Nesse caso, após a chamada assíncrona para o servidor, o código continuou executando, printando o valor 3 e quando o servidor respondeu, printou a resposta e após a resposta o fluxo continua. (Aqui estamos imaginando que o servidor respondeu logo após o print do 3, por isso a resposta do servidor acontece antes do 4, mas poderia acontecer de printar o 4 antes da resposta do servidor tbm)