Buenas prácticas para el uso de Bases de Datos

Un buen diseño de base de datos facilita el mantenimiento, protege la información y ayuda a que las consultas funcionen bien. Estas recomendaciones sirven como guía: conviene adaptarlas al motor de base de datos, a la aplicación y a las necesidades del proyecto.

Nombres y estructura

1. Usa nombres claros y consistentes

Elige una convención y aplícala a tablas y columnas. Los nombres en singular son una opción habitual: Estudiante en vez de Estudiantes. Usa nombres descriptivos, como FechaInscripcion en lugar de Fecha.

Ejemplo: Estudiante, Curso e Inscripcion, con columnas como FechaInscripcion y EstadoInscripcion.

2. Evita espacios y caracteres difíciles de manejar

Los espacios pueden obligar a escribir los nombres entre comillas o delimitadores, según el motor de base de datos.

Ejemplo: usa FechaNacimiento en vez de Fecha de nacimiento. También puedes usar fecha_nacimiento si esa es la convención elegida.

3. Evita prefijos redundantes

El nombre de un objeto debe describir su propósito. No hace falta indicar que es una tabla si ya se sabe por el contexto.

Ejemplo: usa Escuela en vez de TblEscuela o EscuelaTabla.

4. Define identificadores y relaciones de forma deliberada

Las claves primarias permiten identificar registros y relacionarlos. No todas tienen que ser enteros: elige el tipo que mejor represente el dominio y considera si necesitas una clave natural, una clave generada o ambas.

Ejemplo: EstudianteId puede ser un entero generado por la base de datos. Para un país, un código como MX podría ser una clave natural adecuada.

Tipos de datos y seguridad

5. Guarda las contraseñas como hashes seguros

Nunca guardes contraseñas como texto legible ni como valores que se puedan descifrar. La aplicación debe verificarlas mediante una función de hash diseñada para contraseñas, como Argon2id, bcrypt o scrypt.

Ejemplo: almacena el resultado del hash y sus parámetros. Al iniciar sesión, verifica la contraseña ingresada contra ese valor.

6. Elige tipos de datos adecuados para las claves y los índices

Los tipos compactos y compatibles con las relaciones pueden ayudar al rendimiento, pero el tipo de columna debe corresponder a los datos y las consultas reales. Una columna de texto puede ser una clave o un índice válido cuando el dominio lo justifica.

Ejemplo: usa un entero para EstudianteId. Para un código postal, conserva el texto si puede incluir ceros iniciales o letras.

7. Usa tipos booleanos para valores de verdadero o falso

Usa el tipo booleano que ofrezca el motor de base de datos. En algunos motores se representa internamente como BIT; en otros, como un tipo distinto. El nombre de la columna puede indicar claramente que contiene una condición.

Ejemplo: usa EsActivo o IsActive, con un valor verdadero o falso.

8. Protege el acceso a la base de datos con autenticación y permisos mínimos

Cada aplicación o servicio debe usar una cuenta propia con solo los permisos necesarios. Reserva las cuentas administrativas para tareas administrativas.

Ejemplo: la cuenta de la aplicación puede leer y modificar las tablas que necesita, pero no crear usuarios ni cambiar la configuración del servidor.

Consultas y acceso a los datos

9. Selecciona solo las columnas que necesitas

Evita SELECT * en las consultas de la aplicación. Solicitar campos innecesarios puede aumentar el tráfico y complicar los cambios en el esquema.

Ejemplo: usa SELECT EstudianteId, Nombre FROM Estudiante si la pantalla solo muestra el identificador y el nombre.

10. Evalúa el uso de un ORM según la complejidad del proyecto

Un ORM puede simplificar el acceso a los datos y el mantenimiento. Aun así, conviene revisar las consultas que genera y configurar correctamente las relaciones y la carga de datos.

Ejemplo: usa carga anticipada cuando necesites consultar los cursos de varios estudiantes. Evita cargar colecciones que la pantalla no utiliza. Para consultas especializadas, puede ser más conveniente escribir SQL directamente.

11. Diseña índices a partir de las consultas reales

Los índices pueden acelerar búsquedas, ordenamientos y uniones, pero también ocupan espacio y pueden hacer más lentas las escrituras. Usa planes de ejecución y herramientas de análisis para decidir cuáles crear.

Ejemplo: si las consultas filtran con frecuencia por Correo, evalúa crear un índice sobre esa columna. Si buscan por EscuelaId y ordenan por FechaInscripcion, analiza un índice compuesto para ese patrón.

