Posted on: April 21, 2025 Posted by: rahulgite Comments: 0

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 Specification to construct filtering logic based on the provided fields.
  • A generic converter method to handle different data types in attributes.
  • cb.like for String fields and cb.equal for 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.

Leave a Comment