Giter VIP home page Giter VIP logo

java-server-sdk's Introduction

LaunchDarkly Server-side SDK for Java

Circle CI Javadocs

LaunchDarkly overview

LaunchDarkly is a feature management platform that serves trillions of feature flags daily to help teams build better software, faster. Get started using LaunchDarkly today!

Twitter Follow

Supported Java versions

This version of the LaunchDarkly SDK works with Java 8 and above.

Distributions

Two variants of the SDK jar are published to Maven:

  • The default uberjar - this is accessible as com.launchdarkly:launchdarkly-java-server-sdk:jar and is the dependency used in the "Getting started" section of the SDK reference guide as well as in the hello-java sample app. This variant contains the SDK classes and all of its required dependencies. All bundled dependencies that are not surfaced in the public API have shaded package names (and are not exported in OSGi), so they will not interfere with any other versions of the same packages.
  • The "thin" jar - add <classifier>thin</classifier> in Maven, or :thin in Gradle. This contains only the SDK classes, without its dependencies. Applications using this jar must provide all of the dependencies that are in the SDK's build.gradle, so it is intended for use only in special cases.

Previous SDK versions also included a third classifier, all, which was the same as the default uberjar but also contained the SLF4J API. This no longer exists because the SDK no longer requires the SLF4J API to be in the classpath.

Getting started

Refer to the SDK reference guide for instructions on getting started with using the SDK.

Logging

By default, the LaunchDarkly SDK uses SLF4J if the SLF4J API is present in the classpath. SLF4J has its own configuration mechanisms for determining where output will go, and filtering by level and/or logger name.

If SLF4J is not in the classpath, the SDK's default logging destination is System.err.

The SDK can also be configured to use other adapters from the com.launchdarkly.logging facade. See LoggingConfigurationBuilder. This allows the logging behavior to be completely determined by the application, rather than by external SLF4J configuration.

For an example of using the default SLF4J behavior with a simple console logging configuration, check out the slf4j-logging branch of the hello-java project. The main branch of hello-java uses console logging that is programmatically configured without SLF4J.

All loggers are namespaced under com.launchdarkly, if you are using name-based filtering.

Be aware of two considerations when enabling the DEBUG log level:

  1. Debug-level logs can be very verbose. It is not recommended that you turn on debug logging in high-volume environments.
  2. Potentially sensitive information is logged including LaunchDarkly users created by you in your usage of this SDK.

Using flag data from a file

For testing purposes, the SDK can be made to read feature flag state from a file or files instead of connecting to LaunchDarkly. See FileData for more details.

DNS caching issues

LaunchDarkly servers operate in a load-balancing framework which may cause their IP addresses to change. This could result in the SDK failing to connect to LaunchDarkly if an old IP address is still in your system's DNS cache.

Unlike some other languages, in Java the DNS caching behavior is controlled by the Java virtual machine rather than the operating system. The default behavior varies depending on whether there is a security manager: if there is, IP addresses will never expire. In that case, we recommend that you set the security property networkaddress.cache.ttl, as described here, to a number of seconds such as 30 or 60 (a lower value will reduce the chance of intermittent failures, but will slightly reduce networking performance).

Learn more

Read our documentation for in-depth instructions on configuring and using LaunchDarkly. You can also head straight to the complete reference guide for this SDK or our code-generated API documentation.

Testing

We run integration tests for all our SDKs using a centralized test harness. This approach gives us the ability to test for consistency across SDKs, as well as test networking behavior in a long-running application. These tests cover each method in the SDK, and verify that event sending, flag evaluation, stream reconnection, and other aspects of the SDK all behave correctly.

Contributing

We encourage pull requests and other contributions from the community. Check out our contributing guidelines for instructions on how to contribute to this SDK.

About LaunchDarkly

  • LaunchDarkly is a continuous delivery platform that provides feature flags as a service and allows developers to iterate quickly and safely. We allow you to easily flag your features and manage them from the LaunchDarkly dashboard. With LaunchDarkly, you can:
    • Roll out a new feature to a subset of your users (like a group of users who opt-in to a beta tester group), gathering feedback and bug reports from real-world use cases.
    • Gradually roll out a feature to an increasing percentage of users, and track the effect that the feature has on key metrics (for instance, how likely is a user to complete a purchase if they have feature A versus feature B?).
    • Turn off a feature that you realize is causing performance problems in production, without needing to re-deploy, or even restart the application with a changed configuration file.
    • Grant access to certain features based on user attributes, like payment plan (eg: users on the ‘gold’ plan get access to more features than users in the ‘silver’ plan). Disable parts of your application to facilitate maintenance, without taking everything offline.
  • LaunchDarkly provides feature flag SDKs for a wide variety of languages and technologies. Read our documentation for a complete list.
  • Explore LaunchDarkly

java-server-sdk's People

Contributors

aengelberg avatar apucacao avatar arun251 avatar ashanbrown avatar atrakh avatar bwoskow-ld avatar cwaldren-ld avatar drichelson avatar eli-darkly avatar eplusminus avatar ivorpatl avatar jkodumal avatar kparkinson-ld avatar kutsal avatar kxwu avatar kyeotic avatar launchdarklyci avatar launchdarklyreleasebot avatar ld-repository-standards[bot] avatar levlaz avatar louis-launchdarkly avatar mightyguava avatar pkaeding avatar richardfearn avatar samhaldane avatar ssrm avatar tanderson-ld avatar tim-launchdarkly avatar zmdavis avatar zurab-darkly avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

java-server-sdk's Issues

Add error handler

  1. Define a class hierarchy of LaunchDarkly exceptions.
  2. Create an Interface called ErrorHandler with one method that looks like this: void handle(Exception e)
  3. Optionally pass in an implementation of this interface when creating the config.
  4. Call handle() with the exception wherever we log an error message.

java reactive client

hello, I have detected that thanks to Spring 5 and Project Reactor more and more projects use so-called reactive approach. To be able to use an existing Launch Darkly client in reactive projects I have to write a kind of boilerplate code to be able to translate existing return types to appropriate reactive types. For example:
public boolean boolVariation(String featureKey, LDUser user, boolean defaultValue)
ideally should look like
public Mono<Boolean> boolVariation(String featureKey, LDUser user, boolean defaultValue)
the wrapper code which I have to use in my-own LD client warapper to get ride of the issue looks like following:
Mono<Boolean> booleanMono = Mono.fromCallable(() -> LDClient.boolVariation(featureKey, user, false)).subscribeOn( Schedulers.elastic());

Do you consider an option to extend existing LD java-client library (or build java-client-reactive) so provide reactive client out of the box? I feel it could be much appreciated by many of your clients who adopt reactive stack.

FYI here is more information about webflux and Project Reactor
https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html
https://github.com/reactor/reactor-core

A way to serialize and deserialize LDUser

Is your feature request related to a problem? Please describe.
I would like to have the ability use Gson or other json serialization library in order to serialize/deserialize LDUser
and transfer it between microservices.

Describe the solution you'd like
I think that as you already have Gson serialization, just need deserialization that takes the created LDUser and
set LDValue null in case that the specific field is null

Additional context
I tried just to use LDUser ldUser = GSON.fromJson(ldUserStr, LDUser.class);
but then I receive NPE with the following stacktrace:
java.lang.NullPointerException: null
at com.launchdarkly.client.LDUser$UserAdapterWithPrivateAttributeBehavior.write(LDUser.java:201)
at com.launchdarkly.client.LDUser$UserAdapterWithPrivateAttributeBehavior.write(LDUser.java:180)
at com.google.gson.Gson.toJson(Gson.java:704)
at com.launchdarkly.client.EventOutputFormatter.writeUser(EventOutputFormatter.java:186)
at com.launchdarkly.client.EventOutputFormatter.writeOutputEvent(EventOutputFormatter.java:81)
at com.launchdarkly.client.EventOutputFormatter.writeOutputEvents(EventOutputFormatter.java:31)
at com.launchdarkly.client.DefaultEventProcessor$SendEventsTask.run(DefaultEventProcessor.java:644)