12. Elige el tipo de índice según el motor y la consulta

No existe una regla universal según la consulta sea por rango o puntual. Influyen el motor, la distribución de los datos y el orden de las columnas indexadas.

Ejemplo: revisa el plan de ejecución para comprobar si un índice sobre FechaInscripcion ayuda a consultar las inscripciones de un periodo.

Integridad, disponibilidad y documentación

13. Haz cumplir la integridad de los datos en la base de datos

Usa claves primarias y foráneas, restricciones CHECK, valores NOT NULL y reglas de unicidad cuando correspondan. La aplicación también puede validar los datos, pero la base de datos debe proteger las reglas fundamentales.

Ejemplo: una clave foránea de Inscripcion.EstudianteId a Estudiante.EstudianteId evita referencias a estudiantes inexistentes.

14. Documenta el esquema y las decisiones importantes

Mantén diagramas entidad-relación, definiciones de tablas, convenciones y notas sobre procedimientos, disparadores y scripts. Actualiza la documentación cuando cambie el diseño.

Ejemplo: documenta por qué EstadoInscripcion tiene ciertos valores válidos y qué proceso actualiza ese campo.

15. Planifica los respaldos y la recuperación ante fallos

Define la frecuencia de las copias, cuánto tiempo se conservarán y cuánto tiempo de interrupción o pérdida de datos es aceptable. Prueba periódicamente la restauración: una copia que no se puede recuperar no es suficiente.

Ejemplo: una aplicación crítica puede requerir respaldos automáticos, replicación y conmutación por error, además de pruebas de recuperación.

16. Separa los componentes cuando la arquitectura y la carga lo justifiquen

Mantener el servidor de base de datos y el servidor web en máquinas distintas puede mejorar el aislamiento, la seguridad y la gestión de recursos. En sistemas pequeños o administrados en la nube, otras arquitecturas pueden ser más adecuadas.

Ejemplo: una aplicación de alto tráfico puede alojar la base de datos en una instancia privada, accesible solo desde los servicios autorizados.

17. Mantén los datos binarios grandes fuera de las consultas habituales

Las imágenes y otros archivos grandes pueden almacenarse en un sistema de archivos o en un servicio de almacenamiento de objetos, guardando en la base de datos solo una referencia. Si deben residir en la base, considera colocarlos en una tabla separada para evitar cargarlos en consultas frecuentes.

Ejemplo: guarda FotoPerfilUrl en Estudiante y almacena el archivo en un servicio de almacenamiento.

Decisiones de diseño

18. Normaliza los datos para reducir duplicación y errores

La normalización ayuda a mantener la coherencia. Si una consulta importante requiere muchas uniones y el rendimiento lo justifica, considera una desnormalización controlada, documentada y mantenida correctamente.

Ejemplo: conserva los datos de Escuela en una tabla propia. Si un informe muy usado necesita datos resumidos, puedes crear una copia que se actualice mediante un proceso definido.

19. Invierte tiempo en el diseño y valida las decisiones

Identifica las entidades, relaciones, reglas y consultas principales antes de construir el esquema. Revisa el diseño con ejemplos de datos y escenarios de cambio.

Ejemplo: antes de crear Inscripcion, define si un estudiante puede inscribirse varias veces en el mismo curso y qué regla debe impedir duplicados

Anti-patrones en Java: leer y escribir XML a mano

Leer un valor de un XML con indexOf y substring, o fabricar el documento sumando cadenas, parece más rápido que usar un analizador. Funciona con el primer ejemplo que pruebas y falla con el tercero. Lo comprobamos con seis casos, todos XML válidos.

Leer a mano

Esta es la versión que suele aparecer, con el error que trae casi siempre:

String extraer(String xml) {
    int inicio = xml.indexOf("<nombre>");
    int fin = xml.indexOf("</nombre>");
    return xml.substring(inicio, fin);
}

Devuelve <nombre>Ana: indexOf da la posición donde empieza la etiqueta, y hay que saltársela. Corregido eso, la probamos con seis documentos que cualquier analizador lee sin problema:

