Updated batch support following comments from Debajit
diff --git a/src/com/google/wireless/gdata/data/XmlUtils.java b/src/com/google/wireless/gdata/data/XmlUtils.java
index 1c067d3..d838372 100644
--- a/src/com/google/wireless/gdata/data/XmlUtils.java
+++ b/src/com/google/wireless/gdata/data/XmlUtils.java
@@ -77,6 +77,30 @@
                 + "depth " + parentDepth);
     }
 
+  /**
+   * Supply a 'skipSubTree' API which, for some reason, the kxml2 pull parser
+   * hasn't implemented.
+   */
+  public void skipSubTree(XmlPullParser parser)
+      throws XmlPullParserException, IOException {
+    // Iterate the remaining structure for this element, discarding events
+    // until we hit the element's corresponding end tag.
+    int level = 1;
+    while (level > 0) {
+      int eventType = parser.next();
+      switch (eventType) {
+        case XmlPullParser.START_TAG:
+          ++level;
+          break;
+        case XmlPullParser.END_TAG:
+          --level;
+          break;
+        default:
+          break;
+      }
+    }
+  }
+
 //    public static void parseChildrenToSerializer(XmlPullParser parser, XmlSerializer serializer)
 //            throws XmlPullParserException, IOException {
 //        int parentDepth = parser.getDepth();
diff --git a/src/com/google/wireless/gdata2/client/GDataClient.java b/src/com/google/wireless/gdata2/client/GDataClient.java
index ef3d108..90a3a2b 100644
--- a/src/com/google/wireless/gdata2/client/GDataClient.java
+++ b/src/com/google/wireless/gdata2/client/GDataClient.java
@@ -164,4 +164,23 @@
     public InputStream updateMediaEntry(String editUri, String authToken, String eTag,
             InputStream mediaEntryInputStream, String contentType)
         throws HttpException, IOException;
+
+  /**
+   * Connects to a GData server (specified by the batchUrl) and submits a
+   * batch for processing.  The response from the server is returned as an
+   * {@link InputStream}.  The caller is responsible for calling
+   * {@link InputStream#close()} on the returned {@link InputStream}.
+   *
+   * @param batchUrl The batch url to which the batch is submitted.
+   * @param authToken the authentication token that should be used when
+   * submitting the batch.
+   * @param batch The batch of entries to submit.
+   * @throws IOException Thrown if an io error occurs while communicating with
+   * the service.
+   * @throws HttpException if the service returns an error response.
+   */
+  InputStream submitBatch(String batchUrl,
+       String authToken,
+       GDataSerializer batch)
+       throws HttpException, IOException;
 }
diff --git a/src/com/google/wireless/gdata2/client/GDataParserFactory.java b/src/com/google/wireless/gdata2/client/GDataParserFactory.java
index 56546fe..f08b384 100644
--- a/src/com/google/wireless/gdata2/client/GDataParserFactory.java
+++ b/src/com/google/wireless/gdata2/client/GDataParserFactory.java
@@ -8,6 +8,7 @@
 import com.google.wireless.gdata2.serializer.GDataSerializer;
 
 import java.io.InputStream;
+import java.util.Enumeration;
 
 /**
  * Factory that creates {@link GDataParser}s and {@link GDataSerializer}s.
@@ -45,4 +46,12 @@
    * @return The GDataSerializer that will serialize entry.
    */
   GDataSerializer createSerializer(Entry entry);
+
+  /**
+   * Creates a new {@link GDataSerializer} for the provided batch of entries.
+   *
+   * @param batch An enumeration of entries comprising the batch.
+   * @return The GDataSerializer that will serialize the batch.
+   */
+  GDataSerializer createSerializer(Enumeration batch);
 }
diff --git a/src/com/google/wireless/gdata2/client/GDataServiceClient.java b/src/com/google/wireless/gdata2/client/GDataServiceClient.java
index ca12df3..e39f580 100644
--- a/src/com/google/wireless/gdata2/client/GDataServiceClient.java
+++ b/src/com/google/wireless/gdata2/client/GDataServiceClient.java
@@ -11,6 +11,7 @@
 
 import java.io.IOException;
 import java.io.InputStream;
