Skip to content

CRITICAL: MavenNexusClassSource silently swallows all errors - impossible to debug #59

@sfloess

Description

@sfloess

Severity: CRITICAL

File: MavenNexusClassSource.java

Problem

Lines 89-90 silently swallow ALL IOExceptions with an empty catch block. When class loading fails, there's ZERO indication why - no logging, no error accumulation, nothing.

Bug Details

Lines 83-91: Silent exception swallowing

for (MavenArtifact artifact : artifacts) {
    try {
        String jarUrl = buildJarUrl(artifact);
        byte[] classData = extractClassFromJar(jarUrl, classFileName);
        classCache.put(cacheKey, classData);
        return classData;
    } catch (IOException e) {
        // ← COMPLETELY SILENT! No log, no nothing!
    }
}

throw new IOException("Class not found in any configured Maven artifacts: " + className);

Problem: When extractClassFromJar() fails, exception is silently discarded

Result: Cannot debug why class loading fails:

  • Network timeout? Silent.
  • Auth failure? Silent.
  • HTTP 404? Silent.
  • HTTP 403? Silent.
  • Malformed JAR? Silent.
  • Out of memory? Silent.

All errors look the same: "Class not found in any configured Maven artifacts"

Real-World Debugging Nightmare

Scenario: Production system fails to load classes

ERROR: Class not found in any configured Maven artifacts: com.example.CriticalClass

What's actually wrong?

  • Nexus server is down? Can't tell.
  • Wrong credentials? Can't tell.
  • Artifact doesn't exist? Can't tell.
  • Network partition? Can't tell.
  • JAR is corrupted? Can't tell.

Time to resolution: HOURS of guessing, checking logs on Nexus server, inspecting network traffic, etc.

Compare to proper error message:

ERROR: Failed to load class com.example.CriticalClass from Maven artifacts:
  - Artifact org.example:lib:1.0 - HTTP 401 Unauthorized (check credentials)
  - Artifact org.example:lib:2.0 - Connection timeout after 30s (network issue?)
  - Artifact org.example:lib:3.0 - JAR not found (HTTP 404)
Class not found in any of 3 configured artifacts

Now you know IMMEDIATELY: credentials are wrong for first artifact, network is slow for second, third doesn't exist.

Additional Issues

Lines 116-145: extractClassFromJar() has critical bugs

  1. Line 118: No timeout

    HttpURLConnection connection = (HttpURLConnection) url.openConnection();

    Can hang forever - same bug as RestApiClassSource (Issue CRITICAL: RestApiClassSource has 6+ serious bugs #53)

  2. Lines 133-139: No size validation

    ByteArrayOutputStream out = new ByteArrayOutputStream();
    byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
    int bytesRead;
    while ((bytesRead = jarIn.read(buffer)) != -1) {
        out.write(buffer, 0, bytesRead);
    }
    return out.toByteArray();

    Can cause OOM - same bug as other ClassSources (Issues CRITICAL: MinioClassSource has 7+ production-breaking issues #51-54)

  3. Line 127: Downloads entire JAR into memory

    try (InputStream in = connection.getInputStream();
         JarInputStream jarIn = new JarInputStream(in)) {

    No Content-Length check - can download multi-GB JARs

Required Fixes

Fix 1: Accumulate errors and report all failures

@Override
public byte[] loadClassData(String className) throws IOException {
    byte[] cachedData = classCache.get(className);
    if (cachedData != null) {
        return cachedData;
    }

    String classFileName = ClassNameUtil.toClassFilePath(className);
    List<String> errorMessages = new ArrayList<>();

    for (MavenArtifact artifact : artifacts) {
        try {
            String jarUrl = buildJarUrl(artifact);
            byte[] classData = extractClassFromJar(jarUrl, classFileName);
            classCache.put(className, classData);
            return classData;
        } catch (IOException e) {
            // Accumulate error with context
            String errorMsg = String.format("Artifact %s - %s",
                artifact.toString(), e.getMessage());
            errorMessages.add(errorMsg);
            
            // Log at DEBUG level for troubleshooting
            if (logger.isDebugEnabled()) {
                logger.debug("Failed to load {} from {}", className, artifact, e);
            }
        }
    }

    // Throw with ALL error details
    String allErrors = String.join("\n  - ", errorMessages);
    throw new IOException(
        "Class not found in any of " + artifacts.size() + " configured Maven artifacts: " +
        className + "\nAttempted artifacts:\n  - " + allErrors
    );
}

Fix 2: Add timeouts to HTTP connections

private byte[] extractClassFromJar(String jarUrl, String classFileName) throws IOException {
    URL url = new URL(jarUrl);
    HttpURLConnection connection = (HttpURLConnection) url.openConnection();
    connection.setConnectTimeout(10000);  // 10 seconds
    connection.setReadTimeout(30000);     // 30 seconds
    configureAuthentication(connection);
    connection.setRequestMethod("GET");
    // ...
}

Fix 3: Add size validation

private byte[] extractClassFromJar(String jarUrl, String classFileName) throws IOException {
    URL url = new URL(jarUrl);
    HttpURLConnection connection = (HttpURLConnection) url.openConnection();
    connection.setConnectTimeout(10000);
    connection.setReadTimeout(30000);
    configureAuthentication(connection);
    connection.setRequestMethod("GET");

    int responseCode = connection.getResponseCode();
    if (responseCode != HttpURLConnection.HTTP_OK) {
        throw new IOException("HTTP " + responseCode + " for JAR URL: " + jarUrl);
    }

    // Check JAR size
    long contentLength = connection.getContentLengthLong();
    if (contentLength > MAX_JAR_SIZE) {  // e.g., 100MB
        throw new IOException(
            "JAR too large: " + contentLength + " bytes (max " + MAX_JAR_SIZE + ")"
        );
    }

    try (InputStream in = connection.getInputStream();
         JarInputStream jarIn = new JarInputStream(in)) {

        JarEntry entry;
        while ((entry = jarIn.getNextJarEntry()) != null) {
            if (entry.getName().equals(classFileName) && !entry.isDirectory()) {
                long size = entry.getSize();
                
                // Validate entry size
                if (size > MAX_CLASS_SIZE) {
                    throw new IOException(
                        "Class entry too large: " + size + " bytes (max " + MAX_CLASS_SIZE + ")"
                    );
                }
                
                // Read with size limit
                ByteArrayOutputStream out = new ByteArrayOutputStream();
                byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
                long totalRead = 0;
                int bytesRead;
                
                while ((bytesRead = jarIn.read(buffer)) != -1) {
                    totalRead += bytesRead;
                    if (totalRead > MAX_CLASS_SIZE) {
                        throw new IOException(
                            "Class entry exceeded size limit: " + totalRead + " bytes"
                        );
                    }
                    out.write(buffer, 0, bytesRead);
                }
                
                return out.toByteArray();
            }
        }
    }

    throw new IOException("Class file not found in JAR: " + classFileName + " (JAR URL: " + jarUrl + ")");
}

Impact

Current state:

  • Impossible to debug class loading failures
  • Hours wasted troubleshooting with no error details
  • Can hang forever on slow networks
  • Can OOM on large JARs/classes

With fixes:

  • Clear error messages showing exactly what failed and why
  • Timeouts prevent hangs
  • Size limits prevent OOM
  • Debug logging for deep troubleshooting

This is CRITICAL because debugging production class loading issues is a common operations task, and silent failures make it 10x harder.

Related Issues

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions