DEV Community

Cover image for System Design: How to Build a Networking Library Like Retrofit or OkHttp
Padmakar Android
Padmakar Android

Posted on

System Design: How to Build a Networking Library Like Retrofit or OkHttp

System Design: How to Build a Networking Library Like Retrofit or OkHttp

Networking is one of the most important infrastructure layers in a modern Android application.

Most Android developers use libraries such as Retrofit and OkHttp without thinking about everything happening behind a simple API call:

api.getUsers()
Enter fullscreen mode Exit fullscreen mode

Under the hood, a production networking stack may need to handle:

  • HTTP and HTTPS
  • Request construction
  • URL resolution
  • DNS
  • TCP/TLS connections
  • Connection pooling
  • Interceptors
  • Authentication
  • Serialization
  • Coroutines
  • Threading
  • Timeouts
  • Cancellation
  • Retries
  • HTTP caching
  • Response parsing
  • Error handling
  • Certificate validation
  • Observability

This article designs a networking library from scratch and explains the architecture behind a Retrofit/OkHttp-style system.

The goal is not to recreate Retrofit or OkHttp internally. The goal is to understand the system-design principles that make a networking library reliable, reusable, testable, and scalable.


1. What Are We Building?

We want an Android networking library that allows developers to write:

interface UserApi {

    @GET("/users")
    suspend fun getUsers(): List<User>
}
Enter fullscreen mode Exit fullscreen mode

Then:

val users = api.getUsers()
Enter fullscreen mode Exit fullscreen mode

Instead of manually writing:

val request = Request.Builder()
    .url("https://example.com/users")
    .get()
    .build()

val response = client.newCall(request).execute()
Enter fullscreen mode Exit fullscreen mode

Our library will convert a high-level API definition into an executable HTTP request.


2. Requirements

Functional Requirements

The library should support:

  1. GET, POST, PUT, PATCH and DELETE.
  2. Query parameters.
  3. Path parameters.
  4. Headers.
  5. Request bodies.
  6. Response bodies.
  7. JSON serialization/deserialization.
  8. Interceptors.
  9. Authentication.
  10. Logging.
  11. Coroutines.
  12. Request cancellation.
  13. Timeouts.
  14. Retry policies.
  15. HTTP caching.
  16. TLS/HTTPS.
  17. Connection pooling.
  18. Error handling.
  19. Multipart requests.
  20. Streaming responses.

3. Non-Functional Requirements

The networking library should be:

  • Fast
  • Thread-safe
  • Memory efficient
  • Secure
  • Testable
  • Modular
  • Extensible
  • Coroutine-friendly
  • Lifecycle-aware
  • Observable
  • Suitable for high request concurrency

4. High-Level Architecture

The architecture can be divided into two major layers.

                    Android Application
                           |
                           v
                  API Interface Layer
                           |
                           v
                  Request Builder
                           |
                           v
                  Interceptor Chain
                           |
                           v
                  HTTP Client Engine
                           |
             +-------------+-------------+
             |                           |
             v                           v
       Connection Manager            Dispatcher
             |                           |
             v                           v
       DNS / TCP / TLS             Coroutine / Threads
             |
             v
           Server
Enter fullscreen mode Exit fullscreen mode

A more complete pipeline:

flowchart LR

    APP["Android App"]

    APP --> API["API Interface"]

    API --> FACTORY["Request Factory"]

    FACTORY --> INTERCEPTOR["Interceptor Chain"]

    INTERCEPTOR --> AUTH["Auth"]
    INTERCEPTOR --> LOG["Logging"]
    INTERCEPTOR --> RETRY["Retry"]

    RETRY --> CLIENT["HTTP Client"]

    CLIENT --> DISPATCHER["Dispatcher"]

    DISPATCHER --> DNS["DNS"]

    DNS --> TLS["TCP / TLS"]

    TLS --> POOL["Connection Pool"]

    POOL --> SERVER["HTTP Server"]

    SERVER --> RESPONSE["HTTP Response"]

    RESPONSE --> SERIALIZER["Serializer"]

    SERIALIZER --> RESULT["Application Result"]

5. Core Components

A possible package structure:

