Gunnar Morling

Gunnar Morling

Random Musings on All Things Software Engineering

Gunnar Morling

Gunnar Morling

Random Musings on All Things Software Engineering

Maven, What Are You Waiting For?!

Posted at Dec 18, 2022

As part of my new job at Decodable, I am also planning to contribute to the Apache Flink project (as Decodable’s fully-managed stream processing platform is based on Flink). Right now, I am in the process of familiarizing myself with the Flink code base, and as such I am of course building the project from source, too.

Flink uses Apache Maven as its build tool. It comes with the Maven Wrapper, simplifying the onboarding experience for new contributors, who don’t need to have Maven installed upfront. The configured Maven version is quite old though, 3.2.5 from 2014. Not even coloured output on the CLI yet — Boo! So I tried to build Flink with the latest stable version of Maven, 3.8.6 at the time of writing, but ran into some issues doing so.

Specifically, there are several dependencies with repository information embedded into their POM files. This is generally considered a bad practice for libraries, as it will inject those repositories into the build of any consumers, e.g. causing slower build processes. In the case at hand, the situation is even worse, as Maven since version 3.8.1 blocks access to non-HTTPS repositories for security reasons. This means that your build will fail if any dependency pulls in an HTTP repository.

Dealing with this is a bit cumbersome, as it’s not always obvious which dependency is causing that issue. For Flink, I encountered two instances of that problem. First, a transitive dependency of the flink-connector-hive_2.12 module (message slightly adapted for readability):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
...
[ERROR] Failed to execute goal on project flink-connector-hive_2.12:
  Could not resolve dependencies for project org.apache.flink:flink-connector-hive_2.12:jar:1.17-SNAPSHOT:
  Failed to collect dependencies at org.apache.hive:hive-exec:jar:2.3.9 -> org.pentaho:pentaho-aggdesigner-algorithm:jar:5.1.5-jhyde:
  Failed to read artifact descriptor for org.pentaho:pentaho-aggdesigner-algorithm:jar:5.1.5-jhyde:
  Could not transfer artifact org.pentaho:pentaho-aggdesigner-algorithm:pom:5.1.5-jhyde from/to maven-default-http-blocker (http://0.0.0.0/):
  Blocked mirror for repositories: [
    repository.jboss.org (http://repository.jboss.org/nexus/content/groups/public/, default, disabled),
    conjars (http://conjars.org/repo, default, releases+snapshots),
    apache.snapshots (http://repository.apache.org/snapshots, default, snapshots)
  ]
...

There’s three non-HTTPS repositories involved here which got blocked by Maven. Note that those are all the unsecure repositories found in the dependency chain, they are not necessarily related to that particular error.

Unfortunately, there’s no good way for identifying which dependency exactly is pulling them into the build and which repository is the problem here. Instead, you need to analyse all the dependencies from the project root to the flagged dependency, including any potential parent POM(s). In the case at hand, the problematic repo is the "conjars" one, as defined in the parent POM of the org:apache:hive:hive-exec artifact, org:apache:hive.

As far as I am aware, there’s no way for overriding such dependency-defined repositories in a downstream build; the only way I’ve found is to define a repository with the same id in a custom settings.xml file, redefining its URL to make use of HTTPS:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<?xml version="1.0" encoding="UTF-8"?>
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 https://maven.apache.org/xsd/settings-1.0.0.xsd">

  <mirrors>
    <mirror>
      <id>conjars</id>
      <name>conjars</name>
      <url>https://conjars.org/repo</url>
      <mirrorOf>conjars</mirrorOf>
    </mirror>
  </mirrors>
</settings>

Building Flink with this settings.xml file gets us beyond that error. As far as the other two repositories are concerned, the JBoss one is actually defined in the root POM of Apache Flink itself. I’m not sure whether it’s actually needed, but I have created a pull request for changing it to HTTPS, just in case. The "apache.snapshots" repo is defined in the parent POM of org:apache:hive and seems also not needed. You could override it in your settings.xml using its HTTPS URL as a measure of good practice, though.

With that settings.xml in place, I could build Apache Flink using the current Maven version 3.8.6. I noticed though that the build gets stuck for quite some time at the following step:

1
2
3
4
5
6
...
[INFO] ------------------< org.apache.flink:flink-hadoop-fs >------------------
[INFO] Building Flink : FileSystems : Hadoop FS 1.17-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
Downloading from maven-default-http-blocker: http://0.0.0.0/net/minidev/json-smart/maven-metadata.xml
...

The build wouldn’t fail, though: after exactly 75 seconds, it continues and runs to completion. So what’s causing this stall? Again, a non-HTTPS repository is the culprit, but in a slightly more confusing way. As it turns out, that transitive dependency to the net.minidev:json-smart library is declared using a version range by the artifact com.nimbusds:nimbus-jose-jwt: [1.3.1,2.3].

So Maven reaches out to all configured repositories in order to identify the latest version within that range. Now the hadoop-auth dependency (via its parent hadoop-main) pulls in the JBoss HTTP repository; and while access to this is prevented via Maven’s HTTP blocker, for some reason it still tries to connect to that blocker’s pseudo URL 0.0.0.0. After 75 seconds, this request eventually times out and the build continues. Go figure.

For preventing this issue, you have a few options:

  • Add the JBoss repository with HTTPS to your settings.xml (again, the definition in the root POM of your own build does not suffice for that)

  • Run the build with the -o (offline) flag

  • Pin down the version of the artifact in the dependency management of your build, sidestepping the need for resolving the version range:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    
    ...
    <dependencyManagement>
      <dependencies>
        <dependency>
          <groupId>net.minidev</groupId>
          <artifactId>json-smart</artifactId>
          <version>2.3</version>
        </dependency>
      </dependencies>
    </dependencyManagement>
    ...
    

    This approach has the advantage that it can be done in a persistent way as part of the Maven POM itself, there’s no need for a custom settings.xml or build time parameters like the offline flag.

In any case, the build will now skip that 75 seconds pause. I.e. less time for drinking a coffee while the build is running, which is a good thing of course. Now you might wonder why exactly 75 seconds, and I have to admit it’s not fully clear to me.

When running the build with a debugger attached (I know, I know, it’s not en-vogue these days), I didn’t see any timeout configuration for establishing that HTTP connection. Some default TCP connection timeout on macOS perhaps? Interestingly, when trying with the latest Alpha of Maven 4, the build would only stall for ten seconds when trying to resolve that version range; Maven’s HTTP client is configured with a timeout of ten seconds as of this release.

The moral of the story? Don’t put repository information into published Maven POMs. If you publish something to Maven Central, all its dependencies should be resolvable from there, too. Luckily, Maven 4 will make this problem an issue of the past, bringing the long-awaited separation of build and consumer POMs.

I’d also advise caution when it comes to adding version ranges to dependency definitions, it can have unexpected consequences as demonstrated above, and it’s probably not worth the hassle.