+import java.util.Enumeration;
 
 /**
  * Abstract base class for service-specific clients to access GData feeds.
@@ -217,4 +218,23 @@
             }
         }
     }
+
+  /**
+   * Submits a batch of operations.
+   * @param feedEntryClass the type of the entry to expect.
+   * @param batchUrl The url to which the batch is submitted.
+   * @param authToken The authentication token for this user.
+   * @param entries an enumeration of the entries to submit.
+   * @throws HttpException if the service returns an error response
+   * @throws ParseException Thrown if the server response cannot be parsed.
+   * @throws IOException Thrown if an error occurs while communicating with the
+   * GData service.
+   */
+  public GDataParser submitBatch(Class feedEntryClass, String batchUrl, String authToken,
+      Enumeration entries) throws ParseException, IOException, HttpException {
+    GDataSerializer serializer = gDataParserFactory.createSerializer(entries);
+    InputStream is = gDataClient.submitBatch(batchUrl, authToken, serializer);
+    return gDataParserFactory.createParser(feedEntryClass, is);
+  }
+
 }
diff --git a/src/com/google/wireless/gdata2/contacts/parser/xml/XmlContactsGDataParserFactory.java b/src/com/google/wireless/gdata2/contacts/parser/xml/XmlContactsGDataParserFactory.java
index 2eb3209..9b13d8d 100644
--- a/src/com/google/wireless/gdata2/contacts/parser/xml/XmlContactsGDataParserFactory.java
+++ b/src/com/google/wireless/gdata2/contacts/parser/xml/XmlContactsGDataParserFactory.java
@@ -14,11 +14,13 @@
 import com.google.wireless.gdata2.parser.xml.XmlParserFactory;
 import com.google.wireless.gdata2.parser.xml.XmlMediaEntryGDataParser;
 import com.google.wireless.gdata2.serializer.GDataSerializer;
+import com.google.wireless.gdata2.serializer.xml.XmlBatchGDataSerializer;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.InputStream;
+import java.util.Enumeration;
 
 /**
  * GDataParserFactory that creates XML GDataParsers and GDataSerializers for
@@ -122,4 +124,14 @@
     }
     throw new IllegalArgumentException("unexpected entry type, " + entry.getClass().toString());
   }
+
+  /**
+   * Creates a new {@link GDataSerializer} for the given batch.
+   *
+   * @param batch the {@link Enumeration} of entries in the batch.
+   * @return The {@link GDataSerializer} tha will serialize this batch.
+   */
+  public GDataSerializer createSerializer(Enumeration batch) {
+    return new XmlBatchGDataSerializer(this, xmlFactory, batch);
+  }
 }
diff --git a/src/com/google/wireless/gdata2/contacts/serializer/xml/XmlContactEntryGDataSerializer.java b/src/com/google/wireless/gdata2/contacts/serializer/xml/XmlContactEntryGDataSerializer.java
index 1b8392c..4646490 100644
--- a/src/com/google/wireless/gdata2/contacts/serializer/xml/XmlContactEntryGDataSerializer.java
+++ b/src/com/google/wireless/gdata2/contacts/serializer/xml/XmlContactEntryGDataSerializer.java
@@ -33,7 +33,6 @@
     super(factory, entry);
   }
 
