Can't show splash screen on java app in production environment

Gradle 7.3
Java 1.8

Here my build.gradle:

plugins {
    // Apply the application plugin to add support for building a CLI application in Java.
    id 'application'
    // Build uber-jar
    id 'com.github.johnrengelman.shadow' version '7.1.2' apply false
    // https://github.com/NocWriter/runsql-gradle-plugin
    id "com.nocwriter.runsql" version "1.0.3"
}

apply plugin: "com.github.johnrengelman.shadow"

repositories {
    mavenCentral()
    flatDir {
        dirs 'lib'
    }
}


application {
    applicationName = "${projectShortName}" // /bin/m2cm.bat
    mainClass = javaMainClass
    applicationDefaultJvmArgs = ["-splash:${buildDir}/resources/main/images/splash.png"]
}


dependencies {
 
    implementation 'log4j:log4j:1.2.17'
    implementation 'org.xerial:sqlite-jdbc:3.36.0.3'
    implementation 'com.toedter:jcalendar:1.4'

    dependencies { implementation name: 'ssp-0.5.6' }

    // Use JUnit Jupiter for testing.
    testImplementation 'org.junit.jupiter:junit-jupiter:5.8.2'
}

jar {
    exclude("db")
    archiveFileName = "${projectShortName}.jar"
    manifest {
        attributes(
                "Main-Class": "$javaMainClass",
                "SplashScreen-Image": "images/splash.png")
    }
}

I run myProject like this:

./gradle run

As result success show splash screen. Because has this settings:

applicationDefaultJvmArgs = ["-splash:${buildDir}/resources/main/images/splash.png"]

Nice.

Now I create distributive like this:

gradle distZip

As result create zip with myProject.jar in lib folder.

OK. The splash screen (splash.png) is in jar file: /lib/myProject.jar

The distZip also generate bin/myProj.bat with the next content:

@if "%DEBUG%" == "" @echo off

@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal

set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%..

@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi

@rem Add default JVM options here. You can also use JAVA_OPTS and M2CM_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-splash:C:\dev\temp\m2cm\app\build/resources/main/images/splash.png"

@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome

set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute

goto fail

:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe

if exist "%JAVA_EXE%" goto execute

goto fail

:execute
@rem Setup the command line

set CLASSPATH=%APP_HOME%\lib\m2cm.jar;%APP_HOME%\lib\log4j-1.2.17.jar;%APP_HOME%\lib\sqlite-jdbc-3.36.0.3.jar;%APP_HOME%\lib\jcalendar-1.4.jar;%APP_HOME%\lib\ssp-0.5.6.jar


@rem Execute m2cm
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %M2CM_OPTS%  -classpath "%CLASSPATH%" com.mycompany.myproject.Main %*

:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd

:fail
rem Set variable M2CM_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if  not "" == "%M2CM_EXIT_CONSOLE%" exit 1
exit /b 1

:mainEnd
if "%OS%"=="Windows_NT" endlocal

:omega

And when client run my app by myProj.bat the splash screen is NOT show. This is because in client no this path:

C:\dev\temp\m2cm\app\build/resources/main/images/splash.png"

Is it possible to create distributive by Gradle to show splash screen on client (production)?

This also NOT help

manifest {
        attributes(
                "Main-Class": "$javaMainClass",
                "SplashScreen-Image": "images/splash.png")
    }

Maybe either SplashScreen-Image manifest entry only works if you run it using -jar option, or it is disturbed by also supplying -splash: which overrides it.
But anyway, I thinks what should work is if you use -splash: with a location in the class path.
So change

applicationDefaultJvmArgs = ["-splash:${buildDir}/resources/main/images/splash.png"]

to

applicationDefaultJvmArgs = ["-splash:images/splash.png"]

and it whould probably work in both cases.

Additionally as you build a proper distribution archive using the application plugin, I’d suggest you remove the shadow plugin, as it imho is very bad practice anyway, see https://fatjar.net/ if you want more comments on why. Besides that you should not apply plugins using the legacy apply method but always in the plugins { ... } block.
Along with that, you should then also remove the Main-Class and SplashScreen-Image manifest entries, as they have no effect anyway when you have a proper distribution with start scripts and the nested dependencies within dependencies is just unnecessary visual clutter. :slight_smile:

Not help. Now splash screen not show in BOTH cases (in development and production environment)

build.gradle


plugins {
    id 'application'
    id "com.nocwriter.runsql" version "1.0.3"
}

repositories {
    mavenCentral()
    flatDir {
        dirs 'lib'
    }
}
application {
    applicationName = "${projectShortName}" 
    mainClass = javaMainClass
    applicationDefaultJvmArgs = ["-splash:images/splash.png"]
}



dependencies {
 
    implementation 'log4j:log4j:1.2.17'
    implementation 'org.xerial:sqlite-jdbc:3.36.0.3'
    implementation 'com.toedter:jcalendar:1.4'

    implementation name: 'ssp-0.5.6'

    // Use JUnit Jupiter for testing.
    testImplementation 'org.junit.jupiter:junit-jupiter:5.8.2'
}


jar {
    exclude("db")
    archiveFileName = "${projectShortName}.jar"
}

// Copy file from "build/resources/main/db/sqlite/m2cm.db" to folder "data" in distributive archive (zip, tar)
distributions {
    main {
        contents {
            from("$buildResourcesDbSqlitePath/${projectShortName}.db") {
                into "data"
            }
        }
    }
}

