<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Kaoru</title>
    <description>The latest articles on Forem by Kaoru (@devkaoru).</description>
    <link>https://forem.com/devkaoru</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F93818%2F303f93be-8430-4f46-aaf9-4be71df7ba66.jpg</url>
      <title>Forem: Kaoru</title>
      <link>https://forem.com/devkaoru</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/devkaoru"/>
    <language>en</language>
    <item>
      <title>Dependency Hell</title>
      <dc:creator>Kaoru</dc:creator>
      <pubDate>Mon, 02 Dec 2019 04:08:01 +0000</pubDate>
      <link>https://forem.com/devkaoru/dependency-hell-3535</link>
      <guid>https://forem.com/devkaoru/dependency-hell-3535</guid>
      <description>&lt;p&gt;Resolving dependency conflicts is not fun. I personally have not had to pleasure of dealing with conflicts until more recently. &lt;code&gt;Bundler&lt;/code&gt; and the entire Ruby community does an amazing job making sure I didn't have to. In the JVM world, things are less clear. For example, with SBT, the latest version of a dependency always wins, but if the latest version is higher by a major version, things will most likely break. SBT won't tell you when it replaces dependencies, you have to ask! I covered more details of the strange behavior in a &lt;a href="https://dev.to/devkaoru/surprising-behaviors-of-sbt-1mp9"&gt;previous post&lt;/a&gt;. What I didn't go over is how we can get into this situation in the first place.&lt;/p&gt;

&lt;h1&gt;
  
  
  Setting the stage
&lt;/h1&gt;

&lt;p&gt;The best way I learn is by doing, tweaking things, and breaking it, then fixing it...maybe. Let's come up with a contrived example (follow along by cloning &lt;a href="https://github.com/KaoruDev/jvm_dependency_hell"&gt;this repo&lt;/a&gt;). Below we have 3 files describing classes depending on each other Life &amp;gt; Person &amp;gt; Cat.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// com/kaoruk/Life.java =============================&lt;/span&gt;
&lt;span class="kn"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;com.kaoruk&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.kaoruk.Person&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Life&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;[])&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Person&lt;/span&gt; &lt;span class="n"&gt;person&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Person&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Kaoru"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;person&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;adoptCat&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;person&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sayHello&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;


&lt;span class="c1"&gt;// com/kaoruk/Person.java =============================&lt;/span&gt;
&lt;span class="kn"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;com.kaoruk&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.kaoruk.Cat&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.util.Objects&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Person&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Cat&lt;/span&gt; &lt;span class="n"&gt;cat&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;Person&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;adoptCat&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Cat&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;sayHello&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isNull&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cat&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;name&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;": Life has no meaning without a cat"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;name&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;": Oh hai, I haz kitty! "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;cat&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sayHello&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;


&lt;span class="c1"&gt;// com/kaoruk/Cat.java =============================&lt;/span&gt;
&lt;span class="kn"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;com.kaoruk&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Cat&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;sayHello&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"meow!"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now let's compile each file and place them in a jar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;

&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt;

&lt;span class="nb"&gt;mkdir &lt;/span&gt;jars &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true

echo&lt;/span&gt; &lt;span class="s2"&gt;"Compling Cat..."&lt;/span&gt;
javac com/kaoruk/Cat.java
jar &lt;span class="nt"&gt;-cvf&lt;/span&gt; jars/Cat.1.0.0.jar com/kaoruk/Cat.class
&lt;span class="nb"&gt;rm &lt;/span&gt;com/kaoruk/Cat.class

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Compling Person..."&lt;/span&gt;
javac &lt;span class="nt"&gt;-cp&lt;/span&gt; jars/Cat.1.0.0.jar com/kaoruk/Person.java
jar &lt;span class="nt"&gt;-cvf&lt;/span&gt; jars/Person.1.0.0.jar com/kaoruk/Person.class
&lt;span class="nb"&gt;rm &lt;/span&gt;com/kaoruk/Person.class

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Compling Life..."&lt;/span&gt;
javac &lt;span class="nt"&gt;-cp&lt;/span&gt; jars/Person.1.0.0.jar com/kaoruk/Life.java
jar &lt;span class="nt"&gt;-cvf&lt;/span&gt; jars/Life.1.0.0.jar com/kaoruk/Life.class
&lt;span class="nb"&gt;rm &lt;/span&gt;com/kaoruk/Life.class
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;If all goes well we should be able to run Life:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;java &lt;span class="nt"&gt;-cp&lt;/span&gt; jars/Life.1.0.0.jar:jars/Person.1.0.0.jar:jars/Cat.1.0.0.jar com.kaoruk.Life

