18

I have a typed abstract RestController that contains some common logic for processing of all objects of the type. The service for processing is provided through the constructor.

During the bean instantiation of the subclass, both constructors are called with non-null parameters and the superclass non-null assertion successfully passed.

Calling the API endpoint (URI path is a composition of the subclass and superclass paths) calls the correct method, with correctly identified parameters. However, the endpoint method throws a null pointer exception because the provided service (the one that passed the non-null assertion) was null. Upon inspection all properties of both subclass and superclass of the bean whose method was called report all properties to be null.

Here is a simplified example:

Model:

public class Cookie { public long id; } public class ChocolateCookie extends Cookie { public long chipCount; } 

Service:

public interface CookieService<T extends Cookie> { T findCookie(long cookieId); void eatCookie(T cookie); } @Service public class ChocolateCookieService implements CookieService<ChocolateCookie> { @Override public ChocolateCookie findCookie(long cookieId) { // TODO Load a stored cookie and return it. return new ChocolateCookie(); } @Override public void eatCookie(ChocolateCookie cookie) { // TODO Eat cookie; } } 

Rest Controllers:

public abstract class CookieApi<T extends Cookie> { private final CookieService<T> cookieService; public CookieApi(CookieService<T> cookieService) { this.cookieService = cookieService; Assert.notNull(this.cookieService, "Cookie service must be set."); } @PostMapping("/{cookieId}") public ResponseEntity eatCookie(@PathVariable long cookieId) { final T cookie = cookieService.findCookie(cookieId); // Cookie service is null cookieService.eatCookie(cookie); return ResponseEntity.ok(); } } @RestController @RequestMapping("/chocolateCookies") public class ChocolateCookieApi extends CookieApi<ChocolateCookie> { @Autowired public ChocolateCookieApi(ChocolateCookieService cookieService) { super(cookieService); } @PostMapping public ResponseEntity<ChocolateCookie> create(@RequestBody ChocolateCookie dto) { // TODO Process DTO and store the cookie return ResponseEntity.ok(dto); } } 

As a note, if instead of providing a service object to the superclass I defined an abstract method for getting the service on demand and implemented it in the subclass, the superclass would function as intended.

The same principle works in any case where @RestController and @RequestMapping are not included in the equation.

My question is two-fold:

  1. Why is the happening?
  2. Is there a way to use the constructor, or at least to not have to implement getter methods for each subclass and each service required by the superclass?

EDIT 1:

I tried recreating the issue, but the provided code was working fine as people suggested. After tampering with the simplified project, I finally managed to reproduce the issue. The actual condition for reproducing the issue is that the endpoint method in the superclass must be inaccessible by the subclass (example: Classes are in different packages and the method has package visibility). This causes spring to create an enhancerBySpringCGLIB proxy class with zero populated fields.

Modifying the superclass methods to have protected/public visibility resolved the issue.

5
  • Very interesting question and the matter is very well exposed. Commented Feb 22, 2019 at 9:34
  • @davidxxx though a repo reproducing the problem would've been nice as well, as apparently people are unable to reproduce the issue Commented Feb 26, 2019 at 21:10
  • @eis I agree according to the actual answers/comment. Nikola Antic you should indeed add more information or provide a repo with a minimal project that reproduces the issue. Commented Feb 27, 2019 at 19:38
  • Well, after reading your last edited comment it totally makes sense, in your question you missed to mention those details and as all methods of your classes posted are public we did not catch it. Good thing you found out what happened. Happy coding! Commented Feb 27, 2019 at 21:44
  • you could add your own answer and accept that, so this question would at least be marked as answered. Either that or accept one of the answers that are essentially saying that the code you've posted works just fine. Commented Feb 28, 2019 at 4:52

3 Answers 3

2
+50

Nikola,

I'm not sure why your code is not working in your system, I created same classes in a project and it is working fine, I even added another Cookie type, service and api classes.

SpringBoot log (you can see 4 end points initialized):

2019-02-26 14:39:07.612 INFO 86060 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/chocolateCookies],methods=[POST]}" onto public org.springframework.http.ResponseEntity<cookie.ChocolateCookie> cookie.ChocolateCookieApi.create(cookie.ChocolateCookie) 2019-02-26 14:39:07.613 INFO 86060 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/chocolateCookies/{cookieId}],methods=[POST]}" onto public org.springframework.http.ResponseEntity<?> cookie.CookieApi.eatCookie(long) 2019-02-26 14:39:07.615 INFO 86060 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/oatmeal-raisin-cookie],methods=[POST]}" onto public org.springframework.http.ResponseEntity<cookie.OatmealRaisinCookie> cookie.OatmealRaisingCookieApi.create(cookie.OatmealRaisinCookie) 2019-02-26 14:39:07.615 INFO 86060 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/oatmeal-raisin-cookie/{cookieId}],methods=[POST]}" onto public org.springframework.http.ResponseEntity<?> cookie.CookieApi.eatCookie(long) 

Testing controllers in postman enter image description here

enter image description here

As @Domingo mentioned, you may have some configuration problems in your application because from OOP and Spring IoC perspectives your code looks fine and runs with no problems.

NOTE: I'm running these controllers using SpringBoot 2.0.5, Java 8, Eclipse

I posted my project in GitHub for your reference. https://github.com/karl-codes/cookie-monster

Cheers!

Sign up to request clarification or add additional context in comments.

2 Comments

I reproduced the same project without using sprign boot and it works; i agree with others. It seems to a configuration problem
The provided code was working fine as people suggested. The actual condition for reproducing the issue is that the endpoint method in the superclass must be inaccessible by the subclass (example: Classes are in different packages and the method has package visibility). This causes spring to create an enhancerBySpringCGLIB proxy class with zero populated fields. Modifying the superclass methods to have protected/public visibility resolved the issue. Accepting this answer as it technically was correct and the effort ultimately led me to the final solution.
1

you can define an abstract method in your abstract class and autowire the correct service on each implementation :

public abstract class CookieApi<T extends Cookie> { protected abstract CookieService<T> getCookieService(); @RequestMapping("/cookieId") public void eatCookie(@PathVariable long cookieId) { final T cookie = cookieService.findCookie(cookieId); // Cookie service is null this.getCookieService().eatCookie(cookie); } } @RestController @RequestMapping("/chocolateCookies") public class ChocolateCookieApi extends CookieApi<ChocolateCookie> { @Autowired private ChocolateCookie chocolateCookie; @Override protected CookieService<T> getCookieService() { return this.chocolateCookie; } @PostMapping public ResponseEntity<ChocolateCookie> create(@RequestBody ChocolateCookie dto) { // TODO Process DTO and store the cookie return ResponseEntity.ok(dto); } } 

1 Comment

I did try this approach before and it works. My goal is to avoid implementing getXXXService() in each subclass. There are around 10 subclasses that inherit the problematic superclass and the superclass has 3-4 services that it needs. This amounts to a lot of unnecessary method implementations.
0

your sample in general looks alright and dependency injection should work with spring.

If you want to access service which reference is in parent abstract class the reference should be not private but protected.

--

Spring initializes all RestControllers as Singleton Beans into application context injecting existing Services Beans, if there is no bean to inject application startup will fail. If calling rest endpoint access the controller that has no service reference in it, it is not the same one that was initialized with service in it(which I don't know how could happen) or something is wrong with your config.

Put that on git hub.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.