0

I have a JAR file that looks like this:

MacBook@ ~/workspace-sync/mparnisa_dynamodb2/out/artifacts/dynamodb $ jar tf dynamodb.jar | head
META-INF/MANIFEST.MF
mparnisa-dynamodb-table.json
com/
com/mparnisa/
com/mparnisa/dynamodb/
com/mparnisa/dynamodb/table/
com/mparnisa/dynamodb/table/ResourceModel$ResourceModelBuilder.class
com/mparnisa/dynamodb/table/ResourceModel.class
com/amazonaws/AmazonServiceException.class
// and many more...

From an IntelliJ project, I want to create an instance of ResourceModel, which looks like this:

package com.mparnisa.dynamodb.table;

@com.fasterxml.jackson.annotation.JsonAutoDetect(fieldVisibility = com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.ANY, getterVisibility = com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.NONE, setterVisibility = com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.NONE)
public class ResourceModel {

So in my code I wrote this:

File file  = new File("/Users/mparnisa/workspace-sync/mparnisa_dynamodb2/out/artifacts/dynamodb/dynamodb.jar");

URL url = file.toURI().toURL();
URL[] urls = new URL[]{url};

ClassLoader urlClassLoader = new URLClassLoader(urls);

String resourceModelClassName = "com.mparnisa.dynamodb.table.ResourceModel";

Class<?> resourceModelClass = urlClassLoader.loadClass(resourceModelClassName);

but the last line throws an exception:

java.lang.ClassNotFoundException: com.mparnisa.dynamodb.table.ResourceModel
Maria Ines Parnisari
  • 16,584
  • 9
  • 85
  • 130
  • @Michael no, i'm not a beginner. I can't do `import com.mparnisa.dynamodb.table.CreateHandler` because of reasons that are not relevant to this question – Maria Ines Parnisari Mar 20 '20 at 00:38
  • @Michael because I won't know until runtime what class I need to instantiate – Maria Ines Parnisari Mar 20 '20 at 00:43
  • From what I can tell, your jar contains other jars. The class you want to import is within one of the nested jars. The usual way to create a fat jar involves effectively unzipping all of the dependency jars (Maven shade plugin will do this). That is, you would end up with directories containing .class files, but no jars. Spring Boot allows nested jars but it has it's own classloader which knows how to find classes in these nested jars. The default classloader won't do that. – Michael Mar 20 '20 at 00:43
  • @Michael the class I want to load is within the JAR (it's not nested). But I think your point still applies... So I need to create a fat jar? Is there no other solution? – Maria Ines Parnisari Mar 20 '20 at 00:48
  • The class you want to load is in the jar but it's failing before that. It fails on `AmazonServiceException`, which from the output you listed, is not at the top-level of the jar. Fat jar is the easy solution, yes. In fact what you have is already kind of a fat jar (the dependencies are bundled with your code) but it is not structured in a way that's usable by the default classloader. – Michael Mar 20 '20 at 00:51
  • `AmazonServiceException` is within `aws-java-sdk-core-1.11.728.jar`. Is there a way I can structure this JAR better? I generated it using IntelliJ artifacts, as in [option 2 of this SO answer](https://stackoverflow.com/a/42200519/1623249). – Maria Ines Parnisari Mar 20 '20 at 00:57
  • I don't know how option 2 is ever supposed to work. AFAIK the classloader will not check the manifest. And well, you can see yourself that it doesn't load the class. I am not familiar with IntelliJ artifact configuration, but it looks like you want to right click the dependency and do "Extract in output root" - the first option they mention. Not 'put'. – Michael Mar 20 '20 at 01:04
  • Yes, I am trying that now :) Now the JAR has a lot of `class` files but my code still doesn't work an in fact can't load any class :( – Maria Ines Parnisari Mar 20 '20 at 01:06
  • 1
    Maybe update your question. What's the error now? When you run `jar tf` are the dependency classes in the same structure as yours? (e.g. `com/amazonaws/AmazonServiceException.class`) – Michael Mar 20 '20 at 01:08
  • Updated question :) – Maria Ines Parnisari Mar 20 '20 at 01:14
  • Can it definitely find the file? In the code it's lowercase: `dynamodb.jar`. In your command, it's `DynamoDb.jar`. I tried to replicate with a gibberish filepath, it gave `ClassNotFoundException` like yours – Michael Mar 20 '20 at 01:18
  • Oh yeah, sorry about that. Yeah the file is there and the case is correct. – Maria Ines Parnisari Mar 20 '20 at 01:26
  • What you have should work. But since it's in the same jar, you can take a simpler approach anyway. Try `Class> resourceModelClass = Class.forName("com.mparnisa.dynamodb.table.ResourceModel")` and ditch the rest – Michael Mar 20 '20 at 01:29
  • That gives `Method threw 'java.lang.ClassNotFoundException' exception.` again :( – Maria Ines Parnisari Mar 20 '20 at 01:32
  • For some reason `urlClassLoader.classes` has 0 classes.. – Maria Ines Parnisari Mar 20 '20 at 02:01
  • 1
    It's not surprising that the list of classes is empty, it will be filled with classes which have been loaded and your attempt to load a class failed. I suggest running the code with a newer JDK, because they will provide a more meaningful message when class loading failed, e.g. naming the failed dependency when a class was present but depends on another class that failed. After identifying the problem, you can turn back to the old Java version. – Holger Mar 20 '20 at 11:24
  • Thanks @Holger. Will try that :) I think it's definitely a problem with how I am building the JAR. I am using "IntelliJ artifacts" to build it, but it's definitely not right. Depending on what configuration I use, I get different errors – Maria Ines Parnisari Mar 20 '20 at 18:13

1 Answers1

0

NoClassDefFound is also thrown when the class is found but static initialization fails. In this case there should be a more informative exception in the log at program start.

kiwiron
  • 1,677
  • 11
  • 17