104

Have a strange problem and can't figure out how to deal with it. Have simple POJO:

@Entity
@Table(name = "persons")
public class Person {

    @Id
    @GeneratedValue
    private Long id;

    @Column(name = "first_name")
    private String firstName;

    @Column(name = "middle_name")
    private String middleName;

    @Column(name = "last_name")
    private String lastName;

    @Column(name = "comment")
    private String comment;

    @Column(name = "created")
    private Date created;

    @Column(name = "updated")
    private Date updated;

    @PrePersist
    protected void onCreate() {
        created = new Date();
    }

    @PreUpdate
    protected void onUpdate() {
        updated = new Date();
    }

    @Valid
    @OrderBy("id")
    @OneToMany(mappedBy = "person", fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true)
    private List<PhoneNumber> phoneNumbers = new ArrayList<>();

    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 getMiddleName() {
        return middleName;
    }

    public void setMiddleName(String middleName) {
        this.middleName = middleName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getComment() {
        return comment;
    }

    public void setComment(String comment) {
        this.comment = comment;
    }

    public Date getCreated() {
        return created;
    }

    public Date getUpdated() {
        return updated;
    }

    public List<PhoneNumber> getPhoneNumbers() {
        return phoneNumbers;
    }

    public void addPhoneNumber(PhoneNumber number) {
        number.setPerson(this);
        phoneNumbers.add(number);
    }

    @Override
    public String toString() {
        return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
    }
}

@Entity
@Table(name = "phone_numbers")
public class PhoneNumber {

    public PhoneNumber() {}

    public PhoneNumber(String phoneNumber) {
        this.phoneNumber = phoneNumber;
    }

    @Id
    @GeneratedValue
    private Long id;

    @Column(name = "phone_number")
    private String phoneNumber;

    @ManyToOne
    @JoinColumn(name = "person_id")
    private Person person;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getPhoneNumber() {
        return phoneNumber;
    }

    public void setPhoneNumber(String phoneNumber) {
        this.phoneNumber = phoneNumber;
    }

    public Person getPerson() {
        return person;
    }

    public void setPerson(Person person) {
        this.person = person;
    }

    @Override
    public String toString() {
        return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
    }
}

and rest endpoint:

@ResponseBody
@RequestMapping(method = RequestMethod.GET)
public List<Person> listPersons() {
    return personService.findAll();
}

In json response there are all fields except Id, which I need on front end side to edit/delete person. How can I configure spring boot to serialize Id as well?

That's how response looks like now:

[{
  "firstName": "Just",
  "middleName": "Test",
  "lastName": "Name",
  "comment": "Just a comment",
  "created": 1405774380410,
  "updated": null,
  "phoneNumbers": [{
    "phoneNumber": "74575754757"
  }, {
    "phoneNumber": "575757547"
  }, {
    "phoneNumber": "57547547547"
  }]
}]

UPD Have bidirectional hibernate mapping, maybe it's somehow related to issue.

kryger
  • 12,906
  • 8
  • 44
  • 65
Konstantin
  • 1,057
  • 2
  • 7
  • 8
  • Could you please give us more insights about your spring setup? What json marshaller do you use? The default one, jackson, ...? – Ota Jul 19 '14 at 12:16
  • Actually there is no special setup. Wanted to try out spring boot :) Added spring-boot-starter-data-rest to pom and using @EnableAutoConfiguration that's all. Read couple of tutorials and seems like all have to work out of the box. And it is, except that Id field. Updated post with endpoint response. – Konstantin Jul 19 '14 at 12:50
  • 1
    In Spring 4 you should also use `@RestController` on controller class and remove `@ResponseBody` from methods. Also I would suggest having DTO classes to handle json requests/responses instead of domain objects. – Vaelyr Jul 19 '14 at 18:58

11 Answers11

148

I recently had the same problem and it's because that's how spring-boot-starter-data-rest works by default. See my SO question -> While using Spring Data Rest after migrating an app to Spring Boot, I have observed that entity properties with @Id are no longer marshalled to JSON

To customize how it behaves, you can extend RepositoryRestConfigurerAdapter to expose IDs for specific classes.

import org.springframework.context.annotation.Configuration;
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
import org.springframework.data.rest.webmvc.config.RepositoryRestConfigurerAdapter;

@Configuration
public class RepositoryConfig extends RepositoryRestConfigurerAdapter {
    @Override
    public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
        config.exposeIdsFor(Person.class);
    }
}
Carlos Rafael Ramirez
  • 5,984
  • 1
  • 29
  • 36
