1. De «funciona en mi equipo» a «funciona en el de todos»
Terminar el código no es terminar la aplicación. Hay que empaquetarla con todo lo que necesita, crear un instalador que el usuario sepa usar, firmarla para que el sistema confíe en ella, publicarla por el canal adecuado y prever cómo se actualizará.
Distribuir es como enviar un mueble 📦
Componentes de un paquete de distribución
- Los binarios de la aplicación (clases compiladas, ejecutable).
- Las dependencias (librerías) y los recursos (imágenes, estilos, FXML, traducciones, plantillas de informes).
- El entorno de ejecución si hace falta (una JRE recortada con jlink).
- Configuración por defecto, documentación y licencia (EULA).
- Accesos directos, asociaciones de ficheros, entradas del menú y un desinstalador.
2. Empaquetar una aplicación Java
| Formato | Contiene | El usuario necesita |
|---|---|---|
| JAR normal | Solo tus clases | Java instalado y las dependencias aparte |
| JAR ejecutable «fat» (uber-jar) | Tus clases + todas las dependencias | Java instalado; se abre con java -jar o doble clic |
| Imagen con jlink | Tu aplicación + una JRE recortada con los módulos necesarios | Nada |
| Instalador nativo (jpackage) | Imagen jlink dentro de un .msi/.exe, .dmg/.pkg o .deb/.rpm | Nada: instalación como cualquier programa |
1<!-- JAR ejecutable con todas las dependencias (maven-shade-plugin) -->
2<plugin>
3 <groupId>org.apache.maven.plugins</groupId>
4 <artifactId>maven-shade-plugin</artifactId>
5 <version>3.6.0</version>
6 <executions>
7 <execution>
8 <phase>package</phase>
9 <goals><goal>shade</goal></goals>
10 <configuration>
11 <transformers>
12 <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
13 <mainClass>com.empresa.gestion.Lanzador</mainClass>
14 </transformer>
15 </transformers>
16 </configuration>
17 </execution>
18 </executions>
19</plugin>1mvn clean package # target/gestion-2.1.0.jar (con dependencias)
2java -jar target/gestion-2.1.0.jar
3
4# Runtime a medida: solo los módulos que usa la aplicación
5jdeps --print-module-deps target/gestion-2.1.0.jar # p. ej. java.base,java.sql,javafx.controls
6jlink --add-modules java.base,java.sql --output runtime --strip-debug --no-header-files --no-man-pages
7
8# Instalador nativo con jpackage (hay que generarlo en cada sistema operativo)
9jpackage --name GestionClientes --app-version 2.1.0 --vendor "Empresa S.L." \
10 --input target --main-jar gestion-2.1.0.jar --main-class com.empresa.gestion.Lanzador \
11 --type msi --icon icono.ico --win-menu --win-shortcut --win-dir-chooser
12# macOS: --type dmg --icon icono.icns Linux: --type deb --linux-shortcutJavaFX
javafx-maven-plugin permite ejecutar (mvn javafx:run) y crear la imagen con jlink (mvn javafx:jlink). Como el main de una aplicación JavaFX extiende Application, para el JAR ejecutable se usa una clase Lanzador que llama a App.main().3. Instaladores
Un instalador copia los ficheros, crea accesos directos y entradas en el menú, registra la aplicación en el sistema (para poder desinstalarla), comprueba requisitos y permite elegir opciones (carpeta, idioma, componentes).
| Herramienta | Genera | Notas |
|---|---|---|
| jpackage | MSI/EXE, DMG/PKG, DEB/RPM | Incluido en el JDK desde Java 16 |
| Inno Setup | EXE para Windows | Gratuito, con un lenguaje de script sencillo |
| WiX Toolset | MSI para Windows | Instaladores profesionales; lo usa jpackage por debajo |
| NSIS | EXE para Windows | Libre, muy configurable |
| Install4j, Advanced Installer | Multiplataforma / Windows | Comerciales |
| dpkg-deb, rpmbuild | Paquetes .deb / .rpm | Para repositorios de Linux |
1[Setup]
2AppName=Gestión de Clientes
3AppVersion=2.1.0
4AppPublisher=Empresa S.L.
5DefaultDirName={autopf}\GestionClientes
6DefaultGroupName=Gestión de Clientes
7OutputBaseFilename=GestionClientes-2.1.0-setup
8LicenseFile=LICENSE.txt
9Compression=lzma2
10SetupIconFile=icono.ico
11
12[Languages]
13Name: "es"; MessagesFile: "compiler:Languages\Spanish.isl"
14
15[Files]
16Source: "dist\GestionClientes\*"; DestDir: "{app}"; Flags: recursesubdirs
17
18[Icons]
19Name: "{group}\Gestión de Clientes"; Filename: "{app}\GestionClientes.exe"
20Name: "{autodesktop}\Gestión de Clientes"; Filename: "{app}\GestionClientes.exe"; Tasks: escritorio
21
22[Tasks]
23Name: escritorio; Description: "Crear un acceso directo en el escritorio"- Instalación desatendida: parámetros para instalar sin preguntas en muchos equipos (
msiexec /i app.msi /qn,setup.exe /VERYSILENT), útil para desplegar por GPO o Intune. - Versión portable: un ZIP que se ejecuta sin instalar (no toca el registro).
- Guardar los datos del usuario fuera de la carpeta del programa (
%APPDATA%,~/.config) para que sobrevivan a una actualización.
4. Firma digital y licencias
Una aplicación sin firmar muestra avisos como «Windows protegió su PC» o «desarrollador no identificado». La firma de código garantiza quién la ha publicado y que nadie la ha modificado: se calcula un hash del fichero y se cifra con la clave privada de un certificado de firma de código emitido por una autoridad de certificación.
1# Windows: signtool (Windows SDK) con un certificado de firma de código y sello de tiempo
2signtool sign /f certificado.pfx /p **** /fd SHA256 /tr http://timestamp.digicert.com /td SHA256 GestionClientes-2.1.0.msi
3
4# JAR firmado
5jarsigner -keystore empresa.jks -tsa http://timestamp.digicert.com gestion-2.1.0.jar empresa
6jarsigner -verify gestion-2.1.0.jar
7
8# macOS: codesign + notarización obligatoria de Apple
9codesign --deep --sign "Developer ID Application: Empresa" GestionClientes.app
10
11# Publicar también el hash para que el usuario verifique la descarga
12sha256sum GestionClientes-2.1.0.msi > SHA256SUMS| Licencia | Tipo | Permite |
|---|---|---|
| Propietaria (EULA) | Privativa | Solo lo que diga el contrato; normalmente usar, no modificar ni redistribuir |
| MIT, BSD, Apache 2.0 | Libre permisiva | Usar, modificar y redistribuir, incluso en software cerrado, manteniendo el aviso |
| GPL | Libre copyleft | Usar y modificar; si se distribuye, el derivado debe ser GPL con su código |
| LGPL | Copyleft débil | Enlazarla desde software propietario (es la de OpenJFX con excepción de enlace) |
Revisa las licencias de tus dependencias
license-maven-plugin generan el listado automáticamente.5. Canales de distribución y actualizaciones
| Canal | Para | Notas |
|---|---|---|
| Web propia / GitHub Releases | Escritorio | Descarga directa; publicar el hash y las notas de la versión |
| Microsoft Store, Mac App Store | Escritorio | Revisión de la tienda, actualizaciones automáticas, formatos MSIX / .app |
| winget, Chocolatey, Homebrew | Escritorio (usuarios técnicos) | Instalación con un comando |
| Snap, Flatpak, AppImage | Linux | Paquetes universales con sus dependencias |
| Google Play, App Store | Móvil | AAB firmado (Android), revisión de Apple; fichas con capturas y política de privacidad |
| Web / PWA | Navegador | Sin instalación; la PWA se puede «instalar» desde el navegador |
| Contenedores (Docker) | Servidores | Imagen con la aplicación y su entorno |
| Despliegue corporativo | Empresa | Intune, SCCM, GPO, repositorios internos |
Actualizaciones
- La aplicación comprueba al arrancar si hay una versión nueva (un fichero JSON en el servidor con la última versión y la URL) y ofrece descargarla.
- Herramientas: update4j o JavaFX con un lanzador propio en Java; Squirrel/electron-updater en Electron; las tiendas lo hacen solas.
- Las actualizaciones deben estar firmadas, conservar los datos del usuario y migrar la base de datos si cambia el esquema (Flyway, Liquibase).
1// Comprueba si hay una versión nueva publicada en https://empresa.es/app/ultima.json
2// {"version": "2.2.0", "url": "https://empresa.es/app/GestionClientes-2.2.0.msi"}
3public static Optional<String> nuevaVersion(String actual) throws Exception {
4 HttpClient cliente = HttpClient.newHttpClient();
5 HttpResponse<String> r = cliente.send(
6 HttpRequest.newBuilder(URI.create("https://empresa.es/app/ultima.json")).build(),
7 HttpResponse.BodyHandlers.ofString());
8 JsonObject json = JsonParser.parseString(r.body()).getAsJsonObject(); // Gson
9 String ultima = json.get("version").getAsString();
10 return Version.compare(ultima, actual) > 0 ? Optional.of(json.get("url").getAsString()) : Optional.empty();
11}6. Ejercicios
Elige el formato de distribución
Indica cómo distribuirías cada aplicación y por qué: 1. Una herramienta interna de JavaFX para 40 ordenadores de una empresa con Windows gestionados por el departamento de informática. 2. Un editor de código abierto para Windows, macOS y Linux. 3. Una app Android para los clientes de un gimnasio. 4. Una utilidad de línea de comandos en Java para otros programadores. 5. Una aplicación de gestión para un único cliente que no tiene conocimientos técnicos.
Instalador de una aplicación JavaFX
Partiendo de un proyecto Maven JavaFX (por ejemplo, el de los temas anteriores): 1. Crea una clase Lanzador y genera un JAR ejecutable con todas las dependencias. 2. Con jdeps averigua los módulos necesarios y crea una imagen con jlink. 3. Genera un instalador con jpackage con nombre, versión, icono, acceso directo en el menú y en el escritorio y posibilidad de elegir la carpeta. 4. Comprueba la instalación y la desinstalación en una máquina virtual sin Java. 5. Documenta el proceso en el README (sección «Crear el instalador»).
Publicación en Google Play
Enumera los pasos para publicar una app Android en Google Play por primera vez, desde el código hasta que está disponible, incluyendo la firma, el formato del paquete, la ficha de la tienda, las pruebas y los requisitos de privacidad.