El método main en Java

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

🚀 ¿Qué es el método main?

El método main es el punto de entrada de cualquier aplicación Java. Cuando se ejecuta un programa, la Máquina Virtual de Java (JVM) busca dentro de la clase especificada un método con una firma muy concreta —public static void main(String[] args)— y comienza a ejecutar el código que contiene. Sin este método, la JVM no sabe por dónde empezar y el programa no puede arrancar.

Se puede pensar en el método main como la «puerta de entrada» de un edificio: un edificio puede tener cientos de habitaciones (clases y métodos), pero quien llega desde fuera necesita una puerta principal claramente identificada. La JVM actúa como ese visitante externo: localiza la puerta (main), la abre y a partir de ahí recorre el interior del programa.

💡
Concepto clave: Cada palabra de la firma public static void main(String[] args) tiene una razón de ser. No es una convención arbitraria: la JVM impone exactamente estos modificadores y tipos. A lo largo de este artículo se explica el porqué de cada uno.
// El programa Java más sencillo posible
public class HolaMundo {

    public static void main(String[] args) {
        System.out.println("¡Hola, Mundo!");
    }
}

Al compilar y ejecutar este archivo, la JVM localiza la clase HolaMundo, encuentra el método main y ejecuta la instrucción System.out.println, que imprime el texto en la consola. Este sencillo flujo es el mismo que sigue cualquier aplicación Java, por grande que sea: todo comienza en main.

📝 Sintaxis completa del método main

La firma del método main que la JVM reconoce como punto de entrada es exactamente la siguiente:

public static void main(String[] args) {
    // Código del programa
}

Cada elemento de esta firma cumple una función específica que la JVM verifica antes de iniciar la ejecución:

Elemento Significado Razón
public Modificador de acceso Visible desde cualquier lugar, incluida la JVM
static Método de clase Se invoca sin crear un objeto de la clase
void Tipo de retorno No devuelve ningún valor a la JVM
main Nombre del método Nombre fijo que la JVM busca literalmente
String[] args Parámetro Recibe los argumentos de línea de comandos
⚠️
Atención: Si se modifica cualquiera de estos elementos —por ejemplo, se omite static o se cambia void por int— el método sigue siendo válido como método Java normal, pero la JVM no lo reconocerá como punto de entrada y lanzará un error al intentar ejecutar la clase.

🔓 ¿Por qué public?

El modificador public indica que el método es accesible desde cualquier punto del programa, incluidos paquetes externos y, por supuesto, la propia JVM. Cuando se ejecuta java MiClase, la Máquina Virtual necesita invocar main desde fuera del paquete de la aplicación. Si el método fuese private, protected o tuviese visibilidad de paquete (sin modificador), la JVM no podría acceder a él.

En la práctica, public aquí no es una decisión de diseño del programador, sino un requisito impuesto por la especificación del lenguaje. La JVM debe poder localizar y ejecutar main sin restricciones de visibilidad.

// Correcto: la JVM puede acceder a main
public static void main(String[] args) {
    System.out.println("Programa iniciado");
}

// Incorrecto: la JVM no puede acceder
private static void main(String[] args) {
    System.out.println("Nunca se ejecutará");
}

⚡ ¿Por qué static?

La palabra clave static significa que el método pertenece a la clase y no a una instancia concreta de esa clase. Esto es fundamental: cuando la JVM inicia la ejecución, no existe ningún objeto creado todavía. Si main fuese un método de instancia, la JVM necesitaría crear primero un objeto de la clase, pero ¿cómo lo haría sin un constructor explícito o sin saber qué parámetros pasarle?

Al declarar main como static, se resuelve este problema de raíz: la JVM simplemente invoca MiClase.main(args) directamente sobre la clase, sin necesidad de instanciarla.

// La JVM ejecuta internamente algo equivalente a:
HolaMundo.main(new String[]{});

// Esto funciona porque main es static.
// Si no fuese static, se necesitaría:
// HolaMundo obj = new HolaMundo();  ← ¿Con qué constructor?
// obj.main(args);                    ← Problema resuelto con static
Recuerda: Un método static no puede acceder directamente a variables ni métodos de instancia de su clase. Si desde main necesitas usar miembros no estáticos, primero debes crear un objeto: MiClase obj = new MiClase(); y luego invocar obj.miMetodo().

