GitHub Copilot Integration Guide
A production-ready training guide for Java, Spring Boot, Spring Batch, Servlets, JSP, MS-SQL, React, and Storybook workflows.
Purpose: Equip every developer in the organization with the skills to use GitHub Copilot effectively, responsibly, and efficiently across the full technology stack.
Table of Contents
- Introduction & Philosophy
- Architecture Overview
- Daily Developer Workflow
- Copilot in Java & Servlets/JSP
- Copilot in Spring Boot
- Copilot in Spring Batch
- Copilot with MS-SQL
- Copilot in React
- Copilot in Storybook
- Sample Project A — Spring Boot API
- Sample Project B — React + Storybook Component Library
- Prompt Patterns Reference
- Token-Saving Techniques
- AI Governance Policy
- Exercises & Evaluation Criteria
1. Introduction & Philosophy
GitHub Copilot is an AI pair programmer trained on billions of lines of public code. It integrates directly into IntelliJ IDEA Community and VS Code, the two IDEs approved for use in this organization. Copilot offers three interaction modes:
| Mode | Trigger | Best For |
|---|---|---|
| Inline Completion | Start typing | Boilerplate, method bodies, SQL |
| Copilot Chat | Ctrl+I / sidebar | Explanation, refactoring, debugging |
| Slash Commands | /explain, /fix, /tests | Targeted single-file operations |
Guiding Principles
- You are the author. Copilot suggests; you decide. Review every suggestion before accepting.
- Context is currency. The better your file, comments, and cursor position, the better the output.
- Iterate, don’t regenerate. Refine a suggestion with follow-up prompts rather than dismissing and retrying.
- Governance first. Never paste proprietary customer data, credentials, or PII into Copilot Chat.
Info: IntelliJ Community Edition requires the GitHub Copilot plugin (free tier available). VS Code requires the GitHub Copilot and GitHub Copilot Chat extensions.
2. Architecture Overview
2.1 How Copilot Processes Your Code
The diagram below shows how a developer’s keystrokes travel from the IDE through GitHub’s inference infrastructure and back as a suggestion.
2.2 Copilot in the Full-Stack Architecture
3. Daily Developer Workflow
A structured daily rhythm maximizes Copilot’s value without creating dependency or slowing review.
3.1 Workflow Checklist (Per Feature)
- Read the ticket, understand acceptance criteria before opening Copilot
- Write a plain-English comment above the method you want generated
- Accept completions in small increments — use
Ctrl+→for partial acceptance - Run tests immediately after accepting a suggestion
- Review generated code as if it came from a junior developer
- Never commit code you cannot explain line-by-line
4. Copilot in Java & Servlets/JSP
4.1 Setting Up IntelliJ Community for Copilot
- Open File → Settings → Plugins → Marketplace
- Search for “GitHub Copilot” and install
- Restart IntelliJ, then Tools → GitHub Copilot → Login to GitHub
- Verify: open any
.javafile and start typing — ghost text should appear
4.2 Generating Servlet Boilerplate
Write this comment and let Copilot fill the method:
// Servlet that handles POST /api/orders, reads JSON body,
// validates non-null orderId and positive amount, then
// writes a 201 Created response with the saved order as JSON.
@WebServlet("/api/orders")
public class OrderServlet extends HttpServlet {
// Copilot will suggest the full doPost body after you press Enter here
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// START TYPING: "String body = " → accept suggestion
}
}
Prompt tip for Chat: "Refactor this Servlet to extract business logic into an OrderService class. Keep the Servlet thin."
4.3 JSP with JSTL — Copilot Suggestions
<%-- Copilot prompt comment: Generate a JSTL forEach loop that renders
a Bootstrap 5 table of orders from the 'orderList' request attribute.
Include columns: Order ID, Customer, Amount, Status, Created Date.
--%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %>
<!-- Place cursor here and press Ctrl+I to open Copilot Chat inline -->
<table class="table table-striped">
<thead>
<tr>
<th>Order ID</th><th>Customer</th>
<th>Amount</th><th>Status</th><th>Created</th>
</tr>
</thead>
<tbody>
<%-- Copilot generates: --%>
<c:forEach var="order" items="${orderList}">
<tr>
<td><c:out value="${order.orderId}"/></td>
<td><c:out value="${order.customerName}"/></td>
<td><fmt:formatNumber value="${order.amount}" type="currency"/></td>
<td><span class="badge bg-${order.status == 'COMPLETED' ? 'success' : 'warning'}">
<c:out value="${order.status}"/>
</span></td>
<td><fmt:formatDate value="${order.createdDate}" pattern="yyyy-MM-dd"/></td>
</tr>
</c:forEach>
</tbody>
</table>
4.4 Useful Copilot Chat Prompts for Java/Servlet/JSP
| Goal | Prompt |
|---|---|
| Generate Servlet filter | "Write a Servlet Filter that logs request method, URI, and response time in ms" |
| Explain legacy code | /explain with the method selected |
| Convert Servlet → Spring MVC | "Migrate this HttpServlet doGet to a Spring @GetMapping controller method" |
| Add JSP validation | "Add client-side + server-side validation to this JSP form for email and phone fields" |
| Fix NPE | /fix on the highlighted exception stack trace |
5. Copilot in Spring Boot
5.1 Generating a REST Controller
/**
* Copilot Chat prompt:
* "Generate a Spring Boot REST controller for a Product entity with
* endpoints: GET /products, GET /products/{id}, POST /products,
* PUT /products/{id}, DELETE /products/{id}.
* Use ResponseEntity, include validation with @Valid,
* and inject ProductService via constructor injection."
*/
@RestController
@RequestMapping("/api/v1/products")
@RequiredArgsConstructor
public class ProductController {
private final ProductService productService;
@GetMapping
public ResponseEntity<List<ProductDto>> findAll(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size) {
return ResponseEntity.ok(productService.findAll(PageRequest.of(page, size)));
}
@GetMapping("/{id}")
public ResponseEntity<ProductDto> findById(@PathVariable Long id) {
return productService.findById(id)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
@PostMapping
public ResponseEntity<ProductDto> create(@Valid @RequestBody CreateProductRequest req) {
ProductDto created = productService.create(req);
URI location = URI.create("/api/v1/products/" + created.id());
return ResponseEntity.created(location).body(created);
}
@PutMapping("/{id}")
public ResponseEntity<ProductDto> update(
@PathVariable Long id,
@Valid @RequestBody UpdateProductRequest req) {
return ResponseEntity.ok(productService.update(id, req));
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> delete(@PathVariable Long id) {
productService.delete(id);
return ResponseEntity.noContent().build();
}
}
5.2 Generating Service + Repository Layer
// Copilot prompt above class: "Service layer for Product CRUD using JPA repository.
// Include pagination support and throw ResourceNotFoundException on missing records."
@Service
@RequiredArgsConstructor
@Transactional
public class ProductService {
private final ProductRepository productRepository;
private final ProductMapper mapper;
@Transactional(readOnly = true)
public List<ProductDto> findAll(Pageable pageable) {
return productRepository.findAll(pageable)
.stream()
.map(mapper::toDto)
.collect(Collectors.toList());
}
@Transactional(readOnly = true)
public Optional<ProductDto> findById(Long id) {
return productRepository.findById(id).map(mapper::toDto);
}
public ProductDto create(CreateProductRequest req) {
Product entity = mapper.toEntity(req);
return mapper.toDto(productRepository.save(entity));
}
public ProductDto update(Long id, UpdateProductRequest req) {
Product entity = productRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("Product", id));
mapper.updateEntity(entity, req);
return mapper.toDto(productRepository.save(entity));
}
public void delete(Long id) {
if (!productRepository.existsById(id)) {
throw new ResourceNotFoundException("Product", id);
}
productRepository.deleteById(id);
}
}
5.3 application.yml Generation
# Copilot Chat: "Generate a Spring Boot application.yml for MS-SQL Server
# with connection pooling via HikariCP, JPA configured for MS-SQL dialect,
# separate profiles for dev and prod, and Actuator health endpoints exposed."
spring:
application:
name: product-api
datasource:
url: jdbc:sqlserver://${DB_HOST:localhost}:1433;databaseName=${DB_NAME:productdb};encrypt=true;trustServerCertificate=false
username: ${DB_USER}
password: ${DB_PASS}
driver-class-name: com.microsoft.sqlserver.jdbc.SQLServerDriver
hikari:
maximum-pool-size: 20
minimum-idle: 5
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1800000
jpa:
database-platform: org.hibernate.dialect.SQLServer2016Dialect
hibernate:
ddl-auto: validate
show-sql: false
properties:
hibernate:
format_sql: true
jdbc:
batch_size: 50
management:
endpoints:
web:
exposure:
include: health,info,metrics,loggers
endpoint:
health:
show-details: when-authorized
5.4 Exception Handling
// Copilot Chat: "Generate a @RestControllerAdvice for this Spring Boot app.
// Handle ResourceNotFoundException → 404, MethodArgumentNotValidException → 400
// with field error details, and generic Exception → 500.
// Return a standard ApiError record."
public record ApiError(int status, String message, Instant timestamp, List<String> errors) {
public ApiError(int status, String message) {
this(status, message, Instant.now(), List.of());
}
}
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<ApiError> handleNotFound(ResourceNotFoundException ex) {
return ResponseEntity.status(404)
.body(new ApiError(404, ex.getMessage()));
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ApiError> handleValidation(MethodArgumentNotValidException ex) {
List<String> errors = ex.getBindingResult().getFieldErrors()
.stream()
.map(e -> e.getField() + ": " + e.getDefaultMessage())
.collect(Collectors.toList());
return ResponseEntity.status(400)
.body(new ApiError(400, "Validation failed", Instant.now(), errors));
}
@ExceptionHandler(Exception.class)
public ResponseEntity<ApiError> handleGeneric(Exception ex) {
return ResponseEntity.status(500)
.body(new ApiError(500, "Internal server error"));
}
}
6. Copilot in Spring Batch
Spring Batch has opinionated structure (Job → Step → Reader/Processor/Writer). Copilot is excellent at generating the plumbing once you describe the data flow.
6.1 Batch Architecture Flowchart
6.2 Generating a Batch Job
/**
* Copilot Chat prompt:
* "Generate a Spring Batch job that reads Order records from MS-SQL table 'dbo.orders'
* where status = 'PENDING', processes each by calling an external enrichment service,
* and writes the enriched records back to 'dbo.orders_processed'.
* Use chunk size 500, include skip logic for DataAccessException,
* and add a JobExecutionListener that logs start and completion times."
*/
@Configuration
@EnableBatchProcessing
@RequiredArgsConstructor
public class OrderEnrichmentJobConfig {
private final JobBuilderFactory jobs;
private final StepBuilderFactory steps;
private final DataSource dataSource;
private final EnrichmentService enrichmentService;
@Bean
public Job orderEnrichmentJob(Step enrichStep, JobExecutionListener listener) {
return jobs.get("orderEnrichmentJob")
.incrementer(new RunIdIncrementer())
.listener(listener)
.flow(enrichStep)
.end()
.build();
}
@Bean
public Step enrichStep() {
return steps.get("enrichStep")
.<PendingOrder, ProcessedOrder>chunk(500)
.reader(pendingOrderReader())
.processor(enrichmentProcessor())
.writer(processedOrderWriter())
.faultTolerant()
.skipLimit(100)
.skip(DataAccessException.class)
.retryLimit(3)
.retry(TransientDataAccessException.class)
.listener(skipListener())
.build();
}
@Bean
@StepScope
public JdbcPagingItemReader<PendingOrder> pendingOrderReader() {
SqlPagingQueryProviderFactoryBean provider = new SqlPagingQueryProviderFactoryBean();
provider.setDataSource(dataSource);
provider.setSelectClause("SELECT order_id, customer_id, amount, created_date");
provider.setFromClause("FROM dbo.orders");
provider.setWhereClause("WHERE status = 'PENDING'");
provider.setSortKeys(Map.of("order_id", Order.ASCENDING));
JdbcPagingItemReader<PendingOrder> reader = new JdbcPagingItemReader<>();
reader.setDataSource(dataSource);
reader.setPageSize(500);
reader.setRowMapper(new BeanPropertyRowMapper<>(PendingOrder.class));
try {
reader.setQueryProvider(provider.getObject());
} catch (Exception e) {
throw new BeanCreationException("Failed to create paging query provider", e);
}
return reader;
}
@Bean
public ItemProcessor<PendingOrder, ProcessedOrder> enrichmentProcessor() {
return order -> {
EnrichmentResult result = enrichmentService.enrich(order);
if (result == null) return null; // null = skip this item
return ProcessedOrder.from(order, result);
};
}
@Bean
public JdbcBatchItemWriter<ProcessedOrder> processedOrderWriter() {
return new JdbcBatchItemWriterBuilder<ProcessedOrder>()
.itemSqlParameterSourceProvider(new BeanPropertyItemSqlParameterSourceProvider<>())
.sql("""
INSERT INTO dbo.orders_processed
(order_id, customer_id, amount, enriched_data, processed_at)
VALUES
(:orderId, :customerId, :amount, :enrichedData, GETDATE())
""")
.dataSource(dataSource)
.build();
}
@Bean
public JobExecutionListener batchAuditListener() {
return new JobExecutionListenerSupport() {
@Override
public void beforeJob(JobExecution je) {
log.info("Job [{}] starting at {}", je.getJobInstance().getJobName(), Instant.now());
}
@Override
public void afterJob(JobExecution je) {
log.info("Job [{}] finished: {} | Duration: {}ms",
je.getJobInstance().getJobName(),
je.getStatus(),
je.getEndTime().getTime() - je.getStartTime().getTime());
}
};
}
}
7. Copilot with MS-SQL
7.1 Generating Complex Queries
-- Copilot Chat prompt: "Write a T-SQL query that returns the top 10 customers
-- by total order value in the last 90 days, including their order count,
-- average order value, and last order date. Use CTEs for readability."
;WITH RecentOrders AS (
SELECT
customer_id,
amount,
created_date,
ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY created_date DESC) AS rn
FROM dbo.orders
WHERE created_date >= DATEADD(DAY, -90, GETDATE())
AND status NOT IN ('CANCELLED', 'REFUNDED')
),
CustomerMetrics AS (
SELECT
customer_id,
SUM(amount) AS total_value,
COUNT(*) AS order_count,
AVG(amount) AS avg_order_value,
MAX(created_date) AS last_order_date
FROM RecentOrders
GROUP BY customer_id
)
SELECT TOP 10
cm.customer_id,
c.full_name,
c.email,
cm.total_value,
cm.order_count,
cm.avg_order_value,
cm.last_order_date
FROM CustomerMetrics cm
INNER JOIN dbo.customers c ON c.customer_id = cm.customer_id
ORDER BY cm.total_value DESC;
7.2 Generating Stored Procedures
-- Copilot Chat: "Write a stored procedure sp_UpsertProduct that accepts
-- product_id, name, price, stock_qty. If product_id exists, UPDATE;
-- if not, INSERT. Use output parameter @Result for 'INSERTED'/'UPDATED'.
-- Include error handling with TRY/CATCH and transaction."
CREATE OR ALTER PROCEDURE dbo.sp_UpsertProduct
@ProductId INT,
@Name NVARCHAR(200),
@Price DECIMAL(10,2),
@StockQty INT,
@Result NVARCHAR(10) OUTPUT
AS
BEGIN
SET NOCOUNT ON;
BEGIN TRY
BEGIN TRANSACTION;
IF EXISTS (SELECT 1 FROM dbo.products WHERE product_id = @ProductId)
BEGIN
UPDATE dbo.products
SET name = @Name,
price = @Price,
stock_qty = @StockQty,
updated_at = GETDATE()
WHERE product_id = @ProductId;
SET @Result = 'UPDATED';
END
ELSE
BEGIN
INSERT INTO dbo.products (product_id, name, price, stock_qty, created_at)
VALUES (@ProductId, @Name, @Price, @StockQty, GETDATE());
SET @Result = 'INSERTED';
END
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION;
THROW;
END CATCH
END;
GO
7.3 Index Optimization Prompts
| Scenario | Copilot Chat Prompt |
|---|---|
| Slow SELECT | "Suggest indexes for this query. Explain the trade-off between read and write performance." |
| Fragmentation | "Write T-SQL to check index fragmentation > 30% and rebuild; < 30% reorganize." |
| Query plan | "Explain this execution plan XML. Identify the costliest operation." |
| Deadlock | "I have this deadlock graph XML. What is the cause and how do I resolve it?" |
8. Copilot in React
8.1 Component Generation Workflow
// Step 1: Write the interface first — Copilot uses it as context
interface OrderCardProps {
orderId: string;
customerName: string;
amount: number;
status: 'PENDING' | 'PROCESSING' | 'COMPLETED' | 'CANCELLED';
createdDate: string;
onStatusChange?: (orderId: string, newStatus: string) => void;
}
// Step 2: Write the JSDoc comment — Copilot reads it for the implementation
/**
* Displays a single order as a card with status badge and action button.
* Status color: PENDING=amber, PROCESSING=blue, COMPLETED=green, CANCELLED=red.
* Calls onStatusChange when the action button is clicked.
*/
// Step 3: Type "const OrderCard" and let Copilot complete the component
const OrderCard: React.FC<OrderCardProps> = ({
orderId,
customerName,
amount,
status,
createdDate,
onStatusChange,
}) => {
const statusConfig = {
PENDING: { color: 'bg-amber-100 text-amber-800', label: 'Pending' },
PROCESSING: { color: 'bg-blue-100 text-blue-800', label: 'Processing' },
COMPLETED: { color: 'bg-green-100 text-green-800', label: 'Completed' },
CANCELLED: { color: 'bg-red-100 text-red-800', label: 'Cancelled' },
};
const { color, label } = statusConfig[status];
const handleAction = () => {
onStatusChange?.(orderId, status === 'PENDING' ? 'PROCESSING' : status);
};
return (
<div className="border rounded-lg p-4 shadow-sm bg-white hover:shadow-md transition-shadow">
<div className="flex justify-between items-start mb-3">
<div>
<p className="text-xs text-gray-500 font-mono">#{orderId}</p>
<h3 className="font-semibold text-gray-900">{customerName}</h3>
</div>
<span className={`text-xs px-2 py-1 rounded-full font-medium ${color}`}>
{label}
</span>
</div>
<div className="flex justify-between items-center">
<div>
<p className="text-lg font-bold text-gray-900">
{new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(amount)}
</p>
<p className="text-xs text-gray-400">
{new Date(createdDate).toLocaleDateString('en-US', { dateStyle: 'medium' })}
</p>
</div>
{status === 'PENDING' && onStatusChange && (
<button
onClick={handleAction}
className="text-sm px-3 py-1.5 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors"
>
Process
</button>
)}
</div>
</div>
);
};
export default OrderCard;
8.2 Custom Hook Generation
// Copilot Chat: "Generate a custom React hook usePaginatedOrders that fetches
// orders from /api/v1/orders with page and size params, manages loading/error state,
// provides nextPage/prevPage functions, and uses AbortController for cleanup."
function usePaginatedOrders(initialSize = 20) {
const [orders, setOrders] = useState<Order[]>([]);
const [page, setPage] = useState(0);
const [totalPages, setTotal] = useState(0);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const controller = new AbortController();
setLoading(true);
setError(null);
fetch(`/api/v1/orders?page=${page}&size=${initialSize}`, {
signal: controller.signal,
})
.then(res => {
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
})
.then(data => {
setOrders(data.content);
setTotal(data.totalPages);
})
.catch(err => {
if (err.name !== 'AbortError') setError(err.message);
})
.finally(() => setLoading(false));
return () => controller.abort();
}, [page, initialSize]);
return {
orders,
loading,
error,
page,
totalPages,
nextPage: () => setPage(p => Math.min(p + 1, totalPages - 1)),
prevPage: () => setPage(p => Math.max(p - 1, 0)),
hasPrev: page > 0,
hasNext: page < totalPages - 1,
};
}
9. Copilot in Storybook
9.1 Generating Stories with Args
// Copilot Chat: "Generate a Storybook 7 CSF3 story file for the OrderCard component.
// Include Default, Pending, Processing, Completed, Cancelled, and
// WithInteraction stories. Use @storybook/test for interaction testing."
import type { Meta, StoryObj } from '@storybook/react';
import { fn, expect, userEvent, within } from '@storybook/test';
import OrderCard from './OrderCard';
const meta: Meta<typeof OrderCard> = {
title: 'Order/OrderCard',
component: OrderCard,
tags: ['autodocs'],
argTypes: {
status: {
control: 'select',
options: ['PENDING', 'PROCESSING', 'COMPLETED', 'CANCELLED'],
},
amount: { control: { type: 'number', min: 0, max: 99999, step: 0.01 } },
},
args: {
onStatusChange: fn(),
},
};
export default meta;
type Story = StoryObj<typeof meta>;
const baseArgs = {
orderId: 'ORD-2024-00142',
customerName: 'Acme Corporation',
amount: 1_250.00,
createdDate: '2024-11-01T10:30:00Z',
};
export const Pending: Story = {
args: { ...baseArgs, status: 'PENDING' },
};
export const Processing: Story = {
args: { ...baseArgs, status: 'PROCESSING' },
};
export const Completed: Story = {
args: { ...baseArgs, status: 'COMPLETED' },
};
export const Cancelled: Story = {
args: { ...baseArgs, status: 'CANCELLED' },
};
export const WithInteraction: Story = {
args: { ...baseArgs, status: 'PENDING' },
play: async ({ canvasElement, args }) => {
const canvas = within(canvasElement);
const button = await canvas.findByRole('button', { name: /process/i });
await userEvent.click(button);
expect(args.onStatusChange).toHaveBeenCalledWith('ORD-2024-00142', 'PROCESSING');
},
};
9.2 Copilot Prompts for Storybook
| Task | Prompt |
|---|---|
| Add a new story variant | "Add a LargeAmount story with amount = 999999.99 and a LowAmount story with amount = 0.01" |
| Generate controls | "Add argTypes for all boolean props as checkbox controls" |
| Add play function | "Write a Storybook play function that tabs through all interactive elements and checks aria roles" |
| Generate docs page | "Write a JSDoc description for this component suitable for Storybook autodocs" |
10. Sample Project A — Spring Boot API
10.1 Project Structure
order-api/
├── src/
│ ├── main/
│ │ ├── java/com/company/orderapi/
│ │ │ ├── OrderApiApplication.java ← @SpringBootApplication
│ │ │ ├── config/
│ │ │ │ ├── SecurityConfig.java ← JWT + CORS
│ │ │ │ ├── SwaggerConfig.java ← OpenAPI 3 docs
│ │ │ │ └── BatchConfig.java ← Spring Batch beans
│ │ │ ├── controller/
│ │ │ │ ├── OrderController.java ← REST endpoints
│ │ │ │ └── ProductController.java
│ │ │ ├── service/
│ │ │ │ ├── OrderService.java
│ │ │ │ └── ProductService.java
│ │ │ ├── repository/
│ │ │ │ ├── OrderRepository.java ← JPA Repository
│ │ │ │ └── ProductRepository.java
│ │ │ ├── domain/
│ │ │ │ ├── entity/
│ │ │ │ │ ├── Order.java ← @Entity, @Table("dbo.orders")
│ │ │ │ │ └── Product.java
│ │ │ │ └── dto/
│ │ │ │ ├── OrderDto.java ← record
│ │ │ │ └── ProductDto.java
│ │ │ ├── batch/
│ │ │ │ ├── OrderEnrichmentJobConfig.java
│ │ │ │ ├── reader/PendingOrderReader.java
│ │ │ │ ├── processor/EnrichmentProcessor.java
│ │ │ │ └── writer/ProcessedOrderWriter.java
│ │ │ └── exception/
│ │ │ ├── ResourceNotFoundException.java
│ │ │ └── GlobalExceptionHandler.java
│ │ └── resources/
│ │ ├── application.yml
│ │ ├── application-dev.yml
│ │ ├── application-prod.yml
│ │ └── db/migration/ ← Flyway scripts
│ │ ├── V1__create_orders.sql
│ │ └── V2__create_products.sql
│ └── test/
│ └── java/com/company/orderapi/
│ ├── controller/OrderControllerTest.java
│ ├── service/OrderServiceTest.java
│ └── batch/OrderEnrichmentJobTest.java
├── pom.xml
└── README.md
10.2 Key Dependencies (pom.xml excerpt)
<!-- Copilot Chat: "Generate pom.xml dependencies for Spring Boot 3.x with
Spring Web, Spring Data JPA, Spring Batch, MS-SQL JDBC driver,
Flyway, SpringDoc OpenAPI, Lombok, MapStruct, Spring Security,
and Spring Boot Test." -->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-batch</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>com.microsoft.sqlserver</groupId>
<artifactId>mssql-jdbc</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-sqlserver</artifactId>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>1.5.5.Final</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
10.3 Flyway Migration Script (MS-SQL)
-- V1__create_orders.sql
-- Copilot Chat: "Generate a Flyway V1 migration script for MS-SQL that creates
-- the dbo.orders table with UUID PK, customer_id FK, amount decimal,
-- status NVARCHAR with CHECK constraint, timestamps, and appropriate indexes."
CREATE TABLE dbo.customers (
customer_id BIGINT NOT NULL IDENTITY(1,1) PRIMARY KEY,
full_name NVARCHAR(200) NOT NULL,
email NVARCHAR(320) NOT NULL UNIQUE,
created_at DATETIME2 NOT NULL DEFAULT GETDATE()
);
CREATE TABLE dbo.orders (
order_id BIGINT NOT NULL IDENTITY(1,1) PRIMARY KEY,
order_ref NVARCHAR(36) NOT NULL UNIQUE DEFAULT NEWID(),
customer_id BIGINT NOT NULL,
amount DECIMAL(18,2) NOT NULL,
status NVARCHAR(20) NOT NULL DEFAULT 'PENDING'
CONSTRAINT CHK_orders_status
CHECK (status IN ('PENDING','PROCESSING','COMPLETED','CANCELLED','REFUNDED')),
created_at DATETIME2 NOT NULL DEFAULT GETDATE(),
updated_at DATETIME2 NOT NULL DEFAULT GETDATE(),
CONSTRAINT FK_orders_customer
FOREIGN KEY (customer_id) REFERENCES dbo.customers(customer_id)
);
CREATE INDEX IX_orders_status ON dbo.orders (status) INCLUDE (customer_id, amount);
CREATE INDEX IX_orders_customer ON dbo.orders (customer_id) INCLUDE (status, created_at);
CREATE INDEX IX_orders_created ON dbo.orders (created_at DESC);
10.4 Integration Test with Copilot
// Copilot Chat: "Write a Spring Boot @SpringBootTest integration test for
// POST /api/v1/orders that inserts a test order and verifies the 201 response,
// Location header, and database record. Use TestRestTemplate and @Sql for setup."
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
@Sql(scripts = "/test-data/orders-setup.sql", executionPhase = BEFORE_TEST_METHOD)
@Sql(scripts = "/test-data/orders-teardown.sql", executionPhase = AFTER_TEST_METHOD)
class OrderControllerIT {
@Autowired private TestRestTemplate restTemplate;
@Autowired private OrderRepository orderRepository;
@Test
void createOrder_returns201WithLocation() {
var request = new CreateOrderRequest("CUST-001", new BigDecimal("299.99"));
ResponseEntity<OrderDto> response = restTemplate.postForEntity(
"/api/v1/orders", request, OrderDto.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.CREATED);
assertThat(response.getHeaders().getLocation()).isNotNull();
assertThat(response.getBody()).isNotNull();
assertThat(response.getBody().amount()).isEqualByComparingTo("299.99");
// Verify database persistence
Optional<Order> saved = orderRepository.findByOrderRef(response.getBody().orderRef());
assertThat(saved).isPresent();
assertThat(saved.get().getStatus()).isEqualTo(OrderStatus.PENDING);
}
}
11. Sample Project B — React + Storybook Component Library
11.1 Project Structure
order-ui/
├── src/
│ ├── components/
│ │ ├── OrderCard/
│ │ │ ├── OrderCard.tsx ← Component
│ │ │ ├── OrderCard.stories.tsx ← Storybook stories
│ │ │ ├── OrderCard.test.tsx ← Vitest unit tests
│ │ │ ├── OrderCard.module.css ← CSS Modules (optional)
│ │ │ └── index.ts ← Barrel export
│ │ ├── OrderTable/
│ │ │ ├── OrderTable.tsx
│ │ │ ├── OrderTable.stories.tsx
│ │ │ └── index.ts
│ │ └── shared/
│ │ ├── Badge/
│ │ ├── Button/
│ │ └── Spinner/
│ ├── hooks/
│ │ ├── usePaginatedOrders.ts
│ │ └── useOrderMutation.ts
│ ├── pages/
│ │ ├── OrdersPage.tsx
│ │ └── OrderDetailPage.tsx
│ ├── services/
│ │ └── orderApi.ts ← fetch wrappers
│ ├── types/
│ │ └── order.types.ts ← shared TypeScript types
│ └── App.tsx
├── .storybook/
│ ├── main.ts ← Storybook config
│ └── preview.ts ← Global decorators, themes
├── package.json
└── vite.config.ts
11.2 TypeScript Types (Copilot-Generated)
// types/order.types.ts
// Copilot Chat: "Generate TypeScript interfaces for Order, Customer,
// and OrderStatus from the MS-SQL schema defined in V1__create_orders.sql"
export type OrderStatus =
| 'PENDING'
| 'PROCESSING'
| 'COMPLETED'
| 'CANCELLED'
| 'REFUNDED';
export interface Customer {
customerId: number;
fullName: string;
email: string;
createdAt: string; // ISO 8601
}
export interface Order {
orderId: number;
orderRef: string; // UUID
customerId: number;
customer?: Customer;
amount: number;
status: OrderStatus;
createdAt: string;
updatedAt: string;
}
export interface PaginatedResponse<T> {
content: T[];
page: number;
size: number;
totalElements: number;
totalPages: number;
first: boolean;
last: boolean;
}
export type CreateOrderRequest = Pick<Order, 'customerId' | 'amount'>;
export type UpdateOrderStatusRequest = { status: OrderStatus };
11.3 Storybook Configuration
// .storybook/main.ts
import type { StorybookConfig } from '@storybook/react-vite';
const config: StorybookConfig = {
stories: ['../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
addons: [
'@storybook/addon-essentials',
'@storybook/addon-interactions',
'@storybook/addon-a11y',
'@chromatic-com/storybook',
],
framework: {
name: '@storybook/react-vite',
options: {},
},
docs: { autodocs: 'tag' },
};
export default config;
12. Prompt Patterns Reference
These patterns are proven to produce high-quality Copilot completions and Chat responses.
12.1 The CRISP Pattern
Context → Role → Input → Style → Purpose
CONTEXT: "I'm working on an order management API built with Spring Boot 3 and MS-SQL."
ROLE: "Act as a senior Java architect."
INPUT: "Here is the OrderService class: [paste code]"
STYLE: "Use constructor injection, avoid field injection. Add Javadoc."
PURPOSE: "Add optimistic locking to the update method to prevent concurrent modification."
Combined: “I’m working on a Spring Boot 3 / MS-SQL order API. Act as a senior Java architect. Here is my OrderService [paste]. Using constructor injection and Javadoc style, add optimistic locking to the update method to prevent concurrent modification.”
12.2 Pattern Library
| Pattern | Template | When to Use |
|---|---|---|
| Generate | "Generate a [type] that [does X] using [technology]. Include [constraints]." | Net new code |
| Refactor | "Refactor this [class/method] to [goal]. Keep [constraint]. Do not change [boundary]." | Improve existing |
| Explain | /explain OR "Explain what this [code] does line by line. Identify any risks." | Onboarding, review |
| Test | "Write JUnit 5 tests for [class]. Cover happy path, null input, and [edge case]." | TDD, coverage |
| Convert | "Convert this [source pattern] to [target pattern]. Preserve the original behavior." | Migration |
| Debug | /fix OR "Here is the stack trace: [paste]. Explain the root cause and suggest a fix." | Debugging |
| Optimize | "Optimize this [SQL/method] for performance. Explain each change." | Performance tuning |
| Document | "Write Javadoc/JSDoc for this [class/function]. Include @param, @returns, @throws." | Documentation |
12.3 Anti-Patterns to Avoid
❌ "Write me a login system." → Too vague. No context, no constraints.
❌ "Fix my code." (no code provided) → Copilot needs the actual code.
❌ "Is this code correct?" → Not actionable. Ask: "What are the risks?"
❌ Pasting 1,000+ lines into Chat → Exceeds context; use /explain on a selection.
❌ Accepting suggestions without reading them → Copilot can confidently generate wrong code.
13. Token-Saving Techniques
Copilot Chat has a context window limit. These techniques maximize signal-to-noise ratio.
13.1 Copilot Chat Context Hierarchy
13.2 Technique Reference
| Technique | Saving | How |
|---|---|---|
| Close unused files | High | Copilot reads all open tabs. Close unrelated files before Chat. |
Use /explain on selection | High | Select only the relevant 20–50 lines before using /explain. |
| New chat per feature | High | Start a fresh chat at each feature boundary. Don’t carry long history. |
| Reference by name, not paste | Medium | Say “in the OrderService.create() method” instead of pasting it. |
| Use slash commands | Medium | /fix, /tests, /doc are shorter than equivalent natural language. |
| Summarize before continuing | Medium | Ask Copilot to “summarize the decisions made so far” and paste that summary into a new chat. |
| Write precise interfaces first | Medium | A well-typed interface gives Copilot all the context it needs without prose. |
| Avoid repetition in prompts | Low | Don’t re-describe the tech stack in every message. Set it once at chat start. |
13.3 Chat Initialization Template (paste at start of every session)
STACK: Java 21, Spring Boot 3.2, Spring Batch 5, MS-SQL 2019, React 18, Storybook 8
STYLE: Constructor injection, Records for DTOs, Functional React components, TypeScript strict
CONSTRAINTS: No Lombok on public APIs, use Optional not null returns, chunk size 500 for Batch
CURRENT TASK: [describe the feature you are working on]
14. AI Governance Policy
Warning: Mandatory Reading. All developers must read and acknowledge this section before using GitHub Copilot with organizational code.
14.1 What You May Do ✅
- Use Copilot to generate boilerplate, unit tests, SQL queries, and documentation
- Use Copilot Chat to explain, debug, and refactor code you own
- Accept Copilot suggestions after reviewing them critically
- Use Copilot for learning and exploration in sandbox/local environments
- Generate data migration scripts (review before execution)
14.2 What You Must Never Do ❌
| Prohibited Action | Risk | Enforcement |
|---|---|---|
| Paste customer PII into Chat | Data privacy violation | Automatic audit log review |
| Paste API keys, passwords, secrets | Credential exposure | Secret scanning on commit |
| Accept suggestions without reading | Introducing vulnerabilities | Code review checklist |
| Use Copilot to bypass security reviews | Compliance failure | PR gate |
| Commit AI-generated code without tests | Quality regression | CI test gate |
| Submit AI-generated code as your own without attribution | IP risk | PR template requires disclosure |
14.3 Data Classification Rules
14.4 Code Review Requirements for AI-Generated Code
Every PR containing AI-assisted code must:
- Label the PR with the
ai-assistedtag - Pass all automated gates: unit tests, integration tests, static analysis (SonarQube), secret scanning
- Reviewer checklist:
- I read every line of AI-generated code
- I understand what each line does and can explain it
- No hardcoded values that should be configurable
- No unexplained magic numbers or SQL literals
- Error handling is explicit, not implicit
- No copyright or license markers in generated code
- Document the prompt used if the generated code is non-trivial (add to PR description)
14.5 Incident Response
If you accidentally paste sensitive data into Copilot Chat:
- Immediately close the Copilot Chat session
- Report to your team lead within 30 minutes
- Rotate any credentials that were exposed
- Document the incident in the security incident tracker
- GitHub does not store or train on Copilot for Business prompts, but treat it as potentially exposed
15. Exercises & Evaluation Criteria
15.1 Exercise Set A — Java & Spring Boot (Beginner)
Exercise A1: Servlet Refactoring
Take the provided
LegacyOrderServlet.java(100+ line doGet method) and use Copilot to:
- Extract business logic into an
OrderServiceclass- Add input validation
- Write 3 unit tests
Evaluation Criteria:
| Criterion | Points | Standard |
|---|---|---|
| Service correctly extracted | 20 | No business logic remains in Servlet |
| Validation covers null + empty | 15 | Both null and empty string handled |
| Unit tests pass without mocking the Servlet | 15 | Pure service tests |
| Code compiles and runs | 10 | No syntax errors |
| Copilot prompts documented in PR | 10 | At least 2 prompts shown |
| Total | 70 | Pass: 49/70 |
Exercise A2: Spring Boot REST Endpoint
Generate a complete CRUD REST API for an
Employeeentity backed by MS-SQL. Include validation, pagination, and global error handling.
15.2 Exercise Set B — Spring Batch (Intermediate)
Exercise B1: CSV-to-Database Job
Create a Spring Batch job that:
- Reads from a CSV file with 10,000 rows (provided)
- Validates each row (required fields, numeric ranges)
- Writes valid rows to
dbo.employees, skips invalid rows- Produces a summary report at job completion
Evaluation Criteria:
| Criterion | Points | Standard |
|---|---|---|
| Job completes without exception | 20 | All 10k rows processed |
| Chunk size configured appropriately | 10 | Between 100–1000 |
| Skip logic correctly implemented | 15 | Invalid rows logged, not thrown |
| Summary report generated | 15 | Shows processed/skipped counts |
| Integration test covers job execution | 15 | Job runs in test context |
| Token-saving techniques applied | 10 | New chat per component |
| Total | 85 | Pass: 60/85 |
15.3 Exercise Set C — React + Storybook (Intermediate)
Exercise C1: Data Table Component
Use Copilot to build an
OrderTablecomponent that:
- Renders paginated orders from the API hook
- Supports column sorting (click header)
- Shows a loading skeleton while fetching
- Has at least 4 Storybook stories (Default, Loading, Empty, Sorted)
Exercise C2: Storybook Interaction Test
Add a
playfunction to one of your stories that:
- Clicks a sortable column header
- Verifies the sort icon changes direction
- Verifies the data reorders
Evaluation Criteria:
| Criterion | Points | Standard |
|---|---|---|
| Component renders without errors | 15 | No console errors |
| Pagination works correctly | 15 | Next/prev, boundary behavior |
| Sort changes data order | 20 | Ascending and descending |
| Loading skeleton visible during fetch | 10 | At least 500ms simulated delay |
| 4+ stories with meaningful args | 20 | Each story shows distinct state |
| Play function passes in Storybook test runner | 20 | storybook test passes |
| Total | 100 | Pass: 70/100 |
15.4 Exercise Set D — Governance (All Levels)
Exercise D1: Prompt Audit
Review the provided list of 10 Copilot Chat prompts. Classify each as:
- ✅ Safe to use as-is
- ⚠️ Safe with modification (explain what to change)
- ❌ Prohibited (explain why)
Exercise D2: Token Optimization
Given a 10-message Copilot Chat history, identify which messages could be removed or summarized without losing essential context. Estimate token savings.
Appendix A: Quick Reference Card
Keyboard Shortcuts
| Action | IntelliJ | VS Code |
|---|---|---|
| Accept suggestion | Tab | Tab |
| Reject suggestion | Esc | Esc |
| Next suggestion | Alt+] | Alt+] |
| Prev suggestion | Alt+[ | Alt+[ |
| Partial accept (word) | Ctrl+→ | Ctrl+→ |
| Open Copilot Chat | Ctrl+I | Ctrl+I |
| Explain selection | /explain in Chat | /explain in Chat |
| Fix selection | /fix in Chat | /fix in Chat |
| Generate tests | /tests in Chat | /tests in Chat |
IDE Setup Links
- IntelliJ Plugin:
https://plugins.jetbrains.com/plugin/17718-github-copilot - VS Code Extension ID:
GitHub.copilotandGitHub.copilot-chat - Copilot Docs:
https://docs.github.com/en/copilot
Document version 1.0.0 · Platform Engineering · Last updated May 2026 Review cycle: Quarterly · Owner: Platform Engineering Lead
10.2 Starter: OrderApiApplication.java
@SpringBootApplication
@EnableScheduling
public class OrderApiApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApiApplication.class, args);
}
}
10.3 Starter: Order Entity (MS-SQL mapped)
// Copilot Chat: "Generate a JPA entity for the dbo.orders table in MS-SQL.
// Fields: orderId (Long, PK, identity), customerId (Long), amount (BigDecimal),
// status (enum OrderStatus), createdDate (Instant), updatedDate (Instant).
// Add @CreatedDate and @LastModifiedDate with @EntityListeners(AuditingEntityListener.class)."
@Entity
@Table(name = "orders", schema = "dbo")
@EntityListeners(AuditingEntityListener.class)
@Getter @Setter
@NoArgsConstructor
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "order_id")
private Long orderId;
@Column(name = "customer_id", nullable = false)
private Long customerId;
@Column(nullable = false, precision = 10, scale = 2)
private BigDecimal amount;
@Enumerated(EnumType.STRING)
@Column(nullable = false, length = 20)
private OrderStatus status = OrderStatus.PENDING;
@CreatedDate
@Column(name = "created_date", nullable = false, updatable = false)
private Instant createdDate;
@LastModifiedDate
@Column(name = "updated_date")
private Instant updatedDate;
}
10.4 Starter: Flyway Migration V1
-- src/main/resources/db/migration/V1__create_orders.sql
-- Copilot Chat: "Generate MS-SQL DDL for an orders table with all columns in the Order entity.
-- Add non-clustered index on customer_id and status."
CREATE TABLE dbo.orders (
order_id BIGINT NOT NULL IDENTITY(1,1),
customer_id BIGINT NOT NULL,
amount DECIMAL(10,2) NOT NULL,
status NVARCHAR(20) NOT NULL DEFAULT 'PENDING',
created_date DATETIME2(7) NOT NULL DEFAULT SYSUTCDATETIME(),
updated_date DATETIME2(7) NULL,
CONSTRAINT PK_orders PRIMARY KEY CLUSTERED (order_id),
CONSTRAINT CHK_orders_status CHECK (status IN ('PENDING','PROCESSING','COMPLETED','CANCELLED'))
);
GO
CREATE NONCLUSTERED INDEX IX_orders_customer_id ON dbo.orders (customer_id);
CREATE NONCLUSTERED INDEX IX_orders_status ON dbo.orders (status) INCLUDE (customer_id, amount);
GO
10.5 Starter: Unit Test with Copilot
// Copilot Chat: "Write JUnit 5 unit tests for OrderService.create().
// Mock OrderRepository and OrderMapper. Test: happy path returns saved DTO,
// null request throws IllegalArgumentException."
@ExtendWith(MockitoExtension.class)
class OrderServiceTest {
@Mock private OrderRepository orderRepository;
@Mock private OrderMapper mapper;
@InjectMocks private OrderService sut;
@Test
void create_happyPath_returnsSavedDto() {
// Arrange
var req = new CreateOrderRequest(1L, BigDecimal.TEN);
var entity = new Order();
var saved = new Order(); saved.setOrderId(42L);
var dto = new OrderDto(42L, 1L, BigDecimal.TEN, OrderStatus.PENDING, Instant.now(), null);
given(mapper.toEntity(req)).willReturn(entity);
given(orderRepository.save(entity)).willReturn(saved);
given(mapper.toDto(saved)).willReturn(dto);
// Act
OrderDto result = sut.create(req);
// Assert
assertThat(result.orderId()).isEqualTo(42L);
then(orderRepository).should().save(entity);
}
@Test
void create_nullRequest_throwsIllegalArgument() {
assertThatThrownBy(() -> sut.create(null))
.isInstanceOf(IllegalArgumentException.class);
}
}
11. Sample Project B — React + Storybook Component Library
11.1 Project Structure
order-ui/
├── .storybook/
│ ├── main.ts ← Vite builder, addons config
│ └── preview.ts ← global decorators, Tailwind import
├── src/
│ ├── components/
│ │ ├── order/
│ │ │ ├── OrderCard/
│ │ │ │ ├── OrderCard.tsx ← component (see § 8.1)
│ │ │ │ ├── OrderCard.stories.tsx ← CSF3 stories (see § 9.1)
│ │ │ │ ├── OrderCard.test.tsx ← Vitest + Testing Library
│ │ │ │ └── index.ts ← re-export
│ │ │ └── OrderList/
│ │ │ ├── OrderList.tsx
│ │ │ ├── OrderList.stories.tsx
│ │ │ └── index.ts
│ │ └── common/
│ │ ├── Button/
│ │ ├── Badge/
│ │ └── Pagination/
│ ├── hooks/
│ │ ├── usePaginatedOrders.ts ← custom hook (see § 8.2)
│ │ └── useOrderMutation.ts
│ ├── types/
│ │ └── order.types.ts ← shared TypeScript interfaces
│ ├── utils/
│ │ ├── currency.ts
│ │ └── date.ts
│ └── App.tsx
├── package.json
├── tsconfig.json
├── vite.config.ts
└── tailwind.config.js
11.2 Storybook main.ts
// .storybook/main.ts
// Copilot Chat: "Configure Storybook 8 main.ts for a React + Vite + Tailwind project.
// Include @storybook/addon-essentials, @storybook/addon-interactions,
// @storybook/addon-a11y, and autodocs."
import type { StorybookConfig } from '@storybook/react-vite';
const config: StorybookConfig = {
stories: ['../src/**/*.stories.@(js|jsx|ts|tsx|mdx)'],
addons: [
'@storybook/addon-essentials',
'@storybook/addon-interactions',
'@storybook/addon-a11y',
'@storybook/addon-links',
],
framework: {
name: '@storybook/react-vite',
options: {},
},
docs: {
autodocs: 'tag',
},
typescript: {
reactDocgen: 'react-docgen-typescript',
},
};
export default config;
11.3 OrderList Component
// Copilot Chat: "Generate an OrderList component that uses usePaginatedOrders hook.
// Show a loading spinner while fetching, error message on failure,
// a responsive grid of OrderCard components, and Pagination controls at the bottom."
import { usePaginatedOrders } from '../../hooks/usePaginatedOrders';
import OrderCard from '../order/OrderCard';
import Pagination from '../common/Pagination';
const OrderList: React.FC = () => {
const {
orders, loading, error,
page, totalPages, hasPrev, hasNext,
nextPage, prevPage,
} = usePaginatedOrders(12);
if (loading) return (
<div className="flex justify-center items-center h-64">
<div className="w-10 h-10 border-4 border-blue-500 border-t-transparent rounded-full animate-spin" />
</div>
);
if (error) return (
<div className="p-4 bg-red-50 border border-red-200 rounded-lg text-red-700">
<strong>Error:</strong> {error}
</div>
);
return (
<div className="space-y-6">
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
{orders.map(order => (
<OrderCard key={order.orderId} {...order} />
))}
</div>
{totalPages > 1 && (
<Pagination
currentPage={page}
totalPages={totalPages}
onPrev={prevPage}
onNext={nextPage}
hasPrev={hasPrev}
hasNext={hasNext}
/>
)}
</div>
);
};
export default OrderList;
11.4 Vitest Unit Test
// Copilot Chat: "Write Vitest + React Testing Library tests for OrderCard.
// Test: renders order ID, renders amount as currency, PENDING shows Process button,
// COMPLETED hides button, clicking Process calls onStatusChange."
import { render, screen, fireEvent } from '@testing-library/react';
import { describe, it, expect, vi } from 'vitest';
import OrderCard from './OrderCard';
const base = {
orderId: 'ORD-001',
customerName: 'Test Corp',
amount: 1250,
status: 'PENDING' as const,
createdDate: '2024-01-15T10:00:00Z',
};
describe('OrderCard', () => {
it('renders order ID', () => {
render(<OrderCard {...base} />);
expect(screen.getByText(/#ORD-001/)).toBeInTheDocument();
});
it('formats amount as USD currency', () => {
render(<OrderCard {...base} />);
expect(screen.getByText('$1,250.00')).toBeInTheDocument();
});
it('shows Process button when PENDING', () => {
render(<OrderCard {...base} />);
expect(screen.getByRole('button', { name: /process/i })).toBeInTheDocument();
});
it('hides button when COMPLETED', () => {
render(<OrderCard {...base} status="COMPLETED" />);
expect(screen.queryByRole('button')).not.toBeInTheDocument();
});
it('calls onStatusChange with PROCESSING when Process clicked', () => {
const onStatusChange = vi.fn();
render(<OrderCard {...base} onStatusChange={onStatusChange} />);
fireEvent.click(screen.getByRole('button', { name: /process/i }));
expect(onStatusChange).toHaveBeenCalledWith('ORD-001', 'PROCESSING');
});
});
12. Prompt Patterns Reference
Effective prompting is a learnable skill. These patterns are organized by intent and work across all languages in the stack.
12.1 The ROLE-CONTEXT-TASK-CONSTRAINT Pattern
"Act as a [ROLE] working on [CONTEXT].
Your task is to [TASK].
Constraints: [CONSTRAINT 1], [CONSTRAINT 2]."
Example:
"Act as a senior Spring Boot developer working on a high-traffic REST API.
Your task is to refactor the OrderService.findAll() method to use database-level
pagination instead of loading all records into memory.
Constraints: keep the public method signature unchanged, use JPA Pageable,
do not change the existing unit tests."
12.2 Pattern Catalogue
| Pattern Name | Template | When to Use |
|---|---|---|
| Boilerplate Gen | "Generate [artifact] for [entity] with [fields]. Use [framework/style]." | Entities, DTOs, controllers |
| Explain It | /explain or "Explain this code in plain English. Identify any anti-patterns." | Legacy code, onboarding |
| Fix + Reason | /fix or "Fix this bug. Explain the root cause before giving the fix." | Debugging |
| Test First | "Write failing unit tests for [method]. Do not implement the method." | TDD workflow |
| Refactor | "Refactor this to [pattern]. Keep behavior identical. Show diff." | Code quality |
| Migrate | "Migrate this [old tech] implementation to [new tech]. Preserve all logic." | Modernization |
| Document | "Write Javadoc/JSDoc for every public method in this file." | Documentation debt |
| Security | "Review this code for OWASP Top 10 vulnerabilities. List each risk." | Security review |
| SQL Explain | "Explain this SQL query in business terms. Suggest performance improvements." | DBA collaboration |
| Batch Debug | "This Spring Batch job fails at step 2. Trace the failure path through Reader→Processor→Writer." | Batch troubleshooting |
12.3 Multi-Turn Refinement Workflow
Turn 1: "Generate a Spring Boot service for Order CRUD."
Turn 2: "Add pagination to findAll(). Return Page<OrderDto>."
Turn 3: "Add a method findByCustomerId() with the same pagination."
Turn 4: "Now add @Cacheable on findById() with a 5-minute TTL."
Turn 5: "Write unit tests for all four methods."
This “progressive deepening” approach produces better results than one massive prompt and builds up the context window with agreed-upon code.
13. Token-Saving Techniques
Every Copilot Chat session has a context window limit. Burning tokens on irrelevant content degrades suggestion quality and risks truncation.
13.1 Context Window Flowchart
13.2 Token-Saving Rules
| Rule | Do | Don’t |
|---|---|---|
| Scope precisely | #file:OrderService.java | @workspace for single-class questions |
| New chat per task | Start fresh session for unrelated feature | Continue 50-message thread |
| Concise comments | One-line intent comment above method | Paragraph-length essay in file |
| Reference, don’t paste | Reference the file path | Paste 500 lines into chat |
| Close unused tabs | Keep ≤3 related files open | 20 open tabs polluting context |
| Use slash commands | /fix, /explain, /tests | Full natural-language re-description |
| Summarize long context | "Summary: the above service handles..." | Let conversation grow unchecked |
13.3 IDE-Specific Tips
IntelliJ IDEA Community:
- Use Alt+Enter → Ask Copilot on highlighted code — tight, focused context
- Keep the Copilot tool window on a secondary monitor; close it when not in use
- Prefer inline suggestions (ghost text) for known APIs; save Chat for logic
VS Code:
- Use
#selectioninstead of#filewhen only 20 lines are relevant - Pin the Copilot Chat tab in a split editor to avoid losing history
- Install Copilot Labs extension for experimental features (summarize, brushes)
13.4 Writing Comments That Maximize Suggestion Quality
// ❌ Vague — Copilot produces generic boilerplate
// Get orders
// ✅ Specific — Copilot produces production-quality code
// Fetch orders for customerId where status IN (PENDING, PROCESSING),
// sorted by createdDate DESC, paginated. Throw ResourceNotFoundException
// if customerId does not exist in dbo.customers.
// ❌ Too vague
// Order card component
// ✅ Actionable
// Card showing orderId, customerName, formatted USD amount, status badge (color-coded),
// and a "Process" button visible only when status === 'PENDING'.
// Tailwind CSS. Accessible: button has aria-label. No external icon library.
14. AI Governance Policy
Warning: This section contains mandatory organizational policy. All developers must read and acknowledge this before using GitHub Copilot in any company repository.
14.1 Data Classification & Copilot Chat
| Data Type | Copilot Chat Allowed? | Action |
|---|---|---|
| Public documentation / specs | ✅ Yes | Paste freely |
| Internal architecture diagrams | ✅ Yes | Describe in text |
| Generic code patterns (no real data) | ✅ Yes | Paste freely |
| Production database schemas (no data) | ⚠️ Caution | Anonymize table/column names if sensitive |
| Customer PII (name, email, SSN, etc.) | ❌ Never | Do not paste under any circumstances |
| API keys, passwords, tokens | ❌ Never | Use environment variables; never paste |
| HIPAA / PCI-DSS protected data | ❌ Never | Compliance violation — reportable incident |
| Proprietary business logic (core IP) | ⚠️ Caution | Review with tech lead before sharing |
14.2 Code Review Requirements for AI-Generated Code
All code accepted from Copilot must pass the same review standards as human-authored code. Reviewers should additionally check:
- Correctness: Does the logic handle edge cases (null, empty, overflow)?
- Security: No SQL injection surfaces, no hardcoded secrets, no unsafe deserialization
- Performance: No N+1 queries, appropriate pagination, no blocking I/O in reactive paths
- Licensing: Copilot sometimes surfaces code matching public repositories. Flag any output that looks identical to a known library’s implementation.
- Tests: AI-generated tests must have real assertions, not just
assertNotNull(result) - Attribution: PRs that are >50% AI-generated should include a comment:
// Generated with GitHub Copilot — reviewed by [Author]
14.3 Acceptable Use Policy
PERMITTED PROHIBITED
───────────────────────────────────────── ──────────────────────────────────────────
✅ Generating boilerplate code ❌ Bypassing code review via AI-generation
✅ Writing unit/integration tests ❌ Pasting production data into Chat
✅ Explaining legacy code ❌ Using Copilot to circumvent security review
✅ Suggesting SQL query optimizations ❌ Auto-committing suggestions without review
✅ Drafting technical documentation ❌ Using on classified/restricted projects
✅ Refactoring for readability ❌ Sharing output externally without review
✅ Debugging with /fix ❌ Relying solely on AI for security analysis
14.4 Incident Reporting
If you suspect you have accidentally submitted PII or credentials to Copilot Chat:
- Do not panic. GitHub does not store Copilot Chat messages after the session ends (per GitHub’s data handling policy — verify with your GitHub Enterprise admin for org-specific settings).
- Document what was submitted — data type, approximate volume, timestamp.
- Report within 2 hours to your team lead and the Security team via the internal incident portal.
- Follow the Data Breach Response Checklist in the security wiki.
14.5 Copilot Settings — Required Organization Configuration
Your GitHub organization admin should enforce these settings in GitHub Enterprise → Copilot Policy:
# Required organizational Copilot settings
copilot:
suggestions:
public_code_matching: blocked # Block suggestions matching public repo code
chat:
web_search: disabled # Disable web search in Chat (data privacy)
seat_management:
assignment: selected_teams_only # Limit to approved development teams
audit_log:
enabled: true # All usage logged for compliance
14.6 Governance Flowchart
15. Exercises & Evaluation Criteria
15.1 Exercise Set A — Java & Spring Boot (Beginner)
Exercise A1: Servlet Migration
Using Copilot, migrate the provided
CustomerServlet.java(doGet + doPost) into a Spring Boot@RestController. The controller must preserve all original endpoints, add input validation with@Valid, and return proper HTTP status codes.
Evaluation Criteria:
| Criterion | Points |
|---|---|
| All original endpoints present and functional | 20 |
| Input validation with field-level error messages | 15 |
| Correct HTTP status codes (200/201/400/404/500) | 15 |
| Constructor injection used (no @Autowired field injection) | 10 |
| At least 3 Copilot Chat prompts documented in PR description | 10 |
| Unit tests with ≥80% method coverage | 20 |
| Code passes reviewer AI checklist (§14.2) | 10 |
| Total | 100 |
Exercise A2: Spring Batch Job
Create a Spring Batch job that reads from
dbo.customers(status = ‘INACTIVE’), calls a stubReactivationService.evaluate()on each record, and writes ELIGIBLE records todbo.reactivation_candidates. Include skip/retry and aJobExecutionListenerthat logs summary stats.
Evaluation Criteria:
| Criterion | Points |
|---|---|
| Job completes without error on 10,000-row test dataset | 25 |
| Chunk size configured appropriately (≥100) | 10 |
| Skip and retry policies implemented and tested | 15 |
| Null return from processor correctly skips the item | 10 |
| Listener logs: job name, status, items read/written/skipped | 15 |
Integration test using @SpringBatchTest | 15 |
| Copilot prompts documented (min 5 prompts used) | 10 |
| Total | 100 |
15.2 Exercise Set B — React & Storybook (Intermediate)
Exercise B1: Data Table Component
Build a
CustomerTablecomponent in React + TypeScript that:
- Displays customers with sortable columns (name, email, status, created date)
- Supports client-side filtering by name
- Uses the provided
useCustomershook- Has full Storybook coverage (4+ stories including an interaction test)
- Is fully keyboard accessible
Evaluation Criteria:
| Criterion | Points |
|---|---|
| All columns render correctly with proper data types | 15 |
| Sorting works for all 4 columns (asc/desc toggle) | 20 |
| Filter updates table in real time | 10 |
4+ Storybook stories including WithInteraction | 20 |
| Zero axe accessibility violations in Storybook a11y panel | 15 |
| Vitest tests ≥75% coverage | 10 |
| Copilot prompts documented (min 5) | 10 |
| Total | 100 |
15.3 Exercise Set C — MS-SQL & Integration (Advanced)
Exercise C1: Query Optimization
The following query takes 8+ seconds on a 5M-row table. Use Copilot to:
- Understand the query’s intent
- Identify the performance bottleneck
- Propose and implement index changes
- Rewrite using CTEs for readability
- Document the change in a migration script
Evaluation Criteria:
| Criterion | Points |
|---|---|
| Correct identification of bottleneck (e.g., missing index, full table scan) | 20 |
| New query executes in <1 second on test dataset | 20 |
At least one new index with INCLUDE columns | 15 |
| CTE rewrite is semantically equivalent | 15 |
| Flyway migration script follows naming convention | 10 |
| Explain plan before/after documented in PR | 10 |
| Copilot prompts used and documented | 10 |
| Total | 100 |
15.4 Copilot Proficiency Self-Assessment
Rate yourself after completing the exercises:
| Skill Area | Novice (1) | Developing (2) | Proficient (3) | Expert (4) |
|---|---|---|---|---|
| Writing effective prompts | Vague, one-liners | Context included | ROLE-CONTEXT-TASK-CONSTRAINT pattern | Multi-turn refinement, consistent output |
| Reviewing AI suggestions | Accept all | Check syntax | Check logic + edge cases | Full security + performance review |
| Token management | @workspace always | Files open as needed | #file references | New chat per context, comments optimized |
| Governance compliance | Unaware | Knows rules | Applies rules consistently | Can advise teammates |
| Test generation | No tests | Tests exist | Tests have real assertions | TDD: tests before implementation |
Target: All developers at Proficient (3) within 30 days. Expert (4) for tech leads.
Appendix A — Quick Reference Card
┌──────────────────────────────────────────────────────────────┐
│ GITHUB COPILOT — DAILY QUICK REFERENCE │
├──────────────────┬───────────────────────────────────────────┤
│ INLINE │ Tab = accept | Esc = reject │
│ │ Ctrl+→ = accept word | Alt+[ / ] = cycle│
├──────────────────┼───────────────────────────────────────────┤
│ CHAT TRIGGERS │ Ctrl+I = inline chat │
│ │ Ctrl+Shift+I = sidebar chat │
├──────────────────┼───────────────────────────────────────────┤
│ SLASH COMMANDS │ /explain /fix /tests /doc /simplify │
├──────────────────┼───────────────────────────────────────────┤
│ CONTEXT REFS │ #file:Foo.java #selection #codebase │
├──────────────────┼───────────────────────────────────────────┤
│ TOKEN SAVING │ New chat per task | Close unused tabs │
│ │ Use #file not @workspace | Brief comments │
├──────────────────┼───────────────────────────────────────────┤
│ NEVER PASTE │ PII | Passwords | Customer data | Secrets │
├──────────────────┼───────────────────────────────────────────┤
│ ALWAYS REVIEW │ Logic | Edge cases | Security | Tests │
└──────────────────┴───────────────────────────────────────────┘
Appendix B — Recommended Resources
| Resource | URL | Notes |
|---|---|---|
| GitHub Copilot Docs | docs.github.com/copilot | Official, always current |
| Copilot for Java (JetBrains) | plugins.jetbrains.com/plugin/17718 | IntelliJ plugin page |
| Spring Boot Reference | docs.spring.io/spring-boot | Copilot loves official docs as context |
| Storybook Docs | storybook.js.org/docs | CSF3 format reference |
| OWASP Top 10 | owasp.org/Top10 | Use for security review checklist |
| GitHub Copilot Trust Center | resources.github.com/copilot-trust-center | Data handling, privacy |