-  @Override
   protected void declareExtraEntryNamespaces(XmlSerializer serializer) throws IOException {
     super.declareExtraEntryNamespaces(serializer);
     serializer.setPrefix(XmlContactsGDataParser.NAMESPACE_CONTACTS,
diff --git a/src/com/google/wireless/gdata2/data/Entry.java b/src/com/google/wireless/gdata2/data/Entry.java
index fff706e..f0fbf03 100644
--- a/src/com/google/wireless/gdata2/data/Entry.java
+++ b/src/com/google/wireless/gdata2/data/Entry.java
@@ -2,6 +2,7 @@
 
 package com.google.wireless.gdata2.data;
 
+import com.google.wireless.gdata2.data.batch.BatchInfo;
 import com.google.wireless.gdata2.parser.ParseException;
 
 /**
@@ -26,6 +27,7 @@
     private String updateDate = null;
     private String eTagValue = null;
     private boolean deleted = false;
+    private BatchInfo batchInfo = null;
     
     /**
      * Creates a new empty entry.
@@ -50,6 +52,7 @@
         publicationDate = null;
         updateDate = null;
         deleted = false;
+        batchInfo = null;
     }
 
     /**
@@ -235,7 +238,23 @@
     public void setETag(String eTag) {
         eTagValue = eTag;
     }
- 
+
+    /**
+     * Used internally to access batch related properties.
+     * Clients should use {@link BatchUtils} instead.
+     */
+    public BatchInfo getBatchInfo() {
+        return batchInfo;
+    }
+
+    /**
+     * Used internally to update batch related properties.
+     * Clients should use {@link BatchUtils} instead.
+     */
+    public void setBatchInfo(BatchInfo batchInfo) {
+        this.batchInfo = batchInfo;
+    }
+
     /**
      * Appends the name and value to this StringBuffer, if value is not null.
      * Uses the format: "<NAME>: <VALUE>\n"
@@ -274,6 +293,10 @@
         appendIfNotNull(sb, "PUBLICATION DATE", publicationDate);
         appendIfNotNull(sb, "UPDATE DATE", updateDate);
         appendIfNotNull(sb, "DELETED", String.valueOf(deleted));
+        appendIfNotNull(sb, "ETAG", String.valueOf(eTagValue));
+        if (batchInfo != null) {
+          appendIfNotNull(sb, "BATCH", batchInfo.toString());
+        }
     }
 
     /**
diff --git a/src/com/google/wireless/gdata2/data/batch/BatchInfo.java b/src/com/google/wireless/gdata2/data/batch/BatchInfo.java
new file mode 100644
index 0000000..ef461d2
--- /dev/null
+++ b/src/com/google/wireless/gdata2/data/batch/BatchInfo.java
@@ -0,0 +1,30 @@
+// Copyright 2009 Google Inc. All Rights Reserved.
+
+package com.google.wireless.gdata2.data.batch;
+
+/**
+ * Opaque container for batch related info associated with an entry.
+ * Clients should use {@link BatchUtils} to access this data instead.
+ */
+public class BatchInfo {
+  String id;
+  String operation;
+  BatchStatus status;
+  BatchInterrupted interrupted;
+
+  /* package */ BatchInfo() {
+  }
+
+  public String toString() {
+    StringBuffer sb = new StringBuffer();
+    sb.append("id: ").append(id);
+    sb.append(" op: ").append(operation);
+    if (status != null) {
+      sb.append(" sc: ").append(status.getStatusCode());
+    }
+    if (interrupted != null) {
+      sb.append(" interrupted: ").append(interrupted.getReason());
+    }
+    return sb.toString();
+  }
+}
diff --git a/src/com/google/wireless/gdata2/data/batch/BatchInterrupted.java b/src/com/google/wireless/gdata2/data/batch/BatchInterrupted.java
new file mode 100644
index 0000000..ff8a69f
--- /dev/null
+++ b/src/com/google/wireless/gdata2/data/batch/BatchInterrupted.java
@@ -0,0 +1,75 @@
+// Copyright 2009 Google Inc. All Rights Reserved.
+
+package com.google.wireless.gdata2.data.batch;
+
+/**
+ * Holds status information about a batch that was interrupted.
+ */
+public class BatchInterrupted {
+  private String reason;
+  private int total;
+  private int success;
+  private int error;
+
+  /**
+   * Creates a new empty BatchInterrupted.
+   */
+  public BatchInterrupted() {
+  }
+
+  /**
+   * Returns the reason for this failure.
+   */
+  public String getReason() {
+    return reason;
+  }
+
+  /**
+   * Sets the reason for this failure.
+   */
+  public void setReason(String reason) {
+    this.reason = reason;
+  }
+
+  /**
+   * Gets the total number of entries read.
+   */
+  public int getTotalCount() {
+    return total;
+  }
+
+  /**
+   * Sets the number of entries read.
+   */
+  public void setTotalCount(int total) {
+    this.total = total;
+  }
+
+  /**
+   * Gets the number of entries that were processed successfully.
+   */
+  public int getSuccessCount() {
+    return success;
+  }
+
+  /**
+   * Sets the number of entries successfuly processed.
+   */
+  public void setSuccessCount(int success) {
+    this.success = success;
+  }
+
+  /**
+   * Gets the number of entries that were rejected.
+   */
+  public int getErrorCount() {
+    return error;
+  }
+
+  /**
+   * Sets the number of entries that failed.
+   */
+  public void setErrorCount(int error) {
+    this.error = error;
+  }
+}
diff --git a/src/com/google/wireless/gdata2/data/batch/BatchStatus.java b/src/com/google/wireless/gdata2/data/batch/BatchStatus.java
new file mode 100644
index 0000000..7a19afd
--- /dev/null
+++ b/src/com/google/wireless/gdata2/data/batch/BatchStatus.java
@@ -0,0 +1,75 @@
+// Copyright 2009 Google Inc. All Rights Reserved.
+
+package com.google.wireless.gdata2.data.batch;
+
+/**
+ * Holds result status for an individual batch operation.
+ */
+public class BatchStatus {
+  private int statusCode;
+  private String reason;
+  private String contentType;
+  private String content;
+
+  /**
+   * Creates a new empty BatchStatus.
+   */
+  public BatchStatus() {
+  }
+
+  /**
+   * Returns the status of this operation.
+   */
+  public int getStatusCode() {
+    return statusCode;
+  }
+
+  /**
+   * Sets the status of this operation.
+   */
+  public void setStatusCode(int statusCode) {
+    this.statusCode = statusCode;
+  }
+
+  /**
+   * Returns the reason for this status.
+   */
+  public String getReason() {
+    return reason;
+  }
+
+  /**
+   * Sets the reason for this status.
+   */
+  public void setReason(String reason) {
+    this.reason = reason;
+  }
+
+  /**
+   * Returns the content type of the response.
+   */
+  public String getContentType() {
+    return contentType;
+  }
+
+  /**
+   * Sets the content type of the response.
+   */
+  public void setContentType(String contentType) {
+    this.contentType = contentType;
+  }
+
+  /**
+   * Returns the response content, if any.
+   */
+  public String getContent() {
+    return content;
+  }
+
+  /**
+   * Sets the response content.
+   */
+  public void setContent(String content) {
+    this.content = content;
+  }
+}
diff --git a/src/com/google/wireless/gdata2/data/batch/BatchUtils.java b/src/com/google/wireless/gdata2/data/batch/BatchUtils.java
new file mode 100644
index 0000000..c237601
--- /dev/null
+++ b/src/com/google/wireless/gdata2/data/batch/BatchUtils.java
@@ -0,0 +1,93 @@
+// Copyright 2009 Google Inc. All Rights Reserved.
+
+package com.google.wireless.gdata2.data.batch;
+
+import com.google.wireless.gdata2.data.Entry;
+
+/**
+ * Utility methods for dealing with batch operations.
+ */
+public class BatchUtils {
+
+  public static final String OPERATION_INSERT = "insert";
+
+  public static final String OPERATION_UPDATE = "update";
+
+  public static final String OPERATION_QUERY = "query";
+
+  public static final String OPERATION_DELETE = "delete";
+
+  private BatchUtils() {
+  }
+
+  /**
+   * Returns the batch id of the given entry, or null if none.
+   */
+  public static String getBatchId(Entry entry) {
+    BatchInfo info = entry.getBatchInfo();
+    return info == null ? null : info.id;
+  }
+
+  /**
+   * Sets the batch id of the given entry.
+   */
+  public static void setBatchId(Entry entry, String id) {
+    getOrCreateBatchInfo(entry).id = id;
+  }
+
+  /**
+   * Returns the batch operation of the given entry.
+   */
+  public static String getBatchOperation(Entry entry) {
+    BatchInfo info = entry.getBatchInfo();
+    return info == null ? null : info.operation;
+  }
+
+  /**
+   * Sets the operation for the given batch entry.
+   */
+  public static void setBatchOperation(Entry entry, String operation) {
+    getOrCreateBatchInfo(entry).operation = operation;
+  }
+
+  /**
+   * Returns the status of the given batch entry.
+   */
+  public static BatchStatus getBatchStatus(Entry entry) {
+    BatchInfo info = entry.getBatchInfo();
+    return info == null ? null : info.status;
+  }
+
+  /**
+   * Sets the status of the given batch entry.
+   */
+  public static void setBatchStatus(Entry entry, BatchStatus status) {
+    getOrCreateBatchInfo(entry).status = status;
+  }
+
+  /**
+   * Returns the interrupted status of the given entry, or null if none.
+   */
+  public static BatchInterrupted getBatchInterrupted(Entry entry) {
+    BatchInfo info = entry.getBatchInfo();
+    return info == null ? null : info.interrupted;
+  }
+
+  /**
+   * Sets the interrupted status of the given entry.
+   */
+  public static void setBatchInterrupted(Entry entry,
+      BatchInterrupted interrupted) {
+    getOrCreateBatchInfo(entry).interrupted = interrupted;
+  }
+
+  private static BatchInfo getOrCreateBatchInfo(Entry entry) {
+    BatchInfo info = entry.getBatchInfo();
+    if (info == null) {
+      info = new BatchInfo();
+      entry.setBatchInfo(info);
+    }
+    return info;
+  }
+
+}
diff --git a/src/com/google/wireless/gdata2/parser/xml/XmlGDataParser.java b/src/com/google/wireless/gdata2/parser/xml/XmlGDataParser.java
index 44fd2a2..149882d 100644
--- a/src/com/google/wireless/gdata2/parser/xml/XmlGDataParser.java
+++ b/src/com/google/wireless/gdata2/parser/xml/XmlGDataParser.java
@@ -8,6 +8,9 @@
 import com.google.wireless.gdata2.data.XmlUtils;
 import com.google.wireless.gdata2.parser.GDataParser;
 import com.google.wireless.gdata2.parser.ParseException;
+import com.google.wireless.gdata2.data.batch.BatchInterrupted;
+import com.google.wireless.gdata2.data.batch.BatchStatus;
+import com.google.wireless.gdata2.data.batch.BatchUtils;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -37,6 +40,13 @@
   public static final String NAMESPACE_GD_URI =
       "http://schemas.google.com/g/2005";
 
+  /** Namespace prefix for GData batch operations */
+  public static final String NAMESPACE_BATCH = "batch";
+
+  /** Namespace uri for GData batch operations */
+  public static final String NAMESPACE_BATCH_URI =
+      "http://schemas.google.com/gdata/batch";
+
   private final InputStream is;
   private final XmlPullParser parser;
   private boolean isInBadState;
@@ -210,7 +220,6 @@
           } else {
             handleExtraElementInFeed(feed);
           }
-          break;
         default:
           break;
       }
