How to create an abstract class with generics for controllers in Spring Boot?

I am developing an application on Spring Boot and I have two controllers that handle films and users. Both controllers have similar endpoints, and I would like to create an abstract class to simplify and scale my code.
Here are the examples of controllers that I use:

@RestController
public class FilmController {
  private static Map<Integer, Film> films = new HashMap<>();

  @PostMapping("/film")
  public Film addFilm(@RequestBody Film film) {
      films.put(film.getId(), film);
      return film;
  }

  @PutMapping("/film")
  private Film updateFilm(@RequestBody Film film) {
      films.put(film.getId(), film);
      return film;
  }

  @GetMapping("/films")
  private List<Film> getAllFilms() {
      return new ArrayList<>(films.values());
  }
}
public class UserController {
  private static Map<Integer, User> users = new HashMap<>();

  @PostMapping("/user")
  private User createUser(@RequestBody User user) {
      users.put(user.getId(), user);
      return user;
  }

  @PutMapping("/user")
  private User updateUser(@RequestBody User user) {
      users.put(user.getId(), user);
      return user;
  }

  @GetMapping("/users")
  private List<User> getAllUsers() {
      return new ArrayList<>(users.values());
  }
}

Can I use an abstract class with generics for this? If so, how can I do it?
I would like to create an abstract class that will handle the common logic for these two controllers.
Could you please explain to me, and what is better to use and why?

It would seem a generic CRUD controller is what you’re after.

In which case, I would suggest that you have a look at spring-data-rest.

Getting all the CRUD functionality is as easy as annotating your Spring Data repository with @RepositoryRestResource.

That is of course, if you are planning to use Spring Data as the persistence layer for your entities.

Would the following be acceptable?

  1. create a generic controller object

    public class GenericController <T extends Identifiable>{
         private Map<Integer, T> genericMap;
    
         public GenericController() {
             genericMap = new HashMap<>();
         }
    
         public T create(T t) {
             genericMap.put(t.getid(), t);
             return t;
         }
     }
    

This gives you a base for creating controllers and methods.

  1. create interfaces to ensure that the generic methods work properly. In the above example, creating an object needs an id. So, I create an interface called Identifiable and have T in the genericController extend it.

    public interface Identifiable {
        public Integer getid();
    }
    
  2. create your controllers, autowiring the genericMap as needed (dont forget to setup the generic maps as beans in a @Configuration class somewhere!)

    @RestController
    public class UserController {
        @Autowired
        GenericController<User> userController;
    
        @PostMapping("/user")
        private User createUser(@RequestBody User user) {
            return userController.create(user);
        }
    }
    

As a side note, I think there is only up to a certain point that you will be able to refactor/generalize your code. If I am thinking what you want, you want something like a generic/abstract controller then you just need to extend it while specifying the type, and its done. However, you will still need to specify the RequestMapping’s endpoints, as you will not be able to dynamically configure it if I am not mistaken. So even then, the classes should look something like:

public abstract class GenericController <T extends Identifiable>{
    private Map<Integer, T> genericMap;

    public GenericController() {
        genericMap = new HashMap<>();
    }

    public T create(T t) {
        genericMap.put(t.getId(), t);
        return t;
    }
}
@RestController
public class UserController extends GenericController<User> {
//  GenericController<User> userController;

//    @PostMapping("/user")
//    private User createUser(@RequestBody User user) {
//        return userController.create(user);
//    }

    @PostMapping("/user")
    public User create(User user ) {
        return super.create(user);
    }
}

You absolutely can! Try this way:

public abstract class BaseController<T> {

    private Map<Integer, T> entities = new HashMap<>();

    @PostMapping
    public T addEntity(@RequestBody T entity) {
        entities.put(getEntityId(entity), entity);
        return entity;
    }

    @PutMapping
    public T updateEntity(@RequestBody T entity) {
        entities.put(getEntityId(entity), entity);
        return entity;
    }

    @GetMapping("/all")
    public List<T> getAllEntities() {
        return new ArrayList<>(entities.values());
    }

    protected abstract Integer getEntityId(T entity);
}

But i would recommend you to use ResponseEntity<> instead for handling http exceptions.

Leave a Comment