Installation & configuration IntelliJ IDEA pour Spring
Boot
1 Télécharger & installer IntelliJ IDEA
Version recommandée : IntelliJ IDEA Community (gratuite, largement suffisante
pour Spring Boot).
Lien : https://www.jetbrains.com/idea/download
Choisir Community Edition (ou Ultimate si tu as une licence).
2 Lancer IntelliJ & configurer le JDK 17
1. Ouvrir File → Settings (ou Ctrl+Alt+S).
2. Aller dans Build, Execution, Deployment → Build Tools → Gradle/Maven (ou
Java Compiler).
3. Dans Project SDK → cliquer sur Add JDK → sélectionner le dossier de ton JDK
17 :
o Windows : C:\Program Files\Eclipse Adoptium\jdk-17...
o Linux : /usr/lib/jvm/java-17-openjdk-amd64
o macOS :
/Library/Java/JavaVirtualMachines/temurin-17.jdk/Contents/Home
💡 Vérifie que Project language level est bien en 17.
3 Créer un projet Spring Boot avec IntelliJ
Option A — via Spring Initializr intégré
1. File → New → Project
2. Choisir Spring Initializr
3. Service URL : https://start.spring.io
4. Renseigner :
o Group : com.example
o Artifact : demo
o Language : Java
o Java version : 17
5. Dépendances :
o Spring Web
o Spring Boot DevTools (facultatif mais pratique)
o Validation
o Lombok (optionnel)
6. Terminer → IntelliJ télécharge et configure le projet automatiquement.
Option B — importer un projet existant
1. File → Open
2. Sélectionner le dossier contenant pom.xml
3. IntelliJ détecte Maven et importe automatiquement les dépendances.
4 Structure dans IntelliJ
Dans le panneau Project :
src/main/java → code source
src/main/resources → configs (application.properties)
src/test/java → tests
pom.xml → dépendances Maven
5 Lancer le projet Spring Boot dans IntelliJ
Cliquer sur le petit ▶ vert à gauche de main
Ouvrir DemoApplication.java
Ou menu Run → Run 'DemoApplication'
Accéder ensuite à :
1) Ajouter le starter web (sinon pas de port 8080)
Dans pom.xml :
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
2) Désactiver l’auto-intégration Docker Compose (pour
enlever l’ERROR)
Dans src/main/resources/application.properties (crée le fichier si besoin) :
spring.docker.compose.enabled=false
server.port=8080
Tu pourras remettre à true plus tard si tu veux que Spring démarre compose.yaml
automatiquement, mais il faut Docker en marche.
3) Vérifier la classe main
package org.example.projet1;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Projet1Application {
public static void main(String[] args) {
SpringApplication.run(Projet1Application.class, args);
}
}
4) Ajouter un contrôleur minimal (pour avoir une page)
src/main/java/org/example/projet1/HelloController.java
package org.example.projet1;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
@GetMapping("/")
public String home() {
return "Bienvenue dans mon application Spring Boot 🚀";
}
}
Puis Accéder à la page web via : http://127.0.0.1:8080/
6 Redémarrage à chaud avec DevTools
Si spring-boot-devtools est dans pom.xml :
File → Settings → Build, Execution, Deployment → Compiler → cocher Build
project automatically
Puis Ctrl+Alt+Shift+/ → Registry → cocher
compiler.automake.allow.when.app.running
Résultat : dès que tu sauvegardes, Spring Boot redémarre.
Astuce : Si tu veux, je peux te préparer un projet Spring Boot prêt à ouvrir dans IntelliJ
IDEA avec :
Java 17
Maven
Dépendances Spring Web + DevTools
Contrôleur /api/hello déjà codé
Configuration IntelliJ prête
Veux-tu que je te fasse ce ZIP IntelliJ prêt à l’emploi pour ta formation ?
Parfait—voici un atelier de 2h “Création d’une API REST Spring Boot” prêt à dérouler, avec
Entity + Repository (JPA/Hibernate), Service, Controller, CRUD complet, et gestion
propre des réponses HTTP/erreurs.
Exemple de domaine : Product.
0) Pré-requis & dépendances (5 min)
pom.xml (extrait minimal)
xml
CopierModifier
<properties>
<java.version>17</java.version>
<spring-boot.version>3.3.0</spring-boot.version>
</properties>
<dependencies>
<!-- Web REST -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- JPA/Hibernate -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- DB de démo -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Validation @Valid -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- OpenAPI/Swagger UI (bonus) -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.6.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
application.properties
properties
CopierModifier
spring.datasource.url=jdbc:h2:mem:demo;MODE=PostgreSQL;DB_CLOSE_DELAY=-1
spring.datasource.username=sa
spring.datasource.password=
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.h2.console.enabled=true
# Swagger UI: http://localhost:8080/swagger-ui/index.html
Activer l’auditing (timestamps) dans l’app principale :
java
CopierModifier
@SpringBootApplication
@EnableJpaAuditing
public class DemoApplication { public static void main(String[] args)
{ SpringApplication.run(DemoApplication.class, args); } }
1) Modèle Entity & Repository (20 min)
Product.java
java
CopierModifier
package com.example.demo.product;
import jakarta.persistence.*;
import jakarta.validation.constraints.*;
import lombok.*;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import java.math.BigDecimal;
import java.time.Instant;
@Entity @Table(name = "products", indexes = {
@Index(name="uk_sku", columnList="sku", unique=true),
@Index(name="idx_name", columnList="name")
})
@EntityListeners(AuditingEntityListener.class)
@Getter @Setter @NoArgsConstructor @AllArgsConstructor @Builder
public class Product {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotBlank @Size(max=120)
private String name;
@NotBlank @Size(max=40)
private String sku; // unique business key
@PositiveOrZero
@Column(precision = 12, scale = 2, nullable = false)
private BigDecimal price;
@Size(max=255)
private String description;
@CreatedDate @Column(updatable = false)
private Instant createdAt;
@LastModifiedDate
private Instant updatedAt;
@Version
private Long version; // pour gestion d’update concurrent
}
ProductRepository.java
java
CopierModifier
package com.example.demo.product;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.query.Param;
import java.util.Optional;
public interface ProductRepository extends JpaRepository<Product, Long> {
Optional<Product> findBySku(@Param("sku") String sku);
boolean existsBySku(String sku);
}
2) DTO & mapping léger (10 min)
ProductRequest.java
java
CopierModifier
package com.example.demo.product.dto;
import jakarta.validation.constraints.*;
import java.math.BigDecimal;
public record ProductRequest(
@NotBlank @Size(max=120) String name,
@NotBlank @Size(max=40) String sku,
@PositiveOrZero BigDecimal price,
@Size(max=255) String description
) {}
ProductResponse.java
java
CopierModifier
package com.example.demo.product.dto;
import java.math.BigDecimal;
import java.time.Instant;
public record ProductResponse(
Long id, String name, String sku, BigDecimal price,
String description, Instant createdAt, Instant updatedAt
) {}
ProductMapper.java
java
CopierModifier
package com.example.demo.product;
import com.example.demo.product.dto.*;
public class ProductMapper {
public static Product toEntity(ProductRequest r){
return Product.builder()
.name(r.name()).sku(r.sku()).price(r.price()).description(r.descrip
tion())
.build();
}
public static ProductResponse toResponse(Product p){
return new ProductResponse(p.getId(), p.getName(), p.getSku(),
p.getPrice(),
p.getDescription(), p.getCreatedAt(), p.getUpdatedAt());
}
public static void update(Product p, ProductRequest r){
p.setName(r.name()); p.setSku(r.sku());
p.setPrice(r.price()); p.setDescription(r.description());
}
}
3) Service (règles métier + erreurs) (20 min)
NotFoundException / ConflictException
java
CopierModifier
package com.example.demo.shared;
public class NotFoundException extends RuntimeException {
public NotFoundException(String msg){ super(msg); }
}
public class ConflictException extends RuntimeException {
public ConflictException(String msg){ super(msg); }
}
ProductService.java
java
CopierModifier
package com.example.demo.product;
import com.example.demo.product.dto.*;
import com.example.demo.shared.*;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.*;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service @RequiredArgsConstructor
public class ProductService {
private final ProductRepository repo;
public Page<ProductResponse> list(Pageable pageable){
return repo.findAll(pageable).map(ProductMapper::toResponse);
}
public ProductResponse get(Long id){
var p = repo.findById(id).orElseThrow(() -> new
NotFoundException("Product %d not found".formatted(id)));
return ProductMapper.toResponse(p);
}
@Transactional
public ProductResponse create(ProductRequest req){
if (repo.existsBySku(req.sku())) throw new ConflictException("SKU
already exists");
var saved = repo.save(ProductMapper.toEntity(req));
return ProductMapper.toResponse(saved);
}
@Transactional
public ProductResponse update(Long id, ProductRequest req){
var p = repo.findById(id).orElseThrow(() -> new
NotFoundException("Product %d not found".formatted(id)));
// empêcher duplication SKU
repo.findBySku(req.sku()).filter(x -> !x.getId().equals(id))
.ifPresent(x -> { throw new ConflictException("SKU already
exists"); });
ProductMapper.update(p, req);
return ProductMapper.toResponse(p); // dirty checking
}
@Transactional
public void delete(Long id){
if (!repo.existsById(id)) throw new NotFoundException("Product %d not
found".formatted(id));
repo.deleteById(id);
}
}
4) Controller (CRUD + pagination +
@Valid) (20 min)
ProductController.java
java
CopierModifier
package com.example.demo.product;
import com.example.demo.product.dto.*;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.*;
import org.springframework.http.*;
import org.springframework.web.bind.annotation.*;
import jakarta.validation.Valid;
@RestController
@RequestMapping("/api/products")
@RequiredArgsConstructor
public class ProductController {
private final ProductService service;
// GET /api/products?page=0&size=10&sort=price,desc
@GetMapping
public ResponseEntity<Page<ProductResponse>> list(Pageable pageable){
return ResponseEntity.ok(service.list(pageable));
}
// GET /api/products/{id}
@GetMapping("/{id}")
public ResponseEntity<ProductResponse> get(@PathVariable Long id){
return ResponseEntity.ok(service.get(id));
}
// POST /api/products
@PostMapping
public ResponseEntity<ProductResponse> create(@Valid @RequestBody
ProductRequest req){
var created = service.create(req);
return ResponseEntity
.created(java.net.URI.create("/api/products/" + created.id()))
.body(created);
}
// PUT /api/products/{id}
@PutMapping("/{id}")
public ResponseEntity<ProductResponse> update(@PathVariable Long id,
@Valid @RequestBody ProductRequest req){
return ResponseEntity.ok(service.update(id, req));
}
// DELETE /api/products/{id}
@DeleteMapping("/{id}")
public ResponseEntity<Void> delete(@PathVariable Long id){
service.delete(id);
return ResponseEntity.noContent().build();
}
}
5) Gestion centralisée des erreurs & codes
HTTP (15 min)
GlobalExceptionHandler.java
java
CopierModifier
package com.example.demo.shared;
import org.springframework.http.*;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.*;
import java.time.Instant;
import java.util.*;
@RestControllerAdvice
public class GlobalExceptionHandler {
record ApiError(Instant timestamp, int status, String error, String
message, String path, Map<String,String> validation){}
@ExceptionHandler(NotFoundException.class)
public ResponseEntity<ApiError> handleNotFound(NotFoundException ex,
org.springframework.web.context.request.WebRequest req){
var body = new ApiError(Instant.now(), 404, "Not Found",
ex.getMessage(), getPath(req), null);
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(body);
}
@ExceptionHandler(ConflictException.class)
public ResponseEntity<ApiError> handleConflict(ConflictException ex,
org.springframework.web.context.request.WebRequest req){
var body = new ApiError(Instant.now(), 409, "Conflict",
ex.getMessage(), getPath(req), null);
return ResponseEntity.status(HttpStatus.CONFLICT).body(body);
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ApiError>
handleValidation(MethodArgumentNotValidException ex,
org.springframework.web.context.request.WebRequest req){
Map<String,String> errors = new LinkedHashMap<>();
ex.getBindingResult().getFieldErrors().forEach(fe ->
errors.put(fe.getField(), fe.getDefaultMessage()));
var body = new ApiError(Instant.now(), 400, "Bad Request", "Validation
failed", getPath(req), errors);
return ResponseEntity.badRequest().body(body);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<ApiError> handleOther(Exception ex,
org.springframework.web.context.request.WebRequest req){
var body = new ApiError(Instant.now(), 500, "Internal Server Error",
ex.getMessage(), getPath(req), null);
return
ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(body);
}
private String getPath(org.springframework.web.context.request.WebRequest
req){
return
Optional.ofNullable(req.getDescription(false)).orElse("").replace("uri=",""
);
}
}
Codes HTTP utilisés
200 OK : lecture / update réussi
201 Created + header Location : création
204 No Content : suppression
400 Bad Request : validation
404 Not Found : ressource absente
409 Conflict : contrainte métier (SKU dupliqué)
500 : erreur non gérée
6) Démonstration API (cURL/Postman)
(10–15 min)
Créer :
bash
CopierModifier
curl -X POST http://localhost:8080/api/products \
-H "Content-Type: application/json" \
-d '{"name":"Laptop","sku":"LP-001","price":999.90,"description":"14-
inch"}'
# -> 201 Created, Location: /api/products/1
Lister (pagination tri):
bash
CopierModifier
curl "http://localhost:8080/api/products?page=0&size=5&sort=price,desc"
Lire :
bash
CopierModifier
curl http://localhost:8080/api/products/1
Mettre à jour :
bash
CopierModifier
curl -X PUT http://localhost:8080/api/products/1 \
-H "Content-Type: application/json" \
-d '{"name":"Laptop Pro","sku":"LP-
001","price":1099.90,"description":"16-inch"}'
Supprimer :
bash
CopierModifier
curl -X DELETE http://localhost:8080/api/products/1 -i
Erreurs typiques :
bash
CopierModifier
# Validation: price négatif
curl -X POST http://localhost:8080/api/products \
-H "Content-Type: application/json" \
-d '{"name":"X","sku":"BAD","price":-5,"description":"oops"}'
# -> 400 + détails des champs invalides
# Conflit: SKU déjà pris
# -> 409 Conflict
7) Bonus utiles (10–15 min)
Swagger/OpenAPI : ouvrir http://localhost:8080/swagger-ui/index.html
H2 Console : http://localhost:8080/h2-console (JDBC URL:
jdbc:h2:mem:demo)
Tri & pagination Spring Data déjà gérés par Pageable
Idempotence : utilisez PUT pour remplacer la ressource entière, PATCH (si ajouté)
pour partiel
Validation : toutes les entrées client passent par @Valid → erreurs structurées
Découpage temporel proposé (≈2h)
0–10 min : Dépendances + config
10–30 min : Entity + Repository
30–40 min : DTO/Mapper
40–60 min : Service (règles, exceptions)
60–90 min : Controller + endpoints
90–105 min : Gestion globale des erreurs
105–120 min : Tests via cURL/Postman + Swagger
Si tu veux, je peux te fournir un ZIP de projet prêt à ouvrir dans IntelliJ avec tout ce code
déjà en place (build Maven, endpoints, Swagger).