All Products
Search
Document Center

HTTPDNS:Use HTTPDNS with HttpURLConnection on Android

Last Updated:Jun 04, 2026

This topic builds on the Android SDK Integration Flow and covers HTTPDNS integration with HttpURLConnection for HTTPS scenarios on Android.

Introduction

Learn how to implement direct IP connections with HttpURLConnection on Android in HTTPS scenarios, including Server Name Indication (SNI). The underlying principles are covered in How direct IP connection with HTTPDNS works.

Important

Most Android networking frameworks now use OkHttp, which natively supports custom DNS and simplifies direct IP connections. Consider the Best practices for using HTTPDNS with OkHttp on Android approach first. Use HttpURLConnection only when OkHttp is not available.

Integration solutions

The solution depends on whether your scenario requires SNI:

  1. HTTPS scenario (SNI): When you create an SSLSocket, pass the original domain name to the server through SNI. You must also correctly handle the `HostnameVerifier` logic.

  2. HTTPS scenario (non-SNI): Use the HostnameVerifier interface to revert the IP address to the original domain name during certificate validation.

The following sections cover each scenario with examples.

2.1 HTTPS scenario (SNI)

When using a multi-domain certificate, you must set the SNI hostname when creating the SSLSocket, in addition to using a HostnameVerifier. Create a custom SSLSocketFactory and perform these operations in its createSocket() method:

  1. Replace the domain name with the HTTPDNS-resolved IP address to establish the connection.

  2. Call the system or a custom SSLCertificateSocketFactory and set the SNI hostname using setHostname() before the handshake.

  3. Validate the certificate by reverting the domain name from the IP address to the original hostname.

The HTTPDNS Android Demo includes sample code for HTTPDNS with HttpsURLConnection in SNI scenarios.

Custom SSLSocketFactory example

class TlsSniSocketFactory constructor(conn: HttpsURLConnection): SSLSocketFactory() {
    private val mConn: HttpsURLConnection

    init {
        mConn = conn
    }

    override fun createSocket(plainSocket: Socket?, host: String?, port: Int, autoClose: Boolean): Socket {
        var peerHost = mConn.getRequestProperty("Host")
        if (peerHost == null) {
            peerHost = host
        }

        val address = plainSocket!!.inetAddress
        if (autoClose) {
            // We do not need the plainSocket.
            plainSocket.close()
        }

        // Create and connect the SSL socket, but do not perform hostname or certificate verification yet.
        val sslSocketFactory =
            SSLCertificateSocketFactory.getDefault(0) as SSLCertificateSocketFactory
        val ssl = sslSocketFactory.createSocket(address, R.attr.port) as SSLSocket

        // Enable TLSv1.1 and TLSv1.2 if available.
        ssl.enabledProtocols = ssl.supportedProtocols

        // Set up SNI before the handshake.
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
            // Set the SNI hostname.
            sslSocketFactory.setHostname(ssl, peerHost)
        } else {
            // No documented SNI support exists on Android earlier than 4.2. Try with reflection.
            try {
                val setHostnameMethod = ssl.javaClass.getMethod(
                    "setHostname",
                    String::class.java
                )
                setHostnameMethod.invoke(ssl, peerHost)
            } catch (e: Exception) {
            }
        }

        // Verify the hostname and certificate.
        val session = ssl.session

        if (!HttpsURLConnection.getDefaultHostnameVerifier()
                .verify(peerHost, session)
        ) throw SSLPeerUnverifiedException(
            "Cannot verify hostname: $peerHost"
        )

        return ssl
    }
}
public class TlsSniSocketFactory extends SSLSocketFactory {
    
    private HttpsURLConnection mConn;
    public TlsSniSocketFactory(HttpsURLConnection conn) {
        mConn = conn;
    }

    @Override
    public Socket createSocket(Socket plainSocket, String host, int port, boolean autoClose) throws IOException {
        String peerHost = mConn.getRequestProperty("Host");
        if (peerHost == null)
            peerHost = host;

        InetAddress address = plainSocket.getInetAddress();
        if (autoClose) {
            // We do not need the plainSocket.
            plainSocket.close();
        }
        // Create and connect the SSL socket, but do not perform hostname or certificate verification yet.
        SSLCertificateSocketFactory sslSocketFactory = (SSLCertificateSocketFactory) SSLCertificateSocketFactory.getDefault(0);
        SSLSocket ssl = (SSLSocket) sslSocketFactory.createSocket(address, port);

        // Enable TLSv1.1 and TLSv1.2 if available.
        ssl.setEnabledProtocols(ssl.getSupportedProtocols());

        // Set up SNI before the handshake.
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
            // Set the SNI hostname.
            sslSocketFactory.setHostname(ssl, peerHost);
        } else {
            // No documented SNI support exists on Android earlier than 4.2. Try with reflection.
            try {
                java.lang.reflect.Method setHostnameMethod = ssl.getClass().getMethod("setHostname", String.class);
                setHostnameMethod.invoke(ssl, peerHost);
            } catch (Exception e) {

            }
        }