@@ -388,6 +397,30 @@
   }
 
   /**
+   * Supply a 'skipSubTree' API which, for some reason, the kxml2 pull parser
+   * hasn't implemented.
+   */
+  protected void skipSubTree()
+      throws XmlPullParserException, IOException {
+    // Iterate the remaining structure for this element, discarding events
+    // until we hit the element's corresponding end tag.
+    int level = 1;
+    while (level > 0) {
+      int eventType = parser.next();
+      switch (eventType) {
+        case XmlPullParser.START_TAG:
+          ++level;
+          break;
+        case XmlPullParser.END_TAG:
+          --level;
+          break;
+        default:
+          break;
+      }
+    }
+  }
+
+  /**
    * Parses the current entry in the XML document.  Assumes that the parser
    * is currently pointing just at the beginning of an 
    * &lt;entry&gt;. 
@@ -465,6 +498,8 @@
             entry.setUpdateDate(XmlUtils.extractChildText(parser));
           } else if ("deleted".equals(name)) {
             entry.setDeleted(true);
+          } else if (NAMESPACE_BATCH_URI.equals(parser.getNamespace())) {
+            handleBatchInfo(entry);
           } else {
             handleExtraElementInEntry(entry);
           }
@@ -517,6 +552,43 @@
     }
   }
 
+  private void handleBatchInfo(Entry entry)
+      throws IOException, XmlPullParserException {
+    String name = parser.getName();
+    if ("status".equals(name)) {
+      BatchStatus status = new BatchStatus();
+      BatchUtils.setBatchStatus(entry, status);
+      status.setStatusCode(getIntAttribute(parser, "code"));
+      status.setReason(getAttribute(parser, "reason"));
+      status.setContentType(getAttribute(parser, "content-type"));
+      // TODO: Read sub-tree into content.
+      skipSubTree();
+    } else if ("id".equals(name)) {
+      BatchUtils.setBatchId(entry, XmlUtils.extractChildText(parser));
+    } else if ("operation".equals(name)) {
+      BatchUtils.setBatchOperation(entry, getAttribute(parser, "type"));
+    } else if ("interrupted".equals(name)) {
+      BatchInterrupted interrupted = new BatchInterrupted();
+      BatchUtils.setBatchInterrupted(entry, interrupted);
+      interrupted.setReason(getAttribute(parser, "reason"));
+      interrupted.setErrorCount(getIntAttribute(parser, "error"));
+      interrupted.setSuccessCount(getIntAttribute(parser, "success"));
+      interrupted.setTotalCount(getIntAttribute(parser, "parsed"));
+      // TODO: Read sub-tree into content.
+      skipSubTree();
+    } else {
+      throw new XmlPullParserException("Unexpected batch element " + name);
+    }
+  }
+
+  private static String getAttribute(XmlPullParser parser, String name) {
+    return parser.getAttributeValue(null /* ns */, name);
+  }
+
+  private static int getIntAttribute(XmlPullParser parser, String name) {
+    return Integer.parseInt(getAttribute(parser, name));
+  }
+
   /*
   * (non-Javadoc)
   * @see com.google.wireless.gdata2.parser.GDataParser#close()
diff --git a/src/com/google/wireless/gdata2/serializer/GDataSerializer.java b/src/com/google/wireless/gdata2/serializer/GDataSerializer.java
index 3d75485..e79ad80 100644
--- a/src/com/google/wireless/gdata2/serializer/GDataSerializer.java
+++ b/src/com/google/wireless/gdata2/serializer/GDataSerializer.java
@@ -31,6 +31,11 @@
     public static final int FORMAT_UPDATE = 2;
 
     /**
+     * Serialize the entry as part of a batch of operations.
+     */
+    public static final int FORMAT_BATCH = 3;
+
+    /**
      * Returns the Content-Type for this serialization format.
      * @return The Content-Type for this serialization format.
      */
