Search code examples
javamavenjavax.imageio

ImageIO PSD library plugin not working inside of a jar


I'm working with ImageIO and I'm trying to package the code into a JAR, however, I found out (after some debugging) that the PSD plugin is not present inside of the packaged JAR

I found out thanks to the code in this answer: Add/remove ImageReader from jar to ImageIO-registry

In my pom.xml I have the following ImageIO dependencies:

   <dependency>
        <groupId>com.twelvemonkeys.imageio</groupId>
        <artifactId>imageio-bmp</artifactId>
        <version>3.4</version>
    </dependency>
    <dependency>
        <groupId>com.twelvemonkeys.imageio</groupId>
        <artifactId>imageio-psd</artifactId>
        <version>3.4</version>
    </dependency>
    <dependency>
        <groupId>com.twelvemonkeys.imageio</groupId>
        <artifactId>imageio-core</artifactId>
        <version>3.4</version>
    </dependency>
    <dependency>
        <groupId>com.twelvemonkeys.imageio</groupId>
        <artifactId>imageio-metadata</artifactId>
        <version>3.4</version>
    </dependency>

The maven command I use is:

clean compile assembly:single

Side note the code I used to debug:

        ImageIO.scanForPlugins()
        IIORegistry.getDefaultInstance().registerApplicationClasspathSpis()
        val ir = ImageIO.getImageReadersByFormatName("PSD")
        while (ir.hasNext())
        {
            val r = ir.next() as ImageReader
            println(r)
        }

Which in IDE prints:

com.twelvemonkeys.imageio.plugins.psd.PSDImageReader@1963006a

And when running the jar from a commandline it doesn't print anything, leading me to believe the PSD plugin is not working inside of the JAR but how?


Solution

  • The problem is that the target assembly:single merges "everything" from your own project and all referenced JARs into one JAR but skips files that exist there yet.

    ImageIO relies on the SPI/service loader mechanism of Java and therefore the plugins will be loaded via META-INF\services\javax.imageio.spi.ImageReaderSpi. However, when having more than one JAR with such a file and using assembly:single, one of those file "wins" and the ones from the other JARs are skipped. In your project, both imageio-bmp and imageio-psd do have such a file and the first one "wins" in the resulting JAR. (Seems like the IDE loads those files in another order and the correct version "wins", but that is just a guess.)

    Solution: Maven should merge all META-INF\services\javax.imageio.spi.ImageReaderSpi files into one file in the resulting JAR. To do that, Maven needs additional configuration information.

    1. Add a file descriptor.xml to the root of your project, something like this:
    <?xml version="1.0" encoding="UTF-8"?>
    <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">
        <!-- copied from jar-with-dependencies (http://maven.apache.org/plugins/maven-assembly-plugin/descriptor-refs.html#jar-with-dependencies) -->
        <id>jar-with-deps-merge-services</id>
        <formats>
            <format>jar</format>
        </formats>
        <includeBaseDirectory>false</includeBaseDirectory>
        <containerDescriptorHandlers>
            <containerDescriptorHandler>
                <handlerName>metaInf-services</handlerName>
            </containerDescriptorHandler>
        </containerDescriptorHandlers>
        <dependencySets>
            <dependencySet>
                <outputDirectory>/</outputDirectory>
                <useProjectArtifact>true</useProjectArtifact>
                <unpack>true</unpack>
                <scope>runtime</scope>
            </dependencySet>
        </dependencySets>
    </assembly>
    

    The important part is the metaInf-services setting which merges the files in META-INF\services.

    1. Add a reference to the descriptor.xml in your pom.xml:
    [...]
    <build>
        <plugins>
            <plugin>
                <artifactId>maven-assembly-plugin</artifactId>
                <version>2.2.1</version>
                <configuration>
                    <archive>
                        <manifest>
                            <mainClass>your.main.MainClass</mainClass>
                        </manifest>
                    </archive>
                    <descriptors>
                        <descriptor>descriptor.xml</descriptor>
                    </descriptors>
                </configuration>
            </plugin>
        </plugins>
    </build>
    

    Important notes:

    • The maven-assembly-plugin version should be 2.2.1 because the current 3.x version does not seem to work. However, you can try newer 2.2.x or 2.x versions if really needed. I've only tried 2.2.1 and it works.
    • The main-class block must be changed according to your main class name if you need it.
    • The descriptor file can be placed in a different directory in your project, but then the reference in the pom must be changed (it is relative to the root of your maven project).
    • If you have jar-with-dependencies in your build config, this should be removed there because the descriptor file includes that setting.

    Even though I tried this on a sample project, this solution might not be perfect and you might adapt it according to your needs, but I hope this is a suitable point to start from.