networking/
│
├── api/
│   ├── GET.kt
│   ├── POST.kt
│   ├── PUT.kt
│   ├── DELETE.kt
│   └── ApiService.kt
│
├── request/
│   ├── Request.kt
│   ├── RequestBuilder.kt
│   └── RequestFactory.kt
│
├── response/
│   ├── Response.kt
│   └── ResponseParser.kt
│
├── interceptor/
│   ├── Interceptor.kt
│   ├── AuthInterceptor.kt
│   ├── LoggingInterceptor.kt
│   └── RetryInterceptor.kt
│
├── http/
│   ├── HttpClient.kt
│   ├── Dispatcher.kt
│   ├── ConnectionPool.kt
│   └── Call.kt
│
├── serialization/
│   ├── Converter.kt
│   ├── JsonConverter.kt
│   └── SerializationFactory.kt
│
├── security/
│   ├── TlsConfig.kt
│   └── CertificatePinning.kt
│
└── cache/
    └── HttpCache.kt
Enter fullscreen mode Exit fullscreen mode

6. API Interface Layer

The developer should be able to define APIs declaratively.

For example:

interface UserApi {

    @GET("/users")
    suspend fun getUsers(): List<User>

    @GET("/users/{id}")
    suspend fun getUser(
        @Path("id") id: String
    ): User

    @POST("/users")
    suspend fun createUser(
        @Body request: CreateUserRequest
    ): User
}
Enter fullscreen mode Exit fullscreen mode

The annotations contain metadata.

Conceptually:

@GET("/users")
      |
      v
HTTP Method = GET
Path = /users
Return Type = List<User>
Enter fullscreen mode Exit fullscreen mode

7. How Does the Library Read Annotations?

At API creation time, the library can inspect the service definition and create a request model.

For example:

data class EndpointDefinition(
    val method: HttpMethod,
    val path: String,
    val parameters: List<ParameterDefinition>,
    val returnType: Type
)
Enter fullscreen mode Exit fullscreen mode

Then:

API Method
    ↓
EndpointDefinition
    ↓
RequestFactory
    ↓
HttpRequest
Enter fullscreen mode Exit fullscreen mode

A production implementation may use generated adapters or code generation instead of reflection for performance and startup efficiency.


8. Request Model

The internal HTTP request should be independent from the API interface.

data class HttpRequest(
    val method: HttpMethod,
    val url: String,
    val headers: Map<String, String>,
    val body: RequestBody?
)
Enter fullscreen mode Exit fullscreen mode

Example:

HttpRequest(
    method = HttpMethod.GET,
    url = "https://api.example.com/users",
    headers = mapOf(
        "Authorization" to "Bearer token"
    ),
    body = null
)
Enter fullscreen mode Exit fullscreen mode

This separation is important because the HTTP engine should not care whether the request came from annotations, a manual builder, or generated code.


9. Request Builder

The request builder converts high-level API metadata into an HTTP request.

class RequestBuilder(
    private val baseUrl: String
) {

    fun build(
        endpoint: EndpointDefinition,
        arguments: Map<String, Any?>
    ): HttpRequest {

        // Resolve path parameters
        // Add query parameters
        // Add headers
        // Serialize body

        TODO()
    }
}
Enter fullscreen mode Exit fullscreen mode

For:

@GET("/users/{id}")
suspend fun getUser(
    @Path("id") id: String
): User
Enter fullscreen mode Exit fullscreen mode

The resulting URL becomes:

https://api.example.com/users/42
Enter fullscreen mode Exit fullscreen mode

10. Interceptor Architecture

Interceptors are one of the most powerful concepts in a networking library.

They allow us to modify or observe requests and responses without changing the HTTP engine.

Example:

Request
  |
  v
AuthInterceptor
  |
  v
LoggingInterceptor
  |
  v
RetryInterceptor
  |
  v
HTTP Client
Enter fullscreen mode Exit fullscreen mode

11. Interceptor Interface

A simplified interface:

interface Interceptor {

    suspend fun intercept(
        chain: Chain
    ): HttpResponse

    interface Chain {

        val request: HttpRequest

        suspend fun proceed(
            request: HttpRequest
        ): HttpResponse
    }
}
Enter fullscreen mode Exit fullscreen mode

An authentication interceptor:

class AuthInterceptor(
    private val tokenProvider: TokenProvider
) : Interceptor {

    override suspend fun intercept(
        chain: Interceptor.Chain
    ): HttpResponse {

        val request = chain.request.copy(
            headers = chain.request.headers +
                ("Authorization" to
                    "Bearer ${tokenProvider.token()}")
        )

        return chain.proceed(request)
    }
}
Enter fullscreen mode Exit fullscreen mode

12. Interceptor Chain

Suppose we have:

Auth
Logging
Retry
Enter fullscreen mode Exit fullscreen mode

The execution becomes:

Application
    |
    v
AuthInterceptor
    |
    v
LoggingInterceptor
    |
    v
RetryInterceptor
    |
    v
HTTP Engine
Enter fullscreen mode Exit fullscreen mode

Responses travel back through the chain:

HTTP Engine
    |
    v
Retry
    |
    v
Logging
    |
    v
Auth
    |
    v
Application
Enter fullscreen mode Exit fullscreen mode

This is effectively a chain-of-responsibility design.


13. Authentication

Authentication should be implemented as an interceptor rather than inside the HTTP engine.

class BearerAuthInterceptor(
    private val tokenProvider: TokenProvider
) : Interceptor {

    override suspend fun intercept(
        chain: Interceptor.Chain
    ): HttpResponse {

        val token = tokenProvider.getToken()

        val request = chain.request.copy(
            headers = chain.request.headers +
                ("Authorization" to "Bearer $token")
        )

        return chain.proceed(request)
    }
}
Enter fullscreen mode Exit fullscreen mode

This keeps authentication replaceable.


14. Token Refresh

A production networking library may need:

Request
   |
   v
401 Unauthorized
   |
   v
Refresh Token
   |
   v
Retry Original Request
Enter fullscreen mode Exit fullscreen mode

But there is an important concurrency problem.

Suppose 20 requests simultaneously receive 401.

We should avoid:

20 requests
   ↓
20 token refresh calls
Enter fullscreen mode Exit fullscreen mode

Instead:

20 requests
   ↓
Single token refresh
   ↓
New token
   ↓
20 requests retry
Enter fullscreen mode Exit fullscreen mode

A Mutex or single-flight mechanism can coordinate token refresh.


15. Serialization

The HTTP layer receives bytes.

The application wants objects.

HTTP bytes
    ↓
JSON
    ↓
Deserializer
    ↓
Kotlin Object
Enter fullscreen mode Exit fullscreen mode

Create a converter abstraction:

interface Converter {

    fun serialize(
        value: Any
    ): ByteArray

    fun <T> deserialize(
        bytes: ByteArray,
        type: Type
    ): T
}
Enter fullscreen mode Exit fullscreen mode

Implementations could use:

Kotlin Serialization
Moshi
Gson
Custom binary formats
Enter fullscreen mode Exit fullscreen mode

The HTTP engine should not be tightly coupled to one serializer.


16. Response Model

A useful internal response:

data class HttpResponse(
    val code: Int,
    val headers: Map<String, String>,
    val body: ByteArray?
)
Enter fullscreen mode Exit fullscreen mode

Then a response converter transforms:

HttpResponse
     ↓
Converter
     ↓
User
Enter fullscreen mode Exit fullscreen mode

17. Dispatcher and Concurrency

The networking library may execute many requests concurrently.

For example:

Request A
Request B
Request C
Request D
Request E
Enter fullscreen mode Exit fullscreen mode

We need bounded concurrency.

A dispatcher can control:

Maximum concurrent requests
Per-host concurrency
Queueing
Priorities
Cancellation
Enter fullscreen mode Exit fullscreen mode

Conceptually:

                    Dispatcher
                       |
          +------------+------------+
          |            |            |
       Worker 1     Worker 2     Worker 3
          |            |            |
       Request A    Request B    Request C
Enter fullscreen mode Exit fullscreen mode

Unbounded concurrency can exhaust resources.


18. Kotlin Coroutines

The public API can expose:

suspend fun getUsers(): List<User>
Enter fullscreen mode Exit fullscreen mode

Internally:

suspend fun execute(
    request: HttpRequest
): HttpResponse
Enter fullscreen mode Exit fullscreen mode

The caller can use:

viewModelScope.launch {

    val users = api.getUsers()

}
Enter fullscreen mode Exit fullscreen mode

Cancellation should propagate through the entire stack.

ViewModel
   ↓ cancel
Coroutine
   ↓
Call
   ↓
HTTP request
   ↓
Socket
Enter fullscreen mode Exit fullscreen mode

19. Timeout Management

Networking requires different timeout categories.

Connect Timeout
Read Timeout
Write Timeout
Call Timeout
Enter fullscreen mode Exit fullscreen mode

Example configuration:

data class TimeoutConfig(
    val connectMillis: Long = 10_000,
    val readMillis: Long = 30_000,
    val writeMillis: Long = 30_000,
    val callMillis: Long = 60_000
)
Enter fullscreen mode Exit fullscreen mode

Different applications may need different values.


20. Retry Strategy

Not every failure should be retried.

Potential retry candidates:

Connection timeout
Temporary network failure
HTTP 503
HTTP 502
HTTP 504
Enter fullscreen mode Exit fullscreen mode

Potential non-retry cases:

400 Bad Request
401 Unauthorized
403 Forbidden
404 Not Found
Validation error
Enter fullscreen mode Exit fullscreen mode

A retry policy can be abstracted:

interface RetryPolicy {

    fun shouldRetry(
        attempt: Int,
        error: Throwable?
    ): Boolean

    fun delayMillis(
        attempt: Int
    ): Long
}
Enter fullscreen mode Exit fullscreen mode

Exponential backoff:

Attempt 1 → 250 ms
Attempt 2 → 500 ms
Attempt 3 → 1000 ms
Enter fullscreen mode Exit fullscreen mode

Production systems should also use jitter.


21. Idempotency Matters

Blindly retrying every HTTP request is dangerous.

For example:

POST /payments
Enter fullscreen mode Exit fullscreen mode

If the server processes the request but the client loses the response, retrying may create a duplicate operation.

Therefore retry behavior should consider:

HTTP method
Idempotency
Server semantics
Request headers
Application policy
Enter fullscreen mode Exit fullscreen mode

For sensitive operations, idempotency keys can help:

Idempotency-Key: abc-123
Enter fullscreen mode Exit fullscreen mode

22. DNS

Before connecting to a server:

api.example.com
       ↓
DNS
       ↓
IP address
Enter fullscreen mode Exit fullscreen mode

A networking stack needs to account for DNS resolution and caching.

Conceptually:

interface DnsResolver {

    suspend fun resolve(
        hostname: String
    ): List<InetAddress>
}
Enter fullscreen mode Exit fullscreen mode

A custom resolver can support advanced use cases, but the default system resolver should generally be preferred unless there is a specific requirement.


23. TCP and TLS

For HTTPS:

Application
    ↓
HTTP
    ↓
TLS
    ↓
TCP
    ↓
Internet
    ↓
Server
Enter fullscreen mode Exit fullscreen mode

TLS provides:

  • Encryption
  • Server authentication
  • Integrity

The networking layer should rely on the platform's trusted TLS implementation rather than implementing cryptography itself.


24. Certificate Pinning

Certificate pinning can restrict which certificates or public keys are trusted for a particular host.

Conceptually:

Request
   ↓
TLS handshake
   ↓
Server certificate
   ↓
Pinned identity verification
   ↓
Connection accepted/rejected
Enter fullscreen mode Exit fullscreen mode

Pinning must be operated carefully because certificate rotation can otherwise break connectivity.


25. Connection Pooling

Creating a new TCP/TLS connection for every request is expensive.

Without pooling:

Request A → TCP → TLS → HTTP → Close

Request B → TCP → TLS → HTTP → Close
Enter fullscreen mode Exit fullscreen mode

With pooling:

TCP
 ↓
TLS
 ↓
Connection Pool
 ├── Request A
 ├── Request B
 └── Request C
Enter fullscreen mode Exit fullscreen mode

Benefits:

  • Lower latency
  • Less CPU work
  • Fewer TLS handshakes
  • Better throughput

26. HTTP/2 and HTTP/3

Modern networking libraries should be designed so the transport layer can evolve.

HTTP/2 provides:

Multiplexing
Header compression
Stream prioritization
Enter fullscreen mode Exit fullscreen mode

