March 7, 2019
Pudding Is Tofu’s Greatest Flex
douhua_LEAD

The bean-curd-based pudding is a silky, smooth foundation for chewy taro balls, mushy red beans, fragrant almond syrups, and much more.

The great bean curd is like a chameleon, adapting to whatever environment it’s thrown into. Its neutral base flavor allows it to take on the characteristics of whatever it’s cooked in, from a mouth-numbing mapo tofu made with fermented bean paste and chile oil to a steaming bowl of soondubu jiggae, heavy on anchovy broth and kimchi. The same characteristics that make it great for soaking up oceany broths and brash spice make it a great mild dessert to eat by the spoon, slightly sweetened and tinged with soft floral or herbal flavors.

Known as dau fu fa in Cantonese, tofu pudding is a smooth, silky treat often drizzled with a ginger-infused syrup. It’s served everywhere from street-side carts doling them out in plastic soup containers to New Age Instagram-friendly dessert parlors for millennials. And if you’re lucky, a legit dim sum hall will have its own dedicated dau fu fa cart. A warm bowl of tofu pudding serves as a satisfying snack across all accounts. Although it contains no eggs or dairy, the consistency of tofu pudding is akin to silken tofu or panna cotta. Soy milk is combined with a coagulant (like gypsum powder) over heat, then transferred to the fridge to set into a soft, springy texture. In a traditional Cantonese iteration, once solidified, the dau fu fa gets spooned into smaller bowls with ginger syrup poured on top. With each spoonful, the tofu’s delicate texture melts in your mouth alongside a rich, herbal sweetener.

But it doesn’t stop there. If you treat it like a bowl of plain jook (congee), it serves as a blank canvas—shaped with toppings like chewy boba or hearty steamed sweet potato. In the Philippines, the tofu is served with a brown-sugar syrup and sago pearls, while Taiwanese versions feature mung bean, peanuts, sweetened red bean, taro balls, or almond syrup, and in Thailand, the garnish of choice is fresh fruit. Lighter on sugar, most of these toppings and subtle syrups present a more nuanced flavor to focus on the pudding’s silky texture. Plus, it works equally as well served warm, cold (sometimes over ice), or at room temperature.

For a sublime dau fu fa experience at home, using homemade soy milk (or seeking out a homemade bottle in Chinatown) is the key to mastering a smooth texture. Store-bought soy milks under names like Soy Dream and Earth Balance have additives that both take away from the pure taste and contribute to an inconsistent texture when setting in the fridge. And it’s not as fussy as you might think. You soak the soybeans overnight, blend them with water, boil the mixture, and then finally strain the liquid. Once you combine it with agar (an odorless, tasteless coagulant) over heat, the rest is just a matter of cooling down and waiting for it to solidify in the fridge.

Each batch of dau fu fa that sits in my fridge is an opportunity to experiment with something different, like pandan or chrysanthemum tea-infused syrup, or to merely use up whatever scraping of red bean paste is left in the can. Either way, it’s a simple pleasure each time.

Ingredients

  • 4 ounces soy beans
  • 2 quarts water, for soy milk
  • Sugar to taste
  • ½ cup water, for boiling
  • 1 tablespoon agar
  • Ginger Syrup
  • ½ cup water
  • ½ cup sugar
  • 1 inch ginger, peeled and chopped

The key to a great dau fu fa is rich, homemade soy milk. As long as you have a blender and something to mimic cheesecloth (like an old T-shirt), homemade soy milk (or any other nut milk for that matter) is not too far out of reach. You can replace the ginger syrup with another infused flavor of your liking (almond works very well), and top with chopped fresh fruit, red bean paste, ice cream, or whatever else you enjoy.

  1. To make the soy milk, rinse and soak the beans overnight (or for at least eight hours) in water. The next day, rinse again and drain.
  2. Add the soaked soybeans and 2 quarts water to a blender. Blend until most visible chunks are gone. (Whatever is left will be strained out.)
  3. Once blended, pour the soybean-water mixture into a large pot and boil over medium heat. Stir and lower heat as needed so the milk does not burn. Once it boils, reduce to a simmer and allow soy milk to thicken for about 40 minutes.
  4. Using cheesecloth, strain out the soybean remnants and squeeze out any liquids into a medium-size bowl. Add sugar to taste. Rinse the large pot and bring the strained soy milk back to a boil.
  5. In another large pot, boil the ½ cup water. Once boiling, add the agar and stir until dissolved. Add the soy milk to the water-agar mixture, and heat until boiling.
  6. Remove from heat when boiling, and strain the mixture into a large bowl.
  7. Allow to cool, then set in fridge, covered, for at least two hours.
  8. While waiting for the pudding to set, prepare the ginger syrup like a simple syrup.
  9. To make the ginger syrup, bring ½ cup water to a boil, then add sugar.
  10. After the sugar is dissolved, add chunks of ginger, then remove from heat. Allow the ginger to sit in the syrup for 30-60 minutes. Remove ginger chunks from syrup when ready to serve.
  11. Once the tofu is set, spoon into individual bowls and top with syrup. Depending on your sweetness preference, you can water down the syrup to be thinner. Can be served cold or at room temperature.

