DEV Community

Sorin Costea
Sorin Costea

Posted on • Originally published at tryingthings.wordpress.com

Building Quarkus native images. You can do it too!

If you read the Quarkus native build page, it should be a breeze: enable -Pnative and there we go! Well… not so fast. Not so fast at all, as they recognize in another “Quarkus native tips” page, unfortunately not linked from the first one (so good luck googling for it). And boy do you need those tips…

I had this application happily running for months and while at the beginning there were some recognized issues with native generation on Windows, nowadays the general expectation was it should work. So I updated all dependencies and started a painful three days long trip, but hey no pain no gain right?

hmm

Here’s what it required:

  1. First of all, you do have Docker installed, right? And enabled the container generation in the JVM parameters with

    -Dquarkus.native.container-build=true
    

    ok? Well, if you don’t want to go through the hoops of installing GraalVM too (I did, got a tshirt), use the build JVM parameter to create the image with Mandrel (mentioned on the Quarkus native page, without any explanation what the “Mandrel tags” were)

    `-Dquarkus.native.builder-image=quay.io/quarkus/ubi-quarkus-mandrel:20.3-java11
    
  2. Speaking of JVM parameters, the generation will crash soon running out of memory. Quarkus defaults schmefaults. Add this JVM parameter to increase the Java heap during generation:

    -Dquarkus.native.native-image-xmx=4096m
    
  3. Actually one of the first errors you’ll see will be the complaint you don’t have the src/assembly/zip.xml file mentioned in the native Maven profile. A good start will be this:

    <assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0 http://maven.apache.org/xsd/assembly-1.1.0.xsd">
      <id>function-native-zip</id>
      <baseDirectory>/</baseDirectory>
      <formats>
        <format>zip</format>
      </formats>
      <files>
        <file
          <source>${project.build.directory}${file.separator}${artifactId}-${version}-runner</source>
          <outputDirectory>${file.separator}</outputDirectory>
          <destName>bootstrap</destName>
          <fileMode>755</fileMode>
        </file>
      </files>
    </assembly>
    
  4. You’ll have definitely sooner or later the exception in logs that it cannot find org.apache.commons.logging.impl.LogFactoryImpl. Regardless how you add it to your pom, Quarkus will do some reflection magic and constantly fail at it. Just forget commons-logging and use the recommended JBoss logmanager.

    <dependency>
      <groupId>org.jboss.logmanager</groupId>
      <artifactId>log4j2-jboss-logmanager</artifactId>
    </dependency>
    
  5. Did you know Quarkus offers their own Elasticsearch clients? I didn’t either, and I had to switch to their distribution instead of the official Elasticsearch. Same API luckily, cleaner dependencies (and no commons-logging either)

    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-elasticsearch-rest-high-level-client</artifactId>
    </dependency>   
    
  6. That Elasticsearch builder will produce a static client (configured via application.properties) which nobody needs. I mean, if you want to configure the host at runtime, if you want request signing as you access an AWS Elasticsearch cluster… you’ll get ambiguous injection all in a sudden. That was easy to fix – produce and use your own @Named client.

    @Produces
    @Named("essigned")
    public static RestHighLevelClient getClient(final String url) {
      ...
    
  7. Is it unable to load an HTTP implementation from any provider in the chain? That’s because many AWS SDK v2 clients include the Apache HTTP client only as runtime dependency, for whatever reason – and Quarkus itself does the same (in the above mentioned Elasticsearch client). So add it to your pom AND mention it SPECIFICALLY in the building of your clients!

    <dependency>
      <groupId>software.amazon.awssdk</groupId>
      <artifactId>apache-client</artifactId>
      <scope>compile</scope>
      <exclusions>
        <exclusion>
          <groupId>commons-logging</groupId>
          <artifactId>commons-logging</artifactId>
        </exclusion>
      </exclusions>
    </dependency>
    

    and the Java code:

    final SsmClient ssm = SsmClient.builder().httpClientBuilder(ApacheHttpClient.builder()).credentialsProvider(chain).build();
    

    Oh right, don’t forget to exclude the commons-logging dependency – that little pesky brat keeps crawling back.

  8. Now it’s the moment for your runtime to crash for missing proxies, more precisely because “generating proxy classes at runtime is not supported”. Don’t ask why Quarkus doesn’t handle that at native image generation, or at least warn about it. So just add in your application.properties this line:

    quarkus.native.additional-build-args=-H:DynamicProxyConfigurationFiles=proxies.json
    

    and a new file proxies.json in your src/main/resources, containing exactly those interfaces the error message mentioned. Mine was:

    [
        ["org.apache.http.conn.HttpClientConnectionManager", "org.apache.http.pool.ConnPoolControl", "software.amazon.awssdk.http.apache.internal.conn.Wrapped"]
    ]
    

    The issue is mentioned in the Quarkus native tips page, but you’re left on your own trying to figure out WHAT and HOW to put into the mentioned file.

  9. While we’re at application.properties, if you have any SSL connection in your code (and unless you do Hello World, you’ll have) place this line there as well:

    quarkus.ssl.native=true
    

So, this was my adventure so far. The application works now, startup time down from 3.2s to .38s and half the memory footprint… I’m waiting for the next surprise.
hmm

Top comments (0)