First pass at implementing ValueProxy support.
Resolve issue 5522, issue 5373, issue 5559.
http://code.google.com/p/google-web-toolkit/wiki/RequestFactory_2_1_1
Patch by: bobv
Review by: rchandia,rjrjr

Review at http://gwt-code-reviews.appspot.com/1108802


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@9252 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/console/HttpClientTransport.java b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/console/HttpClientTransport.java
index 4325979..8b5b0c5 100644
--- a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/console/HttpClientTransport.java
+++ b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/console/HttpClientTransport.java
@@ -41,7 +41,6 @@
     this.uri = uri;
   }
 
-  @Override
   public void send(String payload, TransportReceiver receiver) {
     HttpClient client = new DefaultHttpClient();
     HttpPost post = new HttpPost();
diff --git a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/domain/Address.java b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/domain/Address.java
index c7059bd..815f349 100644
--- a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/domain/Address.java
+++ b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/domain/Address.java
@@ -15,8 +15,6 @@
  */
 package com.google.gwt.sample.dynatablerf.domain;
 
-import com.google.gwt.sample.dynatablerf.server.SchoolCalendarService;
-
 import javax.validation.constraints.DecimalMin;
 import javax.validation.constraints.NotNull;
 import javax.validation.constraints.Pattern;
@@ -26,14 +24,6 @@
  * Represents an address.
  */
 public class Address {
-  /**
-   * The RequestFactory requires a static finder method for each proxied type.
-   * Soon it should allow you to customize how instances are found.
-   */
-  public static Address findAddress(String id) {
-    return SchoolCalendarService.findPerson(id).getAddress();
-  }
-
   @NotNull
   @Size(min = 1)
   private String city;
@@ -77,10 +67,6 @@
     return city;
   }
 
-  public String getId() {
-    return id;
-  }
-
   public String getState() {
     return state;
   }
@@ -89,10 +75,6 @@
     return street;
   }
 
-  public Integer getVersion() {
-    return version;
-  }
-
   public String getZip() {
     return zip;
   }
@@ -101,23 +83,10 @@
     return new Address(this);
   }
 
-  /**
-   * When this was written the RequestFactory required a persist method per
-   * type. That requirement should be relaxed very soon (and may well have been
-   * already if we forget to update this comment).
-   */
-  public void persist() {
-    SchoolCalendarService.persist(this);
-  }
-
   public void setCity(String city) {
     this.city = city;
   }
 
-  public void setId(String id) {
-    this.id = id;
-  }
-
   public void setState(String state) {
     this.state = state;
   }
@@ -126,10 +95,6 @@
     this.street = street;
   }
 
-  public void setVersion(Integer version) {
-    this.version = version;
-  }
-
   public void setZip(String zip) {
     this.zip = zip;
   }
diff --git a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/domain/Person.java b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/domain/Person.java
index 3c10fd8..6f65c67 100644
--- a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/domain/Person.java
+++ b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/domain/Person.java
@@ -50,7 +50,7 @@
   }
 
   @NotNull
-  private final Address address = new Address();
+  private Address address = new Address();
 
   @NotNull
   private Schedule classSchedule = new Schedule();
@@ -158,7 +158,7 @@
   }
 
   public void setAddress(Address address) {
-    this.address.copyFrom(address);
+    this.address = address;
   }
 
   public void setDaysFilter(List<Boolean> daysFilter) {
@@ -172,7 +172,6 @@
 
   public void setId(String id) {
     this.id = id;
-    address.setId(id);
   }
 
   public void setMentor(Person mentor) {
diff --git a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/server/PersonSource.java b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/server/PersonSource.java
index 19b06e5..1279342 100644
--- a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/server/PersonSource.java
+++ b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/server/PersonSource.java
@@ -18,7 +18,6 @@
 import static com.google.gwt.sample.dynatablerf.shared.DynaTableRequestFactory.SchoolCalendarRequest.ALL_DAYS;
 import static com.google.gwt.sample.dynatablerf.shared.DynaTableRequestFactory.SchoolCalendarRequest.NO_DAYS;
 
-import com.google.gwt.sample.dynatablerf.domain.Address;
 import com.google.gwt.sample.dynatablerf.domain.Person;
 
 import java.util.ArrayList;
@@ -90,12 +89,6 @@
     }
 
     @Override
-    public void persist(Address address) {
-      address.setVersion(address.getVersion() + 1);
-      findPerson(address.getId()).getAddress().copyFrom(address);
-    }
-
-    @Override
     public void persist(Person person) {
       if (person.getId() == null) {
         person.setId(Long.toString(++serial));
@@ -149,11 +142,6 @@
     }
 
     @Override
-    public void persist(Address address) {
-      backingStore.persist(address);
-    }
-
-    @Override
     public void persist(Person person) {
       backingStore.persist(person);
     }
@@ -187,7 +175,5 @@
   public abstract List<Person> getPeople(int startIndex, int maxCount,
       List<Boolean> daysFilter);
 
-  public abstract void persist(Address address);
-
   public abstract void persist(Person person);
 }
diff --git a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/server/SchoolCalendarService.java b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/server/SchoolCalendarService.java
index 5493cd7..b4097cf 100644
--- a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/server/SchoolCalendarService.java
+++ b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/server/SchoolCalendarService.java
@@ -17,7 +17,6 @@
 
 import static com.google.gwt.sample.dynatablerf.shared.DynaTableRequestFactory.SchoolCalendarRequest.ALL_DAYS;
 
-import com.google.gwt.sample.dynatablerf.domain.Address;
 import com.google.gwt.sample.dynatablerf.domain.Person;
 
 import java.io.IOException;
@@ -62,11 +61,6 @@
         ALL_DAYS).get(0);
   }
 