🔹 Diferencia entre método de instancia y método de clase

Para entender mejor la razón del static, conviene distinguir claramente ambos tipos:

Característica Método de instancia Método static (de clase)
Pertenece a Un objeto concreto La clase en sí
Requiere instancia Sí: obj.metodo() No: Clase.metodo()
Acceso a this No
Ejemplo típico cuenta.getSaldo() Math.sqrt(25)

🔇 ¿Por qué void?

El tipo de retorno void indica que el método no devuelve ningún valor. En el caso de main, esto tiene una razón histórica y práctica: la JVM no espera recibir un valor de retorno del programa. Cuando main termina, la JVM simplemente finaliza el proceso.

En lenguajes como C o C++, la función main devuelve un int que el sistema operativo interpreta como código de salida (0 = éxito, distinto de 0 = error). Java adoptó un enfoque diferente: si se necesita indicar un código de salida al sistema operativo, se utiliza System.exit(int código).

public class ValidadorEntrada {

    public static void main(String[] args) {
        if (args.length == 0) {
            System.err.println("Error: se requiere al menos un argumento");
            System.exit(1);  // Código de error para el SO
        }
        System.out.println("Argumento recibido: " + args[0]);
        // Si main termina normalmente, el código de salida es 0
    }
}
💡
Comparación con C/C++: En C se escribe int main() y se devuelve un entero. En Java se escribe void main() y se usa System.exit() solo cuando se necesita un código de salida explícito. Si no se invoca System.exit(), Java devuelve 0 al sistema operativo automáticamente.

📦 El parámetro String[] args

El parámetro String[] args es un array de cadenas que recoge los argumentos que el usuario pasa al programa a través de la línea de comandos. Cada palabra separada por espacios se convierte en un elemento del array.

// Archivo: Imprime.java
public class Imprime {

    public static void main(String[] args) {
        System.out.println("Número de argumentos: " + args.length);
        for (int i = 0; i < args.length; i++) {
            System.out.println("args[" + i + "] = " + args[i]);
        }
    }
}

Si se compila y ejecuta con argumentos:

$ javac Imprime.java
$ java Imprime hola que tal estas

La salida será:

Número de argumentos: 4
args[0] = hola
args[1] = que
args[2] = tal
args[3] = estas

🔹 ¿Qué pasa si no se pasan argumentos?

Si se ejecuta java Imprime sin nada más, el array args existe pero tiene longitud cero (args.length == 0). Nunca es null. Esto significa que siempre es seguro consultar args.length sin verificar null previamente.

🔹 Recibir números como argumentos

Los argumentos siempre llegan como String. Si se necesitan como números, hay que convertirlos explícitamente con métodos como Integer.parseInt() o Double.parseDouble():

public class Suma {

    public static void main(String[] args) {
        if (args.length != 2) {
            System.out.println("Uso: java Suma <num1> <num2>");
            return;
        }
        int a = Integer.parseInt(args[0]);
        int b = Integer.parseInt(args[1]);
        System.out.println("La suma es: " + (a + b));
    }
}
$ java Suma 15 27
La suma es: 42
⚠️
Cuidado: Si el usuario pasa un texto que no es un número (por ejemplo, java Suma hola 5), Integer.parseInt() lanza una NumberFormatException. En programas profesionales, siempre se envuelve la conversión en un bloque try-catch.

🔄 Variantes válidas de la firma

Aunque la forma más habitual es public static void main(String[] args), Java admite ciertas variaciones sintácticas que la JVM sigue reconociendo como punto de entrada válido:

Firma ¿Válida? Observación
public static void main(String[] args) Forma canónica y recomendada
public static void main(String args[]) Corchetes junto a la variable (estilo C)
public static void main(String... args) Varargs — equivalente a String[]
static public void main(String[] args) Orden de modificadores invertido (válido pero poco convencional)
public static void main(String[] parametros) Nombre del parámetro cambiado — irrelevante para la JVM
public static int main(String[] args) Tipo de retorno distinto de void
public void main(String[] args) Falta static
static void main(String[] args) Falta public
Buena práctica: Usa siempre la forma canónica public static void main(String[] args). Las variantes funcionan pero confunden a otros programadores y herramientas de análisis estático.

