android emulator
localhost connection
web server
development
troubleshooting

How to connect to my http//localhost web server from Android Emulator

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Inside the Android emulator, localhost and 127.0.0.1 refer to the emulator's own loopback interface, not your development machine. To reach a web server running on your host computer, replace localhost with the special alias 10.0.2.2. That single change resolves the vast majority of "connection refused" errors during local Android development.

Why localhost Does Not Work in the Emulator

The Android emulator runs as a virtual machine with its own network stack. When code inside the emulator connects to 127.0.0.1, the request stays inside the virtual machine. It never reaches your host OS.

text
1Host machine (your computer)
2  └── 127.0.0.1:8080  ← your dev server listens here
3
4Android Emulator (virtual machine)
5  └── 127.0.0.1       ← points to the emulator itself, NOT the host
6  └── 10.0.2.2        ← routed to the host's 127.0.0.1

Google's emulator provides 10.0.2.2 as a hard-coded alias that routes traffic through the virtual router to the host machine's loopback address. This is documented in the Android developer guides and is stable across emulator versions.

The Fix: Use 10.0.2.2

If your development API server runs on http://localhost:8080, change every reference in your Android code to http://10.0.2.2:8080.

Retrofit example (Kotlin)

kotlin
1val retrofit = Retrofit.Builder()
2    .baseUrl("http://10.0.2.2:8080/")
3    .addConverterFactory(MoshiConverterFactory.create())
4    .build()
5
6val apiService = retrofit.create(ApiService::class.java)

HttpURLConnection example (Kotlin)

kotlin
1import java.net.HttpURLConnection
2import java.net.URL
3
4fun checkHealth(): Int {
5    val url = URL("http://10.0.2.2:8080/health")
6    val connection = url.openConnection() as HttpURLConnection
7    connection.requestMethod = "GET"
8    connection.connectTimeout = 5000
9    return connection.responseCode
10}

OkHttp example (Kotlin)

kotlin
1import okhttp3.OkHttpClient
2import okhttp3.Request
3
4val client = OkHttpClient()
5
6val request = Request.Builder()
7    .url("http://10.0.2.2:8080/api/users")
8    .build()
9
10client.newCall(request).execute().use { response ->
11    println(response.body?.string())
12}

Making the Base URL Configurable

Hard-coding 10.0.2.2 everywhere is fragile. A better approach is to use a build config field so the base URL changes based on whether you are running on an emulator, a physical device, or in production.

groovy
1// app/build.gradle
2android {
3    buildTypes {
4        debug {
5            buildConfigField "String", "API_BASE_URL", '"http://10.0.2.2:8080"'
6        }
7        release {
8            buildConfigField "String", "API_BASE_URL", '"https://api.example.com"'
9        }
10    }
11}
kotlin
1val retrofit = Retrofit.Builder()
2    .baseUrl(BuildConfig.API_BASE_URL)
3    .addConverterFactory(MoshiConverterFactory.create())
4    .build()

For teams that test on both emulators and physical devices, you can add a product flavor or use a properties file to switch between 10.0.2.2 (emulator) and the machine's LAN IP (physical device).

Ensuring Your Server Is Reachable

Before debugging the Android side, confirm that your server is actually running and accessible on the host.

Step 1: Verify the server is listening

bash
# Check if something is listening on port 8080
lsof -i :8080        # macOS/Linux
netstat -an | findstr 8080   # Windows

Step 2: Test with curl from the host

bash
curl http://127.0.0.1:8080/health

If this fails, the problem is on the server side, not the emulator.

Step 3: Check the bind address

Some frameworks bind only to 127.0.0.1 by default, which is fine for emulator access since 10.0.2.2 routes to the host's loopback. But if you also need physical device access, bind to 0.0.0.0:

python
# Flask
app.run(host="0.0.0.0", port=8080)
javascript
1// Express
2app.listen(8080, "0.0.0.0", () => {
3  console.log("Server listening on all interfaces");
4});
bash
# Django
python manage.py runserver 0.0.0.0:8080

Step 4: Check firewall rules

On macOS, the built-in firewall can block incoming connections. On Windows, Windows Defender Firewall may block the port. Temporarily disabling the firewall or adding an exception for your development port can help isolate the issue.