Kaoru: Life has no meaning without a &lt;span class="nb"&gt;cat&lt;/span&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;java &lt;span class="nt"&gt;-cp&lt;/span&gt; jars/Life.1.0.0.jar:jars/Person.1.0.0.jar:jars/Cat.1.0.0.jar com.kaoruk.Life Foo

Kaoru: Oh hai, I haz kitty! meow!
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Great! Okay so let's say we want to teach our &lt;code&gt;Cat&lt;/code&gt; how to say "Can I haz job?" but also we decide that the method name "sayHello" doesn't really accurately portray the message. We change &lt;code&gt;Cat.java&lt;/code&gt; look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// com/kaoruk/Cat.java =============================&lt;/span&gt;
&lt;span class="kn"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;com.kaoruk&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Cat&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;beg&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"Can I haz job?"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Great, let's compile it, replace &lt;code&gt;Cat.1.0.0.jar&lt;/code&gt; and run it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;javac com/kaoruk/Cat.java
&lt;span class="nv"&gt;$ &lt;/span&gt;jar &lt;span class="nt"&gt;-cvf&lt;/span&gt; jars/Cat.2.0.0.jar com/kaoruk/Cat.class

&lt;span class="nv"&gt;$ &lt;/span&gt;java &lt;span class="nt"&gt;-cp&lt;/span&gt; jars/Life.1.0.0.jar:jars/Person.1.0.0.jar:jars/Cat.2.0.0.jar com.kaoruk.Life

Kaoru: Life has no meaning without a &lt;span class="nb"&gt;cat&lt;/span&gt;


&lt;span class="nv"&gt;$ &lt;/span&gt;java &lt;span class="nt"&gt;-cp&lt;/span&gt; jars/Life.1.0.0.jar:jars/Person.1.0.0.jar:jars/Cat.2.0.0.jar com.kaoruk.Life foo

