I'm writing an application that will end up with a set of standard CRUD operations for a bunch of object types, and I thought it would help simplify things if all the separate controllers had some common functionality. This will be using a view interface for everything rather than RESTful responses. I haven't been able to find a lot of examples of this type of thing and I'm concerned I'm just creating code smell. In addition to providing common functionality, this allows me to pass the view template and handle most of the differences between objects there.

Some questions:

  • in the case of E, I'm unable to specify that it should be an implementer of @Entity, though that would be an accurate statement. Is there a better way? Should I just create an identifying interface for those classes?
  • is there a better way to address the ID type of the repository? The entities will all have either a Long or String primary key. While this works, I know it's wrong, but don't know how to correct it. (note, I didn't include the overload method for editObject that would accept a Long)
  • I feel like the this may not be the right/best way to construct a new object based on the generic type. Is there a better way?
  • has this approach been tried and discarded as a poor one (which is why there aren't a lot of examples)?
class AbstractController<E, R extends JpaRepository<E, ? super Object>> {
    private final R repository;
    private Class<E> eClass;
    private String TEMPLATE_PREFIX;

    String getList() {
        return TEMPLATE_PREFIX + "/list";

    protected AbstractController(R repository, String templatePrefix) {
        this.repository = repository;
        this.TEMPLATE_PREFIX = templatePrefix;

    ModelAndView listObjects(@RequestParam(required = false) Integer page,
                             @RequestParam(required = false) Integer size) {
        ModelAndView modelAndView = new ModelAndView(TEMPLATE_PREFIX + "/list");
        modelAndView.addObject("items", repository.findAll(getPageRequest(page, size)));
        return modelAndView;

    ModelAndView newObject() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        ModelAndView modelAndView = new ModelAndView(TEMPLATE_PREFIX + "/edit");
        modelAndView.addObject("item", eClass.getConstructor().newInstance());
        return modelAndView;

    ModelAndView editObject(String id) {
        ModelAndView modelAndView = new ModelAndView(TEMPLATE_PREFIX + "/edit");
        modelAndView.addObject("item", repository.findById(id));
        return modelAndView;

    ModelAndView saveObject(E entity,
                            BindingResult result) {
        ModelAndView modelAndView;
        if (result.hasErrors()) {
            LOGGER.debug("error result: {}", result.getAllErrors());
            modelAndView = new ModelAndView(TEMPLATE_PREFIX + "/edit");
        } else {
            modelAndView = new ModelAndView("redirect:/" + TEMPLATE_PREFIX + "/list");
        return modelAndView;

    String deleteObject(final String id,
                        final RedirectAttributes redirectAttributes) {
        redirectAttributes.addAttribute("css", "success");
        redirectAttributes.addFlashAttribute("msg", "The entry has been deleted.");

        return "redirect:/" + TEMPLATE_PREFIX + "/list";

     * Provides defaults for a page request
     * @param page which page
     * @param size number of items
     * @return page request parameters
    private PageRequest getPageRequest(Integer page, Integer size) {
        return PageRequest.of(
                (null == page) ? 0 : page,
                (null == size) ? 10 : size);

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.