Internal Server Error on Jackson @ResponseBody
I just want to return my custom object as JSON for use in client side ajax call.
This worked at some point, and after some updates (namely, changing the application to deploy to / in Jetty) it is no longer the case.
I am not throwing an exception from the code, it returns just fine, but it seems to have exploded somewhere in the root code of Jackson when trying to convert an object to JSON.
As I said, I don't get an exception, my ajax call just explodes saying the error code "500, Internal Server Error".
/* User contains information about a user of this site, that exists only
* in the context of this site (no names, addresses).
*/
@Entity(name="User")
@Table(name="USER")
@NamedQuery(
name="findUserByName",
query="SELECT OBJECT(u) FROM User u WHERE u.name = :name"
)
public class User extends AuditableEntity implements Serializable {
private static final long serialVersionUID = -1308795024222223320L;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name="id")
private Long id;
@NotEmpty
@MinSkipEmpty(value=6)
@MaxSkipEmpty(value=32)
@Column(name="name", length=32)
private String name;
@NotEmpty
@MinSkipEmpty(value=4)
@MaxSkipEmpty(value=40)
@Column(name="password", length=40)
private String password;
@Column(name="salt", length=40)
private String salt;
@ManyToOne(fetch=FetchType.LAZY, cascade={CascadeType.PERSIST})
@JoinColumn(name="person_id")
private Person person;
@Column(name="last_login")
private Date lastLogin;
@ElementCollection(fetch=FetchType.EAGER)
@CollectionTable(name ="USER_AUTHORITY")
@Column(name="authority")
private List<Integer> authorities;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = (name == null ? name : name.trim());
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = (password == null ? password : password.trim());
}
public String getSalt() {
return salt;
}
public void setSalt(String salt) {
this.salt = salt;
}
public Person getPerson() {
return person;
}
public void setPerson(Person person) {
this.person = person;
}
public Date getLastLogin() {
return lastLogin;
}
public void setLastLogin(Date lastLogin) {
this.lastLogin = lastLogin;
}
public List<Integer> getAuthorities() {
return authorities;
}
public void setAuthorities(List<Integer> authorities) {
this.authorities = authorities;
}
}
This is a Person object
@Entity(name = "Person")
@Table(name = "PERSON")
public class Person extends AuditableEntity implements Serializable {
private static final long serialVersionUID = -1308795024262635690L;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "id")
private Long id;
@NotEmpty
@MaxSkipEmpty(value=64)
@Column(name = "firstName", length=64)
private String firstName;
@NotEmpty
@MaxSkipEmpty(value=64)
@Column(name = "lastName", length=64)
private String lastName;
@NotEmpty
@Email
@MaxSkipEmpty(value=256, message="")
@Column(name = "email", length=256)
private String email;
@DateTimeFormat(pattern="MM/dd/yyyy")
@NotNull(message = "Required field")
@Column(name = "date")
private Date birthday;
@ManyToOne(fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST })
@JoinColumn(name = "location_id")
private Location location;
public Person() {
}
public Person(String firstName, String lastName) {
super();
this.firstName = firstName;
this.lastName = lastName;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Location getLocation() {
return location;
}
public void setLocation(Location location) {
this.location = location;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
@Override
public String toString() {
return super.toString() + " name = " + firstName + " " + lastName
+ " id = " + id;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result
+ ((firstName == null) ? 0 : firstName.hashCode());
result = prime * result + ((id == null) ? 0 : id.hashCode());
result = prime * result
+ ((lastName == null) ? 0 : lastName.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Person other = (Person) obj;
if (firstName == null) {
if (other.firstName != null)
return false;
} else if (!firstName.equals(other.firstName))
return false;
if (id == null) {
if (other.id != null)
return false;
} else if (!id.equals(other.id))
return false;
if (lastName == null) {
if (other.lastName != null)
return false;
} else if (!lastName.equals(other.lastName))
return false;
return true;
}
}
@Entity(name = "Location")
@Table(name = "LOCATION")
public class Location extends AuditableEntity implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "id")
private Long id;
//name of person/place/thing
@Column(name = "name", length=128)
String name;
//street address, p.o. box, company name, c/o
@NotEmpty
@MaxSkipEmpty(value=128)
@Column(name = "line_1", length=128)
String line1;
// apt., suite, building, floor, entrance, etc.
@Column(name = "line_2", length=128)
String line2;
@NotEmpty
@MaxSkipEmpty(value=64)
@Column(name = "city", length=64)
String city;
// state, providence, region
@NotEmpty
@MaxSkipEmpty(value=40)
@Column(name = "state", length=40)
String state;
// postal code
@NotEmpty
@MaxSkipEmpty(value=16)
@Column(name = "zip", length=16)
String zip;
@Column(name = "country")
String country;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getLine1() {
return line1;
}
public void setLine1(String line1) {
this.line1 = line1;
}
public String getLine2() {
return line2;
}
public void setLine2(String line2) {
this.line2 = line2;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
public String getZip() {
return zip;
}
public void setZip(String zip) {
this.zip = zip;
}
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
private static final long serialVersionUID = -178898928354655555L;
}
@RequestMapping(value="user/{documentId}", method=RequestMethod.GET)
public @ResponseBody User getUserForDocument( Model model, @PathVariable("documentId") Long docId){
Document doc = null;
try{
doc = dService.find(docId);
}catch(Exception e){
Logger logger = Logger.getLogger(DocumentController.class);
logger.error(e.getMessage());
}
User user = doc.getUser();
user.getPerson();
user.getPerson().getLocation();
return user;
}
@Repository()
public class DocumentDaoImpl implements DocumentDao {
@PersistenceContext
private EntityManager entityManager;
@Transactional
public Document find(Long id) {
Document doc = entityManager.find(Document.class, id);
Hibernate.initialize(doc.getUser());
Hibernate.initialize(doc.getUser().getPerson());
Hibernate.initialize(doc.getUser().getPerson().getLocation());
return doc;
}
@SuppressWarnings("unchecked")
@Transactional
public List<Document> getUnassignedDocumentsForUser(User user) {
Query query = entityManager.createQuery(new StringBuffer()
.append("select d from Document d WHERE d.user = :user ")
.append("AND NOT d IN( SELECT d from Book b, IN(b.docs) bd WHERE bd.id = d.id )")
.append("").toString());
query.setParameter("user", user);
List<Document> tmp = (ArrayList<Document>) query.getResultList();
for(Document doc : tmp){
Hibernate.initialize(doc);
Hibernate.initialize(doc.getUser());
Hibernate.initialize(doc.getUser().getPerson());
Hibernate.initialize(doc.getUser().getPerson().getLocation());
entityManager.detach(doc);
entityManager.detach(doc.getUser());
entityManager.detach(doc.getUser().getPerson());
entityManager.detach(doc.getUser().getPerson().getLocation());
}
return tmp;
}
@Transactional
public Document save(Document doc) {
if (doc.getId() == null) {
entityManager.persist(doc);
return doc;
} else {
return entityManager.merge(doc);
}
}
public EntityManager getEntityManager() {
return entityManager;
}
public void setEntityManager(EntityManager entityManager) {
this.entityManager = entityManager;
}
}
ErrorExceptionResolver:
@Component
public class BWHandlerExceptionResolver extends SimpleMappingExceptionResolver implements InitializingBean{
public void afterPropertiesSet() throws Exception {
Properties props = new Properties();
props.put(Exception.class.getName(),"error");
this.setExceptionMappings(props);
}
source to share
I experienced a nearly identical problem, but wanted to know what exactly caused Jackson to fail, instead of only getting 500 back-end servers with no exception stack trace anywhere in the logs. After checking the Spring MVC sources, the first place where the exception is caught is in ServiceInvocableHandlerMethod.java (lines 109-116):
...
try {
returnValueHandlers.handleReturnValue(returnValue, getReturnType(), mavContainer, request);
} catch (Exception ex) {
if (logger.isTraceEnabled()) {
logger.trace(getReturnValueHandlingErrorMessage("Error handling return value", returnValue), ex);
}
throw ex;
}
...
handleReturnValue (...) throws an exception and it is caught here but not printed because logger.isTraceEnabled () returns false in default config. To see the stack trace of the exceptions in the logs, I needed to enable TRACE level logging in the above class. In my particular case (I am using log4j), here is the important part of log4j.properties:
...
log4j.logger.org.springframework.web.servlet.mvc.method.annotation=TRACE
log4j.appender.stdout = org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Threshold=TRACE
...
source to share
I faced a similar issue, but got a solution to print exceptions after writing the code manually to parse an object into JSON.
for example:
public @ResponseBody String getObject() {
ObjectMapper mapper = new ObjectMapper();
try {
return mapper.writeValueAsString(object);
} catch (IOException ex) {
ex.printStackTrace();
}
}
source to share