Firstly, create a servlet.
public class AudioCaptchaServlet extends HttpServlet {
    /**
     * Serial uid.
     */
    private static final long serialVersionUID = 1L;
    /**
     * Logger.
     */
    private static Log logger = LogFactory.getLog(AudioCaptchaServlet.class);
    private static final SoundCaptchaService AUDIO_CAPTCHA_SERVICE =
        AudioCaptchaService.getInstance();
    /**
     * Init.
     */
    @Override
    public void init(ServletConfig servletConfig) throws ServletException {
        super.init(servletConfig);
        logger.info("Initalizing AudioCaptchaServlet...");
    }
    /**
     *
     */
    @Override
    protected void doGet(HttpServletRequest request,
            HttpServletResponse response) throws ServletException, IOException {
        logger.info("Generating sound captcha for session id "
                + request.getSession().getId());
        AudioInputStream challenge = null;
        try {
            // get the session id that will identify the generated captcha.
            // the same id must be used to validate the response, the session id
            // is a good candidate!
            String captchaId = request.getSession().getId();
            // call the AudioCaptchaService getChallenge method
            challenge = AUDIO_CAPTCHA_SERVICE.getSoundChallengeForID(captchaId);
        } catch (IllegalArgumentException e) {
            logger.error("Illegal argument exception occured.", e);
            throw new RuntimeException(e);
        } catch (CaptchaServiceException e) {
            logger.error("Captcha service exception occured.", e);
            throw new RuntimeException(e);
        } catch (Exception e) {
            logger.error("An exception occured.", e);
            throw new RuntimeException(e);
        }
        response.setDateHeader("Expires", 0);
        response.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
        response.addHeader("Cache-Control", "post-check=0, pre-check=0");
        response.setHeader("Pragma", "no-cache");
        response.setContentType("audio/wav");
       
        ServletOutputStream out = response.getOutputStream();
        ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();
        AudioSystem.write(challenge,
                javax.sound.sampled.AudioFileFormat.Type.WAVE,
                byteOutputStream);
        out.write(byteOutputStream.toByteArray());
        out.flush();
        out.close();
       
        logger.info("Generating sound captcha completed");
    }
}
Secondly, add the servlet configuration in web.xml.
    <servlet>
        <servlet-name>Audio Captcha Servlet</servlet-name>
        <servlet-class>myapp.servlet.AudioCaptchaServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>Audio Captcha Servlet</servlet-name>
        <url-pattern>/jcaptcha.wav</url-pattern>
    </servlet-mapping>
Thirdly, add the sound captcha in jsp.
<a href="myapp/jcaptcha.wav"><img src="myapp/resources/images/speaker1.gif" alt="Sound Captcha" /></a>
Everything seemed to be working together nicely so far. I was able to see the sound captcha icon once the web application is deployed. However, when I clicked the icon, it did not play the sound and an error message was found in the log.
Trouble while processing utterance java.lang.IllegalStateException: Syllable relation has already been set.
I googled around and felt lucky to found this page. I downloaded FreeTTS 1.2.2 source release, updated Voice.java as below, and rebuilt the binaries using maven.
    public void deallocate() {
    setLoaded(false);
  //TODO consider removing utteranceProcessors.clear();
  utteranceProcessors.clear();
        if (!externalAudioPlayer) {
            if (audioPlayer != null) {
                audioPlayer.close();
                audioPlayer = null;
            }
        }
       
    if (!externalOutputQueue) {
        //TODO Consider removing comment
        //outputQueue.close();
    }
    }
Once I replaced FreeTTS with the new binary in the web app, everything worked as expected.
In retrospect, it was still a mystery why I had to customize Voice.java to make FreeTTS work sound jCaptcha in servlet. In addition, I was not a big fan that there is no out-of-box support for image and sound captcha to use the same characters. But I was happy that I managed to get jCaptcha work to meet the requirements. This was quite a learning experience.