Once Spring is concerned, there is more Spring-native and elegant way to do the parallel calls than to use Java barebone Executors.newVirtualThreadPerTaskExecutor
.
If your method performGetRequest
is advisable method, i.e. declared in a Spring bean and public, then you could annotate it with @Async
, and then your second way
Future<String> response1 = performGetRequest(uri1);
Future<String> response2 = performGetRequest(uri2);
return response1.get() + response2.get();
will execute in parallel.
For example,
@Service
public class GetRequestPerformer {
@Async
public Future<String> performGetRequest(String url) {
... invoke RestClient
return CompletableFuture.completedFuture(result);
}
}
Note that @Async
should be properly configured:
@Configuration
@EnableAsync
public class AppConfig {
}
and a call to performGetRequest
should be advised (proxied): an instance of GetRequestPerformer
should be injected:
@Autowired
private GetRequestPerformer performer;
Under the hood, Spring (by default) uses SimpleAsyncTaskExecutor
, which implements Spring's TaskExecutor
and does not have all rich capabilities of ExecutorService
(they are unlikely needed in your context), but still implements AutoClosable
, has a lifecycle of Spring Application Context and will attempt to gracefully close all running threads when the context closes.
If you prefer bareboning, a completely barebone way (which in fact demonstrates a simplicity of parallelism with virtual threads), is:
final String[] results = new String[2];
Thread t1 = Thread.ofVirtual().start(() -> results[0] = performGetRequest(url1));
Thread t2 = Thread.ofVirtual().start(() -> results[1] = performGetRequest(url2));
t1.join();
t2.join();
return results[0] + results[1];
and then performGetRequest
method needs not to be annotated with @Async
and returns plain String instead of Future
. This approach does not need any Spring/Spring Boot support and a configuration of spring.threads.virtual.enabled
property.