Farshad
Farshad

Reputation: 13

how IntelliJ is able to resolve dependencies and run a spring boot (maven) application while from the command line, it is not possible?

I'm curious to know how Intellij can resolve the dependency conflicts? Let me explain my situation. I should work on the spring boot application. It uses Maven. IntelliJ can build and run the application without any problem, but when I make a jar file,

mvn clean package 

and run the jar file

java -jar xxx.jar

I faced a java.lang.NoSuchMethodError. Some conflicts on dependencies caused it, and my application uses the wrong version of a jar file. I want to know how IntelliJ can find the correct jar file which contains the method, while it uses the same pom.xml, which I face error while using with the mvn command. And is it possible to find that which version of every jar file used by IntelliJ? (I want to use this for correcting the pom file)

Thanks

Upvotes: 0

Views: 456

Answers (1)

rzwitserloot
rzwitserloot

Reputation: 103637

There is a big difference between runtime and compile time when it comes down to such things.

compiling code (such as mvn package), and writing code (as in, what IntelliJ is doing to ensure that your editing experience is nice; auto-complete dialogs for all the libraries you use, errors if you try to invoke non-existent methods, etcetera) are one thing (let's call it 'write time').

Running your code, as with java -jar xxx.jar is something completely different.

maven and your dependency list is a write time thing. Thus, maven and intellij know where to look for your dependencies, but when you run java -jar xxx.jar, that does not know where to look, and thus, your dependencies aren't found, and thus, NoClassDefFoundError occurs.

That's because that jar file that maven makes just contains your code, it is completely disconnected from your maven file (your maven stuff is not looked up when you run your code at all), and it does not contain your dependencies.

You'd have to ship them separately. There are 3 solutions to this problem:

  • Preferred solution: jar files contain a so-called manifest, which tells the JVM for example what the name of the main class is within that jar file. It can also contain the class-path (this is in fact the only class-path checked when using java -jar to run jar files; you can't specify a -classpath parameter). This lets you deploy your app such that your app installation looks like:
/usr/local/projects/YourApp/main-app.jar
/usr/local/projects/YourApp/dep/guava.jar
/usr/local/projects/YourApp/dep/mysql-jdbc-connector.jar
/usr/local/projects/YourApp/dep/jdbi.jar

etcetera, and to run this application, just run main-app.jar. This requires the manifest of main-app.jar to contain the entry:

Class-Path: dep/guava.jar dep/mysql-jdbc-connector.jar dep/jdbi.jar

When running the jar, the Class-Path entry is split on spaces, and then each entry is looked up relative to the directory that contains main-app.jar. By shipping the jars separately, it's easy to separately update them or replace them, and deploying a new version is much faster (you just ship the jar(s) that were changed, not all of them. Many apps have hundreds of MBs of deps, whereas their own jar is a few MB at most, makes a big difference for example when pushing deps from your dev machine to the test server!)

This leads to the question: How do you make maven put that Class-Path entry in the output jar file's manifest? The maven-jar-plugin can do the job - see this answer for more details.

  • Shade in your deps (also called striping, or fatjar, or bigjar)

This is the notion of taking all your deps, rewriting their name to avoid version conflicts, and then making one humongous jar file that contains everything. It has the considerable downside of being a much slower process, especially if you need to push this out to another system for testing. Use the shade plugin to do so.

  • Don't use java -jar.

java -jar x.jar cannot work unless the jar file either has a Class-Path entry in its manifest, or contains every dep it needs. However, you can also run your java code like so: java -cp main-app.jar:dep/guava.jar:dep/jdbi.jar:/dep/other-deps-here com.foo.YourMainApp. This is.. not convenient, but you could presumably write a shell script or some such. This isn't a very java-like solution, I don't recommend it.

Upvotes: 0

Related Questions