String[] casos = {
    "<persona><nombre>Ana</nombre></persona>",
    "<persona><nombre>Gómez &amp; Hijos</nombre></persona>",
    "<persona><nombre><![CDATA[<b>Ana</b>]]></nombre></persona>",
    "<persona><nombre >Ana</nombre></persona>",
    "<persona>\n  <!-- <nombre>viejo</nombre> -->\n  <nombre>Ana</nombre>\n</persona>",
    "<p:persona xmlns:p=\"urn:x\"><p:nombre>Ana</p:nombre></p:persona>",
};
java AMano.javaoriginal: [<nombre>Ana]
[Ana]
[Gómez &amp; Hijos]
[<![CDATA[<b>Ana</b>]]>]
[a><nombre >Ana]
[viejo]
excepción: Range [7, -1) out of bounds for length 63
CasoResultado a manoQué pasó
SimpleAnaBien
Con una entidadGómez &amp; HijosNo traduce &amp; a &
Con CDATA<![CDATA[…]]>Devuelve el envoltorio en vez del contenido
Con un espacio en la etiquetaa><nombre >Ana<nombre > es válido, pero indexOf no lo encuentra y devuelve basura
Con un comentario delanteviejoLee el nombre que estaba comentado
Con espacio de nombresexcepción<p:nombre> no coincide y el programa se cae

Cinco de seis mal, y lo peor es que solo uno avisa. Los demás devuelven un valor equivocado sin ningún error.

Leer con un analizador

El JDK trae uno, así que no hace falta añadir ninguna dependencia:

String nombre(String xml) throws Exception {
    var f = DocumentBuilderFactory.newInstance();
    f.setNamespaceAware(true);
    var doc = f.newDocumentBuilder().parse(new InputSource(new StringReader(xml)));
    var nodos = doc.getElementsByTagNameNS("*", "nombre");
    return nodos.getLength() == 0 ? null : nodos.item(0).getTextContent();
}
java ConParser.java[Ana]
[Gómez & Hijos]
[<b>Ana</b>]
[Ana]
[Ana]
[Ana]

Los seis bien. El analizador traduce las entidades, abre el CDATA, ignora los comentarios y entiende los espacios de nombres (por eso se activa setNamespaceAware y se busca con "*").

Escribir a mano

Construir el documento concatenando tiene el problema contrario: hay caracteres que en XML tienen significado y deben escaparse.

String aMano(String nombre, String atributo) {
    return "<root>"
         + "<nombre atr=\"" + atributo + "\">"
         + nombre
         + "</nombre>"
         + "</root>";
}

Con nombre = "Gómez & Hijos <S.A.>" y un atributo con comillas, el resultado ni siquiera es XML:

salida<root><nombre atr="dijo "hola"">Gómez & Hijos <S.A.></nombre></root>
[Fatal Error] :1:26: Element type "nombre" must be followed by either attribute specifications, ">" or "/>".
al leerlo: Element type "nombre" must be followed by either attribute specifications, ">" or "/>".

La línea [Fatal Error] la escribe el analizador por su cuenta en la salida de errores, antes de lanzar la excepción. Este es el error que verá quien reciba el archivo.

Escribir con el DOM

var doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
var root = doc.createElement("root");
var n = doc.createElement("nombre");
n.setAttribute("atr", atributo);
n.setTextContent(nombre);
root.appendChild(n);
doc.appendChild(root);

var t = TransformerFactory.newInstance().newTransformer();
t.setOutputProperty(OutputKeys.INDENT, "yes");
t.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
t.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
var out = new StringWriter();
t.transform(new DOMSource(doc), new StreamResult(out));
salida<root>
  <nombre atr="dijo &quot;hola&quot;">Gómez &amp; Hijos &lt;S.A.&gt;</nombre>
</root>

de vuelta: [Gómez & Hijos <S.A.>] atr=[dijo "hola"]

El &, los < > y las comillas se escapan solos. Al volver a leerlo se recupera exactamente lo que se escribió, y eso es lo que hay que exigir a cualquier forma de generar XML.

La versión original de esta entrada usaba JDOM, una biblioteca que sigue existiendo (JDOM 2) y tiene una API más cómoda que el DOM del JDK. Para documentos pequeños como estos no compensa añadir la dependencia; para leer archivos muy grandes sin cargarlos enteros en memoria, mira SAX.

Entrada publicada en 2011 y revisada en octubre de 2026. Todo el código se compiló y se ejecutó con Java 25 LTS en un contenedor limpio; las salidas están copiadas de esa ejecución.

Java y Chuck Norris

Unos cuantos hechos acerca de Chuck Norris, que quizá te convenga saber para tu examen de certificación SCJP.

  • Chuck Norris puede crear clases que son ambas cosas: abstract y final.
  • Chuck Norris no despliega aplicaciones, las mete a patadas en el servidor.
  • Chuck Norris puede usar cualquier clase en java.util.* para matarte y sin leer los javadocs. 
  • Chuck Norris puede golpear tan duro a tu aplicación web que la convierte en una aplicación swing con iconos en forma de cráneos humanos. 
  • Chuck Norris contó el mismo para llegar al valor Float.POSITIVE_INFINITY y demostrar que es correcto, dos veces. 
  • synchronized no protege de Chuck Norris, si él lo necesita lo toma. 
  • Chuck Norris no usa javac, el escribe sus aplicaciones directamente en código binario. 
  • El código java de Chuck Norris nunca necesita optimizarse. 
  • Su código es tan rápido que rompe la velocidad de la luz y durante las pruebas mata a 37 personas en los laboratorios de Sun. 
  • El código de Chuck Norris nunca tiene un error. ¡Nunca! 
  • Cuando alguien intenta usar un método deprecated de Chuck Norris, reciben automáticamente una patada voladora en tiempo de compilación. 
  • El paquete java.lang, originalmente contenía la clase Chuck Norris, pero fue retirada luego de una revisión de diseño y una patada voladora propinada a Bill Joy, el diseñador a cargo. 
  • Chuck Norris no escribe código. El simplemente se sienta frente al monitor y lo mira hasta que obtiene lo que quiere. 
  • El código corre mejor cuando Chuck Norris lo mira. 
  • Si tú obtienes una ChuckNorrisException probablemente morirás. 
  • Los objetos de Chuck Norris puede lanzar patadas voladoras a cualquier objeto privado de otro paquete. 
  • Los niveles de visibilidad son public, default, protected, private y “protectedbyChuckNorris”, ¡No intentes acceder a campos con el último modificador! 
  • Chuck Norris puede dividir por cero. 
  • El Garbage Collector solo pasa sobre el código de Chuck Norris cuando hay cuerpos que recolectar. 
  • Chuck Norris implementa “Indestructible”. Todas las demás criaturas implementan “Killable” 
  • Chuck Norris puede hacer herencia múltiple en java. 
  • El código de Chuck Norris usa generics desde la versión 1.3 de java. 
  • Cuando se ejecuta una clase de Chuck Norris, el CPU la corre al doble de velocidad. 
  • Chuck Norris y Java
  • El código de Chuck Norris no puede ser decompilado, no te molestes en tratar.
🙂

Anti-patrones en Java: concatenar cadenas en un bucle

Ir sumando trozos a una cadena con += dentro de un bucle es de esos errores que no se notan con diez elementos y que, con cien mil, dejan un proceso parado varios segundos. Lo medimos, y vemos de paso que la versión clásica tiene además un fallo con las listas vacías.

El código del problema

record Persona(String nombre) {}

String conMas(List<Persona> personas) {
    String s = "";
    for (Persona p : personas) s += ", " + p.nombre();
    return s.substring(2);
}

Funciona, y con tres nombres devuelve Ana, Luis, Marta. El problema es que las cadenas de Java no se pueden modificar: cada += crea una cadena nueva y copia en ella todo lo que ya había más el trozo nuevo. En la vuelta mil se copian los 999 nombres anteriores; en la diez mil, los 9,999. El trabajo total crece con el cuadrado del número de elementos.

Medido

Comparamos esa función con la versión de StringBuilder que hay más abajo, tomando la mejor de cinco ejecuciones para cada tamaño:

long medir(java.util.function.Supplier<String> f) {
    long mejor = Long.MAX_VALUE;
    for (int r = 0; r < 5; r++) {
        long t = System.nanoTime(); f.get(); mejor = Math.min(mejor, System.nanoTime() - t);
    }
    return mejor;
}
java Medir.javapersonas        +=   StringBuilder
     10,000       70 ms       1.18 ms
     20,000      260 ms       0.47 ms
     40,000     1001 ms       0.62 ms
     80,000     4338 ms       0.91 ms

Los milisegundos dependen de la máquina; lo que hay que mirar es cómo crecen. Cada vez que se dobla la lista, += tarda unas cuatro veces más: 70, 260, 1001, 4338. Es la firma de un algoritmo cuadrático. StringBuilder se queda en torno al milisegundo en todos los casos (a esas escalas el ruido de la medida pesa más que el trabajo, y por eso no crece de forma ordenada).

Y además falla con la lista vacía

El substring(2) del final quita la coma y el espacio que sobran al principio. Pero si la lista está vacía, no hay nada que quitar:

conMas(List.of())  —  salida recortadaException in thread "main" java.lang.StringIndexOutOfBoundsException: Range [2, 0) out of bounds for length 0
	...
	at Concatenar.conMas(Concatenar.java:9)