I am using version 4.12.1 of the library.
Thanks, David.

LDClient doesn't shutdown properly in the streaming mode

Hi!

We have a web application under Tomcat server which uses Java LDClient (v. 2.1.0, streaming mode) to access feature flags.
We have a problem with shutdown of our application. It doesn't stop within 60 seconds.
Our shutdown routine looks like:

  1. Stop Tomcat
  2. Wait 60 seconds for application termination
  3. Make a threaddump and kill the process if application hasn't stopped.

Almost all threaddumps contain following threads:

"OkHttp ConnectionPool" daemon prio=10 tid=0x00007fd7c0027800 nid=0x13e in Object.wait() [0x00007fd76b4f3000]
   java.lang.Thread.State: TIMED_WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x0000000795ab28a0> (a com.launchdarkly.shaded.okhttp3.ConnectionPool)
        at java.lang.Object.wait(Object.java:461)
        at com.launchdarkly.shaded.okhttp3.ConnectionPool$1.run(ConnectionPool.java:66)
        - locked <0x0000000795ab28a0> (a com.launchdarkly.shaded.okhttp3.ConnectionPool)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
        at java.lang.Thread.run(Thread.java:745)

"okhttp-eventsource-0" prio=10 tid=0x00007fd7c4df6000 nid=0x13b runnable [0x00007fd76b5f3000]
   java.lang.Thread.State: RUNNABLE
        at java.net.SocketInputStream.socketRead0(Native Method)
        at java.net.SocketInputStream.read(SocketInputStream.java:152)
        at java.net.SocketInputStream.read(SocketInputStream.java:122)
        at sun.security.ssl.InputRecord.readFully(InputRecord.java:442)
        at sun.security.ssl.InputRecord.read(InputRecord.java:480)
        at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:934)
        - locked <0x0000000795ae8d10> (a java.lang.Object)
        at sun.security.ssl.SSLSocketImpl.readDataRecord(SSLSocketImpl.java:891)
        at sun.security.ssl.AppInputStream.read(AppInputStream.java:102)
        - locked <0x0000000795ae8d28> (a sun.security.ssl.AppInputStream)
        at com.launchdarkly.shaded.okio.Okio$2.read(Okio.java:138)
        at com.launchdarkly.shaded.okio.AsyncTimeout$2.read(AsyncTimeout.java:236)
        at com.launchdarkly.shaded.okio.RealBufferedSource.request(RealBufferedSource.java:66)
        at com.launchdarkly.shaded.okio.RealBufferedSource.require(RealBufferedSource.java:59)
        at com.launchdarkly.shaded.okio.RealBufferedSource.readHexadecimalUnsignedLong(RealBufferedSource.java:284)
        at com.launchdarkly.shaded.okhttp3.internal.http1.Http1Codec$ChunkedSource.readChunkSize(Http1Codec.java:444)
        at com.launchdarkly.shaded.okhttp3.internal.http1.Http1Codec$ChunkedSource.read(Http1Codec.java:425)
        at com.launchdarkly.shaded.okio.RealBufferedSource.read(RealBufferedSource.java:45)
        at com.launchdarkly.shaded.okio.RealBufferedSource.indexOf(RealBufferedSource.java:325)
        at com.launchdarkly.shaded.okio.RealBufferedSource.indexOf(RealBufferedSource.java:314)
        at com.launchdarkly.shaded.okio.RealBufferedSource.readUtf8LineStrict(RealBufferedSource.java:210)
        at com.launchdarkly.shaded.com.launchdarkly.eventsource.EventSource.connect(EventSource.java:148)
        at com.launchdarkly.shaded.com.launchdarkly.eventsource.EventSource.access$600(EventSource.java:29)
        at com.launchdarkly.shaded.com.launchdarkly.eventsource.EventSource$1.run(EventSource.java:74)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
        at java.lang.Thread.run(Thread.java:745)

If we switch the LDClient to events mode the application stops within a few seconds.

EventSource.java:

            for (String line; !Thread.currentThread().isInterrupted() && (line = bs.readUtf8LineStrict()) != null; ) 
              parser.line(line);

It looks like we never leave bs.readUtf8LineStrict().

You can reproduce the issue using very simple console application:

import com.launchdarkly.client.LDClient;
import com.launchdarkly.client.LDConfig;

public class LDShutdownProto
{
    public static void main(String[] args) throws Exception
    {
        new LDShutdownProto().doIt();
    }

    private void doIt() throws Exception
    {
        final LDConfig config = new LDConfig.Builder()
                .offline(false)
                .socketTimeoutMillis(2000)
                .connectTimeoutMillis(2000)
                .build();
        final LDClient client = new LDClient("sdk key here", config);

        Thread.sleep(10000);

        client.close();

        Thread.sleep(60000);

        // Make a threaddump or add breakpoint here.
        // Mentioned threads are still alive.
    }
}

The threads will die after ~175 seconds delay.

CVE-2018-20200 okhttp3 library security vulnerability

We are using OWASP scan for checking dependency security vulnerabilities.
java-server-sdk:4.12.0 version has shaded com.squareup.okhttp3:okhttp:3.8.1 client. Scan raises issue with CVE-2018-20200

We have strict policy for suppressing vulnerabilities.
Do you consider to update okhttp3 library in java-server-sdk in near future?

Can't add custom values of diferent type

In launchdarkly app we have a custom rule where a rule can be an int (an id) or a string (a role).

In java it's impossible through the LDUser.Builder to add a List of objects, it only allows a list of String or a list of Number, with the custom, customString and customNumber methods.

There should be a builder.customObject(String k, List vs) in order to allow operations that are allowed through js for example.

Using java client in behind a proxy

Hi

I am evaluating LaunchDarkly for the use in our Spring-Boot Services.
Unfortunately on my local machine I am behind our Corporate Proxy.
So I tried to configure the Proxy:
`
LDConfig config =new LDConfig.Builder()
.proxyHost("some.proxy.de")
.proxyPort(8080)
.proxyPassword("some.password")
.proxyUsername("some.technical.user")
.build();

    ldClient = new LDClient("<sdk-key>", config);

`

This only led to timeouts in all attempts to connect to launchdarkly.
Digging deeper into this I noticed, that the StreamProcessor, when building the EventSource does not pass on the proxy configuration from the config object to the EventSource.

I could fix this by adding the following code after line 257 in StreamProcessor.java.
.proxy(config.proxy) .proxyAuthenticator(config.proxyAuthenticator)

My question: is the proxy configuration omitted here on purpose? Is there another way to configure the LDClient behind a proxy?

Setting Trust Store location in JAVA_OPTS causes LD SDK to fail

Hello,

When referencing our own trustStore location, -Djavax.net.ssl.trustStore=/path/to/our/certs/cacerts.jks, the LD SDK throws the following error:

com.launchdarkly.client.StreamProcessor [] - Encountered EventSource error: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

Is there a way to set the cert location for the SDK? Or is there somewhere we can get the LD cert to add it ourselves?

Thank you,
Travis Meares

Typo in https://github.com/launchdarkly/java-server-sdk/blob/master/src/main/java/com/launchdarkly/sdk/server/EventBroadcasterImpl.java#L93

Is this a support request?
No

Describe the bug
Typo in https://github.com/launchdarkly/java-server-sdk/blob/master/src/main/java/com/launchdarkly/sdk/server/EventBroadcasterImpl.java#L93

Currently reads :
Loggers.MAIN.warn("Unexpected error from listener ({0}): {1}", l.getClass(), e.toString());

Should read:
Loggers.MAIN.warn("Unexpected error from listener ({}): {}", l.getClass(), e.toString());

