When updating from spring boot 3.2.8 to 3.2.11 an error started to show when I try to persist an object. I tried the 3.2.10 version and the error disapeared. It seems like a bug, since my code stopped to work in 3.2.11.
The error is related to a composite entity. A simplified version of my scenario is as follows:
Spring Boot:
@Entity
public class Estado {
@Id
@GeneratedValue( strategy = GenerationType.IDENTITY )
private Long id;
private String nome;
private String sigla;
}
@Entity
public class Cidade {
@Id
@GeneratedValue( strategy = GenerationType.IDENTITY )
private Long id;
private String name;
private Estado estado;
}
@RepositoryRestResource( collectionResourceRel = "cidades", path = "cidades", excerptProjection = CidadeInfo.class )
public interface CidadeRepository extends JpaRepository<Cidade , Long> {
}
When trying to persist a Cidade (city) which have an Estado (state/province), the following error appeared:
POST method:
JSON parse error: Cannot construct instance of
br.com.studiocore.companion.entidades.Estado(although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('/api/estados/1')
PATCH method:
Could not read payload
My JavaScript code:
const obj = new Cidade(
hdId.value === "" ? null : hdId.value,
txtNome.value,
contextPath + "api/estados/" + selEstado.value
);
const id = hdId.value ? hdId.value : null;
fetch( contextPath + "api/cidades" + ( id ? `/${id}` : "" ), {
method: id ? "PATCH" : "POST",
headers: {
"Authorization": "Bearer " + localStorage.getItem( "token" ),
"Accept": "application/json",
"Content-Type": "application/json"
},
body: JSON.stringify( obj )
}).then( response => {
modalAguarde.hide();
if ( response.ok ) {
return response.json();
}
response.text().then( text => {
let error = JSON.parse( text );
let mensagem = "<p>Ocorreu um erro ao tentar salvar uma Cidade!</p>";
mensagem += `<p>${error.message}</p>`;
Utils.abrirModalMensagem( "Erro", mensagem );
});
throw new Error( "" );
}).then( data => {
resetarFormulario();
carregar( paginaAtual.pagina );
}).catch( error => {
Utils.mostrarErro( error );
});
Comment From: bclozel
Please share a minimal sample application that reproduces the problem.
Because it involves JSON deserialisation, maybe we don't need to include JPA nor any JavaScript (a simple curl command should work).
Comment From: davidbuzatto
Here is the sample app: https://github.com/davidbuzatto/isthisabug
3.2.8 and 3.2.10:
POST:
curl -d "{\"bar\":\"bars/1\"}" -H "Content-Type: application/json" -X POST http://localhost:8080/foos
Response OK:
{
"_links" : {
"self" : {
"href" : "http://localhost:8080/foos/2"
},
"foo" : {
"href" : "http://localhost:8080/foos/2"
},
"bar" : {
"href" : "http://localhost:8080/foos/2/bar"
}
}
}
PATCH:
curl -d "{\"bar\":\"bars/1\"}" -H "Content-Type: application/json" -X PATCH http://localhost:8080/foos/1
Response OK:
{
"_links" : {
"self" : {
"href" : "http://localhost:8080/foos/1"
},
"foo" : {
"href" : "http://localhost:8080/foos/1"
},
"bar" : {
"href" : "http://localhost:8080/foos/1/bar"
}
}
}
3.2.11:
POST:
curl -d "{\"bar\":\"bars/1\"}" -H "Content-Type: application/json" -X POST http://localhost:8080/foos
Error:
{"cause":{"cause":null,"message":"Cannot construct instance ofcom.example.demo.Bar(although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('bars/1')\n at [Source: (org.springframework.util.StreamUtils$NonClosingInputStream); line: 1, column: 8] (through reference chain: com.example.demo.Foo[\"bar\"])"},"message":"JSON parse error: Cannot construct instance ofcom.example.demo.Bar(although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('bars/1')"}
PATCH:
curl -d "{\"bar\":\"bars/1\"}" -H "Content-Type: application/json" -X PATCH http://localhost:8080/foos/1
Error:
{"cause":{"cause":null,"message":"Cannot construct instance ofcom.example.demo.Bar(although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('bars/1')\n at [Source: UNKNOWN; byte offset: #UNKNOWN] (through reference chain: com.example.demo.Foo[\"bar\"])"},"message":"Could not read payload"}
Comment From: bclozel
Thanks for the sample, I think this is a regression in Spring Data. Using Spring Boot 3.2.11 but downgrading Spring Data works:
<properties>
<spring-data-bom.version>2023.1.10</spring-data-bom.version>
</properties>
I think this has been reported already in https://github.com/spring-projects/spring-data-rest/issues/2425 so I'm closing this issue as a duplicate.