blog.itcode.devblog.itcode.dev

[OAuth2.0] Building an OAuth2.0 Authentication Server with ScribeJAVA - 9. Providing RESTful API Services with Jersey

This chapter covers how to provide RESTful API services using Jersey. Since this project receives and responds to requests through Jersey, we'll briefly cover Jersey before setting up the controllers. Typically, the Spring framework is widely used to build JAVA servers. Still, the reason I chose Jersey instead is, first of all, that I don't know Spring very well. But beyond that, compared to Spring, it's smaller in scale and simpler to configure, letting me focus entirely on building a RESTful server. Not to mention how vicious Spring's configuration can be, this project doesn't require complex logic or a wide variety of features by its nature. Since we wouldn't be using Spring's vast scale to its full extent, the tail would be wagging the dog.

[OAuth2.0] Building an OAuth2.0 Authentication Server with ScribeJAVA - 9. Providing RESTful API Services with Jersey

This chapter covers how to provide RESTful API services using Jersey. Since this project receives and responds to requests through Jersey, we'll briefly cover Jersey before setting up the controllers. Typically, the Spring framework is widely used to build JAVA servers. Still, the reason I chose Jersey instead is, first of all, that I don't know Spring very well. But beyond that, compared to Spring, it's smaller in scale and simpler to configure, letting me focus entirely on building a RESTful server. Not to mention how vicious Spring's configuration can be, this project doesn't require complex logic or a wide variety of features by its nature. Since we wouldn't be using Spring's vast scale to its full extent, the tail would be wagging the dog.
RWB0104
@RWBwritten at 2021-10-24 15:12:58

This chapter covers how to provide RESTful API services using Jersey. Since this project receives and responds to requests through Jersey, we'll briefly cover Jersey before setting up the controllers.

Typically, the Spring framework is widely used to build JAVA servers. Still, the reason I chose Jersey instead is, first of all, that I don't know Spring very well. But beyond that, compared to Spring, it's smaller in scale and simpler to configure, letting me focus entirely on building a RESTful server. Not to mention how vicious Spring's configuration can be, this project doesn't require complex logic or a wide variety of features by its nature. Since we wouldn't be using Spring's vast scale to its full extent, the tail would be wagging the dog.


Make sure to use Tomcat version 10 or higher. As mentioned repeatedly in the previous chapter, Jersey 3.x only uses jakarta.*, which is Servlet 5.0. It doesn't support anything below Servlet 4.x, so no matter how spec-compliant your request is, all you'll get is a 404. Tomcat only provides this starting from version 10, so be careful. I lost several hours to this issue.

Servlet 5.0?
Starting with Servlet 5.0, the jakarta.* package is used. Servlet 4.x and below, which we've used until now, uses the javax.* package. Aside from the package name change, the usage is completely identical. Migration is complete simply by changing javax.* to jakarta.*.

Let's set up Jersey 3 in the project.

The explanation is based on Gradle. Add the following to the dependencies section of build.gradle.

TXT

dependencies {
	// https://mvnrepository.com/artifact/jakarta.servlet/jakarta.servlet-api
	compileOnly group: 'jakarta.servlet', name: 'jakarta.servlet-api', version: '5.0.0'
	
	// https://mvnrepository.com/artifact/org.glassfish.jersey.core/jersey-server
	implementation group: 'org.glassfish.jersey.core', name: 'jersey-server', version: '3.0.3'
	
	// https://mvnrepository.com/artifact/org.glassfish.jersey.containers/jersey-container-servlet
	implementation group: 'org.glassfish.jersey.containers', name: 'jersey-container-servlet', version: '3.0.3'
	
	// https://mvnrepository.com/artifact/org.glassfish.jersey.inject/jersey-hk2
	implementation group: 'org.glassfish.jersey.inject', name: 'jersey-hk2', version: '3.0.3'
	
	// https://mvnrepository.com/artifact/org.glassfish.jersey.media/jersey-media-json-jackson
	implementation group: 'org.glassfish.jersey.media', name: 'jersey-media-json-jackson', version: '3.0.3'

    // other libraries...
}
  • jakarta.servlet-api - Servlet 5.0
  • jersey-server - Jersey server core implementation
  • jersey-container-servlet - Jersey's Servlet implementation
  • jersey-hk2 - HK2 InjectionManager implementation
  • jersey-media-json-jackson - A module that connects Jersey's response object with a JSON provider using Jackson

That's what each library means. Of these, jersey-media-json-jackson is a plugin, and this provider plugin is required in order to respond in JSON format. There are many other useful plugins as well, but for this project, these four are enough to use Jersey.

Let's specify Jersey's request URL. What this means is that we're specifying the top-level URL that Jersey will handle.

For example, if you specify /api, requests starting with https://example.com/api will be delegated to Jersey instead of a regular Servlet.

There's a traditional way to specify this in web.xml, but it's rather cumbersome and not recommended. We'll implement this at the server level instead.

JAVA

@ApplicationPath("/api")
public class App extends Application
{
    // Requests with the api prefix are handled by jersey
}

Specify it as shown above. Just create a class and have it extend Application. Then specify the desired path prefix in @ApplicationPath.

The above configuration delegates paths starting with /api to Jersey.

With just the configuration above, Jersey is ready to use. Now let's design a RESTful API and receive a response.

Create an arbitrary class and specify it as shown below. The class name can be anything you want.

JAVA

@Path("/userinfo")
public class TestAPI
{
	// /api/userinfo API
}

Specify the desired path in @Path. The class above becomes an API class that handles the /api/userinfo request.

JAVA

@Path("/userinfo")
public class TestAPI
{
    @GET
    @Path("")
	public String testResponse()
    {
        return "It's Worked!";
    }
}

Let's create a method as shown above. A GET request to /api/userinfo will return "It's Worked!" via the testResponse method, which will be displayed in the browser. Besides @GET, various other HTTP methods such as @POST and @PUT are also supported, so configure them as needed.

@Path likewise specifies the URL to receive requests on. Note that the parent @Path values are prepended to the URL prefix in order. Using this pattern, you can create a variety of RESTful APIs.

JAVA

@Path("/userinfo")
public class TestAPI
{
    @GET
    @Path("")
	public String testResponse()
    {
        return "It's Worked!";
    }

    @GET
    @Path("/{id}")
	public String userinfoResponse(@PathParam("id") String id)
    {
        return "{ key1: \"value1\", key2: \"value2\", id: \"" + id + "\" }";
    }

    @POST
    @Path("/{hash}")
	public String useraddResponse(@PathParam("hash") String hash, @FormParam("key") String key)
    {
        return hash + key;
    }

    @GET
    @Path("/check")
	public boolean usercheckResponse(@QueryParam("id") String id, @QueryParam("key") String key)
    {
        return true;
    }

    @DELETE
    @Path("/remove")
	public int userremoveResponse(@CookieParam("auth") String auth)
    {
        return 1;
    }
}

Let's design a variety of RESTful APIs like this. You can return various kinds of values.

AnnotationDescription
@PathParamAssigned the value of the specified URL. Entered as /{key} in @Path
@QueryParamAssigned the URL parameter with the specified key
@FormParamAssigned the specified body parameter
@CookieParamAssigned the Cookie with the specified key
@HeaderParamAssigned the Header with the specified key

Using these annotations, you can easily receive the desired elements as arguments.

MethodHTTPTarget URL
testResponseGET/api/userinfo
userinfoResponseGET/api/userinfo/{id}
useraddResponsePOST/api/userinfo/{hash}
usercheckResponseGET/api/userinfo/check?id={id}&key={key}
userremoveResponseDELETE/api/userinfo/remove

The URL matching each method is as shown in the table above.

There's also a @Producer feature that lets you enforce the response type, response headers, etc., but that's not particularly important for this project, so we'll skip it.

For more details, refer to the official Jersey 3 documentation.

But as you use it, something feels a bit off. Where did HttpServletRequest and HttpServletResponse, which we used constantly with Servlet, go?

Since Jersey is delegated the request in place of Servlet, the Servlet objects aren't exposed on the surface. In this case, you can access the Servlet objects through the @Context annotation.

I create one abstract object that bundles all the Context objects I need, and have every controller API inherit from it.

JAVA

abstract public class API
{
	@Context
	protected HttpServletRequest request;
	
	@Context
	protected HttpServletResponse response;
	
	@Context
	protected UriInfo uriInfo;
}

Declare one abstract object called API as shown above. Then specify HttpServletRequest and HttpServletResponse with the @Context annotation attached. I haven't really used UriInfo much myself, so it's fine to omit it if you don't need it.

If there are other methods you'd like to include in the common API object, feel free to include them as well.

JAVA

@Path("/userinfo")
public class TestAPI extends API
{
    @GET
    @Path("")
	public String testResponse()
    {
        return request.getContextPath();
    }
}

Have the TestAPI object inherit from the API abstract object. Since it's declared as protected, any object that extends API can access HttpServletRequest and the other Context objects.

If for whatever reason you don't want to implement it via inheritance, you can just add everything directly as shown below.

JAVA

@Path("/userinfo")
public class TestAPI
{
    @Context
	protected HttpServletRequest request;
	
	@Context
	protected HttpServletResponse response;
	
	@Context
	protected UriInfo uriInfo;

    @GET
    @Path("")
	public String testResponse()
    {
        return request.getContextPath();
    }
}

This works fine too. Just remember that you'll need to add the same code to every controller API.

By using Jersey, we can handle requests and responses much more powerfully than with Servlet. In the next chapter, let's build the controllers directly based on this.

# JAVA# OAuth2.0# Jersey
ship
blog.itcode.dev

Notes from the π-th Alpaca

7.0.1
Developed by RWB since 2021.057th upgraded at 2026.08