Enable headless AWT on Windows
~- Add SystemTray API classes to AwtProcess so that it works on native mode. Recently we added support to compile JavaFX apps to native using the extension quarkus-fx. However, the SystemTray API needs AWT to operate.~
~- Enable Windows AWT, which was previously disabled by UnsupportedOSBuildItem.~
~Tested this on Windows 11 64-bit and Ubuntu 25.10 64-bit using this demo app: https://github.com/Eng-Fouad/quarkus-awt-native-demo~
~The demo uses the following extensions which they also depend on quarkus-awt:~
~- quarkus-zxing: to generate a QR image.~
~- quarkus-pdfbox: to generate a PDF file.~
~- quarkus-poi: to generate an Excel file.~
- Resolves #32993
/cc @Karm (awt), @galderz (awt)
The only problem that I couldn't figure how to solve is that the popup menu from the systemstary icon appears to be smaller and does not fit the text (only on Windows OS):
JVM mode:
Native mode:
:waning_crescent_moon: This workflow status is outdated as a new workflow run has been triggered.
Status for workflow Quarkus CI
This is the status report for running Quarkus CI on commit 2518d59da09faf08ad1d5653bba0edf5a4cd3205.
Failing Jobs
| Status | Name | Step | Failures | Logs | Raw logs | Build scan |
|---|---|---|---|---|---|---|
| :x: | Native Tests - AWT, ImageIO and Java2D, Packaging .so files | Build |
Failures | Logs | Raw logs | :mag: |
You can consult the Develocity build scans.
Failures
:gear: Native Tests - AWT, ImageIO and Java2D, Packaging .so files #
- Failing: integration-tests/awt integration-tests/awt-packaging
:package: integration-tests/awt
:x: Failed to execute goal io.quarkus:quarkus-maven-plugin:999-SNAPSHOT:build (default) on project quarkus-integration-test-awt: Failed to build quarkus application
:package: integration-tests/awt-packaging
:x: Failed to execute goal io.quarkus:quarkus-maven-plugin:999-SNAPSHOT:build (default) on project quarkus-integration-test-awt-packaging: Failed to build quarkus application
:waning_crescent_moon: This workflow status is outdated as a new workflow run has been triggered.
Status for workflow Quarkus CI
This is the status report for running Quarkus CI on commit 18cb38446f59c937a4999a461527520a0b8b66e4.
Failing Jobs
| Status | Name | Step | Failures | Logs | Raw logs | Build scan |
|---|---|---|---|---|---|---|
| :x: | Initial JDK 17 Build | Build |
Failures | Logs | Raw logs | :mag: |
You can consult the Develocity build scans.
Failures
:gear: Initial JDK 17 Build #
- Failing: extensions/awt/deployment
! Skipped: docs integration-tests/awt integration-tests/awt-packaging
:package: extensions/awt/deployment
:x: Failed to execute goal net.revelc.code.formatter:formatter-maven-plugin:2.27.0:validate (default) on project quarkus-awt-deployment: File '/home/runner/_work/quarkus/quarkus/extensions/awt/deployment/src/main/java/io/quarkus/awt/deployment/AwtProcessor.java' has not been previously formatted. Please format file (for example by invoking `mvn -f extensions/awt/deployment net.revelc.code.formatter:formatter-maven-plugin:2.27.0:format`) and commit before running validation!
:waning_crescent_moon: This workflow status is outdated as a new workflow run has been triggered.
Status for workflow Quarkus CI
This is the status report for running Quarkus CI on commit a101b3188b43ab40fe4cfd1089aba278efe84825.
:white_check_mark: The latest workflow run for the pull request has completed successfully.
It should be safe to merge provided you have a look at the other checks in the summary.
You can consult the Develocity build scans.
Hello @Eng-Fouad , first and foremost, thanks so much for looking into this. There are some things to clear out, but given the fact that Windows, unlike MacOS, is supported in GraalVM's AWT Feature, it should be fairly smooth.
I will list the points here and I'll be glad if you could have a look:
1) Headless only, no Swing, Desktop integration, icons etc.
Quarkus in its core supports (on Linux) intentionally only headless graphics, i.e. images, compressions, color profiles, document processing, fonts etc. The reason is to limit the scope and keep the core rock solid. Anything related with desktop, icons, windows, frames or even whole functional Swing...printers :) ... belongs to separate extensions in the Quarkiverse.
I would like to kindly ask you to remove the superfluous packages, classes from the PR. There are also suspicious looking lines like java.util.Locale, java.lang.Thread or java.lang.Enum. The set of what is added to AwtProcessor must be minimal. It is very well possible that just a handful of lines are really needed to get the headless image processing running on Windows.
Regarding D3D/AWT unrelated classes, you might check these files Quarkus generates:
META-INF/native-image/jni-config.json
META-INF/native-image/resource-config.json
META-INF/native-image/proxy-config.json
META-INF/native-image/serialization-config.json
META-INF/native-image/reflect-config.json
inside e.g. (or any other Quarkus app's native-image source jar) C:\tmp\quarkus\integration-tests\awt\target\quarkus-integration-test-awt-999-SNAPSHOT-native-image-source-jar\quarkus-integration-test-awt-999-SNAPSHOT-runner.jar
2) Passing the testsuite
For any users depending on the core AWT extension to e.g. generate QR codes or more trickily, to process an arbitrary image users send the server's way, there is a rather exhaustive test suite that touches much of Java2D and I hope all the formats, image containers and their metadata, color profiles, fonts loading, compression...
That testsuite is currently not passing with this PR:
C:\tmp\quarkus(awt-native -> Eng-Fouad)
λ mvnw verify -f integration-tests/pom.xml -pl awt -Pnative -Dquarkus.native.container-build=false -Dquarkus.container-image.build=false -DskipITs=false
...SNIP...
========================================================================================================================
GraalVM Native Image: Generating 'quarkus-integration-test-awt-999-SNAPSHOT-runner.exe' (executable)...
========================================================================================================================
For detailed information and explanations on the build output, visit:
https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/BuildOutput.md
------------------------------------------------------------------------------------------------------------------------
[1/8] Initializing... (17.5s @ 0.19GB)
Java version: 25+36-LTS, vendor version: Mandrel-25.0.0.1-Final
Graal compiler: optimization level: 2, target machine: x86-64-v3
C compiler: cl.exe (microsoft, x64, 19.43.34808)
Garbage collector: Serial GC (max heap size: 80% of RAM)
8 user-specific feature(s):
- com.oracle.svm.thirdparty.gson.GsonFeature
- io.quarkus.awt.runtime.graal.AwtFeature
- io.quarkus.awt.runtime.graal.DarwinAwtFeature
- io.quarkus.runner.Feature: Auto-generated class by Quarkus from the existing extensions
- io.quarkus.runtime.graal.DisableLoggingFeature: Adapts logging during the analysis phase
- io.quarkus.runtime.graal.SkipConsoleServiceProvidersFeature: Skip unsupported console service providers when quarkus.native.auto-service-loader-registration is false
- org.eclipse.angus.activation.nativeimage.AngusActivationFeature
- org.eclipse.angus.mail.nativeimage.AngusMailFeature
------------------------------------------------------------------------------------------------------------------------
5 experimental option(s) unlocked:
- '-H:+AllowFoldMethods' (origin(s): command line)
- '-H:BuildOutputJSONFile' (origin(s): command line)
- '-H:-UseServiceLoaderFeature' (origin(s): command line)
- '-H:+GenerateBuildArtifactsFile' (origin(s): command line)
- '-H:AddOpens' (alternative API option(s): --add-opens java.base/java.lang=ALL-UNNAMED; origin(s): command line)
------------------------------------------------------------------------------------------------------------------------
Build resources:
- 7.74GB of memory (46.2% of system memory, using available memory)
- 4 thread(s) (100.0% of 4 available processor(s), determined at start)
WARNING: A terminally deprecated method in sun.misc.Unsafe has been called
WARNING: sun.misc.Unsafe::arrayBaseOffset has been called by io.netty.util.internal.shaded.org.jctools.util.UnsafeRefArrayAccess (file:/C:/tmp/quarkus/integration-tests/awt/target/quarkus-integration-test-awt-999-SNAPSHOT-native-image-source-jar/lib/io.netty.netty-common-4.1.127.Final.jar)
WARNING: Please consider reporting this to the maintainers of class io.netty.util.internal.shaded.org.jctools.util.UnsafeRefArrayAccess
WARNING: sun.misc.Unsafe::arrayBaseOffset will be removed in a future release
WARNING: A restricted method in java.lang.System has been called
WARNING: java.lang.System::loadLibrary has been called by com.aayushatharva.brotli4j.Brotli4jLoader in an unnamed module (file:/C:/tmp/quarkus/integration-tests/awt/target/quarkus-integration-test-awt-999-SNAPSHOT-native-image-source-jar/lib/com.aayushatharva.brotli4j.brotli4j-1.16.0.jar)
WARNING: Use --enable-native-access=ALL-UNNAMED to avoid a warning for callers in this module
WARNING: Restricted methods will be blocked in a future release unless native access is enabled
[2/8] Performing analysis... [*******] (75.9s @ 1.80GB)
14,651 types, 24,302 fields, and 77,010 methods found reachable
4,698 types, 106 fields, and 4,182 methods registered for reflection
254 types, 2,589 fields, and 4,410 methods registered for JNI access
0 downcalls and 0 upcalls registered for foreign access
5 native libraries: crypt32, ncrypt, psapi, version, winhttp
[3/8] Building universe... (8.9s @ 2.06GB)
[4/8] Parsing methods... [***] (6.2s @ 2.22GB)
[5/8] Inlining methods... [***] (5.2s @ 1.19GB)
[6/8] Compiling methods... [*********] (88.4s @ 1.52GB)
[7/8] Laying out methods... [****] (16.2s @ 2.11GB)
[8/8] Creating image... [****] (14.7s @ 2.60GB)
37.78MB (50.17%) for code area: 52,406 compilation units
35.85MB (47.60%) for image heap: 387,234 objects and 90 resources
1.68MB ( 2.23%) for other data
75.31MB in total image size, 75.31MB in total file size
------------------------------------------------------------------------------------------------------------------------
Top 10 origins of code area: Top 10 object types in image heap:
14.86MB java.base 10.02MB byte[] for code metadata
9.05MB java.desktop 4.93MB byte[] for java.lang.String
3.56MB svm.jar (Native Image) 3.54MB java.lang.String
883.17kB q.jar 2.93MB com.oracle.svm.core.hub.DynamicHubCompanion
847.11kB modified-io.vertx.vertx-core-4.5.21.jar 2.67MB byte[] for general heap data
664.31kB org.jboss.resteasy.resteasy-core-6.2.12.Final.jar 2.15MB java.lang.Class
626.36kB io.netty.netty-buffer-4.1.127.Final.jar 762.77kB java.lang.Object[]
478.72kB io.netty.netty-common-4.1.127.Final.jar 643.10kB java.util.HashMap$Node
398.43kB io.netty.netty-codec-http-4.1.127.Final.jar 594.41kB java.lang.String[]
390.15kB com.fasterxml.jackson.core.jackson-core-2.20.0.jar 535.40kB c.o.svm.core.hub.DynamicHub$ReflectionMetadata
5.22MB for 90 more packages 7.07MB for 3274 more object types
------------------------------------------------------------------------------------------------------------------------
Recommendations:
FUTR: Use '--future-defaults=all' to prepare for future releases.
HEAP: Set max heap for improved and more predictable memory usage.
CPU: Enable more CPU features with '-march=native' for improved performance.
------------------------------------------------------------------------------------------------------------------------
15.4s (6.4% of total time) in 1981 GCs | Peak RSS: 3.42GB | CPU load: 2.92
------------------------------------------------------------------------------------------------------------------------
Build artifacts:
C:\tmp\quarkus\integration-tests\awt\target\quarkus-integration-test-awt-999-SNAPSHOT-native-image-source-jar\awt.dll (jdk_library)
C:\tmp\quarkus\integration-tests\awt\target\quarkus-integration-test-awt-999-SNAPSHOT-native-image-source-jar\build-artifacts.json (build_info)
C:\tmp\quarkus\integration-tests\awt\target\quarkus-integration-test-awt-999-SNAPSHOT-native-image-source-jar\fontmanager.dll (jdk_library)
C:\tmp\quarkus\integration-tests\awt\target\quarkus-integration-test-awt-999-SNAPSHOT-native-image-source-jar\freetype.dll (jdk_library)
C:\tmp\quarkus\integration-tests\awt\target\quarkus-integration-test-awt-999-SNAPSHOT-native-image-source-jar\java.dll (jdk_library_shim)
C:\tmp\quarkus\integration-tests\awt\target\quarkus-integration-test-awt-999-SNAPSHOT-native-image-source-jar\javajpeg.dll (jdk_library)
C:\tmp\quarkus\integration-tests\awt\target\quarkus-integration-test-awt-999-SNAPSHOT-native-image-source-jar\jvm.dll (jdk_library_shim)
C:\tmp\quarkus\integration-tests\awt\target\quarkus-integration-test-awt-999-SNAPSHOT-native-image-source-jar\lcms.dll (jdk_library)
C:\tmp\quarkus\integration-tests\awt\target\quarkus-integration-test-awt-999-SNAPSHOT-native-image-source-jar\mlib_image.dll (jdk_library)
C:\tmp\quarkus\integration-tests\awt\target\quarkus-integration-test-awt-999-SNAPSHOT-native-image-source-jar\quarkus-integration-test-awt-999-SNAPSHOT-runner-build-output-stats.json (build_info)
C:\tmp\quarkus\integration-tests\awt\target\quarkus-integration-test-awt-999-SNAPSHOT-native-image-source-jar\quarkus-integration-test-awt-999-SNAPSHOT-runner.exe (executable)
========================================================================================================================
Finished generating 'quarkus-integration-test-awt-999-SNAPSHOT-runner' in 4m 0s.
[INFO] [io.quarkus.deployment.QuarkusAugmentor] Quarkus augmentation completed in 261768ms
[INFO]
[INFO] --- failsafe:3.5.4:integration-test (default) @ quarkus-integration-test-awt ---
[INFO] Using auto detected provider org.apache.maven.surefire.junitplatform.JUnitPlatformProvider
[INFO] Using auto detected provider org.apache.maven.surefire.junitplatform.JUnitPlatformProvider
[INFO] Using auto detected provider org.apache.maven.surefire.junitplatform.JUnitPlatformProvider
[INFO]
[INFO] -------------------------------------------------------
[INFO] T E S T S
[INFO] -------------------------------------------------------
[INFO] Running io.quarkus.awt.it.ImageDecodersIT
WARNING: A terminally deprecated method in sun.misc.Unsafe has been called
WARNING: sun.misc.Unsafe::allocateMemory has been called by io.netty.util.internal.PlatformDependent0$2 (file:/C:/Users/karm/.m2/repository/io/netty/netty-common/4.1.127.Final/netty-common-4.1.127.Final.jar)
WARNING: Please consider reporting this to the maintainers of class io.netty.util.internal.PlatformDependent0$2
WARNING: sun.misc.Unsafe::allocateMemory will be removed in a future release
Executing "C:\tmp\quarkus\integration-tests\awt\target/quarkus-integration-test-awt-999-SNAPSHOT-runner -Dquarkus.http.port=8081 -Dquarkus.http.ssl-port=8444 -Dtest.url=http://localhost:8081 -Dquarkus.log.file.path=C:\tmp\quarkus\integration-tests\awt\target\quarkus.log -Dquarkus.log.file.enabled=true -Dquarkus.log.c
ategory."io.quarkus".level=INFO -Dquarkus.profile=prod -Dquarkus.container-image.build=false -Dquarkus.native.container-build=false"
2025-10-17 14:45:14,133 WARN [io.qua.tes.com.DefaultNativeImageLauncher] (main) Log file target\quarkus.log deletion failed, could happen on Windows, we can carry on.
LogManager error of type GENERIC_FAILURE: Failed to move file C:\tmp\quarkus\integration-tests\awt\target\quarkus.log to C:\tmp\quarkus\integration-tests\awt\target\quarkus.log.1.
java.nio.file.FileSystemException: C:\tmp\quarkus\integration-tests\awt\target\quarkus.log -> C:\tmp\quarkus\integration-tests\awt\target\quarkus.log.1: The process cannot access the file because it is being used by another process
at java.base@25/sun.nio.fs.WindowsFileCopy.move(WindowsFileCopy.java:400)
at java.base@25/sun.nio.fs.WindowsFileSystemProvider.move(WindowsFileSystemProvider.java:287)
at java.base@25/java.nio.file.Files.move(Files.java:1319)
at org.jboss.logmanager.handlers.SuffixRotator.move(SuffixRotator.java:255)
at org.jboss.logmanager.handlers.SuffixRotator.rotate(SuffixRotator.java:182)
at org.jboss.logmanager.handlers.SuffixRotator.rotate(SuffixRotator.java:241)
at org.jboss.logmanager.handlers.SuffixRotator.rotate(SuffixRotator.java:201)
at org.jboss.logmanager.handlers.SizeRotatingFileHandler.setFile(SizeRotatingFileHandler.java:156)
at io.quarkus.runtime.logging.LoggingSetupRecorder.configureFileHandler(LoggingSetupRecorder.java:702)
at io.quarkus.runtime.logging.LoggingSetupRecorder.initializeLogging(LoggingSetupRecorder.java:215)
at io.quarkus.runner.recorded.LoggingResourceProcessor$setupLoggingRuntimeInit1072790680.deploy_0(Unknown Source)
at io.quarkus.runner.recorded.LoggingResourceProcessor$setupLoggingRuntimeInit1072790680.deploy(Unknown Source)
at io.quarkus.runner.ApplicationImpl.doStart(Unknown Source)
at io.quarkus.runtime.Application.start(Application.java:101)
at io.quarkus.runtime.ApplicationLifecycleManager.run(ApplicationLifecycleManager.java:119)
at io.quarkus.runtime.Quarkus.run(Quarkus.java:80)
at io.quarkus.runtime.Quarkus.run(Quarkus.java:51)
at io.quarkus.runtime.Quarkus.run(Quarkus.java:144)
at io.quarkus.runner.GeneratedMain.main(Unknown Source)
__ ____ __ _____ ___ __ ____ ______
--/ __ \/ / / / _ | / _ \/ //_/ / / / __/
-/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/
2025-10-17 14:45:14,397 WARN [io.qua.config] (main) Unrecognized configuration key "quarkus.log.category.io.quarkus.level" was provided; it will be ignored; verify that the dependency extension for this configuration is set or that you did not make a typo
2025-10-17 14:45:14,513 INFO [io.qua.awt.it.Application] (main) Available image reader: Standard WBMP Image Reader
2025-10-17 14:45:14,513 INFO [io.qua.awt.it.Application] (main) Available image reader: Standard BMP Image Reader
2025-10-17 14:45:14,513 INFO [io.qua.awt.it.Application] (main) Available image reader: Standard TIFF image reader
2025-10-17 14:45:14,513 INFO [io.qua.awt.it.Application] (main) Available image reader: Standard PNG image reader
2025-10-17 14:45:14,514 INFO [io.qua.awt.it.Application] (main) Available image reader: Standard JPEG Image Reader
2025-10-17 14:45:14,514 INFO [io.qua.awt.it.Application] (main) Available image reader: Standard GIF image reader
2025-10-17 14:45:14,629 ERROR [io.qua.run.Application] (main) Failed to start application: java.lang.RuntimeException: Failed to start quarkus
at io.quarkus.runner.ApplicationImpl.doStart(Unknown Source)
at io.quarkus.runtime.Application.start(Application.java:101)
at io.quarkus.runtime.ApplicationLifecycleManager.run(ApplicationLifecycleManager.java:119)
at io.quarkus.runtime.Quarkus.run(Quarkus.java:80)
at io.quarkus.runtime.Quarkus.run(Quarkus.java:51)
at io.quarkus.runtime.Quarkus.run(Quarkus.java:144)
at io.quarkus.runner.GeneratedMain.main(Unknown Source)
Caused by: jakarta.enterprise.inject.CreationException: java.io.IOException: Problem reading font data.
at io.quarkus.awt.it.Application_Bean.create(Unknown Source)
at io.quarkus.awt.it.Application_Bean.create(Unknown Source)
at io.quarkus.arc.impl.AbstractSharedContext.createInstanceHandle(AbstractSharedContext.java:119)
at io.quarkus.arc.impl.AbstractSharedContext$1.get(AbstractSharedContext.java:38)
at io.quarkus.arc.impl.AbstractSharedContext$1.get(AbstractSharedContext.java:35)
at io.quarkus.arc.generator.Default_jakarta_enterprise_context_ApplicationScoped_ContextInstances.c1(Unknown Source)
at io.quarkus.arc.generator.Default_jakarta_enterprise_context_ApplicationScoped_ContextInstances.computeIfAbsent(Unknown Source)
at io.quarkus.arc.impl.AbstractSharedContext.get(AbstractSharedContext.java:35)
at io.quarkus.arc.impl.ClientProxies.getApplicationScopedDelegate(ClientProxies.java:23)
at io.quarkus.awt.it.Application_ClientProxy.arc$delegate(Unknown Source)
at io.quarkus.awt.it.Application_ClientProxy.arc_contextualInstance(Unknown Source)
at io.quarkus.awt.it.Application_Observer_Synthetic_61j1e0rxH9Q8NRDv40h8C85TE8g.notify(Unknown Source)
at io.quarkus.arc.impl.EventImpl$Notifier.notifyObservers(EventImpl.java:365)
at io.quarkus.arc.impl.EventImpl$Notifier.notify(EventImpl.java:347)
at io.quarkus.arc.impl.EventImpl.fire(EventImpl.java:81)
at io.quarkus.arc.runtime.ArcRecorder.fireLifecycleEvent(ArcRecorder.java:164)
at io.quarkus.arc.runtime.ArcRecorder.handleLifecycleEvents(ArcRecorder.java:115)
at io.quarkus.runner.recorded.LifecycleEventsBuildStep$startupEvent1144526294.deploy_0(Unknown Source)
at io.quarkus.runner.recorded.LifecycleEventsBuildStep$startupEvent1144526294.deploy(Unknown Source)
... 7 more
Caused by: java.io.IOException: Problem reading font data.
at java.desktop@25/java.awt.Font.createFont0(Font.java:1077)
at java.desktop@25/java.awt.Font.createFont(Font.java:1024)
at io.quarkus.awt.it.Application.init(Application.java:63)
at io.quarkus.awt.it.Application_Bean.doCreate(Unknown Source)
... 26 more
3) Pointers about ominous "Problem reading font data"
Here are some differences between GraalVM, Mandrel one one side and BellSoft Liberica on the other side. The graphics context initialization in OpenJDK naturally assumes you have a JAVA_HOME and tries to look there for fonts. On Linux, there is also native library fontconfig and OS fonts integration, there is harfbuzz lib for fonts antialiasing etc. I don't know how it is on Windows, but I am sure at least the JAVA_HOME fonts lookup is happening.
Liberica hides this JAVA_HOME lookup by default with some substitutions. GraalVM and Mandrel don't hide it, but we hide it here, on the Quarkus side: JDKSubstitutions.java i.e. we basically create a fake JAVA_HOME directory structure.
Here be dragons: Yes. Liberica and Quarkus are touching a similar place in the JDK runtime with substitutions.
3.1) Why doesn't it work on Windows?
I am not sure at the moment, but I know how to find out. I suspect that the JNI initialization is not complete and that there is something up with that. This error is meaningless:
Caused by: java.io.IOException: Problem reading font data.
at java.desktop@25/java.awt.Font.createFont0(Font.java:1077)
at java.desktop@25/java.awt.Font.createFont(Font.java:1024)
You can see here that the true cause is hidden: https://github.com/openjdk/jdk25u/blob/6c48f4ed707bf0b15f9b6098de30db8aae6fa40f/src/java.desktop/share/classes/java/awt/Font.java#L1077
I wanted to change that in Font.java, but the explanation at the time was that the true cause is hidden for information leakage reasons. So what I can do is to build a windows dev JDK 25, build Mandrel/GraalVM with it and have some more logging at that place. That's how I did it on Linux to find out errors. If you find it helpful I just attach such build here.
It might be something really simple like a different path expected on WIndows for the initial fonts lookup.
4) GraalVM/Mandrel, Liberica
It's awesome that Liberica went an extra mile to make Swing et al. more friendly, but we must not count on that in Quarkus core. Anything we do here must work with GraalVM CE and Mandrel. We can have a conditional substitution or registration not/applied if the detected SDK used is Liberica.
This text was handmade for you by a human :-)
Cheers K.
@Karm Thanks for you detailed response.
Quarkus in its core supports (on Linux) intentionally only headless graphics, i.e. images, compressions, color profiles, document processing, fonts etc. The reason is to limit the scope and keep the core rock solid. Anything related with desktop, icons, windows, frames or even whole functional Swing...printers :) ... belongs to separate extensions in the Quarkiverse.
That makes sense and I agree with you on that. I will probably move the SystemTray API classes registration to quarkus-fx project. I will close this PR for now, and I could open a new one later if I manage to make AWT works on Windows and pass all test suites.
3.1) Why doesn't it work on Windows? I am not sure at the moment, but I know how to find out. I suspect that the JNI initialization is not complete and that there is something up with that. This error is meaningless:
Caused by: java.io.IOException: Problem reading font data. at java.desktop@25/java.awt.Font.createFont0(Font.java:1077) at java.desktop@25/java.awt.Font.createFont(Font.java:1024)You can see here that the true cause is hidden: https://github.com/openjdk/jdk25u/blob/6c48f4ed707bf0b15f9b6098de30db8aae6fa40f/src/java.desktop/share/classes/java/awt/Font.java#L1077
I wanted to change that in Font.java, but the explanation at the time was that the true cause is hidden for information leakage reasons. So what I can do is to build a windows dev JDK 25, build Mandrel/GraalVM with it and have some more logging at that place. That's how I did it on Linux to find out errors. If you find it helpful I just attach such build here.
It might be something really simple like a different path expected on WIndows for the initial fonts lookup.
So I substituted the method java.awt.Font#createFont0:
@TargetClass(className = "java.awt.Font")
final class Target_java_awt_Font {
@Substitute
private static Font[] createFont0(int fontFormat, InputStream fontStream,
boolean allFonts)
throws java.awt.FontFormatException, java.io.IOException {
if (fontFormat != Font.TRUETYPE_FONT &&
fontFormat != Font.TYPE1_FONT) {
throw new IllegalArgumentException("font format not recognized");
}
boolean copiedFontData = false;
try {
final File tFile = Files.createTempFile("+~JF", ".tmp").toFile();
int totalSize = 0;
try {
final OutputStream outStream = new FileOutputStream(tFile);
try (outStream) { /* don't close the input stream */
byte[] buf = new byte[8192];
for (;;) {
int bytesRead = fontStream.read(buf);
if (bytesRead < 0) {
break;
}
outStream.write(buf, 0, bytesRead);
}
}
copiedFontData = true;
// replaced direct access to sun classes with reflection access
Class<?> fontManagerFactoryClass = Class.forName("sun.font.FontManagerFactory");
Class<?> fontManagerClass = Class.forName("sun.font.FontManager");
Object fontManager = fontManagerFactoryClass.getMethod("getInstance").invoke(null);
Object[] font2DArr = (Object[]) fontManagerClass
.getMethod("createFont2D", File.class, int.class, boolean.class, boolean.class)
.invoke(fontManager, tFile, fontFormat, allFonts, true);
int num = font2DArr.length;
Font[] fonts = new Font[num];
Constructor<Font> fontConstructor = Font.class.getDeclaredConstructor(Class.forName("sun.font.Font2D"));
fontConstructor.setAccessible(true);
for (int i = 0; i < num; i++) {
fonts[i] = fontConstructor.newInstance(font2DArr[i]);
}
return fonts;
} finally {
if (!copiedFontData) {
tFile.delete();
}
}
} catch (Throwable t) {
if (t instanceof FontFormatException) {
throw (FontFormatException) t;
}
if (t instanceof IOException) {
throw (IOException) t;
}
Throwable cause = t.getCause();
if (cause instanceof FontFormatException) {
throw (FontFormatException) cause;
}
t.printStackTrace(); // <-- added this line
throw new IOException("Problem reading font data.");
}
}
}
and I got the true cause:
java.lang.reflect.InvocationTargetException
at java.base@25/java.lang.reflect.Method.invoke(Method.java:565)
at java.desktop@25/java.awt.Font.createFont0(Font.java:68)
at java.desktop@25/java.awt.Font.createFont(Font.java:1024)
at io.fouad.quarkus.awt.demo.AwtDemoCli.createPdfFile(AwtDemoCli.java:138)
at io.fouad.quarkus.awt.demo.AwtDemoCli.run(AwtDemoCli.java:53)
at picocli.CommandLine.executeUserObject(CommandLine.java:2045)
at picocli.CommandLine.access$1500(CommandLine.java:148)
at picocli.CommandLine$RunLast.executeUserObjectOfLastSubcommandWithSameParent(CommandLine.java:2469)
at picocli.CommandLine$RunLast.handle(CommandLine.java:2461)
at picocli.CommandLine$RunLast.handle(CommandLine.java:2423)
at picocli.CommandLine$AbstractParseResultHandler.execute(CommandLine.java:2277)
at picocli.CommandLine$RunLast.execute(CommandLine.java:2425)
at io.quarkus.picocli.runtime.PicocliRunner$EventExecutionStrategy.execute(PicocliRunner.java:26)
at picocli.CommandLine.execute(CommandLine.java:2174)
at io.quarkus.picocli.runtime.PicocliRunner.run(PicocliRunner.java:40)
at io.quarkus.runtime.ApplicationLifecycleManager.run(ApplicationLifecycleManager.java:141)
at io.quarkus.runtime.Quarkus.run(Quarkus.java:80)
at io.quarkus.runtime.Quarkus.run(Quarkus.java:51)
at io.quarkus.runner.GeneratedMain.main(Unknown Source)
at java.base@25/java.lang.invoke.LambdaForm$DMH/sa346b79c.invokeStaticInit(LambdaForm$DMH)
Caused by: java.lang.RuntimeException: Fontconfig head is null, check your fonts or fonts configuration
at java.desktop@25/sun.awt.FontConfiguration.getVersion(FontConfiguration.java:1242)
at java.desktop@25/sun.awt.FontConfiguration.readFontConfigFile(FontConfiguration.java:207)
at java.desktop@25/sun.awt.FontConfiguration.init(FontConfiguration.java:103)
at java.desktop@25/sun.awt.Win32FontManager.createFontConfiguration(Win32FontManager.java:169)
at java.desktop@25/sun.font.SunFontManager.<init>(SunFontManager.java:341)
at java.desktop@25/sun.awt.Win32FontManager.<init>(Win32FontManager.java:79)
at java.desktop@25/sun.font.PlatformFontInfo.createFontManager(PlatformFontInfo.java:37)
at java.desktop@25/sun.font.FontManagerFactory.getInstance(FontManagerFactory.java:51)
... 20 more
@Eng-Fouad Not sure if closing this PR and kind of losing the track is a good idea. I would prefer we iterate here, keep the context in a one thread, one PR. You can force push as much as you like, edit the title etc. Let's have a history here.
@Eng-Fouad, so looking at it briefly,
Caused by: java.lang.RuntimeException: Fontconfig head is null, check your fonts or fonts configuration
at java.desktop@25/sun.awt.FontConfiguration.getVersion(FontConfiguration.java:1242)
at java.desktop@25/sun.awt.FontConfiguration.readFontConfigFile(FontConfiguration.java:207)
at java.desktop@25/sun.awt.FontConfiguration.init(FontConfiguration.java:103)
at java.desktop@25/sun.awt.Win32FontManager.createFontConfiguration(Win32FontManager.java:169)
at java.desktop@25/sun.font.SunFontManager.<init>(SunFontManager.java:341)
at java.desktop@25/sun.awt.Win32FontManager.<init>(Win32FontManager.java:79)
at java.desktop@25/sun.font.PlatformFontInfo.createFontManager(PlatformFontInfo.java:37)
at java.desktop@25/sun.font.FontManagerFactory.getInstance(FontManagerFactory.java:51)
... 20 more
It looks like an equivalent of what Linux has for fontconfig installed in a package. So it's fine there is no font configuration in our fake JAVA_HOME directory structure, because there is a font configuration found later in /usr/share/fontconfig and copied over to an .java dir that's created on the fly, e.g. /home/karm/.java/fonts/25/fcinfo-1-laptop-RedHat-9-en-US.properties
You can see we are installing the package explicitly on Linux: fontconfig, freetype
If this fails the "head is null" error pops up: https://github.com/quarkusio/quarkus/issues/49226 i.e. in the aforementioned case, fonconfig dependencies were not installed correctly, co the native library could not be loaded. That might be the case here too, but it also could mean that there are simply no fontconfig files to be found.
We are not installing any fontconfig or freetype packages on Windows after all.
So I suggest:
1) Check Runtime init, JNI access
e.g. Win32FontManager appears it needs runtime init, because its values are populated from FontManager here at runtime: fontpath.c
This might be it.
2) Fontconfig files
Another idea is that really the absence of any? fontconfig properties might be an issue and you might need some in the fake JAVA_HOME, since there is no /usr/share/fontconfig. I am more inclined to the Win32FontManager runtime init though.
FAKE_TMP_JAVA_HOME\lib\fontconfig.bfc
FAKE_TMP_JAVA_HOME\lib\fontconfig.properties
Hope it helps.
to add to what @Karm said this is what we do in the JasperReports extenions when its needs "non-headless" AWT stuff
https://github.com/quarkiverse/quarkus-jasperreports/blob/0c3a0f77d0ff732b12af68661e0d24818bbeb424/deployment/src/main/java/io/quarkiverse/jasperreports/deployment/JasperReportsProcessor.java#L277-L284
so I agree with @Karm this should be done in the extension or we need a Quarkiverse extension called "AWT Desktop"
i also had to add these
classNames.add(java.awt.Color.class.getName());
classNames.add(java.awt.color.ColorSpace.class.getName());
- Check Runtime init, JNI access e.g. Win32FontManager appears it needs runtime init, because its values are populated from FontManager here at runtime: fontpath.c
This might be it.
AwtProcessor already has that in runtimeInitializedClasses():
Stream.of(
"com.sun.imageio",
"java.awt",
"javax.imageio",
"sun.awt", // <----
"sun.font",
"sun.java2d")
.map(RuntimeInitializedPackageBuildItem::new)
.forEach(runtimeInitializedPackages::produce);
Another idea is that really the absence of any? fontconfig properties might be an issue and you might need some in the fake JAVA_HOME, since there is no /usr/share/fontconfig. I am more inclined to the Win32FontManager runtime init though.
FAKE_TMP_JAVA_HOME\lib\fontconfig.bfc FAKE_TMP_JAVA_HOME\lib\fontconfig.properties
So after debugging how Java loads fonts on Windows on JVM mode, it turns out that it loads the file JAVA_HOME\lib\fontconfig.bfc. How can we go around this? Bundle fontconfig.bfc inside quarkus-awt?
Even if we workaround this with -D"sun.awt.fontconfig=C:/Program Files/Java/jdk-25/lib/fontconfig.bfc", another error appears:
java.lang.reflect.InvocationTargetException
at java.base@25/java.lang.reflect.Method.invoke(Method.java:565)
at java.desktop@25/java.awt.Font.createFont0(Font.java:68)
at java.desktop@25/java.awt.Font.createFont(Font.java:1024)
at io.fouad.quarkus.awt.demo.AwtDemoCli.createPdfFile(AwtDemoCli.java:138)
at io.fouad.quarkus.awt.demo.AwtDemoCli.run(AwtDemoCli.java:53)
at picocli.CommandLine.executeUserObject(CommandLine.java:2045)
at picocli.CommandLine.access$1500(CommandLine.java:148)
at picocli.CommandLine$RunLast.executeUserObjectOfLastSubcommandWithSameParent(CommandLine.java:2469)
at picocli.CommandLine$RunLast.handle(CommandLine.java:2461)
at picocli.CommandLine$RunLast.handle(CommandLine.java:2423)
at picocli.CommandLine$AbstractParseResultHandler.execute(CommandLine.java:2277)
at picocli.CommandLine$RunLast.execute(CommandLine.java:2425)
at io.quarkus.picocli.runtime.PicocliRunner$EventExecutionStrategy.execute(PicocliRunner.java:26)
at picocli.CommandLine.execute(CommandLine.java:2174)
at io.quarkus.picocli.runtime.PicocliRunner.run(PicocliRunner.java:40)
at io.quarkus.runtime.ApplicationLifecycleManager.run(ApplicationLifecycleManager.java:141)
at io.quarkus.runtime.Quarkus.run(Quarkus.java:80)
at io.quarkus.runtime.Quarkus.run(Quarkus.java:51)
at io.quarkus.runner.GeneratedMain.main(Unknown Source)
at java.base@25/java.lang.invoke.LambdaForm$DMH/sa346b79c.invokeStaticInit(LambdaForm$DMH)
Caused by: java.lang.NullPointerException
at java.desktop@25/sun.awt.FontConfiguration.getInitELC(FontConfiguration.java:447)
at java.desktop@25/sun.awt.FontConfiguration.initFontConfig(FontConfiguration.java:423)
at java.desktop@25/sun.awt.FontConfiguration.init(FontConfiguration.java:104)
at java.desktop@25/sun.awt.Win32FontManager.createFontConfiguration(Win32FontManager.java:169)
at java.desktop@25/sun.font.SunFontManager.<init>(SunFontManager.java:341)
at java.desktop@25/sun.awt.Win32FontManager.<init>(Win32FontManager.java:79)
at java.desktop@25/sun.font.PlatformFontInfo.createFontManager(PlatformFontInfo.java:37)
at java.desktop@25/sun.font.FontManagerFactory.getInstance(FontManagerFactory.java:51)
... 20 more
so I agree with @Karm this should be done in the extension or we need a Quarkiverse extension called "AWT Desktop"
That sounds a nice idea cc @gastaldi
If we create and AWT Desktop extension i know at least one or two of my extensions like JasperReports would then switch to it!
I copied the file JAVA_HOME\lib\fontconfig.properties.src to ./extensions/awt/runtime/src/main/resources/windows-fontconfig.properties, and added the following to JDKSubstitutions$Target_sun_awt_FontConfiguration#setOsNameAndVersion:
if (OS.WINDOWS.isCurrent()) {
Files.deleteIfExists(Path.of(javaHome.toString(), "lib", "fontconfig.properties"));
Files.copy(JDKSubstitutions.class.getResourceAsStream("/windows-fontconfig.properties"),
Path.of(javaHome.toString(), "lib", "fontconfig.properties"));
}
Also added the following to AwtProcessor:
@BuildStep(onlyIf = NativeOrNativeSourcesBuild.class)
public void registerNativeImageResources(BuildProducer<NativeImageResourcePatternsBuildItem> resource) {
resource.produce(
NativeImageResourcePatternsBuildItem.builder().includeGlobs("/windows-fontconfig.properties").build());
}
Now, Font.createFont() works fine on native mode on Windows.
Is this implementation acceptable? Bundling fontconfig.properties into quarkus-awt.
I am not sure about this as it looks kinda invasive:
Files.deleteIfExists(Path.of(javaHome.toString(), "lib", "fontconfig.properties"));
I am not sure about this as it looks kinda invasive:
Files.deleteIfExists(Path.of(javaHome.toString(), "lib", "fontconfig.properties"));
Just to make it possible if we need to update the file fontconfig.properties in the future.
@Eng-Fouad Any more thoughts to AWT Dekstop or AWT extension Quarkiverse Extension?
I think it would be pretty useful!
I would be able to reuse it in:
Quarkus Batik Quarkus JasperReports ...and probably a few others.
Shouldn't these changes go in https://github.com/quarkusio/quarkus/tree/main/extensions/awt?
@Eng-Fouad Any more thoughts to AWT Dekstop or AWT extension Quarkiverse Extension?
I think it would be pretty useful!
I agree with you, it would be pretty useful. I can participate on such extension but I don't have the time to lead the development.
Shouldn't these changes go in https://github.com/quarkusio/quarkus/tree/main/extensions/awt?
quarkus-awt is headless only. The proposed extension quarkus-awt-desktop would support GUI apps by registering required classes and resources for native image.
How about introducing a build item in the AWT extension and perform the registration of these classes if some other extension peoduces this item?
How about introducing a build item in the AWT extension and perform the registration of these classes if some other extension peoduces this item?
cc @Karm
It could be named something like HeadfulEnvironmentBuildItem
@Eng-Fouad On my list to circle back to this. Needs to wait a bit.
:waning_crescent_moon: This workflow status is outdated as a new workflow run has been triggered.
Status for workflow Quarkus CI
This is the status report for running Quarkus CI on commit b2fc490a643c17a31cb3d426011ca3ed0acae56a.
Failing Jobs
| Status | Name | Step | Failures | Logs | Raw logs | Build scan |
|---|---|---|---|---|---|---|
| :x: | Initial JDK 17 Build | Build |
Failures | Logs | Raw logs | :mag: |
You can consult the Develocity build scans.
Failures
:gear: Initial JDK 17 Build #
- Failing: extensions/awt/runtime
! Skipped: devtools/bom-descriptor-json docs extensions/awt/deployment and 3 more
:package: extensions/awt/runtime
:x: Failed to execute goal net.revelc.code:impsort-maven-plugin:1.12.0:check (check-imports) on project quarkus-awt: Imports are not sorted in /home/runner/_work/quarkus/quarkus/extensions/awt/runtime/src/main/java/io/quarkus/awt/runtime/JDKSubstitutions.java
:waning_crescent_moon: This workflow status is outdated as a new workflow run has been triggered.
Status for workflow Quarkus CI
This is the status report for running Quarkus CI on commit 1b8b5ed9cc4bf9574343e370dca80bec1ee6f125.
:white_check_mark: The latest workflow run for the pull request has completed successfully.
It should be safe to merge provided you have a look at the other checks in the summary.
You can consult the Develocity build scans.
:waning_crescent_moon: This workflow status is outdated as a new workflow run has been triggered.
Status for workflow Quarkus CI
This is the status report for running Quarkus CI on commit 56ae8d757937ff98632170fb68400498d4453a85.
:white_check_mark: The latest workflow run for the pull request has completed successfully.
It should be safe to merge provided you have a look at the other checks in the summary.
You can consult the Develocity build scans.
Added all required configurations to run headless AWT on Windows. Now all native tests in quarkus-integration-test-awt are passing on Windows.
:waning_crescent_moon: This workflow status is outdated as a new workflow run has been triggered.
Status for workflow Quarkus CI
This is the status report for running Quarkus CI on commit e2dbafb686938f7bb1e1aaef9eb6d1b2c92d6232.
:white_check_mark: The latest workflow run for the pull request has completed successfully.
It should be safe to merge provided you have a look at the other checks in the summary.
You can consult the Develocity build scans.
@Karm could you have another look at this one?
Pushed a new commit that:
- Resolves a conflict with recent changes to
AwtProcessor(#50846). - Adds new CI test
AWT, ImageIO and Java2D, Packaging .dll files (Windows).