@@ -43,6 +48,7 @@
      * @see #FORMAT_FULL
      * @see #FORMAT_CREATE
      * @see #FORMAT_UPDATE
+     * @see #FORMAT_BATCH
      * 
      * @param out The {@link OutputStream} to which the entry should be 
      * serialized.
diff --git a/src/com/google/wireless/gdata2/serializer/xml/XmlBatchGDataSerializer.java b/src/com/google/wireless/gdata2/serializer/xml/XmlBatchGDataSerializer.java
new file mode 100644
index 0000000..d929263
--- /dev/null
+++ b/src/com/google/wireless/gdata2/serializer/xml/XmlBatchGDataSerializer.java
@@ -0,0 +1,93 @@
+// Copyright 2008 Google Inc. All Rights Reserved.
+
+package com.google.wireless.gdata2.serializer.xml;
+
+import com.google.wireless.gdata2.serializer.GDataSerializer;
+import com.google.wireless.gdata2.parser.ParseException;
+import com.google.wireless.gdata2.parser.xml.XmlParserFactory;
+import com.google.wireless.gdata2.parser.xml.XmlGDataParser;
+import com.google.wireless.gdata2.client.GDataParserFactory;
+import com.google.wireless.gdata2.data.Entry;
+
+import org.xmlpull.v1.XmlSerializer;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.OutputStream;
+import java.io.IOException;
+import java.util.Enumeration;
+
+/**
+ * Serializes a batch of GData requests as an XML stream.
+ */
+public class XmlBatchGDataSerializer implements GDataSerializer {
+
+  private final GDataParserFactory gdataFactory;
+  private final XmlParserFactory xmlFactory;
+  private final Enumeration batch;
+
+  /*
+   * Constructs an XmlBatchGDataSerializer for serializing the given batch
+   * of GData requests
+   *
+   * @param gdataFactory used to create serializers for individual requests
+   * @param xmlFactory used to create the XML stream
+   * @param Enumeration the batch of requests to serialize
+   */
+  public XmlBatchGDataSerializer(GDataParserFactory gdataFactory,
+      XmlParserFactory xmlFactory, Enumeration batch) {
+    this.gdataFactory = gdataFactory;
+    this.xmlFactory = xmlFactory;
+    this.batch = batch;
+  }
+
+  public String getContentType() {
+    return "application/atom+xml";
+  }
+
+  public void serialize(OutputStream out, int format)
+      throws IOException, ParseException {
+    XmlSerializer serializer;
+    try {
+      serializer = xmlFactory.createSerializer();
+    } catch (XmlPullParserException e) {
+      throw new ParseException("Unable to create XmlSerializer.", e);
+    }
+
+    serializer.setOutput(out, "UTF-8");
+    serializer.startDocument("UTF-8", new Boolean(false));
+
+    declareNamespaces(serializer);
+
+    boolean first = true;
+    while (batch.hasMoreElements()) {
+      Entry entry = (Entry) batch.nextElement();
+      XmlEntryGDataSerializer entrySerializer = (XmlEntryGDataSerializer)
+          gdataFactory.createSerializer(entry);
+
+      if (first) {
+        // Let the first entry serializer declare extra namespaces.
+        first = false;
+        serializer.startTag(XmlGDataParser.NAMESPACE_ATOM_URI, "feed");
+        entrySerializer.declareExtraEntryNamespaces(serializer);
+      }
+      entrySerializer.serialize(out, GDataSerializer.FORMAT_BATCH);
+    }
+
+    if (first) {
+      serializer.startTag(XmlGDataParser.NAMESPACE_ATOM_URI, "feed");
+    }
+
+    serializer.endTag(XmlGDataParser.NAMESPACE_ATOM_URI, "feed");
+    serializer.endDocument();
+    serializer.flush();
+  }
+
+  private static void declareNamespaces(XmlSerializer serializer) throws IOException {
+    serializer.setPrefix("" /* default ns */,
+        XmlGDataParser.NAMESPACE_ATOM_URI);
+    serializer.setPrefix(XmlGDataParser.NAMESPACE_GD,
+        XmlGDataParser.NAMESPACE_GD_URI);
+    serializer.setPrefix(XmlGDataParser.NAMESPACE_BATCH,
+        XmlGDataParser.NAMESPACE_BATCH_URI);
+  }
+}
diff --git a/src/com/google/wireless/gdata2/serializer/xml/XmlEntryGDataSerializer.java b/src/com/google/wireless/gdata2/serializer/xml/XmlEntryGDataSerializer.java
index 23036ea..654348f 100644
--- a/src/com/google/wireless/gdata2/serializer/xml/XmlEntryGDataSerializer.java
+++ b/src/com/google/wireless/gdata2/serializer/xml/XmlEntryGDataSerializer.java
@@ -2,6 +2,7 @@
 
 package com.google.wireless.gdata2.serializer.xml;
 
