ChartRequestBuilder() fails with IEXCloudClient (map casting issue)
eric-hendrickson opened this issue · comments
Describe the bug
I am trying to use ChartRequestBuilder()
with the IEXCloudClient
, but whenever I do so, I get this error:
Handler dispatch failed; nested exception is java.lang.NoClassDefFoundError: org/glassfish/jersey/internal/l10n/LocalizableMessageFactory$ResourceBundleSupplier
Stepping through my debugger, I see this message:
((com.google.common.collect.SingletonImmutableBiMap)request.queryParams).entrySet().toArray()[0]
= Cannot cast 'com.google.common.collect.RegularImmutableMap' to 'com.google.common.collect.SingletonImmutableBiMap'
To Reproduce
Create a new IEXCloudClient, like so:
private final IEXCloudClient iexTradingClient = IEXTradingClient.create(IEXTradingApiVersion.IEX_CLOUD_V1,
new IEXCloudTokenBuilder()
.withPublishableToken("{publishable token}")
.build());
Below that, try the following code:
RestRequest<List<Chart>> request = new ChartRequestBuilder()
.withSymbol("VOO").withChartRange(ChartRange.ONE_DAY).build();
final List<Chart> chartList = iexTradingClient.executeRequest(request);
Note: This is the request
in question when in the debugger I see
Cannot cast 'com.google.common.collect.RegularImmutableMap' to 'com.google.common.collect.SingletonImmutableBiMap'
Expected behavior
To successfully have my iexTradingClient
(an instance of IEXCloudClient
) successfully return a List<Chart>
.
Additional context
When debugging, I've noticed that the URL that is generated in pl.zankowski.iextrading4j.client.rest.manager.RestManager
(which is https://cloud.iexapis.com/v1/stock/VOO/chart/1d?token={publishable key}
) is successful in Postman, indicating that both my token and the built endpoint are correct.
Hey,
((com.google.common.collect.SingletonImmutableBiMap)request.queryParams).entrySet().toArray()[0]
I am not sure if I understood this one, but of course it will throw an exception if you cast queryParams to SingletonImmutableBiMap in your debugger, because queryParams is not BiDirectional Map. It is RegularImmutableMap. There is no such casting anywhere in the code.
So back to your problem:
Handler dispatch failed; nested exception is java.lang.NoClassDefFoundError: org/glassfish/jersey/internal/l10n/LocalizableMessageFactory$ResourceBundleSupplier
I believe the problem is that in your project setup there are 2 different versions of jersey libraries. Your issue comes from the fact that in my library I use the newest version of jersey version 2.28. And this class looks like this:
public class LocalizableMessageFactory {
private final String _bundlename;
private final ResourceBundleSupplier _rbSupplier;
@Deprecated
public LocalizableMessageFactory(String bundlename) {
_bundlename = bundlename;
_rbSupplier = null;
}
public LocalizableMessageFactory(String bundlename, ResourceBundleSupplier rbSupplier) {
_bundlename = bundlename;
_rbSupplier = rbSupplier;
}
public Localizable getMessage(String key, Object... args) {
return new LocalizableMessage(_bundlename, _rbSupplier, key, args);
}
public interface ResourceBundleSupplier {
/**
* Gets the ResourceBundle.
* @param locale the requested bundle's locale
* @return ResourceBundle
*/
ResourceBundle getResourceBundle(Locale locale);
}
}
In earlier version it looked like this:
public class LocalizableMessageFactory {
private final String _bundlename;
public LocalizableMessageFactory(String bundlename) {
this._bundlename = bundlename;
}
public Localizable getMessage(String key, Object... args) {
return new LocalizableMessage(this._bundlename, key, args);
}
}
So obviously it cannot find this ResourceBundleSupplier class.
You didn't provide enough information so let me guess. You are using Spring Boot which is using jersey version 2.27 or earlier. Somehow your dependencies end up being messed up in the runtime and it fails. Simplest solution that I can provide without more details is to exclude jersey dependencies from my lib and use dependencies from other libraries. Something like this:
<dependency>
<dependency>
<groupId>pl.zankowski</groupId>
<artifactId>iextrading4j-all</artifactId>
<version>3.2.0</version>
<exclusions>
<exclusion>
<groupId>org.glassfish.jersey.core</groupId>
<artifactId>jersey-client</artifactId>
</exclusion>
<exclusion>
<groupId>org.glassfish.jersey.inject</groupId>
<artifactId>jersey-hk2</artifactId>
</exclusion>
<exclusion>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
</exclusion>
<exclusion>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-sse</artifactId>
</exclusion>
</exclusions>
</dependency>
If you have Intellij IDEA Ultimate you can display dependency tree to track conflicts.
Cheers,
Wojtek
I am indeed using Spring Boot 2.0.1. I'm using gradle, and I haven't had any success with either excluding or using the provided jersey (I get an org.springframework.beans.factory.UnsatisfiedDependencyException
with any classes that use the iextrading package), however, I did see something interesting when I ran my gradle dependencies:
+--- pl.zankowski:iextrading4j-all:3.2.0
| +--- pl.zankowski:iextrading4j-api:3.2.0
| | +--- com.fasterxml.jackson.core:jackson-annotations:2.9.8 -> 2.9.0
| | \--- com.google.guava:guava:27.1-jre -> 24.1-jre (*)
| \--- pl.zankowski:iextrading4j-client:3.2.0
| +--- pl.zankowski:iextrading4j-api:3.2.0 (*)
| +--- javax.ws.rs:javax.ws.rs-api:2.1.1
| +--- org.glassfish.jersey.core:jersey-client:2.28 -> 2.26
| | +--- javax.ws.rs:javax.ws.rs-api:2.1 -> 2.1.1
| | +--- org.glassfish.jersey.core:jersey-common:2.26
| | | +--- javax.ws.rs:javax.ws.rs-api:2.1 -> 2.1.1
| | | +--- javax.annotation:javax.annotation-api:1.2 -> 1.3.2
| | | +--- org.glassfish.hk2.external:javax.inject:2.5.0-b42
| | | \--- org.glassfish.hk2:osgi-resource-locator:1.0.1
| | \--- org.glassfish.hk2.external:javax.inject:2.5.0-b42
| +--- org.glassfish.jersey.inject:jersey-hk2:2.28
| | +--- org.glassfish.jersey.core:jersey-common:2.28 -> 2.26 (*)
| | \--- org.glassfish.hk2:hk2-locator:2.5.0
| | +--- org.glassfish.hk2.external:jakarta.inject:2.5.0
| | +--- org.glassfish.hk2.external:aopalliance-repackaged:2.5.0
| | +--- org.glassfish.hk2:hk2-api:2.5.0
| | | +--- org.glassfish.hk2.external:jakarta.inject:2.5.0
| | | +--- org.glassfish.hk2:hk2-utils:2.5.0
| | | | +--- jakarta.annotation:jakarta.annotation-api:1.3.4
| | | | \--- org.glassfish.hk2.external:jakarta.inject:2.5.0
| | | \--- org.glassfish.hk2.external:aopalliance-repackaged:2.5.0
| | +--- org.glassfish.hk2:hk2-utils:2.5.0 (*)
| | +--- jakarta.annotation:jakarta.annotation-api:1.3.4
| | \--- org.javassist:javassist:3.22.0-CR2 -> 3.22.0-GA
| +--- org.glassfish.jersey.media:jersey-media-json-jackson:2.28 -> 2.26
| | +--- org.glassfish.jersey.core:jersey-common:2.26 (*)
| | +--- org.glassfish.jersey.ext:jersey-entity-filtering:2.26
| | | \--- javax.ws.rs:javax.ws.rs-api:2.1 -> 2.1.1
| | +--- com.fasterxml.jackson.core:jackson-annotations:2.8.4 -> 2.9.0
| | +--- com.fasterxml.jackson.core:jackson-databind:2.8.4 -> 2.9.3 (*)
| | \--- com.fasterxml.jackson.module:jackson-module-jaxb-annotations:2.8.4 -> 2.9.3
| | +--- com.fasterxml.jackson.core:jackson-annotations:2.9.0
| | +--- com.fasterxml.jackson.core:jackson-core:2.9.3
| | \--- com.fasterxml.jackson.core:jackson-databind:2.9.3 (*)
| +--- org.glassfish.jersey.media:jersey-media-sse:2.28
| | +--- org.glassfish.hk2.external:jakarta.inject:2.5.0
| | \--- org.glassfish.jersey.core:jersey-server:2.28 -> 2.26
| | +--- org.glassfish.jersey.core:jersey-common:2.26 (*)
| | +--- org.glassfish.jersey.core:jersey-client:2.26 (*)
| | +--- javax.ws.rs:javax.ws.rs-api:2.1 -> 2.1.1
| | +--- org.glassfish.jersey.media:jersey-media-jaxb:2.26
| | | +--- org.glassfish.jersey.core:jersey-common:2.26 (*)
| | | +--- org.glassfish.hk2.external:javax.inject:2.5.0-b42
| | | \--- org.glassfish.hk2:osgi-resource-locator:1.0.1
| | +--- javax.annotation:javax.annotation-api:1.2 -> 1.3.2
| | +--- org.glassfish.hk2.external:javax.inject:2.5.0-b42
| | \--- javax.validation:validation-api:1.1.0.Final -> 2.0.1.Final
| +--- com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.9.8 -> 2.9.3 (*)
| +--- io.socket:socket.io-client:1.0.0
| | +--- io.socket:engine.io-client:1.0.0
| | | +--- com.squareup.okhttp3:okhttp:3.8.1
| | | | \--- com.squareup.okio:okio:1.13.0
| | | \--- org.json:json:20090211
| | \--- org.json:json:20090211
| +--- javax.xml.bind:jaxb-api:2.3.1 -> 2.3.0
| +--- com.sun.xml.bind:jaxb-impl:2.3.1
| +--- org.glassfish.jaxb:jaxb-runtime:2.3.1
| | +--- javax.xml.bind:jaxb-api:2.3.1 -> 2.3.0
| | +--- org.glassfish.jaxb:txw2:2.3.1
| | +--- com.sun.istack:istack-commons-runtime:3.0.7
| | +--- org.jvnet.staxex:stax-ex:1.8
| | +--- com.sun.xml.fastinfoset:FastInfoset:1.2.15
| | \--- javax.activation:javax.activation-api:1.2.0
| \--- javax.activation:activation:1.1.1
It appears that jersey can only update to as high as version 2.26 (guava is favoring a deprecated version too, maybe that explains the different map data structures I saw in my debugger as well).
Is there any way that I could use my current version of jersey with your code, or in your opinion should I pursue another means of performing these requests (be that updating Spring Boot or using a different approach altogether)?
Much obliged,
Eric
Sure, just go and exclude whatever you want. I use the newest versions because they are the newest. There is no other reason. Library will work probably with any other old version. I don't use anything specific to the newest version. Same goes for guava and other. Just exclude them like this and you should be fine then:
<dependency>
<groupId>pl.zankowski</groupId>
<artifactId>iextrading4j-all</artifactId>
<version>3.2.0</version>
<exclusions>
<exclusion>
<groupId>org.glassfish.jersey.core</groupId>
<artifactId>jersey-client</artifactId>
</exclusion>
<exclusion>
<groupId>org.glassfish.jersey.inject</groupId>
<artifactId>jersey-hk2</artifactId>
</exclusion>
<exclusion>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
</exclusion>
<exclusion>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-sse</artifactId>
</exclusion>
</exclusions>
</dependency>
Or however you do exclusions in gradle ;)