⚙️ Flujo de ejecución de la JVM

Cuando se escribe java MiClase arg1 arg2 en la terminal, la JVM realiza una secuencia precisa de pasos antes de que la primera línea de main se ejecute:

🔹 Paso 1: Carga de la clase

El Class Loader busca el archivo MiClase.class en el classpath. Si no lo encuentra, lanza ClassNotFoundException. Si lo encuentra, lee el bytecode y lo carga en memoria.

🔹 Paso 2: Enlace (linking)

La JVM verifica que el bytecode es válido (verificación), asigna memoria para las variables estáticas (preparación) y resuelve las referencias simbólicas (resolución).

🔹 Paso 3: Inicialización

Se ejecutan los bloques static { } de la clase y se asignan los valores iniciales a las variables estáticas. Este paso ocurre antes de que se invoque main.

🔹 Paso 4: Invocación de main

La JVM busca un método con la firma exacta public static void main(String[]). Si lo encuentra, crea el array de String con los argumentos de línea de comandos y lo pasa como parámetro. Si no lo encuentra, lanza NoSuchMethodError.

public class DemoFlujo {

    static {
        System.out.println("1. Bloque estático ejecutado");
    }

    public static void main(String[] args) {
        System.out.println("2. Método main ejecutado");
    }
}
$ java DemoFlujo
1. Bloque estático ejecutado
2. Método main ejecutado

Observa que el bloque static se ejecuta antes que main. Este orden es determinista y forma parte de la especificación de Java.

🏗️ Ejemplo completo integrador

El siguiente programa combina todos los conceptos del artículo: argumentos de línea de comandos, conversión de tipos, validación de entrada, manejo de errores y uso de métodos estáticos y de instancia desde main.

/**
 * Calculadora de nómina simple.
 * Uso: java CalculadoraNomina <nombre> <horasTrabajadas> <precioPorHora>
 */
public class CalculadoraNomina {

    private String nombre;
    private int horas;
    private double precioHora;

    public CalculadoraNomina(String nombre, int horas, double precioHora) {
        this.nombre = nombre;
        this.horas = horas;
        this.precioHora = precioHora;
    }

    public double calcularSalarioBruto() {
        double salario = horas * precioHora;
        if (horas > 40) {
            // Horas extra al 150%
            int horasExtra = horas - 40;
            salario = 40 * precioHora + horasExtra * precioHora * 1.5;
        }
        return salario;
    }

    public void mostrarResumen() {
        System.out.println("=== Resumen de Nómina ===");
        System.out.println("Empleado:      " + nombre);
        System.out.println("Horas:         " + horas);
        System.out.printf("Precio/hora:   %.2f €%n", precioHora);
        System.out.printf("Salario bruto: %.2f €%n", calcularSalarioBruto());
    }

    // --- Punto de entrada ---
    public static void main(String[] args) {

        // Paso 1: Validar número de argumentos
        if (args.length != 3) {
            System.err.println("Uso: java CalculadoraNomina <nombre> <horas> <precioHora>");
            System.exit(1);
        }

        // Paso 2: Leer y convertir argumentos
        String nombre = args[0];
        int horas;
        double precioHora;

        try {
            horas = Integer.parseInt(args[1]);
            precioHora = Double.parseDouble(args[2]);
        } catch (NumberFormatException e) {
            System.err.println("Error: horas y precioHora deben ser números.");
            System.exit(1);
            return; // Inalcanzable, pero satisface al compilador
        }

        // Paso 3: Crear objeto y mostrar resultado
        CalculadoraNomina nomina = new CalculadoraNomina(nombre, horas, precioHora);
        nomina.mostrarResumen();
    }
}
$ java CalculadoraNomina Ana 45 20.00
=== Resumen de Nómina ===
Empleado:      Ana
Horas:         45
Precio/hora:   20,00 €
Salario bruto: 950,00 €
💡
Observa el patrón: El método main (estático) lee los datos de entrada, crea un objeto de la clase y delega la lógica de negocio en métodos de instancia. Esta separación mantiene el código limpio y testable: los métodos de instancia se pueden probar independientemente sin depender de main.

🐛 Errores frecuentes

