Geonode logo
Geonode Team

Geonode Team

Actualizado: 7 de octubre de 2026

Publicado: 2 de septiembre de 2026

OkHttp en Java: Guía de introducción

OkHttp es el cliente HTTP que acaban utilizando la mayoría de los proyectos de JVM y Android, y presenta dos decisiones de diseño que suelen pillar desprevenidos a los principiantes: el cliente está pensado para ser compartido y los cuerpos de las respuestas deben cerrarse. Si se hacen bien, todo lo demás resulta sencillo. Si se hacen mal, se producen fugas de conexiones hasta que algo falla. Esta guía abarca los conceptos básicos, los interceptores, los tiempos de espera y los proxies, además de una nota sobre la ubicación actual del proyecto, que ha cambiado.

Nuestro punto de vista: somos Geonode y vendemos proxies, y la configuración del proxy de OkHttp es realmente inusual: la autenticación del proxy se realiza a través de un proxyAuthenticator, no de un encabezado, y si se comete un error al configurarlo, se produce un error 407 que parece un problema de credenciales cuando en realidad es un problema de configuración. Esa sección se encuentra casi al final. Todo lo anterior funciona sin necesidad de ningún proxy, y si estás aprendiendo a utilizar la biblioteca, hazlo primero con tu propia conexión.

Dónde se encuentra ahora OkHttp

Vale la pena mencionarlo desde el principio, ya que los enlaces antiguos ya no funcionan.

El repositorio de OkHttp se ha trasladado. github.com/square/okhttp

ahora redirige a github.com/lysine-dev/okhttp

, y la página de documentación que se encontraba en square.github.io/okhttp

devuelve un error 404; la página actual está en lysine.dev/okhttp

. El proyecto se mantiene de forma activa y está bajo licencia Apache-2.0, tal y como se verificó en septiembre de 2026.

Las coordenadas de Maven no han cambiado. Sigue publicado como com.squareup.okhttp3:okhttp

:

implementation("com.squareup.okhttp3:okhttp:5.5.0")

. También hay una lista de materiales para mantener al día los artefactos relacionados:

implementation(platform("com.squareup.okhttp3:okhttp-bom:5.5.0"))

Requisitos: Android 5.0+ (nivel de API 21+) y Java 8+. OkHttp depende de Okio para E/S y de la biblioteca estándar de Kotlin; ambas descritas por el proyecto como «pequeñas bibliotecas con una sólida compatibilidad con versiones anteriores». En Android utiliza AndroidX Startup, y si desactivas el inicializador en el manifiesto, tu aplicación debe llamar a ``OkHttp.initialize(applicationContext)`

en ``Application.onCreate

`.

La antigua rama ``3.12.x`

` es compatible con Android 2.3+ y Java 7, y el proyecto deja claro que esas plataformas «carecen de soporte para TLS 1.2 y no deben utilizarse».

Tu primera solicitud

OkHttpClient client = new OkHttpClient();

String run(String url) throws IOException {
  Request request = new Request.Builder()
      .url(url)
      .build();

  try (Response response = client.newCall(request).execute()) {
    return response.body().string();
  }
}

Hay tres aspectos importantes en ese ejemplo de siete líneas.

**try

-with-resources no es opcional.** Response

implementa Closeable

, y una respuesta no cerrada mantiene su conexión. Si se producen suficientes fugas, el grupo de conexiones se agota, momento en el que las solicitudes se cuelgan en lugar de fallar —un síntoma que parece un problema de red, pero no lo es—.

**body().string()

puede llamarse una sola vez.** Consume el flujo. Si se llama dos veces, se produce un error, y si se llama después de cerrar la respuesta, también se produce un error. Si necesitas el cuerpo más de una vez, guarda la cadena.

**execute()

es sincrónico.** Para tareas asíncronas, enqueue()

toma un Callback

y se ejecuta en el grupo de subprocesos del distribuidor de OkHttp.

Una solicitud POST tiene la misma estructura con un cuerpo:

public static final MediaType JSON = MediaType.get("application/json");

String post(String url, String json) throws IOException {
  RequestBody body = RequestBody.create(json, JSON);
  Request request = new Request.Builder()
      .url(url)
      .post(body)
      .build();
  try (Response response = client.newCall(request).execute()) {
    return response.body().string();
  }
}

Compartir el cliente

La decisión de diseño en la que la gente suele equivocarse con más frecuencia.

OkHttpClient

