How to allow cross origin requests in a JAX-RS micro service

Leejjon
5 min readAug 2, 2019

--

Earlier this year (2019) I started building a modular micro service with Jersey, the JAX-RS reference implementation. I wrote a few blog posts (1, 2, 3, 4 & 5) about it since then. In this post, we are going to create a simple index.html and try to fetch data from the Java backend, and see what we run into.

If you are only interested in reading how to allow cross origin requests in JAX-RS, scroll down.

Grabbing the template from last blog post

Make sure you have:

On github, do a git clone https://github.com/Leejjon/SimpleJerseyService and then switch to the 3.5.1 tag by running: git checkout tags/3.5.1

Verify that it works by running mvn clean install exec:exec and visit http://localhost:8080/person to see: {"dateOfBirth":"1989-01-01T00:00:00Z[UTC]","firstName":"Leon"}

Character encoding and javax.ws.core.Response

Something you will notice when visiting http://localhost:8080 in Firefox is a console message about character encoding:

So if we take a look at our method that returns “Got it!” in MyResource.java:

@GET
@Produces(MediaType.TEXT_PLAIN)
public String getIt() {
return "Got it!";
}

We can use the javax.ws.rs.core.Response object to craft custom responses to solve this problem:

@GET
@Produces(MediaType.TEXT_PLAIN)
public Response getIt() {
return Response.ok().entity("Got it!").encoding("UTF-8").build();
}

We could wrap the Person object in a Response object in the getPerson() call:

@GET
@Path("person")
@Produces(MediaType.APPLICATION_JSON)
public Response getPerson() {
Person p = new Person();
p.setFirstName("Leon");
LocalDate birthDay = LocalDate.of(1989, 1, 1);
ZonedDateTime birthDayZdt = birthDay.atStartOfDay(ZoneId.of("UTC"));
Date birthDayDate = Date.from(birthDayZdt.toInstant());
p.setDateOfBirth(birthDayDate);

return Response.ok().entity(p).build();
}

Adding Response object makes WADL crash

When visiting the WADL url (http://localhost:8080/application.wadl) you will see this error:

FINE: service exception
javax.ws.rs.ProcessingException: Error generating /application.wadl.
at jersey.server@2.29/org.glassfish.jersey.server.wadl.internal.WadlResource.getWadl(WadlResource.java:105)
Caused by: java.lang.reflect.InaccessibleObjectException: Unable to make protected javax.ws.rs.core.Response() accessible: module java.ws.rs does not "opens javax.ws.rs.core" to module je
rsey.server
at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:340)

Either turn off WADL:

final ResourceConfig rc = new ResourceConfig();
rc.property("jersey.config.server.wadl.disableWadl", true);

Or if you want to keep using WADL, don’t use the Response object and revert it to the old way.

Fetching data from a frontend

So let’s create the simplest frontend possible, an index.html with the following html:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script>
function logPerson() {
fetch('http://localhost:8080/person')
.then(function (response) {
return response.json();
})
.then(function (myJson) {
console.log(JSON.stringify(myJson));
});
}
</script>
</head>
<body>
<button onclick="logPerson()">Log Person</button>
</body>
</html>

Simple create a file anywhere on your hard drive and open it with chrome while the SimpleJerseyService is running (use mvn clean install exec:execagain to launch it)

When you click on the “Log Person” button get this CORS error:

Cross Origin Resource Sharing

This is blocked by a Cross Origin Resource Sharing policy. CORS is a mechanism that was added to most browsers in 2014 to protect sites and users against cross site scripting (which was one of the most dangerous vulnerabilities in websites, see this myspace example). What this means is that FireFox will try to do a call to http://localhost:8080/person, but because it doesn’t find CORS headers in the reply from the java backend, it will think it is not allowed because index.html is not on the same domain (actually, since we are running from the hard drive, index.html has no domain at all, you can see it’s null in the browser message).

On production I use Google App Engine. If I would have a frontend that calls the backend, I would run a static frontend on the App Engine nodejs10 runtime, and the backend on the App Engine java11 runtime. Using a dispatch.yaml I could run both on the same domain, and thus I would not have this CORS errors.

But locally, we have this problem. What we want is our Java backend to include CORS headers in the requests to allow fetching data from any domain ONLY when running locally.

Running in local or production mode

I have already changed my Main.java so that if the first argument of the main method is “LOCAL”, it will pass a boolean to the startServer method telling to run locally.

    /**
* Main method.
*
@param args First value should contain "LOCAL" to start with CORS disabled.
*/
public static void main(String[] args) {
boolean local = false;
if (args.length > 0 && args[0].contains("LOCAL")) {
local = true;
}

startServer
(local);
}

The startServer method will then use this boolean to decide whether use the local or production hostname and port.

Adding a response filter to add the CORS headers

We can modify the javax.ws.rs.core.Response object of each request to include CORS headers like this:

return Response.ok()
.entity(p)
.header("Access-Control-Allow-Origin", "*")
.header("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT")

But that is a lot of code for something that only needs to happen when running locally. Therefore I added the following filter to the startServer() method:

if (local) {
final String HEADERS = "Origin, Content-Type, Accept";
final String ALLOW_ORIGIN = "Access-Control-Allow-Origin";
final String ALLOW_HEADERS = "Access-Control-Allow-Headers";
final String ALLOW_METHODS = "Access-Control-Allow-Methods";

rc.register(new ContainerResponseFilter() {
@Override
public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) throws IOException {
responseContext.getHeaders().add(ALLOW_ORIGIN, "*");
responseContext.getHeaders().add(ALLOW_HEADERS, HEADERS);
responseContext.getHeaders().add(ALLOW_METHODS, "GET, POST, PUT, DELETE, OPTIONS, HEAD");
}
});
}

When running mvn clean install exec:exec again with this filter enabled, you no longer have the CORS errors and the index.html nicely prints the person object retrieved from our backend:

Check out the full source code on Github.

--

--

Leejjon
Leejjon

Written by Leejjon

Java/TypeScript Developer. Interested in web/mobile/backend/database/cloud. Freelancing, only interested in job offers from employers directly. No middle men.