Вот что я использовал в итоге:
public ResponseEntity<StreamingResponseBody> example() throws IOException {
Process p = new ProcessBuilder(/*...*/).redirectErrorStream(true).start();
//better to use a static executor, but here I'm creating a new one every time
Executors.newSingleThreadScheduledExecutor().schedule(() -> {
if (p != null && p.isAlive()) {
p.destroyForcibly(); //Destroy the process after a timeout period if it's still running
}
}, 10, TimeUnit.SECONDS);
//No need to read err stream separately, it's redirected to stdout due to "redirectErrorStream"
InputStream inputStream = p.getInputStream();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
response.setHeader("Content-Disposition", "attachment; filename=foo.gz");
return new ResponseEntity<>(new StreamingResponseBody() {
@Override
public void writeTo(OutputStream outputStream) throws IOException {
try (GZIPOutputStream gzOut = new GZIPOutputStream(outputStream)) {
while (p.isAlive()) {
inputStream.transferTo(gzOut);
gzOut.flush();
}
}
}
}, headers, HttpStatus.OK);
}