Operations 12 min read

Debugging Excel Parsing Errors with Arthas and Building a Custom Java Agent

This article walks through diagnosing a hidden Excel parsing exception using Arthas, identifies the root cause in header mapping logic, and then explains Java Agent fundamentals before providing a step‑by‑step demo that injects custom code into a running JVM.

Alibaba Cloud Native
Alibaba Cloud Native
Alibaba Cloud Native
Debugging Excel Parsing Errors with Arthas and Building a Custom Java Agent

Problem Overview

In a project that uses com.github.dreamroute:excel-helper to parse Excel files, the catch block only logs e.getMessage(), which hides the full stack trace. This makes reproducing the error difficult.

Investigation with Arthas

Install Arthas on the production server (see https://arthas.aliyun.com/doc/install-detail.html).

Run the following watch command to monitor ExcelHelper.importFromFile and print parameters and thrown exceptions:

watch com.github.dreamroute.excel.helper.ExcelHelper importFromFile '{params,throwExp}' -e -x 3

The command captures the exception stack (see image).

Arthas watch output
Arthas watch output

Root Cause Analysis

The headerInfoMap may contain incorrect data.

The columnIndex of a cell might exceed the valid range.

Local testing shows the second case is unlikely, so the focus is on the first.

Map<Integer, HeaderInfo> headerInfoMap = processHeaderInfo(rows, cls);

The processHeaderInfo method reads the first row of the Excel file and builds the map:

public static Map<Integer, HeaderInfo> processHeaderInfo(Iterator<Row> rows, Class<?> cls) {
    if (rows.hasNext()) {
        Row header = rows.next();
        return CacheFactory.findHeaderInfo(cls, header);
    }
    return new HashMap<>(0);
}

public static Map<Integer, HeaderInfo> findHeaderInfo(Class<?> cls, Row header) {
    Map<Integer, HeaderInfo> headerInfo = HEADER_INFO.get(cls);
    if (MapUtils.isEmpty(headerInfo)) {
        headerInfo = ClassAssistant.getHeaderInfo(cls, header);
        HEADER_INFO.put(cls, headerInfo);
    }
    return headerInfo;
}

The HEADER_INFO cache is stored in CacheFactory. If the cache contains wrong entries, every subsequent Excel upload fails.

Using an OGNL expression to inspect the cache reveals only four entries instead of the expected six columns:

ognl '#value=new com.tom.dto.ExcelDTO(),#[email protected]@HEADER_INFO,#valueMap.get(#value.getClass()).entrySet().iterator.{#this.value.name}'

The missing entries explain the exception. A colleague confirmed that the first uploaded file was malformed, which corrupted the cache.

Arthas Internals – Java Agent Fundamentals

Arthas attaches a Java Agent to a running JVM. A Java Agent can be loaded at JVM startup via -javaagent or attached at runtime using the Attach API, providing dynamic instrumentation.

Key Concepts

JVMTI (JVM Tool Interface): Event‑driven native interface for JVM extensions.

JVMTI Agent: Native library that uses JVMTI and the Attach mechanism to load agents.

JPLISAgent: Initializes agents written with the Java Instrumentation API.

VirtualMachine: Provides attach and loadAgent methods to inject agents at runtime.

Instrumentation: Allows bytecode transformation before class loading ( premain) or after ( agentmain).

Arthas architecture
Arthas architecture

Demo: Building a Simple Java Agent

The demo uses Javassist to insert custom logic before and after a target method.

1. Define the Agent

/**
 * AgentMain
 */
public class AgentMain {
    public static void agentmain(String agentArgs, Instrumentation instrumentation)
            throws UnmodifiableClassException, ClassNotFoundException {
        instrumentation.addTransformer(new InterceptorTransformer(agentArgs), true);
        Class clazz = Class.forName(agentArgs.split(",")[1]);
        instrumentation.retransformClasses(clazz);
    }
}

public class InterceptorTransformer implements ClassFileTransformer {
    private String agentArgs;
    public InterceptorTransformer(String agentArgs) { this.agentArgs = agentArgs; }

    @Override
    public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined,
                            ProtectionDomain protectionDomain, byte[] classfileBuffer)
            throws IllegalClassFormatException {
        if (className != null && className.indexOf("/") != -1) {
            className = className.replaceAll("/", ".");
        }
        try {
            CtClass cc = ClassPool.getDefault().get(className);
            CtMethod m = cc.getDeclaredMethod(agentArgs.split(",")[2]);
            m.insertBefore("{ System.out.println(\"=========开始执行=========\"); }");
            m.insertAfter("{ System.out.println(\"=========结束执行=========\"); }");
            return cc.toBytecode();
        } catch (Exception e) { }
        return null;
    }
}

2. Maven Manifest Configuration

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-jar-plugin</artifactId>
            <version>2.3.1</version>
            <configuration>
                <archive>
                    <manifest>
                        <addClasspath>true</addClasspath>
                    </manifest>
                    <manifestEntries>
                        <Agent-Class>com.tom.mdc.AgentMain</Agent-Class>
                        <Can-Redefine-Classes>true</Can-Redefine-Classes>
                        <Can-Retransform-Classes>true</Can-Retransform-Classes>
                    </manifestEntries>
                </archive>
            </configuration>
        </plugin>
    </plugins>
</build>

3. Attach to Target JVM

import com.sun.tools.attach.VirtualMachine;
import java.io.IOException;

public class AttachMain {
    public static void main(String[] args) {
        VirtualMachine vm = null;
        try {
            vm = VirtualMachine.attach(args[0]);
            vm.loadAgent("target/agent-demo-1.0-SNAPSHOT.jar", String.join(",", args));
        } catch (Exception e) {
            if (vm != null) {
                try { vm.detach(); } catch (IOException ex) { ex.printStackTrace(); }
            }
        }
    }
}

4. Test Application

package com.tom.mdc;
import java.lang.management.ManagementFactory;
import java.util.Random;
import java.util.concurrent.TimeUnit;

public class PrintParamTarget {
    public static void main(String[] args) {
        System.out.println(ManagementFactory.getRuntimeMXBean().getName());
        Random random = new Random();
        while (true) {
            int sleepTime = 5 + random.nextInt(5);
            running(sleepTime);
        }
    }
    private static void running(int sleepTime) {
        try { TimeUnit.SECONDS.sleep(sleepTime); } catch (InterruptedException e) { e.printStackTrace(); }
        System.out.println("running sleep time " + sleepTime);
    }
}

This demo shows how to attach an agent at runtime, transform a method, and observe the injected logging output.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

DebuggingJavaInstrumentationJava AgentExcelArthas
Alibaba Cloud Native
Written by

Alibaba Cloud Native

We publish cloud-native tech news, curate in-depth content, host regular events and live streams, and share Alibaba product and user case studies. Join us to explore and share the cloud-native insights you need.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.