contiene un grupo de conexiones y un grupo de subprocesos. Crear uno por cada solicitud desperdicia todas las conexiones que podrías haber reutilizado y genera subprocesos que luego acabas abandonando. Funciona, pero es lento y, bajo carga, agota los recursos.

Crea un único cliente para tu aplicación y compártelo. Es seguro para subprocesos por diseño.

Cuando necesites una configuración diferente para una parte de tu código, no crees un segundo cliente desde cero: clona el existente para que se compartan los grupos:

OkHttpClient shortTimeout = client.newBuilder()
    .readTimeout(5, TimeUnit.SECONDS)
    .build();

newBuilder()

genera un cliente que comparte el grupo de conexiones y el distribuidor con su padre, que es exactamente lo que quieres.

Para el apagado, especialmente en procesos de corta duración, libera los recursos de forma explícita:

client.dispatcher().executorService().shutdown();
client.connectionPool().evictAll();

Sin esto, una JVM podría quedarse colgada durante el tiempo de mantenimiento de la conexión del grupo antes de salir, lo cual resulta muy complicado de depurar en una herramienta de línea de comandos.

Qué añade OkHttp a tu solicitud

La biblioteca reescribe las solicitudes, y saber qué añade evita cierto tipo de confusión.

La documentación es explícita: «OkHttp puede añadir encabezados que no figuran en la solicitud original, entre ellos Content-Length, Transfer-Encoding, User-Agent, Host, Connection y Content-Type. Añadirá un encabezado Accept-Encoding para la compresión transparente de la respuesta, a menos que dicho encabezado ya esté presente. Si tienes cookies, OkHttp añadirá un encabezado Cookie con ellas».

Esto tiene dos consecuencias.

La compresión transparente es automática y se revierte automáticamente. OkHttp solicita la compresión, descomprime la respuesta y, a continuación, «eliminará los encabezados de respuesta correspondientes Content-Encoding y Content-Length, ya que no se aplican al cuerpo de la respuesta descomprimida». Por lo tanto, que falte Content-Length en una respuesta es algo normal y no un error, a menos que tú mismo hayas establecido Accept-Encoding, en cuyo caso la descompresión es responsabilidad tuya.

Las solicitudes condicionales se producen automáticamente cuando el almacenamiento en caché está habilitado. OkHttp añade If-Modified-Since y If-None-Match para revalidar las entradas caducadas de la caché.

También sigue las redirecciones de forma predeterminada y, ante un desafío de autorización, «solicitará al Authenticator (si está configurado) que resuelva el desafío», reintentando con las credenciales proporcionadas.

La biblioteca se describe a sí misma como «basada en principios y que evita ser excesivamente configurable, especialmente cuando dicha configuración tiene como objetivo sortear un servidor defectuoso, probar escenarios inválidos o que contradigan el RFC pertinente». Reconoce con honestidad sus propias limitaciones: «no permite GET con cuerpo» y la caché «no es una interfaz con implementaciones alternativas». Si necesitas enviar solicitudes deliberadamente inválidas, esta no es la biblioteca adecuada, y eso es una elección de diseño más que un descuido.

Interceptores

El principal punto de extensión y la característica que determinará cómo utilizas la biblioteca.

class LoggingInterceptor implements Interceptor {
  @Override public Response intercept(Interceptor.Chain chain) throws IOException {
    Request request = chain.request();
    long t1 = System.nanoTime();
    logger.info(String.format("Sending request %s on %s%n%s",
        request.url(), chain.connection(), request.headers()));

    Response response = chain.proceed(request);

    long t2 = System.nanoTime();
    logger.info(String.format("Received response for %s in %.1fms%n%s",
        response.request().url(), (t2 - t1) / 1e6d, response.headers()));
    return response;
  }
}

La documentación insiste en que «una llamada a chain.proceed(request) es una parte fundamental de la implementación de cada interceptor. Este método, de apariencia sencilla, es donde tiene lugar todo el trabajo HTTP». Y una advertencia que conviene tener en cuenta: «si se llama a chain.proceed(request) más de una vez, se deben cerrar los cuerpos de las respuestas anteriores».

Hay dos tipos, y es importante elegir correctamente. Regístrate con addInterceptor() o addNetworkInterceptor(). La documentación expone la diferencia con precisión.

