playframework / play-ws

Standalone Play WS, an async HTTP client with fluent API
https://www.playframework.com/documentation/latest/JavaWS
Apache License 2.0
223 stars 87 forks source link

Binary incompatibility between v2.1.11 and v2.2.x due to AhcWSClientConfig #845

Open cptwunderlich opened 8 months ago

cptwunderlich commented 8 months ago

I'm upgrading our services from Play 2.8 to 2.9 (and play-ws 2.2.5). A service has a dependency on another packages, which provides a client (which uses play-ws). That dependency is still on Play 2.8 / play-ws 2.1.11.

There are no eviction warnings or binary incompatibility errors, but I get an Exception at runtime:

java.lang.NoSuchMethodError 'int play.api.libs.ws.ahc.AhcWSClientConfig$.apply$default$6()'

We create an instance of AhcWSClientConfig directly, like so:

AhcWSClientConfig(
    WSClientConfig(connectionTimeout = settings.http.connectionTimeout, requestTimeout = settings.http.requestTimeout)
  )

Unfortunately, a new field was added to that case class a while ago. Even though there are default values provided, this is not binary compatible. (AhcConfig in 2.1.11 vs AhcConfig in 2.2.5 )

It would be great to get a compiler error, instead of a runtime error. I am not sure how to achieve that though. Not sure if versionScheme could be used, since in this case, the minor version increase is a binary incompatible version. Evolving case classes in a binary compatible way is an issue in general.

While I'd like a solution for my immediate problem (i.e., a build failure would be nice to catch this in CI), I also wonder how to avoid this in the future.

mkurz commented 7 months ago

versionScheme might be an approach. However, in general I see that more as a "problem" of Scala and not play-ws. On our side we can just take more care to not break code (which we can not always avoid) or start working with versionScheme settings, which we do not do right now. In general I would advice to always make sure your dependencies makes use of the same major/minor releases like you main lib/app does. Patch releases should not break in general.

cptwunderlich commented 7 months ago

Yes, I agree, that this is a general Scala issue. Things like versionScheme are workarounds. The problem is, that tools like scala-steward can upgrade your dependencies without you noticing if you didn't pin them and then your service fails at runtime. An integration test would have to actually use the client to trigger this exception. Unfortunately, that makes things more complicated, so we just mock the client.

So, yes, keeping all versions the same is definitely the way to go, but difficult if you have many services, automatic dependency updates and start updating your services gradually. Bc. incompatible versions can sneak in and it's hard to find that before running them without something like versionScheme.