Es el típico arreglo que funciona en todas las pruebas hasta el día en que llega una lista vacía.

La versión con StringBuilder

String conBuilder(List<Persona> personas) {
    var sb = new StringBuilder();
    for (Persona p : personas) {
        if (sb.length() > 0) sb.append(", ");
        sb.append(p.nombre());
    }
    return sb.toString();
}

StringBuilder sí se puede modificar: va añadiendo al final de un búfer que crece cuando hace falta, así que cada nombre se copia una sola vez. Y la coma se pone antes de cada elemento salvo el primero, con lo que no hay nada que quitar al final y la lista vacía devuelve una cadena vacía.

Se le puede pasar una capacidad inicial, new StringBuilder(personas.size() * 16), para que no tenga que crecer por el camino. Es una mejora pequeña: el crecimiento ya es eficiente, y la diferencia de verdad está entre += y StringBuilder, no entre poner o no la capacidad.

Sin escribir el bucle

Para el caso más común, unir elementos con un separador, ni siquiera hace falta el StringBuilder:

IO.println(String.join(", ", tres.stream().map(Persona::nombre).toList()));
IO.println(tres.stream().map(Persona::nombre).collect(Collectors.joining(", ", "[", "]")));
var sj = new StringJoiner(" | ");
for (Persona p : tres) sj.add(p.nombre());
IO.println(sj);
salidaAna, Luis, Marta
[Ana, Luis, Marta]
Ana | Luis | Marta
Para…Se usa
Unir una lista de cadenas con un separadorString.join(", ", lista)
Unir mientras transformas, con prefijo y sufijoCollectors.joining(sep, prefijo, sufijo)
Ir añadiendo en un bucle con separadorStringJoiner
Construir un texto con formato libre en un bucleStringBuilder

¿Y el + fuera de los bucles?

No hay que tenerle miedo. Una expresión como "Hola, " + nombre + ". Tienes " + n + " mensajes" se resuelve en una sola operación: desde Java 9 el compilador la traduce a una llamada especializada que calcula el tamaño final y copia cada parte una vez. Cambiarla por un StringBuilder escrito a mano solo la hace más difícil de leer. El problema es acumular en un bucle, no el operador.

Entrada publicada en 2011 y revisada en octubre de 2026. Todo el código se compiló y se ejecutó con Java 25 LTS en un contenedor limpio; las salidas están copiadas de esa ejecución.

Invocar al recolector de basura

Java y otros lenguajes tienen un mecanismo de liberación de memoria llamado Garbage Collector, el cual permite reutilizar la memoria durante la ejecución de un programa. Se puede llamar al recolector de basura utilizando System.gc() o Runtime.getRuntime.gc(). Aunque hacer esto no se considera buena práctica ya que tiene repercusiones importantes en el rendimiento de una aplicación. La mejor práctica es no tener que llamar a estos métodos haciendo un buen diseño y uso de los recursos.

La descripción en la documentación para estos métodos es la siguiente:


System.gc()

Hace correr al recolector de basura.

Al llamar al método gc se sugiere a la máquina virtual realice un esfuerzo de reciclaje de objetos no utilizados con el fin de hacer que la memoria que estos objetos ocupan esté disponible para su uso rápido. Cuando se devuelve el control de la llamada al método, la maquina virtual de java ha hecho su mejor esfuerzo para recuperar el espacio de los objetos desechados.

La llamada System.gc() es efectivamente equivalente a la llamada Runtime.getRuntime.gc()


Runtime.getRuntime.gc()


Hace correr al recolector de basura. Al llamar a este método se sugiere a la maquina virtual realice su mayor es fuerzo de reciclaje de objetos no utilizados con el fin de hacer que la memoria que estos objetos ocupan esté disponible para su uso rápido. Cuando se devuelve el control de la llamada a al método, la máquina virtual ha hecho su mejor esfuerzo para recuperar el espacio de los objetos desechados.

La maquina virtual lleva a cabo este proceso de reciclaje de forma automática cuando es necesario, en un hilo separado, incluso si el método gc no se invoca explícitamente.

El método System.gc() es el medio convencional y conveniente de la invocación de este proceso. En algunas ocasiones esta invocación no tendrá mayor efecto, ya que la liberación de recursos nunca está garantizada.

Java versión 7

Oracle ha lanzado la versión 7 de Java. Algunas de las mejoras las listamos a continuación.

Maquina Virtual

  • JSR 292: Soporte para lenguajes escritos dinámicamente (InvokeDynamic)
  • Revisión estricta de archivos clase

Lenguaje

  • JSR 334: Mejoras pequeñas al lenguaje (Project Coin)

Core

  • Actualización de la arquitectura de carga de clases
  • Método para cerrar un URLClassLoader
  • Actualizaciones a concurrencia y colecciones (jsr166y)

Internacionalización

  • Soporte a Unicode 6.0
  • Mejoras en la API Locale
  • Localización de usuario separada y localización interfaz de usuario

IO y Networking

  • JSR 203: Nuevas APIs IO para la plataforma Java (NIO.2)
  • Un proveedor de sistema de archivos NIO.2 para archivos zip/jar
  • SCTP (Protocolo de transmisión de control de flujos)
  • SDP (Protocolo directo de sockets )
  • Uso de la pila IPv6 de Windows Vista
  • Soporte para TLS 1.2

Seguridad y Criptografía

  • Implementación de criptografía elliptic-curve (ECC)

Conectividad y Bases de Datos

  • JDBC 4.1

Cliente

  • XRender para Java 2D
  • Permite crear nuevas plataformas para gráficas 6u10
  • Nuevo look-and-feel Nimbus
  • Componente JLayer para Swing
  • Sintetizador de sonido Gervill

Web

  • Actualización de la pila de XML

Administración

  • MBeans mejorados

Mejoras diferidas a la versión de JDK8 o superiores

  • JSR 294: Soporte a la máquina virtual y al lenguaje para programación modular
  • JSR308: Anotaciones sobre tipos de java
  • JSR TBD: Proyecto Lambda
  • Modularización (Proyecto Jigsaw)
  • JSR 296: Marco de trabajo para aplicaciones Swing
  • Componente Swing JDatePicker
Puedes ver mas detalle en el artículo original de openjdk.java.net en:

Commons IO: leer un archivo y listar directorios con FileUtils

FileUtils, de Apache Commons IO, resume en una línea tareas con archivos que con java.io llevaban diez. Este es el ejemplo de siempre, leer un archivo y listar los de ciertas extensiones, puesto al día. Y al lado, la versión con java.nio, que hoy hace casi lo mismo sin añadir nada.

La dependencia

<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.22.0</version>
</dependency>

Es la última versión publicada en Maven Central. Si no usas Maven, basta con descargar el .jar y añadirlo al classpath.

El ejemplo

import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Collection;

public class Prueba {
    public static void main(String[] args) throws IOException {
        File archivo = new File("ejemplo.txt");
        String contenido = FileUtils.readFileToString(archivo, StandardCharsets.UTF_8);
        System.out.println("contenido: " + contenido.strip());

        String[] extensiones = {"xml", "java", "dat"};
        Collection<File> archivos = FileUtils.listFiles(new File("proy"), extensiones, true);
        for (File f : archivos) {
            System.out.println("> " + f.getPath());
        }
    }
}

Lo probamos con un ejemplo.txt de una línea y una carpeta proy con seis archivos, de los que solo tres tienen las extensiones que buscamos:

javac -cp commons-io-2.22.0.jar Prueba.java
java -cp .:commons-io-2.22.0.jar Prueba
salidacontenido: Hola desde decodigo.com
> proy/pom.xml
> proy/src/main/java/ejemplo/App.java
> proy/datos/clientes.dat

Encontró el .xml, el .java y el .dat, en cualquier nivel de subcarpetas, y dejó fuera el .txt, el .md y el .class. El orden es el que devuelve el sistema de archivos, que no tiene por qué ser alfabético.

Lo que cambió desde la versión de 2011

El código original llamaba a readFileToString(archivo) sin indicar la codificación. Ese método está obsoleto:

javac -Xlint:deprecationVieja.java:6: warning: [deprecation] readFileToString(File) in FileUtils has been deprecated
        System.out.println(FileUtils.readFileToString(new File("ejemplo.txt")).strip());
                                    ^
1 warning

Sin codificación, el archivo se lee con la del sistema, y el mismo programa puede leer bien los acentos en un equipo y mal en otro. Por eso hay que pasar siempre StandardCharsets.UTF_8 (o la que tenga el archivo). El otro cambio es de estilo: la lista ahora es una Collection<File> con tipo, y se recorre con un for normal, sin Iterator ni conversiones.

Lo mismo sin dependencias

import java.io.IOException;
import java.nio.file.*;
import java.util.Set;
import java.util.stream.Stream;