Interceptores de aplicación:

  • «No hay que preocuparse por las respuestas intermedias, como redireccionamientos y reintentos».
  • «Siempre se invocan una vez, incluso si la respuesta HTTP se sirve desde la caché».
  • «Respetan la intención original de la aplicación. No tienen en cuenta los encabezados inyectados por OkHttp, como If-None-Match».
  • «Se permite el cortocircuito y no llamar a Chain.proceed()».
  • «Se permite reintentar y realizar múltiples llamadas a Chain.proceed()».
  • «Se pueden ajustar los tiempos de espera de las llamadas mediante withConnectTimeout, withReadTimeout y withWriteTimeout».

Interceptores de red:

  • «Capaces de actuar sobre respuestas intermedias, como redireccionamientos y reintentos».
  • «No se invocan para respuestas almacenadas en caché que evitan el paso por la red».
  • «Observa los datos tal y como se transmitirán por la red».
  • «Accede al objeto Connection que transporta la solicitud».

La regla práctica: utiliza un interceptor de aplicación para todo lo relacionado con la intención de tu solicitud —añadir un encabezado de autorización, un agente de usuario, registro a nivel de aplicación—. Utiliza un interceptor de red para todo lo relacionado con lo que realmente se transmite por la red: inspeccionar cuerpos comprimidos, ver cada salto de redireccionamiento, examinar la conexión.

El error más común es registrar un interceptor de autenticación como interceptor de red, lo que hace que se active una vez por cada salto de redireccionamiento y puede provocar la filtración de tus credenciales a un servidor al que no tenías intención de enviarlas.

Tiempos de espera

OkHttp cuenta con valores predeterminados razonables y cuatro ajustes distintos; saber cuál de ellos se ha activado te indica dónde está el problema.

OkHttpClient client = new OkHttpClient.Builder()
    .connectTimeout(10, TimeUnit.SECONDS)
    .readTimeout(30, TimeUnit.SECONDS)
    .writeTimeout(30, TimeUnit.SECONDS)
    .callTimeout(60, TimeUnit.SECONDS)
    .build();

**connectTimeout

** se refiere al establecimiento de la conexión TCP y TLS. **readTimeout

** se aplica entre lecturas individuales, no a la respuesta completa; por lo tanto, una descarga lenta pero que avanza nunca lo activa. **writeTimeout

** hace lo mismo con las subidas. **callTimeout

** limita toda la llamada, incluidas las redirecciones, los reintentos y la transferencia del cuerpo. Por defecto es cero, lo que significa que no hay límite global.

Este último es el que hay que configurar. Sin él, una llamada que va enviando datos poco a poco puede ejecutarse indefinidamente, ya que el tiempo de espera de lectura se reinicia con cada byte recibido. callTimeout

es el límite máximo que hace que una tarea finalice, y es el equivalente a --max-time

en curl —una distinción que ya tratamos en cómo establecer un tiempo de espera con curl.

Las modificaciones por llamada están disponibles a través de ``withReadTimeout`

` y métodos similares de un interceptor de aplicaciones; así es como se le da más margen a un punto final lento sin relajar los valores predeterminados para todo.

Proxies

En qué se diferencia OkHttp de la mayoría de los clientes, y en qué se pierde tiempo.

Proxy proxy = new Proxy(Proxy.Type.HTTP,
    new InetSocketAddress("proxy.example.com", 9000));

OkHttpClient client = new OkHttpClient.Builder()
    .proxy(proxy)
    .build();

Para SOCKS, utiliza Proxy.Type.SOCKS con el mismo formato.

La autenticación es la parte que sorprende a la gente. No hay que establecer un encabezado Proxy-Authorization. Se proporciona un proxyAuthenticator, que OkHttp invoca cuando el proxy emite un desafío 407:

Authenticator proxyAuth = (route, response) -> {
  if (response.request().header("Proxy-Authorization") != null) {
    return null;   // already tried these credentials; give up
  }
  String credential = Credentials.basic("user", "pass");
  return response.request().newBuilder()
      .header("Proxy-Authorization", credential)
      .build();
};

OkHttpClient client = new OkHttpClient.Builder()
    .proxy(proxy)
    .proxyAuthenticator(proxyAuth)
    .build();

Es esencial que devuelva «null». Sin ello, una contraseña incorrecta provoca un bucle infinito de reintentos en lugar de un error: OkHttp consulta al autenticador, obtiene la misma credencial incorrecta, recibe otro 407 y vuelve a preguntar. Para romper el ciclo, hay que comprobar si ya se ha enviado un encabezado Proxy-Authorization.

Tres notas adicionales.

Un 407 no es un 401. El proxy te ha rechazado y nunca ha llegado al destino. Un 401 significa que el proxy ha funcionado y que el destino solicita credenciales, lo cual es una configuración de authenticator() totalmente diferente.