Exception &lt;span class="k"&gt;in &lt;/span&gt;thread &lt;span class="s2"&gt;"main"&lt;/span&gt; java.lang.NoSuchMethodError: com.kaoruk.Cat.sayHello&lt;span class="o"&gt;()&lt;/span&gt;Ljava/lang/String&lt;span class="p"&gt;;&lt;/span&gt;
    at com.kaoruk.Person.sayHello&lt;span class="o"&gt;(&lt;/span&gt;Person.java:22&lt;span class="o"&gt;)&lt;/span&gt;
    at com.kaoruk.Life.main&lt;span class="o"&gt;(&lt;/span&gt;Life.java:11&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Oh hai, dependency hell!&lt;/p&gt;

&lt;h1&gt;
  
  
  Detection is difficult
&lt;/h1&gt;

&lt;p&gt;You might be thinking to yourself, well duh &lt;code&gt;Life&lt;/code&gt; is broken because you updated a method in &lt;code&gt;Cat&lt;/code&gt; and replaced it with a new JAR. This is &lt;em&gt;exactly&lt;/em&gt; what build tools do! To detect the problem above, &lt;em&gt;before&lt;/em&gt; running the code, we would need to &lt;em&gt;recompile&lt;/em&gt; &lt;code&gt;Person.1.0.0.jar&lt;/code&gt;. But more importantly, we would need to recompile it using &lt;code&gt;Cat.2.0.0.jar&lt;/code&gt;, ignoring its explicit reliance on &lt;code&gt;Cat.1.0.0.jar&lt;/code&gt;. As a build tool author, not that I am one, I would imagine my users would not expect the tool to recompile the entire world, because it would take too long. &lt;/p&gt;

&lt;p&gt;The logic to recompile the entire world is no easy task. First, you'd have to list all dependencies and transitive dependencies, override dependencies if there is a higher version, create a graph, find the leaves, and the compile JARs all the way back. I'm not even sure this would work, I'm just spitballing!&lt;/p&gt;

&lt;h1&gt;
  
  
  Tests are not thou Savior
&lt;/h1&gt;

&lt;p&gt;In our example, if our tests only cover running the &lt;code&gt;main&lt;/code&gt; method without an argument, we wouldn't catch this bug, one of our poor users will. So that means we have to have tests for every single branch of our code? Yes, regardless if it doesn't work in this scenario! Having a test for every branch might not work because you'd also have to test every branch of your dependencies and transitive dependencies! In our example, if we depended on &lt;code&gt;Life&lt;/code&gt;, our tests probably would not have hit the bug, because why would we test every branch of &lt;code&gt;Life&lt;/code&gt;?&lt;/p&gt;

&lt;h1&gt;
  
  
  It ain't so bad...right?
&lt;/h1&gt;

&lt;p&gt;Yes, &lt;em&gt;if&lt;/em&gt; we follow a standard. In this case, the standard is semantic versioning, or &lt;a href="https://semver.org/"&gt;&lt;code&gt;semver&lt;/code&gt;&lt;/a&gt;. If a library's major version has changed, we know that something will break, we don't have to go digging into the source code of the library or every dependee of the library. Unfortunately, &lt;em&gt;not all&lt;/em&gt; library maintainers follow &lt;code&gt;semver&lt;/code&gt;. For example, Scala Akka, does not follow this pattern. Their minor versions introduce breaking changes that are incompatible with older minor versions. The reality is we have to keep track of which library follows &lt;code&gt;semver&lt;/code&gt; and which follows a different pattern.&lt;/p&gt;

&lt;h1&gt;
  
  
  Okay so what can we do about it?
&lt;/h1&gt;

&lt;p&gt;&lt;code&gt;Bundler&lt;/code&gt; solves this problem by allowing engineers to specify an acceptable range. For example, &lt;code&gt;gem 'thin', '~&amp;gt; 1.1'&lt;/code&gt; means that &lt;code&gt;Bundler&lt;/code&gt; is allowed to install newer versions of &lt;code&gt;thin&lt;/code&gt; &lt;code&gt;&amp;lt; 2.0&lt;/code&gt;. If I bring in a &lt;code&gt;gem&lt;/code&gt; which requires &lt;code&gt;thin 2.0&lt;/code&gt;, &lt;code&gt;Bundler&lt;/code&gt; throws an exception forcing me to deal with the problem. &lt;code&gt;Maven&lt;/code&gt; also has this &lt;a href="https://cwiki.apache.org/confluence/display/MAVENOLD/Dependency+Mediation+and+Conflict+Resolution#DependencyMediationandConflictResolution-DependencyVersionRanges"&gt;functionality&lt;/a&gt;, so Coursier shouldn't be too far behind? But it still doesn't solve the problem if library maintainers ignore &lt;code&gt;semver&lt;/code&gt;. So I'm not entirely sure there's a solid solution to this problem yet. I think &lt;code&gt;Bazel&lt;/code&gt;, &lt;a href="https://github.com/bazelbuild/rules_jvm_external"&gt;&lt;code&gt;rules_jvm_external&lt;/code&gt;&lt;/a&gt; to be specific, at least, makes a step towards the right direction informing us when there is a conflict.&lt;/p&gt;

</description>
      <category>java</category>
      <category>dependency</category>
      <category>bazel</category>
      <category>sbt</category>
    </item>
    <item>
      <title>Surprising behaviors of SBT</title>
      <dc:creator>Kaoru</dc:creator>
      <pubDate>Sun, 17 Nov 2019 23:49:32 +0000</pubDate>
      <link>https://forem.com/devkaoru/surprising-behaviors-of-sbt-1mp9</link>
      <guid>https://forem.com/devkaoru/surprising-behaviors-of-sbt-1mp9</guid>
      <description>&lt;p&gt;I recently learned about an interesting behavior of SBT while working on migrating my company's monorepo from &lt;a href="https://www.scala-sbt.org/"&gt;SBT&lt;/a&gt; to &lt;a href="https://bazel.build"&gt;Bazel&lt;/a&gt;. Why we're migrating deserves a blog post of its own (coming soon). The fundamental problem is: what does your build tool do when it encounters two versions of the same library? The problem may seem trivial at face value, but consider the fact that libraries do not exist in isolation. Resolving conflicting library versions becomes exponentially worse when taking into consideration transitive dependencies. &lt;/p&gt;

&lt;h3&gt;
  
  
  Helpful definitions
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;direct dependency&lt;/code&gt;: a dependency your project has on an external library.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;transitive dependency&lt;/code&gt;: a dependency which your direct dependency depends on. For example, I depend on my cats for unlimited cuddle time, my cats depend on cat food. Thus, I have a &lt;code&gt;transitive dependency&lt;/code&gt; on cat food. &lt;/li&gt;
&lt;li&gt;
&lt;code&gt;compile-time&lt;/code&gt;: an environment which a program, the compiler (i.e. &lt;code&gt;javac&lt;/code&gt; or &lt;code&gt;scalac&lt;/code&gt;), run to convert your human-readable code into java byte code.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;runtime&lt;/code&gt;: the environment which your java-byte code runs in.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Latest and greatest
&lt;/h3&gt;

&lt;p&gt;The default behavior of SBT, version 1.3.0, is to defer to Coursier, the library which manages dependencies for your project. To be honest, &lt;a href="https://www.scala-sbt.org/release/docs/Library-Management.html#Automatic+Dependency+Management"&gt;SBT's documentation&lt;/a&gt; leaves you alone in a desert of the internet to understand the actual behavior. I found this Scala &lt;a href="https://www.scala-lang.org/2019/10/17/dependency-management.html"&gt;blog post&lt;/a&gt; which sheds light on how to control the behavior, but again does not really go into detail on what the actual behavior is. Eugene Yokota, a maintainer of SBT, thankfully, explained in detail what the behavior is in this &lt;a href="http://eed3si9n.com/dependency-resolver-semantics"&gt;blog post&lt;/a&gt;. The TLDR: Coursier will choose the latest version. Good enough right?&lt;/p&gt;

&lt;h3&gt;
  
  
  Devil in the detail
&lt;/h3&gt;

&lt;p&gt;In this &lt;a href="https://github.com/KaoruDev/learning_sbt/tree/kaoru/conflict-dep"&gt;Github repo&lt;/a&gt; we have a &lt;code&gt;core&lt;/code&gt; library is using &lt;code&gt;CheckedFuture&lt;/code&gt;, an &lt;code&gt;interface&lt;/code&gt; in &lt;code&gt;com.google.guava::guava27.1-jre&lt;/code&gt;. A project, &lt;code&gt;hello&lt;/code&gt; is using &lt;code&gt;core&lt;/code&gt; and pulling in &lt;code&gt;com.google.guava:guava:28.0-jre&lt;/code&gt;. You might be thinking, "well, obviously, the compiler will catch this and throw an error because &lt;code&gt;core&lt;/code&gt; is using a class which was removed in &lt;code&gt;com.google.guava:guava:28.0.0&lt;/code&gt;", and you'd be wrong. &lt;/p&gt;

&lt;p&gt;Why does this happen? I assume when SBT runs &lt;code&gt;scalac&lt;/code&gt; to compile &lt;code&gt;core&lt;/code&gt;, it will include all of the necessary jars in the classpath to compile &lt;code&gt;core&lt;/code&gt; correctly. Once &lt;code&gt;core&lt;/code&gt; is compiled correctly, SBT will then move to &lt;code&gt;hello&lt;/code&gt;. When compiling &lt;code&gt;hello&lt;/code&gt; SBT won't recompile &lt;code&gt;core&lt;/code&gt;, thus avoiding the &lt;code&gt;compile-time&lt;/code&gt; guard to make sure &lt;code&gt;CheckedFuture&lt;/code&gt; is available in the classpath. This example is similar to how &lt;code&gt;Bazel&lt;/code&gt; behaves, so there's no difference there. I can only speculate the reason this happens is to avoid having to compile the entire world. The result, your code will break during &lt;code&gt;runtime&lt;/code&gt;, aka Production. &lt;em&gt;Hopefully&lt;/em&gt; you'll catch the error when running tests, otherwise, you'll find &lt;code&gt;NoClassDefFoundError&lt;/code&gt; or &lt;code&gt;NoSuchMethodError&lt;/code&gt; in your logs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Problems with Abstraction
&lt;/h3&gt;

&lt;p&gt;SBT and Coursier basically deal and hide version conflicts from you, the SBT user. While, yes you can control the rules which it determines how to choose dependency versions, the tool itself does not allow you to detect conflicts by default nor does it give you the proper mechanisms to resolve them. What's the result? Your system crashes or throws a 5xx caused by a &lt;code&gt;NoSuchMethodError&lt;/code&gt;. &lt;code&gt;NoSuchMethodError&lt;/code&gt; inherits from &lt;code&gt;Error&lt;/code&gt; which according to the &lt;a href="https://docs.oracle.com/javase/8/docs/api/java/lang/Error.html"&gt;Java documentation&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;...indicates serious problems that a reasonable application should not try to catch.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Bazel, the clearly opinionated
&lt;/h3&gt;

&lt;p&gt;Bazel aims to work with monorepo giving it the freedom to have opinions on certain things. One opinion is to make sure there is only one version of an external library. Bazel forces..err asks you to list all your dependencies in one logical location, which Bazel calls the repository. From the repository, you will then tell Bazel which dependencies (without version) your project will use. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;java_library&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"core"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;dependencies&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s"&gt;"@maven//com_google_guava_guava"&lt;/span&gt; &lt;span class="c1"&gt;# Note the omission of the version.
&lt;/span&gt;  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;When Bazel detects conflicts due to either direct or transitive dependencies it will, by default, choose the highest version. Here's the difference though: &lt;em&gt;Bazel will then place the highest version of the jar when it compiles all projects.&lt;/em&gt; Thus avoiding the issue we saw with SBT, as we won't be able to compile the &lt;code&gt;core&lt;/code&gt; project. Problem solved, let's go home.&lt;/p&gt;

&lt;h3&gt;
  
  
  Well actually...
&lt;/h3&gt;

&lt;p&gt;Unfortunately, this does not solve all the issues you might see. Specifically, Bazel does not solve the problem when two transitive dependencies are using two different versions of the same library. Let's break this down. Say you have a project which uses &lt;a href="https://mvnrepository.com/artifact/io.grpc/grpc-protobuf/1.25.0"&gt;&lt;code&gt;io.grpc:grpc-protobuf:1.25.0&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://mvnrepository.com/artifact/com.fasterxml.jackson.datatype/jackson-datatype-guava/2.10.1"&gt;&lt;code&gt;com.fasterxml.jackson.datatype:jackson-datatype-guava:2.10.1&lt;/code&gt;&lt;/a&gt;. Jackson-datatype-guava depends on &lt;code&gt;com.google.guava:guava:20.0&lt;/code&gt; and gRPC-Protobufs uses &lt;code&gt;com.google.guava:guava:28.1-android&lt;/code&gt;, Bazel will compile and package your project's JAR with &lt;code&gt;com.google.guava:guava:28.1-android&lt;/code&gt;, but it won't recompile Jackson-datatype-guava (cause it doesn't have to). If a user hits a code path that uses a method of Jackson-datatype-guava which relies on a class or method present in &lt;code&gt;guava:20.0&lt;/code&gt; but removed in &lt;code&gt;guava:28.1-android&lt;/code&gt;, you just a lost a user, or maybe much worse!&lt;/p&gt;

&lt;p&gt;Fortunately, if you catch this during testing it can be easily solved by Bazel or SBT, but that's a BIG if. I feel Bazel, or more specifically, &lt;code&gt;rules_jvm_external&lt;/code&gt;, has the ability to address this by explicitly calling out these conflicts. I have opened an &lt;a href="https://github.com/bazelbuild/rules_jvm_external/issues/294"&gt;issue&lt;/a&gt; on &lt;code&gt;rules_jvm_external&lt;/code&gt; to hopefully add a feature which makes it more clear when transitive conflicts occur. Until then, we're left to manually looking out for these changes, write comprehensive tests (you should be doing this anyway), and hope our users don't do weird things.&lt;/p&gt;

</description>
      <category>builds</category>
      <category>sbt</category>
      <category>bazel</category>
      <category>jvm</category>
    </item>
  </channel>
</rss>
