Servlets en Java: qué son, ciclo de vida y ejemplos prácticos

📅 Actualizado en febrero 2026 ✍️ Ángel López ⏱️ 18 min de lectura ✓ Nivel intermedio ★ ★ ★ ★ ★ (5/5)

🌐 ¿Qué son los Servlets en Java?

Los Servlets en Java son componentes del lado del servidor que permiten generar contenido web dinámico mediante el procesamiento de peticiones HTTP. En esencia, un Servlet es una clase Java que extiende las capacidades de un servidor web, recibiendo solicitudes de los clientes (normalmente navegadores), procesándolas y devolviendo respuestas en formato HTML, JSON, XML o cualquier otro tipo de contenido.

La tecnología Servlet fue introducida por Sun Microsystems en 1997 como una alternativa robusta y eficiente a la tecnología CGI (Common Gateway Interface) que dominaba la web dinámica en aquel momento. Mientras que CGI creaba un nuevo proceso del sistema operativo por cada petición entrante, los Servlets aprovechan la máquina virtual de Java (JVM) para gestionar múltiples peticiones mediante hilos ligeros (threads), lo que supone una mejora radical en eficiencia y escalabilidad.

La especificación Servlet ha evolucionado significativamente a lo largo de los años. La versión actual, dentro del proyecto Jakarta EE (antes Java EE), utiliza el paquete jakarta.servlet en lugar del histórico javax.servlet. Sin embargo, los conceptos fundamentales permanecen intactos, y comprender la arquitectura de Servlets es esencial para cualquier desarrollador Java que trabaje con tecnologías web.

💡 Dato clave: Aunque frameworks modernos como Spring MVC, Spring Boot o Jakarta Faces abstraen la capa de Servlets, internamente todos ellos se construyen sobre la API Servlet. Comprender los Servlets equivale a comprender los cimientos de toda la web Java.

🏗️ Arquitectura del Servlet Container

Un Servlet no se ejecuta de forma independiente: necesita un Servlet Container (también llamado contenedor de servlets o motor de servlets) que gestione su ciclo de vida, le proporcione el entorno de ejecución y se encargue de la comunicación HTTP con los clientes. Los contenedores más conocidos son Apache Tomcat, Eclipse Jetty, WildFly (antes JBoss) y GlassFish.

El flujo de una petición HTTP dentro de la arquitectura Servlet sigue estos pasos fundamentales:

Paso Descripción
1El cliente (navegador) envía una petición HTTP al servidor web.
2El servidor web delega la petición al Servlet Container.
3El Container identifica qué Servlet debe manejar la URL solicitada (mediante el mapeo definido en web.xml o anotaciones).
4Si el Servlet no está cargado en memoria, el Container lo instancia y ejecuta su método init().
5El Container crea los objetos HttpServletRequest y HttpServletResponse e invoca el método service(), que delega en doGet(), doPost(), etc.
6El Servlet procesa la petición, genera la respuesta y la escribe en el objeto HttpServletResponse.
7El Container envía la respuesta HTTP al cliente.

Modelo multihilo del Container

Un aspecto fundamental de los Servlets es que el Container utiliza un pool de hilos (thread pool) para atender las peticiones. Cada nueva petición se asigna a un hilo disponible, pero todos los hilos comparten la misma instancia del Servlet. Esto significa que los Servlets son inherentemente multihilo, lo cual ofrece una gran eficiencia pero exige precaución: las variables de instancia del Servlet son compartidas y deben manejarse con cuidado para evitar problemas de concurrencia.

⚠️ Precaución: Nunca se deben usar variables de instancia en un Servlet para almacenar datos específicos de una petición. Cada petición puede ejecutarse en un hilo diferente, y los datos se mezclarían entre usuarios. Los datos de cada petición deben manejarse como variables locales dentro de los métodos doGet() o doPost().

🔄 Ciclo de vida de un Servlet