proxySelector() te permite elegir un proxy por solicitud en lugar de por cliente, lo que te permite enrutar diferentes hosts de forma distinta sin tener que crear múltiples clientes.

Comprueba que ha surtido efecto. Solicita un servicio que repita tu dirección, con y sin el proxy configurado. Una configuración errónea en este caso pasa desapercibida —las solicitudes se realizan con éxito y van directamente a su destino— y confirmar la dirección de salida es la única forma de estar seguro. Ese patrón de «éxito silencioso» es el que describimos en por qué es importante probar los proxies.

Cómo gestionar correctamente las respuestas

Algunos patrones que marcan la diferencia entre un código que funciona y uno que funciona bajo carga.

**Comprueba si se produce un error de red (isSuccessful()

), no solo la ausencia de una excepción.** OkHttp lanza un objeto ``IOException`

para los fallos de red, no para los códigos de error HTTP. Un 404 o un 500 se reciben como un objeto ``Response

` normal:

try (Response response = client.newCall(request).execute()) {
  if (!response.isSuccessful()) {
    String body = response.body() != null ? response.body().string() : "";
    throw new IOException("HTTP " + response.code() + " from " + request.url()
                          + ": " + body.substring(0, Math.min(200, body.length())));
  }
  return response.body().string();
}

Incluir la primera parte del cuerpo del error convierte un código de estado opaco en un mensaje que identifica el problema; la mayoría de las API se explican a sí mismas en el cuerpo de un 400.

No almacenes en búfer respuestas grandes en una cadena de caracteres (String). body().string()

lee todo en memoria. Para una descarga grande, utilice un flujo:

try (Response response = client.newCall(request).execute();
     BufferedSource source = response.body().source();
     BufferedSink sink = Okio.buffer(Okio.sink(new File("out.bin")))) {
  sink.writeAll(source);
}

Las llamadas asíncronas siguen necesitando que se cierre el cuerpo, y la llamada de retorno se ejecuta en un hilo en segundo plano:

client.newCall(request).enqueue(new Callback() {
  @Override public void onFailure(Call call, IOException e) {
    logger.warn("request failed", e);
  }
  @Override public void onResponse(Call call, Response response) throws IOException {
    try (ResponseBody body = response.body()) {
      handle(body.string());
    }
  }
});

Ten en cuenta que onFailure

se activa únicamente por problemas de red. Un error HTTP 500 llega a onResponse

, lo que sorprende a quienes esperan que el nombre signifique lo que parece.

Los reintentos requieren precaución. OkHttp reintenta automáticamente algunos errores a nivel de conexión, controlados por retryOnConnectionFailure()

, que está activado por defecto. No reintenta los códigos de error HTTP, y no debería hacerlo: reintentar un POST que podría haber tenido éxito puede duplicar una escritura. Si añades tu propia lógica de reintentos, hazlo en un interceptor de la aplicación, limita el número de intentos, deja un tiempo de espera entre ellos y limítate a métodos idempotentes, a menos que la API ofrezca una clave de idempotencia.

Qué más te ofrece

En resumen, estas son las razones para elegirlo.

HTTP/2, cuya «compatibilidad permite que todas las solicitudes dirigidas al mismo host compartan un socket». Agrupación de conexiones, que «reduce la latencia de las solicitudes (si HTTP/2 no está disponible)». GZIP transparente. Almacenamiento en caché de respuestas, que «evita por completo el uso de la red para las solicitudes repetidas».

Resiliencia. «Se recuperará de forma silenciosa ante problemas comunes de conexión» y, cuando un servicio tenga varias direcciones, «intentará conectarse a direcciones alternativas si falla la primera conexión», lo cual, según señala el proyecto, «es necesario para IPv4+IPv6 y para servicios alojados en centros de datos redundantes».

TLS moderno, incluyendo TLS 1.3, ALPN y «certificate pinning», utilizando la implementación de la plataforma. En la JVM también es compatible con Conscrypt, que integra BoringSSL con Java y se utiliza automáticamente si es el primer proveedor de seguridad.

Cumplimiento de estándares. El proyecto enumera las especificaciones que sigue: RFC 9110 para la semántica de HTTP, RFC 9111 para el almacenamiento en caché, RFC 9112 para HTTP/1.1, RFC 9113 para HTTP/2, RFC 6455 para WebSockets y la especificación WHATWG para eventos enviados por el servidor. Cuando una especificación es ambigua, «sigue el ejemplo de los agentes de usuario modernos, como los navegadores más populares o las bibliotecas HTTP habituales».

