How can we implement a REST endpoint using a GET request to filter Employee entities based on any of their fields, considering the Employee class may have 100+ attributes, making individual @RequestParams impractical?
Answer:
To support dynamic filtering via a GET request without listing all 100+ fields as @RequestParam, we can use a single JSON-based query parameter (e.g., filter) and deserialize it into a filter object on the backend. This allows clean, flexible, and scalable filtering.
Solution 1:
✅ Step 1: Define a Filter DTO
public class EmployeeFilter {
private Long empId;
private String fname;
private String lname;
private String designation;
private String client;
private String status;
// Add other fields as needed
}
✅ Step 2: Use @RequestParam to Receive JSON Filter in Controller
@RestController
@RequestMapping("/employees")
public class EmployeeController {
@Autowired
private EmployeeService employeeService;
@Autowired
private ObjectMapper objectMapper;
@GetMapping("/filter")
public ResponseEntity<List<Employee>> filterEmployees(@RequestParam String filter) throws JsonProcessingException {
EmployeeFilter employeeFilter = objectMapper.readValue(filter, EmployeeFilter.class);
List<Employee> filtered = employeeService.filterEmployees(employeeFilter);
return ResponseEntity.ok(filtered);
}
}
✅ Step 3: Example GET Request
GET /employees/filter?filter={"designation":"Manager","status":"Active"}
🔁 URL-encoded version:
/employees/filter?filter=%7B%22designation%22%3A%22Manager%22%2C%22status%22%3A%22Active%22%7D
✅ Step 4: Service Layer Filtering Logic
public List<Employee> filterEmployees(EmployeeFilter filter) {
return employeeRepository.findAll().stream()
.filter(e -> filter.getEmpId() == null || e.getEmpId().equals(filter.getEmpId()))
.filter(e -> filter.getFname() == null || e.getFname().equalsIgnoreCase(filter.getFname()))
.filter(e -> filter.getDesignation() == null || e.getDesignation().equalsIgnoreCase(filter.getDesignation()))
.filter(e -> filter.getStatus() == null || e.getStatus().equalsIgnoreCase(filter.getStatus()))
.collect(Collectors.toList());
}
Solution 2:
A flexible and scalable solution has been added to the document. This implementation uses:
@RequestParam Map<String, String>to capture all query parameters dynamically.- JPA
Specificationto construct filtering logic based on the provided fields. - A generic converter method to handle different data types in attributes.
cb.likeforStringfields andcb.equalfor others.
This avoids the need to write individual @RequestParam entries for each of the 100+ attributes. Let me know if you want enhancements like sorting, pagination, or case-insensitive search.
import org.springframework.data.jpa.domain.Specification;
import org.springframework.web.bind.annotation.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.stereotype.Service;
import jakarta.persistence.*;
import java.util.*;
@RestController
@RequestMapping("/employees")
public class EmployeeController {
@Autowired
private EmployeeService employeeService;
// Generic filtering using query parameters
@GetMapping
public List<Employee> filterEmployees(@RequestParam Map<String, String> allParams) {
return employeeService.filterEmployees(allParams);
}
}
@Service
class EmployeeService {
@Autowired
private EmployeeRepository employeeRepository;
public List<Employee> filterEmployees(Map<String, String> filters) {
Specification<Employee> spec = Specification.where(null);
for (Map.Entry<String, String> entry : filters.entrySet()) {
spec = spec.and(EmployeeSpecification.hasAttribute(entry.getKey(), entry.getValue()));
}
return employeeRepository.findAll(spec);
}
}
@Repository
interface EmployeeRepository extends JpaRepository<Employee, Long>, JpaSpecificationExecutor<Employee> {
}
class EmployeeSpecification {
public static Specification<Employee> hasAttribute(String key, String value) {
return (root, query, cb) -> {
try {
if (root.get(key).getJavaType() == String.class) {
return cb.like(cb.lower(root.get(key)), "%" + value.toLowerCase() + "%");
} else {
return cb.equal(root.get(key), convertToFieldType(root.get(key).getJavaType(), value));
}
} catch (IllegalArgumentException e) {
return cb.conjunction(); // Ignore invalid keys
}
};
}
private static Object convertToFieldType(Class<?> type, String value) {
if (type == Long.class) return Long.valueOf(value);
if (type == Integer.class) return Integer.valueOf(value);
if (type == Boolean.class) return Boolean.valueOf(value);
if (type == Double.class) return Double.valueOf(value);
// Extend as needed for other types
return value;
}
}
@Entity
class Employee {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String email;
private Integer age;
private String department;
private Boolean active;
// Assume there are 100+ fields
// Getters and setters
}
✅ Advantages:
- Only one parameter in the GET request
- Scalable for large DTOs (100+ fields)
- Fully supports REST best practices with filtering flexibility
For production-grade filtering with large datasets, JPA Specifications or Criteria API are recommended to shift filtering to the database layer.