google-site-verification: google61fe8ba583a51912.html
Retrofit: Type-Safe HTTP Client for Java and Android

Retrofit is a popular HTTP client library for Java and Android that turns your HTTP API into a Java interface. It simplifies HTTP calls by handling serialization, deserialization, and execution, making API integration clean and type-safe.


Why Retrofit?

Advantages over traditional HTTP clients:

  • Type-safe - Compile-time checking of API contracts
  • Annotation-based - Declarative API definition
  • Automatic serialization - JSON, XML, Protocol Buffers, etc.
  • Synchronous & Asynchronous support
  • Pluggable - Custom converters and adapters
  • Clean code - Separation of concerns

Core Concepts

  • Interface - Define your API endpoints as Java interface methods
  • Annotations - Specify HTTP methods, headers, parameters
  • Converter - Handle request/response serialization (Gson, Jackson, etc.)
  • Call Adapter - Convert responses to different types (RxJava, Coroutines, etc.)

Getting Started

Dependencies (Maven)

<dependencies> <!-- Retrofit Core --> <dependency> <groupId>com.squareup.retrofit2</groupId> <artifactId>retrofit</artifactId> <version>2.9.0</version> </dependency> <!-- GSON Converter --> <dependency> <groupId>com.squareup.retrofit2</groupId> <artifactId>converter-gson</artifactId> <version>2.9.0</version> </dependency> <!-- Optional: RxJava Adapter --> <dependency> <groupId>com.squareup.retrofit2</groupId> <artifactId>adapter-rxjava3</artifactId> <version>2.9.0</version> </dependency> <!-- Optional: Logging Interceptor --> <dependency> <groupId>com.squareup.okhttp3</groupId> <artifactId>logging-interceptor</artifactId> <version>4.10.0</version> </dependency> </dependencies>

Basic Usage Examples

Example 1: Basic Setup and GET Request