Tatiana Bautista

Tatiana Bautista is an assistant editor at TASTE.

[email_signup id="3"]
<div class="email-subscribe exp-subscribe subscribe"> <div class="alert alert-success exp-success ga-etembed-success" style="display:none;"> Great! You've signed up successfully! </div> <div class="alert alert-danger exp-error" style="display:none;"> We are experiencing an error, please try again. </div> <form name="subscribe" id="email-signup" class="email-signup-form" action="https://www.tastecooking.com/wp-admin/admin-ajax.php" method="POST" target="_top" data-event-assigned="false"> <fieldset class="form-group form-inline email-submit-group"> <label for="email-address" class="sr-only">Email Address</label> <input id="email-newsletter" type="email" placeholder="your@email.com" class="email-newsletter text email form-control" name="email" value=""> <button type="submit" value="Subscribe" name="subscribe" class="btn btn-primary email-submit-btn ga-etembed-success"> <i class="fa fa-envelope"></i> Subscribe </button> </fieldset> <small class="exp-small terms" style=""> By clicking Subscribe, I acknowledge that I have read and agree to the Penguin Random House <a href="http://www.penguinrandomhouse.com/privacy/" target="_blank">Privacy Policy</a> and <a href="http://www.penguinrandomhouse.com/terms/" target="_blank">Terms of Use</a> and agree to receive news and updates from TASTE and Penguin Random House. </small> <fieldset class="field-group checkboxes" style="display:none"> <div class="checkbox"> <label> <input type="checkbox" name="programs[]" value="505,50501,1" class="exp-checkbox" checked="checked"> <span><strong>Tastebook General</strong></span> </label> </div> <div class="checkbox"> <label> <input type="checkbox" name="programs[]" value="505,50502,1" class="exp-checkbox" checked="checked"> <span><strong>Tastebook Promo</strong></span> </label> </div> </fieldset><!-- /.checkboxes --> <input type="hidden" name="data_acq_code" value="TASTE_POPUP_STATIC_ONGOING" /> <input type="hidden" name="data_programs" value="50501|505,50502|505" /> <input type="hidden" name="form_id" value="3" /> <input type="hidden" name="partner" value="" /> <input type="hidden" name="action" value="crown_email_signup_submit" /> <input type="hidden" name="ref_url" value="" /> </form> </div> <script> jQuery(document).ready(function () { var unassigned_forms; setInterval( function() { //Check for unassigned forms var forms = jQuery( '.email-signup-form' ); unassigned_forms = false; forms.each( function( index ) { if ( jQuery( this ).attr( 'data-event-assigned' ) === 'false' ) { unassigned_forms = true; } }); if ( unassigned_forms ) { forms.off( 'submit' ).on( 'submit', function ( e ) { forms.attr( 'data-event-assigned', 'true' ); e.preventDefault(); // Basic email validation var email = jQuery(e.target).find('.email-newsletter').val(), valid_email = false, groupEl = jQuery(e.target).closest('.email-subscribe'); if ( !!email && typeof(email) === "string" ) { var email_array = email.split('@'); if ( email_array.length === 2 ) { if ( email_array[1].split('.').length > 1 ) { valid_email = true; } } } if ( valid_email ) { jQuery('.alert-danger').hide(); // Show loading gif groupEl.find('.email-signup-form').hide(); groupEl.find('.email-submit-loading').show(); // Populate ref_url jQuery(this).find('input[name="ref_url"]').val(window.location.href); // Generate Payload var payload = jQuery(this).serializeArray(); jQuery.ajax({ url: jQuery(this).attr('action'), type: 'POST', data: payload, success: function (data) { // Hide loading spinner groupEl.find('.email-signup-form').show(); groupEl.find('.email-submit-loading').hide(); // Display success msg var target = jQuery(e.target); jQuery(e.target).hide(); jQuery(e.target).parent().find('.alert-success').show(); jQuery(e.target).parent().find('#downloadbtn').show(); }, fail: function (err) { // Hide loading spinner groupEl.find('.email-signup-form').show(); groupEl.find('.email-submit-loading').hide(); // Display failure msg var target = jQuery(e.target); jQuery(e.target).hide(); jQuery(e.target).parent().find('.alert-danger').show(); } }); } else { groupEl.find('.alert-danger').html('Please enter a valid email address.').show(); } }); forms.off( 'keypress' ).on( 'keypress', function ( e ) { if ( e.which === 13 ) { e.preventDefault(); jQuery( this ).submit(); } }); } }, 100); }); </script>
[email_signup id="3"]
<div class="email-subscribe exp-subscribe subscribe"> <div class="alert alert-success exp-success ga-etembed-success" style="display:none;"> Great! You've signed up successfully! </div> <div class="alert alert-danger exp-error" style="display:none;"> We are experiencing an error, please try again. </div> <form name="subscribe" id="email-signup" class="email-signup-form" action="https://www.tastecooking.com/wp-admin/admin-ajax.php" method="POST" target="_top" data-event-assigned="false"> <fieldset class="form-group form-inline email-submit-group"> <label for="email-address" class="sr-only">Email Address</label> <input id="email-newsletter" type="email" placeholder="your@email.com" class="email-newsletter text email form-control" name="email" value=""> <button type="submit" value="Subscribe" name="subscribe" class="btn btn-primary email-submit-btn ga-etembed-success"> <i class="fa fa-envelope"></i> Subscribe </button> </fieldset> <small class="exp-small terms" style=""> By clicking Subscribe, I acknowledge that I have read and agree to the Penguin Random House <a href="http://www.penguinrandomhouse.com/privacy/" target="_blank">Privacy Policy</a> and <a href="http://www.penguinrandomhouse.com/terms/" target="_blank">Terms of Use</a> and agree to receive news and updates from TASTE and Penguin Random House. </small> <fieldset class="field-group checkboxes" style="display:none"> <div class="checkbox"> <label> <input type="checkbox" name="programs[]" value="505,50501,1" class="exp-checkbox" checked="checked"> <span><strong>Tastebook General</strong></span> </label> </div> <div class="checkbox"> <label> <input type="checkbox" name="programs[]" value="505,50502,1" class="exp-checkbox" checked="checked"> <span><strong>Tastebook Promo</strong></span> </label> </div> </fieldset><!-- /.checkboxes --> <input type="hidden" name="data_acq_code" value="TASTE_POPUP_STATIC_ONGOING" /> <input type="hidden" name="data_programs" value="50501|505,50502|505" /> <input type="hidden" name="form_id" value="3" /> <input type="hidden" name="partner" value="" /> <input type="hidden" name="action" value="crown_email_signup_submit" /> <input type="hidden" name="ref_url" value="" /> </form> </div> <script> jQuery(document).ready(function () { var unassigned_forms; setInterval( function() { //Check for unassigned forms var forms = jQuery( '.email-signup-form' ); unassigned_forms = false; forms.each( function( index ) { if ( jQuery( this ).attr( 'data-event-assigned' ) === 'false' ) { unassigned_forms = true; } }); if ( unassigned_forms ) { forms.off( 'submit' ).on( 'submit', function ( e ) { forms.attr( 'data-event-assigned', 'true' ); e.preventDefault(); // Basic email validation var email = jQuery(e.target).find('.email-newsletter').val(), valid_email = false, groupEl = jQuery(e.target).closest('.email-subscribe'); if ( !!email && typeof(email) === "string" ) { var email_array = email.split('@'); if ( email_array.length === 2 ) { if ( email_array[1].split('.').length > 1 ) { valid_email = true; } } } if ( valid_email ) { jQuery('.alert-danger').hide(); // Show loading gif groupEl.find('.email-signup-form').hide(); groupEl.find('.email-submit-loading').show(); // Populate ref_url jQuery(this).find('input[name="ref_url"]').val(window.location.href); // Generate Payload var payload = jQuery(this).serializeArray(); jQuery.ajax({ url: jQuery(this).attr('action'), type: 'POST', data: payload, success: function (data) { // Hide loading spinner groupEl.find('.email-signup-form').show(); groupEl.find('.email-submit-loading').hide(); // Display success msg var target = jQuery(e.target); jQuery(e.target).hide(); jQuery(e.target).parent().find('.alert-success').show(); jQuery(e.target).parent().find('#downloadbtn').show(); }, fail: function (err) { // Hide loading spinner groupEl.find('.email-signup-form').show(); groupEl.find('.email-submit-loading').hide(); // Display failure msg var target = jQuery(e.target); jQuery(e.target).hide(); jQuery(e.target).parent().find('.alert-danger').show(); } }); } else { groupEl.find('.alert-danger').html('Please enter a valid email address.').show(); } }); forms.off( 'keypress' ).on( 'keypress', function ( e ) { if ( e.which === 13 ) { e.preventDefault(); jQuery( this ).submit(); } }); } }, 100); }); </script>