Search code examples
javaquarkusgraalvm-native-image

Quarkus native build class in both build and run time initialization


I have a 3rd party class which is added to build time initialization by Quarkus, but it requires run time initialization due to static thread usage. When adding it to run time initialization native build then complains about it being in both.

Example project which re-produces this: https://github.com/hshorter/quarkus-avro-decode-example

With "--initialize-at-run-time=org.apache.avro.specific.SpecificDatumReader":

Error: Classes that should be initialized at run time got initialized during image building: org.apache.avro.specific.SpecificDatumReader the class was requested to be initialized at build time (from the command line). To see why org.apache.avro.specific.SpecificDatumReader got initialized use -H:+TraceClassInitialization

Without "--initialize-at-run-time=org.apache.avro.specific.SpecificDatumReader":

Error: Detected a started Thread in the image heap. Threads running in the image generator are no longer running at image run time. To see how this object got instantiated use -H:+TraceClassInitialization. The object was probably created by a class initializer and is reachable from a static field. You can request class initialization at image run time by using the option --initialize-at-build-time=. Or you can write your own initialization methods and call them explicitly from your main entry point. Detailed message: Trace: object org.apache.avro.specific.SpecificDatumReader

Any help much appreciated.


Solution

  • We just fought through this, in the generated code there's a static initializer like this: private static BinaryMessageEncoder ENCODER = new BinaryMessageEncoder(MODEL$, SCHEMA$); private static BinaryMessageDecoder DECODER = new BinaryMessageDecoder(MODEL$, SCHEMA$);

    We modified the Velocity templates in the Avro code generation to:

    1. Add the @io.quarkus.runtime.annotations.RegisterForReflection annotation
    2. initialize the statics in the constructor using lazy initialization.
    3. Remove those classes from the runtime class init. The downside is that you have to maintain your custom codegen templates. It's relatively easy though, here's the maven config to automate your codegen:
    <plugin>
      <groupId>org.apache.avro</groupId>
      <artifactId>avro-maven-plugin</artifactId>
      <version>${avro.version}</version>
      <executions>
        <execution>
          <id>schemas</id>
          <phase>generate-sources</phase>
          <goals>
            <goal>schema</goal>
          </goals>
          <configuration>
     <templateDirectory>${project.basedir}/src/main/resources/avro/templates/</templateDirectory>
          </configuration>
        </execution>
      </executions>
    </plugin>
    

    you can find the base templates at https://github.com/apache/avro/tree/master/lang/java/compiler/src/main/velocity/org/apache/avro/compiler/specific/templates/java/classic