To reproduce
Generate an exception from the listener

Expected behavior
The message should be correctly logged

Logs
[LaunchDarkly-tasks-0] WARN com.launchdarkly.sdk.server.LDClient - Unexpected error from listener ({0}): {1}

SDK version
5.0.3

Language version, developer tools
Java

OS/platform
Mac

Additional context
I think this is just a clear typo and should be fixed.

Pom dependencies that should be "compile" are "runtime"

In the process of fixing #122, it looks like we made an unintended change.

Previously: all of the SDK's dependencies were appearing in pom.xml with "compile" scope— even the ones that are bundled in the jar and shaded, and therefore should not be loaded separately. This was due to a problem in how we were configuring the Gradle Shadow plugin.

Now: the bundled dependencies are no longer in the pom, but Gson and SLF4J— which are not bundled, and must be provided by the host app— are listed with "runtime" scope. Therefore, Java build tools will not automatically include them in the classpath at compile time. You'll get a compile error if your application 1. does not explicitly pull in Gson and SLF4J in its own build configuration, and 2. does reference them in your code.

The workaround is simply to add those as explicit compile-time dependencies if they're not already there.

(This is for the default jar. The jar with the "all" classifier provides Gson and SLF4J itself, and therefore doesn't need them in the pom.)

Add capability of listening for feature flag value changes.

I'd like to create a LaunchDarkly backend for Spring Cloud Config. It's basically an integration for Spring where you look up properties the usual way in Spring, and internally Spring will ask LaunchDarkly for the value of that property.

In order to do that, I'd need to know when feature flags change to trigger a refresh event in Spring's environment.

Right now there doesn't seem to be an option of registering a listener in the client to know when things change, unless I completely overwrite a few classes? Would it be possible to add it?

Consider improving LDUser::getValueForEvaluation performance for custom attributes

For custom attributes, currently {{LDUSer::getValueForEvaluation}} checks for {{UserAttribute}} first and moves on to check "custom" attributes.

https://github.com/launchdarkly/java-client/blob/8b29441d5870ecc8b20d31d0b2fff2c5a32007de/src/main/java/com/launchdarkly/client/LDUser.java#L73

https://github.com/launchdarkly/java-client/blob/8b29441d5870ecc8b20d31d0b2fff2c5a32007de/src/main/java/com/launchdarkly/client/LDUser.java#L77

As a part of this, it always encounters "IllegalArguementException" which ends up populating the entire stacktrace (which could be 100s in size). This turns out to be very expensive depending on the number of calls.

It would be good consider, where custom attributes are checked first and then check the user attribute. This way, we do not need to pay the penalty of filling up the stack trace for every call involving custom attribute.

java.lang.NoClassDefFoundError: com/google/gson/JsonElement after updating to version 2.3.4.

We are using the LaunchDarkly Java client and when upgrading from version 2.3.3 to 2.3.4, the com.google.code.gson dependency is now missing at runtime. We receive the following error when trying to initialize the LDClient:

1) Error injecting constructor, java.lang.NoClassDefFoundError: com/google/gson/JsonElement
at com.unific.aws.config.launchdarkly.LaunchDarklyClientProvider.<init>(LaunchDarklyClientProvider.java:21)
while locating com.unific.aws.config.launchdarkly.LaunchDarklyClientProvider
at com.unific.aws.config.ConfigurationModule.configure(ConfigurationModule.java:21)
while locating com.launchdarkly.client.LDClient
for the 1st parameter of com.unific.aws.config.ConfigurationProvider.<init>(ConfigurationProvider.java:30)
while locating com.unific.aws.config.ConfigurationProvider
at com.unific.aws.config.ConfigurationModule.configure(ConfigurationModule.java:22)
while locating com.unific.aws.config.Configuration
for the 6th parameter of com.unific.graphql.services.CampaignService.<init>(CampaignService.java:68)
while locating com.unific.graphql.services.CampaignService
while locating com.unific.graphql.services.GraphQLService annotated with @com.google.inject.multibindings.Element(setName=,uniqueId=3, type=MULTIBINDER, keyType=)
at com.unific.graphql.guice.GraphQLModule.configure(GraphQLModule.java:60) (via modules: com.unific.graphql.guice.GraphQLModule -> com.google.inject.multibindings.Multibinder$RealMultibinder)
while locating java.util.Set<com.unific.graphql.services.GraphQLService>
for the 1st parameter of com.unific.graphql.guice.GraphQLSchemaProvider.<init>(GraphQLSchemaProvider.java:25)
while locating com.unific.graphql.guice.GraphQLSchemaProvider
at com.unific.graphql.guice.GraphQLModule.configure(GraphQLModule.java:34)
while locating graphql.schema.GraphQLSchema
for the 1st parameter of com.unific.graphql.guice.GraphQLProvider.<init>(GraphQLProvider.java:20)
while locating com.unific.graphql.guice.GraphQLProvider
at com.unific.graphql.guice.GraphQLModule.configure(GraphQLModule.java:35)
while locating graphql.GraphQL
for the 1st parameter of com.unific.graphql.GraphQLExecutor.<init>(GraphQLExecutor.java:22)
at com.unific.graphql.guice.GraphQLModule.configure(GraphQLModule.java:52)
while locating com.unific.graphql.GraphQLExecutor

