25

Currently I try to rewrite my Java Spring Boot Application with Kotlin. I encountered a problem that in all of my classes which are annotated with @Service the dependency injection is not working correctly (all instances are null). Here is an example:

@Service
@Transactional
open class UserServiceController @Autowired constructor(val dsl: DSLContext, val teamService: TeamService) {
  //dsl and teamService are null in all methods
}

Doing the same in Java works without any problems:

@Service
@Transactional
public class UserServiceController
{
    private DSLContext dsl;
    private TeamService teamService;

    @Autowired
    public UserServiceController(DSLContext dsl,
                             TeamService teamService)
    {
        this.dsl = dsl;
        this.teamService = teamService;
    }

If I annotate the component with @Component in Kotlin everything works fine:

@Component
open class UserServiceController @Autowired constructor(val dsl: DSLContext, val teamService: TeamService) {
  //dsl and teamService are injected properly
}

Google provided many different approaches for Kotlin and @Autowired which I tried but all resulted in the same NullPointerException I would like to know what the difference between Kotlin and Java is and how I can fix this?

Andrii Abramov
  • 10,019
  • 9
  • 74
  • 96
Deutro
  • 3,113
  • 4
  • 18
  • 26

3 Answers3

21

I just bumped into exactly same issue - injection worked well, but after adding @Transactional annotation all the autowired fields are null.

My code:

@Service
@Transactional  
open class MyDAO(val jdbcTemplate: JdbcTemplate) {

   fun update(sql: String): Int {
       return jdbcTemplate.update(sql)
   }

} 

The problem here is that the methods are final by default in Kotlin, so Spring is unable to create proxy for the class:

 o.s.aop.framework.CglibAopProxy: Unable to proxy method [public final int org.mycompany.MyDAO.update(...

"Opening" the method fixes the issue:

Fixed code:

@Service
@Transactional  
open class MyDAO(val jdbcTemplate: JdbcTemplate) {

   open fun update(sql: String): Int {
       return jdbcTemplate.update(sql)
   }

} 
miran
  • 1,419
  • 1
  • 12
  • 26
7

I faced the same problem working with Kotlin but the null instance was a JpaRepository. When I added the @Transactional annotation to a method inside a service, I got a message saying Methods annotated with '@Transactional' must be overridable so I went ahead and marked both, the class and the method as open. Easy, right?! Well, not quite.

Although this compiles, I got the required repository as null at execution time. I was able to solve the problem in two ways:

  1. Mark the class and ALL of its methods as open:
open class FooService(private val barRepository: BarRepository) {
    open fun aMethod(): Bar {
        ...
    }

    @Transactional
    open fun aTransactionalMethod(): Bar {
        ...
    }
}

This works but having all the methods in a class marked with open might look a bit odd, so I tried something else.

  1. Declare an interface:
interface IFooService {
    fun aMethod(): Bar

    fun aTransactionalMethod(): Bar
}

open class FooService(private val barRepository: BarRepository) : IFooService {
    override fun aMethod(): Bar {
        ...
    }

    @Transactional
    override fun aTransactionalMethod(): Bar {
        ...
    }
}

This way you can still use the annotation since all the methods will be overridable and you won't need to use open everywhere.

Hope this helps =)

  • I dont understand.. Tried the exact same approach - but as soon as i add OPEN class - it fks up the dep. injection. val repository: Foo is NULL. And when i remove Open it doesnt let me apply transactional. SO Freaking angry – kristjan reinhold Feb 02 '21 at 08:32
4

Which Spring Boot version do you use? Since 1.4 Spring Boot is based on Spring Framework 4.3 and since then you should be able to use constructor injection without any @Autowired annotation at all. Have you tried that?

It would look like this and works for me:

@Service
class UserServiceController(val dsl: DSLContext, val teamService: TeamService) {

  // your class members

}
Andrii Abramov
  • 10,019
  • 9
  • 74
  • 96
David
  • 260
  • 1
  • 5
  • 13
  • Hi, is there a way to do this without getting a `No default constructor found` and also by not making the properties null-able? ***(FYI Some of my parameters are repositories)*** – jasperagrante Jul 15 '17 at 20:18
  • I already solved my problem. For those who are getting the `No default constructor` or the @Autowired is already found. Make sure that your constructor doesn't have default values. – jasperagrante Jul 16 '17 at 07:20