Update, September 2026: AngularJS reached its end of life on December 31, 2021 and gets no more security fixes, so don’t start a new project with it. The back end half of this tutorial still holds. A Struts action that returns JSON is exactly what an Angular, React or Vue front end needs today. At the end of this post you’ll find what changed on the Struts side since 2015.

This post shows how simple it is to create a new web application based on the client side JavaScript framework AngularJS and the server side Java framework Struts2.

Why this Struts2 – AngularJS framework combination

Struts2 is an MVC framework to build server side Java web applications. Struts2 is stable, easy to use, and has been actively developed and maintained for years. AngularJS is a modern client side MVC framework created and maintained by Google. Together they make a solid base for web applications. You can mix single-page with multi-page web applications like multi-page for user sites and single-page for administration sites. Struts2 can be used for back-end actions as JSON data provider, for file-uploads, streaming actions, security, text provider and more. And of course with the flexible structure of Interceptor Stacks and namespaces you are able to develop extendable good scalable software. AngularJS, on the other hand, is one of the most widely used front-end frameworks with good tutorials and documentation. The only disadvantage of this combination is you have to master both Java and JavaScript.

Create initial project structure

To create the initial project structure we are using the widely used build tool Maven. For details about how to setup and install maven please read following blog post 4 Easy Steps to create a Java based Web Application with Struts2 and jQuery.

mvn archetype:generate -B -DgroupId=com.jgeppert.samples \
    -DartifactId=struts2-angularjs \
    -DarchetypeGroupId=org.apache.struts \
    -DarchetypeArtifactId=struts2-archetype-angularjs \
    -DarchetypeVersion=6.0.0
Code language: Bash (bash)

The Struts2 parts

Let’s take a look at the generated Struts2 parts. First we have the struts.xml in which we can configure our Struts2 application like namespaces, interceptors and other defaults. In our created project structure these struts.xml is minimal because we are using the Struts2 Convention Plugin to define our actions via conventions or annotations and most defaults are fine for the start.

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
	"-//Apache Software Foundation//DTD Struts Configuration 2.5//EN"
	"http://struts.apache.org/dtds/struts-2.5.dtd">
<struts>

  <constant name="struts.enable.DynamicMethodInvocation" value="false"/>
  <constant name="struts.devMode" value="true"/>

  <constant name="struts.convention.default.parent.package" value="angularstruts"/>
  <package name="angularstruts" extends="json-default">
    <default-action-ref name="index" />
  </package>
  <package name="data" extends="angularstruts" namespace="/data"></package>

</struts>Code language: HTML, XML (xml)

This initial project structure includes two Struts2 actions. The first one is the Index action class which is the entry point for this application. According to the Struts2 Convention plugin defaults, the corresponding JSP page is located under WEB-INF/content/index.jsp.

<!DOCTYPE html>
<%@ page contentType="text/html; charset=UTF-8" %>
<%@ taglib prefix="s" uri="/struts-tags" %>
<html lang="en" ng-app="app">
<head>
    <meta charset="utf-8">
    <title>My AngularJS Struts2 App</title>

    <base href="<s:url forceAddSchemeHostAndPort="true" includeContext="true" value="/" namespace="/" />">
</head>
<body>

<h2><s:property value="message"/></h2>

<div>
    <a href="/home">Home</a> - <a href="/projects">Projects</a>
</div>

<div ng-controller="AppController as app">
    <div ng-view></div>
</div>

<s:if test="useMinifiedResources">
    <script src="<s:url value="js/external.js" />"></script>
    <script src="<s:url value="js/application.js" />"></script>
</s:if>
<s:else>
    <script src="<s:url value="js/lib/angular/angular.min.js" />"></script>
    <script src="<s:url value="js/lib/angular/angular-route.min.js" />"></script>
    <script src="<s:url value="js/app.js" />"></script>
    <script src="<s:url value="js/config.js" />"></script>
    <script src="<s:url value="js/services/DataService.js" />"></script>
    <script src="<s:url value="js/controllers/AppController.js" />"></script>
    <script src="<s:url value="js/controllers/HomeController.js" />"></script>
    <script src="<s:url value="js/controllers/ApacheProjectsController.js" />"></script>