Patrick Grimard
  • 7,033
  • 8
  • 51
  • 68
  • 4
    And don't forget to support now getter and setter for the id in the entity class!.. (I forgot it and was searching much time for that) – phil Apr 21 '16 at 18:12
  • 3
    coudnt find `RepositoryRestConfigurerAdapter ` in `org.springframework.data.rest.webmvc.config` – Govind Singh Nov 11 '17 at 12:18
  • 2
    Yields: `The type RepositoryRestConfigurerAdapter is deprecated`. Also does not appear to work – GreenAsJade Jan 05 '19 at 06:40
  • 3
    Indeed `RepositoryRestConfigurerAdapter` is deprecated in latest versions, you have to implement `RepositoryRestConfigurer` directly (use `implements RepositoryRestConfigurer` instead of `extends RepositoryRestConfigurerAdapter`) – Yann39 May 22 '19 at 15:16
55

In case you need to expose the identifiers for all entities:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
import org.springframework.data.rest.webmvc.config.RepositoryRestConfigurer;

import javax.persistence.EntityManager;
import javax.persistence.metamodel.Type;

@Configuration
public class RestConfiguration implements RepositoryRestConfigurer {

    @Autowired
    private EntityManager entityManager;

    @Override
    public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
        config.exposeIdsFor(
                entityManager.getMetamodel().getEntities().stream()
                .map(Type::getJavaType)
                .toArray(Class[]::new));
    }
}

Note that in versions of Spring Boot prior to 2.1.0.RELEASE you must extend the (now deprecated) org.springframework.data.rest.webmvc.config.RepositoryRestConfigurerAdapter instead of implement RepositoryRestConfigurer directly.


If you only want to expose the identifiers of entities that extends or implements specific super class or interface:

    ...
    @Override
    public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
        config.exposeIdsFor(
                entityManager.getMetamodel().getEntities().stream()
                .map(Type::getJavaType)
                .filter(Identifiable.class::isAssignableFrom)
                .toArray(Class[]::new));
    }

If you only want to expose the identifiers of entities with a specific annotation:

    ...
    @Override
    public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
        config.exposeIdsFor(
                entityManager.getMetamodel().getEntities().stream()
                .map(Type::getJavaType)
                .filter(c -> c.isAnnotationPresent(ExposeId.class))
                .toArray(Class[]::new));
    }

Sample annotation:

import java.lang.annotation.*;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ExposeId {}
lcnicolau
  • 3,252
  • 4
  • 36
  • 53
  • If one were to use the first block of code, what directory and file might it go into? – David Krider Aug 28 '18 at 21:45
  • 2
    @DavidKrider : It should be in its own file, in any directory covered by the _Component Scan_. Any sub package below your main app (with the `@SpringBootApplication` annotation) should be fine. – lcnicolau Aug 29 '18 at 14:02
  • `Field entityManager in com.myapp.api.config.context.security.RepositoryRestConfig required a bean of type 'javax.persistence.EntityManager' that could not be found. ` – Dimitri Kopriwa Mar 10 '20 at 06:21
  • I tried to add a `@ConditionalOnBean(EntityManager.class)` in `MyRepositoryRestConfigurerAdapter` , but the method is not called and the id are still not exposed. I use Spring data with spring data mybatis : https://github.com/hatunet/spring-data-mybatis – Dimitri Kopriwa Mar 10 '20 at 06:27
  • @DimitriKopriwa : The `EntityManager` is part of the JPA specification and _MyBatis_ does not implement JPA (take a look at [Does MyBatis follows JPA?](https://stackoverflow.com/q/52916497/4071001)). So, I think you should configure all entities one by one, using the `config.exposeIdsFor(...)` method as in the [accepted answer](https://stackoverflow.com/a/25066565/4071001). – lcnicolau Mar 23 '20 at 17:10
24

Answer from @eric-peladan didn't work out of the box, but was pretty close, maybe that worked for previous versions of Spring Boot. Now this is how it is supposed to be configured instead, correct me if I'm wrong:

import org.springframework.context.annotation.Configuration;
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
import org.springframework.data.rest.webmvc.config.RepositoryRestConfigurerAdapter;

@Configuration
public class RepositoryConfiguration extends RepositoryRestConfigurerAdapter {

    @Override
    public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
        config.exposeIdsFor(User.class);
        config.exposeIdsFor(Comment.class);
    }
}
Salvatorelab
  • 11,614
  • 6
  • 53
  • 80
  • 3
    Confirmed that this solution works fine under Spring Boot v1.3.3.RELEASE unlike one proposed by @eric-peladan. – Poliakoff May 02 '16 at 23:11
5

The class RepositoryRestConfigurerAdapter has been deprecated since 3.1, implement RepositoryRestConfigurer directly.

@Configuration
public class RepositoryConfiguration implements RepositoryRestConfigurer  {
 @Override
 public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
  config.exposeIdsFor(YouClass.class);
  RepositoryRestConfigurer.super.configureRepositoryRestConfiguration(config);
 }
}