+import com.google.wireless.gdata2.data.batch.BatchUtils;
 import com.google.wireless.gdata2.data.Entry;
 import com.google.wireless.gdata2.data.ExtendedProperty;
 import com.google.wireless.gdata2.data.StringUtils;
@@ -68,15 +69,19 @@
     // TODO: make the output compact
 
     serializer.setOutput(out, "UTF-8");
-    serializer.startDocument("UTF-8", new Boolean(false));
+    if (format != FORMAT_BATCH) {
+      serializer.startDocument("UTF-8", new Boolean(false));
+      declareEntryNamespaces(serializer);
+    }
 
-    declareEntryNamespaces(serializer);
     serializer.startTag(XmlGDataParser.NAMESPACE_ATOM_URI, "entry");
 
     serializeEntryContents(serializer, format);
 
     serializer.endTag(XmlGDataParser.NAMESPACE_ATOM_URI, "entry");
-    serializer.endDocument();
+    if (format != FORMAT_BATCH) {
+      serializer.endDocument();
+    }
     serializer.flush();
   }
 
@@ -102,6 +107,10 @@
       int format)
       throws ParseException, IOException {
 
+    if (format == FORMAT_BATCH) {
+      serializeBatchInfo(serializer);
+    }
+
     if (format != FORMAT_CREATE) {
       serializeId(serializer, entry.getId());
     }
@@ -277,4 +286,24 @@
     serializer.text(updateDate);
     serializer.endTag(null /* ns */, "updated");
   }
+
+  private void serializeBatchInfo(XmlSerializer serializer)
+      throws IOException {
+    if (!StringUtils.isEmpty(entry.getETag())) {
+     serializer.attribute(XmlGDataParser.NAMESPACE_GD_URI, "etag",
+         entry.getETag());
+    }
+    if (!StringUtils.isEmpty(BatchUtils.getBatchOperation(entry))) {
+      serializer.startTag(XmlGDataParser.NAMESPACE_BATCH_URI, "operation");
+      serializer.attribute(null /* ns */, "type",
+          BatchUtils.getBatchOperation(entry));
+      serializer.endTag(XmlGDataParser.NAMESPACE_BATCH_URI, "operation");
+    }
+    if (!StringUtils.isEmpty(BatchUtils.getBatchId(entry))) {
+      serializer.startTag(XmlGDataParser.NAMESPACE_BATCH_URI, "id");
+      serializer.text(BatchUtils.getBatchId(entry));
+      serializer.endTag(XmlGDataParser.NAMESPACE_BATCH_URI, "id");
+    }
+  }
+
 }