Skip to content

How to Fix Android Retrofit Network Error

DodaTech Updated 2026-06-24 3 min read

In this tutorial, you'll learn about How to Fix Android Retrofit Network Error. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.

The Problem

Your Retrofit call fails:

java.net.ConnectException: Failed to connect to api.example.com/:443

Or:

java.net.SocketTimeoutException: timeout

Or:

javax.net.ssl.SSLHandshakeException: Chain validation failed

Retrofit cannot reach the server due to incorrect URL, missing internet permission, network timeout, or SSL configuration issues.

Quick Fix

Step 1: Check internet permission in manifest

<uses-permission android:name="android.permission.INTERNET" />

This is a normal permission and is granted automatically. Missing it causes all network calls to fail silently.

Step 2: Verify the base URL format

// Wrong -- missing trailing slash
Retrofit.Builder().baseUrl("https://api.example.com")

// Right
Retrofit.Builder().baseUrl("https://api.example.com/")

Retrofit requires the base URL to end with /. Endpoint annotations are relative to this base.

Step 3: Add OkHttp logging for debugging

val loggingInterceptor = HttpLoggingInterceptor().apply {
    level = HttpLoggingInterceptor.Level.BODY
}

val client = OkHttpClient.Builder()
    .addInterceptor(loggingInterceptor)
    .connectTimeout(30, TimeUnit.SECONDS)
    .readTimeout(30, TimeUnit.SECONDS)
    .build()

val retrofit = Retrofit.Builder()
    .baseUrl("https://api.example.com/")
    .client(client)
    .addConverterFactory(GsonConverterFactory.create())
    .build()

The logging interceptor prints the full request and response, making it easy to spot URL or body issues.

Step 4: Handle network timeout

val client = OkHttpClient.Builder()
    .connectTimeout(15, TimeUnit.SECONDS)
    .readTimeout(15, TimeUnit.SECONDS)
    .writeTimeout(15, TimeUnit.SECONDS)
    .build()

Different endpoints may need different timeouts. For file uploads, increase writeTimeout.

Step 5: Fix SSL certificate issues

For development with self-signed certificates, create a custom trust manager (never use in production):

// Development only -- pin certificates for production
val sslContext = SSLContext.getInstance("TLS").apply {
    init(null, arrayOf(TrustAllManager()), null)
}

In production, use certificate pinning with OkHttp:

val certificatePinner = CertificatePinner.Builder()
    .add("api.example.com", "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")
    .build()

Step 6: Check the endpoint annotation

interface ApiService {
    @GET("users/{id}")
    suspend fun getUser(@Path("id") id: Int): Response<User>
}

The @GET, @POST, etc. annotation must use a relative path. Using an absolute URL triggers a MalformedURLException.

Step 7: Enable cleartext for HTTP (development only)

In AndroidManifest.xml:

<application
    android:usesCleartextTraffic="true"
    ...>

Or create res/xml/network_security_config.xml for fine-grained control.

Prevention

  • Always add the INTERNET permission to the manifest.
  • Use OkHttp logging interceptor during development.
  • Test endpoints with a REST client (Postman) before coding.

Common Mistakes with retrofit error

  1. Forgetting that lazy evaluation defers computation until the value is forced, causing space leaks with unevaluated thunks
  2. Using return to exit a function early instead of wrapping a pure value in the monad
  3. Mixing let bindings with <- bindings in do notation, producing type errors

These mistakes appear frequently in real-world Android code. DodaTech's contributors have identified these patterns through analysis of open-source projects and production systems.

Practice Exercise

Write a pure function that safely divides two integers using Maybe, then test it with edge cases like division by zero and negative numbers.

This exercise reinforces the concepts covered in this guide. Try implementing it before checking online solutions.

FAQ

### Why does Retrofit return empty body on error?

Use Response<T> as the return type instead of T directly. Response.isSuccessful and Response.errorBody() let you inspect both success and error responses.

How do I pass query parameters?

Use @Query("page") or @QueryMap for dynamic parameters:

@GET("users")
suspend fun getUsers(@Query("page") page: Int): Response<List<User>>

What is the difference between @Path and @Query?

@Path replaces a segment in the URL path. @Query appends a query parameter. For URL users/123?sort=asc, 123 is a path, sort is a query.

DodaTech Tool Reference

Doda Browser's Network Inspector captures all HTTP traffic from your app, including Retrofit calls, with full request and response logging for debugging API issues.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro