Java Web Services Tutorial: Improve App Communication And Flexibility

By: Eugen
  |  March 11, 2024
Java Web Services Tutorial: Improve App Communication And Flexibility

Web services have taken the development world by storm, especially in recent years as they’ve become more and more widely adopted. There are naturally many reasons for this, but first, let’s understand what exactly a web service is.

The World Wide Web Consortium (W3C) defines “web of services” as “message-based design frequently found on the Web and in enterprise software”. Basically, a web service is a method of sending a message between two devices through a network.

In practical terms, this translates to an application which outputs communication in a standardized format for other client applications to receive and act on.

Web services have been adopted so quickly because they bring several important advantages:

  • allow communication and interoperability between applications running on different platforms and built with different technologies
  • enable different applications to share common standard formats and representations
  • can be reused by many different types of applications
  • are loosely coupled with other services
  • allow flexibility in choosing the functionalities you need

Historically, there are two primary types of web services: SOAP (Simple Object Access Protocol) and REST (REpresentational State Transfer) services; the latter is more recent and more widely used today.

This article will detail both, but put a stronger focus on REST.

Differences between SOAP and REST web services

SOAP is a protocol for communication between applications and is an early standard for creating web services, developed by Microsoft in 1998. It relies heavily on XML and can only exchange XML messages and requires a complex parsing and processing stack.

One of the advantages of SOAP is that it supports multiple protocols, has built-in security and error handling, and is somewhat strictly regulated, which can lead to a higher level of standardization.

However, SOAP is also fairly difficult to use and requires significant resources, which excludes it as an option on some embedded or mobile devices.

By contrast, REST is lighter, faster, more flexible and, as such, easier to use. It can also output data in several formats including XML and JSON.

Here’s a simple, high-level summary of the main differences between the two standards:

Here's a simple, high-level summary of the main differences between the two standards:

You can read more about the differences between the two architectural approaches here.

SOAP Web Services 

As we discussed earlier, SOAP is an XML-based protocol for application communication. Although it’s definitely slower and more resource heavy than its REST counterpart, it is similarly platform and language independent.

In the Java ecosystem, Java EE provides the JAX-WS API to help you create SOAP-based web services.

With JAX-WS, you can define a SOAP service in both an RPC or Document style. Both styles consist of a set of annotations to be applied to your classes, based on which the XML files are generated.

Let’s see an example of an RPC style web service. First, you need to create an interface or class with the proper annotations, which will declare the methods to be accessed by other applications:

@WebService
@SOAPBinding(style = SOAPBinding.Style.RPC)
public interface UserService {
    @WebMethod
    public void addUser(User user);
    @WebMethod
    public Users getUsers();
}

We used two primary annotations here – @WebService to declare the service interface, and @WebMethod for each method to be exposed.

The @SoapBinding annotation specifies the style of web service. A Document-style service is declared in a similar manner, replacing the @SoapBinding annotation with:

@SOAPBinding(style = SOAPBinding.Style.Document)

The difference between the two styles is in the way the XML files are generated.

Finally, you need to add an implementation class for the service interface:

@WebService(endpointInterface = "com.stackify.services.UserService")
public class DefaultUserImpl implements UserService {
    ArrayList<User> usersList = new ArrayList<>();
    @Override
    public void addUser(User user) {
        usersList.add(user);
    }
    @Override
    public Users getUsers() {
        Users users = new Users();
        users.setUsers(usersList);
        return users;
    }
}

The implementing methods must be public, and must not be static or final. You can also make use of methods annotated with @PostConstruct and @PreDestroy for lifecycle event callbacks.

Note that creating the interface is optional for a JAX-WS web service implementation. You can add the annotations directly to the class, and JAX-WS will implicitly define a service endpoint interface.

Finally, to publish the web service, use the Endpoint class:

public class ServicePublisher {
    public static void main(String[] args) {
        Endpoint.publish("http://localhost:8080/users",new DefaultUserService());
    }
}

If you run this application, you can see the XML describing your endpoint, written in WSDL (Web Service Description Language) format, by accessing the URL:

http://localhost:8080/users?wsdl

SOAP Web Service Client

To make use of the SOAP service, let’s implement a simple client application.

One way to do this is by creating a Java project and importing the web service definitions from the web service WSDL document. After creating the project, open a command line and move to the source folder of the new project; then execute the command:

wsimport -s . http://localhost:8080/users?wsdl

This will have the effect of generating the following classes in your source folder:

Source Folder Classes

Now, you can easily make use of the generated classes:

public class JAXWSClient {
    public static void main(String[] args) {
        DefaultUserImplService service = new DefaultUserImplService();
        User user = new User();
        user.setEmail("[email protected]");
        user.setName("John");
        
        UserService port = service.getDefaultUserImplPort();
        port.addUser(user);
        Users users = port.getUsers();
        System.out.println(users.getUsers().iterator().next().getName());
    }
}

REST Web Services

REST or REpresentational State Transfer, is an architectural style for building applications that can communicate over a network. The principles of REST were first laid out by Roy Fielding in his 2000 doctoral dissertation.

In a few short years, REST has overtaken SOAP in popularity due to its ease of use, speed, flexibility, and similarity to core architecture choices that power the web itself.

Here’s an interesting graph that shows the popularity of both approaches in the Java ecosystem:

Here's an interesting graph that shows the popularity of both approaches in the Java ecosystem:

Let’s have a quick look at the core principles of a REST API:

  • it follows a client-server architecture
  • it’s based on Resources accessible through their URIs
  • it uses unique Resource URIs
  • it’s stateless and cacheable
  • Clients can manipulate Resources through their URIs
  • the web service can be layered
  • can run over a wide range of protocols (though most implementations run over HTTP/HTTPS)

REST With JAX-RS

For a clearer understanding of these principles, let’s take a look at an implementation example. We’re going to use the JAX-RS API to create a simple REST API as a good starting point for our discussion.

JAX-RS Annotations and Setup

Like JAX-WS, JAX-RS API relies heavily on annotations. Since this is only a specification – meaning a set of interfaces and annotations – you also need to choose an implementation of the spec.

In the example, we’re going to use the reference implementation of JAX-RS, which is Jersey; another very popular implementation you can try is RESTEasy.

Let’s start by first understanding the most important annotations in JAX-RS:

  • @Path – defines the path used to access the web service
  • @PathParam – injects values from the URL into a method parameter
  • @FormParam – injects values from an HTML form into a method parameter
  • @Produces – specifies the type of the response
  • @Consumes – specifies the type of the request data

The API also contains annotations corresponding to each HTTP verb: @GET, @POST, @PUT, @DELETE, @HEAD, @OPTIONS.

To start working with the Jersey JAX-RS implementation, you need to add the jersey-server dependency to your project classpath. If you’re using Maven, this is easily done and will also bring in the required jsr311-api dependency:

<dependency> 
    <groupId>org.glassfish.jersey.containers</groupId> 
    <artifactId>jersey-container-servlet</artifactId> 
    <version>2.25.1</version> 
</dependency>

In addition to this, you can also add the jersey-media-moxy library to enable the API to enable JSON representations:

<dependency> 
    <groupId>org.glassfish.jersey.media</groupId> 
    <artifactId>jersey-media-moxy</artifactId> 
    <version>2.25.1</version> 
</dependency>

Having the option to use JSON as the primary representation media type can be quite powerful, not only for flexibility but also in terms of performance.

The JAX-RS Web Service

Now you can start writing the simple web service:

@Path("/users")
public class UserService {
    private static List<User> users = new ArrayList<>();
    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    public Response addUser(User user) {
        users.add(user);
        return Response.ok().build();
    }
    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public List<User> getUsers() {
        return users;
    }
}

Notice how we’re creating two endpoints here – a /users POST endpoint for adding a User resources, and a /users GET endpoint for retrieving the list of users.

Next, you need to define the configuration – extending ResourceConfig and specifying the packages to be scanned for JAX-RS services:

public class ApplicationInitializer extends ResourceConfig {
    public ApplicationInitializer() {
        packages("com.stackify.services");
    }
}

Finally, you need to add a web.xml file which registers the ServletContainer servlet and the ApplicationInitializer class:

<?xml version="1.0" encoding="UTF-8"?><?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  xmlns="http://xmlns.jcp.org/xml/ns/javaee" 
  xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee 
  http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" id="WebApp_ID" version="3.1">
    
    <display-name>rest-server</display-name> 
    <servlet> 
        <servlet-name>rest-server</servlet-name> 
        <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class> 
        <init-param> 
            <param-name>javax.ws.rs.Application</param-name> 
            <param-value>com.stackify.ApplicationInitializer</param-value> 
        </init-param> 
        <load-on-startup>1</load-on-startup> 
    </servlet> 
    <servlet-mapping> 
        <servlet-name>rest-server</servlet-name> 
        <url-pattern>/*</url-pattern> 
    </servlet-mapping>
</web-app>

After running the application, you can access the endpoints at http://localhost:8080/rest-server/users.

Using curl, you can add a new user:

curl -i -X POST -H "Content-Type:application/json" -d "{"email":"[email protected]","name":"John"}" http://localhost:8080/rest-server/users

Then retrieve the collection resource using a simple GET:

curl -i -X GET http://localhost:8080/rest-server/users/

Testing the REST Web Service

As you’re building a web service, you naturally need to test the functionality you’re implementing. And, because is is an HTTP-based API, you can interact with it relatively simply, using a standard HTTP client.

That’s exactly what we’re going to do here – use a powerful client specifically focused on testing REST services – REST Assured:

<dependency> 
    <groupId>io.rest-assured</groupId> 
    <artifactId>rest-assured</artifactId> 
    <version>3.0.3</version> 
</dependency>

REST Assured follows a typical BDD format, with each statement structured like this:

  • given – the section used to declare things like parameters or content type (optional)
  • when – the part where the HTTP method and URL to call are defined
  • then – the section where the response is verified

Let’s take a look at a simple JUnit test for the REST service we developed previously:

@Test
public void whenAddUser_thenGetUserOk() {
    RestAssured.baseURI = "http://localhost:8080/rest-server";
    String json = "{\"email\":\"[email protected]\",\"name\":\"John\"}";
    given()
      .contentType("application/json")
      .body(json)
    .when()
      .post("/users")
    .then()
      .statusCode(200);
    when()
      .get("/users")
    .then()
      .contentType("application/json")
      .body("name", hasItem("John"))
      .body("email", hasItem("[email protected]"));
}

In this quick test, we send a POST to the /users endpoint to create a new User. Then, we do a GET, retrieve all users and verify the response to check if it contains the new user we just created.

Of course, to run the test, you first need to make sure the API is running on localhost first.

Building a Client for the REST Service

Your web services will usually be consumed by a different client application that interacts with the API over HTTP.

Let’s use Angular 4 to create a simple front-end client, with an HTML form to add a user and a table to display all users in the system.

Angular 4 is quite well-suited as a front-end framework for working with REST services – as it has first-class REST support with the Http module.

To start using the library, you first need to install node.js and npm, then download the Angular command line interface:

npm install -g @angular/cli

To setup a new Angular project, navigate to your project location and run:

ng new rest-client

The process of setting up the project can take a few minutes. At the end, a number of new files will be created.

In the src/app folder, let’s create an app.service.ts file where a User class will be defined, as well as UserService which calls the two web services endpoints:

export class User {
  constructor(
    public email: string,
    public name: string) { }
} 
 
@Injectable()
export class UserService {
  constructor(
    private _http: Http){}
    
    url = 'http://localhost:8080/rest-server/users';
	
    addUser(user){   
      let headers = new Headers({'Content-Type': 'application/json'});
      let options = new RequestOptions({ headers: headers});
     
      return this._http.post(this.url, JSON.stringify(user), options)
        .map( 
		  (_response: Response) => {
            return _response;
		  },
          err => alert('Error adding user')); 
    }
    
    getUsers() {
      return this._http.get(this.url)
        .map((_response: Response) => {
          return _response.json();
        });
    }    
}

Next, let’s create an Angular Component that will make use of the UserService and create objects to bind to HTML elements:

@Component({
  selector: 'users-page',
  providers: [UserService],
  templateUrl: './users.component.html',
  styleUrls: ['./app.component.css']
})
export class UsersComponent {
  title = 'Users';
  
  constructor(
        private _service:UserService){}
     
    public user = {email: "", name: ""}; 	
	public res=[];
 
    addUser() {
      this._service.addUser(this.user)
		.subscribe( () => this.getUsers());
    }
    
    getUsers() {
      this._service.getUsers()
        .subscribe(
          users => { this.res=[];
            users.forEach(usr => {
              this.res.push(
                new User(usr.email, usr.name))
            });
          });
    }
}

The visual interface of the Angular application contains an HTML form and table, with data bindings:

<form>
Email: <input type="text" [(ngModel)]="user.email" name="email"/><br />
Name: <input type="text" [(ngModel)]="user.name" name="name"/><br />
<input type="submit" (click)="addUser()" value="Add User"/>
</form>
<table>
<tr *ngFor="let user of res" >
<td>{{user.email}} </td>
<td>{{user.name}} </td> </tr>
</table>

Let’s define the main AppComponent that will include the UsersComponent:

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'app';
}

This is all wrapped up in the main application module:

@NgModule({
  declarations: [
    AppComponent,
    UsersComponent
  ],
  imports: [
    BrowserModule,
    HttpModule,
    FormsModule,
    RouterModule.forRoot([
      { path: '', component: AppComponent },
      { path: 'users', component: UsersComponent }])],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Finally, the UsersComponent is included in the main HTML page:

<!DOCTYPE html>
<html>
  <head>
    <title>REST Client</title>
    <base href="/">
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
  </head>
  <body>
    <users-page>loading users component</users-page>
  </body>
</html>

To run the Angular application, go to the project directory in a command line, and simply run the command:

ng serve

The application can be accessed at the URL: http://localhost:4200/

Since the REST client and server run on different origins, by default the Angular client cannot access the REST endpoints, due to the CORS constraint in the browser. In order to allow cross-origin requests, let’s add a filter to the REST web service application that adds an Access-Control-Allow-Origin header to every response:

@Provider
public class CorsFilter implements ContainerResponseFilter {
   @Override
   public void filter(ContainerRequestContext requestContext,
                      ContainerResponseContext response) throws IOException {
       response.getHeaders().add("Access-Control-Allow-Origin", "*");
       response.getHeaders().add("Access-Control-Allow-Headers", "origin, content-type, accept");
   }
}

Finally, you will be able to communicate with the REST web service from the Angular application.

REST Services with Spring

As a strong alternative to JAX-RS, the Spring Framework also provides first-class support for quickly building a REST web service.

Simply put, Spring MVC offers a similar programming model, driven by the @RestController and @RequestMapping annotations, to expose the API to clients.

A good way to start bootstrapping a Spring application is making use of Spring Boot:

<parent> 
    <groupId>org.springframework.boot</groupId> 
    <artifactId>spring-boot-starter-parent</artifactId> 
    <version>1.5.4.RELEASE</version> 
</parent>
<dependencies> 
    <dependency> 
        <groupId>org.springframework.boot</groupId> 
        <artifactId>spring-boot-starter-web</artifactId> 
    </dependency> 
</dependencies>

After adding the necessary dependencies, you can create a @RestController annotated class with methods for each web service endpoint.

The example will be the same as the previous one – handing User resources – to better illustrate the differences and similarities between the two approaches:

@RestController
public class UserController {
    private static List<User> users = new ArrayList<>();
    @PostMapping(value = "/users", consumes = MediaType.APPLICATION_JSON_VALUE)
    @ResponseStatus(HttpStatus.CREATED)
    public void addUser(@RequestBody User user) {
        users.add(user);
    }
    @GetMapping("/users")
    public List<User> getUsers() {
        return users;
    }
}

With Spring, you have similar handy annotations, corresponding to each HTTP method, such as @PostMapping and @GetMapping. You can define the endpoint URL using the value attribute, or specify the media type consumed or produced by the service by using the consumes or produces attributes.

Of course, the REST support in Spring goes far beyond these simple operations and provides a full programming model to build APIs. If you’d like to go deeper into what’s possible, have a look at the reference documentation here.

Best Practices for REST Services

Now that you’ve seen two different ways that you can build REST services, it’s important to also discuss some best practices that will allow you to create more useful, standardized and easy to use APIs.

One of the most important principles to follow when building your REST service is the HATEOAS constraint.

HATEOAS stands for “Hypermedia as the Engine of Application State” and states that a client should be able to interact with a web service, by only using the hypermedia information provided by the service itself.

The main purpose of HATEOAS is to decouple client and server functionality so that changes to the service do not break client functionality, and the service can evolve independently of clients.

Simply put, in addition to the standard responses, a service implementing HATEOAS will also include links to provide the client with a set of available operations they can perform with the API.

Both JAX-RS and Spring provide support for building HATEOAS services.

Spring REST HATEOAS Support

Let’s see an example of adding HATEOAS links to the previously developed Spring REST service.

First, you need to add the spring-boot-starter-hateoas dependency:

<dependency> 
    <groupId>org.springframework.boot</groupId> 
    <artifactId>spring-boot-starter-hateoas</artifactId> 
</dependency>

Next, you have to modify the resource class User so that it extends the ResourceSupport class:

public class User extends ResourceSupport { ... }

Then you can modify the REST controller to add a link. Spring HATEOAS contains the Link class and helpful methods to obtain the URIs:

@GetMapping("/users")
public List<User> getUsers() {
    users.forEach(user -> {
        Link selfLink = 
          linkTo(methodOn(UserController.class).getUsers())
          .slash(user.getEmail())
          .withSelfRel();
        
        user.add(selfLink);
    });
    return users;
}

This simple implementation adds a Link to each User resource – pointing the client to the canonical URI of the resource – /users/{email}.

Once you expose that URI, you of course also have to define a method to handle that endpoint mapping as well:

@GetMapping("/users/{email}")
public User getUser(@PathVariable String email) {
    return users.stream()
    .filter(user -> !user.getEmail()
    .equals(email))
    .findAny()
    .orElse(null);
}

Calling the /users endpoint will now return an enriched Resource:

[
  {
    "email": "[email protected]",
    "name": "ana",
    "links": [
      {
        "rel": "self",
        "href": "http://localhost:8080/users/[email protected]"
      }
    ]
  }
]

The standardization work in the REST ecosystem is making slow but steady progress; however, we’re not yet at a point where documentation of the API isn’t necessary.

Right now, documentation is actually very helpful and makes exploring and understanding the API a lot easier; that’s why tools like Swagger and RAML have gained so much traction in recent years.

Documenting the API with Swagger

The documentation for your REST API can be created manually or with a documentation generation tool, such as Swagger.

Swagger is a specification that can be integrated with JAX-RS implementations, as well as with Spring REST implementations.

Let’s set up Swagger in the simple Spring REST API we created, and have a look at the generated documentation.

First, you’ll need the springfox-swagger2 dependency, as well as springfox-swagger-ui if you want a visual interface:

<dependency> 
    <groupId>io.springfox</groupId> 
    <artifactId>springfox-swagger2</artifactId> 
    <version>2.7.0</version> 
</dependency>
<dependency> 
    <groupId>io.springfox</groupId> 
    <artifactId>springfox-swagger-ui</artifactId> 
    <version>2.7.0</version> 
</dependency>

In Spring Boot, the configuration only requires defining a Docket bean and adding the @EnableSwagger2 annotation:

@Configuration
@EnableSwagger2
public class SwaggerConfig {
    @Bean
    public Docket api() {
        return new Docket(DocumentationType.SWAGGER_2)
          .select()
          .apis(RequestHandlerSelectors.basePackage("com.stackify.controllers"))
          .paths(PathSelectors.any())
          .build();
    }
}

This is all that is needed to support Swagger documentation generation for your Spring REST API in a Spring Boot application.

Then you can access the documentation at /swagger-ui.html:

Notice how each endpoint in the application is individually documented; you can expand each section to find details about the endpoint and even interact with it right from the Swagger UI.

Notice how each endpoint in the application is individually documented; you can expand each section to find details about the endpoint and even interact with it right from the Swagger UI.

Conclusion

Web Services have become a very common and powerful way of architecting web applications, and the adoption trends are still going strong with the major focus on microservices in the last couple of years.

Naturally, there are several mature frameworks available to implement both REST and SOAP services in the Java ecosystem. And beyond the server side, there’s solid support to test and document web services running over HTTP.

And, of course, the upcoming first-class Spring support for reactive architectures promises to keep this momentum strong and to address some of the limitations of the HTTP protocol, leading to even more performant APIs.

Improve Your Code with Retrace APM

Stackify's APM tools are used by thousands of .NET, Java, PHP, Node.js, Python, & Ruby developers all over the world.
Explore Retrace's product features to learn more.

Learn More

Want to contribute to the Stackify blog?

If you would like to be a guest contributor to the Stackify blog please reach out to [email protected]