HTTP/3 uses:

QUIC
UDP
Enter fullscreen mode Exit fullscreen mode

The high-level API should not care which transport is used.

API
 ↓
HTTP abstraction
 ↓
Transport
 ├── HTTP/1.1
 ├── HTTP/2
 └── HTTP/3
Enter fullscreen mode Exit fullscreen mode

27. HTTP Caching

Caching can reduce network traffic.

Important headers include:

Cache-Control
ETag
Last-Modified
Expires
If-None-Match
If-Modified-Since
Enter fullscreen mode Exit fullscreen mode

Example:

First request
    ↓
200 OK
ETag: abc123
Enter fullscreen mode Exit fullscreen mode

Later:

If-None-Match: abc123
Enter fullscreen mode Exit fullscreen mode

Server:

304 Not Modified
Enter fullscreen mode Exit fullscreen mode

The client can reuse its cached representation.


28. Error Handling

Don't expose only:

Exception
Enter fullscreen mode Exit fullscreen mode

A useful error model distinguishes categories.

sealed interface NetworkError {

    data object Timeout : NetworkError

    data object NoConnection : NetworkError

    data class Http(
        val code: Int
    ) : NetworkError

    data object Serialization : NetworkError

    data object Cancellation : NetworkError

    data class Unknown(
        val cause: Throwable
    ) : NetworkError
}
Enter fullscreen mode Exit fullscreen mode

This makes error handling much easier at the application layer.


29. Logging

Logging should happen through an interceptor.

Example:

--> GET /users
Authorization: Bearer ***

<-- 200 /users
Duration: 142ms
Enter fullscreen mode Exit fullscreen mode

Never log:

Passwords
Access tokens
Refresh tokens
Sensitive personal information
Enter fullscreen mode Exit fullscreen mode

A good logging system should support:

NONE
BASIC
HEADERS
BODY
Enter fullscreen mode Exit fullscreen mode

and redact sensitive headers.


30. Request and Response Compression

For large payloads:

gzip
brotli
Enter fullscreen mode Exit fullscreen mode

can reduce bandwidth.

The transport layer can negotiate compression using HTTP headers.

The application API should not need to know whether compression was used.


31. Streaming

Not every response should be loaded into memory.

For large files:

HTTP response
     ↓
Stream
     ↓
File
Enter fullscreen mode Exit fullscreen mode

A response abstraction could expose:

interface ResponseBody {

    suspend fun read(): ByteArray

    suspend fun stream(): InputStream
}
Enter fullscreen mode Exit fullscreen mode

Streaming is important for:

  • Large downloads
  • Media
  • Documents
  • Backup files

32. Multipart Upload

A networking library should support:

multipart/form-data
Enter fullscreen mode Exit fullscreen mode

For example:

POST /profile
--------------------------
name = Padmakar
avatar = profile.jpg
--------------------------
Enter fullscreen mode Exit fullscreen mode

The request body can be modeled as:

sealed interface RequestBody {

    data class Json(
        val bytes: ByteArray
    ) : RequestBody

    data class Multipart(
        val parts: List<Part>
    ) : RequestBody

    data class Stream(
        val source: InputStream
    ) : RequestBody
}
Enter fullscreen mode Exit fullscreen mode

33. Request Cancellation

Consider:

Search screen
     ↓
User types "android"
     ↓
Request A
Enter fullscreen mode Exit fullscreen mode

Then:

User types "android networking"
     ↓
Request B
Enter fullscreen mode Exit fullscreen mode

Request A may no longer be useful.

Cancel it:

Request A → Cancel
Request B → Execute
Enter fullscreen mode Exit fullscreen mode

This reduces unnecessary work.


34. Observability

A production networking library should expose metrics.

Useful metrics:

Request count
Success rate
Error rate
Average latency
P95 latency
P99 latency
Timeout count
Retry count
DNS duration
TLS duration
Response size
Cache hit rate
Cancellation count
Enter fullscreen mode Exit fullscreen mode

A useful request timeline:

Request
  |
  +-- DNS: 12ms
  |
  +-- Connect: 20ms
  |
  +-- TLS: 31ms
  |
  +-- Server: 70ms
  |
  +-- Download: 15ms
  |
  +-- Total: 148ms
