Java 8, Tomcat 8, and Jersey on Mac OS X

A while back, I wrote a blog post on how to quickly develop and deploy a Web app in Java, using RestEasy on top of Tomcat. While this worked well, I never felt really comfortable with the rest-easy dependencies. Moreover, since I wanted a really lean web-app, manually extracting only the absolutely necessary jars from the rather large RestEasy project seemed a bit of a hack. Time for a reboot.

This time, I’m using Jersey, the open source Web Services, providing support for JAX-RS APIs. Jersey is also the JAX-RS (JSR 311 & JSR 339) reference implementation and can be found at https://jersey.java.net

1. Java 8

1st stop is at Oracle.com, to pick up the latest Java 8 JDK. As I’m writting this, Java SE Development Kit 8u45 downloads as jdk-8u45-macosx-x64.dmg into my ~/Downloads folder. Opening the dmg file and running the installer, copies the Java VM and Runtime into

/Library/Java/JavaVirtualMachines/jdk1.8.0_45.jdk

and to make sure that this new Java version is used, I’m editing the hidden

~/.bash_profile

file (TextMate for instance, will edit this file, if you enter mate ~/.bash_profile). JAVA_HOME is an important environment variable, not just for Tomcat, and it’s important to get it right. Here is a trick that allows me to keep the environment variable current, even after a Java Update was installed. In ~/.bash_profile, I set the variable like so:

export JAVA_HOME=$(/usr/libexec/java_home)

and after closing and re-opening the terminal, java -version should respond like so:

$ java -version
java version "1.8.0_45"
Java(TM) SE Runtime Environment (build 1.8.0_45-b14)
Java HotSpot(TM) 64-Bit Server VM (build 25.45-b02, mixed mode)

2. Tomcat 8

Next stop is Apache.org, to pick up the latest Tomcat distribution. The current version for Tomcat is 8.0.23 and I’m downloading the Binary Distributions – Core tar.gz file.
The next few steps include uncompressing the archive, followed by moving its files into /Library/Tomcat, and changing their ownership and permissions.

cd ~/Downloads/ 
tar -zxvf ./apache-tomcat-8.0.23.tar.gz
sudo mkdir -p /usr/local
sudo mv ~/Downloads/apache-tomcat-8.0.23 /usr/local
sudo rm -f /Library/Tomcat
sudo ln -s /usr/local/apache-tomcat-8.0.23 /Library/Tomcat
sudo chown -R <your_username> /Library/Tomcat
sudo chmod +x /Library/Tomcat/bin/*.sh

(I have written in much more detail about those steps here.

Now it’s time to install Activata’s Tomcat Controller, a tiny freeware app, providing a UI to quickly start/stop Tomcat. Launch it, but click the Start button only after entering /Library/Tomcat for its Tomcat Home Directory preference.
Clicking View loads Tomcat’s default home page in your browser.

Almost done with Tomcat. Let’s fix some settings, while we are here. To access Tomcat’s manager-gui and admin-gui, a username and password need to be entered into /Library/Tomcat/conf/tomcat-users.xml

<?xml version='1.0' encoding='utf-8'?>
<tomcat-users xmlns="http://tomcat.apache.org/xml"
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xsi:schemaLocation="http://tomcat.apache.org/xml tomcat-users.xsd"
              version="1.0">
  <user username="YOUR_USER_NAME" password="******" roles="manager,admin,manager-gui"/>
</tomcat-users>

3. IntelliJ-IDEA / Gradle

In this step I’m using IntelliJ, to quickly generate the project structure for me. However, you can also go ahead and pull it from my git repository here: https://github.com/wolfpaulus/JerseyTemplate As I create a new project, I select Gradle on the left and Java and Web on the right, like shown below.

IntellliJ Project Wizard

In the following steps of the new-project wizard, I use “com.techcasita” as my GroupId and WebAppTemplate as the ArtifactId, select “Use default grade wrapper” – and enter “WebAppTemplate” for Project name. The project structure now looks like this.

Project Structure

4. Java App / Web App

Let’s use the same sample I used in the before mentioned rest-easy blogpost. For that I create a ‘java’ folder in src/main,  create a new class ‘com.techcasita.demo.Dictionary’, and enter the source below.

4.1. Java Application Module

The simple Java application I’m going to write is just a simple Dictionary, exposing this functionality:

  • lookup word – returns its meaning
  • enter word, meaning – enters a new entry into the dictionary
  • delete word – removes an entry from the dictionary
  • content – returns the complete dictionary
  • serialize – returns the complete dictionary, json-encoded
import org.boon.json.JsonFactory;
import org.boon.json.ObjectMapper;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.util.Map;
import java.util.TreeMap;

/**
 * A simple Dictionary implementation, that lists the words of a language in alphabetical order and gives their meaning.
 */
public class Dictionary {
    private static final String NOT_FOUND = "The dictionary does not contain an entry for ";
    private final Map<String, String> mDictionary;

    public Dictionary() {
        mDictionary = new TreeMap<String, String>();
    }

    /**
     * @param word {@link java.lang.String}
     * @return the meaning of then given word.
     */
    public String lookup(final String word) {
        if (mDictionary.containsKey(word)) {
            return mDictionary.get(word);
        } else {
            return NOT_FOUND + word;
        }
    }

    /**
     * Enters a new entry in the dictionary.
     *
     * @param word    {@link java.lang.String}
     * @param meaning {@link java.lang.String}
     */
    public void enter(final String word, final String meaning) {
        mDictionary.put(word, meaning);
    }

    /**
     * Deletes the entry for the given word.
     *
     * @param word {@link java.lang.String}
     */
    public void delete(final String word) {
        mDictionary.remove(word);
    }

    /**
     * @param os {@link java.io.OutputStream}
     * @return the complete alphabetically sorted dictionary
     * @throws IOException
     */
    public OutputStream content(final OutputStream os) throws IOException {
        for (Map.Entry<String, String> entry : mDictionary.entrySet()) {
            os.write((entry.getKey() + " : " + entry.getValue() + "\n").getBytes());
        }
        return os;
    }

    /**
     * Writes the complete alphabetically sorted dictionary as a JSON String into the output stream.
     *
     * @param os {@link java.io.OutputStream}
     * @return the given OutputStream.
     * @throws IOException
     */
    public OutputStream serialize(final OutputStream os) throws IOException {
        ObjectMapper mapper = JsonFactory.create();
        mapper.writeValue(os, mDictionary);
        os.flush();
        return os;
    }

    public static void main(String[] args) {

        Dictionary dict = new Dictionary();
        System.out.printf("Usage:\n" +
                " lookup  - returns its meaning\n" +
                " enter   - enters a new entry into the dictionary\n" +
                " delete  - removes an entry from the dictionary\n" +
                " content - returns the complete dictionary\n" +
                " serialize - returns the complete dictionary, json-encoded");

        while (true) {
            System.out.print("\n> ");
            BufferedReader lineOfText = new BufferedReader(new InputStreamReader(System.in));
            try {
                final String textLine = lineOfText.readLine();
                int m = textLine.indexOf(" ");
                final String cmd = textLine.substring(0, m > 0 ? m++ : textLine.length());
                if ("content".equals(cmd)) {
                    dict.content(System.out);
                } else if ("serialize".equals(cmd)) {
                    dict.serialize(System.out);
                } else {
                    int n = textLine.indexOf(" ", m);
                    final String param = textLine.substring(m, n > 0 ? n++ : textLine.length());
                    if ("delete".equals(cmd)) {
                        dict.delete(param);
                    } else if ("enter".equals(cmd)) {
                        dict.enter(param, textLine.substring(n));
                    }
                    System.out.println(param + " : " + dict.lookup(param));
                }
            } catch (IOException e) {
                System.out.println(e.toString());
                break;
            }
        }
    }
}

The serialization into JSON is mainly to have at least one dependent module library. In this case, I’m using Richard Hightower’s wickedly fast boon json parser. Since the dependency has not been declared yet, IntelliJ will mark the import statements red.

4.2. The service class wraps each Dictionary method

Now I’m adding another class that will expose the Dictionary to all users or maintain a different one for each user. The content() method shows how a dynamically generated JSON – File is returned.

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.ws.rs.*;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.io.IOException;

@Path("/")
public class MyService {
    private static final boolean GLOBAL_DICTIONARY = false; // true means all sessions access the same Dictionary
    private static final String SESSION_ATTR_NAME = MyService.class.getName() + "_Dictionary";
    private final Dictionary mDictionary = new Dictionary();

    @Context
    private HttpServletRequest httpRequest;
    @Context
    private HttpServletResponse httpResponse;

    private Dictionary getDictionary(HttpSession session) {
        if (GLOBAL_DICTIONARY) { // all sessions access the same Dictionary
            return mDictionary;
        } else { // each session gets its own Dictionary
            Dictionary dictionary = (Dictionary) session.getAttribute(SESSION_ATTR_NAME);
            if (dictionary == null) {
                session.setAttribute(SESSION_ATTR_NAME, dictionary = new Dictionary());
            }
            return dictionary;
        }
    }

    /**
     * Here we declare how the meaning for a word can be accessed.
     * E.g.:  http://server:port/dictionary/hello
     *
     * @param key {@link String} word to be lookup in the dictionary
     * @return the meaning of the word.
     */
    @GET
    @Path("/dictionary/{key}")
    @Produces(MediaType.TEXT_PLAIN)
    public Response lookup(@PathParam("key") final String key) {
        return Response.ok(getDictionary(httpRequest.getSession()).lookup(key)).build();
    }

    @GET
    @Path("/dictionary")
    @Produces(MediaType.APPLICATION_OCTET_STREAM)
    public Response content() {
        httpResponse.setHeader("Content-Disposition", "attachment;filename=dictionary.json");
        try {
            getDictionary(httpRequest.getSession()).serialize(httpResponse.getOutputStream());
        } catch (IOException e) {
            e.printStackTrace();
        }
        return Response.ok().build();
    }

    @POST
    @Path("/dictionary")
    @Produces(MediaType.TEXT_PLAIN)
    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
    public Response enter(@FormParam("word") final String word, @FormParam("meaning") final String meaning) {
        final Dictionary d = getDictionary(httpRequest.getSession());
        d.enter(word, meaning);
        return Response.ok("OK").build();
    }

    @DELETE
    @Path("/{word}")
    @Produces(MediaType.TEXT_PLAIN)
    public Response delete(@PathParam("word") final String word) {
        getDictionary(httpRequest.getSession()).delete(word);
        return Response.ok("OK").build();
    }
}

But once again, since dependencies have not been declared yet, there is a lot or red now in IntelliJ.

4.3. Dependencies / build.gradle

My modified ~/WebAppTemplate/build.gradle file looks something like this. The IntelliJ’s Gradle wrapper takes care of downloading the mentioned projects and their dependencies. Since Tomcat already provides the servlet api libraries, there is no need to package them into the app, i.e. those are marked with ‘providedCompile’. Btw, Gradle will cache the downloaded libraries here ~/.gradle/caches.

group 'com.techcasita'
version '1.0'

apply plugin: 'java'
apply plugin: 'war'

repositories {
    mavenCentral()
}

dependencies {
    compile 'io.fastjson:boon:0.32'
    compile 'javax.ws.rs:javax.ws.rs-api:2.0'
    compile 'org.glassfish.jersey.containers:jersey-container-servlet:2.17'
    compile 'log4j:log4j:1.2.17'
    providedCompile 'javax.servlet:javax.servlet-api:3.1.0'
    testCompile group: 'junit', name: 'junit', version: '4.11'
}

4.4. The Deployment Descriptor

There is no web.xml a.k.a. deployment descriptor. Therefore, I create a ‘WEB-INF’ folder in src/main/webapp, create a new file ‘web.xml’, and enter the source below.

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1">
    <display-name>Jersey Web Application</display-name>
    <servlet>
        <servlet-name>javax.ws.rs.core.Application</servlet-name>
        <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
 
        <init-param>
            <param-name>jersey.config.server.provider.packages</param-name>
            <param-value>com.techcasita.demo</param-value>
        </init-param>
 
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>javax.ws.rs.core.Application</servlet-name>
        <url-pattern>/service/*</url-pattern>
    </servlet-mapping>
 
    <session-config>
        <session-timeout>
            30
        </session-timeout>
    </session-config>
    <welcome-file-list>
        <welcome-file>index.html</welcome-file>
    </welcome-file-list>
</web-app>
Project Structure

After adding an index.html file that is used to write into the dictionary, the application and web module are done and the project structure in IntelliJ IDEA looks something like this:

5. Run/Debug Configuration

The Run/Debug configuration, on the Server tab, selects Tomcat 8.0.23 as the Application Server and on the Deployment tab, /service is entered as the context. I.e.
http://localhost:8080/service will be the URL to access this web app. In the deployment descriptor (web.xml) /dictionary/* was mapped to dispatch the MyApplication. Therefore: http://localhost:8080/service/dictionary/hello would be the URL to find the meaning for ‘hello’ in the dictionary.

Run/Debug Configuration

With all this configured, Tomcat can now easily be started in the RUN or DEBUG mode and new entries can be posted into the dictionary or meaning can be retrieved with GET request.

http://localhost:8080/service/dictionary/hello

The war file is not small, but with about 5.2 MB still pretty lean. It includes the following libraries:

Libraries included in the war file

 

 

2 Replies to “Java 8, Tomcat 8, and Jersey on Mac OS X”

  1. Thanks for your post about Tomcat and Java. Good start for me.

  2. This is exactly what I wanted. Not a single concrete solution in StackOverflow of making JAX-RS work with gradle in IntelliJ Idea on Mac. Thank you

Leave a Reply to Ivan Telenkov Cancel reply