Font: https://docs.spring.io/spring-data/rest/docs/current-SNAPSHOT/api/org/springframework/data/rest/webmvc/config/RepositoryRestConfigurer.html

Nick is tired
  • 6,860
  • 20
  • 39
  • 51
4

With Spring Boot you have to extends SpringBootRepositoryRestMvcConfiguration
if you use RepositoryRestMvcConfiguration the configuration define in application.properties may not worked

@Configuration
public class MyConfiguration extends SpringBootRepositoryRestMvcConfiguration  {

@Override
protected void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
    config.exposeIdsFor(Project.class);
}
}

But for a temporary need You can use projection to include id in the serialization like :

@Projection(name = "allparam", types = { Person.class })
public interface ProjectionPerson {
Integer getIdPerson();
String getFirstName();
String getLastName();

}

  • 1
    In spring boot 2, this doesn't work. The class RepositoryRestMvcConfiguration doesn't have configureRepositoryRestConfiguration to override. – pitchblack408 Mar 18 '19 at 21:24
4

Just add @JsonProperty annotation to the Id and it works.

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@JsonProperty
private long id;

another approach is to implement RepositoryRestConfigurerAdapter in configuration. (This approach will be usefull when you have to do marshalling in many places)

Amol Patil
  • 347
  • 3
  • 11
Bikram
  • 828
  • 8
  • 21
2
@Component
public class EntityExposingIdConfiguration extends RepositoryRestConfigurerAdapter {

    @Override
    public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
        try {
            Field exposeIdsFor = RepositoryRestConfiguration.class.getDeclaredField("exposeIdsFor");
            exposeIdsFor.setAccessible(true);
            ReflectionUtils.setField(exposeIdsFor, config, new ListAlwaysContains());
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
    }

    class ListAlwaysContains extends ArrayList {

        @Override
        public boolean contains(Object o) {
            return true;
        }
    }
}
David B.
  • 39
  • 1
  • 4
    Welcome to SO! When posting code in your answers, it is helpful to explain how your code solves the OP's problem :) – Joel Sep 12 '18 at 00:16
1

Hm, ok seems like I found the solution. Removing spring-boot-starter-data-rest from pom file and adding @JsonManagedReference to phoneNumbers and @JsonBackReference to person gives desired output. Json in response isn't pretty printed any more but now it has Id. Don't know what magic spring boot performs under hood with this dependency but I don't like it :)

Konstantin
  • 1,057
  • 2
  • 7
  • 8
  • It's for exposing your Spring Data repositories as REST endpoints. If you don't want that feature you can leave it out. The id thing is to do with a Jackson `Module` that is registered by SDR I believe. – Dave Syer Jul 20 '14 at 06:38
  • 2
    RESTful API's should not expose surrogate key ID's because they mean nothing to external systems. In RESTful architecture the ID is the canonical URL for that resource. – Chris DaMour Aug 01 '14 at 22:37
  • 5
    I have read this before, but honestly I don't understand how can I link front end and back end without Id. I have to pass Id to orm for delete/update operation. Maybe you have some link, how can it be performed in true RESTful way :) – Konstantin Aug 02 '14 at 12:28
1

Easy way: rename your variable private Long id; to private Long Id;

Works for me. You can read more about it here

Community
  • 1
  • 1
  • 14
    oh, man... that's such a code smell... really, do not do that – Igor Donin Apr 01 '17 at 15:12
  • @IgorDonin say that to Java community who loves sniffing and crawling semantics from variable/class/function names... all the time, everywhere. – EralpB Apr 03 '17 at 14:00
0

Implement the RepositoryRestConfigurer and use @Configuration annotation on the class.

Here's the snippet

@Configuration
public class BasicConfig implements RepositoryRestConfigurer{

    @Override
    public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config, CorsRegistry cors) {
        // TODO Auto-generated method stub
        config.exposeIdsFor(Person.class);
    }
    
}
Anshu Mnn
  • 31
  • 2
0

You can also use the static configuration method to easily enable exposing ids in a few lines.

From the Spring Data Rest RepsositoryRestConfigurer docs:

static RepositoryRestConfigurer withConfig(Consumer<RepositoryRestConfiguration> consumer)

Convenience method to easily create simple RepositoryRestConfigurer instances that solely want to tweak the RepositoryRestConfiguration.

Parameters: consumer - must not be null.

Since: 3.1

So this works for me in an existing @Configuration-annotated class:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.rest.webmvc.config.RepositoryRestConfigurer;

@Configuration
public class ApplicationConfiguration {

    @Bean
    public RepositoryRestConfigurer repositoryRestConfigurer() {
        return RepositoryRestConfigurer.withConfig(repositoryRestConfiguration ->
            repositoryRestConfiguration.exposeIdsFor(Person.class)
        );
    }
}
homersimpson
  • 4,124
  • 4
  • 29
  • 39