package com.repro; import static org.junit.Assert.*; import static org.springframework.http.HttpHeaders.*; import static org.springframework.http.HttpMethod.*; import java.util.Base64; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.boot.test.WebIntegrationTest; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.ResponseEntity; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.util.concurrent.ListenableFuture; import org.springframework.web.client.AsyncRestTemplate; import org.springframework.web.client.RestTemplate; @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = AsyncrestremplatebugApplication.class) @WebIntegrationTest("server.port=8080") public class AsyncrestremplatebugApplicationTests { // exception is thrown @Test(expected=SystemException.class) public void works() throws Exception { AsyncRestTemplate asyncRestTemplate = new AsyncRestTemplate(); HttpHeaders headers = new HttpHeaders(); String authHeader = "Basic " + Base64.getEncoder().encode(("user:secret").getBytes()); headers.add(AUTHORIZATION, authHeader); ListenableFuture<ResponseEntity<String>> future = asyncRestTemplate.exchange("http://localhost:8080/sleepingendpoint", GET, new HttpEntity<String>(headers), String.class); // NOTE - only difference is that this sleeps Thread.sleep(5000); future.addCallback(s -> {}, f -> { System.err.println("CALLED ERROR HANDLER"); throw new SystemException(f); // <-- this fails in the test thread }); // the below is never reached ResponseEntity<String> resp = future.get(); assertTrue(resp.getStatusCode().is2xxSuccessful()); } // exception is not thrown @Test(expected=SystemException.class) public void bug() throws Exception { AsyncRestTemplate asyncRestTemplate = new AsyncRestTemplate(); HttpHeaders headers = new HttpHeaders(); String authHeader = "Basic " + new String(Base64.getEncoder().encode(("user:secret").getBytes())); headers.add(AUTHORIZATION, authHeader); ListenableFuture<ResponseEntity<String>> future = asyncRestTemplate.exchange("http://localhost:8080/sleepingendpoint", GET, new HttpEntity<String>(headers), String.class); future.addCallback(s -> {}, f -> { System.err.println("BUG -> SystemException not propaging up the chain <- BUG"); throw new SystemException(f); // <-- this fails in executor thread }); ResponseEntity<String> resp = future.get(); // <-- this fails with ExecutionException assertTrue(resp.getStatusCode().is2xxSuccessful()); } @Test public void testRestTemplate() throws Exception { RestTemplate restTemplate = new RestTemplate(); HttpHeaders headers = new HttpHeaders(); String authHeader = "Basic " + new String(Base64.getEncoder().encode(("user:password").getBytes())); headers.add(AUTHORIZATION, authHeader); ResponseEntity<String> resp = restTemplate.exchange("http://localhost:8080/sleepingendpoint", GET, new HttpEntity<String>(headers), String.class); assertTrue(resp.getStatusCode().is2xxSuccessful()); } }