TL;DR
The TCP connection timed out before the server responded. Three things fix this in most cases: raise your OkHttp timeouts to something realistic for mobile networks, verify the endpoint is actually reachable from the device's network, and add retry logic for transient failures.
The Error
java.net.SocketTimeoutException: failed to connect to api.example.com/192.168.1.1 (port 443) from /192.168.1.2 (port 52341) after 15000ms
This shows up in logcat when Android tries to open a TCP connection to your API server but gets no response within the timeout window โ 15 seconds in this case. The connection never completed. That makes this a connect timeout, not a read timeout. The distinction matters when you start debugging.
Root Cause
Several things can trigger this โ some obvious, some not:
- Server unreachable or firewall blocking port 443 โ the SYN packet goes out but the server never replies with SYN-ACK
- DNS resolved to wrong or stale IP โ the IP in the error message doesn't match what you expect
- Device on restricted network โ corporate WiFi, VPN, or a hotspot blocking outbound connections
- Timeout configured too low โ OkHttp defaults to 10s connect/read/write, which regularly fails on slow 3G or congested networks
- Server under load โ accepting connections but the queue is full and packets get dropped
Fix 1: Tune Timeouts in OkHttp / Retrofit
Most Retrofit setups use OkHttp under the hood. The defaults โ 10s connect, 10s read, 10s write โ hold up fine on fast connections but fail regularly on mobile. Bump them:
val okHttpClient = OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS) // time to establish TCP connection
.readTimeout(30, TimeUnit.SECONDS) // time waiting for server to respond
.writeTimeout(30, TimeUnit.SECONDS) // time to send the request body
.build()
val retrofit = Retrofit.Builder()
.baseUrl("https://api.example.com/")
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.build()
30s is a sensible starting point for most mobile APIs. Don't go above 60s โ at that point you're just making users stare at a spinner on a connection that's clearly not coming back.
Fix 2: Add Retry Logic for Transient Failures
OkHttp has a built-in retry mechanism, but it's conservative. For temporary network blips or DNS hiccups, a custom retry interceptor with backoff is more reliable:
class RetryInterceptor(private val maxRetries: Int = 3) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
var attempt = 0
var lastException: IOException? = null
while (attempt < maxRetries) {
try {
return chain.proceed(chain.request())
} catch (e: SocketTimeoutException) {
lastException = e
attempt++
if (attempt < maxRetries) {
Thread.sleep(1000L * attempt) // backoff: 1s, 2s, 3s
}
}
}
throw lastException ?: IOException("Unknown error after $maxRetries retries")
}
}
// Attach to OkHttp
val okHttpClient = OkHttpClient.Builder()
.addInterceptor(RetryInterceptor(maxRetries = 3))
.connectTimeout(30, TimeUnit.SECONDS)
.build()
Fix 3: Fail Fast With a Connectivity Check
Waiting 30 seconds for a timeout is awful UX. Check network availability before making the call โ fail immediately with a useful message instead:
fun isNetworkAvailable(context: Context): Boolean {
val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val network = cm.activeNetwork ?: return false
val capabilities = cm.getNetworkCapabilities(network) ?: return false
return capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
}
// In your Repository
suspend fun fetchData(): Result<MyData> {
if (!isNetworkAvailable(context)) {
return Result.failure(IOException("No internet connection"))
}
return try {
Result.success(api.getData())
} catch (e: SocketTimeoutException) {
Result.failure(e)
}
}
Fix 4: Debug the DNS Resolution
The IP address in the error is your first clue. api.example.com/192.168.1.1 โ that's a private IP. If you're calling a public API and seeing a 192.168.x.x or 10.x.x.x address, DNS is resolving to the wrong host. Stale cache, split-horizon DNS, or a misconfigured VPN are the usual suspects.
# Check what IP the device resolves
adb shell nslookup api.example.com
# Verify basic connectivity
adb shell ping -c 3 api.example.com
# Check if port 443 is reachable
adb shell nc -zv api.example.com 443
If the IP looks wrong, switch from WiFi to mobile data and retry. Call succeeds on mobile data? The WiFi network is filtering or misrouting your traffic โ not your code.
Fix 5: Check Network Security Config
Android 9+ blocks cleartext HTTP by default. If your app accidentally hits an HTTP endpoint โ wrong base URL, or a redirect that drops back to HTTP โ Android kills the connection and surfaces it as a network error. Check your config:
<!-- res/xml/network_security_config.xml -->
<network-security-config>
<domain-config cleartextTrafficPermitted="false">
<domain includeSubdomains="true">api.example.com</domain>
</domain-config>
</network-security-config>
Verification Steps
Once you've applied a fix, here's how to confirm it actually worked:
- Test on a real device, not the emulator โ emulator networking behaves differently
- Filter logcat by
OkHttptag โ you should see request/response logs, not exception traces - Test on both WiFi and mobile data to catch network-specific issues
- Add OkHttp's logging interceptor in debug builds to see full timing:
// Debug builds only
if (BuildConfig.DEBUG) {
val logging = HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.HEADERS
}
builder.addInterceptor(logging)
}
Tips
If the IP in the error looks like a private address (10.x.x.x, 172.16โ31.x.x, 192.168.x.x) but you're hitting a public API, DNS is sending you somewhere wrong. The IP Subnet Calculator on ToolCraft lets you quickly check whether an IP is private or publicly routable โ runs entirely in the browser, nothing uploaded. Useful when you're debugging in the field without your usual tools.
Further Reading
- OkHttp documentation โ official timeout and interceptor guide
- Retrofit documentation โ client configuration
- Android: Reading network state โ ConnectivityManager API

