Technology Consulting

"Field Injection is not Recommended"

Why does IntelliJ tell you that "Field Injection is not Recommended"? Read on to find out.

Autowiring Is Good, Autowiring Is Bad

Let's say you've declared a bean using the @Service annotation:

@Service
public class MyService {

    public void someLogic(PrivateInfo privateInfo) {
        // super-secret business logic goes here
    }

}

Then you go to autowire the bean in your @Controller to a class field:

@Controller
public class MyController {

    @Autowired
    MyService _myService;

}

And IntelliJ gives you the ominous, yet unhelpful, warning:

`Field injection is not recommended`

It's actually really excellent advice, but why? And what should you do instead?

Mutability vs Immutability

The Spring framework saves us a lot of trouble by automatically instantiating singleton instances of objects annotated with @Service and injecting them as dependencies into variables, functions, etc., that are annotated with @Autowired.

But the autowiring of class variables inherently requires them to be mutable (read/write), rather than immutable with the final keyword (read-only), because Spring needs to be able to write the singleton instances into the variables after the class instance has been created.

This opens the potential for a couple of different problems.

Autowired Field Initialization Failure

If the singleton @Service instance has failed to initialize properly, it may not be injected into the @Autowired field, leaving it with a NULL value that would result in errors being thrown when any code tries to access it.

Malicious or Accidental Field Injection

Worse is the possibility that the @Autowired field is modified AFTER Spring has injected the singleton @Service instance. Nothing is preventing rogue code from replacing the field with a different object that may capture and store or transmit sensitive information elsewhere, or preventing poorly-written and unreviewed code from replacing the field with a NULL or some other buggy or beta implementation that will result in failure.

class MyEvilService extends MyService {
    public void someLogic(PrivateInfo privateInfo) {
        this._stealPrivateInfo(privateInfo);                        
        super.someLogic(privateInfo);
    }

    private void _stealPrivateInfo(PrivateInfo privateInfo) {
        // I'm stealing your private info here
    }
}


@Controller
public class MyController {

    @Autowired
    MyService _myService;

    @GetMapping("/evil-request")
    ResponseEntity<?> thisIsEvil() {
        // Nothing stops me from doing this
        this._myService = new MyEvilService();

        // Now I'm stealing all your data
        return ResponseEntity.ok("We're stealing now!");
    }

}

Best Practice: Constructor Injection

The best practice is to inject dependencies through the class constructor that can check for any NULL values, and inject the singleton instance into an immutable final field that cannot be changed, thereby guaranteeing a much higher level of code stability and security.

@Controller
public class MyController {

    final private MyService _myService;        

    @Autowired
    MyController(MyService myService) {
        if (myService == null) {
            throw new RuntimeException("This should not happen");
        }

        this._myService = myService;
    }

}

Instantiating the MyController class will fail if MyService is NULL. Attempting to overwrite _myService with something else will fail. And IntelliJ doesn't complain about it. It's a win all-around.

@Controller
public class MyController {

    final private MyService _myService;        

    @Autowired
    MyController(MyService myService) {
        if (myService == null) {
            throw new RuntimeException("This should not happen");
        }

        this._myService = myService;
    }

    @GetMapping("/evil-request")
    ResponseEntity<?> thisIsEvil() {
        // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%
        // I can't even compile this now
        // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%
        this._myService = new MyEvilService();
    }

}