Preguntas frecuentes

¿Se sigue manteniendo OkHttp?

Sí. El repositorio se ha trasladado de square/okhttp a lysine-dev/okhttp y la página de documentación se encuentra ahora en lysine.dev/okhttp, pero el proyecto se sigue desarrollando activamente y está bajo licencia Apache-2.0. Las coordenadas de Maven siguen siendo com.squareup.okhttp3:okhttp.

¿Debo crear un nuevo OkHttpClient para cada solicitud?

No. El cliente dispone de un grupo de conexiones y un grupo de subprocesos, y está diseñado para ser seguro entre subprocesos. Crea uno para tu aplicación y compártelo. Cuando necesites configuraciones diferentes, utiliza newBuilder() en el cliente existente para que se compartan los grupos.

¿Por qué tengo que cerrar la respuesta?

Porque Response mantiene una conexión hasta que se cierra, y las respuestas que se quedan colgadas agotan el grupo de conexiones. El síntoma es que las solicitudes se quedan colgadas en lugar de fallar, lo cual es difícil de diagnosticar. Utiliza siempre try-with-resources.

¿Cuál es la diferencia entre un interceptor de aplicación y un interceptor de red?

Un interceptor de aplicación ve tu solicitud original y se invoca exactamente una vez, incluso para respuestas almacenadas en caché, y puede acortar el proceso o volver a intentarlo. Un interceptor de red ve cada intercambio de red individual, incluidas las redirecciones y los reintentos, tiene acceso a la conexión y se omite por completo en el caso de las respuestas almacenadas en caché.

¿Cómo configuro un proxy en OkHttp?

Pasa un objeto java.net.Proxy a OkHttpClient.Builder.proxy(). Para la autenticación, configura un proxyAuthenticator en lugar de un encabezado, y devuelve null cuando ya exista un encabezado Proxy-Authorization, ya que unas credenciales incorrectas producirían un bucle infinito de reintentos.

¿Qué tiempos de espera debo establecer?

connectTimeout en torno a los 10 segundos, readTimeout y writeTimeout en torno a los 30, y —lo más importante— un callTimeout, cuyo valor por defecto es cero y es el único parámetro que limita toda la llamada. Sin él, una respuesta que llega muy lentamente nunca agota el tiempo de espera, ya que el tiempo de espera de lectura se reinicia con cada byte.

¿Gestiona OkHttp GZIP automáticamente?

Sí, si no configuras tú mismo Accept-Encoding. Solicita la compresión, descomprime la respuesta y descarta Content-Encoding y Content-Length, ya que estos ya no describen el cuerpo descomprimido. Si configuras el encabezado manualmente, también te encargas de la descompresión.

¿Qué versión de Java necesita OkHttp?

Java 8 o posterior, y Android 5.0 (nivel de API 21) o posterior. Depende de Okio y de la biblioteca estándar de Kotlin. La antigua rama 3.12.x es compatible con Java 7 y Android 2.3, pero carece de compatibilidad con TLS 1.2 y no debe utilizarse.

Conclusión

OkHttp es una pequeña API basada en una implementación bien pensada, y hay dos hábitos que cubren la mayor parte de su uso correcto: compartir un único cliente en toda la aplicación y cerrar cada respuesta con ``try-with-resources. Ambos fallos son silenciosos y, con el tiempo, se manifiestan como peticiones que se cuelgan, en lugar de como errores que se puedan leer.

Los interceptores son donde dedicarás la mayor parte de tu tiempo, y merece la pena aprender bien la distinción entre «aplicación» y «red», en lugar de aprenderla a base de prueba y error. Los interceptores de aplicación detectan tu intención y se ejecutan una sola vez; los interceptores de red detectan cada salto y se omiten en el caso de las respuestas almacenadas en caché. Registrar un interceptor de autorización en la capa de red es el error clásico, y se activa en cada redirección.

Establece un callTimeout. Su valor por defecto es cero; es el único parámetro que limita toda una llamada, y su ausencia es la razón por la que una tarea que debería tardar segundos a veces se ejecuta hasta que algo más la interrumpe.

Y si estás siguiendo documentación antigua, comprueba los enlaces. El proyecto se ha trasladado a lysine.dev/okhttp y el sitio alojado en Square ya no existe —aunque las coordenadas del artefacto no han cambiado, por lo que tu archivo de compilación no necesita ningún ajuste—.