🌐 ¿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 |
|---|---|
| 1 | El cliente (navegador) envía una petición HTTP al servidor web. |
| 2 | El servidor web delega la petición al Servlet Container. |
| 3 | El Container identifica qué Servlet debe manejar la URL solicitada (mediante el mapeo definido en web.xml o anotaciones). |
| 4 | Si el Servlet no está cargado en memoria, el Container lo instancia y ejecuta su método init(). |
| 5 | El Container crea los objetos HttpServletRequest y HttpServletResponse e invoca el método service(), que delega en doGet(), doPost(), etc. |
| 6 | El Servlet procesa la petición, genera la respuesta y la escribe en el objeto HttpServletResponse. |
| 7 | El 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.
Javaimport 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.Servlet | Interfaz raíz que define los métodos del ciclo de vida: init(), service(), destroy(). |
javax.servlet.GenericServlet | Clase abstracta que implementa Servlet de forma genérica (independiente del protocolo). |
javax.servlet.http.HttpServlet | Extiende GenericServlet para el protocolo HTTP. Es la clase base que se extiende en la práctica. |
HttpServletRequest | Representa la petición HTTP entrante. Contiene parámetros, cabeceras, cookies, sesión, etc. |
HttpServletResponse | Representa la respuesta HTTP. Permite establecer código de estado, cabeceras, tipo de contenido y escribir el cuerpo. |
HttpSession | Permite gestionar el estado de sesión del usuario entre múltiples peticiones. |
ServletContext | Representa el contexto de la aplicación web completa. Compartido por todos los Servlets. |
ServletConfig | Contiene la configuración específica de un Servlet individual (parámetros de init). |
RequestDispatcher | Permite redirigir (forward) o incluir (include) otras páginas o Servlets. |
Filter | Interfaz 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.javaimport 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) |
|---|---|---|
| Datos | En la URL (query string) | En el cuerpo de la petición |
| Visibilidad | Parámetros visibles en la URL | Parámetros ocultos |
| Tamaño máximo | Limitado (~2 KB según navegador) | Sin límite práctico |
| Idempotencia | Sí (repetir no cambia el estado) | No (puede modificar datos) |
| Caché | Se puede cachear | No se cachea |
| Uso típico | Consultas, búsquedas, navegación | Formularios, 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.javaimport 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ón | Un proceso nuevo por cada petición | Un hilo ligero por petición, misma instancia |
| Uso de memoria | N procesos = N copias en memoria | N hilos = 1 copia del Servlet |
| Tiempo de arranque | Arranque de proceso completo | Creación de hilo (microsegundos) |
| Persistencia de datos | Sin estado entre peticiones | Variables de instancia, sesiones, contexto |
| Portabilidad | Depende del SO y del lenguaje | Java: «escribe una vez, ejecuta en cualquier servidor» |
| Seguridad | Limitada al SO | Sandbox de la JVM + API de seguridad Java |
| Comunicación con el servidor | Indirecta (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 WARmiAplicacion.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.
💬 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.
Todavía no hay mensajes. ¡Sé el primero en participar!