-  public static void persist(Address address) {
-    checkPersonSource();
-    PERSON_SOURCE.get().persist(address);
-  }
-
   public static void persist(Person person) {
     checkPersonSource();
     PERSON_SOURCE.get().persist(person);
diff --git a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/shared/AddressProxy.java b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/shared/AddressProxy.java
index f96e5c8..8e87ef6 100644
--- a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/shared/AddressProxy.java
+++ b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/shared/AddressProxy.java
@@ -15,16 +15,15 @@
  */
 package com.google.gwt.sample.dynatablerf.shared;
 
-import com.google.gwt.requestfactory.shared.EntityProxy;
-import com.google.gwt.requestfactory.shared.EntityProxyId;
 import com.google.gwt.requestfactory.shared.ProxyFor;
+import com.google.gwt.requestfactory.shared.ValueProxy;
 import com.google.gwt.sample.dynatablerf.domain.Address;
 
 /**
  * Represents an Address in the client code.
  */
-@ProxyFor(Address.class)
-public interface AddressProxy extends EntityProxy {
+@ProxyFor(value = Address.class)
+public interface AddressProxy extends ValueProxy {
   String getCity();
 
   String getState();
@@ -40,6 +39,4 @@
   void setStreet(String street);
 
   void setZip(String zip);
-
-  EntityProxyId<AddressProxy> stableId();
 }
diff --git a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/shared/DynaTableRequestFactory.java b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/shared/DynaTableRequestFactory.java
index 0a0e38a..ce18e88 100644
--- a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/shared/DynaTableRequestFactory.java
+++ b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/shared/DynaTableRequestFactory.java
@@ -21,7 +21,6 @@
 import com.google.gwt.requestfactory.shared.RequestContext;
 import com.google.gwt.requestfactory.shared.RequestFactory;
 import com.google.gwt.requestfactory.shared.Service;
-import com.google.gwt.sample.dynatablerf.domain.Address;
 import com.google.gwt.sample.dynatablerf.domain.Person;
 import com.google.gwt.sample.dynatablerf.server.SchoolCalendarService;
 
@@ -34,15 +33,6 @@
  * {@link com.google.gwt.core.client.GWT#create}.
  */
 public interface DynaTableRequestFactory extends RequestFactory {
-
-  /**
-   * Source of request objects for the Address class.
-   */
-  @Service(Address.class)
-  interface AddressRequest extends RequestContext {
-    InstanceRequest<AddressProxy, Void> persist();
-  }
-
   /**
    * Source of request objects for the Person class.
    */
@@ -67,8 +57,6 @@
     Request<PersonProxy> getRandomPerson();
   }
 
-  AddressRequest addressRequest();
-
   LoggingRequest loggingRequest();
 
   PersonRequest personRequest();
diff --git a/user/src/com/google/gwt/autobean/client/impl/JsoSplittable.java b/user/src/com/google/gwt/autobean/client/impl/JsoSplittable.java
index f3a0bb2..e8aa4bb 100644
--- a/user/src/com/google/gwt/autobean/client/impl/JsoSplittable.java
+++ b/user/src/com/google/gwt/autobean/client/impl/JsoSplittable.java
@@ -24,11 +24,11 @@
 import java.util.List;
 
 /**
- * Implements the EntityCodex.Splittable interface
+ * Implements the EntityCodex.Splittable interface using a raw JavaScriptObject.
  */
 public final class JsoSplittable extends JavaScriptObject implements Splittable {
   /**
-   * This type is only used in DevMode because we can't treat Strings as JSOs.
+   * This type is used because we can't treat Strings as JSOs.
    */
   public static class StringSplittable implements Splittable {
     private final String value;
diff --git a/user/src/com/google/gwt/autobean/server/BeanMethod.java b/user/src/com/google/gwt/autobean/server/BeanMethod.java
index 117bf54..24c2009 100644
--- a/user/src/com/google/gwt/autobean/server/BeanMethod.java
+++ b/user/src/com/google/gwt/autobean/server/BeanMethod.java
@@ -138,13 +138,21 @@
     Class<?>[] searchParams = new Class<?>[declaredParams.length + 1];
     searchParams[0] = AutoBean.class;
     System.arraycopy(declaredParams, 0, searchParams, 1, declaredParams.length);
+    Class<?> autoBeanType = handler.getBean().getType();
 
     for (Class<?> clazz : handler.getBean().getConfiguration().getCategories()) {
       try {
         Method found = clazz.getMethod(method.getName(), searchParams);
-        if (Modifier.isStatic(found.getModifiers())) {
-          return found;
+        if (!Modifier.isStatic(found.getModifiers())) {
+          continue;
         }
+        // Check the AutoBean parameterization of the 0th argument
+        Class<?> foundAutoBean = TypeUtils.ensureBaseType(TypeUtils.getSingleParameterization(
+            AutoBean.class, found.getGenericParameterTypes()[0]));
+        if (!foundAutoBean.isAssignableFrom(autoBeanType)) {
+          continue;
+        }
+        return found;
       } catch (NoSuchMethodException expected) {
       } catch (IllegalArgumentException e) {
         throw new RuntimeException(e);
diff --git a/user/src/com/google/gwt/autobean/server/SimpleBeanHandler.java b/user/src/com/google/gwt/autobean/server/SimpleBeanHandler.java
index a15d27b..b331ad5 100644
--- a/user/src/com/google/gwt/autobean/server/SimpleBeanHandler.java
+++ b/user/src/com/google/gwt/autobean/server/SimpleBeanHandler.java
@@ -15,6 +15,8 @@
  */
 package com.google.gwt.autobean.server;
 
+import com.google.gwt.autobean.shared.AutoBeanCodex;
+
 import java.lang.reflect.InvocationHandler;
 import java.lang.reflect.Method;
 
@@ -47,4 +49,12 @@
     }
     throw new RuntimeException("Unhandled invocation " + method.getName());
   }
+
+  /**
+   * For debugging use only.
+   */
+  @Override
+  public String toString() {
+    return AutoBeanCodex.encode(bean).getPayload();
+  }
 }
\ No newline at end of file
diff --git a/user/src/com/google/gwt/autobean/server/impl/JsonSplittable.java b/user/src/com/google/gwt/autobean/server/impl/JsonSplittable.java
index 6b9f22e..6550f82 100644
--- a/user/src/com/google/gwt/autobean/server/impl/JsonSplittable.java
+++ b/user/src/com/google/gwt/autobean/server/impl/JsonSplittable.java
@@ -138,6 +138,21 @@
     return array.length();
   }
 
+  /**
+   * For debugging use only.
+   */
+  @Override
+  public String toString() {
+    if (obj != null) {
+      return obj.toString();
+    } else if (array != null) {
+      return array.toString();
+    } else if (string != null) {
+      return string;
+    }
+    return "<Uninitialized>";
+  }
+
   private JsonSplittable makeSplittable(Object object) {
     if (object instanceof JSONObject) {
       return new JsonSplittable((JSONObject) object);
diff --git a/user/src/com/google/gwt/autobean/server/impl/TypeUtils.java b/user/src/com/google/gwt/autobean/server/impl/TypeUtils.java
index d4d954b..c7c64ab 100644
--- a/user/src/com/google/gwt/autobean/server/impl/TypeUtils.java
+++ b/user/src/com/google/gwt/autobean/server/impl/TypeUtils.java
@@ -60,6 +60,7 @@
 
   static {
     Map<Class<?>, Class<?>> autoBoxMap = new HashMap<Class<?>, Class<?>>();
+    autoBoxMap.put(boolean.class, Boolean.class);
     autoBoxMap.put(byte.class, Byte.class);
     autoBoxMap.put(char.class, Character.class);
     autoBoxMap.put(double.class, Double.class);
diff --git a/user/src/com/google/gwt/autobean/shared/impl/EnumMap.java b/user/src/com/google/gwt/autobean/shared/impl/EnumMap.java
index b82af0e..d55d45f 100644
--- a/user/src/com/google/gwt/autobean/shared/impl/EnumMap.java
+++ b/user/src/com/google/gwt/autobean/shared/impl/EnumMap.java
@@ -20,7 +20,7 @@
  * convert enum values to strings.
  */
 public interface EnumMap {
-  public <E extends Enum<E>> E getEnum(Class<E> clazz, String token);
+  <E extends Enum<E>> E getEnum(Class<E> clazz, String token);
 
-  public String getToken(Enum<?> e);
+  String getToken(Enum<?> e);
 }
diff --git a/user/src/com/google/gwt/requestfactory/client/impl/AbstractRequestFactoryEditorDriver.java b/user/src/com/google/gwt/requestfactory/client/impl/AbstractRequestFactoryEditorDriver.java
index 50fd171..2e22c51 100644
--- a/user/src/com/google/gwt/requestfactory/client/impl/AbstractRequestFactoryEditorDriver.java
+++ b/user/src/com/google/gwt/requestfactory/client/impl/AbstractRequestFactoryEditorDriver.java
@@ -24,6 +24,7 @@
 import com.google.gwt.requestfactory.shared.EntityProxy;
 import com.google.gwt.requestfactory.shared.RequestContext;
 import com.google.gwt.requestfactory.shared.RequestFactory;
+import com.google.gwt.requestfactory.shared.ValueProxy;
 import com.google.gwt.requestfactory.shared.Violation;
 
 import java.util.ArrayList;
@@ -38,10 +39,34 @@
 public abstract class AbstractRequestFactoryEditorDriver<R, E extends Editor<R>>
     implements RequestFactoryEditorDriver<R, E> {
 
+  /**
+   * Since the ValueProxy is being mutated in-place, we need a way to stabilize
+   * its hashcode for future equality checks.
+   */
+  private static class ValueProxyHolder {
+    private final ValueProxy proxy;
+
+    public ValueProxyHolder(ValueProxy proxy) {
+      this.proxy = proxy;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      return proxy.equals(((ValueProxyHolder) o).proxy);
+    }
+
+    @Override
+    public int hashCode() {
+      return proxy.getClass().hashCode();
+    }
+  }
+
   private static final DelegateMap.KeyMethod PROXY_ID_KEY = new DelegateMap.KeyMethod() {
-    public Object key(Object object) {
+    public Object key(final Object object) {
       if (object instanceof EntityProxy) {
         return ((EntityProxy) object).stableId();
+      } else if (object instanceof ValueProxy) {
+        return new ValueProxyHolder((ValueProxy) object);
       }
       return null;
     }
@@ -115,7 +140,11 @@
        * Find the delegates that are attached to the object. Use getRaw() here
        * since the violation doesn't include an EntityProxy reference
        */
-      List<AbstractEditorDelegate<?, ?>> delegateList = delegateMap.getRaw(error.getProxyId());
+      Object key = error.getInvalidProxy();
+      // Object key = error.getOriginalProxy();
+      // if (key == null) {
+      // }
+      List<AbstractEditorDelegate<?, ?>> delegateList = delegateMap.get(key);
       if (delegateList != null) {
 
         // For each delegate editing some record...
diff --git a/user/src/com/google/gwt/requestfactory/rebind/RequestFactoryGenerator.java b/user/src/com/google/gwt/requestfactory/rebind/RequestFactoryGenerator.java
index b7eb331..916eee7 100644
--- a/user/src/com/google/gwt/requestfactory/rebind/RequestFactoryGenerator.java
+++ b/user/src/com/google/gwt/requestfactory/rebind/RequestFactoryGenerator.java
@@ -33,19 +33,23 @@
 import com.google.gwt.requestfactory.client.impl.AbstractClientRequestFactory;
 import com.google.gwt.requestfactory.rebind.model.ContextMethod;
 import com.google.gwt.requestfactory.rebind.model.EntityProxyModel;
+import com.google.gwt.requestfactory.rebind.model.EntityProxyModel.Type;
 import com.google.gwt.requestfactory.rebind.model.RequestFactoryModel;
 import com.google.gwt.requestfactory.rebind.model.RequestMethod;
 import com.google.gwt.requestfactory.shared.EntityProxyId;
 import com.google.gwt.requestfactory.shared.impl.AbstractRequest;
 import com.google.gwt.requestfactory.shared.impl.AbstractRequestContext;
 import com.google.gwt.requestfactory.shared.impl.AbstractRequestFactory;
+import com.google.gwt.requestfactory.shared.impl.BaseProxyCategory;
 import com.google.gwt.requestfactory.shared.impl.EntityProxyCategory;
 import com.google.gwt.requestfactory.shared.impl.RequestData;
+import com.google.gwt.requestfactory.shared.impl.ValueProxyCategory;
 import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
 import com.google.gwt.user.rebind.SourceWriter;
 
 import java.io.PrintWriter;
 import java.util.HashMap;
+import java.util.HashSet;
 
 /**
  * Generates implementations of
@@ -96,8 +100,11 @@
 
   private void writeAutoBeanFactory(SourceWriter sw) {
     // Map in static implementations of EntityProxy methods
-    sw.println("@%s(%s.class)", Category.class.getCanonicalName(),
-        EntityProxyCategory.class.getCanonicalName());
+    sw.println("@%s({%s.class, %s.class, %s.class})",
+        Category.class.getCanonicalName(),
+        EntityProxyCategory.class.getCanonicalName(),
+        ValueProxyCategory.class.getCanonicalName(),
+        BaseProxyCategory.class.getCanonicalName());
     // Don't wrap our id type, because it makes code grungy
     sw.println("@%s(%s.class)", NoWrap.class.getCanonicalName(),
         EntityProxyId.class.getCanonicalName());
@@ -251,6 +258,12 @@
         + " = new %1$s<String, Class<?>>();", HashMap.class.getCanonicalName());
     sw.println("private static final %1$s<Class<?>, String> typesToTokens"
         + " = new %1$s<Class<?>, String>();", HashMap.class.getCanonicalName());
+    sw.println(
+        "private static final %1$s<Class<?>> entityProxyTypes = new %1$s<Class<?>>();",
+        HashSet.class.getCanonicalName());
+    sw.println(
+        "private static final %1$s<Class<?>> valueProxyTypes = new %1$s<Class<?>>();",
+        HashSet.class.getCanonicalName());
     sw.println("static {");
     sw.indent();
     for (EntityProxyModel type : model.getAllProxyModels()) {
@@ -260,6 +273,10 @@
       // typesToTokens.put(Foo.class, Foo);
       sw.println("typesToTokens.put(%1$s.class, \"%1$s\");",
           type.getQualifiedSourceName());
+      // fooProxyTypes.add(MyFooProxy.class);
+      sw.println("%s.add(%s.class);", type.getType().equals(Type.ENTITY)
+          ? "entityProxyTypes" : "valueProxyTypes",
+          type.getQualifiedSourceName());
     }
     sw.outdent();
     sw.println("}");
@@ -271,5 +288,11 @@
     sw.println("@Override protected String getTypeToken(Class type) {");
     sw.indentln("return typesToTokens.get(type);");
     sw.println("}");
+    sw.println("@Override public boolean isEntityType(Class<?> type) {");
+    sw.indentln("return entityProxyTypes.contains(type);");
+    sw.println("}");
+    sw.println("@Override public boolean isValueType(Class<?> type) {");
+    sw.indentln("return valueProxyTypes.contains(type);");
+    sw.println("}");
   }
 }
diff --git a/user/src/com/google/gwt/requestfactory/rebind/model/EntityProxyModel.java b/user/src/com/google/gwt/requestfactory/rebind/model/EntityProxyModel.java
index 52989d1..7676035 100644
--- a/user/src/com/google/gwt/requestfactory/rebind/model/EntityProxyModel.java
+++ b/user/src/com/google/gwt/requestfactory/rebind/model/EntityProxyModel.java
@@ -23,6 +23,14 @@
  */
 public class EntityProxyModel {
   /**
+   * The kind of proxy. This is an enum in case more proxy types are defined in
+   * the future.
+   */
+  public enum Type {
+    ENTITY, VALUE
+  }
+
+  /**
    * Builds {@link EntityProxyModel}.
    */
   public static class Builder {
@@ -56,11 +64,16 @@
     public void setRequestMethods(List<RequestMethod> requestMethods) {
       toReturn.requestMethods = requestMethods;
     }
+
+    public void setType(Type type) {
+      toReturn.type = type;
+    }
   }
 
   private Class<?> proxyFor;
   private String qualifiedSourceName;
   private List<RequestMethod> requestMethods;
+  private Type type;
 
   private EntityProxyModel() {
   }
@@ -77,6 +90,10 @@
     return Collections.unmodifiableList(requestMethods);
   }
 
+  public Type getType() {
+    return type;
+  }
+
   /**
    * For debugging use only.
    */
diff --git a/user/src/com/google/gwt/requestfactory/rebind/model/RequestFactoryModel.java b/user/src/com/google/gwt/requestfactory/rebind/model/RequestFactoryModel.java
index be2eb96..56d859a 100644
--- a/user/src/com/google/gwt/requestfactory/rebind/model/RequestFactoryModel.java
+++ b/user/src/com/google/gwt/requestfactory/rebind/model/RequestFactoryModel.java
@@ -24,6 +24,7 @@
 import com.google.gwt.core.ext.typeinfo.JType;
 import com.google.gwt.core.ext.typeinfo.TypeOracle;
 import com.google.gwt.editor.rebind.model.ModelUtils;
+import com.google.gwt.requestfactory.rebind.model.EntityProxyModel.Type;
 import com.google.gwt.requestfactory.rebind.model.RequestMethod.CollectionType;
 import com.google.gwt.requestfactory.shared.EntityProxy;
 import com.google.gwt.requestfactory.shared.InstanceRequest;
@@ -34,6 +35,7 @@
 import com.google.gwt.requestfactory.shared.RequestFactory;
 import com.google.gwt.requestfactory.shared.Service;
 import com.google.gwt.requestfactory.shared.ServiceName;
+import com.google.gwt.requestfactory.shared.ValueProxy;
 
 import java.util.ArrayList;
 import java.util.Collection;
@@ -61,13 +63,13 @@
     return "Unable to create RequestFactoryModel model due to previous errors";
   }
 
-  private final TreeLogger logger;
   private final JClassType collectionInterface;
   private final List<ContextMethod> contextMethods = new ArrayList<ContextMethod>();
   private final JClassType entityProxyInterface;
   private final JClassType factoryType;
   private final JClassType instanceRequestInterface;
   private final JClassType listInterface;
+  private final TreeLogger logger;
   private final TypeOracle oracle;
   /**
    * This map prevents cyclic type dependencies from overflowing the stack.
@@ -82,6 +84,7 @@
   private final JClassType requestContextInterface;
   private final JClassType requestFactoryInterface;
   private final JClassType requestInterface;
+  private final JClassType valueProxyInterface;
 
   public RequestFactoryModel(TreeLogger logger, JClassType factoryType)
       throws UnableToCompleteException {
@@ -96,6 +99,7 @@
     requestContextInterface = oracle.findType(RequestContext.class.getCanonicalName());
     requestFactoryInterface = oracle.findType(RequestFactory.class.getCanonicalName());
     requestInterface = oracle.findType(Request.class.getCanonicalName());
+    valueProxyInterface = oracle.findType(ValueProxy.class.getCanonicalName());
 
     for (JMethod method : factoryType.getOverridableMethods()) {
       if (method.getEnclosingType().equals(requestFactoryInterface)) {
@@ -206,6 +210,17 @@
       peerBuilders.put(entityProxyType, builder);
 
       builder.setQualifiedSourceName(ModelUtils.getQualifiedBaseName(entityProxyType));
+      if (entityProxyInterface.isAssignableFrom(entityProxyType)) {
+        builder.setType(Type.ENTITY);
+      } else if (valueProxyInterface.isAssignableFrom(entityProxyType)) {
+        builder.setType(Type.VALUE);
+      } else {
+        poison("The type %s is not assignable to either %s or %s",
+            entityProxyInterface.getQualifiedSourceName(),
+            valueProxyInterface.getQualifiedSourceName());
+        // Cannot continue, since knowing the behavior is crucial
+        die(poisonedMessage());
+      }
 
       // Get the server domain object type
       ProxyFor proxyFor = entityProxyType.getAnnotation(ProxyFor.class);
@@ -332,8 +347,9 @@
     if (ModelUtils.isValueType(oracle, transportedClass)) {
       // Simple values, like Integer and String
       methodBuilder.setValueType(true);
-    } else if (entityProxyInterface.isAssignableFrom(transportedClass)) {
-      // EntityProxy return types
+    } else if (entityProxyInterface.isAssignableFrom(transportedClass)
+        || valueProxyInterface.isAssignableFrom(transportedClass)) {
+      // EntityProxy and ValueProxy return types
       methodBuilder.setEntityType(getEntityProxyType(transportedClass));
     } else if (collectionInterface.isAssignableFrom(transportedClass)) {
       // Only allow certain collections for now
diff --git a/user/src/com/google/gwt/requestfactory/server/ReflectiveServiceLayer.java b/user/src/com/google/gwt/requestfactory/server/ReflectiveServiceLayer.java
index c2bfc34..6ffb4c2 100644
--- a/user/src/com/google/gwt/requestfactory/server/ReflectiveServiceLayer.java
+++ b/user/src/com/google/gwt/requestfactory/server/ReflectiveServiceLayer.java
@@ -18,19 +18,24 @@
 import com.google.gwt.autobean.server.impl.TypeUtils;
 import com.google.gwt.autobean.shared.ValueCodex;
 import com.google.gwt.requestfactory.server.SimpleRequestProcessor.ServiceLayer;
+import com.google.gwt.requestfactory.shared.BaseProxy;
 import com.google.gwt.requestfactory.shared.EntityProxy;
 import com.google.gwt.requestfactory.shared.EntityProxyId;
+import com.google.gwt.requestfactory.shared.InstanceRequest;
 import com.google.gwt.requestfactory.shared.ProxyFor;
 import com.google.gwt.requestfactory.shared.ProxyForName;
+import com.google.gwt.requestfactory.shared.Request;
 import com.google.gwt.requestfactory.shared.RequestContext;
 import com.google.gwt.requestfactory.shared.Service;
 import com.google.gwt.requestfactory.shared.ServiceName;
+import com.google.gwt.requestfactory.shared.ValueProxy;
 import com.google.gwt.requestfactory.shared.messages.EntityCodex;
 import com.google.gwt.requestfactory.shared.messages.EntityCodex.EntitySource;
 
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
+import java.lang.reflect.Type;
 import java.util.Collections;
 import java.util.List;
 import java.util.Set;
@@ -91,13 +96,14 @@
         clazz.getCanonicalName());
   }
 
-  public Class<?> getClientType(Class<?> domainClass) {
+  public Class<?> getClientType(Class<?> domainClass, Class<?> clientClass) {
     String name;
     synchronized (validator) {
-      name = validator.getEntityProxyTypeName(domainClass.getName());
+      name = validator.getEntityProxyTypeName(domainClass.getName(),
+          clientClass.getName());
     }
     if (name != null) {
-      return forName(name).asSubclass(EntityProxy.class);
+      return forName(name).asSubclass(BaseProxy.class);
     }
     if (List.class.isAssignableFrom(domainClass)) {
       return List.class;
@@ -117,7 +123,7 @@
       return List.class;
     } else if (Set.class.equals(clazz)) {
       return Set.class;
-    } else if (EntityProxy.class.isAssignableFrom(clazz)) {
+    } else if (BaseProxy.class.isAssignableFrom(clazz)) {
       ProxyFor pf = clazz.getAnnotation(ProxyFor.class);
       if (pf != null) {
         return pf.value();
@@ -135,7 +141,7 @@
   public String getFlatId(EntitySource source, Object domainObject) {
     Object id = getProperty(domainObject, "id");
     if (id == null) {
-      report("Could not retrieve id property for domain object");
+      return null;
     }
     if (!isKeyType(id.getClass())) {
       die(null, "The type %s is not a valid key type",
@@ -165,6 +171,23 @@
     return die(toReport, "Could not retrieve property %s", property);
   }
 
+  public Type getRequestReturnType(Method contextMethod) {
+    Class<?> returnClass = contextMethod.getReturnType();
+    if (InstanceRequest.class.isAssignableFrom(returnClass)) {
+      Type[] params = TypeUtils.getParameterization(InstanceRequest.class,
+          contextMethod.getGenericReturnType());
+      assert params.length == 2;
+      return params[1];
+    } else if (Request.class.isAssignableFrom(returnClass)) {
+      Type param = TypeUtils.getSingleParameterization(Request.class,
+          contextMethod.getGenericReturnType());
+      return param;
+    } else {
+      throw new IllegalArgumentException("Unknown RequestContext return type "
+          + returnClass.getCanonicalName());
+    }
+  }
+
   public String getTypeToken(Class<?> clazz) {
     return clazz.getName();
   }
@@ -173,7 +196,7 @@
     // TODO: Make version any value type
     Object version = getProperty(domainObject, "version");
     if (version == null) {
-      report("Could not retrieve version property");
+      return 0;
     }
     if (!(version instanceof Integer)) {
       die(null, "The getVersion() method on type %s did not return"
@@ -244,21 +267,23 @@
     return die(ex, "Cauld not load domain object using id", id.toString());
   }
 
-  public Class<? extends EntityProxy> resolveClass(String typeToken) {
+  public Class<? extends BaseProxy> resolveClass(String typeToken) {
     Class<?> found = forName(typeToken);
-    if (!EntityProxy.class.isAssignableFrom(found)) {
-      die(null, "The requested type %s is not assignable to %s", typeToken,
-          EntityProxy.class.getName());
+    if (!EntityProxy.class.isAssignableFrom(found)
+        && !ValueProxy.class.isAssignableFrom(found)) {
+      die(null, "The requested type %s is not assignable to %s or %s",
+          typeToken, EntityProxy.class.getCanonicalName(),
+          ValueProxy.class.getCanonicalName());
     }
     synchronized (validator) {
       validator.antidote();
-      validator.validateEntityProxy(found.getName());
+      validator.validateProxy(found.getName());
       if (validator.isPoisoned()) {
         die(null, "The type %s did not pass RequestFactory validation",
             found.getCanonicalName());
       }
     }
-    return found.asSubclass(EntityProxy.class);
+    return found.asSubclass(BaseProxy.class);
   }
 
   public Method resolveDomainMethod(Method requestContextMethod) {
@@ -289,8 +314,8 @@
     Class<?>[] parameterTypes = requestContextMethod.getParameterTypes();
     Class<?>[] domainArgs = new Class<?>[parameterTypes.length];
     for (int i = 0, j = domainArgs.length; i < j; i++) {
-      if (EntityProxy.class.isAssignableFrom(parameterTypes[i])) {
-        domainArgs[i] = getDomainClass(parameterTypes[i].asSubclass(EntityProxy.class));
+      if (BaseProxy.class.isAssignableFrom(parameterTypes[i])) {
+        domainArgs[i] = getDomainClass(parameterTypes[i].asSubclass(BaseProxy.class));
       } else if (EntityProxyId.class.isAssignableFrom(parameterTypes[i])) {
         domainArgs[i] = TypeUtils.ensureBaseType(TypeUtils.getSingleParameterization(
             EntityProxyId.class,
@@ -346,7 +371,7 @@
       report(e);
       return;
     }
-    die(ex, "Could not locate getter for property %s in type %s", property,
+    die(ex, "Could not locate setter for property %s in type %s", property,
         domainObject.getClass().getCanonicalName());
   }
 
@@ -381,7 +406,7 @@
 
   private boolean isKeyType(Class<?> clazz) {
     return ValueCodex.canDecode(clazz)
-        || EntityProxy.class.isAssignableFrom(clazz);
+        || BaseProxy.class.isAssignableFrom(clazz);
   }
 
   /**
diff --git a/user/src/com/google/gwt/requestfactory/server/RequestFactoryInterfaceValidator.java b/user/src/com/google/gwt/requestfactory/server/RequestFactoryInterfaceValidator.java
index bbe81e9..2108542 100644
--- a/user/src/com/google/gwt/requestfactory/server/RequestFactoryInterfaceValidator.java
+++ b/user/src/com/google/gwt/requestfactory/server/RequestFactoryInterfaceValidator.java
@@ -28,6 +28,7 @@
 import com.google.gwt.dev.util.Name;
 import com.google.gwt.dev.util.Name.BinaryName;
 import com.google.gwt.dev.util.Name.SourceOrBinaryName;
+import com.google.gwt.requestfactory.shared.BaseProxy;
 import com.google.gwt.requestfactory.shared.EntityProxy;
 import com.google.gwt.requestfactory.shared.InstanceRequest;
 import com.google.gwt.requestfactory.shared.ProxyFor;
@@ -37,6 +38,7 @@
 import com.google.gwt.requestfactory.shared.RequestFactory;
 import com.google.gwt.requestfactory.shared.Service;
 import com.google.gwt.requestfactory.shared.ServiceName;
+import com.google.gwt.requestfactory.shared.ValueProxy;
 
 import java.io.IOException;
 import java.io.InputStream;
@@ -138,6 +140,7 @@
   private class DomainMapper extends EmptyVisitor {
     private final ErrorContext logger;
     private String domainInternalName;
+    private boolean isValueType;
 
     public DomainMapper(ErrorContext logger) {
       this.logger = logger;
@@ -148,6 +151,10 @@
       return domainInternalName;
     }
 
+    public boolean isValueType() {
+      return isValueType;
+    }
+
     @Override
     public void visit(int version, int access, String name, String signature,
         String superName, String[] interfaces) {
@@ -168,9 +175,12 @@
 
           @Override
           public void visit(String name, Object value) {
-            domainInternalName = ((Type) value).getInternalName();
+            if ("value".equals(name)) {
+              domainInternalName = ((Type) value).getInternalName();
+            } else if ("isValueType".equals(name)) {
+              isValueType = (Boolean) value;
+            }
           }
-
         };
       }
 
@@ -178,25 +188,30 @@
         return new EmptyVisitor() {
           @Override
           public void visit(String name, Object value) {
-            String sourceName = (String) value;
+            if ("value".equals(name)) {
+              String sourceName = (String) value;
 
-            /*
-             * The input is a source name, so we need to convert it to an
-             * internal name. We'll do this by substituting dollar signs for the
-             * last slash in the name until there are no more slashes.
-             */
-            StringBuffer desc = new StringBuffer(sourceName.replace('.', '/'));
-            while (!loader.exists(desc.toString() + ".class")) {
-              logger.spam("Did not find " + desc.toString());
-              int idx = desc.lastIndexOf("/");
-              if (idx == -1) {
-                return;
+              /*
+               * The input is a source name, so we need to convert it to an
+               * internal name. We'll do this by substituting dollar signs for
+               * the last slash in the name until there are no more slashes.
+               */
+              StringBuffer desc = new StringBuffer(sourceName.replace('.', '/'));
+              while (!loader.exists(desc.toString() + ".class")) {
+                logger.spam("Did not find " + desc.toString());
+                int idx = desc.lastIndexOf("/");
+                if (idx == -1) {
+                  return;
+                }
+                desc.setCharAt(idx, '$');
               }
-              desc.setCharAt(idx, '$');
-            }
 
-            domainInternalName = desc.toString();
-            logger.spam(domainInternalName);
+              domainInternalName = desc.toString();
+              logger.spam(domainInternalName);
+            } else if ("isValueType".equals(name)) {
+              isValueType = (Boolean) value;
+              logger.spam("isValueType: %s", isValueType);
+            }
           }
         };
       }
@@ -399,6 +414,58 @@
     }
   }
 
+  /**
+   * Return all types referenced by a method signature.
+   */
+  private static class TypesInSignatureCollector extends SignatureAdapter {
+    private final Set<Type> found = new HashSet<Type>();
+
+    public Type[] getFound() {
+      return found.toArray(new Type[found.size()]);
+    }
+
+    public SignatureVisitor visitArrayType() {
+      return this;
+    }
+
+    public SignatureVisitor visitClassBound() {
+      return this;
+    }
+
+    @Override
+    public void visitClassType(String name) {
+      found.add(Type.getObjectType(name));
+    }
+
+    public SignatureVisitor visitExceptionType() {
+      return this;
+    }
+
+    public SignatureVisitor visitInterface() {
+      return this;
+    }
+
+    public SignatureVisitor visitInterfaceBound() {
+      return this;
+    }
+
+    public SignatureVisitor visitParameterType() {
+      return this;
+    }
+
+    public SignatureVisitor visitReturnType() {
+      return this;
+    }
+
+    public SignatureVisitor visitSuperclass() {
+      return this;
+    }
+
+    public SignatureVisitor visitTypeArgument(char wildcard) {
+      return this;
+    }
+  }
+
   @SuppressWarnings("unchecked")
   static final Set<Class<?>> VALUE_TYPES = Collections.unmodifiableSet(new HashSet<Class<?>>(
       Arrays.asList(Boolean.class, Character.class, Class.class, Date.class,
@@ -439,10 +506,19 @@
   private final Set<String> badTypes = new HashSet<String>();
 
   /**
+   * The type {@link BaseProxy}.
+   */
+  private final Type baseProxyIntf = Type.getType(BaseProxy.class);
+  /**
    * Maps client types (e.g. FooProxy) to server domain types (e.g. Foo).
    */
   private final Map<Type, Type> clientToDomainType = new HashMap<Type, Type>();
-  private final Map<Type, Type> domainToClientType = new HashMap<Type, Type>();
+  /**
+   * Maps domain types (e.g Foo) to client proxy types (e.g. FooAProxy,
+   * FooBProxy).
+   */
+  private final Map<Type, List<Type>> domainToClientType = new HashMap<Type, List<Type>>();
+
   /**
    * The type {@link EntityProxy}.
    */
@@ -480,6 +556,10 @@
    */
   private final Map<Type, List<Type>> supertypes = new HashMap<Type, List<Type>>();
   /**
+   * The type {@link ValueProxy}.
+   */
+  private final Type valueProxyIntf = Type.getType(ValueProxy.class);
+  /**
    * A set to prevent re-validation of a type.
    */
   private final Set<String> validatedTypes = new HashSet<String>();
@@ -530,53 +610,37 @@
    * <li>The domain object has getId() and getVersion() methods</li>
    * <li>All property methods in the EntityProxy can be mapped onto an
    * equivalent domain method</li>
-   * <li>All referenced EntityProxy types are valid</li>
+   * <li>All referenced proxy types are valid</li>
    * </ul>
    * 
    * @param binaryName the binary name (e.g. {@link Class#getName()}) of the
    *          EntityProxy subtype
    */
   public void validateEntityProxy(String binaryName) {
+    validateProxy(binaryName, entityProxyIntf, true);
+  }
+
+  /**
+   * Determine if the specified type implements a proxy interface and apply the
+   * appropriate validations. This can be used as a general-purpose entry method
+   * when writing unit tests.
+   * 
+   * @param binaryName the binary name (e.g. {@link Class#getName()}) of the
+   *          EntityProxy or ValueProxy subtype
+   */
+  public void validateProxy(String binaryName) {
     if (fastFail(binaryName)) {
       return;
     }
 
     Type proxyType = Type.getObjectType(BinaryName.toInternalName(binaryName));
-    ErrorContext logger = parentLogger.setType(proxyType);
-
-    // Quick sanity check for calling code
-    if (!isAssignable(logger, entityProxyIntf, proxyType)) {
-      parentLogger.poison("%s is not a %s", print(proxyType),
-          EntityProxy.class.getSimpleName());
-      return;
-    }
-
-    // Find the domain type
-    Type domainType = getDomainType(logger, proxyType);
-    if (domainType == errorType) {
-      logger.poison(
-          "The type %s must be annotated with a @%s or @%s annotation",
-          BinaryName.toSourceName(binaryName), ProxyFor.class.getSimpleName(),
-          ProxyForName.class.getSimpleName());
-      return;
-    }
-
-    // Check for getId() and getVersion() in domain
-    checkIdAndVersion(logger, domainType);
-
-    // Collect all methods in the client proxy type
-    Set<RFMethod> clientPropertyMethods = getMethodsInHierarchy(logger,
-        proxyType);
-
-    // Find the equivalent domain getter/setter method
-    for (RFMethod clientPropertyMethod : clientPropertyMethods) {
-      // Ignore stableId(). Can't use descriptor due to overrides
-      if ("stableId".equals(clientPropertyMethod.getName())
-          && clientPropertyMethod.getArgumentTypes().length == 0) {
-        continue;
-      }
-      checkPropertyMethod(logger, clientPropertyMethod, domainType);
-      maybeCheckReferredProxies(logger, clientPropertyMethod);
+    if (isAssignable(parentLogger, entityProxyIntf, proxyType)) {
+      validateEntityProxy(binaryName);
+    } else if (isAssignable(parentLogger, valueProxyIntf, proxyType)) {
+      validateValueProxy(binaryName);
+    } else {
+      parentLogger.poison("%s is neither an %s nor a %s", print(proxyType),
+          print(entityProxyIntf), print(valueProxyIntf));
     }
   }
 
@@ -685,13 +749,78 @@
   }
 
   /**
-   * Given the binary name of a domain type, return the EntityProxy type that
-   * has been seen to map to the domain type.
+   * This method checks a ValueProxy interface against its peer domain object to
+   * determine if the server code would be able to process a request using the
+   * methods defined in the ValueProxy interface. It does not perform any checks
+   * as to whether or not the ValueProxy could actually be generated by the
+   * Generator.
+   * <p>
+   * This method may be called repeatedly on a single instance of the validator.
+   * Doing so will amortize type calculation costs.
+   * <p>
+   * Checks implemented:
+   * <ul>
+   * <li> <code>binaryName</code> implements ValueProxy</li>
+   * <li><code>binaryName</code> has a {@link ProxyFor} or {@link ProxyForName}
+   * annotation</li>
+   * <li>All property methods in the EntityProxy can be mapped onto an
+   * equivalent domain method</li>
+   * <li>All referenced proxy types are valid</li>
+   * </ul>
+   * 
+   * @param binaryName the binary name (e.g. {@link Class#getName()}) of the
+   *          EntityProxy subtype
    */
-  String getEntityProxyTypeName(String domainTypeNameBinaryName) {
-    Type key = Type.getObjectType(BinaryName.toInternalName(domainTypeNameBinaryName));
-    Type found = domainToClientType.get(key);
-    return found == null ? null : found.getClassName();
+  public void validateValueProxy(String binaryName) {
+    validateProxy(binaryName, valueProxyIntf, false);
+  }
+
+  /**
+   * Given the binary name of a domain type, return the BaseProxy type that is
+   * assignable to {@code clientType}. This method allows multiple proxy types
+   * to be assigned to a domain type for use in different contexts (e.g. API
+   * slices). If there are multiple client types mapped to
+   * {@code domainTypeBinaryName} and assignable to {@code clientTypeBinaryName}
+   * , the first matching type will be returned.
+   */
+  String getEntityProxyTypeName(String domainTypeBinaryName,
+      String clientTypeBinaryName) {
+    Type key = Type.getObjectType(BinaryName.toInternalName(domainTypeBinaryName));
+    List<Type> found = domainToClientType.get(key);
+    if (found == null || found.isEmpty()) {
+      return null;
+    }
+    // Common case
+    if (found.size() == 1) {
+      return found.get(0).getClassName();
+    }
+
+    // Search for the first assignable type
+    Type assignableTo = Type.getObjectType(BinaryName.toInternalName(clientTypeBinaryName));
+    for (Type t : found) {
+      if (isAssignable(parentLogger, assignableTo, t)) {
+        return t.getClassName();
+      }
+    }
+    return null;
+  }
+
+  /**
+   * Record the mapping of a domain type to a client type. Proxy types will be
+   * added to {@link #domainToClientType}.
+   */
+  private void addToDomainMap(ErrorContext logger, Type domainType,
+      Type clientType) {
+    clientToDomainType.put(clientType, domainType);
+
+    if (isAssignable(logger, baseProxyIntf, clientType)) {
+      List<Type> list = domainToClientType.get(domainType);
+      if (list == null) {
+        list = new ArrayList<Type>();
+        domainToClientType.put(domainType, list);
+      }
+      list.add(clientType);
+    }
   }
 
   /**
@@ -918,14 +1047,15 @@
    * object.
    */
   private Type getDomainType(ErrorContext logger, Type clientType) {
-    Type toReturn = clientToDomainType.get(clientType);
-    if (toReturn != null) {
-      return toReturn;
+    Type domainType = clientToDomainType.get(clientType);
+    if (domainType != null) {
+      return domainType;
     }
     if (isValueType(logger, clientType) || isCollectionType(logger, clientType)) {
-      toReturn = clientType;
-    } else if (entityProxyIntf.equals(clientType)) {
-      toReturn = objectType;
+      domainType = clientType;
+    } else if (entityProxyIntf.equals(clientType)
+        || valueProxyIntf.equals(clientType)) {
+      domainType = objectType;
     } else {
       logger = logger.setType(clientType);
       DomainMapper pv = new DomainMapper(logger);
@@ -934,22 +1064,16 @@
         logger.poison("%s has no mapping to a domain type (e.g. @%s or @%s)",
             print(clientType), ProxyFor.class.getSimpleName(),
             Service.class.getSimpleName());
-        toReturn = errorType;
+        domainType = errorType;
       } else {
-        toReturn = Type.getObjectType(pv.getDomainInternalName());
+        domainType = Type.getObjectType(pv.getDomainInternalName());
+        if (pv.isValueType()) {
+          valueTypes.add(clientType);
+        }
       }
     }
-    clientToDomainType.put(clientType, toReturn);
-    if (isAssignable(logger, entityProxyIntf, clientType)) {
-      Type previousProxyType = domainToClientType.put(toReturn, clientType);
-      if (previousProxyType != null) {
-        logger.poison(
-            "The domain type %s has more than one proxy type: %s and %s",
-            toReturn.getClassName(), previousProxyType.getClassName(),
-            clientType.getClassName());
-      }
-    }
-    return toReturn;
+    addToDomainMap(logger, domainType, clientType);
+    return domainType;
   }
 
   /**
@@ -1094,28 +1218,88 @@
   }
 
   /**
-   * Examine an array of Types and call {@link #validateEntityProxy(String)} if
-   * the type is an EntityProxy.
+   * Examine an array of Types and call {@link #validateEntityProxy(String)} or
+   * {@link #validateValueProxy(String)} if the type is a proxy.
    */
-  private void maybeCheckEntityProxyType(ErrorContext logger, Type... types) {
+  private void maybeCheckProxyType(ErrorContext logger, Type... types) {
     for (Type type : types) {
       if (isAssignable(logger, entityProxyIntf, type)) {
         validateEntityProxy(type.getClassName());
+      } else if (isAssignable(logger, valueProxyIntf, type)) {
+        validateValueProxy(type.getClassName());
+      } else if (isAssignable(logger, baseProxyIntf, type)) {
+        logger.poison(
+            "Invalid type hierarchy for %s. Only types derived from %s or %s may be used.",
+            print(type), print(entityProxyIntf), print(valueProxyIntf));
       }
     }
   }
 
   /**
-   * Examine the arguments ond return value of a method and check any
+   * Examine the arguments and return value of a method and check any
    * EntityProxies referred.
    */
   private void maybeCheckReferredProxies(ErrorContext logger, RFMethod method) {
-    Type[] argTypes = method.getArgumentTypes();
-    Type returnType = getReturnType(logger, method);
+    if (method.getSignature() != null) {
+      TypesInSignatureCollector collector = new TypesInSignatureCollector();
+      SignatureReader reader = new SignatureReader(method.getSignature());
+      reader.accept(collector);
+      maybeCheckProxyType(logger, collector.getFound());
+    } else {
+      Type[] argTypes = method.getArgumentTypes();
+      Type returnType = getReturnType(logger, method);
 
-    // Check EntityProxy args ond return types against the domain
-    maybeCheckEntityProxyType(logger, argTypes);
-    maybeCheckEntityProxyType(logger, returnType);
+      // Check EntityProxy args ond return types against the domain
+      maybeCheckProxyType(logger, argTypes);
+      maybeCheckProxyType(logger, returnType);
+    }
+  }
+
+  private void validateProxy(String binaryName, Type expectedType,
+      boolean requireId) {
+    if (fastFail(binaryName)) {
+      return;
+    }
+
+    Type proxyType = Type.getObjectType(BinaryName.toInternalName(binaryName));
+    ErrorContext logger = parentLogger.setType(proxyType);
+
+    // Quick sanity check for calling code
+    if (!isAssignable(logger, expectedType, proxyType)) {
+      parentLogger.poison("%s is not a %s", print(proxyType),
+          print(expectedType));
+      return;
+    }
+
+    // Find the domain type
+    Type domainType = getDomainType(logger, proxyType);
+    if (domainType == errorType) {
+      logger.poison(
+          "The type %s must be annotated with a @%s or @%s annotation",
+          BinaryName.toSourceName(binaryName), ProxyFor.class.getSimpleName(),
+          ProxyForName.class.getSimpleName());
+      return;
+    }
+
+    // Check for getId() and getVersion() in domain
+    if (requireId) {
+      checkIdAndVersion(logger, domainType);
+    }
+
+    // Collect all methods in the client proxy type
+    Set<RFMethod> clientPropertyMethods = getMethodsInHierarchy(logger,
+        proxyType);
+
+    // Find the equivalent domain getter/setter method
+    for (RFMethod clientPropertyMethod : clientPropertyMethods) {
+      // Ignore stableId(). Can't use descriptor due to overrides
+      if ("stableId".equals(clientPropertyMethod.getName())
+          && clientPropertyMethod.getArgumentTypes().length == 0) {
+        continue;
+      }
+      checkPropertyMethod(logger, clientPropertyMethod, domainType);
+      maybeCheckReferredProxies(logger, clientPropertyMethod);
+    }
   }
 
   /**
diff --git a/user/src/com/google/gwt/requestfactory/server/RequestState.java b/user/src/com/google/gwt/requestfactory/server/RequestState.java
new file mode 100644
index 0000000..9dcccdc
--- /dev/null
+++ b/user/src/com/google/gwt/requestfactory/server/RequestState.java
@@ -0,0 +1,199 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.requestfactory.server;
+
+import com.google.gwt.autobean.server.AutoBeanFactoryMagic;
+import com.google.gwt.autobean.shared.AutoBean;
+import com.google.gwt.requestfactory.server.SimpleRequestProcessor.IdToEntityMap;
+import com.google.gwt.requestfactory.server.SimpleRequestProcessor.ServiceLayer;
+import com.google.gwt.requestfactory.shared.BaseProxy;
+import com.google.gwt.requestfactory.shared.EntityProxy;
+import com.google.gwt.requestfactory.shared.ValueProxy;
+import com.google.gwt.requestfactory.shared.impl.Constants;
+import com.google.gwt.requestfactory.shared.impl.IdFactory;
+import com.google.gwt.requestfactory.shared.impl.SimpleProxyId;
+import com.google.gwt.requestfactory.shared.messages.EntityCodex;
+import com.google.gwt.requestfactory.shared.messages.IdUtil;
+
+import java.util.IdentityHashMap;
+import java.util.Map;
+
+/**
+ * Encapsulates all state relating to the processing of a single request so that
+ * the SimpleRequestProcessor can be stateless.
+ */
+class RequestState implements EntityCodex.EntitySource {
+  final IdToEntityMap beans = new IdToEntityMap();
+  private final Map<Object, SimpleProxyId<?>> domainObjectsToId;
+  private final IdFactory idFactory;
+  private final ServiceLayer service;
+  private final Resolver resolver;
+
+  public RequestState(RequestState parent) {
+    idFactory = parent.idFactory;
+    domainObjectsToId = parent.domainObjectsToId;
+    service = parent.service;
+    resolver = new Resolver(this);
+  }
+
+  public RequestState(final ServiceLayer service) {
+    this.service = service;
+    idFactory = new IdFactory() {
+      @Override
+      public boolean isEntityType(Class<?> clazz) {
+        return EntityProxy.class.isAssignableFrom(clazz);
+      }
+
+      @Override
+      public boolean isValueType(Class<?> clazz) {
+        return ValueProxy.class.isAssignableFrom(clazz);
+      }
+
+      @Override
+      @SuppressWarnings("unchecked")
+      protected <P extends BaseProxy> Class<P> getTypeFromToken(String typeToken) {
+        return (Class<P>) service.resolveClass(typeToken);
+      }
+
+      @Override
+      protected String getTypeToken(Class<?> clazz) {
+        return service.getTypeToken(clazz);
+      }
+    };
+    domainObjectsToId = new IdentityHashMap<Object, SimpleProxyId<?>>();
+    resolver = new Resolver(this);
+  }
+
+  public <Q extends BaseProxy> AutoBean<Q> getBeanForPayload(
+      SimpleProxyId<Q> id, Object domainObject) {
+    @SuppressWarnings("unchecked")
+    AutoBean<Q> toReturn = (AutoBean<Q>) beans.get(id);
+    if (toReturn == null) {
+      toReturn = createEntityProxyBean(id, domainObject);
+    }
+    return toReturn;
+  }
+
+  /**
+   * EntityCodex support.
+   */
+  public <Q extends BaseProxy> AutoBean<Q> getBeanForPayload(
+      String serializedProxyId) {
+    String typeToken = IdUtil.getTypeToken(serializedProxyId);
+    SimpleProxyId<Q> id;
+    if (IdUtil.isEphemeral(serializedProxyId)) {
+      id = idFactory.getId(typeToken, null,
+          IdUtil.getClientId(serializedProxyId));
+    } else {
+      id = idFactory.getId(
+          typeToken,
+          SimpleRequestProcessor.fromBase64(IdUtil.getServerId(serializedProxyId)));
+    }
+    return getBeanForPayload(id);
+  }
+
+  public IdFactory getIdFactory() {
+    return idFactory;
+  }
+
+  public Resolver getResolver() {
+    return resolver;
+  }
+
+  /**
+   * EntityCodex support. This method is identical to
+   * {@link IdFactory#getHistoryToken(SimpleProxyId)} except that it
+   * base64-encodes the server ids.
+   * <p>
+   * XXX: Fix the above
+   */
+  public String getSerializedProxyId(SimpleProxyId<?> stableId) {
+    if (stableId.isEphemeral()) {
+      return IdUtil.ephemeralId(stableId.getClientId(),
+          service.getTypeToken(stableId.getProxyClass()));
+    } else if (stableId.isSynthetic()) {
+      return IdUtil.syntheticId(stableId.getSyntheticId(),
+          service.getTypeToken(stableId.getProxyClass()));
+    } else {
+      return IdUtil.persistedId(
+          SimpleRequestProcessor.toBase64(stableId.getServerId()),
+          service.getTypeToken(stableId.getProxyClass()));
+    }
+  }
+
+  public ServiceLayer getServiceLayer() {
+    return service;
+  }
+
+  /**
+   * If the given domain object has been previously associated with an id,
+   * return it
+   */
+  public SimpleProxyId<?> getStableId(Object domain) {
+    return domainObjectsToId.get(domain);
+  }
+
+  /**
+   * EntityCodex support.
+   */
+  public boolean isEntityType(Class<?> clazz) {
+    return idFactory.isEntityType(clazz);
+  }
+
+  /**
+   * EntityCodex support.
+   */
+  public boolean isValueType(Class<?> clazz) {
+    return idFactory.isValueType(clazz);
+  }
+
+  /**
+   * Creates an AutoBean for the given id, tracking a domain object.
+   */
+  private <Q extends BaseProxy> AutoBean<Q> createEntityProxyBean(
+      SimpleProxyId<Q> id, Object domainObject) {
+    AutoBean<Q> toReturn = AutoBeanFactoryMagic.createBean(id.getProxyClass(),
+        SimpleRequestProcessor.CONFIGURATION);
+    toReturn.setTag(Constants.STABLE_ID, id);
+    toReturn.setTag(Constants.DOMAIN_OBJECT, domainObject);
+    beans.put(id, toReturn);
+    return toReturn;
+  }
+
+  /**
+   * Returns the AutoBean corresponding to the given id, or creates if it does
+   * not yet exist.
+   */
+  private <Q extends BaseProxy> AutoBean<Q> getBeanForPayload(
+      SimpleProxyId<Q> id) {
+    @SuppressWarnings("unchecked")
+    AutoBean<Q> toReturn = (AutoBean<Q>) beans.get(id);
+    if (toReturn == null) {
+      // Resolve the domain object
+      Class<?> domainClass = service.getDomainClass(id.getProxyClass());
+      Object domain;
+      if (id.isEphemeral()) {
+        domain = service.createDomainObject(domainClass);
+      } else {
+        String address = id.getServerId();
+        domain = service.loadDomainObject(this, domainClass, address);
+      }
+      domainObjectsToId.put(domain, id);
+      toReturn = createEntityProxyBean(id, domain);
+    }
+    return toReturn;
+  }
+}
\ No newline at end of file
diff --git a/user/src/com/google/gwt/requestfactory/server/Resolver.java b/user/src/com/google/gwt/requestfactory/server/Resolver.java
new file mode 100644
index 0000000..20abe2c
--- /dev/null
+++ b/user/src/com/google/gwt/requestfactory/server/Resolver.java
@@ -0,0 +1,431 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.requestfactory.server;
+
+import com.google.gwt.autobean.server.impl.TypeUtils;
+import com.google.gwt.autobean.shared.AutoBean;
+import com.google.gwt.autobean.shared.AutoBeanUtils;
+import com.google.gwt.autobean.shared.AutoBeanVisitor;
+import com.google.gwt.autobean.shared.ValueCodex;
+import com.google.gwt.requestfactory.server.SimpleRequestProcessor.ServiceLayer;
+import com.google.gwt.requestfactory.shared.BaseProxy;
+import com.google.gwt.requestfactory.shared.EntityProxy;
+import com.google.gwt.requestfactory.shared.EntityProxyId;
+import com.google.gwt.requestfactory.shared.impl.Constants;
+import com.google.gwt.requestfactory.shared.impl.SimpleProxyId;
+
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+
+/**
+ * Responsible for converting between domain and client entities. This class has
+ * a small amount of temporary state used to handle graph cycles and assignment
+ * of synthetic ids.
+ * 
+ * @see RequestState#getResolver()
+ */
+class Resolver {
+  /**
+   * A parameterized type with a single parameter.
+   */
+  private static class CollectionType implements ParameterizedType {
+    private final Class<?> rawType;
+    private final Class<?> elementType;
+
+    private CollectionType(Class<?> rawType, Class<?> elementType) {
+      this.rawType = rawType;
+      this.elementType = elementType;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (!(o instanceof CollectionType)) {
+        return false;
+      }
+      CollectionType other = (CollectionType) o;
+      return rawType.equals(other.rawType)
+          && elementType.equals(other.elementType);
+    }
+
+    public Type[] getActualTypeArguments() {
+      return new Type[] {elementType};
+    }
+
+    public Type getOwnerType() {
+      return null;
+    }
+
+    public Type getRawType() {
+      return rawType;
+    }
+
+    @Override
+    public int hashCode() {
+      return rawType.hashCode() * 13 + elementType.hashCode() * 7;
+    }
+  }
+
+  /**
+   * Used to map the objects being resolved and its API slice to the client-side
+   * value. This handles the case where a domain object is returned to the
+   * client mapped to two proxies of differing types.
+   */
+  private static class ResolutionKey {
+    private final Object domainObject;
+    private final int hashCode;
+    private final Type requestedType;
+
+    public ResolutionKey(Object domainObject, Type requestedType) {
+      this.domainObject = domainObject;
+      this.requestedType = requestedType;
+      this.hashCode = System.identityHashCode(domainObject) * 13
+          + requestedType.hashCode() * 7;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (!(o instanceof ResolutionKey)) {
+        return false;
+      }
+      ResolutionKey other = (ResolutionKey) o;
+      // Object identity comparison intentional
+      if (domainObject != other.domainObject) {
+        return false;
+      }
+      if (!requestedType.equals(other.requestedType)) {
+        return false;
+      }
+      return true;
+    }
+
+    @Override
+    public int hashCode() {
+      return hashCode;
+    }
+
+    /**
+     * For debugging use only.
+     */
+    @Override
+    public String toString() {
+      return domainObject.toString() + " => " + requestedType.toString();
+    }
+  }
+
+  /**
+   * Returns the trailing {@code [n]} index value from a path.
+   */
+  static int index(String path) {
+    int idx = path.lastIndexOf('[');
+    if (idx == -1) {
+      return -1;
+    }
+    return Integer.parseInt(path.substring(idx + 1, path.lastIndexOf(']')));
+  }
+
+  /**
+   * Returns {@code true} if the given prefix is one of the requested property
+   * references.
+   */
+  static boolean matchesPropertyRef(Set<String> propertyRefs, String newPrefix) {
+    return propertyRefs.contains(newPrefix.replaceAll("\\[\\d+\\]", ""));
+  }
+
+  /**
+   * Removes the trailing {@code [n]} from a path.
+   */
+  static String snipIndex(String path) {
+    int idx = path.lastIndexOf('[');
+    if (idx == -1) {
+      return path;
+    }
+    return path.substring(0, idx);
+  }
+
+  /**
+   * Maps domain values to client values. This map prevents cycles in the object
+   * graph from causing infinite recursion.
+   */
+  private final Map<ResolutionKey, Object> resolved = new HashMap<ResolutionKey, Object>();
+  private final ServiceLayer service;
+  private final RequestState state;
+  private int syntheticId;
+
+  /**
+   * Should only be called from {@link RequestState}.
+   */
+  Resolver(RequestState state) {
+    this.state = state;
+    this.service = state.getServiceLayer();
+  }
+
+  /**
+   * Given a domain object, return a value that can be encoded by the client.
+   * 
+   * @param domainValue the domain object to be converted into a client-side
+   *          value
+   * @param assignableTo the type in the client to which the resolved value
+   *          should be assignable
+   * @param propertyRefs the property references requested by the client
+   */
+  public Object resolveClientValue(Object domainValue, Type assignableTo,
+      Set<String> propertyRefs) {
+    return resolveClientValue(domainValue, assignableTo,
+        getPropertyRefs(propertyRefs), "");
+  }
+
+  /**
+   * Convert a client-side value into a domain value.
+   * 
+   * @param maybeEntityProxy the client object to resolve
+   * @param detectDeadEntities if <code>true</code> this method will throw a
+   *          ReportableException containing a {@link DeadEntityException} if an
+   *          EntityProxy cannot be resolved
+   */
+  public Object resolveDomainValue(Object maybeEntityProxy,
+      boolean detectDeadEntities) {
+    if (maybeEntityProxy instanceof BaseProxy) {
+      AutoBean<BaseProxy> bean = AutoBeanUtils.getAutoBean((BaseProxy) maybeEntityProxy);
+      Object domain = bean.getTag(Constants.DOMAIN_OBJECT);
+      if (domain == null && detectDeadEntities) {
+        throw new ReportableException(new DeadEntityException(
+            "The requested entity is not available on the server"));
+      }
+      return domain;
+    } else if (maybeEntityProxy instanceof Collection<?>) {
+      Collection<Object> accumulator;
+      if (maybeEntityProxy instanceof List) {
+        accumulator = new ArrayList<Object>();
+      } else if (maybeEntityProxy instanceof Set) {
+        accumulator = new HashSet<Object>();
+      } else {
+        throw new ReportableException("Unsupported collection type "
+            + maybeEntityProxy.getClass().getName());
+      }
+      for (Object o : (Collection<?>) maybeEntityProxy) {
+        accumulator.add(resolveDomainValue(o, detectDeadEntities));
+      }
+      return accumulator;
+    }
+    return maybeEntityProxy;
+  }
+
+  /**
+   * Expand the property references in an InvocationMessage into a
+   * fully-expanded list of properties. For example, <code>[foo.bar.baz]</code>
+   * will be converted into <code>[foo, foo.bar, foo.bar.baz]</code>.
+   */
+  private Set<String> getPropertyRefs(Set<String> refs) {
+    if (refs == null) {
+      return Collections.emptySet();
+    }
+
+    Set<String> toReturn = new TreeSet<String>();
+    for (String raw : refs) {
+      for (int idx = raw.length(); idx >= 0; idx = raw.lastIndexOf('.', idx - 1)) {
+        toReturn.add(raw.substring(0, idx));
+      }
+    }
+    return toReturn;
+  }
+
+  /**
+   * Converts a domain entity into an EntityProxy that will be sent to the
+   * client.
+   */
+  private <T extends BaseProxy> T resolveClientProxy(final Object domainEntity,
+      Class<T> proxyType, final Set<String> propertyRefs, ResolutionKey key,
+      final String prefix) {
+    if (domainEntity == null) {
+      return null;
+    }
+
+    SimpleProxyId<? extends BaseProxy> id = state.getStableId(domainEntity);
+
+    boolean isEntityProxy = state.isEntityType(proxyType);
+    final boolean isOwnerValueProxy = state.isValueType(proxyType);
+    int version;
+
+    // Create the id or update an ephemeral id by calculating its address
+    if (id == null || id.isEphemeral()) {
+      // The address is an id or an id plus a path
+      String address;
+      if (isEntityProxy) {
+        // Compute data needed to return id to the client
+        address = service.getFlatId(state, domainEntity);
+        version = service.getVersion(domainEntity);
+      } else {
+        address = null;
+        version = 0;
+      }
+      if (id == null) {
+        if (address == null) {
+          /*
+           * This will happen when server code attempts to return an unpersisted
+           * object to the client. In this case, we'll assign a synthetic id
+           * that is valid for the duration of the response. The client is
+           * expected to assign a client-local id to this object and then it
+           * will behave as though it were an object newly-created by the
+           * client.
+           */
+          id = state.getIdFactory().allocateSyntheticId(proxyType,
+              ++syntheticId);
+        } else {
+          id = state.getIdFactory().getId(proxyType, address, null);
+        }
+      } else {
+        id.setServerId(address);
+      }
+    } else if (isEntityProxy) {
+      // Already have the id, just pull the current version
+      version = service.getVersion(domainEntity);
+    } else {
+      // The version of a value object is always 0
+      version = 0;
+    }
+
+    @SuppressWarnings("unchecked")
+    AutoBean<T> bean = (AutoBean<T>) state.getBeanForPayload(id, domainEntity);
+    resolved.put(key, bean.as());
+    bean.setTag(Constants.IN_RESPONSE, true);
+    bean.setTag(Constants.ENCODED_VERSION_PROPERTY, version);
+    bean.accept(new AutoBeanVisitor() {
+
+      @Override
+      public boolean visitReferenceProperty(String propertyName,
+          AutoBean<?> value, PropertyContext ctx) {
+        // Does the user care about the property?
+        String newPrefix = (prefix.length() > 0 ? (prefix + ".") : "")
+            + propertyName;
+
+        /*
+         * Send the property if the enclosing type is a ValueProxy, if the owner
+         * requested the property, or if the property is a list of values.
+         */
+        Class<?> elementType = ctx instanceof CollectionPropertyContext
+            ? ((CollectionPropertyContext) ctx).getElementType() : null;
+        boolean shouldSend = isOwnerValueProxy
+            || matchesPropertyRef(propertyRefs, newPrefix)
+            || elementType != null && ValueCodex.canDecode(elementType);
+
+        if (!shouldSend) {
+          return false;
+        }
+
+        // Call the getter
+        Object domainValue = service.getProperty(domainEntity, propertyName);
+        if (domainValue == null) {
+          return false;
+        }
+
+        // Turn the domain object into something usable on the client side
+        Type type;
+        if (elementType == null) {
+          type = ctx.getType();
+        } else {
+          type = new CollectionType(ctx.getType(), elementType);
+        }
+        Object clientValue = resolveClientValue(domainValue, type,
+            propertyRefs, newPrefix);
+
+        ctx.set(clientValue);
+        return false;
+      }
+
+      @Override
+      public boolean visitValueProperty(String propertyName, Object value,
+          PropertyContext ctx) {
+        // Limit unrequested value properties?
+        value = service.getProperty(domainEntity, propertyName);
+        ctx.set(value);
+        return false;
+      }
+    });
+
+    return bean.as();
+  }
+
+  /**
+   * Recursive-descent implementation.
+   */
+  private Object resolveClientValue(Object domainValue, Type returnType,
+      Set<String> propertyRefs, String prefix) {
+    if (domainValue == null) {
+      return null;
+    }
+
+    Class<?> assignableTo = TypeUtils.ensureBaseType(returnType);
+    ResolutionKey key = new ResolutionKey(domainValue, returnType);
+
+    Object previous = resolved.get(key);
+    if (previous != null && assignableTo.isInstance(previous)) {
+      return assignableTo.cast(previous);
+    }
+
+    Class<?> returnClass = service.getClientType(domainValue.getClass(),
+        assignableTo);
+
+    // Pass simple values through
+    if (ValueCodex.canDecode(returnClass)) {
+      return assignableTo.cast(domainValue);
+    }
+
+    // Convert entities to EntityProxies or EntityProxyIds
+    boolean isProxy = BaseProxy.class.isAssignableFrom(returnClass);
+    boolean isId = EntityProxyId.class.isAssignableFrom(returnClass);
+    if (isProxy || isId) {
+      BaseProxy entity = resolveClientProxy(domainValue,
+          assignableTo.asSubclass(BaseProxy.class), propertyRefs, key, prefix);
+      if (isId) {
+        return assignableTo.cast(((EntityProxy) entity).stableId());
+      }
+      return assignableTo.cast(entity);
+    }
+
+    // Convert collections
+    if (Collection.class.isAssignableFrom(returnClass)) {
+      Collection<Object> accumulator;
+      if (List.class.isAssignableFrom(returnClass)) {
+        accumulator = new ArrayList<Object>();
+      } else if (Set.class.isAssignableFrom(returnClass)) {
+        accumulator = new HashSet<Object>();
+      } else {
+        throw new ReportableException("Unsupported collection type"
+            + returnClass.getName());
+      }
+      resolved.put(key, accumulator);
+
+      Type elementType = TypeUtils.getSingleParameterization(Collection.class,
+          returnType);
+      for (Object o : (Collection<?>) domainValue) {
+        accumulator.add(resolveClientValue(o, elementType, propertyRefs, prefix));
+      }
+      return assignableTo.cast(accumulator);
+    }
+
+    throw new ReportableException("Unsupported domain type "
+        + returnClass.getCanonicalName());
+  }
+}
\ No newline at end of file
diff --git a/user/src/com/google/gwt/requestfactory/server/SimpleRequestProcessor.java b/user/src/com/google/gwt/requestfactory/server/SimpleRequestProcessor.java
index e8dede4..8637cf6 100644
--- a/user/src/com/google/gwt/requestfactory/server/SimpleRequestProcessor.java
+++ b/user/src/com/google/gwt/requestfactory/server/SimpleRequestProcessor.java
@@ -24,15 +24,17 @@
 import com.google.gwt.autobean.shared.AutoBeanVisitor;
 import com.google.gwt.autobean.shared.Splittable;
 import com.google.gwt.autobean.shared.ValueCodex;
+import com.google.gwt.requestfactory.shared.BaseProxy;
 import com.google.gwt.requestfactory.shared.EntityProxy;
 import com.google.gwt.requestfactory.shared.EntityProxyId;
 import com.google.gwt.requestfactory.shared.InstanceRequest;
 import com.google.gwt.requestfactory.shared.ServerFailure;
 import com.google.gwt.requestfactory.shared.WriteOperation;
+import com.google.gwt.requestfactory.shared.impl.BaseProxyCategory;
 import com.google.gwt.requestfactory.shared.impl.Constants;
 import com.google.gwt.requestfactory.shared.impl.EntityProxyCategory;
-import com.google.gwt.requestfactory.shared.impl.IdFactory;
-import com.google.gwt.requestfactory.shared.impl.SimpleEntityProxyId;
+import com.google.gwt.requestfactory.shared.impl.SimpleProxyId;
+import com.google.gwt.requestfactory.shared.impl.ValueProxyCategory;
 import com.google.gwt.requestfactory.shared.messages.EntityCodex;
 import com.google.gwt.requestfactory.shared.messages.EntityCodex.EntitySource;
 import com.google.gwt.requestfactory.shared.messages.IdUtil;
@@ -53,13 +55,10 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
-import java.util.HashSet;
-import java.util.IdentityHashMap;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import java.util.TreeSet;
 
 import javax.validation.ConstraintViolation;
 
@@ -77,14 +76,24 @@
     /**
      * The way to introduce polymorphism.
      */
-    Class<?> getClientType(Class<?> domainClass);
+    Class<?> getClientType(Class<?> domainClass, Class<?> clientType);
 
     Class<?> getDomainClass(Class<?> clazz);
 
+    /**
+     * May return {@code null} to indicate that the domain object has not been
+     * persisted.
+     */
     String getFlatId(EntitySource source, Object domainObject);
 
     Object getProperty(Object domainObject, String property);
 
+    /**
+     * Compute the return type for a method declared in a RequestContext by
+     * analyzing the generic method declaration.
+     */
+    Type getRequestReturnType(Method contextMethod);
+
     String getTypeToken(Class<?> domainClass);
 
     int getVersion(Object domainObject);
@@ -93,7 +102,7 @@
 
     Object loadDomainObject(EntitySource source, Class<?> clazz, String flatId);
 
-    Class<? extends EntityProxy> resolveClass(String typeToken);
+    Class<? extends BaseProxy> resolveClass(String typeToken);
 
     Method resolveDomainMethod(Method requestContextMethod);
 
@@ -111,120 +120,24 @@
    * specific type.
    */
   @SuppressWarnings("serial")
-  private static class IdToEntityMap extends
-      HashMap<SimpleEntityProxyId<?>, AutoBean<? extends EntityProxy>> {
+  static class IdToEntityMap extends
+      HashMap<SimpleProxyId<?>, AutoBean<? extends BaseProxy>> {
   }
 
-  private class RequestState implements EntityCodex.EntitySource {
-    private final IdToEntityMap beans = new IdToEntityMap();
-    private final Map<Object, Integer> domainObjectsToClientId;
-    private final IdFactory idFactory;
+  /**
+   * Allows the creation of properly-configured AutoBeans without having to
+   * create an AutoBeanFactory with the desired annotations.
+   */
+  static final Configuration CONFIGURATION = new Configuration.Builder().setCategories(
+      EntityProxyCategory.class, ValueProxyCategory.class,
+      BaseProxyCategory.class).setNoWrap(EntityProxyId.class).build();
 
-    public RequestState() {
-      idFactory = new IdFactory() {
-        @Override
-        @SuppressWarnings("unchecked")
-        protected <P extends EntityProxy> Class<P> getTypeFromToken(
-            String typeToken) {
-          return (Class<P>) service.resolveClass(typeToken);
-        }
+  /**
+   * Vends message objects.
+   */
+  static final MessageFactory FACTORY = AutoBeanFactoryMagic.create(MessageFactory.class);
 
-        @Override
-        protected String getTypeToken(Class<?> clazz) {
-          return service.getTypeToken(clazz);
-        }
-      };
-      domainObjectsToClientId = new IdentityHashMap<Object, Integer>();
-    }
-
-    public RequestState(RequestState parent) {
-      idFactory = parent.idFactory;
-      domainObjectsToClientId = parent.domainObjectsToClientId;
-    }
-
-    public <Q extends EntityProxy> AutoBean<Q> getBeanForPayload(
-        SimpleEntityProxyId<Q> id) {
-      @SuppressWarnings("unchecked")
-      AutoBean<Q> toReturn = (AutoBean<Q>) beans.get(id);
-      if (toReturn == null) {
-        toReturn = AutoBeanFactoryMagic.createBean(id.getProxyClass(),
-            CONFIGURATION);
-        toReturn.setTag(STABLE_ID, id);
-
-        // Resolve the domain object
-        Class<?> domainClass = service.getDomainClass(id.getProxyClass());
-
-        Object domain;
-        if (id.isEphemeral()) {
-          domain = service.createDomainObject(domainClass);
-          // Don't call getFlatId here, resolve the ids after invocations
-          domainObjectsToClientId.put(domain, id.getClientId());
-        } else {
-          domain = service.loadDomainObject(this, domainClass,
-              fromBase64(id.getServerId()));
-        }
-        toReturn.setTag(DOMAIN_OBJECT, domain);
-
-        beans.put(id, toReturn);
-      }
-      return toReturn;
-    }
-
-    /**
-     * EntityCodex support.
-     */
-    public <Q extends EntityProxy> AutoBean<Q> getBeanForPayload(
-        String serializedProxyId) {
-      String typeToken = IdUtil.getTypeToken(serializedProxyId);
-      SimpleEntityProxyId<Q> id;
-      if (IdUtil.isEphemeral(serializedProxyId)) {
-        id = idFactory.getId(typeToken, null,
-            IdUtil.getClientId(serializedProxyId));
-      } else {
-        id = idFactory.getId(typeToken, IdUtil.getServerId(serializedProxyId));
-      }
-      return getBeanForPayload(id);
-    }
-
-    /**
-     * If the domain object was sent with an ephemeral id, return the client id.
-     */
-    public Integer getClientId(Object domainEntity) {
-      return domainObjectsToClientId.get(domainEntity);
-    }
-
-    /**
-     * EntityCodex support.
-     */
-    public String getSerializedProxyId(EntityProxyId<?> stableId) {
-      SimpleEntityProxyId<?> impl = (SimpleEntityProxyId<?>) stableId;
-      if (impl.isEphemeral()) {
-        return IdUtil.ephemeralId(impl.getClientId(),
-            service.getTypeToken(stableId.getProxyClass()));
-      } else {
-        return IdUtil.persistedId(impl.getServerId(),
-            service.getTypeToken(stableId.getProxyClass()));
-      }
-    }
-
-    /**
-     * EntityCodex support.
-     */
-    public boolean isEntityType(Class<?> clazz) {
-      return EntityProxy.class.isAssignableFrom(clazz);
-    }
-  }
-
-  private static final Configuration CONFIGURATION = new Configuration.Builder().setCategories(
-      EntityProxyCategory.class).setNoWrap(EntityProxyId.class).build();
-  private static final MessageFactory FACTORY = AutoBeanFactoryMagic.create(MessageFactory.class);
-
-  // Tag constants
-  private static final String DOMAIN_OBJECT = "domainObject";
-  private static final String IN_RESPONSE = "inResponse";
-  private static final String STABLE_ID = "stableId";
-
-  private static String fromBase64(String encoded) {
+  static String fromBase64(String encoded) {
     try {
       return new String(Base64Utils.fromBase64(encoded), "UTF-8");
     } catch (UnsupportedEncodingException e) {
@@ -232,7 +145,7 @@
     }
   }
 
-  private static String toBase64(String data) {
+  static String toBase64(String data) {
     try {
       return Base64Utils.toBase64(data.getBytes("UTF-8"));
     } catch (UnsupportedEncodingException e) {
@@ -241,6 +154,7 @@
   }
 
   private ExceptionHandler exceptionHandler = new DefaultExceptionHandler();
+
   private final ServiceLayer service;
 
   public SimpleRequestProcessor(ServiceLayer serviceLayer) {
@@ -271,7 +185,7 @@
    * Main processing method.
    */
   void process(RequestMessage req, ResponseMessage resp) {
-    final RequestState source = new RequestState();
+    final RequestState source = new RequestState(service);
     // Apply operations
     processOperationMessages(source, req);
 
@@ -319,41 +233,44 @@
 
   private void createReturnOperations(List<OperationMessage> operations,
       RequestState returnState, IdToEntityMap toProcess) {
-    for (Map.Entry<SimpleEntityProxyId<?>, AutoBean<? extends EntityProxy>> entry : toProcess.entrySet()) {
-      SimpleEntityProxyId<?> id = entry.getKey();
+    for (Map.Entry<SimpleProxyId<?>, AutoBean<? extends BaseProxy>> entry : toProcess.entrySet()) {
+      SimpleProxyId<?> id = entry.getKey();
 
-      AutoBean<? extends EntityProxy> bean = entry.getValue();
-      Object domainObject = bean.getTag(DOMAIN_OBJECT);
+      AutoBean<? extends BaseProxy> bean = entry.getValue();
+      Object domainObject = bean.getTag(Constants.DOMAIN_OBJECT);
       WriteOperation writeOperation;
 
       if (id.isEphemeral()) {
-        resolveClientEntityProxy(returnState, domainObject,
-            Collections.<String> emptySet(), "");
-        if (id.isEphemeral()) {
-          throw new ReportableException("Could not persist entity "
-              + service.getFlatId(returnState, domainObject.toString()));
-        }
+        // See if the entity has been persisted in the meantime
+        returnState.getResolver().resolveClientValue(domainObject,
+            id.getProxyClass(), Collections.<String> emptySet());
       }
 
-      if (service.loadDomainObject(returnState,
-          service.getDomainClass(id.getProxyClass()),
-          fromBase64(id.getServerId())) == null) {
+      int version;
+      if (id.isEphemeral() || id.isSynthetic()) {
+        // If the object isn't persistent, there's no reason to send an update
+        writeOperation = null;
+        version = 0;
+      } else if (service.loadDomainObject(returnState,
+          service.getDomainClass(id.getProxyClass()), id.getServerId()) == null) {
         writeOperation = WriteOperation.DELETE;
+        version = 0;
       } else if (id.wasEphemeral()) {
         writeOperation = WriteOperation.PERSIST;
+        version = service.getVersion(domainObject);
       } else {
         writeOperation = WriteOperation.UPDATE;
+        version = service.getVersion(domainObject);
       }
 
-      boolean inResponse = bean.getTag(IN_RESPONSE) != null;
-      int version = domainObject == null ? 0 : service.getVersion(domainObject);
+      boolean inResponse = bean.getTag(Constants.IN_RESPONSE) != null;
 
       /*
        * Don't send any data back to the client for an update on an object that
        * isn't part of the response payload when the client's version matches
        * the domain version.
        */
-      if (writeOperation.equals(WriteOperation.UPDATE) && !inResponse) {
+      if (WriteOperation.UPDATE.equals(writeOperation) && !inResponse) {
         if (Integer.valueOf(version).equals(
             bean.getTag(Constants.ENCODED_VERSION_PROPERTY))) {
           continue;
@@ -361,9 +278,15 @@
       }
 
       OperationMessage op = FACTORY.operation().as();
-      if (writeOperation == WriteOperation.PERSIST) {
+
+      /*
+       * Send a client id if the id is ephemeral or was previously associated
+       * with a client id.
+       */
+      if (id.wasEphemeral()) {
         op.setClientId(id.getClientId());
       }
+
       op.setOperation(writeOperation);
 
       // Only send properties for entities that are part of the return graph
@@ -380,7 +303,12 @@
         op.setPropertyMap(propertyMap);
       }
 
-      op.setServerId(id.getServerId());
+      if (!id.isEphemeral() && !id.isSynthetic()) {
+        // Send the server address only for persistent objects
+        op.setServerId(toBase64(id.getServerId()));
+      }
+
+      op.setSyntheticId(id.getSyntheticId());
       op.setTypeToken(service.getTypeToken(id.getProxyClass()));
       op.setVersion(version);
 
@@ -411,7 +339,8 @@
         split = invocation.getParameters().get(i);
       }
       Object arg = EntityCodex.decode(source, type, elementType, split);
-      arg = resolveDomainValue(arg, !EntityProxyId.class.equals(contextArgs[i]));
+      arg = source.getResolver().resolveDomainValue(arg,
+          !EntityProxyId.class.equals(contextArgs[i]));
       args.add(arg);
     }
 
@@ -446,26 +375,6 @@
     return args;
   }
 
-  /**
-   * Expand the property references in an InvocationMessage into a
-   * fully-expanded list of properties. For example, <code>[foo.bar.baz]</code>
-   * will be converted into <code>[foo, foo.bar, foo.bar.baz]</code>.
-   */
-  private Set<String> getPropertyRefs(InvocationMessage invocation) {
-    Set<String> refs = invocation.getPropertyRefs();
-    if (refs == null) {
-      return Collections.emptySet();
-    }
-
-    Set<String> toReturn = new TreeSet<String>();
-    for (String raw : refs) {
-      for (int idx = raw.length(); idx >= 0; idx = raw.lastIndexOf('.', idx - 1)) {
-        toReturn.add(raw.substring(0, idx));
-      }
-    }
-    return toReturn;
-  }
-
   private void processInvocationMessages(RequestState state,
       List<InvocationMessage> invlist, List<Splittable> results,
       List<Boolean> success, RequestState returnState) {
@@ -483,8 +392,9 @@
         Object returnValue = service.invoke(domainMethod, args.toArray());
 
         // Convert domain object to client object
-        returnValue = resolveClientValue(returnState, returnValue,
-            getPropertyRefs(invocation), "");
+        Type requestReturnType = service.getRequestReturnType(contextMethod);
+        returnValue = state.getResolver().resolveClientValue(returnValue,
+            requestReturnType, invocation.getPropertyRefs());
 
         // Convert the client object to a string
         results.add(EntityCodex.encode(returnState, returnValue));
@@ -511,10 +421,10 @@
               operation.getServerId(), operation.getTypeToken());
       AutoBean<? extends EntityProxy> bean = state.getBeanForPayload(payloadId);
       // Use the version later to know which objects need to be sent back
-      bean.setTag(Constants.ENCODED_ID_PROPERTY, operation.getVersion());
+      bean.setTag(Constants.ENCODED_VERSION_PROPERTY, operation.getVersion());
 
       // Load the domain object with properties, if it exists
-      final Object domain = bean.getTag(DOMAIN_OBJECT);
+      final Object domain = bean.getTag(Constants.DOMAIN_OBJECT);
       if (domain != null) {
         // Apply any property updates
         final Map<String, Splittable> flatValueMap = operation.getPropertyMap();
@@ -529,7 +439,8 @@
                     ? ((CollectionPropertyContext) ctx).getElementType() : null;
                 Object newValue = EntityCodex.decode(state, ctx.getType(),
                     elementType, flatValueMap.get(propertyName));
-                Object resolved = resolveDomainValue(newValue, false);
+                Object resolved = state.getResolver().resolveDomainValue(
+                    newValue, false);
                 service.setProperty(domain, propertyName,
                     service.getDomainClass(ctx.getType()), resolved);
               }
@@ -542,7 +453,8 @@
               if (flatValueMap.containsKey(propertyName)) {
                 Splittable split = flatValueMap.get(propertyName);
                 Object newValue = ValueCodex.decode(ctx.getType(), split);
-                Object resolved = resolveDomainValue(newValue, false);
+                Object resolved = state.getResolver().resolveDomainValue(
+                    newValue, false);
                 service.setProperty(domain, propertyName, ctx.getType(),
                     resolved);
               }
@@ -555,175 +467,30 @@
   }
 
   /**
-   * Converts a domain entity into an EntityProxy that will be sent to the
-   * client.
-   */
-  private EntityProxy resolveClientEntityProxy(final RequestState state,
-      final Object domainEntity, final Set<String> propertyRefs,
-      final String prefix) {
-    if (domainEntity == null) {
-      return null;
-    }
-
-    // Compute data needed to return id to the client
-    String flatId = toBase64(service.getFlatId(state, domainEntity));
-    Class<? extends EntityProxy> proxyType = service.getClientType(
-        domainEntity.getClass()).asSubclass(EntityProxy.class);
-
-    // Retrieve the id, possibly setting its persisted value
-    SimpleEntityProxyId<? extends EntityProxy> id = state.idFactory.getId(
-        proxyType, flatId, state.getClientId(domainEntity));
-
-    AutoBean<? extends EntityProxy> bean = state.getBeanForPayload(id);
-    bean.setTag(IN_RESPONSE, true);
-    bean.accept(new AutoBeanVisitor() {
-      @Override
-      public boolean visitReferenceProperty(String propertyName,
-          AutoBean<?> value, PropertyContext ctx) {
-        // Does the user care about the property?
-        String newPrefix = (prefix.length() > 0 ? (prefix + ".") : "")
-            + propertyName;
-
-        /*
-         * Send if the user cares about the property or if it's a collection of
-         * values.
-         */
-        Class<?> elementType = ctx instanceof CollectionPropertyContext
-            ? ((CollectionPropertyContext) ctx).getElementType() : null;
-        boolean shouldSend = propertyRefs.contains(newPrefix)
-            || elementType != null && !state.isEntityType(elementType);
-
-        if (!shouldSend) {
-          return false;
-        }
-
-        // Call the getter
-        Object domainValue = service.getProperty(domainEntity, propertyName);
-        if (domainValue == null) {
-          return false;
-        }
-
-        // Turn the domain object into something usable on the client side
-        Object resolved = resolveClientValue(state, domainValue, propertyRefs,
-            newPrefix);
-
-        ctx.set(ctx.getType().cast(resolved));
-        return false;
-      }
-
-      @Override
-      public boolean visitValueProperty(String propertyName, Object value,
-          PropertyContext ctx) {
-        // Limit unrequested value properties?
-        value = service.getProperty(domainEntity, propertyName);
-        ctx.set(ctx.getType().cast(value));
-        return false;
-      }
-    });
-    bean.setTag(Constants.ENCODED_VERSION_PROPERTY,
-        service.getVersion(domainEntity));
-    return proxyType.cast(bean.as());
-  }
-
-  /**
-   * Given a method a domain object, return a value that can be encoded by the
-   * client.
-   */
-  private Object resolveClientValue(RequestState source, Object domainValue,
-      Set<String> propertyRefs, String prefix) {
-    if (domainValue == null) {
-      return null;
-    }
-
-    Class<?> returnClass = service.getClientType(domainValue.getClass());
-
-    // Pass simple values through
-    if (ValueCodex.canDecode(returnClass)) {
-      return domainValue;
-    }
-
-    // Convert entities to EntityProxies
-    if (EntityProxy.class.isAssignableFrom(returnClass)) {
-      return resolveClientEntityProxy(source, domainValue, propertyRefs, prefix);
-    }
-
-    // Convert collections
-    if (Collection.class.isAssignableFrom(returnClass)) {
-      Collection<Object> accumulator;
-      if (List.class.isAssignableFrom(returnClass)) {
-        accumulator = new ArrayList<Object>();
-      } else if (Set.class.isAssignableFrom(returnClass)) {
-        accumulator = new HashSet<Object>();
-      } else {
-        throw new ReportableException("Unsupported collection type"
-            + returnClass.getName());
-      }
-
-      for (Object o : (Collection<?>) domainValue) {
-        accumulator.add(resolveClientValue(source, o, propertyRefs, prefix));
-      }
-      return accumulator;
-    }
-
-    throw new ReportableException("Unsupported domain type "
-        + returnClass.getCanonicalName());
-  }
-
-  /**
-   * Convert a client-side value into a domain value.
-   * 
-   * @param the client object to resolve
-   * @param detectDeadEntities if <code>true</code> this method will throw a
-   *          ReportableException containing a {@link DeadEntityException} if an
-   *          EntityProxy cannot be resolved
-   */
-  private Object resolveDomainValue(Object maybeEntityProxy,
-      boolean detectDeadEntities) {
-    if (maybeEntityProxy instanceof EntityProxy) {
-      AutoBean<EntityProxy> bean = AutoBeanUtils.getAutoBean((EntityProxy) maybeEntityProxy);
-      Object domain = bean.getTag(DOMAIN_OBJECT);
-      if (domain == null && detectDeadEntities) {
-        throw new ReportableException(new DeadEntityException(
-            "The requested entity is not available on the server"));
-      }
-      return domain;
-    } else if (maybeEntityProxy instanceof Collection<?>) {
-      Collection<Object> accumulator;
-      if (maybeEntityProxy instanceof List) {
-        accumulator = new ArrayList<Object>();
-      } else if (maybeEntityProxy instanceof Set) {
-        accumulator = new HashSet<Object>();
-      } else {
-        throw new ReportableException("Unsupported collection type "
-            + maybeEntityProxy.getClass().getName());
-      }
-      for (Object o : (Collection<?>) maybeEntityProxy) {
-        accumulator.add(resolveDomainValue(o, detectDeadEntities));
-      }
-      return accumulator;
-    }
-    return maybeEntityProxy;
-  }
-
-  /**
    * Validate all of the entities referenced in a RequestState.
    */
   private List<ViolationMessage> validateEntities(RequestState source) {
     List<ViolationMessage> errorMessages = new ArrayList<ViolationMessage>();
-    for (Map.Entry<SimpleEntityProxyId<?>, AutoBean<? extends EntityProxy>> entry : source.beans.entrySet()) {
-      Object domainObject = entry.getValue().getTag(DOMAIN_OBJECT);
+    for (Map.Entry<SimpleProxyId<?>, AutoBean<? extends BaseProxy>> entry : source.beans.entrySet()) {
+      AutoBean<? extends BaseProxy> bean = entry.getValue();
+      Object domainObject = bean.getTag(Constants.DOMAIN_OBJECT);
 
       // The object could have been deleted
       if (domainObject != null) {
         Set<ConstraintViolation<Object>> errors = service.validate(domainObject);
         if (errors != null && !errors.isEmpty()) {
-          SimpleEntityProxyId<?> id = entry.getKey();
+          SimpleProxyId<?> id = entry.getKey();
           for (ConstraintViolation<Object> error : errors) {
             ViolationMessage message = FACTORY.violation().as();
             message.setClientId(id.getClientId());
             message.setMessage(error.getMessage());
             message.setPath(error.getPropertyPath().toString());
-            message.setServerId(id.getServerId());
+            if (id.isEphemeral()) {
+              message.setClientId(id.getClientId());
+            }
+            if (id.getServerId() != null) {
+              message.setServerId(toBase64(id.getServerId()));
+            }
             message.setTypeToken(service.getTypeToken(id.getProxyClass()));
             errorMessages.add(message);
           }
diff --git a/user/src/com/google/gwt/requestfactory/server/testing/InProcessRequestFactory.java b/user/src/com/google/gwt/requestfactory/server/testing/InProcessRequestFactory.java
index e6c412b..74247af 100644
--- a/user/src/com/google/gwt/requestfactory/server/testing/InProcessRequestFactory.java
+++ b/user/src/com/google/gwt/requestfactory/server/testing/InProcessRequestFactory.java
@@ -21,12 +21,16 @@
 import com.google.gwt.autobean.shared.AutoBeanFactory.NoWrap;
 import com.google.gwt.event.shared.EventBus;
 import com.google.gwt.requestfactory.server.testing.InProcessRequestContext.RequestContextHandler;
+import com.google.gwt.requestfactory.shared.BaseProxy;
 import com.google.gwt.requestfactory.shared.EntityProxy;
 import com.google.gwt.requestfactory.shared.EntityProxyId;
 import com.google.gwt.requestfactory.shared.RequestContext;
 import com.google.gwt.requestfactory.shared.RequestFactory;
+import com.google.gwt.requestfactory.shared.ValueProxy;
 import com.google.gwt.requestfactory.shared.impl.AbstractRequestFactory;
+import com.google.gwt.requestfactory.shared.impl.BaseProxyCategory;
 import com.google.gwt.requestfactory.shared.impl.EntityProxyCategory;
+import com.google.gwt.requestfactory.shared.impl.ValueProxyCategory;
 
 import java.lang.reflect.InvocationHandler;
 import java.lang.reflect.InvocationTargetException;
@@ -37,7 +41,9 @@
  * An JRE-compatible implementation of RequestFactory.
  */
 class InProcessRequestFactory extends AbstractRequestFactory {
-  @Category(EntityProxyCategory.class)
+  @Category(value = {
+      EntityProxyCategory.class, ValueProxyCategory.class,
+      BaseProxyCategory.class})
   @NoWrap(EntityProxyId.class)
   interface Factory extends AutoBeanFactory {
   }
@@ -60,7 +66,7 @@
           InProcessRequestFactory.this).new RequestContextHandler();
       return context.cast(Proxy.newProxyInstance(
           Thread.currentThread().getContextClassLoader(),
-          new Class<?>[]{context}, handler));
+          new Class<?>[] {context}, handler));
     }
   }
 
@@ -71,17 +77,27 @@
   }
 
   @Override
+  public boolean isEntityType(Class<?> clazz) {
+    return EntityProxy.class.isAssignableFrom(clazz);
+  }
+
+  @Override
+  public boolean isValueType(Class<?> clazz) {
+    return ValueProxy.class.isAssignableFrom(clazz);
+  }
+
+  @Override
   protected AutoBeanFactory getAutoBeanFactory() {
     return AutoBeanFactoryMagic.create(Factory.class);
   }
 
   @Override
   @SuppressWarnings("unchecked")
-  protected <P extends EntityProxy> Class<P> getTypeFromToken(String typeToken) {
+  protected <P extends BaseProxy> Class<P> getTypeFromToken(String typeToken) {
     try {
-      Class<? extends EntityProxy> found = Class.forName(typeToken, false,
+      Class<? extends BaseProxy> found = Class.forName(typeToken, false,
           Thread.currentThread().getContextClassLoader()).asSubclass(
-          EntityProxy.class);
+          BaseProxy.class);
       return (Class<P>) found;
     } catch (ClassNotFoundException e) {
       return null;
@@ -90,6 +106,6 @@
 
   @Override
   protected String getTypeToken(Class<?> clazz) {
-    return EntityProxy.class.isAssignableFrom(clazz) ? clazz.getName() : null;
+    return isEntityType(clazz) || isValueType(clazz) ? clazz.getName() : null;
   }
 }
diff --git a/user/src/com/google/gwt/requestfactory/shared/BaseProxy.java b/user/src/com/google/gwt/requestfactory/shared/BaseProxy.java
new file mode 100644
index 0000000..3764fc0
--- /dev/null
+++ b/user/src/com/google/gwt/requestfactory/shared/BaseProxy.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.requestfactory.shared;
+
+/**
+ * The root type from which all client-side proxy objects are derived. Users
+ * should not extend the BaseProxy type directly, but use either
+ * {@link EntityProxy} or {@link ValueProxy}.
+ * 
+ * @see EntityProxy
+ * @see ValueProxy
+ */
+public interface BaseProxy {
+}
diff --git a/user/src/com/google/gwt/requestfactory/shared/EntityProxy.java b/user/src/com/google/gwt/requestfactory/shared/EntityProxy.java
index 1e5cecb..4bb312d 100644
--- a/user/src/com/google/gwt/requestfactory/shared/EntityProxy.java
+++ b/user/src/com/google/gwt/requestfactory/shared/EntityProxy.java
@@ -18,7 +18,7 @@
 /**
  * A proxy for a server-side domain object.
  */
-public interface EntityProxy {
+public interface EntityProxy extends BaseProxy {
   /**
    * Returns the {@link EntityProxyId} that identifies a particular instance of
    * the type proxied by the receiver.
diff --git a/user/src/com/google/gwt/requestfactory/shared/RequestContext.java b/user/src/com/google/gwt/requestfactory/shared/RequestContext.java
index 495d252..c5ad7af 100644
--- a/user/src/com/google/gwt/requestfactory/shared/RequestContext.java
+++ b/user/src/com/google/gwt/requestfactory/shared/RequestContext.java
@@ -21,13 +21,13 @@
 public interface RequestContext {
   /**
    * Returns a new mutable proxy that this request can carry to the server,
-   * perhaps to be persisted. If a persist does happen, a CREATE event will be
-   * posted including the EntityProxyId of this proxy.
-   *
+   * perhaps to be persisted. If the object is succesfully persisted, a PERSIST
+   * event will be posted including the EntityProxyId of this proxy.
+   * 
    * @param clazz a Class object of type T
-   * @return an {@link EntityProxy} instance of type T
+   * @return an {@link BaseProxy} instance of type T
    */
-  <T extends EntityProxy> T create(Class<T> clazz);
+  <T extends BaseProxy> T create(Class<T> clazz);
 
   /**
    * Returns a mutable version of the proxy, whose mutations will accumulate in
@@ -35,9 +35,9 @@
    * be mutable.
    * 
    * @param object an instance of type T
-   * @return an {@link EntityProxy} instance of type T
+   * @return an {@link EntityProxy} or {@link ValueProxy} instance of type T
    */
-  <T extends EntityProxy> T edit(T object);
+  <T extends BaseProxy> T edit(T object);
 
   /**
    * Send the accumulated changes and method invocations associated with the
@@ -51,7 +51,7 @@
 
   /**
    * For receiving errors or validation failures only.
-   *
+   * 
    * @param receiver a {@link Receiver} instance
    * @throws IllegalArgumentException if <code>receiver</code> is
    *           <code>null</code>
diff --git a/user/src/com/google/gwt/requestfactory/shared/ValueProxy.java b/user/src/com/google/gwt/requestfactory/shared/ValueProxy.java
new file mode 100644
index 0000000..2882b3e
--- /dev/null
+++ b/user/src/com/google/gwt/requestfactory/shared/ValueProxy.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.requestfactory.shared;
+
+/**
+ * An analog to EntityProxy for domain types that do not have an identity
+ * concept.
+ */
+public interface ValueProxy extends BaseProxy {
+}
diff --git a/user/src/com/google/gwt/requestfactory/shared/Violation.java b/user/src/com/google/gwt/requestfactory/shared/Violation.java
index 3698740..23466b0 100644
--- a/user/src/com/google/gwt/requestfactory/shared/Violation.java
+++ b/user/src/com/google/gwt/requestfactory/shared/Violation.java
@@ -21,6 +21,14 @@
  */
 public interface Violation {
   /**
+   * If the ConstraintViolation occurred while validating a object, this method
+   * will return a BaseProxy that contains the invalid values.
+   * 
+   * @return the BaseProxy that caused the ConstraintViolation
+   */
+  BaseProxy getInvalidProxy();
+
+  /**
    * Returns the message associated with this {@link Violation}.
    * 
    * @return a String message
@@ -28,6 +36,16 @@
   String getMessage();
 
   /**
+   * If the ConstraintViolation occurred while validating a value object that
+   * originated from the server, this method will return a BaseProxy that
+   * contains the original values.
+   * 
+   * @return the BaseProxy originally sent by the server or {@code null} if the
+   *         BaseProxy was created on the client.
+   */
+  BaseProxy getOriginalProxy();
+
+  /**
    * Returns the path associated with this {@link Violation}.
    * 
    * @return a String path
@@ -35,9 +53,11 @@
   String getPath();
 
   /**
-   * Returns the proxy id associated with this {@link Violation}.
+   * Returns the proxy id associated with this {@link Violation} if the object
+   * associated with the violation is an {@link EntityProxy}.
    * 
-   * @return an {@link EntityProxyId} instance
+   * @return an {@link EntityProxyId} instance or {@code null} if the object is
+   *         a ValueProxy.
    */
   EntityProxyId<?> getProxyId();
 }
diff --git a/user/src/com/google/gwt/requestfactory/shared/impl/AbstractRequestContext.java b/user/src/com/google/gwt/requestfactory/shared/impl/AbstractRequestContext.java
index 680286b..1b3befb 100644
--- a/user/src/com/google/gwt/requestfactory/shared/impl/AbstractRequestContext.java
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/AbstractRequestContext.java
@@ -15,12 +15,16 @@
  */
 package com.google.gwt.requestfactory.shared.impl;
 
+import static com.google.gwt.requestfactory.shared.impl.BaseProxyCategory.stableId;
+import static com.google.gwt.requestfactory.shared.impl.Constants.REQUEST_CONTEXT;
+
 import com.google.gwt.autobean.shared.AutoBean;
 import com.google.gwt.autobean.shared.AutoBeanCodex;
 import com.google.gwt.autobean.shared.AutoBeanUtils;
 import com.google.gwt.autobean.shared.AutoBeanVisitor;
 import com.google.gwt.autobean.shared.Splittable;
 import com.google.gwt.autobean.shared.ValueCodex;
+import com.google.gwt.requestfactory.shared.BaseProxy;
 import com.google.gwt.requestfactory.shared.EntityProxy;
 import com.google.gwt.requestfactory.shared.EntityProxyChange;
 import com.google.gwt.requestfactory.shared.EntityProxyId;
@@ -28,10 +32,13 @@
 import com.google.gwt.requestfactory.shared.RequestContext;
 import com.google.gwt.requestfactory.shared.RequestTransport.TransportReceiver;
 import com.google.gwt.requestfactory.shared.ServerFailure;
+import com.google.gwt.requestfactory.shared.ValueProxy;
 import com.google.gwt.requestfactory.shared.Violation;
 import com.google.gwt.requestfactory.shared.WriteOperation;
+import com.google.gwt.requestfactory.shared.impl.posers.DatePoser;
 import com.google.gwt.requestfactory.shared.messages.EntityCodex;
 import com.google.gwt.requestfactory.shared.messages.IdMessage;
+import com.google.gwt.requestfactory.shared.messages.IdUtil;
 import com.google.gwt.requestfactory.shared.messages.InvocationMessage;
 import com.google.gwt.requestfactory.shared.messages.MessageFactory;
 import com.google.gwt.requestfactory.shared.messages.OperationMessage;
@@ -41,6 +48,9 @@
 import com.google.gwt.requestfactory.shared.messages.ViolationMessage;
 
 import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.LinkedHashMap;
@@ -55,20 +65,46 @@
     EntityCodex.EntitySource {
   private class MyViolation implements Violation {
 
+    private final BaseProxy currentProxy;
     private final EntityProxyId<?> id;
-    private final String path;
     private final String message;
+    private final String path;
+    private final BaseProxy parentProxy;
 
     public MyViolation(ViolationMessage message) {
-      id = getId(message);
+      // Support violations for value objects.
+      SimpleProxyId<BaseProxy> baseId = getId(message);
+      if (baseId instanceof EntityProxyId<?>) {
+        id = (EntityProxyId<?>) baseId;
+      } else {
+        id = null;
+      }
+      // The stub is empty, since we don't process any OperationMessages
+      AutoBean<BaseProxy> stub = getProxyForReturnPayloadGraph(baseId);
+
+      // So pick up the instance that we just sent to the server
+      AutoBean<?> edited = editedProxies.get(BaseProxyCategory.stableId(stub));
+      currentProxy = (BaseProxy) edited.as();
+
+      // Try to find the original, immutable version.
+      AutoBean<BaseProxy> parentBean = edited.getTag(PARENT_OBJECT);
+      parentProxy = parentBean == null ? null : parentBean.as();
       path = message.getPath();
       this.message = message.getMessage();
     }
 
+    public BaseProxy getInvalidProxy() {
+      return currentProxy;
+    }
+
     public String getMessage() {
       return message;
     }
 
+    public BaseProxy getOriginalProxy() {
+      return parentProxy;
+    }
+
     public String getPath() {
       return path;
     }
@@ -88,12 +124,20 @@
    * Objects are placed into this map by being passed into {@link #edit} or as
    * an invocation argument.
    */
-  private final Map<SimpleEntityProxyId<?>, AutoBean<?>> editedProxies = new LinkedHashMap<SimpleEntityProxyId<?>, AutoBean<?>>();
+  private final Map<SimpleProxyId<?>, AutoBean<?>> editedProxies = new LinkedHashMap<SimpleProxyId<?>, AutoBean<?>>();
   /**
    * A map that contains the canonical instance of an entity to return in the
    * return graph, since this is built from scratch.
    */
-  private final Map<SimpleEntityProxyId<?>, AutoBean<?>> returnedProxies = new HashMap<SimpleEntityProxyId<?>, AutoBean<?>>();
+  private final Map<SimpleProxyId<?>, AutoBean<?>> returnedProxies = new HashMap<SimpleProxyId<?>, AutoBean<?>>();
+
+  /**
+   * A map that allows us to handle the case where the server has sent back an
+   * unpersisted entity. Because we assume that the server is stateless, the
+   * client will need to swap out the request-local ids with a regular
+   * client-allocated id.
+   */
+  private final Map<Integer, SimpleProxyId<?>> syntheticIds = new HashMap<Integer, SimpleProxyId<?>>();
 
   protected AbstractRequestContext(AbstractRequestFactory factory) {
     this.requestFactory = factory;
@@ -102,21 +146,27 @@
   /**
    * Create a new object, with an ephemeral id.
    */
-  public <T extends EntityProxy> T create(Class<T> clazz) {
+  public <T extends BaseProxy> T create(Class<T> clazz) {
     checkLocked();
 
-    AutoBean<T> created = requestFactory.createEntityProxy(clazz,
+    AutoBean<T> created = requestFactory.createProxy(clazz,
         requestFactory.allocateId(clazz));
     return takeOwnership(created);
   }
 
-  public <T extends EntityProxy> T edit(T object) {
-    AutoBean<T> bean = checkStreamsNotCrossed(object);
+  public <T extends BaseProxy> T edit(T object) {
+    return editProxy(object);
+  }
 
+  /**
+   * Take ownership of a proxy instance and make it editable.
+   */
+  public <T extends BaseProxy> T editProxy(T object) {
+    AutoBean<T> bean = checkStreamsNotCrossed(object);
     checkLocked();
 
     @SuppressWarnings("unchecked")
-    AutoBean<T> previouslySeen = (AutoBean<T>) editedProxies.get(object.stableId());
+    AutoBean<T> previouslySeen = (AutoBean<T>) editedProxies.get(BaseProxyCategory.stableId(bean));
     if (previouslySeen != null && !previouslySeen.isFrozen()) {
       /*
        * If we've seen the object before, it might be because it was passed in
@@ -130,7 +180,7 @@
     AutoBean<T> parent = bean;
     bean = cloneBeanAndCollections(bean);
     bean.setTag(PARENT_OBJECT, parent);
-    return takeOwnership(bean);
+    return bean.as();
   }
 
   /**
@@ -169,9 +219,15 @@
   /**
    * EntityCodex support.
    */
-  public <Q extends EntityProxy> AutoBean<Q> getBeanForPayload(
+  public <Q extends BaseProxy> AutoBean<Q> getBeanForPayload(
       String serializedProxyId) {
-    SimpleEntityProxyId<Q> id = requestFactory.getProxyId(serializedProxyId);
+    SimpleProxyId<Q> id;
+    if (IdUtil.isSynthetic(serializedProxyId)) {
+      id = allocateSyntheticId(IdUtil.getTypeToken(serializedProxyId),
+          IdUtil.getSyntheticId(serializedProxyId));
+    } else {
+      id = requestFactory.getBaseProxyId(serializedProxyId);
+    }
     return getProxyForReturnPayloadGraph(id);
   }
 
@@ -182,7 +238,7 @@
   /**
    * EntityCodex support.
    */
-  public String getSerializedProxyId(EntityProxyId<?> stableId) {
+  public String getSerializedProxyId(SimpleProxyId<?> stableId) {
     return requestFactory.getHistoryToken(stableId);
   }
 
@@ -212,7 +268,7 @@
    * EntityCodex support.
    */
   public boolean isEntityType(Class<?> clazz) {
-    return requestFactory.getTypeToken(clazz) != null;
+    return requestFactory.isEntityType(clazz);
   }
 
   public boolean isLocked() {
@@ -220,6 +276,13 @@
   }
 
   /**
+   * EntityCodex support.
+   */
+  public boolean isValueType(Class<?> clazz) {
+    return requestFactory.isValueType(clazz);
+  }
+
+  /**
    * Called by generated subclasses to enqueue a method invocation.
    */
   protected void addInvocation(AbstractRequest<?> request) {
@@ -229,6 +292,22 @@
     }
   }
 
+  /**
+   * Get-or-create method for synthetic ids.
+   * 
+   * @see #syntheticIds
+   */
+  private <Q extends BaseProxy> SimpleProxyId<Q> allocateSyntheticId(
+      String typeToken, int syntheticId) {
+    @SuppressWarnings("unchecked")
+    SimpleProxyId<Q> toReturn = (SimpleProxyId<Q>) syntheticIds.get(syntheticId);
+    if (toReturn == null) {
+      toReturn = requestFactory.allocateId(requestFactory.<Q> getTypeFromToken(typeToken));
+      syntheticIds.put(syntheticId, toReturn);
+    }
+    return toReturn;
+  }
+
   private void checkLocked() {
     if (locked) {
       throw new IllegalStateException("A request is already in progress");
@@ -246,7 +325,7 @@
       throw new IllegalArgumentException(object.getClass().getName());
     }
 
-    RequestContext context = bean.getTag(EntityProxyCategory.REQUEST_CONTEXT);
+    RequestContext context = bean.getTag(REQUEST_CONTEXT);
     if (!bean.isFrozen() && context != this) {
       /*
        * This means something is way off in the weeds. If a bean is editable,
@@ -268,17 +347,64 @@
    * Shallow-clones an autobean and makes duplicates of the collection types. A
    * regular {@link AutoBean#clone} won't duplicate reference properties.
    */
-  private <T> AutoBean<T> cloneBeanAndCollections(AutoBean<T> toClone) {
+  private <T extends BaseProxy> AutoBean<T> cloneBeanAndCollections(
+      AutoBean<T> toClone) {
     AutoBean<T> clone = toClone.clone(false);
+    /*
+     * Take ownership here to prevent cycles in value objects from overflowing
+     * the stack.
+     */
+    takeOwnership(clone);
     clone.accept(new AutoBeanVisitor() {
+
+      @Override
+      public boolean visitCollectionProperty(String propertyName,
+          AutoBean<Collection<?>> value, CollectionPropertyContext ctx) {
+        if (value != null) {
+          Collection<Object> collection;
+          if (List.class == ctx.getType()) {
+            collection = new ArrayList<Object>();
+          } else if (Set.class == ctx.getType()) {
+            collection = new HashSet<Object>();
+          } else {
+            // Should not get here if the validator works correctly
+            throw new IllegalArgumentException(ctx.getType().getName());
+          }
+
+          if (isValueType(ctx.getElementType())) {
+            /*
+             * Value proxies must be cloned upfront, since the value is replaced
+             * outright.
+             */
+            for (Object o : value.as()) {
+              if (o == null) {
+                collection.add(null);
+              } else {
+                collection.add(editProxy((ValueProxy) o));
+              }
+            }
+          } else {
+            // For entities and simple values, just alias the values
+            collection.addAll(value.as());
+          }
+
+          ctx.set(collection);
+        }
+        return false;
+      }
+
       @Override
       public boolean visitReferenceProperty(String propertyName,
           AutoBean<?> value, PropertyContext ctx) {
         if (value != null) {
-          if (List.class == ctx.getType()) {
-            ctx.set(new ArrayList<Object>((List<?>) value.as()));
-          } else if (Set.class == ctx.getType()) {
-            ctx.set(new HashSet<Object>((Set<?>) value.as()));
+          if (isValueType(ctx.getType())) {
+            /*
+             * Value proxies must be cloned upfront, since the value is replaced
+             * outright.
+             */
+            @SuppressWarnings("unchecked")
+            AutoBean<ValueProxy> valueBean = (AutoBean<ValueProxy>) value;
+            ctx.set(editProxy(valueBean.as()));
           }
         }
         return false;
@@ -382,32 +508,30 @@
   }
 
   /**
-   * Resolves an IdMessage into an EntityProxyId.
+   * Resolves an IdMessage into an SimpleProxyId.
    */
-  private SimpleEntityProxyId<EntityProxy> getId(IdMessage op) {
-    SimpleEntityProxyId<EntityProxy> id;
-    if (op.getClientId() > 0) {
-      id = requestFactory.getId(op.getTypeToken(), op.getServerId(),
-          op.getClientId());
-    } else {
-      id = requestFactory.getId(op.getTypeToken(), op.getServerId());
+  private SimpleProxyId<BaseProxy> getId(IdMessage op) {
+    if (op.getSyntheticId() > 0) {
+      return allocateSyntheticId(op.getTypeToken(), op.getSyntheticId());
     }
-    return id;
+    if (op.getClientId() > 0) {
+      return requestFactory.getId(op.getTypeToken(), op.getServerId(),
+          op.getClientId());
+    }
+    return requestFactory.getId(op.getTypeToken(), op.getServerId());
   }
 
   /**
    * Creates or retrieves a new canonical AutoBean to represent the given id in
    * the returned payload.
    */
-  private <Q extends EntityProxy> AutoBean<Q> getProxyForReturnPayloadGraph(
-      SimpleEntityProxyId<Q> id) {
-    assert !id.isEphemeral();
-
+  private <Q extends BaseProxy> AutoBean<Q> getProxyForReturnPayloadGraph(
+      SimpleProxyId<Q> id) {
     @SuppressWarnings("unchecked")
     AutoBean<Q> bean = (AutoBean<Q>) returnedProxies.get(id);
     if (bean == null) {
       Class<Q> proxyClass = id.getProxyClass();
-      bean = requestFactory.createEntityProxy(proxyClass, id);
+      bean = requestFactory.createProxy(proxyClass, id);
       returnedProxies.put(id, bean);
     }
 
@@ -417,11 +541,11 @@
   /**
    * Make an EntityProxy immutable.
    */
-  private void makeImmutable(final AutoBean<? extends EntityProxy> toMutate) {
+  private void makeImmutable(final AutoBean<? extends BaseProxy> toMutate) {
     // Always diff'ed against itself, producing a no-op
     toMutate.setTag(PARENT_OBJECT, toMutate);
     // Act with entity-identity semantics
-    toMutate.setTag(EntityProxyCategory.REQUEST_CONTEXT, null);
+    toMutate.setTag(REQUEST_CONTEXT, null);
     toMutate.setFrozen(true);
   }
 
@@ -442,37 +566,46 @@
     // Compute deltas for each entity seen by the context
     for (AutoBean<?> currentView : editedProxies.values()) {
       @SuppressWarnings("unchecked")
-      SimpleEntityProxyId<EntityProxy> stableId = EntityProxyCategory.stableId((AutoBean<EntityProxy>) currentView);
+      SimpleProxyId<BaseProxy> stableId = stableId((AutoBean<BaseProxy>) currentView);
+      assert !stableId.isSynthetic() : "Should not send synthetic id to server";
 
       // The OperationMessages describes operations on exactly one entity
       OperationMessage operation = f.operation().as();
       operation.setTypeToken(requestFactory.getTypeToken(stableId.getProxyClass()));
 
       // Find the object to compare against
-      AutoBean<?> parent = currentView.getTag(PARENT_OBJECT);
-      if (parent == null) {
+      AutoBean<?> parent;
+      if (stableId.isEphemeral()) {
         // Newly-created object, use a blank object to compare against
-        parent = requestFactory.createEntityProxy(stableId.getProxyClass(),
-            stableId);
+        parent = requestFactory.createProxy(stableId.getProxyClass(), stableId);
 
         // Newly-created objects go into the persist operation bucket
         operation.setOperation(WriteOperation.PERSIST);
         // The ephemeral id is passed to the server
         operation.setClientId(stableId.getClientId());
       } else {
+        parent = currentView.getTag(PARENT_OBJECT);
         // Requests involving existing objects use the persisted id
         operation.setServerId(stableId.getServerId());
         operation.setOperation(WriteOperation.UPDATE);
       }
+      assert parent != null;
 
-      // Send our version number to the server to cut down on payload size
+      // Send our version number to the server to cut down on future payloads
       Integer version = currentView.getTag(Constants.ENCODED_VERSION_PROPERTY);
       if (version != null) {
         operation.setVersion(version);
       }
 
-      // Compute what's changed on the client
-      Map<String, Object> diff = AutoBeanUtils.diff(parent, currentView);
+      Map<String, Object> diff = Collections.emptyMap();
+      if (isEntityType(stableId.getProxyClass())) {
+        // Compute what's changed on the client
+        diff = AutoBeanUtils.diff(parent, currentView);
+      } else if (isValueType(stableId.getProxyClass())) {
+        // Send everything
+        diff = AutoBeanUtils.getAllProperties(currentView);
+      }
+
       if (!diff.isEmpty()) {
         Map<String, Splittable> propertyMap = new HashMap<String, Splittable>();
         for (Map.Entry<String, Object> entry : diff.entrySet()) {
@@ -530,9 +663,8 @@
    * @param returnRecord the JSON map containing property/value pairs
    * @param operations the WriteOperation eventns to broadcast over the EventBus
    */
-  private <Q extends EntityProxy> Q processReturnOperation(
-      SimpleEntityProxyId<Q> id, OperationMessage op,
-      WriteOperation... operations) {
+  private <Q extends BaseProxy> Q processReturnOperation(SimpleProxyId<Q> id,
+      OperationMessage op, WriteOperation... operations) {
 
     AutoBean<Q> toMutate = getProxyForReturnPayloadGraph(id);
     toMutate.setTag(Constants.ENCODED_VERSION_PROPERTY, op.getVersion());
@@ -564,6 +696,10 @@
             if (properties.containsKey(propertyName)) {
               Splittable raw = properties.get(propertyName);
               Object decoded = ValueCodex.decode(ctx.getType(), raw);
+              // Hack for Date, consider generalizing for "custom serializers"
+              if (Date.class.equals(ctx.getType())) {
+                decoded = new DatePoser((Date) decoded);
+              }
               ctx.set(decoded);
             }
           }
@@ -580,7 +716,7 @@
      * Notify subscribers if the object differs from when it first came into the
      * RequestContext.
      */
-    if (operations != null) {
+    if (operations != null && requestFactory.isEntityType(id.getProxyClass())) {
       for (WriteOperation writeOperation : operations) {
         if (writeOperation.equals(WriteOperation.UPDATE)
             && !requestFactory.hasVersionChanged(id, op.getVersion())) {
@@ -588,8 +724,8 @@
           continue;
         }
         requestFactory.getEventBus().fireEventFromSource(
-            new EntityProxyChange<EntityProxy>(proxy, writeOperation),
-            id.getProxyClass());
+            new EntityProxyChange<EntityProxy>((EntityProxy) proxy,
+                writeOperation), id.getProxyClass());
       }
     }
     return proxy;
@@ -607,8 +743,10 @@
     }
 
     for (OperationMessage op : records) {
-      SimpleEntityProxyId<EntityProxy> id = getId(op);
-      if (id.wasEphemeral()) {
+      SimpleProxyId<?> id = getId(op);
+      if (id.isEphemeral()) {
+        processReturnOperation(id, op);
+      } else if (id.wasEphemeral()) {
         processReturnOperation(id, op, WriteOperation.PERSIST,
             WriteOperation.UPDATE);
       } else {
@@ -643,10 +781,9 @@
   /**
    * Make the EnityProxy bean edited and owned by this RequestContext.
    */
-  private <T extends EntityProxy> T takeOwnership(AutoBean<T> bean) {
-    editedProxies.put(EntityProxyCategory.stableId(bean), bean);
-    bean.setTag(EntityProxyCategory.REQUEST_CONTEXT,
-        AbstractRequestContext.this);
+  private <T extends BaseProxy> T takeOwnership(AutoBean<T> bean) {
+    editedProxies.put(stableId(bean), bean);
+    bean.setTag(REQUEST_CONTEXT, AbstractRequestContext.this);
     return bean.as();
   }
 }
diff --git a/user/src/com/google/gwt/requestfactory/shared/impl/AbstractRequestFactory.java b/user/src/com/google/gwt/requestfactory/shared/impl/AbstractRequestFactory.java
index 3689365..48cfab3 100644
--- a/user/src/com/google/gwt/requestfactory/shared/impl/AbstractRequestFactory.java
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/AbstractRequestFactory.java
@@ -15,9 +15,12 @@
  */
 package com.google.gwt.requestfactory.shared.impl;
 
+import static com.google.gwt.requestfactory.shared.impl.Constants.STABLE_ID;
+
 import com.google.gwt.autobean.shared.AutoBean;
 import com.google.gwt.autobean.shared.AutoBeanFactory;
 import com.google.gwt.event.shared.EventBus;
+import com.google.gwt.requestfactory.shared.BaseProxy;
 import com.google.gwt.requestfactory.shared.EntityProxy;
 import com.google.gwt.requestfactory.shared.EntityProxyId;
 import com.google.gwt.requestfactory.shared.Request;
@@ -30,7 +33,7 @@
 import java.util.Map.Entry;
 
 /**
- * 
+ * Base type for generated RF interfaces.
  */
 public abstract class AbstractRequestFactory extends IdFactory implements
     RequestFactory {
@@ -49,17 +52,16 @@
   private RequestTransport transport;
 
   /**
-   * Creates a new EntityProxy with an assigned ID.
+   * Creates a new proxy with an assigned ID.
    */
-  public <T extends EntityProxy> AutoBean<T> createEntityProxy(Class<T> clazz,
-      SimpleEntityProxyId<T> id) {
+  public <T extends BaseProxy> AutoBean<T> createProxy(Class<T> clazz,
+      SimpleProxyId<T> id) {
     AutoBean<T> created = getAutoBeanFactory().create(clazz);
     if (created == null) {
       throw new IllegalArgumentException("Unknown EntityProxy type "
           + clazz.getName());
     }
-    created.setTag(EntityProxyCategory.REQUEST_FACTORY, this);
-    created.setTag(EntityProxyCategory.STABLE_ID, id);
+    created.setTag(STABLE_ID, id);
     return created;
   }
 
@@ -79,7 +81,7 @@
       protected RequestData makeRequestData() {
         return new RequestData(
             "com.google.gwt.requestfactory.shared.impl.FindRequest::find",
-            new Object[]{proxyId}, propertyRefs, proxyId.getProxyClass(), null);
+            new Object[] {proxyId}, propertyRefs, proxyId.getProxyClass(), null);
       }
     };
   }
@@ -88,6 +90,14 @@
     return eventBus;
   }
 
+  public String getHistoryToken(Class<? extends EntityProxy> clazz) {
+    return getTypeToken(clazz);
+  }
+
+  public String getHistoryToken(EntityProxyId<?> proxy) {
+    return getHistoryToken((SimpleProxyId<?>) proxy);
+  }
+
   public Class<? extends EntityProxy> getProxyClass(String historyToken) {
     String typeToken = IdUtil.getTypeToken(historyToken);
     if (typeToken != null) {
@@ -96,6 +106,11 @@
     return getTypeFromToken(historyToken);
   }
 
+  @SuppressWarnings("unchecked")
+  public <T extends EntityProxy> EntityProxyId<T> getProxyId(String historyToken) {
+    return (EntityProxyId<T>) getBaseProxyId(historyToken);
+  }
+
   public RequestTransport getRequestTransport() {
     return transport;
   }
@@ -120,8 +135,7 @@
    * Used by {@link AbstractRequestContext} to quiesce update events for objects
    * that haven't truly changed.
    */
-  protected boolean hasVersionChanged(SimpleEntityProxyId<?> id,
-      int observedVersion) {
+  protected boolean hasVersionChanged(SimpleProxyId<?> id, int observedVersion) {
     String key = getHistoryToken(id);
     Integer existingVersion = version.get(key);
     // Return true if we haven't seen this before or the versions differ
@@ -132,4 +146,5 @@
     }
     return toReturn;
   }
+
 }
diff --git a/user/src/com/google/gwt/requestfactory/shared/impl/BaseProxyCategory.java b/user/src/com/google/gwt/requestfactory/shared/impl/BaseProxyCategory.java
new file mode 100644
index 0000000..9782088
--- /dev/null
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/BaseProxyCategory.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.requestfactory.shared.impl;
+
+import static com.google.gwt.requestfactory.shared.impl.Constants.REQUEST_CONTEXT;
+import static com.google.gwt.requestfactory.shared.impl.Constants.STABLE_ID;
+
+import com.google.gwt.autobean.shared.AutoBean;
+import com.google.gwt.autobean.shared.AutoBeanUtils;
+import com.google.gwt.requestfactory.shared.BaseProxy;
+
+/**
+ * Contains behaviors common to all proxy instances.
+ */
+public class BaseProxyCategory {
+  /**
+   * Sniff all return values and ensure that if the current bean is a mutable
+   * EntityProxy, that its return values are mutable.
+   */
+  // CHECKSTYLE_OFF
+  public static <T> T __intercept(AutoBean<?> bean, T returnValue) {
+    // CHECKSTYLE_ON
+
+    AbstractRequestContext context = requestContext(bean);
+
+    /*
+     * The context will be null if the bean is immutable. If the context is
+     * locked, don't try to edit.
+     */
+    if (context == null || context.isLocked()) {
+      return returnValue;
+    }
+
+    /*
+     * EntityProxies need to be recorded specially by the RequestContext, so
+     * delegate to the edit() method for wiring up the context.
+     */
+    if (returnValue instanceof BaseProxy) {
+      @SuppressWarnings("unchecked")
+      T toReturn = (T) context.editProxy((BaseProxy) returnValue);
+      return toReturn;
+    }
+
+    if (returnValue instanceof Poser) {
+      ((Poser<?>) returnValue).setFrozen(false);
+    }
+
+    /*
+     * We're returning some object that's not an EntityProxy, most likely a
+     * Collection type. At the very least, propagate the current RequestContext
+     * so that editable chains can be constructed.
+     */
+    AutoBean<T> otherBean = AutoBeanUtils.getAutoBean(returnValue);
+    if (otherBean != null) {
+      otherBean.setTag(REQUEST_CONTEXT, bean.getTag(REQUEST_CONTEXT));
+    }
+    return returnValue;
+  }
+
+  public static AbstractRequestContext requestContext(AutoBean<?> bean) {
+    return bean.getTag(REQUEST_CONTEXT);
+  }
+
+  public static <T extends BaseProxy> SimpleProxyId<T> stableId(
+      AutoBean<? extends T> bean) {
+    return bean.getTag(STABLE_ID);
+  }
+}
diff --git a/user/src/com/google/gwt/requestfactory/shared/impl/CollectionProperty.java b/user/src/com/google/gwt/requestfactory/shared/impl/CollectionProperty.java
deleted file mode 100644
index 7c2ff23..0000000
--- a/user/src/com/google/gwt/requestfactory/shared/impl/CollectionProperty.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright 2010 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.google.gwt.requestfactory.shared.impl;
-
-import java.util.Collection;
-
-/**
- * @param <C> the type of the Container, must be List or Set
- * @param <E> the type of the element the container contains
- */
-public class CollectionProperty<C extends Collection<?>, E> extends Property<C> {
-
-  private Class<E> leafType;
-
-  public CollectionProperty(String name, String displayName, Class<C> colType,
-      Class<E> type) {
-    super(name, displayName, colType);
-    this.leafType = type;
-  }
-
-  public CollectionProperty(String name, Class<C> colType, Class<E> type) {
-    super(name, colType);
-    this.leafType = type;
-  }
-
-  public Class<E> getLeafType() {
-    return leafType;
-  }
-}
diff --git a/user/src/com/google/gwt/requestfactory/shared/impl/Constants.java b/user/src/com/google/gwt/requestfactory/shared/impl/Constants.java
index 9ebc01a..58f3543 100644
--- a/user/src/com/google/gwt/requestfactory/shared/impl/Constants.java
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/Constants.java
@@ -16,25 +16,12 @@
 package com.google.gwt.requestfactory.shared.impl;
 
 /**
- * Contains a variety of contstants shared between client and server code.
+ * Contains a variety of AutoBean tag constants to prevent typos.
  */
 public interface Constants {
-
-  /**
-   * Property on a proxy JSO that holds its encoded server side data store id.
-   */
-  String ENCODED_ID_PROPERTY = "!id";
-  /**
-   * Property on a proxy JSO that holds its server side version data.
-   */
-  String ENCODED_VERSION_PROPERTY = "!version";
-  /**
-   * Id property that server entity objects are required to define.
-   */
-  String ENTITY_ID_PROPERTY = "id";
-  /**
-   * Version property that server entity objects are required to define.
-   */
-  Property<Integer> ENTITY_VERSION_PROPERTY = new Property<Integer>("version",
-      Integer.class);
+  String DOMAIN_OBJECT = "domainObject";
+  String ENCODED_VERSION_PROPERTY = "version";
+  String IN_RESPONSE = "inResponse";
+  String REQUEST_CONTEXT = "requestContext";
+  String STABLE_ID = "stableId";
 }
diff --git a/user/src/com/google/gwt/requestfactory/shared/impl/EntityProxyCategory.java b/user/src/com/google/gwt/requestfactory/shared/impl/EntityProxyCategory.java
index 6fe67c6..26ed7f2 100644
--- a/user/src/com/google/gwt/requestfactory/shared/impl/EntityProxyCategory.java
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/EntityProxyCategory.java
@@ -15,19 +15,22 @@
  */
 package com.google.gwt.requestfactory.shared.impl;
 
+import static com.google.gwt.requestfactory.shared.impl.BaseProxyCategory.requestContext;
+import static com.google.gwt.requestfactory.shared.impl.Constants.STABLE_ID;
+
 import com.google.gwt.autobean.shared.AutoBean;
 import com.google.gwt.autobean.shared.AutoBeanUtils;
 import com.google.gwt.requestfactory.shared.EntityProxy;
-import com.google.gwt.requestfactory.shared.RequestFactory;
 
 /**
- * Contains static implementation methods used by the AutoBean generator.
+ * Contains static implementation of EntityProxy-specific methods.
  */
 public class EntityProxyCategory {
-  static final String REQUEST_CONTEXT = "requestContext";
-  static final String REQUEST_FACTORY = "requestFactory";
-  static final String STABLE_ID = "stableId";
 
+  /**
+   * EntityProxies are equal if they are from the same RequestContext and their
+   * stableIds are equal.
+   */
   public static boolean equals(AutoBean<? extends EntityProxy> bean, Object o) {
     if (!(o instanceof EntityProxy)) {
       return false;
@@ -50,59 +53,12 @@
     return stableId(bean).hashCode();
   }
 
-  public static AbstractRequestContext requestContext(AutoBean<?> bean) {
-    return (AbstractRequestContext) bean.getTag(REQUEST_CONTEXT);
-  }
-
-  public static RequestFactory requestFactory(
-      AutoBean<? extends EntityProxy> bean) {
-    return (RequestFactory) bean.getTag(REQUEST_FACTORY);
-  }
-
-  @SuppressWarnings("unchecked")
+  /**
+   * Effectively overrides {@link BaseProxyCategory#stableId(AutoBean)} to
+   * return a narrower bound.
+   */
   public static <T extends EntityProxy> SimpleEntityProxyId<T> stableId(
       AutoBean<? extends T> bean) {
-    return (SimpleEntityProxyId<T>) bean.getTag(STABLE_ID);
-  }
-
-  /**
-   * Sniff all return values and ensure that if the current bean is a mutable
-   * EntityProxy, that its return values are mutable.
-   */
-  // CHECKSTYLE_OFF
-  public static <T> T __intercept(AutoBean<?> bean, T returnValue) {
-    // CHECKSTYLE_ON
-
-    AbstractRequestContext context = requestContext(bean);
-
-    /*
-     * The context will be null if the bean is immutable. If the context is
-     * locked, don't try to edit.
-     */
-    if (context == null || context.isLocked()) {
-      return returnValue;
-    }
-
-    /*
-     * EntityProxies need to be recorded specially by the RequestContext, so
-     * delegate to the edit() method for wiring up the context.
-     */
-    if (returnValue instanceof EntityProxy) {
-      @SuppressWarnings("unchecked")
-      T toReturn = (T) context.edit((EntityProxy) returnValue);
-      return toReturn;
-    }
-
-    /*
-     * We're returning some object that's not an EntityProxy, most likely a
-     * Collection type. At the very least, propagate the current RequestContext
-     * so that editable chains can be constructed.
-     */
-    AutoBean<T> otherBean = AutoBeanUtils.getAutoBean(returnValue);
-    if (otherBean != null) {
-      otherBean.setTag(EntityProxyCategory.REQUEST_CONTEXT,
-          bean.getTag(EntityProxyCategory.REQUEST_CONTEXT));
-    }
-    return returnValue;
+    return bean.getTag(STABLE_ID);
   }
 }
diff --git a/user/src/com/google/gwt/requestfactory/shared/impl/EnumProperty.java b/user/src/com/google/gwt/requestfactory/shared/impl/EnumProperty.java
deleted file mode 100644
index 842d745..0000000
--- a/user/src/com/google/gwt/requestfactory/shared/impl/EnumProperty.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright 2010 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.google.gwt.requestfactory.shared.impl;
-
-/**
- * Defines a property of a {@link com.google.gwt.requestfactory.shared.EntityProxy}.
- *
- * @param <V> the type of the property's value
- */
-public class EnumProperty<V> extends Property<V> {
-
-  private V[] values;
-
-  /**
-   * @param name the property's name and displayName
-   * @param type the class of the property's value
-   * @param values the result of Enum.values() method
-   */
-  public EnumProperty(String name, Class<V> type, V[] values) {
-    super(name, type);
-    this.values = values;
-  }
-
-  /**
-   * Returns the values that the enum may take on.
-   */
-  public V[] getValues() {
-    return values;
-  }
-}
diff --git a/user/src/com/google/gwt/requestfactory/shared/impl/IdFactory.java b/user/src/com/google/gwt/requestfactory/shared/impl/IdFactory.java
index cc106e4..2d9ac74 100644
--- a/user/src/com/google/gwt/requestfactory/shared/impl/IdFactory.java
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/IdFactory.java
@@ -15,15 +15,16 @@
  */
 package com.google.gwt.requestfactory.shared.impl;
 
+import com.google.gwt.requestfactory.shared.BaseProxy;
 import com.google.gwt.requestfactory.shared.EntityProxy;
-import com.google.gwt.requestfactory.shared.EntityProxyId;
+import com.google.gwt.requestfactory.shared.ValueProxy;
 import com.google.gwt.requestfactory.shared.messages.IdUtil;
 
 import java.util.HashMap;
 import java.util.Map;
 
 /**
- * Handles common code for creating SimpleEntityProxyIds.
+ * Handles common code for creating SimpleProxyIds.
  */
 public abstract class IdFactory {
   /**
@@ -31,57 +32,114 @@
    * resolves the problem of EntityProxyIds hashcodes changing after persist.
    * Only ids that are created in the RequestFactory are stored here.
    */
-  private final Map<String, SimpleEntityProxyId<?>> ephemeralIds = new HashMap<String, SimpleEntityProxyId<?>>();
+  private final Map<String, SimpleProxyId<?>> ephemeralIds = new HashMap<String, SimpleProxyId<?>>();
 
   /**
    * Allocates an ephemeral proxy id. This object is only valid for the lifetime
    * of the RequestFactory.
    */
-  public <P extends EntityProxy> SimpleEntityProxyId<P> allocateId(
-      Class<P> clazz) {
-    SimpleEntityProxyId<P> toReturn = new SimpleEntityProxyId<P>(clazz,
-        ephemeralIds.size() + 1);
+  public <P extends BaseProxy> SimpleProxyId<P> allocateId(Class<P> clazz) {
+    SimpleProxyId<P> toReturn = createId(clazz, ephemeralIds.size() + 1);
     ephemeralIds.put(getHistoryToken(toReturn), toReturn);
     return toReturn;
   }
 
-  public String getHistoryToken(Class<? extends EntityProxy> clazz) {
-    return getTypeToken(clazz);
+  /**
+   * Allocates a synthetic proxy id. This object is only valid for the lifetime
+   * of a request.
+   */
+  public <P extends BaseProxy> SimpleProxyId<P> allocateSyntheticId(
+      Class<P> clazz, int syntheticId) {
+    SimpleProxyId<P> toReturn = createId(clazz, "%" + syntheticId);
+    toReturn.setSyntheticId(syntheticId);
+    return toReturn;
   }
 
-  public String getHistoryToken(EntityProxyId<?> proxy) {
-    SimpleEntityProxyId<?> id = (SimpleEntityProxyId<?>) proxy;
+  /**
+   * A utility function to handle generic type conversion. This method will also
+   * assert that {@code clazz} is actually an EntityProxy type.
+   */
+  @SuppressWarnings("unchecked")
+  public <P extends EntityProxy> Class<P> asEntityProxy(
+      Class<? extends BaseProxy> clazz) {
+    assert isEntityType(clazz);
+    return (Class<P>) clazz;
+  }
+
+  /**
+   * A utility function to handle generic type conversion. This method will also
+   * assert that {@code clazz} is actually a ValueProxy type.
+   */
+  @SuppressWarnings("unchecked")
+  public <P extends ValueProxy> Class<P> asValueProxy(
+      Class<? extends BaseProxy> clazz) {
+    assert isEntityType(clazz);
+    return (Class<P>) clazz;
+  }
+
+  public <P extends BaseProxy> SimpleProxyId<P> getBaseProxyId(
+      String historyToken) {
+    assert !IdUtil.isSynthetic(historyToken) : "Synthetic id resolution"
+        + " should be handled by AbstractRequestContext";
+    if (IdUtil.isPersisted(historyToken)) {
+      return getId(IdUtil.getTypeToken(historyToken),
+          IdUtil.getServerId(historyToken));
+    }
+    if (IdUtil.isEphemeral(historyToken)) {
+      @SuppressWarnings("unchecked")
+      SimpleProxyId<P> toReturn = (SimpleProxyId<P>) ephemeralIds.get(historyToken);
+
+      /*
+       * This is tested in FindServiceTest.testFetchUnpersistedFutureId. In
+       * order to get here, the user would have to get an unpersisted history
+       * token and attempt to use it with a different RequestFactory instance.
+       * This could occur if an ephemeral token were bookmarked. In this case,
+       * we'll create a token, however it will never match anything.
+       */
+      if (toReturn == null) {
+        Class<P> clazz = checkTypeToken(IdUtil.getTypeToken(historyToken));
+        toReturn = createId(clazz, -1 * ephemeralIds.size());
+        ephemeralIds.put(historyToken, toReturn);
+      }
+
+      return toReturn;
+    }
+    throw new IllegalArgumentException(historyToken);
+  }
+
+  public String getHistoryToken(SimpleProxyId<?> proxy) {
+    SimpleProxyId<?> id = (SimpleProxyId<?>) proxy;
+    String token = getTypeToken(proxy.getProxyClass());
     if (id.isEphemeral()) {
-      return IdUtil.ephemeralId(id.getClientId(),
-          getHistoryToken(proxy.getProxyClass()));
+      return IdUtil.ephemeralId(id.getClientId(), token);
+    } else if (id.isSynthetic()) {
+      return IdUtil.syntheticId(id.getSyntheticId(), token);
     } else {
-      return IdUtil.persistedId(id.getServerId(),
-          getHistoryToken(proxy.getProxyClass()));
+      return IdUtil.persistedId(id.getServerId(), token);
     }
   }
 
   /**
-   * Create or retrieve a SimpleEntityProxyId.
+   * Create or retrieve a SimpleProxyId.
    */
-  public <P extends EntityProxy> SimpleEntityProxyId<P> getId(Class<P> clazz,
+  public <P extends BaseProxy> SimpleProxyId<P> getId(Class<P> clazz,
       String serverId) {
     return getId(getTypeToken(clazz), serverId);
   }
 
   /**
-   * Create or retrieve a SimpleEntityProxyId. If both the serverId and clientId
-   * are specified and the id is ephemeral, it will be updated with the server
-   * id.
+   * Create or retrieve a SimpleProxyId. If both the serverId and clientId are
+   * specified and the id is ephemeral, it will be updated with the server id.
    */
-  public <P extends EntityProxy> SimpleEntityProxyId<P> getId(Class<P> clazz,
+  public <P extends BaseProxy> SimpleProxyId<P> getId(Class<P> clazz,
       String serverId, Integer clientId) {
     return getId(getTypeToken(clazz), serverId, clientId);
   }
 
   /**
-   * Create or retrieve a SimpleEntityProxyId.
+   * Create or retrieve a SimpleProxyId.
    */
-  public <P extends EntityProxy> SimpleEntityProxyId<P> getId(String typeToken,
+  public <P extends BaseProxy> SimpleProxyId<P> getId(String typeToken,
       String serverId) {
     return getId(typeToken, serverId, null);
   }
@@ -91,7 +149,7 @@
    * are specified and the id is ephemeral, it will be updated with the server
    * id.
    */
-  public <P extends EntityProxy> SimpleEntityProxyId<P> getId(String typeToken,
+  public <P extends BaseProxy> SimpleProxyId<P> getId(String typeToken,
       String serverId, Integer clientId) {
     /*
      * If there's a clientId, that probably means we've just created a brand-new
@@ -101,12 +159,12 @@
       // Try a cache lookup for the ephemeral key
       String ephemeralKey = IdUtil.ephemeralId(clientId, typeToken);
       @SuppressWarnings("unchecked")
-      SimpleEntityProxyId<P> toReturn = (SimpleEntityProxyId<P>) ephemeralIds.get(ephemeralKey);
+      SimpleProxyId<P> toReturn = (SimpleProxyId<P>) ephemeralIds.get(ephemeralKey);
 
       // Do we need to allocate an ephemeral id?
       if (toReturn == null) {
         Class<P> clazz = getTypeFromToken(typeToken);
-        toReturn = new SimpleEntityProxyId<P>(clazz, clientId);
+        toReturn = createId(clazz, clientId);
         ephemeralIds.put(ephemeralKey, toReturn);
       }
 
@@ -133,7 +191,7 @@
 
     String serverKey = IdUtil.persistedId(serverId, typeToken);
     @SuppressWarnings("unchecked")
-    SimpleEntityProxyId<P> toReturn = (SimpleEntityProxyId<P>) ephemeralIds.get(serverKey);
+    SimpleProxyId<P> toReturn = (SimpleProxyId<P>) ephemeralIds.get(serverKey);
     if (toReturn != null) {
       // A cache hit for a locally-created object that has been persisted
       return toReturn;
@@ -145,38 +203,14 @@
      * case for read-dominated applications.
      */
     Class<P> clazz = getTypeFromToken(typeToken);
-    return new SimpleEntityProxyId<P>(clazz, serverId);
+    return createId(clazz, serverId);
   }
 
-  public <P extends EntityProxy> SimpleEntityProxyId<P> getProxyId(
-      String historyToken) {
-    if (IdUtil.isPersisted(historyToken)) {
-      return getId(IdUtil.getTypeToken(historyToken),
-          IdUtil.getServerId(historyToken));
-    }
-    if (IdUtil.isEphemeral(historyToken)) {
-      @SuppressWarnings("unchecked")
-      SimpleEntityProxyId<P> toReturn = (SimpleEntityProxyId<P>) ephemeralIds.get(historyToken);
+  public abstract boolean isEntityType(Class<?> clazz);
 
-      /*
-       * This is tested in FindServiceTest.testFetchUnpersistedFutureId. In
-       * order to get here, the user would have to get an unpersisted history
-       * token and attempt to use it with a different RequestFactory instance.
-       * This could occur if an ephemeral token was bookmarked. In this case,
-       * we'll create a token, however it will never match anything.
-       */
-      if (toReturn == null) {
-        Class<P> clazz = checkTypeToken(IdUtil.getTypeToken(historyToken));
-        toReturn = new SimpleEntityProxyId<P>(clazz, -1 * ephemeralIds.size());
-        ephemeralIds.put(historyToken, toReturn);
-      }
+  public abstract boolean isValueType(Class<?> clazz);
 
-      return toReturn;
-    }
-    throw new IllegalArgumentException(historyToken);
-  }
-
-  protected abstract <P extends EntityProxy> Class<P> getTypeFromToken(
+  protected abstract <P extends BaseProxy> Class<P> getTypeFromToken(
       String typeToken);
 
   protected abstract String getTypeToken(Class<?> clazz);
@@ -190,4 +224,32 @@
     return clazz;
   }
 
+  private <P extends BaseProxy> SimpleProxyId<P> createId(Class<P> clazz,
+      int clientId) {
+    SimpleProxyId<P> toReturn;
+    if (isValueType(clazz)) {
+      toReturn = new SimpleProxyId<P>(clazz, clientId);
+    } else {
+      @SuppressWarnings("unchecked")
+      SimpleProxyId<P> temp = (SimpleProxyId<P>) new SimpleEntityProxyId<EntityProxy>(
+          asEntityProxy(clazz), clientId);
+      toReturn = (SimpleProxyId<P>) temp;
+    }
+    return toReturn;
+  }
+
+  private <P extends BaseProxy> SimpleProxyId<P> createId(Class<P> clazz,
+      String serverId) {
+    SimpleProxyId<P> toReturn;
+    if (isValueType(clazz)) {
+      toReturn = new SimpleProxyId<P>(clazz, serverId);
+    } else {
+      @SuppressWarnings("unchecked")
+      SimpleProxyId<P> temp = (SimpleProxyId<P>) new SimpleEntityProxyId<EntityProxy>(
+          asEntityProxy(clazz), serverId);
+      toReturn = (SimpleProxyId<P>) temp;
+    }
+    return toReturn;
+  }
+
 }
\ No newline at end of file
diff --git a/user/src/com/google/gwt/requestfactory/shared/impl/Poser.java b/user/src/com/google/gwt/requestfactory/shared/impl/Poser.java
new file mode 100644
index 0000000..0c16b50
--- /dev/null
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/Poser.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.requestfactory.shared.impl;
+
+/**
+ * Used to lock down mutable, non-proxy, value objects when their owning proxy
+ * is frozen.
+ * 
+ * @param <T> the type of simple value the Poser is standing in for
+ */
+public interface Poser<T> {
+  T getPosedValue();
+
+  boolean isFrozen();
+
+  void setFrozen(boolean frozen);
+}
diff --git a/user/src/com/google/gwt/requestfactory/shared/impl/Property.java b/user/src/com/google/gwt/requestfactory/shared/impl/Property.java
deleted file mode 100644
index 15e8b84..0000000
--- a/user/src/com/google/gwt/requestfactory/shared/impl/Property.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright 2010 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.google.gwt.requestfactory.shared.impl;
-
-/**
- * Defines a property of an {@link EntityProxy}.
- *
- * @param <V> the type of the property's value
- */
-public class Property<V> {
-  private final String name;
-  private final Class<V> type;
-  private final String displayName;
-
-  /**
-   * @param name the property's name and displayName
-   * @param type the class of the property's value
-   */
-  public Property(String name, Class<V> type) {
-    this(name, name, type);
-  }
-
-  /**
-   * @param name the property's name
-   * @param displayName the property's user visible name
-   * @param type the class of the property's value
-   */
-  public Property(String name, String displayName, Class<V> type) {
-    this.name = name;
-    this.displayName = displayName;
-    this.type = type;
-  }
-
-  @Override
-  public boolean equals(Object o) {
-    if (this == o) {
-      return true;
-    }
-    if (!(o instanceof Property<?>)) {
-      return false;
-    }
-    Property<?> property = (Property<?>) o;
-    if (name != null ? !name.equals(property.name) : property.name != null) {
-      return false;
-    }
-    if (type != null ? !type.equals(property.type) : property.type != null) {
-      return false;
-    }
-    return true;
-  }
-
-  public String getDisplayName() {
-    return displayName;
-  }
-
-  public String getName() {
-    return name;
-  }
-
-  public Class<V> getType() {
-    return type;
-  }
-
-  @Override
-  public int hashCode() {
-    int nameHash = name == null ? 0 : name.hashCode();
-    int typeHash = type == null ? 0 : type.hashCode();
-    return (nameHash * 31) ^ typeHash;
-  }
-}
diff --git a/user/src/com/google/gwt/requestfactory/shared/impl/SimpleEntityProxyId.java b/user/src/com/google/gwt/requestfactory/shared/impl/SimpleEntityProxyId.java
index 5a98ae6..027a1d6 100644
--- a/user/src/com/google/gwt/requestfactory/shared/impl/SimpleEntityProxyId.java
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/SimpleEntityProxyId.java
@@ -17,144 +17,28 @@
 
 import com.google.gwt.requestfactory.shared.EntityProxy;
 import com.google.gwt.requestfactory.shared.EntityProxyId;
-import com.google.gwt.requestfactory.shared.messages.IdUtil;
 
 /**
- * Nothing fancy.
+ * Extends SimpleProxyId with the correct parameterization to implement
+ * EntityProxyId.
  * 
  * @param <P> the type of EntityProxy object the id describes
  */
-public class SimpleEntityProxyId<P extends EntityProxy> implements
-    EntityProxyId<P> {
-  /**
-   * A placeholder value for {@link #clientId} to indicate the id was not
-   * created locally.
-   */
-  public static final int NEVER_EPHEMERAL = -1;
-
-  /**
-   * The client-side id is ephemeral, and is valid only during the lifetime of a
-   * module. Any use of the client-side id except to send to the server as a
-   * bookkeeping exercise is wrong.
-   */
-  private final int clientId;
-
-  /**
-   * The hashcode of the id must remain stable, even if the server id is later
-   * assigned.
-   */
-  private final int hashCode;
-
-  private final Class<P> proxyClass;
-
-  /**
-   * The serverId is totally opaque to the client. It's probably a
-   * base64-encoded string, but it could be digits of pi. Any code that does
-   * anything other than send the contents of this field back to the server is
-   * wrong.
-   */
-  private String serverId;
+public class SimpleEntityProxyId<P extends EntityProxy> extends
+    SimpleProxyId<P> implements EntityProxyId<P> {
 
   /**
    * Construct an ephemeral id. May be called only from
    * {@link IdFactory#getId()}.
    */
   SimpleEntityProxyId(Class<P> proxyClass, int clientId) {
-    assert proxyClass != null;
-    this.clientId = clientId;
-    this.proxyClass = proxyClass;
-    hashCode = clientId;
+    super(proxyClass, clientId);
   }
 
   /**
    * Construct a stable id. May only be called from {@link IdFactory#getId()}
    */
-  SimpleEntityProxyId(Class<P> proxyClass, String serverId) {
-    assert proxyClass != null;
-    assert serverId != null && !serverId.contains("@")
-        && !"null".equals(serverId);
-    setServerId(serverId);
-    clientId = NEVER_EPHEMERAL;
-    hashCode = serverId.hashCode();
-    this.proxyClass = proxyClass;
-  }
-
-  @Override
-  public boolean equals(Object o) {
-    if (this == o) {
-      return true;
-    }
-    if (!(o instanceof SimpleEntityProxyId<?>)) {
-      return false;
-    }
-    SimpleEntityProxyId<?> other = (SimpleEntityProxyId<?>) o;
-    if (!proxyClass.equals(other.proxyClass)) {
-      return false;
-    }
-
-    if (clientId != NEVER_EPHEMERAL && clientId == other.clientId) {
-      /*
-       * Unexpected: It should be the case that locally-created ids are never
-       * aliased and will be caught by the first if statement.
-       */
-      return true;
-    }
-
-    if (serverId != null && serverId.equals(other.serverId)) {
-      return true;
-    }
-    return false;
-  }
-
-  public int getClientId() {
-    return clientId;
-  }
-
-  public Class<P> getProxyClass() {
-    return proxyClass;
-  }
-
-  public String getServerId() {
-    return serverId;
-  }
-
-  @Override
-  public int hashCode() {
-    return hashCode;
-  }
-
-  public boolean isEphemeral() {
-    return serverId == null;
-  }
-
-  /**
-   * Allows the server id token to be set. This method may be called exactly
-   * once over the lifetime of an id.
-   */
-  public void setServerId(String serverId) {
-    if (this.serverId != null) {
-      throw new IllegalStateException();
-    }
-    assert !"null".equals(serverId);
-    this.serverId = serverId;
-  }
-
-  /**
-   * For debugging use only.
-   */
-  @Override
-  public String toString() {
-    if (isEphemeral()) {
-      return IdUtil.ephemeralId(clientId, proxyClass.getName());
-    } else {
-      return IdUtil.persistedId(serverId, proxyClass.getName());
-    }
-  }
-
-  /**
-   * Returns <code>true</code> if the id was created as an ephemeral id.
-   */
-  public boolean wasEphemeral() {
-    return clientId != NEVER_EPHEMERAL;
+  SimpleEntityProxyId(Class<P> proxyClass, String encodedAddress) {
+    super(proxyClass, encodedAddress);
   }
 }
diff --git a/user/src/com/google/gwt/requestfactory/shared/impl/SimpleProxyId.java b/user/src/com/google/gwt/requestfactory/shared/impl/SimpleProxyId.java
new file mode 100644
index 0000000..943c970
--- /dev/null
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/SimpleProxyId.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.requestfactory.shared.impl;
+
+import com.google.gwt.requestfactory.shared.BaseProxy;
+import com.google.gwt.requestfactory.shared.messages.IdUtil;
+
+/**
+ * The base implementation of id objects in the RequestFactory system. This type
+ * exists to allow ValueProxies to be implemented in the same manner as an
+ * EntityProxy as far as metadata maintenance is concerned. There is a specific
+ * subtype {@link SimpleEntityProxyId} which implements the requisite public
+ * interface for EntityProxy types.
+ * 
+ * @param <P> the type of BaseProxy object the id describes
+ */
+public class SimpleProxyId<P extends BaseProxy> {
+  /**
+   * A placeholder value for {@link #clientId} to indicate the id was not
+   * created locally.
+   */
+  public static final int NEVER_EPHEMERAL = -1;
+
+  /**
+   * The client-side id is ephemeral, and is valid only during the lifetime of a
+   * module. Any use of the client-side id except to send to the server as a
+   * bookkeeping exercise is wrong.
+   */
+  private final int clientId;
+
+  /**
+   * The encodedAddress is totally opaque to the client. It's probably a
+   * base64-encoded string, but it could be digits of pi. Any code that does
+   * anything other than send the contents of this field back to the server is
+   * wrong.
+   */
+  private String encodedAddress;
+
+  /**
+   * The hashcode of the id must remain stable, even if the server id is later
+   * assigned.
+   */
+  private final int hashCode;
+
+  /**
+   * The EntityProxy type.
+   */
+  private final Class<P> proxyClass;
+
+  /**
+   * A flag to indicate that the id is synthetic, that it is not valid beyond
+   * the duration of the request.
+   */
+  private int syntheticId;
+
+  /**
+   * Construct an ephemeral id. May be called only from
+   * {@link IdFactory#createId()}.
+   */
+  SimpleProxyId(Class<P> proxyClass, int clientId) {
+    assert proxyClass != null;
+    this.clientId = clientId;
+    this.proxyClass = proxyClass;
+    hashCode = clientId;
+  }
+
+  /**
+   * Construct a stable id. May only be called from {@link IdFactory#createId()}
+   */
+  SimpleProxyId(Class<P> proxyClass, String encodedAddress) {
+    assert proxyClass != null;
+    assert encodedAddress != null && !encodedAddress.contains("@")
+        && !"null".equals(encodedAddress);
+    setServerId(encodedAddress);
+    clientId = NEVER_EPHEMERAL;
+    hashCode = encodedAddress.hashCode();
+    this.proxyClass = proxyClass;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (!(o instanceof SimpleProxyId<?>)) {
+      return false;
+    }
+    SimpleProxyId<?> other = (SimpleProxyId<?>) o;
+    if (!proxyClass.equals(other.proxyClass)) {
+      return false;
+    }
+
+    if (clientId != NEVER_EPHEMERAL && clientId == other.clientId) {
+      /*
+       * Unexpected: It should be the case that locally-created ids are never
+       * aliased and will be caught by the first if statement.
+       */
+      return true;
+    }
+
+    if (encodedAddress != null && encodedAddress.equals(other.encodedAddress)) {
+      return true;
+    }
+    return false;
+  }
+
+  public int getClientId() {
+    return clientId;
+  }
+
+  public Class<P> getProxyClass() {
+    return proxyClass;
+  }
+
+  /**
+   * TODO: Rename to getAddress().
+   */
+  public String getServerId() {
+    return encodedAddress;
+  }
+
+  /**
+   * A flag to indicate that the id is synthetic, that it is not valid beyond
+   * the duration of the request.
+   */
+  public int getSyntheticId() {
+    return syntheticId;
+  }
+
+  @Override
+  public int hashCode() {
+    return hashCode;
+  }
+
+  public boolean isEphemeral() {
+    return encodedAddress == null;
+  }
+
+  public boolean isSynthetic() {
+    return syntheticId > 0;
+  }
+
+  /**
+   * Allows the server address token to be set. This method may be called
+   * exactly once over the lifetime of an id.
+   */
+  public void setServerId(String encodedAddress) {
+    if (this.encodedAddress != null) {
+      throw new IllegalStateException();
+    }
+    assert !"null".equals(encodedAddress);
+    this.encodedAddress = encodedAddress;
+  }
+
+  public void setSyntheticId(int syntheticId) {
+    this.syntheticId = syntheticId;
+  }
+
+  /**
+   * For debugging use only.
+   */
+  @Override
+  public String toString() {
+    if (isEphemeral()) {
+      return IdUtil.ephemeralId(clientId, proxyClass.getName());
+    } else if (isSynthetic()) {
+      return IdUtil.syntheticId(syntheticId, proxyClass.getName());
+    } else {
+      return IdUtil.persistedId(encodedAddress, proxyClass.getName());
+    }
+  }
+
+  /**
+   * Returns <code>true</code> if the id was created as an ephemeral id.
+   */
+  public boolean wasEphemeral() {
+    return clientId != NEVER_EPHEMERAL;
+  }
+}
diff --git a/user/src/com/google/gwt/requestfactory/shared/impl/ValueProxyCategory.java b/user/src/com/google/gwt/requestfactory/shared/impl/ValueProxyCategory.java
new file mode 100644
index 0000000..b2b3422
--- /dev/null
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/ValueProxyCategory.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.requestfactory.shared.impl;
+
+import static com.google.gwt.requestfactory.shared.impl.BaseProxyCategory.stableId;
+
+import com.google.gwt.autobean.shared.AutoBean;
+import com.google.gwt.autobean.shared.AutoBeanUtils;
+import com.google.gwt.requestfactory.shared.ValueProxy;
+
+/**
+ * Contains static implementation of EntityProxy-specific methods.
+ */
+public class ValueProxyCategory {
+
+  /**
+   * ValueProxies are equal if they are from the same RequestContext and all of
+   * their properties are equal.
+   */
+  public static boolean equals(AutoBean<? extends ValueProxy> bean, Object o) {
+    if (!(o instanceof ValueProxy)) {
+      return false;
+    }
+    AutoBean<ValueProxy> other = AutoBeanUtils.getAutoBean((ValueProxy) o);
+    if (other == null) {
+      // Unexpected, could be an user-provided implementation?
+      return false;
+    }
+    if (!stableId(bean).getProxyClass().equals(stableId(other).getProxyClass())) {
+      // Compare AppleProxies to AppleProxies
+      return false;
+    }
+
+    /*
+     * Comparison of ValueProxies is based solely on property values. Unlike an
+     * EntityProxy, neither the id nor the RequestContext is used
+     */
+    return AutoBeanUtils.getAllProperties(bean).equals(
+        AutoBeanUtils.getAllProperties(other));
+  }
+
+  /**
+   * Hashcode depends on property values.
+   */
+  public static int hashCode(AutoBean<? extends ValueProxy> bean) {
+    return AutoBeanUtils.getAllProperties(bean).hashCode();
+  }
+}
diff --git a/user/src/com/google/gwt/requestfactory/shared/impl/posers/DatePoser.java b/user/src/com/google/gwt/requestfactory/shared/impl/posers/DatePoser.java
new file mode 100644
index 0000000..403ebc3
--- /dev/null
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/posers/DatePoser.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.requestfactory.shared.impl.posers;
+
+import com.google.gwt.requestfactory.shared.impl.Poser;
+
+import java.util.Date;
+
+/**
+ * A sometimes-mutable implementation of {@link Date}.
+ */
+@SuppressWarnings("deprecation")
+public class DatePoser extends Date implements Poser<Date> {
+  private boolean frozen;
+
+  public DatePoser(Date copy) {
+    super(copy.getTime());
+    setFrozen(true);
+  }
+
+  public Date getPosedValue() {
+    return new Date(getTime());
+  }
+
+  public boolean isFrozen() {
+    return frozen;
+  }
+
+  @Override
+  public void setDate(int date) {
+    checkFrozen();
+    super.setDate(date);
+  }
+
+  public void setFrozen(boolean frozen) {
+    this.frozen = frozen;
+  }
+
+  @Override
+  public void setHours(int hours) {
+    checkFrozen();
+    super.setHours(hours);
+  }
+
+  @Override
+  public void setMinutes(int minutes) {
+    checkFrozen();
+    super.setMinutes(minutes);
+  }
+
+  @Override
+  public void setMonth(int month) {
+    checkFrozen();
+    super.setMonth(month);
+  }
+
+  @Override
+  public void setSeconds(int seconds) {
+    checkFrozen();
+    super.setSeconds(seconds);
+  }
+
+  @Override
+  public void setTime(long time) {
+    checkFrozen();
+    super.setTime(time);
+  }
+
+  @Override
+  public void setYear(int year) {
+    checkFrozen();
+    super.setYear(year);
+  }
+
+  private void checkFrozen() {
+    if (frozen) {
+      throw new IllegalStateException("The Date has been frozen");
+    }
+  }
+}
diff --git a/user/src/com/google/gwt/requestfactory/shared/messages/EntityCodex.java b/user/src/com/google/gwt/requestfactory/shared/messages/EntityCodex.java
index 09b8aac..bcf5245 100644
--- a/user/src/com/google/gwt/requestfactory/shared/messages/EntityCodex.java
+++ b/user/src/com/google/gwt/requestfactory/shared/messages/EntityCodex.java
@@ -16,12 +16,16 @@
 package com.google.gwt.requestfactory.shared.messages;
 
 import com.google.gwt.autobean.shared.AutoBean;
+import com.google.gwt.autobean.shared.AutoBeanUtils;
 import com.google.gwt.autobean.shared.Splittable;
 import com.google.gwt.autobean.shared.ValueCodex;
 import com.google.gwt.autobean.shared.impl.LazySplittable;
 import com.google.gwt.autobean.shared.impl.StringQuoter;
-import com.google.gwt.requestfactory.shared.EntityProxy;
+import com.google.gwt.requestfactory.shared.BaseProxy;
 import com.google.gwt.requestfactory.shared.EntityProxyId;
+import com.google.gwt.requestfactory.shared.impl.BaseProxyCategory;
+import com.google.gwt.requestfactory.shared.impl.Poser;
+import com.google.gwt.requestfactory.shared.impl.SimpleProxyId;
 
 import java.util.ArrayList;
 import java.util.Collection;
@@ -37,12 +41,13 @@
    * Abstracts the process by which EntityProxies are created.
    */
   public interface EntitySource {
-    <Q extends EntityProxy> AutoBean<Q> getBeanForPayload(
-        String serializedProxyId);
+    <Q extends BaseProxy> AutoBean<Q> getBeanForPayload(String serializedProxyId);
 
-    String getSerializedProxyId(EntityProxyId<?> stableId);
+    String getSerializedProxyId(SimpleProxyId<?> stableId);
 
     boolean isEntityType(Class<?> clazz);
+
+    boolean isValueType(Class<?> clazz);
   }
 
   /**
@@ -88,7 +93,8 @@
       return collection;
     }
 
-    if (source.isEntityType(type) || EntityProxyId.class.equals(type)) {
+    if (source.isEntityType(type) || source.isValueType(type)
+        || EntityProxyId.class.equals(type)) {
       return source.getBeanForPayload(split.asString()).as();
     }
 
@@ -133,12 +139,17 @@
       return new LazySplittable(toReturn.toString());
     }
 
-    if (value instanceof EntityProxy) {
-      value = source.getSerializedProxyId(((EntityProxy) value).stableId());
+    if (value instanceof BaseProxy) {
+      AutoBean<BaseProxy> autoBean = AutoBeanUtils.getAutoBean((BaseProxy) value);
+      value = BaseProxyCategory.stableId(autoBean);
     }
 
-    if (value instanceof EntityProxyId<?>) {
-      value = source.getSerializedProxyId((EntityProxyId<?>) value);
+    if (value instanceof SimpleProxyId<?>) {
+      value = source.getSerializedProxyId((SimpleProxyId<?>) value);
+    }
+
+    if (value instanceof Poser<?>) {
+      value = ((Poser<?>) value).getPosedValue();
     }
 
     return ValueCodex.encode(value);
diff --git a/user/src/com/google/gwt/requestfactory/shared/messages/IdMessage.java b/user/src/com/google/gwt/requestfactory/shared/messages/IdMessage.java
index 11c758f..7be22c8 100644
--- a/user/src/com/google/gwt/requestfactory/shared/messages/IdMessage.java
+++ b/user/src/com/google/gwt/requestfactory/shared/messages/IdMessage.java
@@ -24,6 +24,7 @@
   String CLIENT_ID = "C";
   String SERVER_ID = "S";
   String TYPE_TOKEN = "T";
+  String SYNTHETIC_ID = "Y";
 
   @PropertyName(CLIENT_ID)
   int getClientId();
@@ -31,6 +32,9 @@
   @PropertyName(SERVER_ID)
   String getServerId();
 
+  @PropertyName(SYNTHETIC_ID)
+  int getSyntheticId();
+
   @PropertyName(TYPE_TOKEN)
   String getTypeToken();
 
@@ -40,6 +44,9 @@
   @PropertyName(SERVER_ID)
   void setServerId(String value);
 
+  @PropertyName(SYNTHETIC_ID)
+  void setSyntheticId(int value);
+
   @PropertyName(TYPE_TOKEN)
   void setTypeToken(String value);
 }
\ No newline at end of file
diff --git a/user/src/com/google/gwt/requestfactory/shared/messages/IdUtil.java b/user/src/com/google/gwt/requestfactory/shared/messages/IdUtil.java
index de8c906..5efd614 100644
--- a/user/src/com/google/gwt/requestfactory/shared/messages/IdUtil.java
+++ b/user/src/com/google/gwt/requestfactory/shared/messages/IdUtil.java
@@ -19,9 +19,11 @@
  * Common functions for slicing and dicing EntityProxy ids.
  */
 public class IdUtil {
-  private static final String ANY_SEPARATOR_PATTERN = "@[01]@";
+  private static final String ANY_SEPARATOR_PATTERN = "@[012]@";
   private static final String EPHEMERAL_SEPARATOR = "@1@";
   private static final String TOKEN_SEPARATOR = "@0@";
+  private static final String SYNTHETIC_SEPARATOR = "@2@";
+
   private static final int ID_TOKEN_INDEX = 0;
   private static final int TYPE_TOKEN_INDEX = 1;
 
@@ -37,6 +39,10 @@
     return asPersisted(encodedId)[ID_TOKEN_INDEX];
   }
 
+  public static int getSyntheticId(String encodedId) {
+    return Integer.valueOf(asSynthetic(encodedId)[ID_TOKEN_INDEX]);
+  }
+
   public static String getTypeToken(String encodedId) {
     String[] split = asAny(encodedId);
     if (split.length == 2) {
@@ -53,10 +59,18 @@
     return encodedId.contains(TOKEN_SEPARATOR);
   }
 
+  public static boolean isSynthetic(String encodedId) {
+    return encodedId.contains(SYNTHETIC_SEPARATOR);
+  }
+
   public static String persistedId(String serverId, String typeToken) {
     return serverId + TOKEN_SEPARATOR + typeToken;
   }
 
+  public static String syntheticId(int syntheticId, String historyToken) {
+    return syntheticId + SYNTHETIC_SEPARATOR + historyToken;
+  }
+
   private static String[] asAny(String encodedId) {
     return encodedId.split(ANY_SEPARATOR_PATTERN);
   }
@@ -69,8 +83,12 @@
     return encodedId.split(TOKEN_SEPARATOR);
   }
 
+  private static String[] asSynthetic(String encodedId) {
+    return encodedId.split(SYNTHETIC_SEPARATOR);
+  }
+
   /**
-   * Utility class
+   * Utility class.
    */
   private IdUtil() {
   }
diff --git a/user/src/com/google/gwt/requestfactory/shared/messages/MessageFactory.java b/user/src/com/google/gwt/requestfactory/shared/messages/MessageFactory.java
index cde822c..5742010 100644
--- a/user/src/com/google/gwt/requestfactory/shared/messages/MessageFactory.java
+++ b/user/src/com/google/gwt/requestfactory/shared/messages/MessageFactory.java
@@ -22,10 +22,10 @@
  * The factory for creating RequestFactory wire messages.
  */
 public interface MessageFactory extends AutoBeanFactory {
-  AutoBean<InvocationMessage> invocation();
-
   AutoBean<ServerFailureMessage> failure();
 
+  AutoBean<InvocationMessage> invocation();
+
   AutoBean<OperationMessage> operation();
 
   AutoBean<RequestMessage> request();
diff --git a/user/src/com/google/gwt/requestfactory/shared/messages/ResponseMessage.java b/user/src/com/google/gwt/requestfactory/shared/messages/ResponseMessage.java
index e4135c1..2555ea5 100644
--- a/user/src/com/google/gwt/requestfactory/shared/messages/ResponseMessage.java
+++ b/user/src/com/google/gwt/requestfactory/shared/messages/ResponseMessage.java
@@ -21,7 +21,7 @@
 import java.util.List;
 
 /**
- * The result of fulfilling a request on the server;
+ * The result of fulfilling a request on the server.
  */
 public interface ResponseMessage extends VersionedMessage {
   String GENERAL_FAILURE = "F";
diff --git a/user/src/com/google/gwt/requestfactory/shared/messages/ViolationMessage.java b/user/src/com/google/gwt/requestfactory/shared/messages/ViolationMessage.java
index ea57a59..b891ac0 100644
--- a/user/src/com/google/gwt/requestfactory/shared/messages/ViolationMessage.java
+++ b/user/src/com/google/gwt/requestfactory/shared/messages/ViolationMessage.java
@@ -18,7 +18,7 @@
 import com.google.gwt.autobean.shared.AutoBean.PropertyName;
 
 /**
- * Represents a ConstraintViolation
+ * Represents a ConstraintViolation.
  */
 public interface ViolationMessage extends IdMessage {
   String MESSAGE = "M";
diff --git a/user/test/com/google/gwt/editor/rebind/model/EditorModelTest.java b/user/test/com/google/gwt/editor/rebind/model/EditorModelTest.java
index 956bfd4..d7164d4 100644
--- a/user/test/com/google/gwt/editor/rebind/model/EditorModelTest.java
+++ b/user/test/com/google/gwt/editor/rebind/model/EditorModelTest.java
@@ -43,7 +43,6 @@
 import com.google.gwt.requestfactory.shared.RequestContext;
 import com.google.gwt.requestfactory.shared.RequestFactory;
 import com.google.gwt.requestfactory.shared.Violation;
-import com.google.gwt.requestfactory.shared.impl.Property;
 import com.google.gwt.user.client.TakesValue;
 import com.google.gwt.user.client.ui.HasText;
 
@@ -260,9 +259,10 @@
     EditorData[] data = m.getEditorData();
     assertNotNull(data);
     assertEquals(2, data.length);
-    assertEquals(Arrays.asList("b", "b.string"), Arrays.asList(
-        data[0].getPath(), data[1].getPath()));
-    assertEquals(Arrays.asList("bEditor().asEditor()", "stringEditor()"),
+    assertEquals(Arrays.asList("b", "b.string"),
+        Arrays.asList(data[0].getPath(), data[1].getPath()));
+    assertEquals(
+        Arrays.asList("bEditor().asEditor()", "stringEditor()"),
         Arrays.asList(data[0].getSimpleExpression(),
             data[1].getSimpleExpression()));
   }
@@ -285,9 +285,11 @@
         "bEditor().viewEditor()"), Arrays.asList(data[0].getExpression(),
         data[1].getExpression(), data[2].getExpression(),
         data[3].getExpression()));
-    assertEquals(Arrays.asList(true, false, true, false), Arrays.asList(
-        data[0].isDelegateRequired(), data[1].isDelegateRequired(),
-        data[2].isDelegateRequired(), data[3].isDelegateRequired()));
+    assertEquals(
+        Arrays.asList(true, false, true, false),
+        Arrays.asList(data[0].isDelegateRequired(),
+            data[1].isDelegateRequired(), data[2].isDelegateRequired(),
+            data[3].isDelegateRequired()));
   }
 
   public void testListDriver() throws UnableToCompleteException {
@@ -333,10 +335,12 @@
   public void testMissingGetter() {
     UnitTestTreeLogger.Builder builder = new UnitTestTreeLogger.Builder();
     builder.setLowestLogLevel(TreeLogger.ERROR);
-    builder.expectError(EditorModel.noGetterMessage("missing",
-        types.findType("t.MissingGetterEditorDriver.AProxy")), null);
-    builder.expectError(EditorModel.noGetterMessage("yetAgain",
-        types.findType("t.MissingGetterEditorDriver.AProxy")), null);
+    builder.expectError(
+        EditorModel.noGetterMessage("missing",
+            types.findType("t.MissingGetterEditorDriver.AProxy")), null);
+    builder.expectError(
+        EditorModel.noGetterMessage("yetAgain",
+            types.findType("t.MissingGetterEditorDriver.AProxy")), null);
     builder.expectError(EditorModel.poisonedMessage(), null);
     UnitTestTreeLogger testLogger = builder.createLogger();
     try {
@@ -354,8 +358,9 @@
   public void testSanityErrorMessages() {
     UnitTestTreeLogger.Builder builder = new UnitTestTreeLogger.Builder();
     builder.setLowestLogLevel(TreeLogger.ERROR);
-    builder.expectError(EditorModel.unexpectedInputTypeMessage(rfedType,
-        types.getJavaLangObject()), null);
+    builder.expectError(
+        EditorModel.unexpectedInputTypeMessage(rfedType,
+            types.getJavaLangObject()), null);
     builder.expectError(EditorModel.mustExtendMessage(rfedType), null);
     builder.expectError(
         EditorModel.tooManyInterfacesMessage(types.findType("t.TooManyInterfacesEditorDriver")),
@@ -384,9 +389,11 @@
   public void testUnparameterizedEditor() {
     UnitTestTreeLogger.Builder builder = new UnitTestTreeLogger.Builder();
     builder.setLowestLogLevel(TreeLogger.ERROR);
-    builder.expectError(EditorModel.noEditorParameterizationMessage(
-        types.findType(Editor.class.getName()), types.findType(
-            SimpleEditor.class.getName()).isGenericType().getRawType()), null);
+    builder.expectError(
+        EditorModel.noEditorParameterizationMessage(
+            types.findType(Editor.class.getName()),
+            types.findType(SimpleEditor.class.getName()).isGenericType().getRawType()),
+        null);
     UnitTestTreeLogger testLogger = builder.createLogger();
     try {
       new EditorModel(testLogger,
@@ -486,7 +493,7 @@
         return code;
       }
     }, new MockJavaResource("t.CyclicEditorDriver") {
-      // Tests error-detection when the editor graph isn't a DAG
+        // Tests error-detection when the editor graph isn't a DAG
       @Override
       protected CharSequence getContent() {
         StringBuilder code = new StringBuilder();
@@ -510,7 +517,7 @@
         return code;
       }
     }, new MockJavaResource("t.DottedPathEditorDriver") {
-      // Tests error-detection when the editor graph isn't a DAG
+        // Tests error-detection when the editor graph isn't a DAG
       @Override
       protected CharSequence getContent() {
         StringBuilder code = new StringBuilder();
@@ -532,7 +539,7 @@
         return code;
       }
     }, new MockJavaResource("t.ListEditor") {
-      // Tests error-detection when the editor graph isn't a DAG
+        // Tests error-detection when the editor graph isn't a DAG
       @Override
       protected CharSequence getContent() {
         StringBuilder code = new StringBuilder();
@@ -555,7 +562,8 @@
         return code;
       }
     }, new MockJavaResource("t.MissingGetterEditorDriver") {
-      // Tests error-detection when the editor structure doesn't match the proxy
+        // Tests error-detection when the editor structure doesn't match the
+        // proxy
       @Override
       protected CharSequence getContent() {
         StringBuilder code = new StringBuilder();
@@ -657,7 +665,7 @@
         return code;
       }
     }, new MockJavaResource("t.TooManyInterfacesEditorDriver") {
-      // Tests a Driver interface that extends more than RFED
+        // Tests a Driver interface that extends more than RFED
       @Override
       protected CharSequence getContent() {
         StringBuilder code = new StringBuilder();
@@ -671,7 +679,8 @@
         return code;
       }
     }, new MockJavaResource("t.UnparameterizedEditorEditorDriver") {
-      // Tests error-detection when the editor structure doesn't match the proxy
+        // Tests error-detection when the editor structure doesn't match the
+        // proxy
       @Override
       protected CharSequence getContent() {
         StringBuilder code = new StringBuilder();
@@ -692,7 +701,8 @@
         return code;
       }
     }, new MockJavaResource("t.UsesIsEditorDriver") {
-      // Tests error-detection when the editor structure doesn't match the proxy
+        // Tests error-detection when the editor structure doesn't match the
+        // proxy
       @Override
       protected CharSequence getContent() {
         StringBuilder code = new StringBuilder();
@@ -721,7 +731,8 @@
         return code;
       }
     }, new MockJavaResource("t.UsesIsEditorAndEditorDriver") {
-      // Tests error-detection when the editor structure doesn't match the proxy
+        // Tests error-detection when the editor structure doesn't match the
+        // proxy
       @Override
       protected CharSequence getContent() {
         StringBuilder code = new StringBuilder();
@@ -751,7 +762,7 @@
         return code;
       }
     }, new MockJavaResource("java.util.List") {
-      // Tests a Driver interface that extends more than RFED
+        // Tests a Driver interface that extends more than RFED
       @Override
       protected CharSequence getContent() {
         StringBuilder code = new StringBuilder();
@@ -774,7 +785,6 @@
         new RealJavaResource(IsEditor.class),
         new EmptyMockJavaResource(Iterable.class),
         new RealJavaResource(LeafValueEditor.class),
-        new EmptyMockJavaResource(Property.class),
         new EmptyMockJavaResource(EntityProxy.class),
         new EmptyMockJavaResource(RequestFactory.class),
         new RealJavaResource(RequestFactoryEditorDriver.class),
diff --git a/user/test/com/google/gwt/requestfactory/client/RequestFactoryTest.java b/user/test/com/google/gwt/requestfactory/client/RequestFactoryTest.java
index 642d96a..19d5130 100644
--- a/user/test/com/google/gwt/requestfactory/client/RequestFactoryTest.java
+++ b/user/test/com/google/gwt/requestfactory/client/RequestFactoryTest.java
@@ -27,6 +27,7 @@
 import com.google.gwt.requestfactory.shared.SimpleEnum;
 import com.google.gwt.requestfactory.shared.SimpleFooProxy;
 import com.google.gwt.requestfactory.shared.SimpleFooRequest;
+import com.google.gwt.requestfactory.shared.SimpleValueProxy;
 import com.google.gwt.requestfactory.shared.Violation;
 import com.google.gwt.requestfactory.shared.impl.SimpleEntityProxyId;
 
@@ -187,48 +188,6 @@
     }
   }
 
-  public void disabled_testEchoComplexFutures() {
-    // relate futures on the server. Check if the relationship is still present
-    // on the client.
-    delayTestFinish(DELAY_TEST_FINISH);
-    final SimpleFooEventHandler<SimpleFooProxy> handler = new SimpleFooEventHandler<SimpleFooProxy>();
-    EntityProxyChange.registerForProxyType(req.getEventBus(),
-        SimpleFooProxy.class, handler);
-    SimpleFooRequest context = req.simpleFooRequest();
-    final SimpleFooProxy simpleFoo = context.create(SimpleFooProxy.class);
-    final SimpleBarProxy simpleBar = context.create(SimpleBarProxy.class);
-    context.echoComplex(simpleFoo, simpleBar).fire(
-        new Receiver<SimpleFooProxy>() {
-          @Override
-          public void onSuccess(SimpleFooProxy response) {
-            assertEquals(0, handler.totalEventCount);
-            checkStableIdEquals(simpleFoo, response);
-            SimpleBarProxy responseBar = response.getBarField();
-            assertNotNull(responseBar);
-            checkStableIdEquals(simpleBar, responseBar);
-            finishTestAndReset();
-          }
-        });
-  }
-
-  public void disabled_testEchoSimpleFutures() {
-    // tests if futureIds can be echoed back.
-    delayTestFinish(DELAY_TEST_FINISH);
-    final SimpleFooEventHandler<SimpleFooProxy> handler = new SimpleFooEventHandler<SimpleFooProxy>();
-    EntityProxyChange.registerForProxyType(req.getEventBus(),
-        SimpleFooProxy.class, handler);
-    SimpleFooRequest context = req.simpleFooRequest();
-    final SimpleFooProxy simpleFoo = context.create(SimpleFooProxy.class);
-    context.echo(simpleFoo).fire(new Receiver<SimpleFooProxy>() {
-      @Override
-      public void onSuccess(SimpleFooProxy response) {
-        assertEquals(0, handler.totalEventCount);
-        checkStableIdEquals(simpleFoo, response);
-        finishTestAndReset();
-      }
-    });
-  }
-
   /**
    * Test that removing a parent entity and implicitly removing the child sends
    * an event to the client that the child was removed.
@@ -925,7 +884,7 @@
    */
   public void testNullValueInIntegerListRequest() {
     delayTestFinish(DELAY_TEST_FINISH);
-    List<Integer> list = Arrays.asList(new Integer[]{1, 2, null});
+    List<Integer> list = Arrays.asList(new Integer[] {1, 2, null});
     final Request<Void> fooReq = req.simpleFooRequest().receiveNullValueInIntegerList(
         list);
     fooReq.fire(new Receiver<Void>() {
@@ -941,7 +900,7 @@
    */
   public void testNullValueInStringListRequest() {
     delayTestFinish(DELAY_TEST_FINISH);
-    List<String> list = Arrays.asList(new String[]{"nonnull", "null", null});
+    List<String> list = Arrays.asList(new String[] {"nonnull", "null", null});
     final Request<Void> fooReq = req.simpleFooRequest().receiveNullValueInStringList(
         list);
     fooReq.fire(new Receiver<Void>() {
@@ -986,11 +945,6 @@
     });
   }
 
-  /*
-   * TODO: all these tests should check the final values. It will be easy when
-   * we have better persistence than the singleton pattern.
-   */
-
   public void testPersistExistingEntityExistingRelation() {
     delayTestFinish(DELAY_TEST_FINISH);
 
@@ -1123,6 +1077,11 @@
   }
 
   /*
+   * TODO: all these tests should check the final values. It will be easy when
+   * we have better persistence than the singleton pattern.
+   */
+
+  /*
    * Find Entity2 Create Entity, Persist Entity Relate Entity2 to Entity Persist
    * Entity
    */
@@ -1268,11 +1227,6 @@
     });
   }
 
-  /*
-   * TODO: all these tests should check the final values. It will be easy when
-   * we have better persistence than the singleton pattern.
-   */
-
   public void testPersistRelation() {
     delayTestFinish(DELAY_TEST_FINISH);
 
@@ -1316,11 +1270,6 @@
     });
   }
 
-  /*
-   * TODO: all these tests should check the final values. It will be easy when
-   * we have better persistence than the singleton pattern.
-   */
-
   public void testPersistSelfOneToManyExistingEntityExistingRelation() {
     delayTestFinish(DELAY_TEST_FINISH);
 
@@ -1374,6 +1323,11 @@
    * TODO: all these tests should check the final values. It will be easy when
    * we have better persistence than the singleton pattern.
    */
+
+  /*
+   * TODO: all these tests should check the final values. It will be easy when
+   * we have better persistence than the singleton pattern.
+   */
   public void testPersistValueListNull() {
     delayTestFinish(DELAY_TEST_FINISH);
     simpleFooRequest().findSimpleFooById(999L).fire(
@@ -1402,6 +1356,11 @@
    * TODO: all these tests should check the final values. It will be easy when
    * we have better persistence than the singleton pattern.
    */
+
+  /*
+   * TODO: all these tests should check the final values. It will be easy when
+   * we have better persistence than the singleton pattern.
+   */
   public void testPersistValueListRemove() {
     delayTestFinish(DELAY_TEST_FINISH);
     simpleFooRequest().findSimpleFooById(999L).fire(
@@ -1617,7 +1576,7 @@
                     Set<SimpleBarProxy> setField = fooProxy.getOneToManySetField();
                     final int listCount = setField.size();
                     assertContains(setField, barProxy);
-                    setField.remove(barProxy);
+                    setField.remove(context.edit(barProxy));
                     assertNotContains(setField, barProxy);
                     updReq.fire(new Receiver<SimpleFooProxy>() {
                       @Override
@@ -1962,6 +1921,80 @@
   }
 
   /**
+   * Check if a graph of unpersisted objects can be echoed.
+   */
+  public void testUnpersistedEchoComplexGraph() {
+    delayTestFinish(DELAY_TEST_FINISH);
+    final SimpleFooEventHandler<SimpleFooProxy> handler = new SimpleFooEventHandler<SimpleFooProxy>();
+    EntityProxyChange.registerForProxyType(req.getEventBus(),
+        SimpleFooProxy.class, handler);
+    SimpleFooRequest context = req.simpleFooRequest();
+    final SimpleBarProxy simpleBar = context.create(SimpleBarProxy.class);
+    simpleBar.setUnpersisted(true);
+    final SimpleFooProxy simpleFoo = context.create(SimpleFooProxy.class);
+    simpleFoo.setUnpersisted(true);
+    simpleFoo.setBarField(simpleBar);
+    context.echoComplex(simpleFoo, simpleBar).with("barField").fire(
+        new Receiver<SimpleFooProxy>() {
+          @Override
+          public void onSuccess(SimpleFooProxy response) {
+            assertEquals(0, handler.totalEventCount);
+            checkStableIdEquals(simpleFoo, response);
+            SimpleBarProxy responseBar = response.getBarField();
+            assertNotNull(responseBar);
+            checkStableIdEquals(simpleBar, responseBar);
+            finishTestAndReset();
+          }
+        });
+  }
+
+  /**
+   * Check if an unpersisted object can be echoed.
+   */
+  public void testUnpersistedEchoObject() {
+    delayTestFinish(DELAY_TEST_FINISH);
+    final SimpleFooEventHandler<SimpleFooProxy> handler = new SimpleFooEventHandler<SimpleFooProxy>();
+    EntityProxyChange.registerForProxyType(req.getEventBus(),
+        SimpleFooProxy.class, handler);
+    SimpleFooRequest context = req.simpleFooRequest();
+    final SimpleFooProxy simpleFoo = context.create(SimpleFooProxy.class);
+    simpleFoo.setUnpersisted(true);
+    context.echo(simpleFoo).fire(new Receiver<SimpleFooProxy>() {
+      @Override
+      public void onSuccess(SimpleFooProxy response) {
+        assertEquals(0, handler.totalEventCount);
+        checkStableIdEquals(simpleFoo, response);
+        finishTestAndReset();
+      }
+    });
+  }
+
+  /**
+   * Return an unpersisted object from a service method and echo it.
+   */
+  public void testUnpersistedObjectFetch() {
+    delayTestFinish(DELAY_TEST_FINISH);
+    req.simpleFooRequest().getUnpersistedInstance().fire(
+        new Receiver<SimpleFooProxy>() {
+          @Override
+          public void onSuccess(final SimpleFooProxy created) {
+            assertNotNull(created);
+            assertTrue(created.getUnpersisted());
+            req.simpleFooRequest().echo(created).fire(
+                new Receiver<SimpleFooProxy>() {
+                  @Override
+                  public void onSuccess(SimpleFooProxy response) {
+                    assertNotNull(response);
+                    assertEquals(created.stableId(), response.stableId());
+                    assertTrue(response.getUnpersisted());
+                    finishTestAndReset();
+                  }
+                });
+          }
+        });
+  }
+
+  /**
    * This is analagous to FindServiceTest.testFetchDeletedEntity() only we're
    * trying to invoke a method on the deleted entity using a stale EntityProxy
    * reference on the client.
@@ -2019,6 +2052,228 @@
         });
   }
 
+  public void testValueObjectCreateSetRetrieveUpdate() {
+    delayTestFinish(DELAY_TEST_FINISH);
+    SimpleFooRequest req = simpleFooRequest();
+    req.findSimpleFooById(1L).fire(new Receiver<SimpleFooProxy>() {
+      @Override
+      public void onSuccess(SimpleFooProxy response) {
+        SimpleFooRequest req = simpleFooRequest();
+
+        // Create
+        final SimpleValueProxy created = req.create(SimpleValueProxy.class);
+        created.setNumber(42);
+        created.setString("Hello world!");
+        created.setSimpleFoo(response);
+        // Test cycles in value
+        created.setSimpleValue(Arrays.asList(created));
+
+        // Set
+        response = req.edit(response);
+        response.setSimpleValue(created);
+
+        // Retrieve
+        req.persistAndReturnSelf().using(response).with(
+            "simpleValue.simpleFoo", "simpleValue.simpleValue").to(
+            new Receiver<SimpleFooProxy>() {
+              @Override
+              public void onSuccess(SimpleFooProxy response) {
+                SimpleValueProxy value = response.getSimpleValue();
+                assertEquals(42, value.getNumber());
+                assertEquals("Hello world!", value.getString());
+                assertSame(response, value.getSimpleFoo());
+                assertSame(value, value.getSimpleValue().get(0));
+
+                try {
+                  // Require owning object to be editable
+                  response.getSimpleValue().setNumber(43);
+                  fail("Should have thrown exception");
+                } catch (IllegalStateException expected) {
+                }
+
+                // Update
+                SimpleFooRequest req = simpleFooRequest();
+                response = req.edit(response);
+                response.getSimpleValue().setNumber(43);
+                req.persistAndReturnSelf().using(response).with("simpleValue").to(
+                    new Receiver<SimpleFooProxy>() {
+                      @Override
+                      public void onSuccess(SimpleFooProxy response) {
+                        assertEquals(43, response.getSimpleValue().getNumber());
+                        finishTestAndReset();
+                      }
+                    }).fire();
+              }
+            }).fire();
+      }
+    });
+  }
+
+  public void testValueObjectCreateSetRetrieveUpdateViaList() {
+    delayTestFinish(DELAY_TEST_FINISH);
+    SimpleFooRequest req = simpleFooRequest();
+    req.findSimpleFooById(1L).fire(new Receiver<SimpleFooProxy>() {
+      @Override
+      public void onSuccess(SimpleFooProxy response) {
+        SimpleFooRequest req = simpleFooRequest();
+
+        // Create
+        final SimpleValueProxy created = req.create(SimpleValueProxy.class);
+        created.setNumber(42);
+        created.setString("Hello world!");
+        created.setSimpleFoo(response);
+
+        // Set
+        response = req.edit(response);
+        response.setSimpleValues(Arrays.asList(created));
+
+        // Retrieve
+        req.persistAndReturnSelf().using(response).with("simpleValues").to(
+            new Receiver<SimpleFooProxy>() {
+              @Override
+              public void onSuccess(SimpleFooProxy response) {
+                SimpleValueProxy value = response.getSimpleValues().get(0);
+                assertEquals(42, value.getNumber());
+
+                try {
+                  // Require owning object to be editable
+                  response.getSimpleValues().get(0).setNumber(43);
+                  fail("Should have thrown exception");
+                } catch (IllegalStateException expected) {
+                }
+
+                // Update
+                SimpleFooRequest req = simpleFooRequest();
+                response = req.edit(response);
+                response.getSimpleValues().get(0).setNumber(43);
+                req.persistAndReturnSelf().using(response).with("simpleValues").to(
+                    new Receiver<SimpleFooProxy>() {
+                      @Override
+                      public void onSuccess(SimpleFooProxy response) {
+                        assertEquals(43,
+                            response.getSimpleValues().get(0).getNumber());
+                        finishTestAndReset();
+                      }
+                    }).fire();
+              }
+            }).fire();
+      }
+    });
+  }
+
+  public void testValueObjectEquality() {
+    SimpleFooRequest req = simpleFooRequest();
+    SimpleValueProxy a = req.create(SimpleValueProxy.class);
+    SimpleValueProxy b = req.create(SimpleValueProxy.class);
+    checkEqualityAndHashcode(a, b);
+
+    a.setString("Hello");
+    assertFalse(a.equals(b));
+    assertFalse(b.equals(a));
+
+    b.setString("Hello");
+    checkEqualityAndHashcode(a, b);
+  }
+
+  public void testValueObjectViolationsOnCreate() {
+    delayTestFinish(DELAY_TEST_FINISH);
+    SimpleFooRequest req = simpleFooRequest();
+    final SimpleValueProxy value = req.create(SimpleValueProxy.class);
+    value.setShouldBeNull("Hello world");
+
+    SimpleFooProxy foo = req.create(SimpleFooProxy.class);
+    foo.setSimpleValue(value);
+    req.echo(foo).fire(new Receiver<SimpleFooProxy>() {
+      @Override
+      public void onSuccess(SimpleFooProxy response) {
+        fail();
+      }
+
+      @Override
+      public void onViolation(Set<Violation> errors) {
+        assertEquals(1, errors.size());
+        Violation v = errors.iterator().next();
+        assertEquals(value, v.getInvalidProxy());
+        assertNull(v.getOriginalProxy());
+        assertEquals("shouldBeNull", v.getPath());
+        assertNull(v.getProxyId());
+        finishTestAndReset();
+      }
+    });
+  }
+
+  public void testValueObjectViolationsOnEdit() {
+    delayTestFinish(DELAY_TEST_FINISH);
+    simpleFooRequest().returnValueProxy().fire(
+        new Receiver<SimpleValueProxy>() {
+          @Override
+          public void onSuccess(final SimpleValueProxy response) {
+            SimpleFooRequest req = simpleFooRequest();
+            final SimpleValueProxy value = req.edit(response);
+            value.setShouldBeNull("Hello world");
+            SimpleFooProxy foo = req.create(SimpleFooProxy.class);
+            foo.setSimpleValue(value);
+            req.echo(foo).fire(new Receiver<SimpleFooProxy>() {
+              @Override
+              public void onSuccess(SimpleFooProxy response) {
+                fail();
+              }
+
+              @Override
+              public void onViolation(Set<Violation> errors) {
+                assertEquals(1, errors.size());
+                Violation v = errors.iterator().next();
+                assertEquals(value, v.getInvalidProxy());
+                assertEquals(response, v.getOriginalProxy());
+                assertEquals("shouldBeNull", v.getPath());
+                assertNull(v.getProxyId());
+                finishTestAndReset();
+              }
+            });
+          }
+        });
+  }
+
+  /**
+   * Since a ValueProxy cannot be passed into RequestContext edit, a proxy
+   * returned from a service method should be mutable by default.
+   */
+  public void testValueObjectReturnedFromRequestIsImmutable() {
+    delayTestFinish(DELAY_TEST_FINISH);
+    simpleFooRequest().returnValueProxy().fire(
+        new Receiver<SimpleValueProxy>() {
+          @Override
+          public void onSuccess(SimpleValueProxy a) {
+            try {
+              a.setNumber(77);
+              fail();
+            } catch (IllegalStateException expected) {
+            }
+            try {
+              // Ensure Dates comply with ValueProxy mutation behaviors
+              a.getDate().setTime(1);
+              fail();
+            } catch (IllegalStateException expected) {
+            }
+            SimpleFooRequest ctx = simpleFooRequest();
+            final SimpleValueProxy toCheck = ctx.edit(a);
+            toCheck.setNumber(77);
+            toCheck.getDate().setTime(1);
+            ctx.returnValueProxy().fire(new Receiver<SimpleValueProxy>() {
+              @Override
+              public void onSuccess(SimpleValueProxy b) {
+                b = simpleFooRequest().edit(b);
+                // Now check that same value is equal across contexts
+                b.setNumber(77);
+                b.setDate(new Date(1));
+                checkEqualityAndHashcode(toCheck, b);
+                finishTestAndReset();
+              }
+            });
+          }
+        });
+  }
+
   public void testViolationAbsent() {
     delayTestFinish(DELAY_TEST_FINISH);
 
diff --git a/user/test/com/google/gwt/requestfactory/client/RequestFactoryTestBase.java b/user/test/com/google/gwt/requestfactory/client/RequestFactoryTestBase.java
index c162c63..ef88d8b 100644
--- a/user/test/com/google/gwt/requestfactory/client/RequestFactoryTestBase.java
+++ b/user/test/com/google/gwt/requestfactory/client/RequestFactoryTestBase.java
@@ -69,6 +69,14 @@
     eventBus = req.getEventBus();
   }
 
+  protected void checkEqualityAndHashcode(Object a, Object b) {
+    assertNotNull(a);
+    assertNotNull(b);
+    assertEquals(a.hashCode(), b.hashCode());
+    assertEquals(a, b);
+    assertEquals(b, a);
+  }
+
   protected void checkStableIdEquals(EntityProxy expected, EntityProxy actual) {
     assertEquals(expected.stableId(), actual.stableId());
     assertEquals(expected.stableId().hashCode(), actual.stableId().hashCode());
diff --git a/user/test/com/google/gwt/requestfactory/rebind/model/RequestFactoryModelTest.java b/user/test/com/google/gwt/requestfactory/rebind/model/RequestFactoryModelTest.java
index 74d268a..3e160e5 100644
--- a/user/test/com/google/gwt/requestfactory/rebind/model/RequestFactoryModelTest.java
+++ b/user/test/com/google/gwt/requestfactory/rebind/model/RequestFactoryModelTest.java
@@ -34,7 +34,7 @@
 import com.google.gwt.requestfactory.shared.RequestContext;
 import com.google.gwt.requestfactory.shared.RequestFactory;
 import com.google.gwt.requestfactory.shared.Service;
-import com.google.gwt.requestfactory.shared.impl.Property;
+import com.google.gwt.requestfactory.shared.ValueProxy;
 
 import junit.framework.TestCase;
 
@@ -267,11 +267,11 @@
 
     toReturn.addAll(Arrays.asList(new Resource[] {
         new EmptyMockJavaResource(Iterable.class),
-        new EmptyMockJavaResource(Property.class),
         new EmptyMockJavaResource(EntityProxy.class),
         new EmptyMockJavaResource(InstanceRequest.class),
         new EmptyMockJavaResource(RequestFactory.class),
         new EmptyMockJavaResource(Receiver.class),
+        new EmptyMockJavaResource(ValueProxy.class),
 
         new RealJavaResource(Request.class),
         new RealJavaResource(Service.class),
diff --git a/user/test/com/google/gwt/requestfactory/server/RequestFactoryInterfaceValidatorTest.java b/user/test/com/google/gwt/requestfactory/server/RequestFactoryInterfaceValidatorTest.java
index 24739f7..46218c4 100644
--- a/user/test/com/google/gwt/requestfactory/server/RequestFactoryInterfaceValidatorTest.java
+++ b/user/test/com/google/gwt/requestfactory/server/RequestFactoryInterfaceValidatorTest.java
@@ -24,10 +24,12 @@
 import com.google.gwt.requestfactory.shared.RequestFactory;
 import com.google.gwt.requestfactory.shared.Service;
 import com.google.gwt.requestfactory.shared.SimpleRequestFactory;
+import com.google.gwt.requestfactory.shared.ValueProxy;
 import com.google.gwt.requestfactory.shared.impl.FindRequest;
 
 import junit.framework.TestCase;
 
+import java.util.List;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
@@ -101,7 +103,6 @@
       return 0;
     }
   }
-
   @ProxyFor(DomainWithOverloads.class)
   interface DomainWithOverloadsProxy extends EntityProxy {
     void foo();
@@ -142,6 +143,13 @@
     Request<Integer> doesNotExist(int a);
   }
 
+  static class Value {
+  }
+
+  @ProxyFor(value = Value.class)
+  interface MyValueProxy extends ValueProxy {
+  }
+
   RequestFactoryInterfaceValidator v;
 
   /**
@@ -164,6 +172,51 @@
     v.validateRequestContext(FindRequest.class.getName());
   }
 
+  @ProxyFor(HasListDomain.class)
+  interface HasList extends EntityProxy {
+    List<ReachableOnlyThroughReturnedList> getList();
+
+    void setList(List<ReachableOnlyThroughParamaterList> list);
+  }
+
+  static class HasListDomain extends Domain {
+    List<Domain> getList() {
+      return null;
+    }
+
+    void setList(List<Domain> value) {
+    }
+
+    public String getId() {
+      return null;
+    }
+
+    public int getVersion() {
+      return 0;
+    }
+  }
+
+  @ProxyFor(Domain.class)
+  interface ReachableOnlyThroughReturnedList extends EntityProxy {
+  };
+  @ProxyFor(Domain.class)
+  interface ReachableOnlyThroughParamaterList extends EntityProxy {
+  };
+
+  /**
+   * Make sure that proxy types referenced through type parameters of method
+   * return types and paramaters types are examined.
+   */
+  public void testFollowingTypeParameters() {
+    v.validateEntityProxy(HasList.class.getName());
+    assertNotNull(v.getEntityProxyTypeName(HasListDomain.class.getName(),
+        HasList.class.getName()));
+    assertNotNull(v.getEntityProxyTypeName(Domain.class.getName(),
+        ReachableOnlyThroughParamaterList.class.getName()));
+    assertNotNull(v.getEntityProxyTypeName(Domain.class.getName(),
+        ReachableOnlyThroughReturnedList.class.getName()));
+  }
+
   /**
    * Ensure that the &lt;clinit> methods don't interfere with validation.
    */
@@ -225,6 +278,11 @@
     assertFalse(v.isPoisoned());
   }
 
+  public void testValueType() {
+    v.validateValueProxy(MyValueProxy.class.getName());
+    assertFalse(v.isPoisoned());
+  }
+
   @Override
   protected void setUp() throws Exception {
     Logger logger = Logger.getLogger("");
diff --git a/user/test/com/google/gwt/requestfactory/server/SimpleBar.java b/user/test/com/google/gwt/requestfactory/server/SimpleBar.java
index d75ff3b..d0a625c 100644
--- a/user/test/com/google/gwt/requestfactory/server/SimpleBar.java
+++ b/user/test/com/google/gwt/requestfactory/server/SimpleBar.java
@@ -1,12 +1,12 @@
 /*
  * Copyright 2010 Google Inc.
- *
+ * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
  * the License at
- *
+ * 
  * http://www.apache.org/licenses/LICENSE-2.0
- *
+ * 
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
@@ -88,12 +88,7 @@
   public static SimpleBar getSingleton() {
     return findSimpleBar("1L");
   }
-
-  public static SimpleBar returnFirst(List<SimpleBar> list) {
-    SimpleBar toReturn = list.get(0);
-    return toReturn;
-  }
-
+  
   public static void reset() {
     resetImpl();
   }
@@ -121,11 +116,17 @@
     return instance;
   }
 
+  public static SimpleBar returnFirst(List<SimpleBar> list) {
+    SimpleBar toReturn = list.get(0);
+    return toReturn;
+  }
+
   Integer version = 1;
 
   private String id = "999L";
   private boolean findFails;
   private boolean isNew = true;
+  private boolean unpersisted;
   private String userName;
 
   public SimpleBar() {
@@ -142,7 +143,11 @@
   }
 
   public String getId() {
-    return id;
+    return unpersisted ? null : id;
+  }
+
+  public Boolean getUnpersisted() {
+    return unpersisted;
   }
 
   public String getUserName() {
@@ -150,7 +155,7 @@
   }
 
   public Integer getVersion() {
-    return version;
+    return unpersisted ? null : version;
   }
 
   public void persist() {
@@ -175,6 +180,10 @@
     this.id = id;
   }
 
+  public void setUnpersisted(Boolean unpersisted) {
+    this.unpersisted = unpersisted;
+  }
+
   public void setUserName(String userName) {
     this.userName = userName;
   }
diff --git a/user/test/com/google/gwt/requestfactory/server/SimpleFoo.java b/user/test/com/google/gwt/requestfactory/server/SimpleFoo.java
index 417aef2..460621d 100644
--- a/user/test/com/google/gwt/requestfactory/server/SimpleFoo.java
+++ b/user/test/com/google/gwt/requestfactory/server/SimpleFoo.java
@@ -1,12 +1,12 @@
 /*
  * Copyright 2010 Google Inc.
- *
+ * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
  * the License at
- *
+ * 
  * http://www.apache.org/licenses/LICENSE-2.0
- *
+ * 
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
@@ -151,6 +151,12 @@
     return foo1;
   }
 
+  public static SimpleFoo getUnpersistedInstance() {
+    SimpleFoo foo = new SimpleFoo();
+    foo.setUnpersisted(true);
+    return foo;
+  }
+
   public static Boolean processBooleanList(List<Boolean> values) {
     return values.get(0);
   }
@@ -273,6 +279,14 @@
     return null;
   }
 
+  public static SimpleValue returnValueProxy() {
+    SimpleValue toReturn = new SimpleValue();
+    toReturn.setNumber(42);
+    toReturn.setString("Hello world!");
+    toReturn.setDate(new Date());
+    return toReturn;
+  }
+
   @SuppressWarnings("unused")
   private static Integer privateMethod() {
     return 0;
@@ -321,6 +335,8 @@
 
   private List<Integer> numberListField;
 
+  private SimpleValue simpleValueField;
+
   /*
    * isChanged is just a quick-and-dirty way to get version-ing for now.
    * Currently, only set by setUserName and setIntId. TODO for later: Use a
@@ -328,6 +344,8 @@
    */
   boolean isChanged;
 
+  private boolean unpersisted;
+
   public SimpleFoo() {
     intId = 42;
     version = 1;
@@ -433,7 +451,7 @@
   }
 
   public Long getId() {
-    return id;
+    return unpersisted ? null : id;
   }
 
   public Integer getIntId() {
@@ -486,12 +504,24 @@
     return shortField;
   }
 
+  public SimpleValue getSimpleValue() {
+    return simpleValueField;
+  }
+
+  public List<SimpleValue> getSimpleValues() {
+    return Arrays.asList(simpleValueField);
+  }
+
+  public boolean getUnpersisted() {
+    return unpersisted;
+  }
+
   public String getUserName() {
     return userName;
   }
 
   public Integer getVersion() {
-    return version;
+    return unpersisted ? null : version;
   }
 
   public String hello(SimpleBar bar) {
@@ -665,6 +695,18 @@
     this.shortField = shortField;
   }
 
+  public void setSimpleValue(SimpleValue simpleValueField) {
+    this.simpleValueField = simpleValueField;
+  }
+
+  public void setSimpleValues(List<SimpleValue> simpleValueField) {
+    this.simpleValueField = simpleValueField.get(0);
+  }
+
+  public void setUnpersisted(Boolean unpersisted) {
+    this.unpersisted = unpersisted;
+  }
+
   public void setUserName(String userName) {
     if (!this.userName.equals(userName)) {
       this.userName = userName;
@@ -686,7 +728,7 @@
 
   /**
    * Persist this entity and all child entities. This method can handle loops.
-   *
+   * 
    * @param processed the entities that have been processed
    */
   private void persistCascadingAndReturnSelfImpl(Set<SimpleFoo> processed) {
diff --git a/user/test/com/google/gwt/requestfactory/server/SimpleValue.java b/user/test/com/google/gwt/requestfactory/server/SimpleValue.java
new file mode 100644
index 0000000..57c2119
--- /dev/null
+++ b/user/test/com/google/gwt/requestfactory/server/SimpleValue.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.requestfactory.server;
+
+import java.util.Date;
+import java.util.List;
+
+import javax.validation.constraints.Null;
+
+/**
+ * A domain object that is used to demonstrate value-object behaviors.
+ */
+public class SimpleValue {
+  private Date date;
+  private int number;
+  private SimpleFoo simpleFoo;
+  /**
+   * Constraint violation testing.
+   */
+  @Null
+  private String shouldBeNull;
+  private List<SimpleValue> simpleValue;
+  private String string;
+
+  public Date getDate() {
+    return date;
+  }
+
+  public int getNumber() {
+    return number;
+  }
+
+  public String getShouldBeNull() {
+    return shouldBeNull;
+  }
+
+  public SimpleFoo getSimpleFoo() {
+    return simpleFoo;
+  }
+
+  public List<SimpleValue> getSimpleValue() {
+    return simpleValue;
+  }
+
+  public String getString() {
+    return string;
+  }
+
+  public void setDate(Date date) {
+    this.date = date;
+  }
+
+  public void setShouldBeNull(String value) {
+    this.shouldBeNull = value;
+  }
+
+  public void setNumber(int number) {
+    this.number = number;
+  }
+
+  public void setSimpleFoo(SimpleFoo simpleFoo) {
+    this.simpleFoo = simpleFoo;
+  }
+
+  public void setSimpleValue(List<SimpleValue> simpleValue) {
+    this.simpleValue = simpleValue;
+  }
+
+  public void setString(String string) {
+    this.string = string;
+  }
+}
diff --git a/user/test/com/google/gwt/requestfactory/shared/BaseFooProxy.java b/user/test/com/google/gwt/requestfactory/shared/BaseFooProxy.java
index 2a40ca3..a628bf3 100644
--- a/user/test/com/google/gwt/requestfactory/shared/BaseFooProxy.java
+++ b/user/test/com/google/gwt/requestfactory/shared/BaseFooProxy.java
@@ -20,6 +20,7 @@
 import java.util.Date;
 import java.util.List;
 import java.util.Set;
+
 /**
  * A simple proxy used for testing. Has an int field and date field. Add other
  * data types as their support gets built in.
@@ -27,59 +28,57 @@
 public interface BaseFooProxy extends EntityProxy {
 
   SimpleBarProxy getBarField();
-  
+
   SimpleBarProxy getBarNullField();
-  
+
   BigDecimal getBigDecimalField();
-  
+
   BigInteger getBigIntField();
-  
+
   Boolean getBoolField();
-  
+
   Byte getByteField();
-  
+
   Character getCharField();
-  
+
   Date getCreated();
-  
+
   Double getDoubleField();
-  
+
   SimpleEnum getEnumField();
-  
+
   Float getFloatField();
-  
+
   Integer getIntId();
-  
+
   Long getLongField();
-  
+
   String getNullField();
-  
-  Boolean getOtherBoolField();
-  
-  Integer getPleaseCrash();
-
-  String getPassword();
-
-  Short getShortField();
-  
-  String getUserName();
-
-  List<SimpleBarProxy> getOneToManyField();
-
-  List<SimpleFooProxy> getSelfOneToManyField();
 
   List<Integer> getNumberListField();
 
+  List<SimpleBarProxy> getOneToManyField();
+
   Set<SimpleBarProxy> getOneToManySetField();
 
-  void setOneToManyField(List<SimpleBarProxy> field);
+  Boolean getOtherBoolField();
 
-  void setOneToManySetField(Set<SimpleBarProxy> field);
+  String getPassword();
 
-  void setSelfOneToManyField(List<SimpleFooProxy> field);
+  Integer getPleaseCrash();
 
-  void setNumberListField(List<Integer> field);
-  
+  List<SimpleFooProxy> getSelfOneToManyField();
+
+  Short getShortField();
+
+  SimpleValueProxy getSimpleValue();
+
+  List<SimpleValueProxy> getSimpleValues();
+
+  Boolean getUnpersisted();
+
+  String getUserName();
+
   void setBarField(SimpleBarProxy barField);
 
   void setBarNullField(SimpleBarProxy barNullField);
@@ -99,21 +98,34 @@
   void setDoubleField(Double d);
 
   void setFloatField(Float f);
-  
+
   void setIntId(Integer intId);
 
   void setLongField(Long longField);
 
   void setNullField(String nullField);
 
+  void setNumberListField(List<Integer> field);
+
+  void setOneToManyField(List<SimpleBarProxy> field);
+
+  void setOneToManySetField(Set<SimpleBarProxy> field);
+
   void setOtherBoolField(Boolean boolField);
 
   void setPassword(String password);
 
   void setPleaseCrash(Integer dummy);
 
+  void setSelfOneToManyField(List<SimpleFooProxy> field);
+
   void setShortField(Short s);
 
+  void setSimpleValue(SimpleValueProxy value);
+
+  void setSimpleValues(List<SimpleValueProxy> value);
+
+  void setUnpersisted(Boolean unpersisted);
+
   void setUserName(String userName);
-  
 }
diff --git a/user/test/com/google/gwt/requestfactory/shared/SimpleBarProxy.java b/user/test/com/google/gwt/requestfactory/shared/SimpleBarProxy.java
index 6c8c1ae..e3fd376 100644
--- a/user/test/com/google/gwt/requestfactory/shared/SimpleBarProxy.java
+++ b/user/test/com/google/gwt/requestfactory/shared/SimpleBarProxy.java
@@ -23,6 +23,8 @@
 public interface SimpleBarProxy extends EntityProxy {
   Boolean getFindFails();
 
+  Boolean getUnpersisted();
+
   /*
    * NB: The lack of a getId() here is intentional, to ensure that the system
    * does not assume that the id property is available to the client.
@@ -32,6 +34,8 @@
 
   void setFindFails(Boolean fails);
 
+  void setUnpersisted(Boolean b);
+
   void setUserName(String userName);
 
   EntityProxyId<SimpleBarProxy> stableId();
diff --git a/user/test/com/google/gwt/requestfactory/shared/SimpleFooRequest.java b/user/test/com/google/gwt/requestfactory/shared/SimpleFooRequest.java
index 9ae1831..a166f9e 100644
--- a/user/test/com/google/gwt/requestfactory/shared/SimpleFooRequest.java
+++ b/user/test/com/google/gwt/requestfactory/shared/SimpleFooRequest.java
@@ -51,6 +51,8 @@
 
   Request<SimpleFooProxy> getTripletReference();
 
+  Request<SimpleFooProxy> getUnpersistedInstance();
+
   InstanceRequest<SimpleFooProxy, String> hello(SimpleBarProxy proxy);
 
   InstanceRequest<SimpleFooProxy, Void> persist();
@@ -92,5 +94,7 @@
 
   Request<String> returnNullString();
 
+  Request<SimpleValueProxy> returnValueProxy();
+
   InstanceRequest<SimpleFooProxy, Integer> sum(List<Integer> values);
 }
diff --git a/user/test/com/google/gwt/requestfactory/shared/SimpleValueProxy.java b/user/test/com/google/gwt/requestfactory/shared/SimpleValueProxy.java
new file mode 100644
index 0000000..67b6d43
--- /dev/null
+++ b/user/test/com/google/gwt/requestfactory/shared/SimpleValueProxy.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.requestfactory.shared;
+
+import com.google.gwt.requestfactory.server.SimpleValue;
+
+import java.util.Date;
+import java.util.List;
+
+/**
+ * A proxy for a non-addressable value object.
+ */
+@ProxyFor(value = SimpleValue.class)
+public interface SimpleValueProxy extends ValueProxy {
+  Date getDate();
+
+  int getNumber();
+
+  String getShouldBeNull();
+
+  SimpleFooProxy getSimpleFoo();
+
+  List<SimpleValueProxy> getSimpleValue();
+
+  String getString();
+
+  void setDate(Date value);
+
+  void setNumber(int value);
+
+  void setShouldBeNull(String value);
+
+  void setSimpleFoo(SimpleFooProxy value);
+
+  void setSimpleValue(List<SimpleValueProxy> value);
+
+  void setString(String value);
+}