资讯详情

Axis反序列化Unmarshalling Error For input string报错全解析

📅 2026/10/6 16:46:43 | 华诺云谱 👁 阅读
Axis反序列化Unmarshalling Error For input string报错全解析
1. 问题现场一个让老Java开发头皮发麻的报错做Java后端的人多多少少都跟WebService打过交道。早年间系统集成没什么微服务大家全靠SOAP和WSDL走天下。我在维护一套老系统的时候就遇到过这么个经典报错客户端用Axis去调用服务端WebService请求发得出去响应也拿得回来偏偏在反序列化阶段炸了[Fault] Unmarshalling Error: For input string: 第一次看到这个异常我盯着屏幕愣了好一会儿。Unmarshalling Error我知道是反序列化失败For input string是Java数字解析的经典错误信息但后面跟了三个引号是什么意思当时第一反应是服务端返回了脏数据但抓包看了半天XML结构、字段类型、命名空间都没毛病。后来排查了一整个下午才把问题彻底吃透。这篇博文就围绕这个异常展开把Axis反序列化的机制、空字符串触发数字类型转换失败的完整链路、定位问题的方法论以及六种从治标到治本的解决方案一次性讲透。不管你是刚接触WebService的新手还是在维护老系统的Java工程师或者正被C#、Delphi、Java跨语言调用WebService折磨的集成开发这篇内容都能直接帮上忙。先说清楚一个前提报这个错的通常是Axis 1.x系列也就是org.apache.axis包不是Axis2。Axis2的异常体系不同WebService框架本身也有了翻天覆地的变化但老项目的维护者绝对不少这个问题至今在网上仍然有很高的搜索热度。2. 异常拆解Unmarshalling Error和For input string各是什么来头2.1 Unmarshalling Error的含义Unmarshalling这个词翻译过来就是“解组”跟Marshalling编组相对。在WebService的调用链里客户端发请求前要把Java对象转换成XML这叫编组服务端返回XML响应后客户端要把XML转换回Java对象这叫解组也就是反序列化。Axis的反序列化过程大致是这样的SOAP响应从网络到达客户端后Axis的DeserializationContext会拿着WSDL里定义的类型信息找到对应的Deserializer反序列化器然后逐个元素地把XML里的文本内容填入Java对象的属性。这个过程中任何一步出错Axis都会抛出一个笼统的异常对外统一表现为Unmarshalling Error。注意这个异常本身并不告诉你具体是哪个字段、哪个元素出了问题。它就像快递公司给你打电话说“包裹破损了”但不说哪里破了你需要自己拆开包装去检查。这是排查这类问题最头疼的地方。2.2 For input string是NumberFormatException的固定台词For input string: xxx是Java里NumberFormatException的标准异常信息。不管是Integer.parseInt、Long.parseLong还是new BigDecimal只要传入的字符串不是合法的数字格式JVM就会抛出这个异常信息里原样带出你传入的字符串内容。举个例子Integer.parseInt()会报For input string: 因为空串不是合法整数Integer.parseInt(3.14)会报For input string: 3.14Integer.parseInt()——也就是输入内容本身是两个双引号字符——会报For input string: 。回到这个标题里的异常信息Unmarshalling Error: For input string: 这里大概率是两种情况之一日志工具显示的是For input string: 即输入是零个字符的空字符串标题在转述时多了一个引号也有可能服务端真的返回了内容为的XML文本也就是两个双引号字符Axis试图把它解析成数字于是NumberFormatException把输入内容打印出来就成了三个引号的样子。无论哪种情况本质都一样Axis在把XML文本内容转换为Java数据类型时拿到的字符串不是它能接受的有效数字格式。2.3 Axis反序列化的完整链路理解问题的根源得先把Axis处理响应的一整套流程捋清楚。Axis 1.x的客户端调用一个WebService方法大致经历以下步骤客户端构造SOAP请求通过HTTP传输到服务端服务端处理业务逻辑返回SOAP格式的XML响应Axis客户端拿到响应流交给MessageFactory解析出SOAPMessageSOAPMessage的Body部分被交到DeserializationContext手里DeserializationContext根据WSDL定义的元素类型在TypeMappingRegistry里查找对应的DeserializerDeserializer逐个读取XML元素将文本内容解析成Java对象解析完成后把Java对象装配成方法返回值交回给调用方。在这条链路里第6步是异常的高发区。WSDL里声明某个元素类型是xsd:intAxis就会用IntegerDeserializer来处理这个元素。IntegerDeserializer的逻辑本质就是拿到XML元素的文本内容调用Integer.valueOf(text)。如果你传进去的text是空串、带引号的串、带小数点的串或者带千分位分隔符的串Integer.valueOf一律不认NumberFormatException拔地而起。3. 根因定位为什么一个空值会把Axis干趴下3.1 WSDL类型映射规则约定大于防御WebService的WSDL文档里每个字段都有明确的XML Schema类型声明。Axis拿到WSDL后会建立一套从xsd类型到Java类型的映射表规则大致如下XML Schema类型Java类型对应Deserializerxsd:intint / IntegerIntegerDeserializerxsd:longlong / LongLongDeserializerxsd:doubledouble / DoubleDoubleDeserializerxsd:booleanboolean / BooleanBooleanDeserializerxsd:stringjava.lang.StringStringDeserializerxsd:dateTimejava.util.DateDateDeserializer这套映射是有严格约定的WSDL说这个字段是int那服务端返回的XML里这个元素就必须是合规的整数文本比如123、-45不能是空串更不能是abc。问题在于实际业务里服务端返回值并不总是那么规矩。比如数据库某个数字字段是NULL服务端开发图省事直接在拼接XML时写了个空标签或者写死的模板里本来就有个空值点。对字符串类型这可能无所谓可一旦被映射成int的字段收到空标签Axis的IntegerDeserializer拿着空字符串去做Integer.valueOf瞬间爆炸。3.2 服务端返回空值的三种姿势服务端返回的XML里数字字段常见的“不规矩”写法有三种得分别看一眼第一种空标签age/age这是最常见的情况。字段本身存在但没有内容XML的文本节点是空字符串。Axis解析时拿到的是Integer.parseInt()直接抛异常。第二种自闭合标签age/在XML里age/和age/age是等价的文本内容都是空串。Axis处理结果也一样照样炸。第三种文本内容带引号或特殊字符age/age服务端代码可能把空值序列化成了字符串形式比如这两个字符或者把用户输入的原样拼了进去。结果是XML文本内容确实是两个双引号字符Integer.parseInt()依然抛异常。标题里的三个引号很可能就是这么来的。3.3 还有哪些常见的类型不匹配场景除了空值实际运维中我还遇到过不少类似的类型不匹配触发点都属于Unmarshalling Error家族服务端返回数字带小数位比如12.5但WSDL声明字段是xsd:int服务端返回数字带千分位比如1,000Java的parse系列方法不认识逗号服务端返回数字带空格比如 123个别熟悉另一个语言体系的开发会踩这个坑服务端返回TRUE或Yes而不是trueBooleanDeserializer只认true/false/1/0服务端返回时间格式跟WSDL里xsd:dateTime的ISO8601格式不一致同一个字段在服务端是多语言环境下的本地化数字比如小数点用的是逗号。还有一个隐蔽场景服务端是大型业务系统做了字段加密或脱敏返回的值不是原始数字而是***之类的掩码。这种情况如果你不了解业务背景排查起来会非常迷惑。4. 排查实操五步定位问题源头4.1 第一步打开Axis的调试开关遇到Unmarshalling Error首先要把Axis的底裤扒开来看。Axis 1.x支持在JVM启动参数里开启调试模式java -Daxis.debugtrue -cp your-app.jar com.example.ClientMain也可以在代码里设置System.setProperty(axis.debug, true);同时配置log4j把Axis相关包的日志级别调到DEBUGlog4j.rootLoggerINFO, console log4j.logger.org.apache.axisDEBUG log4j.logger.org.apache.axis.messageDEBUG log4j.logger.org.apache.axis.encodingDEBUG开启之后Axis会把SOAP请求和响应的内容、DeserializationContext的活动轨迹、TypeMapping的查找过程全部打印出来。这一步能让你确认到底是请求发送前的编组出了问题还是响应返回后的解组出了问题。4.2 第二步用Postman或TCPMon抓原始SOAP响应Axis的日志虽然详细但有时候反序列化失败发生在日志打印之前你什么都看不到。更直接的办法是抓原始报文。最轻量的做法是写一个测试代码用HttpURLConnection直接发送SOAP请求把响应体完整打印出来不经Axis处理String soapRequest ...; // 完整的SOAP请求XML URL url new URL(http://localhost:8080/services/UserService); HttpURLConnection conn (HttpURLConnection) url.openConnection(); conn.setRequestMethod(POST); conn.setRequestProperty(Content-Type, text/xml; charsetutf-8); conn.setRequestProperty(SOAPAction, \getUser\); conn.setDoOutput(true); try (OutputStream os conn.getOutputStream()) { os.write(soapRequest.getBytes(UTF-8)); } String response new String(conn.getInputStream().readAllBytes(), UTF-8); System.out.println(response);把打印出来的XML跟WSDL里的类型定义逐个对照异常字段往往一眼就能发现。如果你想用现成工具Postman也支持WebService调用新建请求时选择POST填好URL、SOAPAction和Body一样能看到原始XML响应。市面上还有一些专门的WebService测试工具比如SoapUI功能更强可以加载WSDL后直接生成测试请求。4.3 第三步对照WSDL找出可疑的数字字段拿到原始响应XML后先看WSDL把响应类型里所有数值型字段全部列出来。重点检查这些字段在XML响应里的实际内容。可以用程序辅助判断// 把XML解析之后检查每个数字类型字段的文本内容 Document doc DocumentBuilderFactory.newInstance() .newDocumentBuilder() .parse(new ByteArrayInputStream(xmlBytes)); XPath xpath XPathFactory.newInstance().newXPath(); NodeList nodes (NodeList) xpath.evaluate(//*, doc, XPathConstants.NODESET); for (int i 0; i nodes.getLength(); i) { Node node nodes.item(i); if (node.getNodeType() Node.ELEMENT_NODE) { String text node.getTextContent(); // 这里需要结合WSDL类型去判断数字类型的text如果是空串或非数字就是嫌疑点 if (text ! null text.trim().isEmpty()) { System.out.println(空值字段: node.getNodeName()); } } }我在实际排查中用这个办法一分钟就找到了问题字段。当时响应里有十几个字段其中一个是Integer类型正好是空标签其他字段全部正常。4.4 第四步确认是单个字段还是多个字段的共性问题如果一个字段有问题很可能是服务端对空值的处理不规范。如果多个数字字段都是空的那基本可以断定是服务端统一把NULL映射成了空标签。还有一种情况是WSDL和实际返回的结构不匹配Axis拿WSDL里定义的元素顺序去解析XML如果服务端返回顺序不一致也会导致取到的文本内容对不上号。这一步的验证方式很简单找一个肯定有值的数字字段和一个可能为空的数字字段把服务端返回内容对比着看。如果前者能正常反序列化、后者报错问题就锁定在空值处理上。4.5 第五步隔离客户端验证排除本地干扰最后还要做一次隔离验证用Postman直接发SOAP请求然后用同样的XML去调用别的客户端比如C#或者其它语言实现的客户端看看是否也会报错。这一步能帮你判断问题到底是服务端数据本身的问题还是Axis特有的类型转换问题。我记得有一次同一个服务端C#客户端调用完全正常Java Axis客户端必然报错。原因就是C#的序列化器对空值的容忍度更高直接把空字符串映射成了0而Axis严格遵循类型映射空串就是拼不过int。这种情况下单纯让服务端改数据是最快的路径但如果你无法控制服务端就只能从客户端入手解决。5. 解决方案从治标到治本的六条路5.1 方案一服务端修正空值返回方式如果服务端代码是你自己控制的这是最推荐的做法。数字字段为空时不要返回空标签也不要返回字符串而是返回XML标准规定的空值表达方式age xsi:niltrue/在Java服务端如果你用JAXB注解或者手工构建SOAP消息要为元素加上xsi:nil属性。例如用JAXB时可以这样import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlElementRef; public class UserResponse { XmlElement(nillable true) private Integer age; }设置age为null后JAXB序列化时会自动输出xsi:niltrue。这样Axis的IntegerDeserializer遇到xsi:nil属性会自动把Java属性置为null不会再去做字符串解析。需要提醒的是xsi:niltrue要求命名空间前缀xsi必须正确声明即xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance。如果你手工拼XML千万别漏了这行声明。5.2 方案二客户端自定义Deserializer兜底如果服务端不是你控制的或者服务端的修复需要走很长的流程那就得在客户端想办法。Axis允许注册自定义的Deserializer来处理特定类型我们可以写一个容忍空字符串的IntegerDeserializerimport org.apache.axis.encoding.DeserializationContext; import org.apache.axis.encoding.ser.SimpleDeserializer; import org.xml.sax.SAXException; public class SafeIntegerDeserializer extends SimpleDeserializer { public SafeIntegerDeserializer() { super(Integer.class, org.apache.axis.encoding.XMLType.XSD_INT); } Override public void onEndElement(String namespace, String localName, DeserializationContext context) throws SAXException { String value context.getCurElement().getValue(); if (value null || value.trim().isEmpty()) { setValue(null); } else { try { setValue(Integer.valueOf(value.trim())); } catch (NumberFormatException e) { // 兜底处理遇上非数字内容时按自己的业务策略处理 setValue(null); } } super.onEndElement(namespace, localName, context); } }注册方式有两种。一种是在代码里为每一次Call注册类型映射org.apache.axis.client.Service service new org.apache.axis.client.Service(); org.apache.axis.client.Call call (org.apache.axis.client.Call) service.createCall(); QName intQName new QName(http://www.w3.org/2001/XMLSchema, int); call.registerTypeMapping(Integer.class, intQName, null, SafeIntegerDeserializer.class);另一种是修改client-config.wsdd文件在全局配置里注册。这个文件位于客户端的classpath下是Axis的核心配置文件。找到typeMapping段添加自定义注册typeMapping qnamehttp://www.w3.org/2001/XMLSchema:int javaTypejava.lang.Integer deserializercom.example.SafeIntegerDeserializer serializerorg.apache.axis.encoding.ser.IntSerializer/这样所有用到xsd:int类型的地方都会走SafeIntegerDeserializer。这个方法对全局生效适合字段多、空值频繁的系统。5.3 方案三修改WSDL2Java生成的Stub代码如果你是用wsdl2java工具生成的客户端代码生成出来的Stub里会对每个操作定义类型映射。找到报错字段对应的注册代码把默认的Deserializer替换掉。典型的生成代码结构长这样private org.apache.axis.client.Call _call null; private void initOperationDescs() { _operations new org.apache.axis.description.OperationDesc[1]; // 注册字段级的类型映射 org.apache.axis.description.TypeDesc typeDesc new org.apache.axis.description.TypeDesc(User.class); QName qname new QName(http://service.example.com, User); org.apache.axis.description.ElementDesc elem new org.apache.axis.description.ElementDesc(); elem.setFieldName(age); elem.setXmlName(new QName(, age)); elem.setElementType(new QName(http://www.w3.org/2001/XMLSchema, int)); elem.setNillable(true); typeDesc.addFieldDesc(elem); }在这种结构里你可以改elem字段的deserializer或者直接给User类的age属性加一个setter的容错处理。如果你用的是wsdl2java生成可序列化Bean的代码最简单的做法是在Bean的setter里做防御public void setAge(Integer age) { // 这里如果age是null不影响业务如果是其他异常值可以根据业务决定默认值 this.age age; }但注意如果反序列化在setter之前就炸了这个方法不生效。更稳妥的还是走自定义Deserializer那是Axis反序列化链路上最早能拦截的关卡。5.4 方案四换掉Axis升级到JAX-WS或CXF如果这个项目不是非要用Axis不可我强烈建议迁移到更现代的实现。Axis 1.x本身已经很老维护基本停滞对比JAX-WSJava标准WebService规范和Apache CXF差距不是一星半点。JAX-WS的风格是基于注解的开发类型映射更智能。最典型的一点是JAX-WS对空值的处理有标准行为服务端返回的null字段会映射成Java的null而不是试图把空字符串塞进Integer里。如果WSDL里声明了nillable解析器能正确感知空值。迁移的成本主要看你的系统规模。如果是几个接口工作量不大用JDK自带的wsimport重新生成客户端即可wsimport -keep -verbose http://localhost:8080/services/UserService?wsdl然后调用代码会变得清爽很多UserServiceService service new UserServiceService(); UserService port service.getUserServicePort(); User user port.getUser(1); // age为null不会抛NumberFormatException System.out.println(user.getAge());如果是老系统、几十个接口迁移成本会高一些但长期看是值得的。网上关于Java WebService框架选型的争议不少但几乎没人会推荐新项目用Axis 1.x。选型这件事过时的技术不是不能用而是维护成本真的高。5.5 方案五客户端手动解析SOAP响应作为兜底如果你只想快速止血不想动Axis的配置也不方便改服务端直接用XML解析库手动处理响应是最后一条后路。这个方案虽然绕但绝对有效而且对排查问题很有帮助相当于你在Axis外面包了一层保护网。思路是这样的先绕过Axis自动反序列化直接把SOAP响应拿下来用DOM或者StAX解析// 调用之前把返回类型设为org.w3c.dom.Element call.setReturnType(org.apache.axis.encoding.XMLType.SOAP_DOM); Object response call.invoke(parameters); if (response instanceof org.w3c.dom.Element) { org.w3c.dom.Element element (org.w3c.dom.Element) response; NodeList nodeList element.getElementsByTagNameNS(http://service.example.com, age); String ageText nodeList.item(0).getTextContent(); Integer age null; if (ageText ! null !ageText.trim().isEmpty()) { try { age Integer.valueOf(ageText.trim()); } catch (NumberFormatException e) { // 按业务规则处理异常值 age null; } } }这个方案把类型转换的主动权完全掌握在自己手里所有异常字符串都能被捕获并处理。缺点是你需要自己写字段映射逻辑字段多了代码会比较繁琐。它更适合临时止血或者那种字段很少、又必须快速上线的场景。5.6 方案六重新审视WSDL兼容性和生成工具的选项有些时候问题根源不在空值本身而在WSDL文件的声明。比如服务端实际返回数字字段可以是空值但WSDL里漏了nillable属性或者字段类型声明成xsd:int而服务端实际返回的是字符串类型。这类问题的排查思路是用WSDL验证工具检查WSDL与Schema的匹配性确认响应元素是否允许为null。如果确实允许看WSDL里的nillable属性是否已声明如果不允许服务端却在特殊场景下返回了空值那就是服务端实现了WSDL之外的“叠加行为”属于规范之外的问题。还有一种常见情况服务端用的是另一个框架生成WSDL生成的xsd:int本身没问题但实际返回的报文结构跟WSDL里描述的命名空间不一致。Axis按命名空间去找Deserializer找不到关键元素只能拿空内容硬填。这种情况下你看到的空值其实只是表象深层原因是命名空间不匹配。排查时如果根因总定位不到建议把响应XML的xmlns声明逐个跟WSDL的targetNamespace对照错一个字符都会出大问题。6. 避坑速查表与经验总结6.1 常见错误信息与原因对照我把实际踩过的坑整理成一张速查表方便大家遇到类似报错时快速对照异常信息可能原因处理方向For input string: 空标签或自闭合标签服务端改xsi:nil客户端容错For input string: 文本内容本身就是双引号检查服务端序列化逻辑For input string: 12.5小数赋给了整数类型修正WSDL类型或做转换For input string: 1,000带千分位分隔符服务端输出纯数字格式For input string: TRUE布尔值大小写不匹配统一为true/falseUnmarshalling Error: 不提供具体信息字段内容与类型不匹配抓原始XML对照WSDL无法找到Deserializer命名空间不匹配比对目标命名空间这张表里的前两行就是本篇博文主题的核心场景。尤其是第二个如果日志里出现三个引号一定要想到文本内容本身可能就是两个双引号字符。6.2 跨语言调用最容易踩的坑WebService的价值在于跨语言互操作但跨语言调用恰恰是坑最多的地方。C#调用Java服务端、Java调用C#服务端、Delphi XE2调用Java服务端每种组合都有各自的脾气。拿C#来说.NET的XmlSerializer对空值处理比较宽松Java服务端返回空字符串给C#的int属性C#往往能自动转成0。但反过来C#服务端给Java客户端返回空标签Java的Axis就受不了。这就是为什么同样一个服务端不同语言客户端表现完全不同的原因。Delphi XE2的WebService客户端也有自己的序列化规则它对WSDL的类型要求比Java宽松但对SOAP头部的处理又比Java严格。如果你的系统里有多语言客户端调试时要特别留意某种语言调通了、另一种语言调不通很可能是序列化器对空值的容忍度不同。Postman在这种场景下特别好用因为它相当于一个中立第三方不依赖任何语言的序列化器你看到的就是最原始的XML。只要把Postman的结果作为基准再去对比各语言客户端的表现问题出在哪一环就一目了然。6.3 我的几条实操心得第一遇到Unmarshalling Error先别急着翻代码排查逻辑第一步永远是抓报文。报文是序列化和反序列化的唯一真相看着报文找问题比凭空猜快得多。第二自定义Deserializer是Axis场景下的万金油技能。只要反序列化报错你都可以用这个思路兜底。不仅限于IntegerLong、Double、Boolean甚至Date类型都能写对应的安全Deserializer。这套逻辑一通百通。第三如果服务端和客户端都是你的团队维护优先修复服务端而不是在客户端做各种防御。客户端防御虽然能绕过异常但本质上是掩盖问题。服务端把该返回的xsi:nil返回好所有客户端都能受益。第四老系统的技术债不能一直背着。每次遇到Axis的问题我都建议大家评估一下迁移的可行性。哪怕今年只迁一个接口也比一直陷在Axis的坑里强。技术选型这事时机很重要越拖越被动。最后说一个实用小技巧。如果你在Axis里调用很多接口想快速知道到底哪个字段是坏的可以在调用前临时用反射打印响应对象里所有的Integer和Long字段再结合响应XML做比对。这个方法虽然土但排查效率极高比我见过的许多复杂工具都管用。这个问题本身不大但背后的排查思路和解决方案是WebService调用这类场景的通用财富。希望这篇文章能帮你少走几个小时的弯路。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