        // Verify the hostname and certificate.
        SSLSession session = ssl.getSession();

        if (!HttpsURLConnection.getDefaultHostnameVerifier().verify(peerHost, session))
            throw new SSLPeerUnverifiedException("Cannot verify hostname: " + peerHost);

        return ssl;
    }
}

Redirection handling example

SNI requests often involve multiple HTTP 3xx redirects. The following example resolves the new host with HTTPDNS during redirection.

fun recursiveRequest(path: String) {
    var conn: HttpURLConnection? = null
    try {
        val url = URL(path)
        conn = url.openConnection() as HttpURLConnection
        // Get the IP address using the sync API.
        val httpdnsResult = HttpDns.getService(accountID)
            .getHttpDnsResultForHostSync(url.host, RequestIpType.both)
        var ip: String? = null
        if (httpdnsResult.ips != null && httpdnsResult.ips.isNotEmpty()) {
            ip = httpdnsResult.ips[0]
        } else if (httpdnsResult.ipv6s != null && httpdnsResult.ipv6s.isNotEmpty()) {
            ip = httpdnsResult.ipv6s[0]
        }
        if (!TextUtils.isEmpty(ip)) {
            // If an IP address is obtained from HTTPDNS, replace the URL and set the Host header.
            val newUrl = path.replaceFirst(url.host.toRegex(), ip!!)
            conn = URL(newUrl).openConnection() as HttpURLConnection
            conn.connectTimeout = 30000
            conn.readTimeout = 30000
            conn.instanceFollowRedirects = false

            // Set the Host field in the HTTP request header.
            conn.setRequestProperty("Host", url.host)
            if (conn is HttpsURLConnection) {
                val httpsURLConnection = conn

                // For an HTTPS scenario, perform certificate validation.
                httpsURLConnection.hostnameVerifier =
                    HostnameVerifier { _, session ->
                        var host = httpsURLConnection.getRequestProperty("Host")
                        if (null == host) {
                            host = httpsURLConnection.url.host
                        }
                        HttpsURLConnection.getDefaultHostnameVerifier().verify(host, session)
                    }

                // For an SNI scenario, create an SSLSocket.
                httpsURLConnection.sslSocketFactory = TlsSniSocketFactory(httpsURLConnection)
            }
        }
        val code = conn.responseCode // Network block
        if (code in 300..399) {
            var location = conn.getHeaderField("Location")
            if (location == null) {
                location = conn.getHeaderField("location")
            }
            if (location != null) {
                if (!(location.startsWith("http://") || location
                        .startsWith("https://"))
                ) {
                    // Sometimes the host is omitted and only the path is returned. In this case, you must complete the URL.
                    val originalUrl = URL(path)
                    location = (originalUrl.protocol + "://"
                            + originalUrl.host + location)
                }
                recursiveRequest(location)
            }
        } else {
            // redirect finish.
            val dis = DataInputStream(conn.inputStream)
            var len: Int
            val buff = ByteArray(4096)
            val response = StringBuilder()
            while (dis.read(buff).also { len = it } != -1) {
                response.append(String(buff, 0, len))
            }
            Log.d(TAG, "Response: $response")
        }
    } catch (e: MalformedURLException) {
        Log.w(TAG, "recursiveRequest MalformedURLException")
    } catch (e: IOException) {
        Log.w(TAG, "recursiveRequest IOException")
    } catch (e: java.lang.Exception) {
        Log.w(TAG, "unknow exception")
    } finally {
        conn?.disconnect()
    }
}
public void recursiveRequest(String path) {
    HttpURLConnection conn = null;
    try {
        URL url = new URL(path);
        conn = (HttpURLConnection) url.openConnection();
        // Get the IP address using the sync API.
        HTTPDNSResult httpdnsResult = HttpDns.getService(accountID).getHttpDnsResultForHostSync(url.getHost(), RequestIpType.both);

        String ip = null;
        if (httpdnsResult.getIps() != null && httpdnsResult.getIps().length > 0) {
            ip = httpdnsResult.getIps()[0];
        } else if (httpdnsResult.getIpv6s() != null && httpdnsResult.getIpv6s().length > 0) {
            ip = httpdnsResult.getIpv6s()[0];
        }

        if (!TextUtils.isEmpty(ip)) {
            // If an IP address is obtained from HTTPDNS, replace the URL and set the Host header.
            String newUrl = path.replaceFirst(url.getHost(), ip);
            conn = (HttpURLConnection) new URL(newUrl).openConnection();

            conn.setConnectTimeout(30000);
            conn.setReadTimeout(30000);
            conn.setInstanceFollowRedirects(false);

            // Set the Host field in the HTTP request header.
            conn.setRequestProperty("Host", url.getHost());

            if (conn instanceof HttpsURLConnection) {
                final HttpsURLConnection httpsURLConnection = (HttpsURLConnection) conn;

                // For an HTTPS scenario, perform certificate validation.
                httpsURLConnection.setHostnameVerifier(new HostnameVerifier() {
                    @Override
                    public boolean verify(String hostname, SSLSession session) {
                        String host = httpsURLConnection.getRequestProperty("Host");
                        if (null == host) {
                            host = httpsURLConnection.getURL().getHost();
                        }
                        return HttpsURLConnection.getDefaultHostnameVerifier().verify(host, session);
                    }
                });

                // For an SNI scenario, create an SSLSocket.
                httpsURLConnection.setSSLSocketFactory(new TlsSniSocketFactory(httpsURLConnection));
            }
        }

        int code = conn.getResponseCode();// Network block
        if (code >= 300 && code < 400) {
            String location = conn.getHeaderField("Location");
            if (location == null) {
                location = conn.getHeaderField("location");
            }

            if (location != null) {
                if (!(location.startsWith("http://") || location
                        .startsWith("https://"))) {
                    // Sometimes the host is omitted and only the path is returned. In this case, you must complete the URL.
                    URL originalUrl = new URL(path);
                    location = originalUrl.getProtocol() + "://"
                            + originalUrl.getHost() + location;
                }

                recursiveRequest(location);
            }
        } else {
            // redirect finish.
            DataInputStream dis = new DataInputStream(conn.getInputStream());
            int len;
            byte[] buff = new byte[4096];
            StringBuilder response = new StringBuilder();
            while ((len = dis.read(buff)) != -1) {
                response.append(new String(buff, 0, len));
            }
            Log.d(TAG, "Response: " + response.toString());
        }
    } catch (MalformedURLException e) {
        Log.w(TAG, "recursiveRequest MalformedURLException");
    } catch (IOException e) {
        Log.w(TAG, "recursiveRequest IOException");
    } catch (Exception e) {
        Log.w(TAG, "unknow exception");
    } finally {
        if (conn != null) {
            conn.disconnect();
        }
    }
}