void main() throws IOException {
    String contenido = Files.readString(Path.of("ejemplo.txt"));
    IO.println("contenido: " + contenido.strip());

    Set<String> extensiones = Set.of("xml", "java", "dat");
    try (Stream<Path> rutas = Files.walk(Path.of("proy"))) {
        rutas.filter(Files::isRegularFile)
             .filter(p -> {
                 String n = p.getFileName().toString();
                 int punto = n.lastIndexOf('.');
                 return punto > 0 && extensiones.contains(n.substring(punto + 1));
             })
             .sorted()
             .forEach(p -> IO.println("> " + p));
    }
}
java SinDependencias.javacontenido: Hola desde decodigo.com
> proy/datos/clientes.dat
> proy/pom.xml
> proy/src/main/java/ejemplo/App.java

Files.readString lee en UTF-8 por omisión, y Files.walk recorre el árbol. Filtrar por extensión lleva unas líneas más que con FileUtils, pero no hace falta ningún .jar. Aquí además las ordenamos con sorted(). El try con recursos es necesario, porque Files.walk deja abiertos los directorios hasta que se cierra el stream.

Para…Commons IOjava.nio (JDK)
Leer un archivo enteroFileUtils.readFileToString(f, UTF_8)Files.readString(ruta)
Escribir un archivoFileUtils.writeStringToFile(f, texto, UTF_8)Files.writeString(ruta, texto)
Listar por extensión, recursivoFileUtils.listFiles(dir, exts, true)Files.walk + filtro
Copiar un directorio enteroFileUtils.copyDirectory(a, b)a mano, con Files.walk
Borrar un directorio con contenidoFileUtils.deleteDirectory(dir)a mano, con Files.walk al revés

Para leer y escribir, java.nio basta. Commons IO sigue ahorrando trabajo en las operaciones con directorios enteros, como copiarlos o borrarlos. Las vemos, con sus salidas, en FileUtils de Commons IO.

Entrada publicada en 2011 y revisada en octubre de 2026. Todo el código se compiló y se ejecutó con Java 25 LTS en un contenedor limpio con Commons IO 2.22.0; las salidas están copiadas de esa ejecución.

Errores comunes en la ejecución de proyectos de TI

¿Quién no ha incurrido en estos errores? Si ya llevas algún tiempo desarrollando, seguro has sufrido por alguno de ellos.

La lista de errores más frecuentes encontrados en la ejecución de los proyectos de TI:

Errores relacionados con la gente

  • Miembros del equipo desmotivados
  • Falta de capacitación de la gente involucrada en el desarrollo
  • Personas problemáticas en el equipo
  • Agregar más programadores a un proyecto ya retrasado
  • Oficinas ruidosas y aglomeradas
  • Fricciones entre quienes desarrollan y el cliente
  • Expectativas poco realistas
  • Falta de un líder efectivo para el proyecto
  • Falta de participación del usuario
  • Falta de acuerdo antes que el desarrollo de inicio
  • Las “Ilusiones”

Errores relacionados con los procesos

  • Planeación demasiado optimista
  • Falta de administración de riesgos
  • Falla en los contratos
  • No hay suficiente planeación
  • Abandono del plan bajo presión
  • Pérdida de tiempo al inicio del proyecto
  • Saltarse las tareas iniciales
  • Diseño no adecuado
  • Falta de supervisión por parte de los responsables
  • Falta de estimaciones adecuadas
  • Errores relacionados con el producto
  • Requerimientos excesivos
  • Cambio de requerimientos
  • Developers muy meticulosos

Errores relacionados con la tecnología

  • Síndrome de la panacea
  • Sobre-estimación de las ventajas del uso de una nueva herramienta
  • Cambio de herramientas a mitad del proyecto
  • Falta de un control de código fuente automático

Seguramente ya hay muchos mas.

Y ahora algunas de las razones por las que se incurre en estos errores:

Errores relacionados con la gente