1 error
at com.google.inject.internal.Errors.throwCreationExceptionIfErrorsExist(Errors.java:470)
at com.google.inject.internal.InternalInjectorCreator.injectDynamically(InternalInjectorCreator.java:184)
at com.google.inject.internal.InternalInjectorCreator.build(InternalInjectorCreator.java:110)
at com.google.inject.Guice.createInjector(Guice.java:99)
at com.google.inject.Guice.createInjector(Guice.java:73)
at com.google.inject.Guice.createInjector(Guice.java:62)
at com.unific.graphql.guice.GraphQLInjector.<clinit>(GraphQLInjector.java:23)
... 5 more
Caused by: java.lang.NoClassDefFoundError: com/google/gson/JsonElement
at com.unific.aws.config.launchdarkly.LaunchDarklyClientProvider.<init>(LaunchDarklyClientProvider.java:26)
at com.unific.aws.config.launchdarkly.LaunchDarklyClientProvider$$FastClassByGuice$$68d9c580.newInstance(<generated>)
at com.google.inject.internal.DefaultConstructionProxyFactory$FastClassProxy.newInstance(DefaultConstructionProxyFactory.java:89)
at com.google.inject.internal.ConstructorInjector.provision(ConstructorInjector.java:111)
at com.google.inject.internal.ConstructorInjector.construct(ConstructorInjector.java:90)
at com.google.inject.internal.ConstructorBindingImpl$Factory.get(ConstructorBindingImpl.java:268)
at com.google.inject.internal.BoundProviderFactory.get(BoundProviderFactory.java:61)
at com.google.inject.internal.ProviderToInternalFactoryAdapter$1.call(ProviderToInternalFactoryAdapter.java:46)
at com.google.inject.internal.InjectorImpl.callInContext(InjectorImpl.java:1092)
at com.google.inject.internal.ProviderToInternalFactoryAdapter.get(ProviderToInternalFactoryAdapter.java:40)
at com.google.inject.internal.SingletonScope$1.get(SingletonScope.java:194)
at com.google.inject.internal.InternalFactoryToProviderAdapter.get(InternalFactoryToProviderAdapter.java:41)
at com.google.inject.internal.SingleParameterInjector.inject(SingleParameterInjector.java:38)
at com.google.inject.internal.SingleParameterInjector.getAll(SingleParameterInjector.java:62)
at com.google.inject.internal.ConstructorInjector.provision(ConstructorInjector.java:110)
at com.google.inject.internal.ConstructorInjector.construct(ConstructorInjector.java:90)
at com.google.inject.internal.ConstructorBindingImpl$Factory.get(ConstructorBindingImpl.java:268)
at com.google.inject.internal.BoundProviderFactory.get(BoundProviderFactory.java:61)
at com.google.inject.internal.ProviderToInternalFactoryAdapter$1.call(ProviderToInternalFactoryAdapter.java:46)
at com.google.inject.internal.InjectorImpl.callInContext(InjectorImpl.java:1092)
at com.google.inject.internal.ProviderToInternalFactoryAdapter.get(ProviderToInternalFactoryAdapter.java:40)
at com.google.inject.internal.SingletonScope$1.get(SingletonScope.java:194)
at com.google.inject.internal.InternalFactoryToProviderAdapter.get(InternalFactoryToProviderAdapter.java:41)
at com.google.inject.internal.SingleParameterInjector.inject(SingleParameterInjector.java:38)
at com.google.inject.internal.SingleParameterInjector.getAll(SingleParameterInjector.java:62)
at com.google.inject.internal.ConstructorInjector.provision(ConstructorInjector.java:110)
at com.google.inject.internal.ConstructorInjector.construct(ConstructorInjector.java:90)
at com.google.inject.internal.ConstructorBindingImpl$Factory.get(ConstructorBindingImpl.java:268)
at com.google.inject.internal.FactoryProxy.get(FactoryProxy.java:56)
at com.google.inject.internal.InjectorImpl$2$1.call(InjectorImpl.java:1019)
at com.google.inject.internal.InjectorImpl.callInContext(InjectorImpl.java:1092)
at com.google.inject.internal.InjectorImpl$2.get(InjectorImpl.java:1015)
at com.google.inject.multibindings.Multibinder$RealMultibinder.get(Multibinder.java:375)
at com.google.inject.multibindings.Multibinder$RealMultibinder.get(Multibinder.java:258)
at com.google.inject.internal.ProviderInternalFactory.provision(ProviderInternalFactory.java:81)
at com.google.inject.internal.InternalFactoryToInitializableAdapter.provision(InternalFactoryToInitializableAdapter.java:53)
at com.google.inject.internal.ProviderInternalFactory.circularGet(ProviderInternalFactory.java:61)
at com.google.inject.internal.InternalFactoryToInitializableAdapter.get(InternalFactoryToInitializableAdapter.java:45)
at com.google.inject.internal.SingleParameterInjector.inject(SingleParameterInjector.java:38)
at com.google.inject.internal.SingleParameterInjector.getAll(SingleParameterInjector.java:62)
at com.google.inject.internal.ConstructorInjector.provision(ConstructorInjector.java:110)
at com.google.inject.internal.ConstructorInjector.construct(ConstructorInjector.java:90)
at com.google.inject.internal.ConstructorBindingImpl$Factory.get(ConstructorBindingImpl.java:268)
at com.google.inject.internal.BoundProviderFactory.get(BoundProviderFactory.java:61)
at com.google.inject.internal.ProviderToInternalFactoryAdapter$1.call(ProviderToInternalFactoryAdapter.java:46)
at com.google.inject.internal.InjectorImpl.callInContext(InjectorImpl.java:1092)
at com.google.inject.internal.ProviderToInternalFactoryAdapter.get(ProviderToInternalFactoryAdapter.java:40)
at com.google.inject.internal.SingletonScope$1.get(SingletonScope.java:194)
at com.google.inject.internal.InternalFactoryToProviderAdapter.get(InternalFactoryToProviderAdapter.java:41)
at com.google.inject.internal.SingleParameterInjector.inject(SingleParameterInjector.java:38)
at com.google.inject.internal.SingleParameterInjector.getAll(SingleParameterInjector.java:62)
at com.google.inject.internal.ConstructorInjector.provision(ConstructorInjector.java:110)
at com.google.inject.internal.ConstructorInjector.construct(ConstructorInjector.java:90)
at com.google.inject.internal.ConstructorBindingImpl$Factory.get(ConstructorBindingImpl.java:268)
at com.google.inject.internal.BoundProviderFactory.get(BoundProviderFactory.java:61)
at com.google.inject.internal.ProviderToInternalFactoryAdapter$1.call(ProviderToInternalFactoryAdapter.java:46)
at com.google.inject.internal.InjectorImpl.callInContext(InjectorImpl.java:1092)
at com.google.inject.internal.ProviderToInternalFactoryAdapter.get(ProviderToInternalFactoryAdapter.java:40)
at com.google.inject.internal.SingletonScope$1.get(SingletonScope.java:194)
at com.google.inject.internal.InternalFactoryToProviderAdapter.get(InternalFactoryToProviderAdapter.java:41)
at com.google.inject.internal.SingleParameterInjector.inject(SingleParameterInjector.java:38)
at com.google.inject.internal.SingleParameterInjector.getAll(SingleParameterInjector.java:62)
at com.google.inject.internal.ConstructorInjector.provision(ConstructorInjector.java:110)
at com.google.inject.internal.ConstructorInjector.construct(ConstructorInjector.java:90)
at com.google.inject.internal.ConstructorBindingImpl$Factory.get(ConstructorBindingImpl.java:268)
at com.google.inject.internal.ProviderToInternalFactoryAdapter$1.call(ProviderToInternalFactoryAdapter.java:46)
at com.google.inject.internal.InjectorImpl.callInContext(InjectorImpl.java:1092)
at com.google.inject.internal.ProviderToInternalFactoryAdapter.get(ProviderToInternalFactoryAdapter.java:40)
at com.google.inject.internal.SingletonScope$1.get(SingletonScope.java:194)
at com.google.inject.internal.InternalFactoryToProviderAdapter.get(InternalFactoryToProviderAdapter.java:41)
at com.google.inject.internal.InternalInjectorCreator$1.call(InternalInjectorCreator.java:205)
at com.google.inject.internal.InternalInjectorCreator$1.call(InternalInjectorCreator.java:199)
at com.google.inject.internal.InjectorImpl.callInContext(InjectorImpl.java:1085)
at com.google.inject.internal.InternalInjectorCreator.loadEagerSingletons(InternalInjectorCreator.java:199)
at com.google.inject.internal.InternalInjectorCreator.injectDynamically(InternalInjectorCreator.java:180)
... 10 more
Caused by: java.lang.ClassNotFoundException: com.google.gson.JsonElement
at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
... 85 more

Also, we tried including the <classifer>all</classifer> within the LaunchDarkly maven dependency config and that broke other dependencies with log4j or slf4j. In the meantime we have rolled back to version 2.3.3 of the LaunchDarkly client.

Jar shading issue with NewRelic integration in NewRelicReflector

Describe the bug
NewRelicReflector never works, because it's looking for the location of the NewRelic jar in the wrong package: com.launchdarkly.shaded.com.newrelic.api.agent.NewRelic

To reproduce
https://github.com/apottere/launchdarkly-newrelic-bug/tree/master

mvn exec:java -Dexec.mainClass="com.launchdarkly.client.DemoApplication"

Expected behavior
When running that application, I would expect to see this logged:

Used actual package: com.newrelic.api.agent.NewRelic

Instead, I see this logged:

Used shaded package: com.launchdarkly.shaded.com.newrelic.api.agent.NewRelic

SDK version
4.8.0

Language version, developer tools
Java 8

OS/platform
MacOS

Encountered EventSource error: Operation not permitted (select/poll failed)

We just began using LaunchDarkly recently for feature hiding, but we are continually getting the following error:

Encountered EventSource error: Operation not permitted (select/poll failed)
com.launchdarkly.client.StreamProcessor$1.onError(StreamProcessor.java:127)

We are running a series of microservices on AWS Lambda and this is our most common error in our logs (appears over 387k times). Please let me know if you need more details.

Lack of programmatic feedback on connection issues on service startup

Is your feature request related to a problem? Please describe.
If LaunchDarkly (or the relay) is unreachable on service startup, there isn't any feedback mechanism for the app to do anything about it (for example if we want the app to crash and page loudly).

