SignedText: 2025 FRQ 2
A step-by-step solution to the 2025 AP CSA FRQ 2 (SignedText), covering building a formatted signature string and rewriting text based on where that signature already appears in Java.
Appending a signature to the end of a message — unless it's already there — is the job of the class in this AP Computer Science A free-response question, and getting it right means carefully working through three different cases for where that signature might already sit.
What This FRQ Tests
- AP CSA units: Unit 5 (Writing Classes)
- Core skill: designing a class from scratch, including a method that formats a value from two stored fields
- Secondary skill: checking a fixed-length prefix and suffix of a
Stringto decide between three different outcomes - Official category: "Classes" — always FRQ 2 on the AP CSA exam
The Setup
- No class skeleton is provided — the entire
SignedTextclass is up to you to design. - The constructor takes a first name and a last name (the last name's length is always at least
1). getSignature()builds a signature string:- If the first name is empty, the signature is just the last name.
- Otherwise, the signature is the first letter of the first name, a dash, and the last name.
addSignature(String text)returns a revised copy oftext, which is guaranteed to contain the signature at most once, and only at the very beginning or the very end:- If the signature doesn't appear in
textat all, append it to the end. - If it already appears at the end, return
textunchanged. - If it appears at the beginning, move it: strip it off the front and append it to the end instead.
- If the signature doesn't appear in
Building the SignedText Class
Step-by-Step Approach
- Store the first and last names as fields in the constructor.
- Write
getSignature(): check whether the first name is empty first, since that decides which of the two formats to build. - Write
addSignature(text): compute the signature once, then check the end oftextfirst (since "already correctly placed" should short-circuit everything else), then the beginning, and only fall through to "append it" if neither matched.
The Code
public class SignedText
{
private String firstName;
private String lastName;
public SignedText(String first, String last)
{
firstName = first;
lastName = last;
}
public String getSignature()
{
if (firstName.equals(""))
{
return lastName;
}
else
{
return firstName.substring(0, 1) + "-" + lastName;
}
}
public String addSignature(String text)
{
String signature = getSignature();
int sigLen = signature.length();
if (text.length() >= sigLen && text.substring(text.length() - sigLen).equals(signature))
{
return text;
}
else if (text.length() >= sigLen && text.substring(0, sigLen).equals(signature))
{
return text.substring(sigLen) + signature;
}
else
{
return text + signature;
}
}
}
Why Each Piece Matters
firstName.equals("")— checked first ingetSignature(), since it decides between two completely different formats, not a variation on one format.firstName.substring(0, 1)— pulls out just the first character as aString, sincecharAtisn't part of the method subset this solution sticks to.- Checking the end of
textbefore the beginning inaddSignature— the precondition guarantees the signature appears in at most one of those two positions, but checking end-first means "already correctly placed" is recognized immediately, without needing to also rule out the beginning case first. text.length() >= sigLen, guarding both substring checks — without it,text.substring(text.length() - sigLen)ortext.substring(0, sigLen)could receive a negative or out-of-range index and throw an exception on atextshorter than the signature itself.text.substring(sigLen) + signature— when the signature is found at the beginning, this strips exactly that many characters off the front (everything from indexsigLenonward) and reattaches the signature at the end.
Tracing the Example
Using the question's own execution sequence, with st4 = new SignedText("", "FOX") (signature "FOX", length 3):
| Call | End matches? | Start matches? | Result |
|---|---|---|---|
st4.addSignature("Dear") |
no | no | "DearFOX" |
st4.addSignature("Best wishesFOX") |
yes | — | "Best wishesFOX" (unchanged) |
st4.addSignature("FOXThanks") |
no | yes | "Thanks" + "FOX" = "ThanksFOX" |
And with st3 = new SignedText("GRACE", "LOPEZ") (signature "G-LOPEZ", length 7):
st3.addSignature("G-LOPEZHello"): end check fails (last 7 characters are"ZHello"... actually the last 7 characters of a 12-character string starting at index 5 don't match"G-LOPEZ"), start check succeeds ("G-LOPEZ"is exactly the first 7 characters) → returns"Hello" + "G-LOPEZ"="HelloG-LOPEZ".
Both results match the question's table exactly, including the case where the signature has to be relocated from front to back.
Common Mistakes to Avoid
- Checking the beginning before the end. Since the precondition guarantees the signature appears at most once, checking order doesn't change correctness here — but checking the end first keeps the "no change needed" case simple and matches the order the rules are listed in the question.
- Forgetting the length guard before calling
substring. Atextshorter than the signature would otherwise crash with aStringIndexOutOfBoundsExceptioninstead of correctly falling through to "doesn't contain it, append." - Building the first-name check backwards — testing
!firstName.equals("")first and putting the "just the last name" case in theelsealso works, but flips which branch does which; either is fine as long as the empty-first-name case returns justlastName. - Using
charAt(0)instead ofsubstring(0, 1). Both grab the first character in real Java, butcharAtisn't on the Quick Reference sheet —substringkeeps the solution within what's guaranteed to be there to look up.
Key Takeaways
- When a rule branches into "already correct / needs one fix / needs a different fix," check the "already correct" case first — it's usually the simplest to rule in or out.
- Any time a solution slices a
Stringby a computed length (not a fixed one), guard the slice with a length comparison first, since a too-short input can otherwise crash asubstringcall. - Building a formatted value from two stored fields inside a getter (rather than storing the formatted result itself) keeps a class's state minimal and always up to date if the fields it's built from could ever change.