Enter fullscreen mode Exit fullscreen mode

35. Complete Architecture

flowchart TB

    APP["Android Application"]

    APP --> API["Retrofit-like API Layer"]

    API --> PARSER["Annotation / Contract Parser"]

    PARSER --> FACTORY["Request Factory"]

    FACTORY --> INTERCEPTORS["Interceptor Chain"]

    INTERCEPTORS --> AUTH["Authentication"]
    INTERCEPTORS --> LOG["Logging"]
    INTERCEPTORS --> RETRY["Retry Policy"]
    INTERCEPTORS --> CACHE["HTTP Cache"]

    RETRY --> DISPATCHER["Dispatcher"]

    DISPATCHER --> HTTP["HTTP Client"]

    HTTP --> DNS["DNS"]

    DNS --> CONNECTION["Connection Manager"]

    CONNECTION --> TLS["TLS"]

    TLS --> POOL["Connection Pool"]

    POOL --> SERVER["Server"]

    SERVER --> POOL

    POOL --> RESPONSE["Response"]

    RESPONSE --> CONVERTER["Serialization"]

    CONVERTER --> RESULT["Result"]

    RESULT --> APP

36. Example End-to-End Request

Consider:

api.getUser("42")
Enter fullscreen mode Exit fullscreen mode

The complete journey is:

1. API method invoked

2. Endpoint metadata resolved

3. Path parameter substituted

4. URL created

5. Headers added

6. Request body serialized

7. Interceptors executed

8. Authentication added

9. Retry policy configured

10. Dispatcher schedules request

11. DNS resolves host

12. Connection pool checked

13. TCP connection established if necessary

14. TLS handshake performed

15. HTTP request sent

16. Server processes request

17. HTTP response received

18. Response validated

19. Response body decoded

20. JSON deserialized

21. Kotlin object returned
Enter fullscreen mode Exit fullscreen mode

That is what a simple:

api.getUser("42")
Enter fullscreen mode Exit fullscreen mode

can represent internally.


37. Retrofit vs OkHttp Responsibilities

Understanding their different responsibilities is important.

Layer Responsibility
Retrofit-style layer API declarations, annotations, request adapters, converters
OkHttp-style layer HTTP requests, connections, interceptors, TLS, pooling, transport
Serialization layer JSON/object conversion
Application layer Business logic and UI state

A useful mental model is:

Retrofit
   ↓
High-level API abstraction

OkHttp
   ↓
Low-level HTTP engine
Enter fullscreen mode Exit fullscreen mode

They solve related but different problems.


38. Design Patterns Used

This architecture combines several classic patterns.

Factory Pattern

API definition
     ↓
Request Factory
Enter fullscreen mode Exit fullscreen mode

Chain of Responsibility

Interceptor → Interceptor → Interceptor
Enter fullscreen mode Exit fullscreen mode

Adapter Pattern

Converts:

API method
   ↓
HTTP request
Enter fullscreen mode Exit fullscreen mode

Strategy Pattern

Used for:

RetryPolicy
CachePolicy
Serialization
Enter fullscreen mode Exit fullscreen mode

Builder Pattern

Used for:

HttpRequest.Builder
Enter fullscreen mode Exit fullscreen mode

Dependency Inversion

The core depends on interfaces:

interface HttpClient
interface Converter
interface Interceptor
interface RetryPolicy
interface DnsResolver
Enter fullscreen mode Exit fullscreen mode

39. Testing Strategy

Networking libraries require extensive testing.

Unit Tests

Test:

URL generation
Path parameters
Query parameters
Headers
Serialization
Retry policy
Cache policy
Interceptor order
Error mapping
Enter fullscreen mode Exit fullscreen mode

Integration Tests

Test:

Real HTTP server
TLS
Connection pooling
Timeouts
Redirects
Caching
Multipart
Streaming
Enter fullscreen mode Exit fullscreen mode

Cancellation Tests

Verify:

Coroutine cancelled
      ↓
HTTP call cancelled
      ↓
Resources released
Enter fullscreen mode Exit fullscreen mode

Concurrency Tests

Run:

1000 concurrent requests
Enter fullscreen mode Exit fullscreen mode

and verify:

