概要
業務システムでは、外部システムとのデータ連携、設定ファイル、SOAP メッセージなど、XML を扱う場面がいまだに少なくありません。Java 標準 API には DOM、SAX、StAX の3つのパーサーが用意されていますが、「どれを使えばいいのか」「それぞれの利点と制約は何か」を整理できている人は多くありません。DOM はツリー全体をメモリに保持するため小〜中規模ファイルに向き、SAX はイベント駆動で大容量ファイルに対応でき、StAX はカーソル方式で SAX より直感的に書けます。この記事では、3方式のパースと DOM による XML 生成を実装しながら、用途に応じた選択基準を整理します。
使いどころ
取引先から送られてくる注文データの XML を読み込み、DB に登録する
帳票出力用のデータを XML 形式で生成し、XSLT で変換してからPDF出力する
SOAP ベースの外部 API と連携するために、リクエスト・レスポンスの XML を処理する
コード例
import javax.xml.parsers.DocumentBuilderFactory;
public class XmlProcessing {
record Employee(String id, String name, String department, int salary) {}
/** DOM で XML をパース */
public static List<Employee> parseWithDom(String xml) throws Exception {
var factory = DocumentBuilderFactory.newInstance();
var doc = factory.newDocumentBuilder()
.parse(new InputSource(new StringReader(xml)));
doc.getDocumentElement().normalize();
var employees = new ArrayList<Employee>();
var list = doc.getElementsByTagName("employee");
for (int i = 0; i < list.getLength(); i++) {
var elem = (Element) list.item(i);
employees.add(new Employee(
elem.getAttribute("id"),
elem.getElementsByTagName("name").item(0).getTextContent(),
elem.getElementsByTagName("department").item(0).getTextContent(),
Integer.parseInt(elem.getElementsByTagName("salary").item(0).getTextContent())
));
}
return employees;
}
/** DOM で XML を生成 */
public static String buildWithDom(List<Employee> employees) throws Exception {
var doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
var root = doc.createElement("employees");
doc.appendChild(root);
for (var emp : employees) {
var e = doc.createElement("employee");
e.setAttribute("id", emp.id());
root.appendChild(e);
appendText(doc, e, "name", emp.name());
appendText(doc, e, "department", emp.department());
appendText(doc, e, "salary", String.valueOf(emp.salary()));
}
var transformer = TransformerFactory.newInstance().newTransformer();
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
var sw = new StringWriter();
transformer.transform(new DOMSource(doc), new StreamResult(sw));
return sw.toString();
}
private static void appendText(Document doc, Element parent, String tag, String text) {
var elem = doc.createElement(tag);
elem.setTextContent(text);
parent.appendChild(elem);
}
public static void main(String[] args) throws Exception {
var xml = """
<?xml version="1.0" encoding="UTF-8"?>
<employees>
<employee id="1">
<name>Yamada Taro</name>
<department>Development</department>
<salary>450000</salary>
</employee>
</employees>
""";
parseWithDom(xml).forEach(System.out::println);
System.out.println(buildWithDom(List.of(
new Employee("2", "Suzuki Hanako", "Sales", 380000))));
}
}Version Coverage
record でパース結果を型安全に保持できる。var で変数宣言が簡潔になる。switch 式(アロー構文)で SAX の endElement 処理が書きやすい。
// Java 17: record + switch 式(アロー構文)
record Employee(String id, String name, String dept, int salary) {}
switch (qName) {
case "name" -> name = text;
case "department" -> department = text;
case "salary" -> salary = text;
}Library Comparison
注意点
DOM は XML 全体をメモリに展開するため、数十MB を超えるファイルでは OutOfMemoryError のリスクがある。大容量なら SAX か StAX を使うこと。
外部から受け取る XML に対して DTD 処理を有効にしたままパースすると、XXE(XML External Entity)攻撃のリスクがある。外部入力のパースでは DTD を無効化すること。
SAX の characters() メソッドは1つのテキストノードでも複数回に分割して呼ばれることがある。StringBuilder に append して endElement() で確定させること。
Transformer の出力エンコーディングは指定しないとプラットフォーム依存になる場合がある。OutputKeys.ENCODING に UTF-8 を設定するのが安全。
FAQ
数MB 以下で要素の追加・削除もしたいなら DOM、大容量で読み取り専用なら SAX、大容量で直感的に書きたいなら StAX が適しています。
DocumentBuilderFactory や SAXParserFactory で FEATURE_EXTERNAL_ENTITIES を false に設定し、DTD 処理を無効化してください。
DocumentBuilderFactory.setNamespaceAware(true) を設定し、getElementsByTagNameNS() を使えば名前空間付き要素を正しく取得できます。