There is a startWaitMillis() option on the builder that will attempt to block until the connection is established, but if it times out or fails, the startup process will just proceed silently. Additionally, this option causes the constructor to LDClient to block, which plays poorly with dependency injection frameworks like Guice.

Logs are generated by the SDK, but logs aren't useful to the application itself.

Describe the solution you'd like

Add a method to LDClient, like

public void awaitInitialized(Duration timeout) throws Exception {
  ...
}

This method blocks until the initial streaming connection to the backend is established. If there are connection errors, or bad HTTP status codes returned, it will throw immediately with the underlying exception (potentially after configurable retries). If the initial connection is successful, the method returns. If the initial connection takes longer than timeout, then it will throw TimeoutException.

Additionally, add a callback mechanism to LDClient to propagate subsequent connection issues, like

public void onConnectionError(... some callback interface...)

This would notify the applicable during steady state that the connection to LaunchDarkly is broken, and the application can choose what it wants to do. The argument could potentially be a com.launchdarkly.eventsource.ConnectionErrorHandler, but maybe the app shouldn't be able to decide the Action for the event source.

Describe alternatives you've considered
That's all I got.

Additional context
This is in context to a connection issue we saw that took a while to pinpoint: #183. Our services take the practice of crashing if LaunchDarkly isn't ready after a certain timeout after startup, and it'd be really useful if that crash could include why LaunchDarkly wasn't ready.

LDClient fails to initialize when used with Azul Zulu 8.46 (OpenJDK 8.0.252) and newer

LDClient fails to initialize when used with Azul Zulu 8.46 (OpenJDK 8.0.252)+ and the runtime is configured with to run in FIPS mode with BouncyCastle cryptography providers.

Describe the bug
LDClient fails to initialize when used with Azul Zulu 8.46 (OpenJDK 8.0.252) and newer. This is due to a back-ported JDK 9 fix which causes OkHttp to detect the 8 runtime as 9. This is fixed in OkHttp 3.14.8+ and 4.6.0+. See square/okhttp#5970 for more information. The java-server-sdk library includes shadowed OkHttp classes.

To reproduce
Download Azul Zulu 8.46 (OpenJDK 8.0.252) and BouncyCastle FIPS libraries(https://www.azul.com/downloads/zulu-community/?version=java-8-lts&architecture=x86-64-bit&package=jdk, https://www.bouncycastle.org/fips-java/). Put these, as well as LD java-server-sdk 4.14.1 on the class path. Configure a custom java.security.override file:

jdk.tls.disabledAlgorithms=SSLv2Hello, SSLv3, TLSv1, RC4, MD5withRSA, DH keySize < 1024, EC keySize < 224, DES, 3DES
crypto.policy=unlimited
security.provider.1=org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider C:DEFRND[HMACSHA256];ENABLE{ALL};
security.provider.2=org.bouncycastle.jsse.provider.BouncyCastleJsseProvider fips:BCFIPS
security.provider.3=com.sun.net.ssl.internal.ssl.Provider
security.provider.4=sun.security.provider.Sun
security.provider.5=com.sun.crypto.provider.SunJCE
security.provider.6=sun.security.jgss.SunProvider
security.provider.7=com.sun.security.sasl.Provider
security.provider.8=org.jcp.xml.dsig.internal.dom.XMLDSigRI
security.provider.9=sun.security.smartcardio.SunPCSC
security.provider.10=sun.security.mscapi.SunMSCAPI
securerandom.source=file:/dev/urandom
jceks.key.serialFilter = org.bouncycastle.**;java.lang.Enum;java.security.KeyRep;
java.security.KeyRep$Type;javax.crypto.spec.SecretKeySpec;!*

Run LDClientTest with Azul Zulu:
java -cp LDClientTest.class:bc-fips-1.0.1.jar:bcprov-jdk16-sahdow-1.38.jar:bctls-fips-1.0.4.jar:launchdarkly-java-server-sdk-4.14.1-patched.jar:launchdarkly-java-server-sdk-4.14.1-all.jar:. -Djava.security.properties=./java.security.override LDClientTest

import com.launchdarkly.client.LDClient;
import com.launchdarkly.client.LDUser;

class LDClientTest {
public static void main(String[] args) {
final String key = "my_sdk_key";
LDClient client = new LDClient(key);
System.out.println("Initialized: " + client.initialized());
LDUser user = new LDUser("1234");
System.out.println("Evaluated to: " + client.boolVariation("my.flag", user, true));
}
}

Expected behavior
The LDClient initializes and gets feature flag without issue.

Logs
Exception in thread "okhttp-eventsource-stream-[]-0" java.lang.AssertionError: unable to get selected protocols
at com.launchdarkly.shaded.okhttp3.internal.Util.assertionError(Util.java:504)
at com.launchdarkly.shaded.okhttp3.internal.platform.Jdk9Platform.getSelectedProtocol(Jdk9Platform.java:72)
at com.launchdarkly.shaded.okhttp3.internal.connection.RealConnection.connectTls(RealConnection.java:347)
at com.launchdarkly.shaded.okhttp3.internal.connection.RealConnection.establishProtocol(RealConnection.java:284)
at com.launchdarkly.shaded.okhttp3.internal.connection.RealConnection.connect(RealConnection.java:169)
at com.launchdarkly.shaded.okhttp3.internal.connection.StreamAllocation.findConnection(StreamAllocation.java:258)
at com.launchdarkly.shaded.okhttp3.internal.connection.StreamAllocation.findHealthyConnection(StreamAllocation.java:135)
at com.launchdarkly.shaded.okhttp3.internal.connection.StreamAllocation.newStream(StreamAllocation.java:114)
at com.launchdarkly.shaded.okhttp3.internal.connection.ConnectInterceptor.intercept(ConnectInterceptor.java:42)
at com.launchdarkly.shaded.okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:147)
at com.launchdarkly.shaded.okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:121)
at com.launchdarkly.shaded.okhttp3.internal.cache.CacheInterceptor.intercept(CacheInterceptor.java:93)
at com.launchdarkly.shaded.okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:147)
at com.launchdarkly.shaded.okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:121)
at com.launchdarkly.shaded.okhttp3.internal.http.BridgeInterceptor.intercept(BridgeInterceptor.java:93)
at com.launchdarkly.shaded.okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:147)
at com.launchdarkly.shaded.okhttp3.internal.http.RetryAndFollowUpInterceptor.intercept(RetryAndFollowUpInterceptor.java:127)
at com.launchdarkly.shaded.okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:147)
at com.launchdarkly.shaded.okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:121)
at com.launchdarkly.shaded.okhttp3.RealCall.getResponseWithInterceptorChain(RealCall.java:257)
at com.launchdarkly.shaded.okhttp3.RealCall.execute(RealCall.java:93)
at com.launchdarkly.shaded.com.launchdarkly.eventsource.EventSource.connect(EventSource.java:270)
at com.launchdarkly.shaded.com.launchdarkly.eventsource.EventSource.access$1300(EventSource.java:54)
at com.launchdarkly.shaded.com.launchdarkly.eventsource.EventSource$2.run(EventSource.java:157)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.launchdarkly.shaded.okhttp3.internal.platform.Jdk9Platform.getSelectedProtocol(Jdk9Platform.java:62)
... 25 more
Caused by: java.lang.UnsupportedOperationException
at javax.net.ssl.SSLSocket.getApplicationProtocol(SSLSocket.java:691)
... 30 more

SDK version
4.14.1, 5.0.3

Language version, developer tools
Azul Zulu 8.46 (OpenJDK 8.0.252) and newer. BouncyCastle cryptography libraries.

OS/platform
Not OS-specific. Issue affects all OS platforms which run Azul Zulu.

