Java’s REST clients are a minefield, and the standard HttpClient is often the least surprising, but Feign and RestTemplate have their own, often hidden, advantages.
Let’s see what happens when we actually use them. Here’s a simple setup: a mock server running on port 8080, and clients that want to call its /hello endpoint.
// Mock Server (using WireMock)
import com.github.tomakehurst.wiremock.WireMockServer;
import static com.github.tomakehurst.wiremock.client.WireMock.*;
public class MockServer {
public static void main(String[] args) {
WireMockServer wireMockServer = new WireMockServer(8080);
wireMockServer.start();
wireMockServer.stubFor(get(urlEqualTo("/hello"))
.willReturn(aResponse()
.withHeader("Content-Type", "text/plain")
.withBody("Hello from Mock Server!")));
System.out.println("Mock server running on http://localhost:8080");
}
}
Now, let’s call this from our clients.
1. java.net.HttpClient (built-in since Java 11)
This is the low-level, "correct" way to do it. It’s asynchronous by default, which is great for performance but can feel verbose.
// HttpClient Client
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.concurrent.CompletableFuture;
public class HttpClientExample {
public static void main(String[] args) throws Exception {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("http://localhost:8080/hello"))
.build();
CompletableFuture<HttpResponse<String>> responseFuture = client.sendAsync(request, HttpResponse.BodyHandlers.ofString());
responseFuture.thenAccept(response -> {
System.out.println("HttpClient Response Status: " + response.statusCode());
System.out.println("HttpClient Response Body: " + response.body());
}).exceptionally(e -> {
System.err.println("HttpClient Error: " + e.getMessage());
return null;
});
// Keep the main thread alive to see the async result
Thread.sleep(5000);
}
}
2. RestTemplate (Spring Framework)
This is the classic Spring way. It’s synchronous and very convenient for simple, blocking operations. It hides a lot of the HTTP plumbing.
// RestTemplate Client (requires Spring Boot Starter Web)
import org.springframework.web.client.RestTemplate;
public class RestTemplateExample {
public static void main(String[] args) {
RestTemplate restTemplate = new RestTemplate();
String url = "http://localhost:8080/hello";
try {
String response = restTemplate.getForObject(url, String.class);
System.out.println("RestTemplate Response Body: " + response);
} catch (Exception e) {
System.err.println("RestTemplate Error: " + e.getMessage());
}
}
}
3. Feign (Netflix OSS)
Feign is a declarative HTTP client. You define an interface, and Feign generates an implementation that makes the actual HTTP calls. This is incredibly powerful for reducing boilerplate and improving code readability.
// Feign Client (requires Feign dependencies)
import feign.Feign;
import feign.RequestLine;
import feign.codec.StringDecoder;
import feign.Logger;
public class FeignExample {
// Define the interface for your API
interface MyApiClient {
@RequestLine("GET /hello")
String sayHello();
}
public static void main(String[] args) {
MyApiClient apiClient = Feign.builder()
.decoder(new StringDecoder()) // How to decode the response body
.logLevel(Logger.Level.FULL) // Log requests and responses
.target(MyApiClient.class, "http://localhost:8080");
try {
String response = apiClient.sayHello();
System.out.println("Feign Response Body: " + response);
} catch (Exception e) {
System.err.println("Feign Error: " + e.getMessage());
}
}
}
The core problem Feign and RestTemplate solve is reducing boilerplate and providing a more domain-specific abstraction over the raw HTTP communication. HttpClient is powerful but requires you to manage request building, response parsing, and asynchronous operations manually. RestTemplate simplifies this by offering direct methods like getForObject, while Feign takes it a step further by letting you define an API interface, making your client code look like you’re calling local methods.
Internally, RestTemplate by default uses HttpComponents (an older library) or OkHttp for its underlying HTTP requests, but you can configure it to use java.net.HttpClient. Feign is designed to be transport-agnostic, meaning it can use various HTTP client implementations (like OkHttp, Apache HttpClient, or even java.net.HttpClient via adapters) under the hood. You can even plug in custom encoders and decoders for request/response bodies, and interceptors for cross-cutting concerns like authentication or logging.
The exact levers you control depend on the library. With HttpClient, you’re managing HttpRequest and HttpResponse objects, setting headers, body, and choosing BodyHandlers. With RestTemplate, you’re using methods like getForObject, postForEntity, and configuring HttpMessageConverters for serialization/deserialization. Feign gives you the most abstract control: defining the API interface, choosing Decoders and Encoders, setting Contracts (how method annotations map to HTTP requests), and configuring Logger levels.
When you use Feign, the target method is where the magic happens. It takes your interface and the base URL, and returns a dynamic proxy. This proxy intercepts calls to your interface methods. When you call apiClient.sayHello(), the proxy uses the @RequestLine("GET /hello") annotation to construct an HttpRequest using the underlying HTTP client, sends it, receives the HttpResponse, and then uses the configured Decoder (in our case, StringDecoder) to turn the response body into the String that sayHello() is expected to return. This dynamic proxy mechanism is what makes Feign so powerful for generating client implementations from API definitions.
The next concept to explore is how these clients handle errors and different HTTP status codes gracefully, and how to integrate them into Spring Boot applications for automatic configuration.