</s:else>
</body>
</html>
Code language: HTML, XML (xml)

This JSP is the entry point for the AngularJS part. In the head section we just define the base context of our application and at the end of the JSP we define all JavaScript resources we need.

The next Action, named ProjectsAction in the actions.data package, with the @Result annotation of type json marks this action as a JSON action. This action provides the frontend a small lists of sample Apache projects in the JSON format.

@Result(type = "json")
public class ProjectsAction extends ActionSupport {

    private static final long serialVersionUID = 9037336532369476225L;
    private static final Logger log = LogManager.getLogger(ProjectsAction.class);

    private List projectNames;

    public String execute() throws Exception {

        projectNames = new ArrayList();
        projectNames.add("Apache Struts");
        projectNames.add("Apache Log4j");
        projectNames.add("Apache Tomcat");
        projectNames.add("Apache Maven");
        projectNames.add("Apache Ant");
        projectNames.add("Apache Log4Net");
        projectNames.add("Apache Log4Cxx");
        projectNames.add("Apache Chainsaw");
        projectNames.add("Apache Incubator");
        projectNames.add("Apache Hadoop");
        projectNames.add("Apache OpenOffice");
        projectNames.add("Apache Mahout");
        projectNames.add("Apache Tapestry");
        projectNames.add("Apache Jena");
        projectNames.add("Apache Solr");
        projectNames.add("Apache Cayenne");
        projectNames.add("Apache OpenEJB");
        projectNames.add("Apache Deltaspike");
        projectNames.add("Apache Cordova");

        log.debug("Return {} Apache projects", projectNames.size());

        return SUCCESS;
    }

    public List getProjectNames() {
        return projectNames;
    }
}
Code language: Java (java)

The AngularJS parts

To step into the frontend part, we can take a look at the generated javascript resources for the app app.js and config.js, DataService.js and the controllers.

app.js

The file app.js contains the initial configuration of our AngularJS application. First we define our module app with a dependency to ngRoute module, which manages the url mapping.

(function() {
    'use strict';

    angular
        .module('app', ['ngRoute']);
})();
Code language: JavaScript (javascript)

config.js

With the $locationProvider we can setup our application to use the HTML5 mode URL’s without the # sign symbol. And with the $routeProvider (provided by the ngRoute module) we define our mapping between URL, JavaScript controllers (defined in the controller.js) and the html partials which should be shown.

(function() {
    'use strict';

    angular
    .module('app')
        .config(['$routeProvider', '$locationProvider',
            function($routeProvider, $locationProvider) {

                $locationProvider.html5Mode(true).hashPrefix('!');

                $routeProvider.when('/projects', {
                    templateUrl: 'partials/projects.html',
                    controller: 'ApacheProjectsController as vm'
                }).when('/home', {
                    templateUrl: 'partials/home.html',
                    controller: 'HomeController as vm'
                }).otherwise({ redirectTo: '/home' });
            }
        ]);
})();
Code language: JavaScript (javascript)

DataService.js

The folder js/services contains the services we need in our frontend. In the generated application it only contains one DataService.js which handles the request to the Project Action. This service contains a general asynchron request handling with promises.

(function() {
    'use strict';

    angular
        .module('app')
        .factory('DataService', DataService);

    function DataService($http, $log, $q) {

        /** Object to manage all backend URL's */
        var urls = {
            projects : "data/projects"
        };

        /** The DataService with all public methods */
        var service = {
            getProjects: getProjects
        };

        return service;

        /** Get all projects */
        function getProjects() {
            return _request(urls.projects);
        }


        /** A generic helper method to execute a HTTP request to the backend */
        function _request(url, method, model){
            var def = $q.defer(),
                req = {
                method: method,
                url: url
            };

            if(!method) {
                req.method = 'GET';
            }

            if(model) {
                req.data = model;
            }
            $http(req).success(function(data) {
                def.resolve(data);
            }).error(function(data, code) {
                def.reject(data);
                $log.error(data, code);
            });
            return def.promise;
        }

    }

})();
Code language: JavaScript (javascript)