Miembros del equipo desmotivados. Es casi imposible encontrar gente que se enamore de todos los proyectos.
Falta de capacitación de la gente involucrada en el desarrollo. «La gente que va comenzando es la que cobra menos, ¿Y por qué no meter solo becarios?» ¡Típico!
Personas problemáticas. Dime si no has estado junto al tipo que cree saber más que el líder de proyecto, pero no lleva más de unas semanas en un proyecto real (los de la escuela no cuentan). O bien, gente que simplemente no está bien con la vida ¡Y se dedicó a programar!
Agregar más programadores a un proyecto ya retrasado. Y como además no es necesario capacitarlos, explicarles las reglas de negocio, explicarles lo que quisieron hacer los otros programadores (que seguro ya no se acuerdan o ya ni están).
Oficinas ruidosas y aglomeradas. «Son programadores, no personas y solo necesitan una PC delante de ellos y unas mesas de madera»
Fricciones entre los developers y el cliente. ¡Esa manía de que la gente de TI de creer que siempre sabe lo que el cliente realmente necesita!
Expectativas poco realistas. Nunca falta el que quiere que le fabriquen un Word y pagar solo lo que cuesta el CD pirata.
Falta de un líder efectivo para el proyecto. Bueno, la gente de TI no nació para administrar proyectos, ¡Por eso programa! Pero casi nadie respeta a un líder de proyecto que no ha programado, por eso debe ser una especie de super administrador-programador-buena-gente.
Falta de participación del usuario. El usuario solo quiere sentarse y que su programa funcione y que no le quite tiempo para entrar al facebook.
Falta de acuerdo antes que el desarrollo de inicio. Y es que, ¿Cómo podemos estar de acuerdo con todas las tonterías que el cliente pide?
Las “Ilusiones”. El usuario piensa que le darán un automóvil, el líder de proyecto cree que solo le dará tiempo para hacer una bicicleta, los programadores tienen problemas para construir las ruedas de unos simples patines, y al final el usuario va a terminar caminando, como siempre lo ha hecho (el usuario es el incauto que trabaja para el que contrató a tu empresa, le vas a quitar tiempo y al final harás mas difícil su trabajo)

Errores relacionados con los procesos

Planeamiento demasiado optimista. ¡Claro! Hacemos un módulo por día y el último integramos todo.
Falta de administración de riesgos. ¿Alguien conoce a los que se dedican a administrar esas cosas?
Falla en los contratos. Lo bueno de usar plantillas de World es que todas incluyen las letras chiquitas donde no nos hacemos responsables de errores o vicios ocultos después de seis o más meses. Que es más o menos el tiempo en el que la base de datos se irá al carajo.
No hay suficiente planeamiento. Muchas veces solo se tiene el texto que viene en la propuesta comercial.
Abandono del planeamiento bajo presión. Sobre todo cuando le dices al usuario que hacer un facebook o un twitter es normalmente más caro que hacer una simple página web.
Pérdida de tiempo al inicio del proyecto. Y cuando pasa el pánico y comienza la negación.
Saltarse las tareas iniciales. Nunca analizas y nunca diseñas, ¡Esa es tarea de ñoños!
Diseño inadecuado. ¡Porque así me enseñaron en la escuela y los profesores son gente muy capacitada que lleva años en la industria del desarrollo de sistemas!
Falta de supervisión. Porque el gestor y el líder llevan como veinte proyectos al mismo tiempo y todavía no terminan otros que ya se entregaron. Y por supuesto los programadores se pueden enseñar solitos sobre lo que el cliente quiere.
Falta de estimaciones. ¡Para que! Si solo es la autoestima de los programadores lo que se va al carajo cuando fallan los proyectos.

Errores relacionados con el producto

Requerimientos excesivos. Si no está clara la propuesta el cliente te va a meter todas las funcionalidades que pueda y si puede hacer todo su trabajo, mejor.
Cambio de requerimientos. Porque claro, el mercado cambia constantemente y hay que ser competitivos y ajustarse a esos cambios. Pero la verdad es que, es hasta que el usuario comienza a usar el sistema, que se da cuenta de que nadie tenía idea de lo que se tenía que hacer realmente.
Developers muy meticulosos. Todos los programmers somos obsesivos compulsivos, es lo que nos metió en esto desde un principio.

Errores relacionados con la tecnología

Síndrome de la panacea. ¡Claro! Hay que usar Struts, Spring y Hibernate…  siempre y ya con eso quedan bien todos los sistemas.
Sobre-estimación de las ventajas del uso de una nueva herramienta. Como cuando usas una de esas herramientas de business intelligence o un CMS, que según el vendedor, harán prácticamente todo lo que el cliente te pida.
Cambio de herramientas a mitad del proyecto. Si no funciona Struts usa JSF, porque es mejor, si no funciona tampoco pues usa Spring MVC, porque es más simple, si no funciona mete Flex, porque se ve bien, si no funciona…  y así.
Falta de un control de código fuente automático. Te apuesto que eso si no te lo enseñaron en la escuela. Y que difícil es entender y  usar subversión y que todos tus compañeros lo usen, la primera vez.