Additional context
Can be resolved by explicitly declaring dependency on okhttp 3.14.8 (or newer) in java-server-sdk. java-server-sdk has transitive dependency on okhttp by way of okthttp-eventsource. An alternative would be to update the okhttp dependency in okhttp-eventsource 1.11.1, then update the okhttp-eventsource dependency in java-server-sdk.

Support specifying thread pool

Is your feature request related to a problem? Please describe.
I would love to see the SDK support passing in a thread pool upon client construction so that I can more precisely tune the number of threads my application uses to make best us of my available resources

Describe the solution you'd like
An additional construction option permitting a thread pool to be passed

Describe alternatives you've considered
None

Additional context
None

TestData FlagBuilder rules seem to be erased when toggling a flag off and on again

Is this a support request?
Yes and no? I filed a support ticket since it affects our work but source seems to be SDK itself.

Describe the bug
The TestData source does not seem to persist rules after toggling a flag off and then back on again.

To reproduce
Run these added unit tests: https://github.com/ashoofly/java-server-sdk/pull/1/files.
rulesPersistAfterTogglingFlagOffAndOn() should fail.
For comparison, targetsPersistAfterTogglingFlagOffAndOn() succeeds.

Expected behavior
Existing rules should remain in place after a flag is toggled back on.

Logs

SDK version
5.2.0

Language version, developer tools
jdk1.8.0_121

OS/platform
Mac OSX Catalina

Additional context
I've also noticed during debugging that the rules field in the TestData.FlagBuilder is always null.

EvaluationException error message does not include feature flag key.

Our codebase has async requests coming in to evaluate feature flag, and we have seen some flags assuming different result type when what is set on LD, but it's very hard for us to find out the culprit flag.

com.launchdarkly.client.EvaluationException: Feature flag evaluation expected result as string type, but got non-string type.
at com.launchdarkly.client.VariationType$4.assertResultType(VariationType.java:40)
at com.launchdarkly.client.LDClient.evaluate(LDClient.java:369)
at com.launchdarkly.client.LDClient.stringVariation(LDClient.java:298)

It would be helpful if the error message actually includes the key of the flag.

Please can you use semver?

We have a process to automatically update any dependencies to the latest minor or patch version, on the assumption that won't contain breaking changes, but will bring in security fixes and additional backward-compatible features.

4.9.0 has broken this as LDUser.getValueForEvaluation now returns an LDValue.

So this is just a request/datapoint on using semver, and arguing to either have labelled this 5.0.0, or added a new method to LDUser, and deprecate the other.

Provide an unshaded SDK

Is your feature request related to a problem? Please describe.
We have a server app that uses dropwizard, jackson and whole lot of other libraries. We cannot register LaunchDarkly with our jackson's ObjectMapper because LDJackson.module() returns a shaded version of the Jackson's Module class.

Describe the solution you'd like
An SDK flavour without any shaded libraries would be welcome.

Describe alternatives you've considered

  1. Copy LDJackson class verbatim and replace references.
  2. Write config serialization ourselves.

userKeysFlushInterval field is not properly set via LDConfig.Builder

Describe the bug
userKeysFlushInterval field is not properly set via LDConfig.Builder

To reproduce
Set it to a value other than the default and observe it not take effect.

Expected behavior
LDClient uses the value I set via the builder.

Logs
If applicable, add any log output related to your problem.

SDK version
4.6.3

Language version, developer tools
Java 8

OS/platform
RHEL 7, OSX Mojave

Additional context
Offending line of code.

javax.annotation is not shaded

In consuming the launchdarkly-client jar, I have noticed that it provides javax.annotation classes. This creates a few warnings when creating a shaded jar with Maven (which warns against its overlapping with jsr305) and breaks the dependency:analyze goal.

We have the following code:

https://github.com/launchdarkly/java-client/blob/4791702a70d4d70b8dcab4283131aa09652fa998/build.gradle#L224

so this is clearly deliberate. Could this decision be explained please? I am confused as to why this is the only unshaded package in the regular jar.

Thanks!

5.x: Symbols don't resolve in Intellij in a Kotlin project

Is this a support request?
I contacted support and in the course of doing that, I tracked down the issue and found a fix. I figured it'd be best to continue here with the technical detail.

Describe the bug
Kotlin projects importing this SDK no longer resolve symbols in IntelliJ, as of version 5 of this SDK. Gradle will still build the project though.

To reproduce

  1. Setup a Kotlin application project
  2. Add this project (@ v5.x) as a dependency
  3. Attempt to use LDUser or LDClient in IntelliJ

Expected behavior
IntelliJ auto-completes and shows you symbols and then auto-imports when you select one. Instead, IntelliJ red-highlights all of your symbols and imports, despite it building properly. This all worked fine in v4.

Logs
Couldn't find helpful logs here sorry.

SDK version
5.0.2

Language version, developer tools

  1. Kotlin 1.3.72
  2. gradle 6.5.1 & 6.6

OS/platform
macOS 10.15.6

Additional context
I got a hint in the public Kotlin slack that it's related to this issue: https://youtrack.jetbrains.com/issue/KT-25709. I tried applying the suggested fix, deploying to maven local, and importing the locally-patched version. The fix worked. Here is the fix. In the shadowJar configuration in build.gradle, add this:

exclude '**/*.kotlin_metadata'
exclude '**/*.kotlin_module'
exclude '**/*.kotlin_builtins'

I'm guessing this started happening due to upgrading this library's dependencies to ones that use the Kotlin stdlib, and maybe before, those dependencies didn't do that.

InvalidCacheLoadException when redis returns null feature flag

com.google.common.cache.CacheLoader$InvalidCacheLoadException: CacheLoader returned null for key <REDACTED>
at com.google.common.cache.LocalCache$Segment.getAndRecordStats(LocalCache.java:2350) at com.google.common.cache.LocalCache$Segment.loadSync(LocalCache.java:2320)
at com.google.common.cache.LocalCache$Segment.lockedGetOrLoad(LocalCache.java:2282) 
at com.google.common.cache.LocalCache$Segment.get(LocalCache.java:2197)
at com.google.common.cache.LocalCache.get(LocalCache.java:3937)
at com.google.common.cache.LocalCache.getOrLoad(LocalCache.java:3941)
at com.google.common.cache.LocalCache$LocalLoadingCache.get(LocalCache.java:4824)
at com.google.common.cache.LocalCache$LocalLoadingCache.getUnchecked(LocalCache.java:4830)
at com.launchdarkly.client.RedisFeatureStore.get(RedisFeatureStore.java:145)
at com.launchdarkly.client.LDClient.evaluate(LDClient.java:202)
...

The client does catch the exception and log it, but it seems like a null value from the redis cache is an expected return, so it spams the logs with stacktraces

I experienced this in v1.0.1 of the client but it appears like it would be present in v2.0.0 as well

Make SSE reconnect strategy configurable, with backoff and jitter

We currently have tens of thousands of clients connecting to a service running ld-relay. If the SSE connection is closed (e.g. because of a proxy restart or network blip) all clients attempt to reconnect once per second, which hammers our ld-relay service. The reconnect strategy should be configurable, and ideally use exponential backoff with a jitter.

Differentiate between recoverable and non recoverable connections Problems

Is your feature request related to a problem? Please describe.
Currently StreamProcessor logs all connection problems as Error, even if it is a recoverable one that will result in a retry.

Describe the solution you'd like
It would be great to differentiate between the different connection errors and log only the non recoverable ones as ERROR.

Would you be open to a PR that changes that behavior?

Additional context
This is the problem space as #183.

Retrieve all variations for a flag

I needed a test to verify that the constant default in the code base (java) is compatible with the given values (json array) on the server.

Describe the solution you'd like
Create a separate method in the client which can retrieve all the variations associated with a feature key.

Describe alternatives you've considered
Currently I have to use REST endpoint to request and parse the variations my self, as the LDClient is final.

Published versions and Dependabot