Controllers

In the folder controllers we can find all AngularJS controllers we currently use in the application.

AppController

In the AppController.js file the main entry point of the application is defined. In this example the AppController does not contain any logic.

(function() {
    'use strict';

    angular
        .module('app')
        .controller('AppController', AppController);

    function AppController() {

    }
})();
Code language: JavaScript (javascript)

HomeController

In the file HomeController.js the view for the initial home page is defined and the scope variable vm.name is initialized with the value Sunshine.

(function() {
    'use strict';

    angular
        .module('app')
        .controller('HomeController', HomeController);

    function HomeController() {
        var vm = this;
        vm.name = "Sunshine";
    }
})();
Code language: JavaScript (javascript)

ApacheProjectsController

The ApacheProjectsController.js is using the DataService which is defined in the services. This Data Service retrieves the JSON array of Apache projects from the Struts2 backend and assign this value to the vm.projects variable.

(function() {
    'use strict';

    angular
        .module('app')
        .controller('ApacheProjectsController', ApacheProjectsController);

    function ApacheProjectsController($log, DataService) {
        var vm = this;

        init();

        function init() {
            return DataService.getProjects().then(function(data) {
                vm.projects = data.projectNames;
                return vm.projects;
            }, function() {
                $log.error('Could not receive project names.');
            });
        }
    }
})();
Code language: JavaScript (javascript)

The AngularJS view part

In the webapp/partials folder we find two partials one is the home.html and the other is finally the projects.html partials which generates the HTML by iterating over the vm.projects variable, we defined in the ApacheProjectsController, with the AngularJS ng-repeat directive.

<ul>
    <li ng-repeat="project in vm.projects">{{project}}</li>
</ul>Code language: HTML, XML (xml)

What’s next?

With this basic setup we are able to simple create new Struts2 backend actions to provide the data we need in the frontend. Next steps could be:

  • Handle internationalization (i18n) in Struts2 and AngularJS
  • Validation and error handling
  • Resource managing (concatenation and minify of our javascript resources)
  • Use Struts2 REST plugin for backend actions

What changed since 2015

AngularJS is gone. The split this tutorial is built on still works: Struts delivers JSON, the browser renders it. Point an Angular, React or Vue front end at the same action and the back end stays as it is.

Struts 7 needs Java 17 and the Jakarta namespace. Servlet imports move from javax.servlet to jakarta.servlet, and the com.opensymphony.xwork2 package no longer exists. ActionSupport now lives in org.apache.struts2. The projects action from above looks like this today:

import org.apache.struts2.ActionSupport;
import org.apache.struts2.convention.annotation.Result;

import java.util.List;

@Result(type = "json")
public class ProjectsAction extends ActionSupport {

    private List<String> projectNames;

    @Override
    public String execute() {
        projectNames = List.of("Apache Struts", "Apache Tomcat", "Apache Maven");
        return SUCCESS;
    }

    public List<String> getProjectNames() {
        return projectNames;
    }
}Code language: Java (java)

Request parameters need @StrutsParameter. Struts 7 enables struts.parameters.requireAnnotations by default. A request parameter only reaches a setter that carries the annotation; all others are ignored. The read-only action above doesn’t need it, but the first form that posts data back will:

import org.apache.struts2.interceptor.parameter.StrutsParameter;

@StrutsParameter
public void setName(String name) {
    this.name = name;
}Code language: JavaScript (javascript)

The archetype stops at Struts 6. struts2-archetype-angularjs was last released as 6.0.0. For Struts 7, set the project up by hand with struts2-core, struts2-convention-plugin and struts2-json-plugin in the current 7.x release (7.3.0 at the time of writing).

Adding a database. The JSON action is where data access belongs. Load the records from your repository in execute() and expose them through a getter; the JSON result serializes what the getters return. The browser only ever sees JSON.

AngularJS with Java: A Web App on a Struts 2 JSON Backend
Tagged on:                             

One thought on “AngularJS with Java: A Web App on a Struts 2 JSON Backend”

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.