Search code examples
javaokhttpnoclassdeffounderrorjava.util.concurrent

What could cause a NoClassDefFoundError on one specific system only?


I'm currently working on a desktop application using Swing, communicating with a server over HTTPS. However, on one production machine which I will have to support, the current development build throws a NoClassDefFoundError despite the class being actually included in the JAR.

The situation is quite simple: I'm starting up the Swing application and configure a server to contact, which will then be used for a quick HTTPS connection test (in the class HTTPClient). I facilitate OkHttp for all communication with the server.

My HTTPClient resembles this code:

package com.myprogram.http;

import okhttp3.*;
import java.net.*;
import java.io.*;

public class HTTPClient {
    private static final OkHttpClient CLIENT = new OkHttpClient ();

    static boolean sendHeartbeat (String server, int port) {
        URL url;
        try { url = new URL ("https", server, port, "/api/v1/heartbeat"); }
        catch (MalformedURLException e) { return false; }

        Request request = new Request.Builder ().url (url).build ();
        try (Response resonse = CLIENT.newCall (request).execute ()) {
            return true;
        } catch (IOException | NullPointerException e) { return false; }
    }
}

This code is being called from middleware which also does state checking etc. and is the real interface between UI and HTTP code. This is the HTTPConnector class:

package com.myprogram.http;

import java.util.concurrent.*;

public class HTTPConnector {
    private static final ExecutorService THREAD_POOL = Executors.newFixedThreadPool (10);
    private static String server; // Set by UI code
    private static int port;      // Set by UI code
    
    public static void setServer (String server, int port) {
        HTTPConnector.server = server;
        HTTPConnector.port = port;
    }

    public static void testServerAvailability () {
        if (server == null) throw new IllegalStateException ("Must set server first!");
        
        boolean success = false;
        for (int i = 0; i < 5; i++) {
            Future <Boolean> future = null;
            try {
/* line x */    future = THREAD_POOL.submit (() -> HTTPClient.sendHeartbeat (server, port));
/* line y */    success = future.get (1500, TimeUnit.MILLISECONDS);
                if (success) break;
            } catch (TimeoutException e) {
                e.printStackTrace ();
                System.out.println ("Server status timed out");
                future.cancel (true);
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace ();
                System.out.println ("Server status could not be queried");
            }
        }
    }
}

(Please note that I abridged and simplified my code here, for the sake of an MCVE. In reality, I make sure that only one single thread dispatches calls into the HTTPConnector.)

However, upon calling HTTPConnector.testServerAvailability (), I get first a TimeoutException and then four NoClassDefFoundErrors, which read:

java.util.concurrent.TimeoutException
    at java.base/java.util.concurrent.FutureTask.get(FutureTask.java:204)
    at com.myprogram.http.HTTPConnector.testServerAvailability(HTTPConnector.java:x)
    at [REDACTED]
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
    at java.base/java.lang.Thread.run(Thread.java:833)

java.util.concurrent.ExecutionException: java.lang.NoClassDefFoundError: Could not initialize class com.myprogram.http.HTTPClient
    at java.base/java.util.concurrent.FutureTask.report(FutureTask.java:122)
    at java.base/java.util.concurrent.FutureTask.get(FutureTask.java:205)
    at com.myprogram.http.HTTPConnector.testServerAvailability(HTTPConnector.java:y)
    at [REDACTED]
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
    at java.base/java.lang.Thread.run(Thread.java:833)
Caused by: java.lang.NoClassDefFoundError: Could not initialize class com.myprogram.http.HTTPClient
    at com.myprogram.http.HTTPConnector.lambda$testServerAvailability$6(HTTPConnector.java:x)
    at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
    ... 3 more

I have confirmed that this Lambda is the very first place where any call into the HTTPClient happens, thus is the only place where the class can be initialised.

The only machine I'm seeing this behaviour so far is a Windows 7 Professional (SP1 build 7601) with a Dual-Core 64-bit CPU and 4 GiB RAM. On my Windows 7 Professional (SP1 build 7601) virtual machine with a virtual Dual-Core 64-bit CPU and 10 GiB of RAM, the very same program works. What bothers me is the fact that the JVM uses the very same amount of RAM on both systems. (I'm bundling OpenJDK 17.0.1 next to the JAR, the same executable is used in both cases.)

To me, that would mean that I have a classpath issue but given that the exact same test layout works in my VM, I tend to believe that it is RAM-related. This, however, is even more curious, because of the exact same memory consumption of the JVM.

Upgrading RAM on the production machine is sadly out of discussion, but since I am unsure what actually causes the problems (could it actually be RAM?) I wanted to make sure that I am not doing something blatantly wrong before going to great lengths in searching for an alternative to running the program on this very machine.

In any case, can RAM actually be the culprit here? Or is it CPU clock-speed (3.3 GHz on the production machine vs 3.6 GHz in the VM) and OkHttp needs more than 1500 ms to initialise, hence the TimeoutException when submitting the future?


Solution

  • In my case, since I had set a timeout of 1,500 milliseconds on the future and because of the slower CPU clock speed of the misbehaving machine, the class was not fully initialised when the timeout occurred. Turns out, OkHttp is more or less the culprit, it takes more than 5 seconds to inistalise the client on the given machine.

    All in all, I am no longer applying any timeout on the first try of the connection test to give OkHttp enough time to initialise itself.

    Note that this would not solve the problem if the initialisation of the HTTPClient was to fail at a different point in the application lifecycle. But since the first try of the connection test is the first place to call into HTTPClient, this is the only place where it can be initialised.