Dave, as Michael's stacktrace includes QueryExecutorImpl.processResults -> PGStream.ReceiveChar, that means the issue has nothing to do with TCP deadlock. The client somehow fails to detect socket failure => it looks like OS-TCP timeouts => it looks like a misconfigured firewall (e.g. the one that silently drops connections that are idle for more than 30 seconds).
Michael, can you please clarify if your setup involves firewalls in-between app and PG?
TCP deadlock symptom is "JDBC is trying to send more data, while the backend is trying to send response".
It is strange that in Michael's case pgjdbc thinks the connection is still alive.
We can try to protect from that kind of case by adding java-level interrupt (and/or set socket timeout to some finite value). However, adding proper interrupt logic does not seem easy, so I would still appreciate more details on the case (and/or reproducer).
Vladimir