Handling Android Cleartext (HTTP) Restrictions

Starting with Android 9 (API 28), cleartext HTTP traffic is blocked by default. If your development server does not use HTTPS, you need to explicitly allow HTTP connections.

Option 1: Allow all cleartext traffic (development only)

xml
1<!-- AndroidManifest.xml -->
2<application
3    android:usesCleartextTraffic="true"
4    ... >

Option 2: Network security config (more precise)

xml
1<!-- res/xml/network_security_config.xml -->
2<?xml version="1.0" encoding="utf-8"?>
3<network-security-config>
4    <domain-config cleartextTrafficPermitted="true">
5        <domain includeSubdomains="false">10.0.2.2</domain>
6    </domain-config>
7</network-security-config>
xml
1<!-- AndroidManifest.xml -->
2<application
3    android:networkSecurityConfig="@xml/network_security_config"
4    ... >

The second option is more precise: it allows cleartext only to the emulator host alias, keeping HTTPS enforcement everywhere else. Never carry either setting into a production build without a deliberate security decision.

Host Aliases for Different Emulators

Not every emulator uses the same alias. The correct address depends on which virtualization environment you are running.

EnvironmentHost aliasNotes
Android Studio Emulator (AVD)10.0.2.2Standard Google emulator
Genymotion10.0.3.2Different virtual router config
Physical device on WiFiMachine's LAN IP (e.g., 192.168.1.42)No alias; use actual IP
Physical device via USB (adb reverse)localhost worksPort forwarded through adb

Using adb reverse for physical devices

For physical devices connected over USB, adb reverse creates a port forward from the device to the host, so localhost works from the device side:

bash
adb reverse tcp:8080 tcp:8080

After running this command, code on the physical device can connect to http://localhost:8080 and the request is forwarded to port 8080 on the host machine. This is often the cleanest approach for physical device testing because no IP addresses need to change.

Debugging Connection Issues

When connections still fail after applying the correct alias, work through this checklist:

bash
1# 1. Confirm the server is running on the host
2curl http://127.0.0.1:8080/health
3
4# 2. Confirm the emulator can reach the host
5adb shell ping -c 3 10.0.2.2
6
7# 3. Confirm the specific port is reachable from the emulator
8adb shell curl http://10.0.2.2:8080/health
9
10# 4. Check Android logcat for network errors
11adb logcat | grep -i "connection\|cleartext\|network"

If ping works but HTTP fails, the issue is typically cleartext restrictions or a firewall rule. If ping also fails, the emulator's network configuration may need to be reset (wipe data and cold boot).

Common Pitfalls

Using localhost or 127.0.0.1 in the emulator. This is the most frequent mistake. Those addresses point to the emulator itself, not the host machine. Replace with 10.0.2.2.

Testing 10.0.2.2 on a physical device. The alias only exists inside the standard Android emulator. Physical devices need either the host's LAN IP or adb reverse.

Forgetting cleartext restrictions on API 28+. The connection silently fails with a generic error. Check logcat for "Cleartext HTTP traffic not permitted" messages.

Server bound to 127.0.0.1 when testing from a physical device. The emulator works because 10.0.2.2 routes to the host's loopback. Physical devices connect over the network, so the server must bind to 0.0.0.0 or the machine's network interface.

Hard-coding the IP instead of using build config. When the app moves from emulator testing to physical device testing to staging, every hard-coded address must change. Use BuildConfig fields or a properties file to manage this cleanly.

Assuming all emulators use the same alias. Genymotion uses 10.0.3.2. Custom AOSP builds may use different addresses entirely. Always verify the alias for your specific emulator.

Summary

  • Replace localhost with 10.0.2.2 to reach your host machine from the standard Android emulator.
  • Configure base URLs through build config rather than hard-coding IP addresses.
  • Verify your server is running and listening on the expected port before debugging the Android side.
  • Allow cleartext HTTP traffic for development builds when not using HTTPS, preferably scoped to 10.0.2.2 only.
  • Use adb reverse for physical device testing to avoid IP address management entirely.
  • Different emulators use different host aliases. Verify the correct address for your environment.

Course illustration
Course illustration

All Rights Reserved.