Il problema degli oggetti "fantasma" nelle compilazioni native (Java/Kotlin)
-
Mohamed Msaad
- 26 May, 2025
- 03 Mins read

English version below
Il problema degli oggetti "fantasma" nelle compilazioni native (Java/Kotlin)
Scenario del problema
Vi siete mai chiesti perché alcune risorse/oggetti/funzionalità non risultino disponibili quando eseguite un rilascio su una piattaforma per esempio docker.
Ebbene si, durante lo sviluppo di un'applicazione Quarkus con linguaggio java/kotlin (valido anche per Spring Boot con GraalVM) ci siamo imbattuti negli oggetti fantasma delle compilazioni native.
Il comportamento era particolarmente insidioso perché:
- La compilazione JVM tradizionale funzionava correttamente
- L'errore si manifestava solo dopo la compilazione nativa
- La classe mancante apparteneva a una dipendenza standard (jakarta.validation)
Ed è proprio qui che abbiamo affrontato l'errore:
Type not found: class jakarta.validation.constraints.Pattern$Flag
when calling ...FieldValidator
Tutto funzionava senza problemi fino a quando l'applicazione era fine a se stessa; con l'obiettivo di generalizzare alcune funzionalità da condividere con più applicazioni abbiamo separato le logiche creando un sdk common, che scatenato il problema sopra descritto.
La compilazione nativa grazie all'approccio conservativo, opera come un barboncino troppo zelante, GraalVM taglia tutto ciò che non è esplicitamente referenziato nel codice durante l'analisi statica.
Diagnosi del problema
Il problema è radicato nel modo in cui la compilazione nativa (via GraalVM) gestisce la reflection:
- Ottimizzazione aggressiva: Il compilatore nativo esclude per default classi non referenziate esplicitamente
- Dynamic Loading: Alcune librerie (come Hibernate Validator) usano reflection per caricare classi dinamicamente
- Jakarta Validation: Le annotazioni come
@Pattern
usano internamente classi annidate (es.Pattern$Flag
) che non vengono rilevate dall'analisi statica
Soluzione implementata
Abbiamo risolto con un'estensione Quarkus che forza l'inclusione delle classi necessarie(es.):
@BuildStep
@Record(ExecutionTime.STATIC_INIT)
void registerReflectionClasses(BuildProducer<ReflectiveClassBuildItem> producer) {
producer.produce(
new FeatureBuildItem("core-validator")
);
...
}
Best practices per evitare il problema
- Test early: Eseguire test in modalità nativa durante lo sviluppo
- Profile Reflection: Usare
quarkus.native.enable-reports=true
per analizzare le classi caricate - Extension-first: Preferire le estensioni Quarkus alle soluzioni manuali
Conclusioni
Le compilazioni native richiedono un approccio diverso alla gestione delle dipendenze. Questo caso specifico dimostra come anche librerie standard possano causare problemi inaspettati. La soluzione tramite estensione Quarkus si è rivelata la più mantenibile e scalabile per il nostro use case.
The Problem of "Ghost" Objects in Native Compiling (Java/Kotlin)
Problem scenario
Have you ever wondered why some resources/objects/functionalities are not available when you perform a release on a platform such as Docker.
Well, yes, during the development of a Quarkus application with java/kotlin language (also valid for Spring Boot with GraalVM) we came across ghost objects of native compilations.
The behavior was particularly insidious, gradually proceeding, because:
- Traditional JVM compilation worked fine
- The error appeared only after native compilation
- The missing class belonged to a standard dependency (jakarta.validation)
And that's where we faced the error:
Type not found: class jakarta.validation.constraints.Pattern$Flag
when calling ...FieldValidator
Everything worked fine until the application was an end in itself; with the aim of generalizing some functionality to share with multiple applications, we separated the logic by creating a common SDK, which triggered the problem described above.
Native compiling, thanks to the conservative approach, works like an overzealous poodle; GraalVM cuts everything that is not explicitly referenced in the code during static analysis.
Problem diagnosis
The problem is rooted in the way native compilation (via GraalVM) handles reflection:
- Aggressive optimization: The native compiler excludes classes that are not explicitly referenced by default
- Dynamic Loading: Some libraries (like Hibernate Validator) use reflection to load classes dynamically
- Jakarta Validation: Annotations like
@Pattern
internally use nested classes (e.g.Pattern$Flag
) that are not detected by static analysis
Implemented solution
We solved it with a Quarkus extension that forces the inclusion of the necessary classes (e.g.):
@BuildStep
@Record(ExecutionTime.STATIC_INIT)
void registerReflectionClasses(BuildProducer<ReflectiveClassBuildItem> producer) {
producer.produce(
new FeatureBuildItem("core-validator")
);
...
}
Best practices to avoid the problem
- Test early: Run native tests during development
- Profile Reflection: Use
quarkus.native.enable-reports=true
to analyze loaded classes - Extension-first: Prefer Quarkus extensions to manual solutions
Conclusions
Native builds require a different approach to dependency management. This specific case shows how even standard libraries can cause unexpected problems. The Quarkus extension solution turned out to be the most maintainable and scalable for our use case.