AS OF TAPESTRY 5.1 this code no longer seems to work. The classes Base64ObjectInputStream and Base64ObjectOutputStream no longer exist in*;

Look at the Cookies API instead

This example code creates a basic PersistentFieldStrategy for "cookie" and "flashcookie" @Persist values

This code is a 'trimmed back' version of what I use... Everything is b64 encoded. You can encode a key of some sort (ValueEncoder index, or class type / name) but it adds extra complexity.

The reason that I wanted to do this was because none of the existing persistence strategies fit my requirements.
The driving factor, as in so many web applications, was to avoid creating a session, and I really didn't like the large t:client:state cgi param.

I have this defined in my common base class:

So, here you have it... my contribution to the tapestry corner. Enjoy!

Oh - a few Caveats, etc
first - when developing add a showAllCookies t:grid into your border component from RequestGlobals.
flashcookie is safe, it doesn't hang around. I recommend you use this for all flash / instant requirements

Here is an example code to add to your border or footer-type component, to display the cookies. Note that it displays _all_ cookies, not just ones with a specific prefix.

        private RequestGlobals _requestGlobals;

        @Component(id = "cookieJar", parameters = { "source=allCookies", "rowsPerPage=100", "pagerPosition=both", "row=currentCookie" })
        private Grid cookieJar;

        private Cookie currentCookie;
        public List<Cookie> getAllCookies() {
                List<Cookie> cookieList = new ArrayList<Cookie>();
                Cookie cookies[] = _requestGlobals.getHTTPServletRequest().getCookies();
                if (cookies != null)
                        for (int i = 0; i < cookies.length; i++)
                return cookieList;

Then add this to the corresponding tml:

<t:cookieJar />

cookie can hang around after sessions... I delete all cookies on logout... but if you don't login (or just exit the browser), they are still there. I don't think this is safe.
all cookies are mapped to the entire app (context root). I don't know if this is strictly necessary, but it works.

Possible extensions:
set cookie max age (inactivity time)... where to get session timeout value from?
how to make cookies expire as soon as session is closed

First of all, a contribution had to made in AppModule:

public void contributePersistentFieldManager(MappedConfiguration<String, PersistentFieldStrategy> configuration,
                RequestGlobals requestGlobals, Request request) {
        System.out.println("################## in contributePersistentFieldManager of " + PostbackModule.class.getName());
        configuration.add(CookiePersistentField.COOKIE, new CookiePersistentField(requestGlobals, request, CookiePersistentField.COOKIE));
        configuration.add(CookiePersistentField.FLASHCOOKIE, new CookiePersistentField(requestGlobals, request, CookiePersistentField.FLASHCOOKIE));

and the CookiePersistentField source (simplified to use B64 _only_)

 * Created on 12 Apr 2008
 * @author nicholas[dot] krul _at_ gmail com
package koncept.postback.base;

import static org.apache.tapestry.ioc.internal.util.Defense.notBlank;

import java.util.*;

import javax.servlet.http.*;

import koncept.postback.base.encoders.*;


public class CookiePersistentField implements PersistentFieldStrategy {
        public static final String COOKIE = "cookie";
        public static final String FLASHCOOKIE = "flashcookie";
        private String persistenceType;
        private static final String SEPERATOR = "|";

        private RequestGlobals requestGlobals;
        private Request request;

        private EncoderBase64 encoder;
        public CookiePersistentField(RequestGlobals requestGlobals, Request request, String persistenceType) {
                this.requestGlobals = requestGlobals;
                this.request = request;
                this.persistenceType = persistenceType;
                encoder = new EncoderBase64();

        public void postChange(String pageName, String componentId, String fieldName, Object newValue) {
        notBlank(pageName, "pageName");
        notBlank(fieldName, "fieldName");

        StringBuilder builder = new StringBuilder(persistenceType);

        if (componentId != null) builder.append(componentId);


        String key = builder.toString();
        if (newValue != null) {
                String value = encoder.toClient(newValue); 
                createCookie(key, value);
        } else { //newValue == null

         private PersistentFieldChange buildChange(Cookie cookie) {
                String value = cookie.getValue();
                if (value == null || value.isEmpty()) return null; //not needed
        String[] chunks = cookie.getName().split("\\" + SEPERATOR); //oops... regexp
        String componentId = chunks[2];
        String fieldName = chunks[3];
        Object attribute = encoder.toValue(value);
        return new PersistentFieldChangeImpl(componentId, fieldName, attribute);
   public Collection<PersistentFieldChange> gatherFieldChanges(String pageName) {
           Collection<PersistentFieldChange> changes = new ArrayList<PersistentFieldChange>();
           String fullPrefix = persistenceType + SEPERATOR + pageName + SEPERATOR;
           for (Cookie cookie: getCookiesStartingWith(fullPrefix)) {
                   try {
                           PersistentFieldChange fieldChange = buildChange(cookie);
                           if (fieldChange != null) changes.add(fieldChange);
                           if (persistenceType.equals(FLASHCOOKIE)) deleteCookie(cookie.getName());
                   } catch (RuntimeException e) {
                           throw new RuntimeException("Error with cookie name: " + cookie.getName(),e);
           return changes;

   public void discardChanges(String pageName) {
       String fullPrefix = persistenceType + SEPERATOR + pageName + SEPERATOR;
       for (Cookie cookie: getCookiesStartingWith(fullPrefix)) {
    private List<Cookie> getCookiesStartingWith(String prefix) {
        List<Cookie> cookieList = new ArrayList<Cookie>();
        Cookie cookies[] = requestGlobals.getHTTPServletRequest().getCookies();
        if (cookies != null) for (int i = 0; i < cookies.length; i++) if (cookies[i].getName().startsWith(prefix)) cookieList.add(cookies[i]);
        return cookieList;
    private void createCookie(String name, String value) {
        Cookie cookie = new Cookie(name, value);
    private void deleteCookie(String name) {
        Cookie cookie = new Cookie(name, "_"); //'empty values may cause problems'

And the EncoderBase64 class ...

 * Created on 13 Apr 2008
package koncept.postback.base.encoders;


import org.apache.tapestry.*;
import org.apache.tapestry.internal.util.*;
import org.apache.tapestry.ioc.internal.util.*;

public class EncoderBase64 implements ValueEncoder<Object> {

        public Object toValue(String clientValue) {
        Object value = null;
        ObjectInputStream in = null;
        try {
            in = new Base64ObjectInputStream(clientValue);
            value = in.readObject();

        } catch (Exception e) {
            throw new RuntimeException("client state corrupted", e);
        } finally {
        return value;
         public String toClient(Object value) {
         Base64ObjectOutputStream os = null;
         try {
             os = new Base64ObjectOutputStream();
         } catch (Exception ex) {
             throw new RuntimeException(ex.getMessage(), ex);
         } finally {
         return os.toBase64();


There you go - an easy solution for



--nicholas Krul

Tapestry5HowToUsePersistentFieldStrategy (last edited 2009-09-20 23:20:19 by localhost)