Validación de campos obligatorios - eliizquierdo/material_programacion_avanzada2026 GitHub Wiki

📦 Proyecto: mini-crud2 — ⬇️ Descargar ZIP · ☁️ Abrir en Codespace


Validación de campos obligatorios y formato del código

En la página anterior (Validación de unicidad de código) el Servlet controlaba que el código no estuviera repetido. Pero esa validación supone que los datos llegan bien: si el código llega vacío o con texto, Integer.parseInt() lanza una NumberFormatException antes de llegar a consultar el DAO, y el usuario ve un error 500 en lugar de un mensaje claro.

En esta página completamos el método agregar() para que el Servlet controle, en este orden:

  1. Que el código y el nombre no lleguen vacíos.
  2. Que el código sea un número entero mayor que 0.
  3. Que el código no esté repetido (la validación que ya teníamos).

Si alguna falla, el Servlet vuelve al formulario con un mensaje de error y conservando lo que el usuario escribió. Si todas pasan, guarda y aplica el patrón PRG.

Luego de los cambios que haremos, cuando se envíe el formulario con un campo vacío el error se mostrará de la siguiente manera:

Validación de campos obligatorios


1. 🔍 Qué devuelve getParameter()

Antes de validar es necesario saber qué recibe el Servlet. request.getParameter() siempre devuelve un String, aunque el campo del formulario sea type="number":

Situación en el formulario getParameter("codigo") devuelve
El campo tiene el valor 5 "5"
El campo se envía vacío "" (texto vacío)
El campo tiene solo espacios " "
El campo no existe o el name está mal escrito null

Dos consecuencias importantes:

  • Un campo vacío no llega como null, llega como "". Por eso no alcanza con preguntar codigoStr == null: también hay que preguntar si está vacío con .trim().isEmpty(). El .trim() quita los espacios, así que un nombre escrito solo con espacios también se detecta como vacío.
  • Un int nunca puede ser null. Es un tipo primitivo, siempre guarda un número. Por eso la validación de vacío se hace sobre el String (codigoStr) antes de convertirlo a int. Escribir if (codigo == null) con un int es un error de compilación.

💡 El operador || (O lógico). En la condición codigoStr == null || codigoStr.trim().isEmpty(), si la primera parte es verdadera Java no evalúa la segunda (evaluación en cortocircuito). Gracias a eso no se produce una NullPointerException al llamar a .trim() sobre un null. El símbolo | se escribe con AltGr + 1.


2. 👨‍💻 Cambios en el Controlador (PersonaControladorServlet.java)

Igual que en la validación de unicidad, el cambio se hace en el método agregar(), que necesita throws ServletException para poder hacer forward.

La idea es usar una variable error que empieza en null. Cada validación que falla le asigna un mensaje. Al final, si error sigue en null, los datos son válidos.

private void agregar(HttpServletRequest request, HttpServletResponse response)
        throws IOException, ServletException { // ServletException se necesita para el forward

    String codigoStr = request.getParameter("codigo");
    String nombre = request.getParameter("nombre");
    String error = null;
    int codigo = 0;

    // 1. Campos obligatorios (se validan sobre el String, antes de convertir)
    if (codigoStr == null || codigoStr.trim().isEmpty()) {
        error = "El código es obligatorio.";
    } else if (nombre == null || nombre.trim().isEmpty()) {
        error = "El nombre es obligatorio.";
    } else {
        // 2. Formato y rango del código
        try {
            codigo = Integer.parseInt(codigoStr.trim());
            if (codigo <= 0) {
                error = "El código debe ser un número mayor que 0.";
            }
        } catch (NumberFormatException e) {
            error = "El código debe ser un número entero.";
        }
    }

    // 3. Unicidad: solo tiene sentido si el código ya es un número válido
    if (error == null && dao.obtenerXCodigo(codigo) != null) {
        error = "El código " + codigo + " ya está registrado. Ingrese uno diferente.";
    }

    // Si hubo algún error: volver al formulario con FORWARD
    if (error != null) {
        request.setAttribute("error", error);
        request.getRequestDispatcher("/WEB-INF/vista/persona-form.jsp").forward(request, response);
        return; // Detener la ejecución
    }

    // 4. Datos válidos: agregar y aplicar PRG
    dao.agregar(new PersonaVO(codigo, nombre.trim()));
    response.sendRedirect(request.getContextPath() + "/persona");
}

Lógica aplicada en agregar()

  • El orden de las validaciones importa. Primero se controla que el código no esté vacío, después que sea un número y recién al final la unicidad. No tiene sentido buscar en el DAO un código que no se pudo convertir a int.
  • try / catch alrededor de parseInt(). Si el texto no es un número entero (por ejemplo "abc" o "3.5"), parseInt() lanza NumberFormatException. En lugar de dejar que la excepción llegue al usuario como error 500, la capturamos y la transformamos en un mensaje.
  • codigo <= 0 y no codigo != 0. Así se rechazan tanto el 0 como los números negativos.
  • forward + return. El forward devuelve el control al formulario sin cambiar la URL y manteniendo el request. El return evita que el método siga ejecutándose y agregue la persona igual.

