Gradle: Spring + FatJar = dependency on Internet connection

See my recent blog post for FatJar configuration sample. Today I found nasty behavior of Gradle plugin FatJar when used on Spring project. I hope that this post will help you not to spend whole day with it - as I did.

If your project uses only one version of each library, java packages system should assure that there will be no conflicts when merging multiple jar libraries into one big. But that does not apply to directory META-INF. This directory typically does not use packages system, so conflicts may appear.

FatJar plugin handles this situation in simple way - it just copies each file in multiple versions:

mess

That - of course - causes issues when loading. Typically only one file is picked. For example Spring stores in spring.schemas paths in resources where XSD schema files referred from your beans.xml are located in resources (so it does not need to load it from Internet). But do that for each module - Context, AOP, Web, … So only one module works.

This problem does not appear on machine with available Internet connection, because it just downloads schemas from addresses like http://www.springframework.org/schema/beans/spring-beans-3.0.xsd. But when you deploy your app on intranet server, problem appears:

143306.358000 2016-01-26 [ main] [ WARN] [factory.xml.XmlBeanDefinitionReader] [] : Ignored XML validation warning org.xml.sax.SAXParseException: schema_reference.4: Failed to read schema document 'http://www.springframework.org/schema/beans/spring-beans-3.0.xsd', because 1) could not find the document; 2) the document could not be read; 3) the root element of the document is not <xsd:schema>.
.... 
Caused by: java.net.UnknownHostException: www.springframework.org 
at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:184) ~[?:1.8.0_72-internal] 
at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392) ~[?:1.8.0_72-internal] 
at java.net.Socket.connect(Socket.java:589) ~[?:1.8.0_72-internal] 
at java.net.Socket.connect(Socket.java:538) ~[?:1.8.0_72-internal] 

And then:

Exception in thread <main> org.springframework.beans.factory.xml.XmlBeanDefinitionStoreException: Line 6 in XML document from class path resource [grab_mode_beans.xml] is invalid; nested exception is org.xml.sax.SAXParseException; lineNumber: 6; columnNumber: 69; cvc-elt.1: Cannot find the declaration of element 'beans'. at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.doLoadBeanDefinitions(XmlBeanDefinitionReader.java:399) 
.... 
Caused by: org.xml.sax.SAXParseException; lineNumber: 6; columnNumber: 69; cvc-elt.1: Cannot find the declaration of element 'beans'. 
at com.sun.org.apache.xerces.internal.util.ErrorHandlerWrapper.createSAXParseException(ErrorHandlerWrapper.java:203) 
at com.sun.org.apache.xerces.internal.util.ErrorHandlerWrapper.error(ErrorHandlerWrapper.java:134)

Solution is to use shadowJar. It may look like this:

buildscript { 
    repositories {
        maven { 
            url "https://maven.eveoh.nl/content/repositories/releases" 
        } 
        mavenCentral() 
        jcenter() 
    } 
    
    dependencies { 
        classpath 'com.github.jengelman.gradle.plugins:shadow:1.2.2' 
    } 
} 

apply plugin: 'com.github.johnrengelman.shadow' 

dependencies { 
    compile project(":MyOwnProjectExample") 
} 

shadowJar { 
    archiveName "MyTest.jar"; 
    append("META-INF/spring.schemas") 
    append("META-INF/spring.handlers") 
    manifest { attributes("Main-Class": "com.my.Test" ) } 
} 

//this helps if you like to build fatJar as part of normal build 
//otherwise use "gradle shadowJar"; 
assemble.dependsOn shadowJar 
build.dependsOn shadowJar

I already suffered at least by 3 similar issues with following libraries:

  • Spring
  • Log4j2
  • Lucene

I hope it helps!

Tags:  Gradle  Java  FatJar  Spring