Is this a support request?
Yes.

Describe the bug
While investigating a dependabot pull request to upgrade from launchdarkly-client 4.6.0 to 4.61, I've discovered a number of issues with published versions of launchdarkly-client.

Screen Shot 2019-10-31 at 9 55 07 AM

  1. 4.61 was published in January, but never removed from Maven Central (even though it appears to have been published accidentally in place of 4.6.1): https://search.maven.org/artifact/com.launchdarkly/launchdarkly-client/4.61/jar
  2. 4.6.4-4.6.6, 4.7.0, 4.7.1, 4.8.0, 4.8.1, and 4.9.0 have been released on GitHub (https://github.com/launchdarkly/java-server-sdk/releases) but not on Maven Central (https://search.maven.org/search?q=g:com.launchdarkly%20AND%20a:launchdarkly-client&core=gav)

To reproduce
Not entirely sure, but it's possible if you have a dependabot-managed repository with a 4.6.0 dependency that dependabot will still see 4.61 and get stuck trying to upgrade you to that version.

Expected behavior
4.61 probably needs to be removed from Maven Central. I have no idea at the moment how to un-stick dependabot without having it stop managing launchdarkly-client altogether until 5.x is released.

Logs
None.

SDK version
4.6.0…for now.

Language version, developer tools
Java.

OS/platform
Ubuntu/MacOS.

Additional context
That's it.

Are we expected to specify the project along with SDK key to get toggle values?

We have the following structure of feature flags in our dashboard

Project: Default
    Environment: Production
       Feature Flag: test-toggle, default value False
    Environment: Test
       Feature Flag: test-toggle, default value False

Project: MyProject
    Environment: Production
       Feature Flag: test-toggle, default value False
    Environment: Test
       Feature Flag: test-toggle, default value False

In my client SDK, I used the sdk key specified for MyProject.Test enviroment. When I check for the presence of toggle "test-toggle", I get the value of false. But when I use the sdk key specified for the default project Test environment to check for the presence of toggle "test-toggle", I get the value of true.

Are we expected to specify the project value along with the sdk key

 new LDClient("sdk-key")
     . launchDarklyClient.boolVariation("test-toggle", user, false);

Please help!

Thanks
K & S

Better LDUser equality

I have a class that wraps LDClient usage. I want to be able to write tests for this class that mock out LDUserInterface::boolVariation and LDUserInterface::allFlags using Mockito. However, there's no way to set up an argument matcher against LDUser because a) it doesn't implement a useful equals method and b) it doesn't expose any of its state.

Sample code:

LDUser expectedUser = new LDUser.Builder("asdf").custom("foo", 1).build();
when(ldClient.allFlags(eq(expectedUser)).thenReturn(true);

Record exception in evaluation error

Is your feature request related to a problem? Please describe.
When an evaluation fails due to an exception (like a bug in this library), the only thing surfaced to the caller via the *Detail methods is that an exception occurred. There is a separate log statement by the library that records the Java type of the exception, with no stacktrace. The stacktrace can be logged by enabling debug logs, which requires a server restart. Enabling debug logs isn't really feasible on production at our volume, but sometimes these bugs are only easily reproducible on production.

Describe the solution you'd like

EvaluationReason should contain the full exception that caused the failure. It can be in a new subclass of EvaluationReason

Describe alternatives you've considered
... or just another method on EvaluationReason.Error. Or, an option to invoke the get*() methods that allow the exception to propagate up rather than being swallowed. We prefer to handle the exceptions ourselves, and have been using the *Detail() methods to simulate it, but unfortunately they don't contain the stacktrace.

Additional context
We are seeing an NPE coming from this library in production, and debugging it has not been easy. (This is the real bug I'd like to fix, but until there's meaningful debugging information filing a bug probably won't be helpful).

Front-end flags from the Java SDK

Passing this issue along from a customer:

I'm reading the docs for the Java (https://docs.launchdarkly.com/docs/java-sdk-reference) and JavaScript (https://docs.launchdarkly.com/docs/js-sdk-reference), where it's said I can bootstrap the client-side SDK by providing the FeatureFlags loaded by the back-end.

However, the back-end seems to only have a method like ldClient.allFlags(), which loads all FeatureFlags, not only those we marked on the UI as front-end ones.

My question is: how can we filter out the FFs so we can print on the page only the flags relevant to the front-end?
From the FeatureFlag class it seems this information is currently only internal?

Fail to read from feature store cache prior to initialization

When a connection issue to launch darkly's servers is simulated for a new uninitialized LDClient and a persistent feature store (redis) is supplied, the expectation is that the feature store cache will be used to evaluate the flags, however in my tests it appears that prior to initialization the feature store cache is not consulted and the hardcoded default value is returned instead. This greatly reduces the effectiveness of a persistent feature store in the event that launch darkly's servers are down for an extended period of time. In such a scenario we would not be able to restart our servers because doing so would put our LDClients in an uninitialized state and our persistent cache would be useless. This also seems to contradict the documentation:

https://support.launchdarkly.com/hc/en-us/articles/115002374068-Do-you-support-Redis-caching-

which states:

Our SDKs can be configured to use Redis as a persistent store, so even if an initial connection to LaunchDarkly fails, the last flag configurations will be used instead of the fallbacks. 

I believe the source of the problem is in this bit of code from LDClient.class:236:

            if (!this.initialized()) {
                logger.warn("Evaluation called before Client has been initialized for feature flag " + featureKey + "; returning default value");
                this.sendFlagRequestEvent(featureKey, user, defaultValue, defaultValue, (Integer)null);
                return defaultValue;
            } else {

As you can see, if an initial connection is not established; the featureStore is never consulted.

Classpath FeatureStore for offline use

Is your feature request related to a problem? Please describe.
Would like to run component tests of dockerized springboot app in offline mode.

Describe the solution you'd like
FeatureStore (or FeatureStoreFactory) implementation that would allow to use file already stored in jar.

Describe alternatives you've considered
FeatureStore (or FeatureStoreFactory implementation that would allow to use provided InputStream for data source.

Additional context
I wasn't able to find a way to use flag configuration file stored in boot jar.

simplified version of my code:

@Autowired
private ResourceLoader resourceLoader;

FileDataSourceBuilder fileSource = FileData.dataSource()
    .filePaths(resourceLoader.getResource("classpath:ldConfig-ct.json").getURI().getRawPath())
    .autoUpdate(false);

Builder config = new Builder()
    .dataSource(fileSource)
    .events(Components.noEvents())
    .build();

I've been getting bean creation exception with below exception as a reason:

Caused by: java.lang.NullPointerException: null
	at java.base/sun.nio.fs.UnixPath.normalizeAndCheck(UnixPath.java:75) ~[na:na]
	at java.base/sun.nio.fs.UnixPath.<init>(UnixPath.java:69) ~[na:na]
	at java.base/sun.nio.fs.UnixFileSystem.getPath(UnixFileSystem.java:280) ~[na:na]
	at java.base/java.nio.file.Path.of(Path.java:147) ~[na:na]
	at java.base/java.nio.file.Paths.get(Paths.java:69) ~[na:na]
	at com.launchdarkly.client.integrations.FileDataSourceBuilder.filePaths(FileDataSourceBuilder.java:40) ~[launchdarkly-java-server-sdk-4.13.0.jar!/:4.13.0]

[com.launchdarkly.client.LDClient] should not WARN about unknown feature flags in test mode.

When a feature flag cannot be found, the client logs at the WARN level:

Unknown feature flag example-feature-flag-key; returning default value

We find it useful in test mode to rely on the default value for features, and only enable/disable features as required. This means we don't have to configure our application to load a default flag list into the client.

The resulting logging statements are painful, and we would like if they could be suppressed in test mode.

TestFeatureStore usage

In the context of some unit tests, I'm using an LDClient configured in offline mode along with a TestFeatureStore that I'm using to stub out values of feature flags. This allows me to completely turn a flag on or off, but is there any way to make that more granular? Specifically, I'm setting some custom attributes on the LDUser, and the actual Feature Toggle when deployed has rules set to look at them, but is there any way to achieve that same effect with TestFeatureStore?

Also, I noticed in the Java docs that TestFeatureStore is deprecated with the hints of a file-based test fixture coming... any update on that?

Thanks

LDClient is failed to initialised with below mentioned error

LDClient is failed to initialised with below mentioned error, not sure what is missing here, as the error is not really helpful here, this is a blocker for us.

Is this a support request?
This issue tracker is maintained by LaunchDarkly SDK developers and is intended for feedback on the SDK code. If you're not sure whether the problem you are having is specifically related to the SDK, or to the LaunchDarkly service overall, it may be more appropriate to contact the LaunchDarkly support team; they can help to investigate the problem and will consult the SDK team if necessary. You can submit a support request by going here and clicking "submit a request", or by emailing [email protected].

Note that issues filed on this issue tracker are publicly accessible. Do not provide any private account information on your issues. If your problem is specific to your account, you should submit a support request as described above.

Describe the bug
A clear and concise description of what the bug is.

To reproduce
Steps to reproduce the behavior.

Expected behavior
A clear and concise description of what you expected to happen.

Logs
20/11/10 16:38:14 INFO FeatureFlagProvider: Revert all boolean flags [false], Revert specific boolean flags [null] 20/11/10 16:38:14 INFO LDClient: Enabling streaming API 20/11/10 16:38:14 INFO EventSource: Starting EventSource client using URI: https://stream.launchdarkly.com/all 20/11/10 16:38:14 INFO LDClient: Waiting up to 5000 milliseconds for LaunchDarkly client to start... 20/11/10 16:38:15 INFO EventSource: Connected to Event Source stream. 20/11/10 16:38:15 ERROR StreamProcessor: LaunchDarkly service request failed or received invalid data: com.launchdarkly.client.StreamProcessor$StreamInputException: com.launchdarkly.client.interfaces.SerializationException: java.lang.RuntimeException: Failed to invoke public com.launchdarkly.client.value.LDValue() with no args 20/11/10 16:38:15 INFO EventSource: Waiting 1286 milliseconds before reconnecting... 20/11/10 16:38:16 INFO EventSource: Connected to Event Source stream. 20/11/10 16:38:16 ERROR StreamProcessor: LaunchDarkly service request failed or received invalid data: com.launchdarkly.client.StreamProcessor$StreamInputException: com.launchdarkly.client.interfaces.SerializationException: java.lang.RuntimeException: Failed to invoke public com.launchdarkly.client.value.LDValue() with no args 20/11/10 16:38:16 INFO EventSource: Waiting 3095 milliseconds before reconnecting... 20/11/10 16:38:19 ERROR LDClient: Timeout encountered waiting for LaunchDarkly client initialization 20/11/10 16:38:19 WARN LDClient: LaunchDarkly client was not successfully initialized

SDK version
4.14.0

Language version, developer tools
Java 8

OS/platform
Ubuntu 16.04

Additional context
Add any other context about the problem here.

Maven artifact has transitive dependencies on shadowed jar's

The POM file for com.launchdarkly:launchdarkly-client:3.0.2 has required dependencies on all the jars it shadows, which causes them to be included in the classpath of the project.

Not only does this seem to defeat the purpose of shadowing in the first place, but also winds up causing dependency issues with spring-data-redis 1.7.X (since it is compiled against Jedis 2.8.X and is binary incompatible with 2.9.X).

Users Not created unless client connection closed, so does this means do we need to close connection for each request

user not get created:
import java.io.IOException;

import com.launchdarkly.client.FeatureFlagsState;
import com.launchdarkly.client.LDClient;
import com.launchdarkly.client.LDUser;

public class FeatureSample {
private static final String YOUR_SDK_KEY = "sdk-7799b0db-8d76-480b-a072-2073134ed1b3";
private static LDUser user;
private static final LDClient ldClient = new LDClient(YOUR_SDK_KEY);

public static void main(final String[] args) throws IOException {
	long startTime = System.currentTimeMillis();
	user = new LDUser.Builder("default").email("[email protected]").custom("deviceType", "YL0008787782").build();
	validateFetaureFlags(user);
	displayFeatureFlags(user);
	long stopTime = System.currentTimeMillis();
	long elapsedTime = stopTime - startTime;
	System.out.println(elapsedTime);
	//ldClient.close();
}

private static void validateFetaureFlags(final LDUser user) {
	boolean showFeature = ldClient.boolVariation("offers", user, false);
	System.out.println(showFeature);
}

private static void displayFeatureFlags(final LDUser user) {
	FeatureFlagsState state = ldClient.allFlagsState(user);
	System.out.println(state.toValuesMap().toString());
}

}

the user gets created:
import java.io.IOException;

import com.launchdarkly.client.FeatureFlagsState;
import com.launchdarkly.client.LDClient;
import com.launchdarkly.client.LDUser;

public class FeatureSample {
private static final String YOUR_SDK_KEY = "sdk-7799b0db-8d76-480b-a072-2073134ed1b3";
private static LDUser user;
private static final LDClient ldClient = new LDClient(YOUR_SDK_KEY);

public static void main(final String[] args) throws IOException {
	long startTime = System.currentTimeMillis();
	user = new LDUser.Builder("default").email("[email protected]").custom("deviceType", "YL0008787782").build();
	validateFetaureFlags(user);
	displayFeatureFlags(user);
	long stopTime = System.currentTimeMillis();
	long elapsedTime = stopTime - startTime;
	System.out.println(elapsedTime);
	ldClient.close();
}

private static void validateFetaureFlags(final LDUser user) {
	boolean showFeature = ldClient.boolVariation("offers", user, false);
	System.out.println(showFeature);
}

private static void displayFeatureFlags(final LDUser user) {
	FeatureFlagsState state = ldClient.allFlagsState(user);
	System.out.println(state.toValuesMap().toString());
}

}

gson should be excluded from shading

In the launchdarkly client, all dependencies are shaded and relocated except for slf4j and gson. slf4j is not shaded (so will be required at runtime), however gson is shaded and not relocated. This will break dependency resolution and potentially lead to multiple versions of GSON on the classpath (for example, if an application uses launchdarkly-client and gson 2.8.0), both GSON 2.7 and 2.8.0 will be on the classpath at the same location.

If gson is not relocated when shading, it would be better to exclude it from shading and require it to be pulled in at runtime (i.e. add it here: https://github.com/launchdarkly/java-client/blob/8c381c8820d5e77d049eb6b57974182de611fc5e/build.gradle#L102). This would allow dependency resolution to work to pull in the appropriate version (and not break tools like https://maven.apache.org/enforcer/enforcer-rules/requireUpperBoundDeps.html).

'matches' operator only matches on the full string of a user value

Given a user like this:

new LDUser.Builder("key").custom("greeting", "hello world")

And a flag target using the 'matches' operator with a regex that should match a part of the value: hello.*orl

The user should match the targeting rule, but does not. In other LaunchDarkly SDKs, the user does match this rule.

timeout error logged as error

The following is logged at ERROR level and polluting our logs. If the error can be recovered from we probably shouldn't log it as error.

Encountered EventSource error: timeout

Add the ability to check for the presence of a flag in the Java client

It would be great if we could use the java client to detect if a flag exists.

We are currently require that flags contain a certain prefix. This is baked into our code. We would now like to relax this restriction and remove the prefix.

eg. product.some.flag changes to some.flag

Having the ability to query if a flag exists on the client would allow us to seamlessly change our flag key structure to remove the now redundant product prefix without having to change all the existing flag keys. We would simply query the old flag if we detected that the new flag isn't known to the client.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.