El ciclo de vida de un Servlet está completamente gestionado por el Servlet Container y consta de tres fases claramente diferenciadas: inicialización, servicio y destrucción. Comprender este ciclo es esencial para escribir Servlets correctos y eficientes.

Fase 1: Inicialización — init()

El Container invoca el método init(ServletConfig config) una sola vez, justo después de crear la instancia del Servlet. Este método es el lugar adecuado para realizar tareas de configuración inicial como establecer conexiones a bases de datos, cargar ficheros de configuración o inicializar recursos compartidos.

Java
import javax.servlet.*; import javax.servlet.http.*; import java.io.*; public class MiServlet extends HttpServlet { private String mensajeBienvenida; @Override public void init(ServletConfig config) throws ServletException { super.init(config); // Leer parámetro de inicialización desde web.xml mensajeBienvenida = config.getInitParameter("mensajeBienvenida"); if (mensajeBienvenida == null) { mensajeBienvenida = "Bienvenido al sistema"; } System.out.println("Servlet inicializado correctamente."); } // ... métodos doGet, doPost, destroy ... }

Fase 2: Servicio — service()

Cada vez que llega una petición HTTP, el Container invoca el método service(). Este método examina el tipo de petición (GET, POST, PUT, DELETE, etc.) y delega automáticamente en el método correspondiente: doGet(), doPost(), doPut() o doDelete(). En la práctica, el desarrollador nunca sobrescribe service() directamente, sino los métodos doXxx() específicos.

Fase 3: Destrucción — destroy()

Cuando el Container decide retirar un Servlet (por inactividad, por recarga de la aplicación o por apagado del servidor), invoca el método destroy() una sola vez. Este es el lugar para liberar recursos: cerrar conexiones de base de datos, detener hilos en segundo plano, vaciar cachés, etc.

Java
@Override public void destroy() { System.out.println("Servlet destruido. Liberando recursos..."); // Cerrar conexiones, liberar pools, etc. } // Resumen del ciclo de vida: // 1. Container crea instancia → init() [una vez] // 2. Cada petición → service() → doGet()/doPost() [múltiples veces] // 3. Container retira el Servlet → destroy() [una vez]

📦 La API javax.servlet y javax.servlet.http

La API de Servlets se organiza en dos paquetes principales que contienen las interfaces y clases necesarias para desarrollar aplicaciones web. Conocer las clases clave de cada paquete es fundamental para trabajar eficazmente con Servlets.

Paquete / Clase Descripción
javax.servlet.ServletInterfaz raíz que define los métodos del ciclo de vida: init(), service(), destroy().
javax.servlet.GenericServletClase abstracta que implementa Servlet de forma genérica (independiente del protocolo).
javax.servlet.http.HttpServletExtiende GenericServlet para el protocolo HTTP. Es la clase base que se extiende en la práctica.
HttpServletRequestRepresenta la petición HTTP entrante. Contiene parámetros, cabeceras, cookies, sesión, etc.
HttpServletResponseRepresenta la respuesta HTTP. Permite establecer código de estado, cabeceras, tipo de contenido y escribir el cuerpo.
HttpSessionPermite gestionar el estado de sesión del usuario entre múltiples peticiones.
ServletContextRepresenta el contexto de la aplicación web completa. Compartido por todos los Servlets.
ServletConfigContiene la configuración específica de un Servlet individual (parámetros de init).
RequestDispatcherPermite redirigir (forward) o incluir (include) otras páginas o Servlets.
FilterInterfaz para crear filtros que interceptan peticiones antes de llegar al Servlet.

Jerarquía de herencia

La jerarquía de clases que utiliza todo Servlet HTTP es la siguiente: la interfaz Servlet define el contrato básico; GenericServlet proporciona una implementación genérica; y HttpServlet añade la lógica específica del protocolo HTTP. Cuando creamos nuestro Servlet, extendemos HttpServlet y sobrescribimos los métodos doGet() y/o doPost() según las necesidades de la aplicación.

🚀 Creación del primer Servlet paso a paso

A continuación se muestra un Servlet completo que responde a peticiones GET generando una página HTML con información dinámica. Este ejemplo ilustra la estructura básica que todo Servlet debe seguir.

Java — SaludoServlet.java
import javax.servlet.*; import javax.servlet.http.*; import javax.servlet.annotation.WebServlet; import java.io.*; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; // Anotación que mapea este Servlet a la URL /saludo @WebServlet("/saludo") public class SaludoServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 1. Establecer el tipo de contenido de la respuesta response.setContentType("text/html;charset=UTF-8"); // 2. Obtener el parámetro "nombre" de la URL String nombre = request.getParameter("nombre"); if (nombre == null || nombre.trim().isEmpty()) { nombre = "Visitante"; } // 3. Obtener la fecha y hora actual String ahora = LocalDateTime.now() .format(DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm:ss")); // 4. Escribir la respuesta HTML PrintWriter salida = response.getWriter(); salida.println("<!DOCTYPE html>"); salida.println("<html lang='es'>"); salida.println("<head><title>Saludo</title></head>"); salida.println("<body>"); salida.println("<h1>¡Hola, " + nombre + "!</h1>"); salida.println("<p>Fecha y hora del servidor: " + ahora + "</p>"); salida.println("</body></html>"); } } // Acceso: http://localhost:8080/miApp/saludo?nombre=Ana // Salida: ¡Hola, Ana! + fecha y hora del servidor

Buena práctica: Desde la especificación Servlet 3.0, la anotación @WebServlet permite mapear URLs directamente en la clase, sin necesidad de configurar web.xml. Esto simplifica enormemente el desarrollo y reduce la posibilidad de errores de configuración.

📨 Manejo de peticiones HTTP: doGet y doPost

Los dos métodos HTTP más utilizados en aplicaciones web son GET y POST. Cada uno tiene características distintas que determinan cuándo usar uno u otro, y los Servlets proporcionan métodos específicos para manejar cada tipo.

Característica GET (doGet) POST (doPost)
DatosEn la URL (query string)En el cuerpo de la petición
VisibilidadParámetros visibles en la URLParámetros ocultos
Tamaño máximoLimitado (~2 KB según navegador)Sin límite práctico
IdempotenciaSí (repetir no cambia el estado)No (puede modificar datos)
CachéSe puede cachearNo se cachea
Uso típicoConsultas, búsquedas, navegaciónFormularios, login, envío de datos

Veamos un ejemplo práctico de un Servlet que maneja un formulario de login utilizando ambos métodos: doGet() para mostrar el formulario y doPost() para procesar las credenciales.

Java — LoginServlet.java
@WebServlet("/login") public class LoginServlet extends HttpServlet { // GET: Muestra el formulario de login @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html;charset=UTF-8"); PrintWriter salida = response.getWriter(); salida.println("<form method='post' action='login'>"); salida.println(" <label>Usuario:</label>"); salida.println(" <input type='text' name='usuario'/><br/>"); salida.println(" <label>Contraseña:</label>"); salida.println(" <input type='password' name='clave'/><br/>"); salida.println(" <button type='submit'>Entrar</button>"); salida.println("</form>"); } // POST: Procesa las credenciales @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html;charset=UTF-8"); String usuario = request.getParameter("usuario"); String clave = request.getParameter("clave"); if ("admin".equals(usuario) && "secreto123".equals(clave)) { // Crear sesión y redirigir al panel HttpSession sesion = request.getSession(); sesion.setAttribute("usuarioActivo", usuario); response.sendRedirect("panel"); } else { // Credenciales incorrectas: volver al formulario con mensaje PrintWriter salida = response.getWriter(); salida.println("<p style='color:red'>Credenciales incorrectas</p>"); doGet(request, response); // Reutilizar el formulario } } }

🔍 Los objetos HttpServletRequest y HttpServletResponse

Los objetos HttpServletRequest y HttpServletResponse son las piezas centrales de la comunicación entre el cliente y el Servlet. El Container los crea por cada petición y los pasa como parámetros a los métodos doGet() y doPost().

Métodos principales de HttpServletRequest

Java
// Obtener parámetros del formulario o de la URL String nombre = request.getParameter("nombre"); String[] aficiones = request.getParameterValues("aficiones"); // múltiples valores // Obtener cabeceras HTTP String navegador = request.getHeader("User-Agent"); String tipoContenido = request.getContentType(); // Información de la petición String metodo = request.getMethod(); // GET, POST, etc. String uri = request.getRequestURI(); // /miApp/login String url = request.getRequestURL().toString(); // http://localhost:8080/miApp/login String ip = request.getRemoteAddr(); // IP del cliente // Obtener o crear la sesión del usuario HttpSession sesion = request.getSession(); // crea si no existe HttpSession sesion2 = request.getSession(false); // null si no existe // Atributos para pasar datos entre Servlet y JSP (forward) request.setAttribute("listaProductos", productos); List<Producto> lista = (List<Producto>) request.getAttribute("listaProductos");

Métodos principales de HttpServletResponse

Java
// Establecer tipo de contenido y codificación response.setContentType("text/html;charset=UTF-8"); response.setContentType("application/json;charset=UTF-8"); // Establecer código de estado HTTP response.setStatus(HttpServletResponse.SC_OK); // 200 response.sendError(HttpServletResponse.SC_NOT_FOUND); // 404 response.sendError(HttpServletResponse.SC_FORBIDDEN, "Acceso denegado"); // 403 // Redirección al cliente (nueva petición) response.sendRedirect("pagina_exito.html"); response.sendRedirect("https://www.ejemplo.com/otra-pagina"); // Establecer cabeceras HTTP response.setHeader("Cache-Control", "no-cache, no-store"); response.setHeader("X-Custom-Header", "valor"); // Añadir cookies Cookie cookie = new Cookie("idioma", "es"); cookie.setMaxAge(60 * 60 * 24 * 30); // 30 días response.addCookie(cookie); // Escribir el cuerpo de la respuesta PrintWriter salida = response.getWriter(); // para texto // OutputStream flujo = response.getOutputStream(); // para binario (imágenes, PDF)

🔐 Gestión de sesiones con HttpSession

El protocolo HTTP es stateless (sin estado): cada petición es independiente y el servidor no recuerda nada sobre peticiones anteriores del mismo cliente. Sin embargo, la mayoría de aplicaciones web necesitan mantener información entre peticiones (datos del usuario logueado, carrito de la compra, preferencias, etc.). Para resolver este problema, la API Servlet proporciona el objeto HttpSession.

El mecanismo de sesiones funciona así: cuando se crea una sesión, el Container genera un identificador único (JSESSIONID) y lo envía al navegador del cliente como una cookie. En cada petición posterior, el navegador incluye esta cookie, permitiendo al Container asociar la petición con la sesión correcta.

Java — Ejemplo de gestión de carrito con sesión
@WebServlet("/carrito") public class CarritoServlet extends HttpServlet { @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // Obtener (o crear) la sesión del usuario HttpSession sesion = request.getSession(); // Recuperar el carrito de la sesión, o crear uno nuevo @SuppressWarnings("unchecked") List<String> carrito = (List<String>) sesion.getAttribute("carrito"); if (carrito == null) { carrito = new ArrayList<>(); } // Añadir el producto solicitado String producto = request.getParameter("producto"); if (producto != null && !producto.trim().isEmpty()) { carrito.add(producto); } // Guardar el carrito actualizado en la sesión sesion.setAttribute("carrito", carrito); // Informar al usuario response.setContentType("text/html;charset=UTF-8"); PrintWriter salida = response.getWriter(); salida.println("<h2>Carrito actualizado</h2>"); salida.println("<p>Productos en el carrito: " + carrito.size() + "</p>"); for (String item : carrito) { salida.println("<li>" + item + "</li>"); } } // Cerrar sesión (logout) @Override protected void doDelete(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { HttpSession sesion = request.getSession(false); if (sesion != null) { sesion.invalidate(); // Destruye la sesión completa } response.setStatus(HttpServletResponse.SC_OK); } }

💡 Configuración del timeout: La duración por defecto de una sesión inactiva suele ser de 30 minutos. Se puede configurar en web.xml mediante <session-config><session-timeout>20</session-timeout></session-config> (en minutos) o programáticamente con sesion.setMaxInactiveInterval(1200) (en segundos).

⚙️ Filtros y listeners en Servlets

Los filtros (javax.servlet.Filter) son componentes que interceptan las peticiones HTTP antes de que lleguen al Servlet de destino (y también la respuesta antes de que vuelva al cliente). Son ideales para tareas transversales como autenticación, logging, compresión de respuestas o establecimiento del encoding.

Java — FiltroEncoding.java
import javax.servlet.*; import javax.servlet.annotation.WebFilter; import java.io.IOException; @WebFilter("/*") // Se aplica a TODAS las URLs public class FiltroEncoding implements Filter { @Override public void init(FilterConfig config) throws ServletException { // Configuración inicial del filtro } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain cadena) throws IOException, ServletException { // Pre-procesamiento: establecer encoding UTF-8 request.setCharacterEncoding("UTF-8"); response.setCharacterEncoding("UTF-8"); // Pasar la petición al siguiente filtro o al Servlet cadena.doFilter(request, response); // Post-procesamiento (después de que el Servlet responda) // Aquí se podría registrar el tiempo de respuesta, etc. } @Override public void destroy() { // Liberar recursos del filtro } }

Filtro de autenticación

Un caso de uso muy frecuente es un filtro que verifica si el usuario está autenticado antes de permitirle acceder a páginas protegidas.

Java — FiltroAutenticacion.java
@WebFilter("/admin/*") // Solo protege las URLs bajo /admin/ public class FiltroAutenticacion implements Filter { @Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain cadena) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) resp; HttpSession sesion = request.getSession(false); boolean estaLogueado = (sesion != null && sesion.getAttribute("usuarioActivo") != null); if (estaLogueado) { cadena.doFilter(request, response); // Continuar } else { response.sendRedirect(request.getContextPath() + "/login"); } } // init() y destroy() omitidos por brevedad }

Listeners

Los listeners son componentes que escuchan eventos del ciclo de vida de la aplicación, las sesiones o las peticiones. Los más utilizados son ServletContextListener (para ejecutar código al arrancar/parar la aplicación), HttpSessionListener (para contar sesiones activas) y ServletRequestListener (para logging de peticiones).

⚡ Ventajas de los Servlets sobre CGI

La tecnología Servlet surgió como respuesta directa a las limitaciones del modelo CGI (Common Gateway Interface). Las diferencias de rendimiento y arquitectura son significativas y explican por qué los Servlets se convirtieron en el estándar de la web Java.

Aspecto CGI tradicional Servlets Java
Modelo de ejecuciónUn proceso nuevo por cada peticiónUn hilo ligero por petición, misma instancia
Uso de memoriaN procesos = N copias en memoriaN hilos = 1 copia del Servlet
Tiempo de arranqueArranque de proceso completoCreación de hilo (microsegundos)
Persistencia de datosSin estado entre peticionesVariables de instancia, sesiones, contexto
PortabilidadDepende del SO y del lenguajeJava: «escribe una vez, ejecuta en cualquier servidor»
SeguridadLimitada al SOSandbox de la JVM + API de seguridad Java
Comunicación con el servidorIndirecta (variables de entorno)Directa (API del Container)

En términos de rendimiento real, un servidor con Servlets puede atender cientos o miles de peticiones concurrentes que compartirían la misma instancia del Servlet, mientras que un modelo CGI equivalente necesitaría arrancar cientos o miles de procesos independientes, consumiendo exponencialmente más recursos del sistema.

📁 Despliegue de Servlets: estructura WAR y web.xml

Las aplicaciones web Java se empaquetan en archivos WAR (Web Application Archive), que son esencialmente archivos ZIP con una estructura de directorios estandarizada. Esta estructura permite que cualquier Servlet Container compatible pueda desplegar la aplicación sin modificaciones.

Estructura de un archivo WAR
miAplicacion.war ├── index.html ← Página de bienvenida ├── estilos/ │ └── main.css ← Recursos estáticos (CSS, JS, imágenes) ├── scripts/ │ └── app.js ├── WEB-INF/ ← Directorio protegido (no accesible desde el navegador) │ ├── web.xml ← Descriptor de despliegue │ ├── classes/ ← Clases compiladas (.class) │ │ └── com/ │ │ └── miempresa/ │ │ ├── SaludoServlet.class │ │ └── LoginServlet.class │ └── lib/ ← Librerías JAR de terceros │ ├── mysql-connector.jar │ └── gson.jar

El descriptor de despliegue web.xml

El archivo web.xml, ubicado dentro de WEB-INF/, es el descriptor de despliegue que define la configuración de la aplicación web. Aunque desde la especificación Servlet 3.0 muchas configuraciones pueden hacerse con anotaciones, web.xml sigue siendo útil para configuraciones globales y para aplicaciones que necesitan mayor control declarativo.

XML — web.xml
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" version="4.0"> <!-- Definición del Servlet --> <servlet> <servlet-name>SaludoServlet</servlet-name> <servlet-class>com.miempresa.SaludoServlet</servlet-class> <init-param> <param-name>mensajeBienvenida</param-name> <param-value>Bienvenido a nuestra tienda</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <!-- Mapeo de URL --> <servlet-mapping> <servlet-name>SaludoServlet</servlet-name> <url-pattern>/saludo</url-pattern> </servlet-mapping> <!-- Configuración de sesión --> <session-config> <session-timeout>30</session-timeout> </session-config> <!-- Página de error personalizada --> <error-page> <error-code>404</error-code> <location>/error404.html</location> </error-page> <!-- Página de bienvenida --> <welcome-file-list> <welcome-file>index.html</welcome-file> </welcome-file-list> </web-app>

🧩 Ejemplo integrador: sistema de gestión de contactos

A continuación se presenta un ejemplo integrador que combina los conceptos estudiados: un Servlet que gestiona una lista de contactos en memoria, utilizando sesiones, diferentes métodos HTTP, manejo de parámetros y generación de HTML dinámico.

Java — ContactoServlet.java (Ejemplo integrador completo)
import javax.servlet.*; import javax.servlet.http.*; import javax.servlet.annotation.WebServlet; import java.io.*; import java.util.*; /** * Servlet que gestiona un directorio de contactos. * Demuestra: sesiones, doGet/doPost, parámetros, HTML dinámico. */ @WebServlet("/contactos") public class ContactoServlet extends HttpServlet { // Clase interna para representar un contacto static class Contacto implements Serializable { String nombre; String email; String telefono; Contacto(String nombre, String email, String telefono) { this.nombre = nombre; this.email = email; this.telefono = telefono; } } // GET: Mostrar la lista de contactos y el formulario @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html;charset=UTF-8"); HttpSession sesion = request.getSession(); @SuppressWarnings("unchecked") List<Contacto> contactos = (List<Contacto>) sesion.getAttribute("contactos"); if (contactos == null) { contactos = new ArrayList<>(); } PrintWriter salida = response.getWriter(); salida.println("<!DOCTYPE html>"); salida.println("<html><head><title>Directorio de Contactos</title></head>"); salida.println("<body>"); salida.println("<h1>Directorio de Contactos (" + contactos.size() + ")</h1>"); // Tabla de contactos existentes if (!contactos.isEmpty()) { salida.println("<table border='1' cellpadding='8'>"); salida.println("<tr><th>Nombre</th><th>Email</th><th>Teléfono</th></tr>"); for (Contacto c : contactos) { salida.println("<tr>"); salida.println(" <td>" + c.nombre + "</td>"); salida.println(" <td>" + c.email + "</td>"); salida.println(" <td>" + c.telefono + "</td>"); salida.println("</tr>"); } salida.println("</table>"); } else { salida.println("<p><em>No hay contactos registrados.</em></p>"); } // Formulario para añadir un nuevo contacto salida.println("<h2>Añadir contacto</h2>"); salida.println("<form method='post' action='contactos'>"); salida.println(" Nombre: <input type='text' name='nombre' required/><br/>"); salida.println(" Email: <input type='email' name='email' required/><br/>"); salida.println(" Teléfono: <input type='text' name='telefono'/><br/>"); salida.println(" <button type='submit'>Guardar contacto</button>"); salida.println("</form>"); salida.println("</body></html>"); } // POST: Añadir un nuevo contacto a la lista @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("UTF-8"); HttpSession sesion = request.getSession(); @SuppressWarnings("unchecked") List<Contacto> contactos = (List<Contacto>) sesion.getAttribute("contactos"); if (contactos == null) { contactos = new ArrayList<>(); } // Leer parámetros y crear el contacto String nombre = request.getParameter("nombre"); String email = request.getParameter("email"); String telefono = request.getParameter("telefono"); if (nombre != null && !nombre.trim().isEmpty()) { contactos.add(new Contacto( nombre.trim(), email.trim(), telefono != null ? telefono.trim() : "")); } // Guardar y redirigir (patrón POST-Redirect-GET) sesion.setAttribute("contactos", contactos); response.sendRedirect("contactos"); } } // Acceso: http://localhost:8080/miApp/contactos // Flujo: GET muestra lista + formulario → POST guarda → redirect a GET

Patrón PRG: El ejemplo implementa el patrón Post-Redirect-Get: después de procesar el POST, redirige con sendRedirect() a un GET. Esto evita que el usuario reenvíe el formulario al pulsar F5 o el botón «Atrás» del navegador.

❌ Errores frecuentes al trabajar con Servlets

A lo largo de los años, ciertos errores se repiten con frecuencia entre los desarrolladores que comienzan con Servlets. Conocerlos permite evitarlos desde el principio.

Error 1: Variables de instancia para datos de petición

Java — ❌ Incorrecto vs ✅ Correcto
// ❌ INCORRECTO: variable de instancia compartida entre hilos public class MiServlet extends HttpServlet { private String nombreUsuario; // ¡PELIGRO! Compartida entre peticiones protected void doGet(HttpServletRequest req, HttpServletResponse resp) { nombreUsuario = req.getParameter("nombre"); // Hilo A escribe "Ana" // ... procesamiento ... // Hilo B escribe "Luis" resp.getWriter().println(nombreUsuario); // Hilo A lee "Luis" → ¡BUG! } } // ✅ CORRECTO: variable local dentro del método public class MiServlet extends HttpServlet { protected void doGet(HttpServletRequest req, HttpServletResponse resp) { String nombreUsuario = req.getParameter("nombre"); // Local al hilo resp.getWriter().println(nombreUsuario); // Siempre el valor correcto } }

Error 2: Llamar a getWriter() y getOutputStream() en la misma respuesta

Una respuesta HTTP solo puede usar getWriter() (para texto) o getOutputStream() (para binario), nunca ambos. Intentar llamar al segundo después del primero lanza una IllegalStateException.

Error 3: Olvidar el encoding UTF-8

Java
// ❌ Sin encoding: los caracteres especiales se corrompen response.setContentType("text/html"); // ✅ Con encoding explícito: caracteres españoles correctos response.setContentType("text/html;charset=UTF-8"); request.setCharacterEncoding("UTF-8"); // Para leer parámetros POST correctamente

Error 4: Escribir después de sendRedirect()

Después de invocar response.sendRedirect(), no se debe escribir nada más en la respuesta. Lo correcto es añadir un return inmediatamente después de la redirección para evitar que el código posterior se ejecute y lance una IllegalStateException.

📝 Ejercicios prácticos resueltos

Ejercicio 1: Servlet calculadora

Escribe un Servlet mapeado a /calculadora que reciba los parámetros num1, num2 y operacion (suma, resta, multiplica, divide) por GET y devuelva el resultado como HTML. Debe manejar el caso de división por cero y parámetros faltantes.

Ejercicio 2: Contador de visitas con sesión

Crea un Servlet en /contador que muestre al usuario cuántas veces ha visitado la página en su sesión actual. Debe mostrar también la fecha y hora de la primera visita y de la visita actual.

Ejercicio 3: Filtro de logging con tiempo de respuesta

Implementa un filtro que registre en la consola del servidor la URL solicitada, el método HTTP, la dirección IP del cliente y el tiempo que tardó en procesarse la petición (en milisegundos). El filtro debe aplicarse a todas las URLs de la aplicación.

❓ Preguntas frecuentes sobre Servlets en Java: qué son, ciclo de vida y ejemplos prácticos

Las dudas más comunes respondidas de forma clara y directa.

Un Servlet es una clase Java que se ejecuta en un servidor web (dentro de un Servlet Container como Apache Tomcat) y que procesa peticiones HTTP para generar respuestas dinámicas. Extiende la clase HttpServlet y sobrescribe métodos como doGet() y doPost() para manejar las solicitudes del cliente.
doGet() maneja peticiones HTTP GET, que envían parámetros en la URL y son idempotentes (se pueden repetir sin efectos secundarios). doPost() maneja peticiones HTTP POST, que envían datos en el cuerpo de la petición, son más seguras para información sensible y no tienen límite de tamaño en los datos enviados.
Los Servlets son más eficientes porque reutilizan hilos (threads) en lugar de crear un nuevo proceso por cada petición como hace CGI. También son más portables al estar escritos en Java, ofrecen mejor gestión de sesiones, acceso directo al servidor web y aprovechan toda la potencia del ecosistema Java.
La gestión de sesiones se realiza mediante el objeto HttpSession, que se obtiene llamando a request.getSession(). Este objeto permite almacenar atributos con setAttribute() y recuperarlos con getAttribute(), manteniendo el estado del usuario entre múltiples peticiones HTTP, que por naturaleza son stateless.
El archivo web.xml es el descriptor de despliegue de una aplicación web Java. Se ubica en el directorio WEB-INF/ y define la configuración de los servlets, sus mapeos de URL, filtros, listeners, parámetros de contexto y otras opciones de configuración de la aplicación. Desde la especificación Servlet 3.0, muchas de estas configuraciones también pueden hacerse con anotaciones como @WebServlet.
Sí. Aunque frameworks modernos como Spring Boot, Jakarta EE o Quarkus abstraen la capa de Servlets, internamente siguen funcionando sobre la API Servlet. Comprender los Servlets es fundamental para entender cómo funcionan las aplicaciones web Java por debajo y para diagnosticar problemas de rendimiento o configuración.
Valora este artículo

💬 Foro de discusión

¿Tienes dudas sobre Servlets en Java: qué son, ciclo de vida y ejemplos prácticos? Comparte tu pregunta con la comunidad.

¿Tienes cuenta? o comenta como invitado ↓

Todavía no hay mensajes. ¡Sé el primero en participar!