Estos son los errores más comunes que encuentran los principiantes al trabajar con el método main:

❌ Error 1: Olvidar static

// Error: Main method is not static in class MiClase
public void main(String[] args) { ... }

Solución: Añadir static a la declaración del método.

❌ Error 2: Escribir Main con mayúscula

// Error: la JVM busca exactamente "main", no "Main"
public static void Main(String[] args) { ... }

Solución: Java es sensible a mayúsculas (case-sensitive). El nombre debe ser main en minúsculas.

❌ Error 3: Parámetro incorrecto

// Error: no es punto de entrada válido
public static void main(int[] args) { ... }
public static void main() { ... }

Solución: El parámetro debe ser exactamente String[] (o String...). Sin parámetros o con otro tipo, la JVM no reconoce el método.

❌ Error 4: Acceder a miembros de instancia desde main

public class MiClase {
    int valor = 10;

    public static void main(String[] args) {
        // Error: non-static variable valor cannot be referenced from a static context
        System.out.println(valor);
    }
}

Solución: Crear una instancia de la clase y acceder a través del objeto:

public static void main(String[] args) {
    MiClase obj = new MiClase();
    System.out.println(obj.valor);  // Correcto
}

✏️ Ejercicios prácticos

Ejercicio 1: Saludo personalizado por línea de comandos

Escribe un programa que reciba por línea de comandos un nombre y un idioma (es, en o fr) y muestre un saludo en el idioma correspondiente. Si no se pasan exactamente 2 argumentos, debe mostrar las instrucciones de uso.

Ejercicio 2: Calculadora de media aritmética

Escribe un programa que reciba por línea de comandos una cantidad variable de números enteros y calcule su media aritmética. Si no se pasan argumentos, debe indicar el error. Si algún argumento no es un número válido, debe mostrarse un mensaje informativo.

Ejercicio 3: Registro de estudiantes por línea de comandos

Diseña un programa con una clase Estudiante (nombre, nota) y un método main que reciba pares de argumentos <nombre> <nota>, cree objetos Estudiante, los almacene en un array y muestre un listado final indicando quién aprobó (nota >= 5) y quién no.

❓ Preguntas frecuentes sobre El método main en Java

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

Sí, una clase puede tener varios métodos llamados main siempre que sus firmas sean diferentes (distinto número o tipo de parámetros), ya que Java permite la sobrecarga de métodos. Sin embargo, la JVM solo reconoce como punto de entrada el método con la firma exacta public static void main(String[] args). Los demás se tratan como métodos normales.
Si se omite la palabra clave static, la JVM no encontrará el punto de entrada y lanzará el error «Main method is not static in class X». Esto sucede porque la JVM necesita invocar main sin crear una instancia de la clase, y solo los métodos estáticos pueden llamarse directamente sobre la clase.
Es un array de cadenas de texto que recibe los argumentos pasados por línea de comandos al ejecutar el programa. Si se ejecuta «java MiClase hola mundo», args[0] será «hola» y args[1] será «mundo». Si no se pasan argumentos, el array existe pero tiene longitud cero (args.length == 0), nunca es null.
Sí. El nombre del parámetro es solo un identificador elegido por el programador. Se podría usar main(String[] argumentos) o main(String[] parametros) y el programa funcionaría exactamente igual. Lo que la JVM comprueba es el tipo (String[]) y la firma completa, no el nombre del parámetro.
No. La JVM exige que main sea void (sin valor de retorno). Si se cambia void por int u otro tipo, el compilador no dará error, pero la JVM no lo reconocerá como punto de entrada y lanzará un error al intentar ejecutar la clase. Para comunicar un código de salida al sistema operativo se utiliza System.exit(int código).
javac es el compilador: transforma el archivo fuente .java en bytecode .class. java es el lanzador de la JVM: carga el archivo .class, busca el método main y comienza la ejecución. Primero se compila con javac y luego se ejecuta con java. A partir de Java 11 también se puede usar java directamente sobre un archivo .java de una sola clase como atajo.
Valora este artículo

💬 Foro de discusión

¿Tienes dudas sobre El método main en Java? Comparte tu pregunta con la comunidad.

¿Tienes cuenta? o comenta como invitado ↓

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