No race conditions
No leaked calls
No invalid shared state
Enter fullscreen mode Exit fullscreen mode

40. Production Concerns

A production networking library should additionally consider:

Security

HTTPS
TLS validation
Certificate pinning where justified
Credential redaction
Secure token handling
Enter fullscreen mode Exit fullscreen mode

Performance

Connection pooling
HTTP/2
Compression
Bounded concurrency
Streaming
Efficient serialization
Enter fullscreen mode Exit fullscreen mode

Reliability

Timeouts
Retries
Backoff
Circuit breaking at appropriate higher layers
Graceful cancellation
Enter fullscreen mode Exit fullscreen mode

Observability

Metrics
Tracing
Structured logs
Request IDs
Enter fullscreen mode Exit fullscreen mode

41. Learning Path

If you want to implement your own networking library, don't start with the complete architecture.

Build it in stages.

Phase 1 — HTTP Client

Implement:

GET
POST
Headers
Response
Enter fullscreen mode Exit fullscreen mode

Phase 2 — Request Builder

Add:

Base URL
Path parameters
Query parameters
Request body
Enter fullscreen mode Exit fullscreen mode

Phase 3 — Serialization

Add:

JSON → Kotlin
Kotlin → JSON
Enter fullscreen mode Exit fullscreen mode

Phase 4 — Coroutines

Add:

suspend functions
Cancellation
Dispatcher
Enter fullscreen mode Exit fullscreen mode

Phase 5 — Interceptors

Implement:

Auth
Logging
Retry
Enter fullscreen mode Exit fullscreen mode

Phase 6 — Connection Management

Study:

DNS
TCP
TLS
Connection Pool
HTTP/2
Enter fullscreen mode Exit fullscreen mode

Phase 7 — Caching

Implement:

HTTP cache
ETag
Cache-Control
304 responses
Enter fullscreen mode Exit fullscreen mode

Phase 8 — Production Features

Add:

Timeouts
Streaming
Multipart
Metrics
Tracing
Certificate pinning
Request prioritization
Enter fullscreen mode Exit fullscreen mode

42. Final Mental Model

The easiest way to remember the architecture is:

             Android App
                  |
                  v
             API Interface
                  |
                  v
            Request Factory
                  |
                  v
          Interceptor Chain
                  |
        +---------+---------+
        |         |         |
       Auth     Logging   Retry
        |         |         |
        +---------+---------+
                  |
                  v
              Dispatcher
                  |
                  v
             HTTP Client
                  |
       +----------+----------+
       |          |          |
      DNS        TLS     Connection Pool
       |          |          |
       +----------+----------+
                  |
                  v
               Server
                  |
                  v
              Response
                  |
                  v
             Serializer
                  |
                  v
             Kotlin Model
                  |
                  v
             Android App
Enter fullscreen mode Exit fullscreen mode

A networking library is therefore much more than an HTTP wrapper.

It is a combination of:

API abstraction
      +
Request construction
      +
Interceptor pipeline
      +
Concurrency
      +
HTTP transport
      +
Connection management
      +
Security
      +
Serialization
      +
Caching
      +
Error handling
      +
Observability
Enter fullscreen mode Exit fullscreen mode

Understanding these layers gives you a much stronger foundation for Android system design interviews and for designing networking infrastructure in real applications.


Conclusion

Retrofit and OkHttp make networking look simple:

api.getUsers()
Enter fullscreen mode Exit fullscreen mode

But behind that line is an entire distributed-system interaction involving the Android runtime, concurrency, DNS, TCP, TLS, HTTP, connection pooling, serialization, caching, retries, and error handling.

The most important system-design lesson is to separate responsibilities.

Keep:

API abstraction
Enter fullscreen mode Exit fullscreen mode

separate from:

HTTP transport
Enter fullscreen mode Exit fullscreen mode

and keep:

serialization
authentication
retry
logging
caching
Enter fullscreen mode Exit fullscreen mode

as replaceable components.

That gives the library a modular architecture that can evolve without forcing application code to change.


Author

Padmakar Garg

Android Developer focused on Kotlin, Jetpack Compose, Android Architecture, System Design, and Kotlin Multiplatform.

GitHub: https://github.com/gargpadmakar


Top comments (0)