2.2 HTTPS scenario (non-SNI)

For single-domain certificates that do not require SNI, the main change is hostname verification. Hook or implement a custom HostnameVerifier that reverts the IP address to the original domain name during certificate validation.

Important This solution applies only to non-SNI scenarios. If your application uses multiple certificates or domain names, verify that SNI is not required. If you get SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found., check whether the target site requires SNI.

The following example shows how to use HostnameVerifier with HttpURLConnection for certificate validation.

try {
    val url = "https://140.205.XX.XX/?sprefer=sypc00"
    val connection = URL(url).openConnection() as HttpURLConnection
    connection.setRequestProperty("Host", "m.taobao.com")
    if (connection is HttpsURLConnection) {
        connection.hostnameVerifier = HostnameVerifier { _, session ->
            /*
            * The official documentation describes this interface as follows:
            * "This is an extended verification option that implementers can provide.
            * It is to be used during a handshake if the URL's hostname does not match the
            * peer's identification hostname."
            *
            * When you use HTTPDNS, the hostname in the URL is not the remote hostname, such as m.taobao.com.
            * This causes a mismatch with the domain for which the certificate was issued.
            * Android's HttpsURLConnection provides a callback interface to handle this custom scenario.
            * After you confirm that the origin IP address from HTTPDNS matches the IP information in the session,
            * replace the domain name to be verified with the original domain name in the callback method.
            *
            */
            HttpsURLConnection.getDefaultHostnameVerifier().verify("m.taobao.com", session)
        }
    }
    connection.connect()
} catch (e: java.lang.Exception) {
    e.printStackTrace()
}
try {
    String url = "https://140.205.XX.XX/?sprefer=sypc00";
    HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();

    connection.setRequestProperty("Host", "m.taobao.com");

    if (connection instanceof HttpsURLConnection) {
        connection.setHostnameVerifier(new HostnameVerifier() {
    
           /*
            * The official documentation describes this interface as follows:
            * "This is an extended verification option that implementers can provide.
            * It is to be used during a handshake if the URL's hostname does not match the
            * peer's identification hostname."
            *
            * When you use HTTPDNS, the hostname in the URL is not the remote hostname, such as m.taobao.com.
            * This causes a mismatch with the domain for which the certificate was issued.
            * Android's HttpsURLConnection provides a callback interface to handle this custom scenario.
            * After you confirm that the origin IP address from HTTPDNS matches the IP information in the session,
            * replace the domain name to be verified with the original domain name in the callback method.
            *
            */
            @Override
            public boolean verify(String hostname, SSLSession session) {
                return HttpsURLConnection.getDefaultHostnameVerifier().verify("m.taobao.com", session);
            }
        });
    }

    connection.connect();
} catch (Exception e) {
    e.printStackTrace();
}

Summary

  1. Challenges of using HTTPDNS with HTTPS

    • Certificate validation requires a domain name match.

    • SNI requires providing the domain name to the server before the handshake.

  2. SNI scenarios

    • Create a custom SSLSocketFactory to set the SNI hostname before the handshake, and handle domain name replacement in the HostnameVerifier.

  3. Non-SNI scenarios

    • Use HostnameVerifier to revert the validation domain name from the IP address to the original hostname.

  4. Prioritize using OkHttp