Master Java SOAP and REST Web Services
Java Web Services
Java Web Services enable applications to communicate over networks using standardized protocols. This guide covers both SOAP (JAX-WS) and REST (JAX-RS) web services, their implementation patterns, security considerations, and best practices.
Table of Contents
- Web Services Overview
- SOAP Web Services (JAX-WS)
- REST Web Services (JAX-RS)
- Service Security
- Client Development
- Testing Web Services
- Best Practices
Web Services Overview
Types of Web Services
SOAP (Simple Object Access Protocol)
- Protocol-based, uses XML messaging
- Strongly typed with WSDL contracts
- Built-in error handling and security
- Better for enterprise integration
REST (Representational State Transfer)
- Architectural style using HTTP methods
- Lightweight, typically uses JSON
- Stateless and cacheable
- Better for web and mobile applications
Java Web Service Technologies
// SOAP Technologies
// JAX-WS (Java API for XML Web Services)
// JAXB (Java Architecture for XML Binding)
// SAAJ (SOAP with Attachments API for Java)
// REST Technologies
// JAX-RS (Java API for RESTful Web Services)
// JSON-P (Java API for JSON Processing)
// JSON-B (Java API for JSON Binding)
SOAP Web Services (JAX-WS)
Creating a SOAP Web Service
import javax.jws.WebService;
import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.soap.SOAPBinding;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
// Service Endpoint Interface
@WebService
@SOAPBinding(style = SOAPBinding.Style.RPC)
public interface UserService {
@WebMethod
User getUserById(@WebParam(name = "userId") int userId);
@WebMethod
User[] getAllUsers();
@WebMethod
boolean createUser(@WebParam(name = "user") User user);
@WebMethod
boolean updateUser(@WebParam(name = "user") User user);
@WebMethod
boolean deleteUser(@WebParam(name = "userId") int userId);
}
// Data Transfer Object
@XmlRootElement(name = "user")
public class User {
private int id;
private String username;
private String email;
private String firstName;
private String lastName;
private String status;
// Default constructor required for JAXB
public User() {}
public User(int id, String username, String email) {
this.id = id;
this.username = username;
this.email = email;
}
@XmlElement
public int getId() { return id; }
public void setId(int id) { this.id = id; }
@XmlElement
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
@XmlElement
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
// Other getters and setters...
}
// Service Implementation
@WebService(endpointInterface = "com.example.UserService")
public class UserServiceImpl implements UserService {
private UserDAO userDAO = new UserDAO();
@Override
public User getUserById(int userId) {
try {
User user = userDAO.findById(userId);
if (user == null) {
throw new RuntimeException("User not found with ID: " + userId);
}
return user;
} catch (Exception e) {
throw new RuntimeException("Error retrieving user: " + e.getMessage());
}
}
@Override
public User[] getAllUsers() {
try {
List<User> userList = userDAO.findAll();
return userList.toArray(new User[0]);
} catch (Exception e) {
throw new RuntimeException("Error retrieving users: " + e.getMessage());
}
}
@Override
public boolean createUser(User user) {
try {
// Validation
if (user.getUsername() == null || user.getUsername().trim().isEmpty()) {
throw new IllegalArgumentException("Username is required");
}
if (user.getEmail() == null || user.getEmail().trim().isEmpty()) {
throw new IllegalArgumentException("Email is required");
}
return userDAO.create(user);
} catch (Exception e) {
throw new RuntimeException("Error creating user: " + e.getMessage());
}
}
@Override
public boolean updateUser(User user) {
try {
if (user.getId() <= 0) {
throw new IllegalArgumentException("Valid user ID is required");
}
User existingUser = userDAO.findById(user.getId());
if (existingUser == null) {
throw new RuntimeException("User not found with ID: " + user.getId());
}
return userDAO.update(user);
} catch (Exception e) {
throw new RuntimeException("Error updating user: " + e.getMessage());
}
}
@Override
public boolean deleteUser(int userId) {
try {
User existingUser = userDAO.findById(userId);
if (existingUser == null) {
throw new RuntimeException("User not found with ID: " + userId);
}
return userDAO.delete(userId);
} catch (Exception e) {
throw new RuntimeException("Error deleting user: " + e.getMessage());
}
}
}
Publishing SOAP Service
import javax.xml.ws.Endpoint;
import javax.xml.ws.soap.SOAPBinding;
public class UserServicePublisher {
public static void main(String[] args) {
// Publish the service
String address = "http://localhost:8080/ws/userservice";
UserServiceImpl implementor = new UserServiceImpl();
Endpoint endpoint = Endpoint.publish(address, implementor);
System.out.println("Service published at: " + address);
System.out.println("WSDL available at: " + address + "?wsdl");
// Keep the service running
System.out.println("Press Enter to stop the service...");
try {
System.in.read();
} catch (Exception e) {
e.printStackTrace();
}
endpoint.stop();
System.out.println("Service stopped.");
}
}
// Alternative: Deploy in application server (web.xml)
/*
<servlet>
<servlet-name>UserServiceServlet</servlet-name>
<servlet-class>com.sun.xml.ws.transport.http.servlet.WSServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>UserServiceServlet</servlet-name>
<url-pattern>/ws/*</url-pattern>
</servlet-mapping>
<listener>
<listener-class>com.sun.xml.ws.transport.http.servlet.WSServletContextListener</listener-class>
</listener>
*/
Advanced SOAP Features
// Custom Fault Exception
@WebFault(name = "UserServiceFault")
public class UserServiceException extends Exception {
private UserServiceFault faultInfo;
public UserServiceException(String message, UserServiceFault faultInfo) {
super(message);
this.faultInfo = faultInfo;
}
public UserServiceFault getFaultInfo() {
return faultInfo;
}
}
@XmlRootElement
public class UserServiceFault {
private String errorCode;
private String errorMessage;
private String details;
// Constructors, getters, setters...
}
// Service with custom exceptions
@WebService
public interface EnhancedUserService {
@WebMethod
User getUserById(@WebParam(name = "userId") int userId)
throws UserServiceException;
@WebMethod
@WebResult(name = "userList")
List<User> searchUsers(
@WebParam(name = "criteria") SearchCriteria criteria
) throws UserServiceException;
}
// Message Handler for logging/security
public class LoggingHandler implements SOAPHandler<SOAPMessageContext> {
@Override
public boolean handleMessage(SOAPMessageContext context) {
Boolean outbound = (Boolean) context.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
try {
SOAPMessage message = context.getMessage();
if (outbound) {
System.out.println("Outbound SOAP Message:");
} else {
System.out.println("Inbound SOAP Message:");
}
// Log the message
ByteArrayOutputStream baos = new ByteArrayOutputStream();
message.writeTo(baos);
System.out.println(baos.toString());
} catch (Exception e) {
System.err.println("Error logging SOAP message: " + e.getMessage());
}
return true; // Continue processing
}
@Override
public boolean handleFault(SOAPMessageContext context) {
System.err.println("SOAP Fault occurred");
return true;
}
@Override
public void close(MessageContext context) {
// Cleanup resources
}
@Override
public Set<QName> getHeaders() {
return null; // Handle all headers
}
}
REST Web Services (JAX-RS)
Creating REST Services
import javax.ws.rs.*;
import javax.ws.rs.core.*;
import java.util.List;
@Path("/users")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class UserRestService {
private UserDAO userDAO = new UserDAO();
@GET
@Path("/{id}")
public Response getUserById(@PathParam("id") int id) {
try {
User user = userDAO.findById(id);
if (user == null) {
return Response.status(Response.Status.NOT_FOUND)
.entity(new ErrorResponse("User not found", "USER_NOT_FOUND"))
.build();
}
return Response.ok(user).build();
} catch (Exception e) {
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(new ErrorResponse("Internal server error", "INTERNAL_ERROR"))
.build();
}
}
@GET
public Response getAllUsers(
@QueryParam("page") @DefaultValue("0") int page,
@QueryParam("size") @DefaultValue("10") int size,
@QueryParam("status") String status) {
try {
List<User> users;
if (status != null && !status.trim().isEmpty()) {
users = userDAO.findByStatus(status, page, size);
} else {
users = userDAO.findAll(page, size);
}
// Add pagination headers
int totalCount = userDAO.getTotalCount();
int totalPages = (int) Math.ceil((double) totalCount / size);
return Response.ok(users)
.header("X-Total-Count", totalCount)
.header("X-Total-Pages", totalPages)
.header("X-Current-Page", page)
.build();
} catch (Exception e) {
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(new ErrorResponse("Error retrieving users", "RETRIEVAL_ERROR"))
.build();
}
}
@POST
public Response createUser(User user, @Context UriInfo uriInfo) {
try {
// Validation
if (user.getUsername() == null || user.getUsername().trim().isEmpty()) {
# Java Web Services
Java Web Services enable applications to communicate over networks using standardized protocols. This guide covers both SOAP (JAX-WS) and REST (JAX-RS) web services, their implementation patterns, security considerations, and best practices.
## Table of Contents
- [Web Services Overview](#web-services-overview)
- [SOAP Web Services (JAX-WS)](#soap-web-services-jax-ws)
- [REST Web Services (JAX-RS)](#rest-web-services-jax-rs)
- [Service Security](#service-security)
- [Client Development](#client-development)
- [Testing Web Services](#testing-web-services)
- [Best Practices](#best-practices)
## Web Services Overview
### Types of Web Services
**SOAP (Simple Object Access Protocol)**
- Protocol-based, uses XML messaging
- Strongly typed with WSDL contracts
- Built-in error handling and security
- Better for enterprise integration
**REST (Representational State Transfer)**
- Architectural style using HTTP methods
- Lightweight, typically uses JSON
- Stateless and cacheable
- Better for web and mobile applications
### Java Web Service Technologies
```java
// SOAP Technologies
// JAX-WS (Java API for XML Web Services)
// JAXB (Java Architecture for XML Binding)
// SAAJ (SOAP with Attachments API for Java)
// REST Technologies
// JAX-RS (Java API for RESTful Web Services)
// JSON-P (Java API for JSON Processing)
// JSON-B (Java API for JSON Binding)
SOAP Web Services (JAX-WS)
Creating a SOAP Web Service
import javax.jws.WebService;
import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.soap.SOAPBinding;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
// Service Endpoint Interface
@WebService
@SOAPBinding(style = SOAPBinding.Style.RPC)
public interface UserService {
@WebMethod
User getUserById(@WebParam(name = "userId") int userId);
@WebMethod
User[] getAllUsers();
@WebMethod
boolean createUser(@WebParam(name = "user") User user);
@WebMethod
boolean updateUser(@WebParam(name = "user") User user);
@WebMethod
boolean deleteUser(@WebParam(name = "userId") int userId);
}
// Data Transfer Object
@XmlRootElement(name = "user")
public class User {
private int id;
private String username;
private String email;
private String firstName;
private String lastName;
private String status;
// Default constructor required for JAXB
public User() {}
public User(int id, String username, String email) {
this.id = id;
this.username = username;
this.email = email;
}
@XmlElement
public int getId() { return id; }
public void setId(int id) { this.id = id; }
@XmlElement
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
@XmlElement
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
// Other getters and setters...
}
// Service Implementation
@WebService(endpointInterface = "com.example.UserService")
public class UserServiceImpl implements UserService {
private UserDAO userDAO = new UserDAO();
@Override
public User getUserById(int userId) {
try {
User user = userDAO.findById(userId);
if (user == null) {
throw new RuntimeException("User not found with ID: " + userId);
}
return user;
} catch (Exception e) {
throw new RuntimeException("Error retrieving user: " + e.getMessage());
}
}
@Override
public User[] getAllUsers() {
try {
List<User> userList = userDAO.findAll();
return userList.toArray(new User[0]);
} catch (Exception e) {
throw new RuntimeException("Error retrieving users: " + e.getMessage());
}
}
@Override
public boolean createUser(User user) {
try {
// Validation
if (user.getUsername() == null || user.getUsername().trim().isEmpty()) {
throw new IllegalArgumentException("Username is required");
}
if (user.getEmail() == null || user.getEmail().trim().isEmpty()) {
throw new IllegalArgumentException("Email is required");
}
return userDAO.create(user);
} catch (Exception e) {
throw new RuntimeException("Error creating user: " + e.getMessage());
}
}
@Override
public boolean updateUser(User user) {
try {
if (user.getId() <= 0) {
throw new IllegalArgumentException("Valid user ID is required");
}
User existingUser = userDAO.findById(user.getId());
if (existingUser == null) {
throw new RuntimeException("User not found with ID: " + user.getId());
}
return userDAO.update(user);
} catch (Exception e) {
throw new RuntimeException("Error updating user: " + e.getMessage());
}
}
@Override
public boolean deleteUser(int userId) {
try {
User existingUser = userDAO.findById(userId);
if (existingUser == null) {
throw new RuntimeException("User not found with ID: " + userId);
}
return userDAO.delete(userId);
} catch (Exception e) {
throw new RuntimeException("Error deleting user: " + e.getMessage());
}
}
}
Publishing SOAP Service
import javax.xml.ws.Endpoint;
import javax.xml.ws.soap.SOAPBinding;
public class UserServicePublisher {
public static void main(String[] args) {
// Publish the service
String address = "http://localhost:8080/ws/userservice";
UserServiceImpl implementor = new UserServiceImpl();
Endpoint endpoint = Endpoint.publish(address, implementor);
System.out.println("Service published at: " + address);
System.out.println("WSDL available at: " + address + "?wsdl");
// Keep the service running
System.out.println("Press Enter to stop the service...");
try {
System.in.read();
} catch (Exception e) {
e.printStackTrace();
}
endpoint.stop();
System.out.println("Service stopped.");
}
}
// Alternative: Deploy in application server (web.xml)
/*
<servlet>
<servlet-name>UserServiceServlet</servlet-name>
<servlet-class>com.sun.xml.ws.transport.http.servlet.WSServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>UserServiceServlet</servlet-name>
<url-pattern>/ws/*</url-pattern>
</servlet-mapping>
<listener>
<listener-class>com.sun.xml.ws.transport.http.servlet.WSServletContextListener</listener-class>
</listener>
*/
Advanced SOAP Features
// Custom Fault Exception
@WebFault(name = "UserServiceFault")
public class UserServiceException extends Exception {
private UserServiceFault faultInfo;
public UserServiceException(String message, UserServiceFault faultInfo) {
super(message);
this.faultInfo = faultInfo;
}
public UserServiceFault getFaultInfo() {
return faultInfo;
}
}
@XmlRootElement
public class UserServiceFault {
private String errorCode;
private String errorMessage;
private String details;
// Constructors, getters, setters...
}
// Service with custom exceptions
@WebService
public interface EnhancedUserService {
@WebMethod
User getUserById(@WebParam(name = "userId") int userId)
throws UserServiceException;
@WebMethod
@WebResult(name = "userList")
List<User> searchUsers(
@WebParam(name = "criteria") SearchCriteria criteria
) throws UserServiceException;
}
// Message Handler for logging/security
public class LoggingHandler implements SOAPHandler<SOAPMessageContext> {
@Override
public boolean handleMessage(SOAPMessageContext context) {
Boolean outbound = (Boolean) context.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
try {
SOAPMessage message = context.getMessage();
if (outbound) {
System.out.println("Outbound SOAP Message:");
} else {
System.out.println("Inbound SOAP Message:");
}
// Log the message
ByteArrayOutputStream baos = new ByteArrayOutputStream();
message.writeTo(baos);
System.out.println(baos.toString());
} catch (Exception e) {
System.err.println("Error logging SOAP message: " + e.getMessage());
}
return true; // Continue processing
}
@Override
public boolean handleFault(SOAPMessageContext context) {
System.err.println("SOAP Fault occurred");
return true;
}
@Override
public void close(MessageContext context) {
// Cleanup resources
}
@Override
public Set<QName> getHeaders() {
return null; // Handle all headers
}
}
REST Web Services (JAX-RS)
Creating REST Services
import javax.ws.rs.*;
import javax.ws.rs.core.*;
import java.util.List;
@Path("/users")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class UserRestService {
private UserDAO userDAO = new UserDAO();
@GET
@Path("/{id}")
public Response getUserById(@PathParam("id") int id) {
try {
User user = userDAO.findById(id);
if (user == null) {
return Response.status(Response.Status.NOT_FOUND)
.entity(new ErrorResponse("User not found", "USER_NOT_FOUND"))
.build();
}
return Response.ok(user).build();
} catch (Exception e) {
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(new ErrorResponse("Internal server error", "INTERNAL_ERROR"))
.build();
}
}
@GET
public Response getAllUsers(
@QueryParam("page") @DefaultValue("0") int page,
@QueryParam("size") @DefaultValue("10") int size,
@QueryParam("status") String status) {
try {
List<User> users;
if (status != null && !status.trim().isEmpty()) {
users = userDAO.findByStatus(status, page, size);
} else {
users = userDAO.findAll(page, size);
}
// Add pagination headers
int totalCount = userDAO.getTotalCount();
int totalPages = (int) Math.ceil((double) totalCount / size);
return Response.ok(users)
.header("X-Total-Count", totalCount)
.header("X-Total-Pages", totalPages)
.header("X-Current-Page", page)
.build();
} catch (Exception e) {
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(new ErrorResponse("Error retrieving users", "RETRIEVAL_ERROR"))
.build();
}
}
@POST
public Response createUser(User user, @Context UriInfo uriInfo) {
try {
// Validation
if (user.getUsername() == null || user.getUsername().trim().isEmpty()) {
return Response.status(Response.Status.BAD_REQUEST)
.entity(new ErrorResponse("Username is required", "VALIDATION_ERROR"))
.build();
}
if (user.getEmail() == null || user.getEmail().trim().isEmpty()) {
return Response.status(Response.Status.BAD_REQUEST)
.entity(new ErrorResponse("Email is required", "VALIDATION_ERROR"))
.build();
}
// Check for duplicate username
if (userDAO.existsByUsername(user.getUsername())) {
return Response.status(Response.Status.CONFLICT)
.entity(new ErrorResponse("Username already exists", "DUPLICATE_USERNAME"))
.build();
}
User createdUser = userDAO.create(user);
// Build location URI for created resource
URI location = uriInfo.getAbsolutePathBuilder()
.path(String.valueOf(createdUser.getId()))
.build();
return Response.status(Response.Status.CREATED)
.location(location)
.entity(createdUser)
.build();
} catch (Exception e) {
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(new ErrorResponse("Error creating user", "CREATION_ERROR"))
.build();
}
}
@PUT
@Path("/{id}")
public Response updateUser(@PathParam("id") int id, User user) {
try {
User existingUser = userDAO.findById(id);
if (existingUser == null) {
return Response.status(Response.Status.NOT_FOUND)
.entity(new ErrorResponse("User not found", "USER_NOT_FOUND"))
.build();
}
user.setId(id); // Ensure ID matches path parameter
User updatedUser = userDAO.update(user);
return Response.ok(updatedUser).build();
} catch (Exception e) {
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(new ErrorResponse("Error updating user", "UPDATE_ERROR"))
.build();
}
}
@DELETE
@Path("/{id}")
public Response deleteUser(@PathParam("id") int id) {
try {
User existingUser = userDAO.findById(id);
if (existingUser == null) {
return Response.status(Response.Status.NOT_FOUND)
.entity(new ErrorResponse("User not found", "USER_NOT_FOUND"))
.build();
}
userDAO.delete(id);
return Response.status(Response.Status.NO_CONTENT).build();
} catch (Exception e) {
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(new ErrorResponse("Error deleting user", "DELETION_ERROR"))
.build();
}
}
// Advanced search endpoint
@POST
@Path("/search")
public Response searchUsers(SearchCriteria criteria) {
try {
List<User> users = userDAO.search(criteria);
return Response.ok(users).build();
} catch (Exception e) {
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(new ErrorResponse("Error searching users", "SEARCH_ERROR"))
.build();
}
}
}
// Error response class
public class ErrorResponse {
private String message;
private String code;
private long timestamp;
public ErrorResponse(String message, String code) {
this.message = message;
this.code = code;
this.timestamp = System.currentTimeMillis();
}
// Getters and setters...
}
REST Service Configuration
import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;
import java.util.HashSet;
import java.util.Set;
@ApplicationPath("/api")
public class RestApplication extends Application {
@Override
public Set<Class<?>> getClasses() {
Set<Class<?>> classes = new HashSet<>();
classes.add(UserRestService.class);
classes.add(ProductRestService.class);
classes.add(OrderRestService.class);
// Add providers
classes.add(JacksonJsonProvider.class);
classes.add(ValidationExceptionMapper.class);
classes.add(CORSFilter.class);
return classes;
}
}
// CORS Filter
@Provider
public class CORSFilter implements ContainerResponseFilter {
@Override
public void filter(ContainerRequestContext requestContext,
ContainerResponseContext responseContext) {
responseContext.getHeaders().add("Access-Control-Allow-Origin", "*");
responseContext.getHeaders().add("Access-Control-Allow-Methods",
"GET, POST, PUT, DELETE, OPTIONS");
responseContext.getHeaders().add("Access-Control-Allow-Headers",
"Content-Type, Authorization");
responseContext.getHeaders().add("Access-Control-Max-Age", "3600");
}
}
// Exception Mapper
@Provider
public class ValidationExceptionMapper implements ExceptionMapper<ValidationException> {
@Override
public Response toResponse(ValidationException exception) {
ErrorResponse error = new ErrorResponse(
exception.getMessage(),
"VALIDATION_ERROR"
);
return Response.status(Response.Status.BAD_REQUEST)
.entity(error)
.type(MediaType.APPLICATION_JSON)
.build();
}
}
Service Security
Basic Authentication
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.core.Response;
import java.util.Base64;
@Provider
@Priority(Priorities.AUTHENTICATION)
public class BasicAuthFilter implements ContainerRequestFilter {
private UserAuthService authService = new UserAuthService();
@Override
public void filter(ContainerRequestContext requestContext) {
String authHeader = requestContext.getHeaderString("Authorization");
if (authHeader == null || !authHeader.startsWith("Basic ")) {
abortWithUnauthorized(requestContext);
return;
}
try {
String encodedCredentials = authHeader.substring("Basic ".length());
String credentials = new String(Base64.getDecoder().decode(encodedCredentials));
String[] parts = credentials.split(":", 2);
if (parts.length != 2) {
abortWithUnauthorized(requestContext);
return;
}
String username = parts[0];
String password = parts[1];
if (!authService.authenticate(username, password)) {
abortWithUnauthorized(requestContext);
return;
}
// Set user context for downstream processing
requestContext.setProperty("authenticated.user", username);
} catch (Exception e) {
abortWithUnauthorized(requestContext);
}
}
private void abortWithUnauthorized(ContainerRequestContext requestContext) {
ErrorResponse error = new ErrorResponse("Authentication required", "AUTH_REQUIRED");
Response response = Response.status(Response.Status.UNAUTHORIZED)
.entity(error)
.header("WWW-Authenticate", "Basic realm=\"API\"")
.build();
requestContext.abortWith(response);
}
}
// JWT Authentication Filter
@Provider
@Priority(Priorities.AUTHENTICATION)
public class JWTAuthFilter implements ContainerRequestFilter {
private JWTService jwtService = new JWTService();
@Override
public void filter(ContainerRequestContext requestContext) {
String authHeader = requestContext.getHeaderString("Authorization");
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
abortWithUnauthorized(requestContext);
return;
}
try {
String token = authHeader.substring("Bearer ".length());
if (!jwtService.validateToken(token)) {
abortWithUnauthorized(requestContext);
return;
}
String username = jwtService.getUsernameFromToken(token);
requestContext.setProperty("authenticated.user", username);
} catch (Exception e) {
abortWithUnauthorized(requestContext);
}
}
private void abortWithUnauthorized(ContainerRequestContext requestContext) {
ErrorResponse error = new ErrorResponse("Invalid or expired token", "INVALID_TOKEN");
Response response = Response.status(Response.Status.UNAUTHORIZED)
.entity(error)
.build();
requestContext.abortWith(response);
}
}
Client Development
SOAP Client
import javax.xml.namespace.QName;
import javax.xml.ws.Service;
import java.net.URL;
public class UserServiceClient {
public static void main(String[] args) {
try {
// Create service from WSDL
URL wsdlURL = new URL("http://localhost:8080/ws/userservice?wsdl");
QName serviceName = new QName("http://example.com/", "UserServiceImplService");
Service service = Service.create(wsdlURL, serviceName);
UserService userService = service.getPort(UserService.class);
// Test service operations
System.out.println("Testing SOAP Web Service...");
// Get all users
User[] users = userService.getAllUsers();
System.out.println("Total users: " + users.length);
// Get specific user
if (users.length > 0) {
User user = userService.getUserById(users[0].getId());
System.out.println("Retrieved user: " + user.getUsername());
}
// Create new user
User newUser = new User();
newUser.setUsername("testuser");
newUser.setEmail("[email protected]");
newUser.setFirstName("Test");
newUser.setLastName("User");
boolean created = userService.createUser(newUser);
System.out.println("User created: " + created);
} catch (Exception e) {
System.err.println("Error calling SOAP service: " + e.getMessage());
e.printStackTrace();
}
}
}
REST Client
import javax.ws.rs.client.*;
import javax.ws.rs.core.*;
import java.util.List;
public class UserRestClient {
private static final String BASE_URL = "http://localhost:8080/api/users";
private Client client;
public UserRestClient() {
this.client = ClientBuilder.newClient();
// Configure client with JSON support
client.register(JacksonJsonProvider.class);
}
public User getUserById(int id) {
try {
Response response = client.target(BASE_URL)
.path(String.valueOf(id))
.request(MediaType.APPLICATION_JSON)
.get();
if (response.getStatus() == Response.Status.OK.getStatusCode()) {
return response.readEntity(User.class);
} else if (response.getStatus() == Response.Status.NOT_FOUND.getStatusCode()) {
return null;
} else {
throw new RuntimeException("Failed to get user: " + response.getStatus());
}
} catch (Exception e) {
throw new RuntimeException("Error calling REST service", e);
}
}
public List<User> getAllUsers(int page, int size) {
try {
Response response = client.target(BASE_URL)
.queryParam("page", page)
.queryParam("size", size)
.request(MediaType.APPLICATION_JSON)
.get();
if (response.getStatus() == Response.Status.OK.getStatusCode()) {
GenericType<List<User>> listType = new GenericType<List<User>>() {};
return response.readEntity(listType);
} else {
throw new RuntimeException("Failed to get users: " + response.getStatus());
}
} catch (Exception e) {
throw new RuntimeException("Error calling REST service", e);
}
}
public User createUser(User user) {
try {
Response response = client.target(BASE_URL)
.request(MediaType.APPLICATION_JSON)
.post(Entity.entity(user, MediaType.APPLICATION_JSON));
if (response.getStatus() == Response.Status.CREATED.getStatusCode()) {
return response.readEntity(User.class);
} else {
throw new RuntimeException("Failed to create user: " + response.getStatus());
}
} catch (Exception e) {
throw new RuntimeException("Error calling REST service", e);
}
}
public boolean updateUser(int id, User user) {
try {
Response response = client.target(BASE_URL)
.path(String.valueOf(id))
.request(MediaType.APPLICATION_JSON)
.put(Entity.entity(user, MediaType.APPLICATION_JSON));
return response.getStatus() == Response.Status.OK.getStatusCode();
} catch (Exception e) {
throw new RuntimeException("Error calling REST service", e);
}
}
public boolean deleteUser(int id) {
try {
Response response = client.target(BASE_URL)
.path(String.valueOf(id))
.request()
.delete();
return response.getStatus() == Response.Status.NO_CONTENT.getStatusCode();
} catch (Exception e) {
throw new RuntimeException("Error calling REST service", e);
}
}
// Client with authentication
public User getUserWithAuth(int id, String token) {
try {
Response response = client.target(BASE_URL)
.path(String.valueOf(id))
.request(MediaType.APPLICATION_JSON)
.header("Authorization", "Bearer " + token)
.get();
if (response.getStatus() == Response.Status.OK.getStatusCode()) {
return response.readEntity(User.class);
} else {
throw new RuntimeException("Failed to get user: " + response.getStatus());
}
} catch (Exception e) {
throw new RuntimeException("Error calling REST service", e);
}
}
public void close() {
if (client != null) {
client.close();
}
}
}
Testing Web Services
Unit Testing REST Services
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.BeforeEach;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import javax.ws.rs.core.Response;
public class UserRestServiceTest {
@Mock
private UserDAO userDAO;
private UserRestService userService;
@BeforeEach
void setUp() {
MockitoAnnotations.openMocks(this);
userService = new UserRestService();
// Inject mock DAO (would typically use dependency injection)
}
@Test
void testGetUserById_UserExists() {
// Given
int userId = 1;
User expectedUser = new User(userId, "testuser", "[email protected]");
when(userDAO.findById(userId)).thenReturn(expectedUser);
// When
Response response = userService.getUserById(userId);
// Then
assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
User actualUser = (User) response.getEntity();
assertEquals(expectedUser.getUsername(), actualUser.getUsername());
}
@Test
void testGetUserById_UserNotFound() {
// Given
int userId = 999;
when(userDAO.findById(userId)).thenReturn(null);
// When
Response response = userService.getUserById(userId);
// Then
assertEquals(Response.Status.NOT_FOUND.getStatusCode(), response.getStatus());
}
@Test
void testCreateUser_Success() {
// Given
User newUser = new User(0, "newuser", "[email protected]");
User createdUser = new User(1, "newuser", "[email protected]");
when(userDAO.existsByUsername("newuser")).thenReturn(false);
when(userDAO.create(newUser)).thenReturn(createdUser);
// When
Response response = userService.createUser(newUser, mockUriInfo);
// Then
assertEquals(Response.Status.CREATED.getStatusCode(), response.getStatus());
User responseUser = (User) response.getEntity();
assertEquals(createdUser.getId(), responseUser.getId());
}
}
Integration Testing
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.AfterAll;
public class UserServiceIntegrationTest {
private static UserRestClient client;
private static final String TEST_BASE_URL = "http://localhost:8080/api/users";
@BeforeAll
static void setUp() {
client = new UserRestClient();
// Start embedded server or ensure test server is running
}
@AfterAll
static void tearDown() {
client.close();
}
@Test
void testCompleteUserLifecycle() {
// Create user
User newUser = new User();
newUser.setUsername("integrationtest");
newUser.setEmail("[email protected]");
newUser.setFirstName("Integration");
newUser.setLastName("Test");
User createdUser = client.createUser(newUser);
assertNotNull(createdUser);
assertNotNull(createdUser.getId());
// Get user
User retrievedUser = client.getUserById(createdUser.getId());
assertNotNull(retrievedUser);
assertEquals(newUser.getUsername(), retrievedUser.getUsername());
// Update user
retrievedUser.setEmail("[email protected]");
boolean updated = client.updateUser(retrievedUser.getId(), retrievedUser);
assertTrue(updated);
// Verify update
User updatedUser = client.getUserById(retrievedUser.getId());
assertEquals("[email protected]", updatedUser.getEmail());
// Delete user
boolean deleted = client.deleteUser(updatedUser.getId());
assertTrue(deleted);
// Verify deletion
User deletedUser = client.getUserById(updatedUser.getId());
assertNull(deletedUser);
}
}
Best Practices
Service Design Principles
// 1. Use proper HTTP status codes
@GET
@Path("/{id}")
public Response getResource(@PathParam("id") Long id) {
Resource resource = resourceService.findById(id);
if (resource == null) {
return Response.status(Response.Status.NOT_FOUND).build();
}
return Response.ok(resource).build();
}
// 2. Implement proper validation
@POST
public Response createResource(@Valid CreateResourceRequest request) {
try {
Resource resource = resourceService.create(request);
return Response.status(Response.Status.CREATED)
.entity(resource)
.build();
} catch (ValidationException e) {
return Response.status(Response.Status.BAD_REQUEST)
.entity(new ErrorResponse(e.getMessage()))
.build();
}
}
// 3. Use DTOs for API contracts
public class UserDTO {
private Long id;
private String username;
private String email;
// Only expose necessary fields, hide internal details
}
// 4. Implement pagination for large datasets
@GET
public Response getResources(
@QueryParam("page") @DefaultValue("0") int page,
@QueryParam("size") @DefaultValue("20") int size) {
if (size > 100) {
size = 100; // Limit maximum page size
}
PagedResult<Resource> result = resourceService.findAll(page, size);
return Response.ok(result.getContent())
.header("X-Total-Count", result.getTotalElements())
.header("X-Total-Pages", result.getTotalPages())
.build();
}
// 5. Implement proper error handling
@Provider
public class GlobalExceptionMapper implements ExceptionMapper<Exception> {
private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionMapper.class);
@Override
public Response toResponse(Exception exception) {
logger.error("Unhandled exception", exception);
ErrorResponse error = new ErrorResponse(
"Internal server error",
"INTERNAL_ERROR"
);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(error)
.build();
}
}
// 6. Use async processing for long-running operations
@POST
@Path("/process")
public Response processAsync(@Valid ProcessRequest request) {
String jobId = UUID.randomUUID().toString();
// Submit for async processing
processingService.submitJob(jobId, request);
ProcessResponse response = new ProcessResponse(jobId, "SUBMITTED");
return Response.status(Response.Status.ACCEPTED)
.entity(response)
.build();
}
@GET
@Path("/process/{jobId}/status")
public Response getProcessStatus(@PathParam("jobId") String jobId) {
ProcessStatus status = processingService.getJobStatus(jobId);
return Response.ok(status).build();
}
Summary
Java Web Services provide powerful mechanisms for distributed application integration:
SOAP Web Services:
- Protocol-based with strong contracts (WSDL)
- Built-in security and error handling
- Better for enterprise integration
- Use JAX-WS for implementation
REST Web Services:
- Lightweight, HTTP-based architecture
- JSON-based communication
- Better for web and mobile applications
- Use JAX-RS for implementation
Best Practices:
- Use appropriate HTTP status codes and methods
- Implement proper authentication and authorization
- Design with security, validation, and error handling in mind
- Use DTOs for clean API contracts
- Implement pagination for large datasets
- Test services thoroughly with unit and integration tests
Performance Considerations:
- Use async processing for long-running operations
- Implement caching where appropriate
- Monitor service performance and usage
- Use connection pooling for database access
Web services remain crucial for modern enterprise applications, enabling system integration, microservices architectures, and API-driven development approaches.