Search code examples
springrestjava-8mybatisprimitive-types

Cannot return String from @RestController method


I'm working with Spring Boot 2.4.5, MyBatis 3.5.6 and Java 8. When trying to return a String from a @RestController, an obscure error shows up in the returned HttpErrorResponse.

The method tries to obtain the value via MyBatis, i.e., a method in a DAO object that acts as a @Mapper.

My controller method:

@RestController
@RequestMapping("/api/myAPI")
public class MyController{
    @Resource
    private MyService service;

    @GetMapping(value = "myString")
    public String getBillingCompany() {
        return this.service.getDAO().getMyString();
    }
}

My DAO:

@Repository
@Mapper
public interface MyDAO{
    String getMyString();
}

...and the MyBatis mapper:

<mapper namespace="com.package.MyDAO">
    <select id="getMyString" resultType="String">
        SELECT 'My desired result' FROM A_TABLE
    </select>
    ...
</mapper>

The HttpErrorResponse:

HttpErrorResponse: {
    "headers": {
        "normalizedNames": {},
        "lazyUpdate": null
    },
    "status": 200,
    "statusText": "OK",
    "url": "http://localhost:4200/api/myAPI/myString",
    "ok": false,
    "name": "HttpErrorResponse",
    "message": "Http failure during parsing for http://localhost:4200/api/myAPI/myString",
    "error": {
        "error": {  SyntaxError: Unexpected number in JSON at position 2
                    at JSON.parse (<anonymous>)
                    at XMLHttpRequest.onLoad (http://localhost:4200/vendor.js:18158:51)
                    at ZoneDelegate.push../node_modules/zone.js/dist/zone.js.ZoneDelegate.invokeTask (http://localhost:4200/polyfills.js:21266:35)
                    at Object.onInvokeTask (http://localhost:4200/vendor.js:74037:33)
                    at ZoneDelegate.push../node_modules/zone.js/dist/zone.js.ZoneDelegate.invokeTask (http://localhost:4200/polyfills.js:21265:40)
                    at Zone.push../node_modules/zone.js/dist/zone.js.Zone.runTask (http://localhost:4200/polyfills.js:21033:51)
                    at ZoneTask.push../node_modules/zone.js/dist/zone.js.ZoneTask.invokeTask [as invoke] (http://localhost:4200/polyfills.js:21348:38)
                    at invokeTask (http://localhost:4200/polyfills.js:22516:18)
                    at XMLHttpRequest.globalZoneAwareCallback (http://localhost:4200/polyfills.js:22553:25)
                 },
        "text": "My desired result"
    }
}

Nonetheless, if I ask the controller and the DAO methods to return an int, it all works flawlessly.

Due to this, I suspected that the issue has to do with non-primitive types "namespacing", so I've tried to set a typeAlias in the MyBatis configuration, to no avail:

<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE configuration
    PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <typeAliases>
        <typeAlias type="java.lang.String" alias="String"/>
    </typeAliases>
</configuration>

Anyways, I'm under the impression that both MyBatis and Spring should be already smart enough to know what a String is. I've successfully returned collections of objects (Maps and Lists) and POJOs in the past.

Any ideas on what I'm lacking or not seeing? Thanks in advance.

Edit: The only thing that has worked for me so far is similar to what @emeraldjava proposed. I've built a wrapper upon an existing one in a dependency, fetching the data in my Front:

@RestController
@RequestMapping("/api/myAPI")
public class MyController{
    @Resource
    private MyService service;

    @GetMapping(value = "myString")
    public Result<String> getBillingCompany() {
        return new Result<>(this.service.getDAO().getMyString());
    }
}
public class Result<T> extends ServiceResult {
    public Result(T data) {
        this.setData(data);
    }
}

The already existing wrapper in a dependency:

public class ServiceResult {
    private Object data;

    ...

    public void setData(Object data) {
        this.data = data;
    }

    public Object getData() {
        return this.data;
    }
}


Solution

  • I'd suggest you update your Controller method to a ResponseEntity which wraps the string.

    @RestController
    @RequestMapping("/api/myAPI")
    public class MyController{
        @Resource
        private MyService service;
    
        @GetMapping(value = "myString")
        public ResponseEntity getBillingCompany() {
            return new ResponseEntity<Object>(this.service.getDAO().getMyString(), HttpStatus.OK);
        }
    }