distZip {
    archiveFileName = "${distArchiveFileName}.zip"
}

distTar {
    archiveFileName = "${distArchiveFileName}.tar"
}

tasks.named('test') {
    // Use JUnit Platform for unit tests.
    useJUnitPlatform()
}

And here myproj.bat

if "%OS%"=="Windows_NT" setlocal

set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%..

@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi

@rem Add default JVM options here. You can also use JAVA_OPTS and M2CM_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-splash:images/splash.png"

@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome

set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute

goto fail

:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe

if exist "%JAVA_EXE%" goto execute


goto fail

:execute
@rem Setup the command line

set CLASSPATH=%APP_HOME%\lib\m2cm.jar;%APP_HOME%\lib\log4j-1.2.17.jar;%APP_HOME%\lib\sqlite-jdbc-3.36.0.3.jar;%APP_HOME%\lib\jcalendar-1.4.jar;%APP_HOME%\lib\ssp-0.5.6.jar


@rem Execute m2cm
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %M2CM_OPTS%  -classpath "%CLASSPATH%" md.deeplace.m2cm.Main %*

:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd

:fail
rem Set variable M2CM_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if  not "" == "%M2CM_EXIT_CONSOLE%" exit 1
exit /b 1

:mainEnd
if "%OS%"=="Windows_NT" endlocal

:omega

Ok, then let’s try it the other way around.
Add back the SplashScreen-Image manifest entry (but not the Main-Class.
Then move the -splash:... with the old value out of the applicationDefaultJvmArgs and instead set it as jvmArg on the run task.
Then when you use run, -splash: is used, but it does not land in the generated start script.
We’ll see whether SplashScreen-Image works in that case or not.

Another alternative if it does not work is, that you do not package the splash.png in the jar but instead (or additionally if you also need it for something else at runtime) in the distribution archive and then use the applicationDefaultJvmArgs again to use it from the distribution, and on the run task override it with jvmArg and the path to the versioned file.

Your approaches not work.

But I was found 2 solutions and it’s work.

1. Solution - change “startScripts”:

In build.gradle:


application {
    applicationName = "${projectShortName}"
    mainClass = javaMainClass
    applicationDefaultJvmArgs = ["-splash:${buildDir}/resources/main/images/splash.png"]
}

jar {
    exclude("db")
    exclude("scripts")
    archiveFileName = "${projectShortName}.jar"
    manifest {
        attributes(
                "Main-Class": "$javaMainClass",
                "SplashScreen-Image": "images/splash.png",
                "Class-Path": configurations.runtimeClasspath.collect { it.getName() }.join(' '))
    }
}

startScripts {
    applicationName = "${projectShortName}"
    outputDir = file('build/scripts')

    def tplUnixName = "${buildResourcesMain}/scripts/customUnixStartScript.txt"
    unixStartScriptGenerator.template = resources.text.fromFile(tplUnixName)
    def tplWindowsName = "${buildResourcesMain}/scripts/customWindowsStartScript.txt"
    windowsStartScriptGenerator.template = resources.text.fromFile(tplWindowsName)
}

Create 2 files:

~/dev/projects/myProj/app/src/main/resources/scripts/customWindowsStartScript.txt
~/dev/projects/myProj/app/src/main/resources/scripts/customUnixStartScript.txt.txt

with the next content:

java -jar ../lib/myApp.jar

Create distribute

gradle clean distZip

And now splash screen show in production environment because in MANIFEST.MF in jar has the next content:

Manifest-Version: 1.0
Class-Path: log4j-1.2.17.jar sqlite-jdbc-3.36.0.3.jar jcalendar-1.4.ja
 r ssp-0.5.6.jar
Main-Class: com.mycompany.myApp.Main
SplashScreen-Image: images/splash.png

And splash show in development environment because in build.gradle ahst
applicationDefaultJvmArgs = ["-splash:${buildDir}/resources/main/images/splash.png"]

OK.

2. Solution by custom folder in dist

In project folder create two files: myApp.bat and myApp.sh

c:\dev\temp\myApp\app\src\dist\bin\

with the next content:

java -jar ../lib/myApp.jar

In build.gradle:

application {
    applicationName = "${projectShortName}"
    mainClass = javaMainClass
    applicationDefaultJvmArgs = ["-splash:${buildDir}/resources/main/images/splash.png"]
}
startScripts.enabled = false

As you can see I disable task startScripts.

Then same steps as in solution #1 -

gradle clean distZip

and so on.

And this solution is also work. Splash screen show in development and production environment.

I’ve read up on it as I was curious myself.
Indeed the SplashScreen-Image just like Main-Class and Class-Path are only considered if you use -jar to start the application
and -splash:... only accepts local file paths, nothing from the class path.

But my last suggestion should still work just fine and be a bit cleaner.

Yes, it does.

I copied a splash to src/main/dist/images/splash.jpg and then just have

application {
    mainClass.set("foo.Foo")
    applicationDefaultJvmArgs = listOf("-splash:images/splash.jpg")
}

tasks.named<JavaExec>("run") {
    jvmArgs("-splash:${file("src/main/dist/images/splash.jpg").absolutePath}")
}

and it works fine with run task and from final distribution.
No other custom scripts, or manifest entries, or anything else needed.

Of course if I put splash.jpg to external folder it’s work. But … the idea is to not create new folder (dist/images). The splash.jpg MUST be inside jar. The client not need to know about splash image. It’s like put all class file to external folder. Smt like dist/classes. But all my classes are INSIDE jar file.