Spring-hateoas: ControllerLinkBuilder - Stackoverflow when using Kotlin

Created on 22 Apr 2016  路  2Comments  路  Source: spring-projects/spring-hateoas

Hello

I'm using org.springframework.hateoas:spring-hateoas:0.19.0.RELEASE, which comes with org.springframework.boot:spring-boot-starter-parent:1.3.3.RELEASE. Below behaviour is also confirmed with the 0.20.0.BUILD-SNAPSHOT as of 22th April 2016.

I'm running the Spring HATEOAS tutorial (https://spring.io/guides/gs/rest-hateoas/) with Kotlin.
Calling the following Java Controller does not cause any unexpected behaviour:
(with calling I mean accessing the REST service under "http://localhost:8080/g1" and "http://localhost:8080/g2" respectively)

`import org.springframework.http.HttpEntity;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn;

@RestController
public class GreetingController {

private static final String TEMPLATE = "Hello, %s!";

@RequestMapping("/g1")
public HttpEntity<Greeting> greeting(
        @RequestParam(value = "name", required = false, defaultValue = "World") String name) {

    Greeting greeting = new Greeting(String.format(TEMPLATE, name));
    greeting.add(linkTo(methodOn(GreetingController.class).greeting(name)).withSelfRel());

    return new ResponseEntity<Greeting>(greeting, HttpStatus.OK);
}

}`

Calling the equivalent Kotlin Controller causes a stackoverflow:

`import org.springframework.http.HttpEntity
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RestController

import org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo
import org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn

@RestController
open class KotlinGreetingController {

@RequestMapping("/g2")
fun greeting(
        @RequestParam(value = "name", required = false, defaultValue = "World") name: String): HttpEntity<Greeting> {

    val greeting = Greeting(String.format(TEMPLATE, name))
    greeting.add(linkTo(methodOn(KotlinGreetingController::class.java).greeting(name)).withSelfRel())

    return ResponseEntity(greeting, HttpStatus.OK)
}

companion object {

    private val TEMPLATE = "Hello, %s!"
}

}`

The following exception is printed when running the app:

2016-04-22 17:53:24.297 ERROR 5023 --- [nio-8080-exec-4] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Handler processing failed; nested exception is java.lang.StackOverflowError] with root cause

java.lang.StackOverflowError: null
at java.security.AccessController.doPrivileged(Native Method) ~[na:1.8.0_77]
at org.springframework.cglib.core.ReflectUtils.getProtectionDomain(ReflectUtils.java:91) ~[spring-core-4.2.5.RELEASE.jar:4.2.5.RELEASE]
at org.springframework.cglib.proxy.Enhancer.getProtectionDomain(Enhancer.java:399) ~[spring-core-4.2.5.RELEASE.jar:4.2.5.RELEASE]
at org.springframework.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:206) ~[spring-core-4.2.5.RELEASE.jar:4.2.5.RELEASE]
at org.springframework.cglib.proxy.Enhancer.createHelper(Enhancer.java:378) ~[spring-core-4.2.5.RELEASE.jar:4.2.5.RELEASE]
at org.springframework.cglib.proxy.Enhancer.createClass(Enhancer.java:318) ~[spring-core-4.2.5.RELEASE.jar:4.2.5.RELEASE]
at org.springframework.hateoas.core.DummyInvocationUtils.getProxyWithInterceptor(DummyInvocationUtils.java:171) ~[spring-hateoas-0.19.0.RELEASE.jar:na]
at org.springframework.hateoas.core.DummyInvocationUtils.methodOn(DummyInvocationUtils.java:148) ~[spring-hateoas-0.19.0.RELEASE.jar:na]
at org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn(ControllerLinkBuilder.java:150) ~[spring-hateoas-0.19.0.RELEASE.jar:na]
at mypackage.KotlinGreetingController.greeting(KotlinGreetingController.kt:22) ~[classes/:na]
at mypackage.KotlinGreetingController.greeting(KotlinGreetingController.kt:22) ~[classes/:na]
at mypackage.KotlinGreetingController.greeting(KotlinGreetingController.kt:22) ~[classes/:na]

Whereas the last line is repeated dozens of times (the full remaining stacktracke only contains the last line repeated)

As "Kotlin" is a choice on start.spring.io, I assume Spring officially supports Kotlin as a compatible JVM language (hence I reported the bug here)

Let me know if I can help you in anyway

kotlin

Most helpful comment

First, apologies for the premature opening of this ticket - I've just solved my problem.
cglib subclasses classes and methods to change their behaviour.
As Kotlin classes and methods are _final by default_, both class and methods have to be declared "open" so cglib can subclass it.

The following example works just fine with Kotlin (pay attention to the open keyword on class and method level):

`import ch.climbinglog.web.dto.Greeting
import org.springframework.http.HttpEntity
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RestController

import org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo
import org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn

@RestController
open class KotlinGreetingController {

@RequestMapping("/g2")
open fun greeting(
        @RequestParam(value = "name", required = false, defaultValue = "World") name: String): HttpEntity<Greeting> {

    val greeting = Greeting(String.format(TEMPLATE, name))
    greeting.add(linkTo(methodOn(KotlinGreetingController::class.java).greeting(name)).withSelfRel())

    return ResponseEntity(greeting, HttpStatus.OK)
}

companion object {

    private val TEMPLATE = "Hello, %s!"
}

}`

All 2 comments

With Kotlin the interceptor is never called (DummyInvocationUtils.getProxyWithInterceptor called by methodOn), therefore actually executing the Controller's method in an infinitive loop.

First, apologies for the premature opening of this ticket - I've just solved my problem.
cglib subclasses classes and methods to change their behaviour.
As Kotlin classes and methods are _final by default_, both class and methods have to be declared "open" so cglib can subclass it.

The following example works just fine with Kotlin (pay attention to the open keyword on class and method level):

`import ch.climbinglog.web.dto.Greeting
import org.springframework.http.HttpEntity
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RestController

import org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo
import org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn

@RestController
open class KotlinGreetingController {

@RequestMapping("/g2")
open fun greeting(
        @RequestParam(value = "name", required = false, defaultValue = "World") name: String): HttpEntity<Greeting> {

    val greeting = Greeting(String.format(TEMPLATE, name))
    greeting.add(linkTo(methodOn(KotlinGreetingController::class.java).greeting(name)).withSelfRel())

    return ResponseEntity(greeting, HttpStatus.OK)
}

companion object {

    private val TEMPLATE = "Hello, %s!"
}

}`

Was this page helpful?
0 / 5 - 0 ratings