Index: src/main/java/org/xmpp/packet/JID.java
===================================================================
--- src/main/java/org/xmpp/packet/JID.java	(revision 11060)
+++ src/main/java/org/xmpp/packet/JID.java	(working copy)
@@ -8,6 +8,8 @@
 
 package org.xmpp.packet;
 
+import net.jcip.annotations.Immutable;
+
 import org.jivesoftware.stringprep.IDNA;
 import org.jivesoftware.stringprep.Stringprep;
 import org.jivesoftware.stringprep.StringprepException;
@@ -17,6 +19,7 @@
 import java.io.IOException;
 import java.io.ObjectInput;
 import java.io.ObjectOutput;
+import java.io.Serializable;
 import java.util.Map;
 import java.util.Queue;
 import java.util.concurrent.ConcurrentHashMap;
@@ -42,7 +45,8 @@
  *
  * @author Matt Tucker
  */
-public class JID implements Comparable<JID>, Externalizable {
+@Immutable
+public class JID implements Comparable<JID>, Serializable {
 
     // Stringprep operations are very expensive. Therefore, we cache node, domain and
     // resource values that have already had stringprep applied so that we can check
@@ -51,12 +55,12 @@
     private static final Cache<String> DOMAINPREP_CACHE = new Cache<String>(500);
     private static final Cache<String> RESOURCEPREP_CACHE = new Cache<String>(10000);
 
-    private String node;
-    private String domain;
-    private String resource;
+    private final String node;
+    private final String domain;
+    private final String resource;
 
-    private String cachedFullJID;
-    private String cachedBareJID;
+    private final String cachedFullJID;
+    private final String cachedBareJID;
 
     /**
      * Escapes the node portion of a JID according to "JID Escaping" (JEP-0106).
@@ -213,26 +217,19 @@
     }
 
     /**
-     * Constructor added for Externalizable. Do not use this constructor.
-     */
-    public JID() {
-    }
-
-    /**
      * Constructs a JID from it's String representation.
      *
      * @param jid a valid JID.
      * @throws IllegalArgumentException if the JID is not valid.
      */
     public JID(String jid) {
-        if (jid == null) {
-            throw new NullPointerException("JID cannot be null");
-        }
-        String[] parts = getParts(jid);
+    	this(getParts(jid));
+    }
 
-        init(parts[0], parts[1], parts[2]);
+    private JID(String[] parts) {
+    	this(parts[0], parts[1], parts[2], false);
     }
-
+    
     /**
      * Constructs a JID given a node, domain, and resource.
      *
@@ -242,10 +239,7 @@
      * @throws IllegalArgumentException if the JID is not valid.
      */
     public JID(String node, String domain, String resource) {
-        if (domain == null) {
-            throw new NullPointerException("Domain cannot be null");
-        }
-        init(node, domain, resource);
+        this(node, domain, resource, false);
     }
 
     /**
@@ -266,12 +260,81 @@
             this.node = node;
             this.domain = domain;
             this.resource = resource;
-            // Cache the bare and full JID String representation
-            updateCache();
         }
         else {
-            init(node, domain, resource);
+            // Set node and resource to null if they are the empty string.
+            if (node != null && node.equals("")) {
+                node = null;
+            }
+            if (resource != null && resource.equals("")) {
+                resource = null;
+            }
+            // Stringprep (node prep, resourceprep, etc).
+            try {
+                if (!NODEPREP_CACHE.contains(node)) {
+                    this.node = Stringprep.nodeprep(node);
+                    // Validate field is not greater than 1023 bytes. UTF-8 characters use two bytes.
+                    if (this.node != null && this.node.length()*2 > 1023) {
+                        throw new IllegalArgumentException("Node cannot be larger than 1023 bytes. " +
+                                "Size is " + (this.node.length() * 2) + " bytes.");
+                    }
+                    NODEPREP_CACHE.put(this.node);
+                }
+                else {
+                    this.node = node;
+                }
+                // XMPP specifies that domains should be run through IDNA and
+                // that they should be run through nameprep before doing any
+                // comparisons. We always run the domain through nameprep to
+                // make comparisons easier later.
+                if (!DOMAINPREP_CACHE.contains(domain)) {
+                    this.domain = Stringprep.nameprep(IDNA.toASCII(domain), false);
+                    // Validate field is not greater than 1023 bytes. UTF-8 characters use two bytes.
+                    if (this.domain.length()*2 > 1023) {
+                        throw new IllegalArgumentException("Domain cannot be larger than 1023 bytes. " +
+                                "Size is " + (this.domain.length() * 2) + " bytes.");
+                    }
+                    DOMAINPREP_CACHE.put(this.domain);
+                }
+                else {
+                    this.domain = domain;
+                }
+                this.resource = resourceprep(resource);
+                // Validate field is not greater than 1023 bytes. UTF-8 characters use two bytes.
+                if (resource != null && resource.length()*2 > 1023) {
+                    throw new IllegalArgumentException("Resource cannot be larger than 1023 bytes. " +
+                            "Size is " + (resource.length() * 2) + " bytes.");
+                }
+            }
+            catch (Exception e) {
+                StringBuilder buf = new StringBuilder();
+                if (node != null) {
+                    buf.append(node).append("@");
+                }
+                buf.append(domain);
+                if (resource != null) {
+                    buf.append("/").append(resource);
+                }
+                throw new IllegalArgumentException("Illegal JID: " + buf.toString(), e);
+            }
         }
+        
+        // Cache the bare JID
+        StringBuilder buf = new StringBuilder(40);
+        if (node != null) {
+            buf.append(node).append("@");
+        }
+        buf.append(domain);
+        cachedBareJID = buf.toString();
+
+        // Cache the full JID
+        if (resource != null) {
+            buf.append("/").append(resource);
+            cachedFullJID = buf.toString();
+        }
+        else {
+            cachedFullJID = cachedBareJID;
+        }
     }
 
     /**
@@ -331,94 +394,6 @@
     }
 
     /**
-     * Transforms the JID parts using the appropriate Stringprep profiles, then
-     * validates them. If they are fully valid, the field values are saved, otherwise
-     * an IllegalArgumentException is thrown.
-     *
-     * @param node the node.
-     * @param domain the domain.
-     * @param resource the resource.
-     */
-    private void init(String node, String domain, String resource) {
-        // Set node and resource to null if they are the empty string.
-        if (node != null && node.equals("")) {
-            node = null;
-        }
-        if (resource != null && resource.equals("")) {
-            resource = null;
-        }
-        // Stringprep (node prep, resourceprep, etc).
-        try {
-            if (!NODEPREP_CACHE.contains(node)) {
-                this.node = Stringprep.nodeprep(node);
-                // Validate field is not greater than 1023 bytes. UTF-8 characters use two bytes.
-                if (this.node != null && this.node.length()*2 > 1023) {
-                    throw new IllegalArgumentException("Node cannot be larger than 1023 bytes. " +
-                            "Size is " + (this.node.length() * 2) + " bytes.");
-                }
-                NODEPREP_CACHE.put(this.node);
-            }
-            else {
-                this.node = node;
-            }
-            // XMPP specifies that domains should be run through IDNA and
-            // that they should be run through nameprep before doing any
-            // comparisons. We always run the domain through nameprep to
-            // make comparisons easier later.
-            if (!DOMAINPREP_CACHE.contains(domain)) {
-                this.domain = Stringprep.nameprep(IDNA.toASCII(domain), false);
-                // Validate field is not greater than 1023 bytes. UTF-8 characters use two bytes.
-                if (this.domain.length()*2 > 1023) {
-                    throw new IllegalArgumentException("Domain cannot be larger than 1023 bytes. " +
-                            "Size is " + (this.domain.length() * 2) + " bytes.");
-                }
-                DOMAINPREP_CACHE.put(this.domain);
-            }
-            else {
-                this.domain = domain;
-            }
-            this.resource = resourceprep(resource);
-            // Validate field is not greater than 1023 bytes. UTF-8 characters use two bytes.
-            if (resource != null && resource.length()*2 > 1023) {
-                throw new IllegalArgumentException("Resource cannot be larger than 1023 bytes. " +
-                        "Size is " + (resource.length() * 2) + " bytes.");
-            }
-            // Cache the bare and full JID String representation
-            updateCache();
-        }
-        catch (Exception e) {
-            StringBuilder buf = new StringBuilder();
-            if (node != null) {
-                buf.append(node).append("@");
-            }
-            buf.append(domain);
-            if (resource != null) {
-                buf.append("/").append(resource);
-            }
-            throw new IllegalArgumentException("Illegal JID: " + buf.toString(), e);
-        }
-    }
-
-    private void updateCache() {
-        // Cache the bare JID
-        StringBuilder buf = new StringBuilder(40);
-        if (node != null) {
-            buf.append(node).append("@");
-        }
-        buf.append(domain);
-        cachedBareJID = buf.toString();
-
-        // Cache the full JID
-        if (resource != null) {
-            buf.append("/").append(resource);
-            cachedFullJID = buf.toString();
-        }
-        else {
-            cachedFullJID = cachedBareJID;
-        }
-    }
-
-    /**
      * Returns the node, or <tt>null</tt> if this JID does not contain node information.
      *
      * @return the node.
@@ -612,18 +587,46 @@
 		}
     }
 
-    public void writeExternal(ObjectOutput out) throws IOException {
-        ExternalizableUtil.getInstance().writeSafeUTF(out, toString());
+    private Object writeReplace() {
+        return new SerializationProxy(this);
     }
 
-    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
-        String jid = ExternalizableUtil.getInstance().readSafeUTF(in);
-        String[] parts = getParts(jid);
+    /**
+	 * A serialization proxy object, as described by Josh Bloch in "Effective
+	 * Java" (Second Edition) as the 'Serialization Proxy Pattern'.
+	 * <p>
+	 * The implementation of this Proxy implements {@link Externalizable}, which
+	 * allows the implementation to take responsibility for saving the content
+	 * of instances on the serialization stream. This way, the pluggable
+	 * interface provided by {@link ExternalizableUtil} can be used.
+	 * 
+	 * @author Guus der Kinderen, guus.der.kinderen@gmail.com
+	 * @see "Effective Java, Second Edition"
+	 */
+    private static class SerializationProxy implements Externalizable {
 
-        this.node = parts[0];
-        this.domain = parts[1];
-        this.resource = parts[2];
-        // Cache the bare and full JID String representation
-        updateCache();
-    }
+		JID jid;
+		
+		public SerializationProxy() {
+		}
+
+		public SerializationProxy(JID jid) {
+			this.jid = jid;
+		}
+
+		Object readResolve() {
+			return jid;
+		}
+		
+	    public void writeExternal(ObjectOutput out) throws IOException {
+	        ExternalizableUtil.getInstance().writeSafeUTF(out, jid.toString());
+	    }
+
+	    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
+	        String string = ExternalizableUtil.getInstance().readSafeUTF(in);
+	        String[] parts = getParts(string);
+
+	        jid = new JID(parts[0], parts[1], parts[2], true);
+	    }
+   }
 }
\ No newline at end of file

