Simple feedback form


Java, Spring, AngularJS, Material

External feedback form for a static site. This application includes a backend and frontend, so that it can be called both from an external site and from the current site. Frontend has a responsive layout for both desktop computers and mobile devices.
The backend uses java mail-sender library for messaging and provides the simple-captcha generation and checking. Three methods are available from frontend: captcha, checkCaptcha and finally sendFeedback, if two previous are passed.
The frontend is expected to use an ajax calls of the backend POST methods. This application uses the AngularJS http service. These calls require configuring of cross origin requests processing on both the backend and frontend.The expected algorithm of user's actions is as follows: first he has to fill the required fields (name, email and message), optionally he may want to add his website address and attach several files. After that, a field with a captcha image becomes available. The browser sends a GET request to the server's captcha endpoint. The server opens a session for this client with a 5 minutes timeout. When user enters the characters from the picture and clicks the send button, the browser sends the first POST request to checkCaptcha and, if it is passed, sends the second POST request to sendFeedback.There are described the main steps to configure this application. The entire code is available on GitHub. You can send feedback to the author using the demo version on Heroku.Frontend of this application (desktop and mobile) looks as follows:
Simple feedback form desktop
Simple feedback form mobile
Example of the incoming message:
Simple feedback form

Configure the application - is the main configuration file. It must be present in the resources folder of this application, or in the user's home folder. This is a template:
# Temp directory for attachments

# Comma-separated domain names,
# where this application runs
# and where it is called from,

# Mail server properties

# Letter box name and password

# User interface properties
form.title=Feedback form
form.homePageName=See GitHub
form.notice=Concerning any questions, comments and suggestions.

Deploy to heroku

You can download precompiled web application archive and deploy it to some server, such as heroku, with an additional file. To do this, you can use heroku-cli:
# check heroku-cli is downloaded successfully
heroku --version
# heroku/7.39.5 linux-x64 node-v12.16.2

# install java plugin
heroku plugins:install java
# Installing plugin java... installed v3.1.1

# deploy <war file> to <application name>
heroku war:deploy simple-feedback-form.war --app drakonoved --jdk 14 --includes
# Uploading simple-feedback-form.war
# -----> Packaging application...
# - app: drakonoved
# - including:
# - including: webapp-runner.jar
# - including: simple-feedback-form.war
# -----> Creating build...
# - file: slug.tgz
# - size: 30MB
# -----> Uploading build...
# - success
# -----> Deploying...
# remote:
# remote: -----> heroku-deploy app detected
# remote: -----> Installing JDK 14... done
# remote: -----> Discovering process types
# remote:        Procfile declares types -> web
# remote:
# remote: -----> Compressing...
# remote:        Done: 96.9M
# remote: -----> Launching...
# remote:        Released v3
# remote: deployed to Heroku
# remote:
# -----> Done


Pay attention to these points, before making any changes to the backend or frontend. This application uses custom libraries for sending mail and for the captcha generation and checking. These libraries should be built and installed into local Maven repository (.m2 folder by default) before building this application:


Configure cross origin requests processing and POST requests

If you plan to call this application from a server, other than the one on which it is running, then you should configure cross origin requests processing. It should be configured on both the backend and frontend.Configure the list of allowed origins:
public class WebMvcConfig implements WebMvcConfigurer {
    private String allowedOrigins;

    public void addCorsMappings(CorsRegistry registry) {
Configure the session cookie to include SameSite=None; Secure:
public class WebAppConfig implements WebApplicationInitializer {
    public void onStartup(ServletContext servletContext) {
                .addFilter("sessionRepositoryFilter", DelegatingFilterProxy.class)
                .addMappingForUrlPatterns(null, false, "/*");

    public MapSessionRepository sessionRepository() {
        final Map<String, Session> sessions = new ConcurrentHashMap<>();
        MapSessionRepository sessionRepository =
                new MapSessionRepository(sessions) {
                    public void save(MapSession session) {
                                .filter(entry -> entry.getValue().isExpired())
                                .forEach(entry -> sessions.remove(entry.getKey()));
        sessionRepository.setDefaultMaxInactiveInterval(60 * 5);
        return sessionRepository;

    public SessionRepositoryFilter<?> sessionRepositoryFilter(MapSessionRepository sessionRepository) {
        SessionRepositoryFilter<?> sessionRepositoryFilter =
                new SessionRepositoryFilter<>(sessionRepository);

        DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer();

        CookieHttpSessionIdResolver cookieHttpSessionIdResolver =
                new CookieHttpSessionIdResolver();


        return sessionRepositoryFilter;
Configure the AngularJS httpProvider to use credentials:

.config(['$httpProvider', function ($httpProvider) {
    $httpProvider.defaults.withCredentials = true;
AngularJS http service POST requests - the first to checkCaptcha and the second to sendFeedback:
scope.formFields = {
    userName: '',
    email: '',
    website: '',
    messageText: '',
    captchaText: '',
scope.sendingData = false;
scope.sendFeedback = function() {
    if (!scope.feedbackForm.$valid) return;

    scope.sendingData = true;

        url: '/checkCaptcha',
        method: "POST",
        params: {captchaText: scope.formFields.captchaText},
        headers: {'Content-Type': undefined },
    }).then(function doSendFeedback(response) {
            url: '/sendFeedback',
            method: "POST",
            params: scope.formFields,
            headers: {'Content-Type': undefined },
            data: formData,
        }).then((response) => successCallback(response),
            (response) => errorCallback(response));
        }, (response) => errorCallback(response));

    let successCallback = function(response) {
        scope.formFields.captchaText = '';
        scope.sendingData = false;
    let errorCallback = function(response) {
        scope.formFields.captchaText = '';
        scope.sendingData = false;
Privacy policy
Back to Top