import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; import retrofit2.Retrofit; import retrofit2.converter.gson.GsonConverterFactory; import retrofit2.http.GET; import retrofit2.http.Path; import java.util.List; public class RetrofitBasicDemo { // Define API interface public interface GitHubService { @GET("users/{username}/repos") Call<List<Repository>> listRepos(@Path("username") String username); @GET("users/{username}") Call<User> getUser(@Path("username") String username); } // Data models static class Repository { private String name; private String description; private boolean fork; private int stargazers_count; // Getters and setters public String getName() { return name; } public void setName(String name) { this.name = name; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public boolean isFork() { return fork; } public void setFork(boolean fork) { this.fork = fork; } public int getStargazersCount() { return stargazers_count; } public void setStargazersCount(int stargazers_count) { this.stargazers_count = stargazers_count; } @Override public String toString() { return String.format("Repository{name='%s', description='%s', stars=%d}", name, description, stargazers_count); } } static class User { private String login; private String name; private int public_repos; private int followers; // Getters and setters public String getLogin() { return login; } public void setLogin(String login) { this.login = login; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getPublicRepos() { return public_repos; } public void setPublicRepos(int public_repos) { this.public_repos = public_repos; } public int getFollowers() { return followers; } public void setFollowers(int followers) { this.followers = followers; } @Override public String toString() { return String.format("User{login='%s', name='%s', repos=%d, followers=%d}", login, name, public_repos, followers); } } public static void main(String[] args) { // Create Retrofit instance Retrofit retrofit = new Retrofit.Builder() .baseUrl("https://api.github.com/") .addConverterFactory(GsonConverterFactory.create()) .build(); // Create service instance GitHubService service = retrofit.create(GitHubService.class); // Synchronous call System.out.println("=== Synchronous Call ==="); try { Call<User> call = service.getUser("octocat"); Response<User> response = call.execute(); if (response.isSuccessful()) { User user = response.body(); System.out.println("User: " + user); } else { System.out.println("Error: " + response.code() + " - " + response.message()); } } catch (Exception e) { e.printStackTrace(); } // Asynchronous call System.out.println("\n=== Asynchronous Call ==="); Call<List<Repository>> reposCall = service.listRepos("octocat"); reposCall.enqueue(new Callback<List<Repository>>() { @Override public void onResponse(Call<List<Repository>> call, Response<List<Repository>> response) { if (response.isSuccessful()) { List<Repository> repos = response.body(); System.out.println("Found " + repos.size() + " repositories:"); repos.forEach(repo -> System.out.println(" - " + repo)); } else { System.out.println("Error: " + response.code() + " - " + response.message()); } } @Override public void onFailure(Call<List<Repository>> call, Throwable t) { System.err.println("Request failed: " + t.getMessage()); } }); // Wait for async call to complete try { Thread.sleep(5000); } catch (InterruptedException e) {} } }

Example 2: Comprehensive API Service with Multiple HTTP Methods

import retrofit2.Call; import retrofit2.http.*; import java.util.List; import java.util.Map; public interface UserApiService { // GET with query parameters @GET("users") Call<List<User>> getUsers( @Query("page") int page, @Query("limit") int limit, @Query("sort") String sortBy ); // GET with path parameter @GET("users/{id}") Call<User> getUserById(@Path("id") Long userId); // POST with body @POST("users") Call<User> createUser(@Body User user); // PUT with path parameter and body @PUT("users/{id}") Call<User> updateUser(@Path("id") Long userId, @Body User user); // PATCH with specific fields @PATCH("users/{id}") Call<User> patchUser(@Path("id") Long userId, @Body Map<String, Object> updates); // DELETE @DELETE("users/{id}") Call<Void> deleteUser(@Path("id") Long userId); // GET with dynamic query parameters @GET("users/search") Call<List<User>> searchUsers(@QueryMap Map<String, String> filters); // POST with form data @FormUrlEncoded @POST("users/login") Call<AuthResponse> login( @Field("username") String username, @Field("password") String password ); // GET with headers @GET("users/profile") Call<UserProfile> getProfile(@Header("Authorization") String authToken); // Multiple headers @GET("users/secure") Call<User> getSecureUser( @Header("Authorization") String token, @Header("X-API-Key") String apiKey ); } // Data models class User { private Long id; private String name; private String email; private String role; // Constructors, getters, setters public User() {} public User(String name, String email, String role) { this.name = name; this.email = email; this.role = role; } // Getters and setters... } class AuthResponse { private String token; private User user; // Getters and setters... } class UserProfile { private User user; private Map<String, Object> preferences; // Getters and setters... }

Advanced Features

Example 3: Custom Interceptor and Logging

import okhttp3.OkHttpClient; import okhttp3.logging.HttpLoggingInterceptor; import retrofit2.Retrofit; import retrofit2.converter.gson.GsonConverterFactory; import java.io.IOException; import java.util.concurrent.TimeUnit; public class AdvancedRetrofitSetup { public static Retrofit createRetrofitClient() { // Create logging interceptor HttpLoggingInterceptor logging = new HttpLoggingInterceptor(); logging.setLevel(HttpLoggingInterceptor.Level.BODY); // Create custom interceptor for headers okhttp3.Interceptor authInterceptor = chain -> { okhttp3.Request original = chain.request(); // Add custom headers okhttp3.Request request = original.newBuilder() .header("User-Agent", "MyApp/1.0") .header("Accept", "application/json") .header("Cache-Control", "no-cache") .method(original.method(), original.body()) .build(); return chain.proceed(request); }; // Create OkHttp client with interceptors OkHttpClient okHttpClient = new OkHttpClient.Builder() .addInterceptor(authInterceptor) .addInterceptor(logging) .connectTimeout(30, TimeUnit.SECONDS) .readTimeout(30, TimeUnit.SECONDS) .writeTimeout(30, TimeUnit.SECONDS) .retryOnConnectionFailure(true) .build(); // Build Retrofit with custom client return new Retrofit.Builder() .baseUrl("https://api.example.com/v1/") .client(okHttpClient) .addConverterFactory(GsonConverterFactory.create()) .build(); } // Usage public static void main(String[] args) { Retrofit retrofit = createRetrofitClient(); UserApiService service = retrofit.create(UserApiService.class); // Now all requests will have custom headers and logging } }

Example 4: Error Handling and Custom Response

import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; public class ErrorHandlingExample { public static <T> void executeWithErrorHandling(Call<T> call, ApiCallback<T> callback) { call.enqueue(new Callback<T>() { @Override public void onResponse(Call<T> call, Response<T> response) { if (response.isSuccessful()) { callback.onSuccess(response.body()); } else { try { String errorBody = response.errorBody() != null ? response.errorBody().string() : "Unknown error"; callback.onError(new ApiException( response.code(), "HTTP " + response.code() + ": " + errorBody )); } catch (Exception e) { callback.onError(new ApiException( response.code(), "HTTP " + response.code() + ": " + response.message() )); } } } @Override public void onFailure(Call<T> call, Throwable t) { callback.onError(new ApiException(0, "Network error: " + t.getMessage())); } }); } // Custom callback interface public interface ApiCallback<T> { void onSuccess(T response); void onError(ApiException error); } // Custom exception public static class ApiException extends Exception { private final int statusCode; public ApiException(int statusCode, String message) { super(message); this.statusCode = statusCode; } public int getStatusCode() { return statusCode; } } // Usage example public static void main(String[] args) { Retrofit retrofit = new Retrofit.Builder() .baseUrl("https://api.github.com/") .addConverterFactory(GsonConverterFactory.create()) .build(); GitHubService service = retrofit.create(GitHubService.class); executeWithErrorHandling(service.getUser("nonexistentuser"), new ApiCallback<User>() { @Override public void onSuccess(User response) { System.out.println("Success: " + response); } @Override public void onError(ApiException error) { System.err.println("API Error (" + error.getStatusCode() + "): " + error.getMessage()); } }); } }

Reactive Programming with RxJava

Example 5: RxJava Integration

import retrofit2.Retrofit; import retrofit2.adapter.rxjava3.RxJava3CallAdapterFactory; import retrofit2.converter.gson.GsonConverterFactory; import retrofit2.http.GET; import retrofit2.http.Path; import io.reactivex.rxjava3.core.Observable; import io.reactivex.rxjava3.core.Single; import io.reactivex.rxjava3.schedulers.Schedulers; public class RxJavaRetrofitExample { public interface RxGitHubService { @GET("users/{username}") Single<User> getUserRx(@Path("username") String username); @GET("users/{username}/repos") Observable<Repository> getReposRx(@Path("username") String username); } public static void main(String[] args) throws InterruptedException { Retrofit retrofit = new Retrofit.Builder() .baseUrl("https://api.github.com/") .addConverterFactory(GsonConverterFactory.create()) .addCallAdapterFactory(RxJava3CallAdapterFactory.create()) .build(); RxGitHubService service = retrofit.create(RxGitHubService.class); System.out.println("=== Single Observable (One item) ==="); service.getUserRx("octocat") .subscribeOn(Schedulers.io()) .observeOn(Schedulers.computation()) .map(user -> { // Transform data return String.format("User: %s (%d followers)", user.getName(), user.getFollowers()); }) .subscribe( result -> System.out.println("Success: " + result), error -> System.err.println("Error: " + error.getMessage()) ); System.out.println("\n=== Observable Stream (Multiple items) ==="); service.getReposRx("octocat") .subscribeOn(Schedulers.io()) .observeOn(Schedulers.computation()) .filter(repo -> !repo.isFork()) // Only non-forked repos .take(5) // Limit to first 5 .map(repo -> repo.getName() + " - " + repo.getStargazersCount() + " stars") .subscribe( repo -> System.out.println("Repo: " + repo), error -> System.err.println("Error: " + error.getMessage()), () -> System.out.println("Completed processing repositories") ); // Wait for async operations Thread.sleep(5000); } }

Custom Converters and Serialization

Example 6: Custom GSON Configuration

import com.google.gson.Gson; import com.google.gson.GsonBuilder; import retrofit2.Retrofit; import retrofit2.converter.gson.GsonConverterFactory; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; public class CustomGsonExample { public static Retrofit createCustomGsonRetrofit() { Gson gson = new GsonBuilder() .setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ") .registerTypeAdapter(LocalDateTime.class, new LocalDateTimeAdapter()) .setPrettyPrinting() .serializeNulls() // Include null values .create(); return new Retrofit.Builder() .baseUrl("https://api.example.com/") .addConverterFactory(GsonConverterFactory.create(gson)) .build(); } // Custom adapter for LocalDateTime static class LocalDateTimeAdapter implements JsonSerializer<LocalDateTime>, JsonDeserializer<LocalDateTime> { private final DateTimeFormatter formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME; @Override public JsonElement serialize(LocalDateTime src, Type typeOfSrc, JsonSerializationContext context) { return new JsonPrimitive(formatter.format(src)); } @Override public LocalDateTime deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) { return LocalDateTime.parse(json.getAsString(), formatter); } } }

Testing with MockWebServer

Example 7: Unit Testing with MockWebServer

import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import retrofit2.Retrofit; import retrofit2.converter.gson.GsonConverterFactory; import java.io.IOException; import java.util.List; import static org.junit.jupiter.api.Assertions.*; public class GitHubServiceTest { private MockWebServer mockWebServer; private GitHubService service; @BeforeEach void setUp() throws IOException { mockWebServer = new MockWebServer(); mockWebServer.start(); Retrofit retrofit = new Retrofit.Builder() .baseUrl(mockWebServer.url("/")) .addConverterFactory(GsonConverterFactory.create()) .build(); service = retrofit.create(GitHubService.class); } @AfterEach void tearDown() throws IOException { mockWebServer.shutdown(); } @Test void testGetUserSuccess() throws IOException { // Arrange String jsonResponse = """ { "login": "testuser", "name": "Test User", "public_repos": 42, "followers": 100 } """; mockWebServer.enqueue(new MockResponse() .setBody(jsonResponse) .setResponseCode(200) .addHeader("Content-Type", "application/json")); // Act Call<User> call = service.getUser("testuser"); Response<User> response = call.execute(); // Assert assertTrue(response.isSuccessful()); User user = response.body(); assertNotNull(user); assertEquals("testuser", user.getLogin()); assertEquals("Test User", user.getName()); assertEquals(42, user.getPublicRepos()); } @Test void testGetUserNotFound() { // Arrange mockWebServer.enqueue(new MockResponse() .setResponseCode(404) .setBody("{\"message\": \"Not Found\"}")); // Act Call<User> call = service.getUser("nonexistent"); // Assert try { Response<User> response = call.execute(); assertFalse(response.isSuccessful()); assertEquals(404, response.code()); } catch (IOException e) { fail("Request failed", e); } } }

Best Practices

  1. Base URL Management
public class ApiClient { private static final String BASE_URL = "https://api.example.com/v1/"; private static Retrofit retrofit; public static synchronized Retrofit getClient() { if (retrofit == null) { retrofit = new Retrofit.Builder() .baseUrl(BASE_URL) .addConverterFactory(GsonConverterFactory.create()) .build(); } return retrofit; } }
  1. Singleton Pattern
public class ApiServiceProvider { private static UserApiService instance; public static synchronized UserApiService getService() { if (instance == null) { Retrofit retrofit = new Retrofit.Builder() .baseUrl("https://api.example.com/") .addConverterFactory(GsonConverterFactory.create()) .build(); instance = retrofit.create(UserApiService.class); } return instance; } }
  1. Error Handling Strategy
public class ApiResponse<T> { private final T data; private final String error; private final int statusCode; // Factory methods for success/error public static <T> ApiResponse<T> success(T data) { return new ApiResponse<>(data, null, 200); } public static <T> ApiResponse<T> error(String error, int statusCode) { return new ApiResponse<>(null, error, statusCode); } }

Conclusion

Retrofit provides a clean, type-safe way to consume HTTP APIs in Java:

Key Features:

  • Declarative API - Define endpoints as Java interfaces
  • Type Safety - Compile-time checking of API contracts
  • Flexible Serialization - Support for JSON, XML, Protobuf, etc.
  • Synchronous & Asynchronous - Both blocking and non-blocking calls
  • Interceptors - For logging, authentication, and caching
  • Testing Support - Easy integration with MockWebServer

When to Use Retrofit:

  • REST API consumption in Java/Android applications
  • Type-safe API clients
  • Applications requiring clean separation of network logic
  • Projects using reactive programming (RxJava, Coroutines)

Performance Benefits:

  • Connection pooling and reuse
  • Efficient serialization/deserialization
  • Minimal boilerplate code
  • Optimized for mobile and server applications

Retrofit has become the de facto standard for HTTP communication in the Java ecosystem, particularly in Android development, due to its simplicity, power, and extensive community support.


Production Tips: Always use interceptors for logging in development, implement proper error handling, consider using Hystrix or Resilience4j for circuit breaking, and cache responses where appropriate to improve performance and reduce network calls.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper