1. java
  2. /enterprise
  3. /web-services

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

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.