3. 🎨 Cambios en la Vista (persona-form.jsp)

El bloque <c:if> que muestra el error es el mismo de la página de unicidad. Lo que cambia es cómo se precargan los campos.

En la validación de unicidad guardábamos los datos en un PersonaVO (request.setAttribute("persona", ...)) y el JSP los leía con ${requestScope.persona.codigo}. Ahora eso no funciona en todos los casos: si el código llega como "abc", no se puede crear un PersonaVO, porque su código es int.

La solución es leer directamente los parámetros originales con el objeto implícito param de EL. Como el Servlet hace forward, el request es el mismo y los parámetros siguen disponibles, tal cual los escribió el usuario:

<%@ page contentType="text/html; charset=UTF-8" %>
<%@ taglib prefix="c" uri="jakarta.tags.core" %>

<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8" />
    <title>Agregar Persona</title>
    <link rel="stylesheet" href="${pageContext.request.contextPath}/css/styles.css" />
</head>
<body>
    <div class="form-container">
        <h1>Agregar Persona</h1>

        <!-- Mensaje de error enviado por el Servlet -->
        <c:if test="${requestScope.error != null}">
            <div style="color: red; border: 1px solid red; padding: 10px;
                        margin-bottom: 15px; background-color: #ffe8e8;">
                <strong>Error:</strong> ${requestScope.error}
            </div>
        </c:if>

        <form action="${pageContext.request.contextPath}/persona" method="post">
            <input type="hidden" name="action" value="agregar" />

            <label>Código</label>
            <input type="number" name="codigo" value="${param.codigo}" required />

            <label>Nombre</label>
            <input type="text" name="nombre" value="${param.nombre}" required />

            <button class="btn" type="submit">Guardar</button>
            <a class="btn btn-secondary" href="${pageContext.request.contextPath}/persona">Volver</a>
        </form>
    </div>
</body>
</html>

⚠️ Error frecuente: en la directiva taglib el atributo es uri (con "i"), no url. Si se escribe url, Tomcat muestra un error 500 con el mensaje "Atributo incorrecto, [url]".


4. 🧪 Probar la validación del servidor

Al probar, puede parecer que la validación no funciona: se deja un campo vacío, se presiona Guardar y el error del Servlet no aparece. Lo que ocurre es que el atributo required hace que el navegador bloquee el envío y muestre su propio aviso ("Completa este campo"). El formulario nunca llega al Servlet.

Para comprobar que el Servlet valida correctamente, desactivamos la validación del navegador agregando novalidate al <form>:

<form action="${pageContext.request.contextPath}/persona" method="post" novalidate>

Con novalidate el navegador envía el formulario aunque tenga campos vacíos, y es el Servlet quien decide. Los required pueden quedar en los input: mientras el <form> tenga novalidate, se ignoran.

Casos para probar:

Código Nombre Mensaje esperado
(vacío) Ana El código es obligatorio.
5 (vacío) El nombre es obligatorio.
5 (solo espacios) El nombre es obligatorio.
0 Ana El código debe ser un número mayor que 0.
-3 Ana El código debe ser un número mayor que 0.
(un código ya cargado) Ana El código … ya está registrado.
7 Ana Se guarda y vuelve al listado (PRG).

En todos los casos con error, el formulario debe volver con los valores que se habían escrito.

Algunos casos se pueden probar sin novalidate: el type="number" acepta el 0 y los negativos, y required acepta un nombre con solo espacios. Esos datos llegan al Servlet aunque el navegador valide.

🔐 ¿Por qué validar en el servidor si el navegador ya valida? La validación del navegador (required, type="number") mejora la experiencia del usuario, pero se puede saltear con facilidad: basta con quitar el required desde las herramientas del navegador (F12) o enviar el formulario desde otra herramienta. La única validación confiable es la del servidor. Por eso se usan las dos: la del navegador para avisar rápido y la del Servlet para garantizar que los datos sean correctos.


5. ✏️ Para practicar

  • Quitar el required del campo código, eliminar temporalmente el try / catch del Servlet y enviar el código vacío. Observar el error 500 por NumberFormatException y explicar por qué ocurre.
  • Aplicar la misma validación de campos obligatorios en el método editar(). ¿Hace falta validar la unicidad del código al editar? ¿Por qué? (Pista: revisar el atributo readonly del campo código en persona-editar.jsp).

📋 Palabras clave

validación del lado del servidor · getParameter · NumberFormatException · try/catch · trim() · isEmpty() · operador || · param · novalidate · required · forward · PRG


🧠 Autoevaluación — Mini-CRUD con MVC


← Validación de unicidad de código · 🏠 Inicio · Comparativa persona-form.jsp →

⚠️ **GitHub.com Fallback** ⚠️